ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GrapesJS Selector Manager 完全指南:CSS 选择器管理与状态系统实战

GrapesJS Selector Manager 完全指南:CSS 选择器管理与状态系统实战 GrapesJS Selector Manager 完全指南CSS 选择器管理与状态系统实战【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs导读Selector Manager选择器管理器是 GrapesJS 中负责统一管理 CSS 选择器类名、ID、标签与状态如hover、active的核心模块它同时服务于 CSS Composer规则编写与组件class 属性两个子系统是实现一处定义类名、多处复用样式、按状态精准命中的关键。阅读本文后你将掌握 Selector 的三种类型与唯一性机制、模块的完整 API增删改查、重命名、状态切换、批量选择、componentFirst组件优先模式以及如何通过事件系统与自定义渲染深度定制这个模块。Selector 的核心概念一套实体两处使用在 GrapesJS 中Selector 是同时存在于两个场景中的同一实体CSS Composer 的规则Rule与组件的 class 属性。官方文档用一段极简的 CSS HTML 组合来阐释这一概念span #send-btn.btn{ ... }span button idsend-btn classbtn/button /span在这段代码中可以得到三种类型的 Selector选择器类型说明spantag标签元素标签名send-btnid元素 IDbtnclass元素类名正是因为btn在规则与组件两侧指向同一个 Selector 实体因此只需维护一份类名实体就能同时做到样式规则的关联跟踪与全局重构例如重命名类时所有引用处同步更新。这一设计是 GrapesJS 中样式与结构解耦但不分裂的基础。从源码实现看Selector 集合以name_type作为模型主键packages/core/src/selector_manager/model/Selectors.ts中的modelId进一步印证了同名同类即同一实体的唯一性约束而Selector模型将类型常量定义为TYPE_CLASS 1、TYPE_ID 2见packages/core/src/selector_manager/model/Selector.tstoString()会依据类型自动输出.btn或#send-btn形式的字符串。模块初始化与配置Selector Manager 可以通过编辑器初始化时的selectorManager配置对象来定制初始状态const editor grapesjs.init({ selectorManager: { // options } })完整的配置项定义在 packages/core/src/selector_manager/config/config.ts常用配置如下配置项类型默认值说明stylePrefixstringclm-模块样式前缀appendTostring | HTMLElement指定渲染容器CSS 选择器或 DOM 元素为空则不渲染默认 UIselectorsarray[]默认的 Selector 集合statesarray[{ name: hover }, { name: active }, { name: nth-of-type(2n) }]默认状态集合escapeName(name) string内置转义自定义选择器名称转义策略例如name name.replace( , _)selectedName({result, state, target}) string-自定义Selected区域展示的名称例如({ result }) Selected: ${result}iconAdd/iconSync/iconTagOn/iconTagOff/iconTagRemovestringSVG内置 SVG新增、同步、启用、禁用、移除标签的图标render(props) string-完全自定义 Selector Manager 的渲染函数componentFirstbooleanfalse组件优先模式见下文专节custombooleanfalse为 true 时不渲染默认 Selector Manager UI便于完全自建界面其中render是深度定制的入口。它接收{ el, labelHead, labelStates, labelInfo }返回一个新 DOM 元素或 HTML 字符串即可替换默认布局返回 HTML 字符串时可以通过data-*属性让模块识别关键节点data-states状态select下拉框的挂载点data-selectors已选 Selector 标签的挂载点data-input新增 Selector 的输入框data-add点击后触发新增的按钮data-sync-style同步样式的按钮componentFirst开启时显示data-selected当前选中目标Selected区域的输出点从源码看默认视图ClassTagsViewpackages/core/src/selector_manager/view/ClassTagsView.ts的模板正是由这些data-*节点组成的因此自定义 HTML 只需沿用同一套契约即可复用模块既有的事件绑定change [data-states]、click [data-add]、keyup [data-input]等。获取模块实例与事件系统编辑器实例化完成后通过editor.Selectors获取模块const sm editor.Selectors;Selector Manager 提供了 7 个事件枚举定义见 packages/core/src/selector_manager/types.ts覆盖新增、删除、更新、状态变化与自定义事件事件触发时机回调参数selector:addSelector 被添加selectorselector:removeSelector 被移除selectorselector:remove:beforeSelector 移除之前selectorselector:updateSelector 被更新selector, changeschanges 为变更对象selector:state状态States变化state含事件全部数据selector:custom自定义事件如渲染完成、选择变化{ states, selected, container }selector上述所有事件的汇总catch-all{ event, selector, changes, ... }使用示例// 新增 editor.on(selector:add, (selector) { ... }); // 移除前 / 移除后 editor.on(selector:remove:before, (selector) { ... }); editor.on(selector:remove, (selector) { ... }); // 更新selector 与变更对象 editor.on(selector:update, (selector, changes) { ... }); // 状态变化 editor.on(selector:state, (state) { ... }); // 自定义事件拿到状态、已选选择器与容器 editor.on(selector:custom, ({ states, selected, container }) { ... }); // 汇总事件 editor.on(selector, ({ event, selector, changes, ... }) { ... });值得说明的是selector:state事件的来源setState()底层调用的是编辑器模型的set(state, value)见 packages/core/src/editor/model/Editor.ts编辑器监听change:state后转发为selector:state事件因此状态是编辑器级的全局状态而非某个组件独有。API 方法详解模块所有公共方法均定义于 packages/core/src/selector_manager/index.ts下面按用途分组详解。查询类getConfig()—— 获取模块配置对象返回Object。get(name, type?)—— 按名称/类型获取 Selectorconst selector selectorManager.get(.my-class); // 获取 Id const selectorId selectorManager.get(#my-id);传入字符串时支持.与#前缀模块会自动剥离前缀并推断类型源码中isId/isClass判断首字符因此get(.my-class)与get(my-class)等价返回Selector | null找不到时。getAll(opts?)—— 获取全部 Selector返回CollectionSelector源码实现支持{ array: true }选项直接得到数组该能力在index.ts的getAll中体现未出现在公开文档属内部增强。getState()—— 获取当前选择器状态值返回String未设置时为空字符串。getStates()—— 获取全部可用状态返回ArrayState。getSelected()—— 获取共同选择器基于所有当前选中组件计算它们共有的 Selector。例如同时选中两个组件若它们都带有btn类则返回结果中包含btnconst selected selectorManager.getSelected(); console.log(selected.map(s s.toString()))底层通过__getCommonSelectors与__common对多个组件的 Selector 集合做交集运算见index.ts这是多选组件批量改样式场景的核心支撑。getSelectedAll()—— 获取当前已选的全部 Selector不做交集过滤const selected selectorManager.getSelectedAll(); console.log(selected.map(s s.toString()))getSelectedTargets()—— 获取当前被选中的目标数组。目标可能是Component或CssRule即 StyleManager 当前可被样式化的对象const targetsToStyle selectorManager.getSelectedTargets(); console.log(targetsToStyle.map(target target.getSelectorsString()))该方法的实现直接委托给em.Styles.getSelectedAll()返回的均为实现了样式接口StyleableModel的模型。相关类型可参考 docs/api/component.md 与 docs/api/css_rule.md。增删改类add(props, opts?)—— 添加新 Selector如果已存在则直接返回现有实例保证实体唯一性。支持传入属性对象或字符串标识const selector selectorManager.add({ name: my-class, label: My class }); console.log(selector.toString()) // .my-class // 等价写法 const selector selectorManager.add(.my-class); console.log(selector.toString()) // .my-class传入字符串时若以.或#开头会自动转换为对应类型的 Selector若只提供label未提供name源码会自动用escapeName(label)生成名称。内部还保留了数组批量添加与addClass的便捷能力源码中add对数组做了兼容addClass(c1 c2)会拆分空白并逐个创建均标注为内部使用。remove(selector, opts?)—— 移除 Selector支持实例或字符串标识返回被移除的 Selectorconst removed selectorManager.remove(.myclass); // 或传入 Selector 实例 selectorManager.remove(selectorManager.get(.myclass));rename(selector, name, opts?)—— 重命名 Selector。若新名称已被占用则返回已存在的同名实例不会覆盖否则更新并返回原 Selectorconst selector selectorManager.get(myclass); const result selectorManager.rename(selector, myclass2); console.log(result selector ? Selector updated : Selector with this name exists already);源码中rename先通过escapeName转义新名称再调用get(newName)做存在性检查——这正是同名类不重复创建约束的体现。状态类setState(value)—— 切换选择器状态如hover返回this支持链式调用selectorManager.setState(hover);setStates(states, opts?)—— 整体替换状态集合返回新的ArrayStateconst states selectorManager.setStates([ { name: hover, label: Hover }, { name: nth-of-type(2n), label: Even/Odd } ]);底层会为每个状态对象创建State模型并 reset 集合见 packages/core/src/selector_manager/model/State.ts。State只有name与label两个属性提供getName()与getLabel()未提供 label 时回退为 name。选择操作类addSelected(props)—— 为所有当前选中的组件添加同一个 Selector先 add 保证唯一再逐个写入组件的 Selector 集合selectorManager.addSelected(.new-class);removeSelected(selector)—— 从所有选中组件上移除共同的 Selector受protected属性保护的选择器不会被移除selectorManager.removeSelected(.myclass);组件优先模式componentFirstsetComponentFirst(value)/getComponentFirst()—— 更新/读取组件优先选项selectorManager.setComponentFirst(true); const isComponentFirst selectorManager.getComponentFirst(); // truecomponentFirst是 Selector Manager 最重要的行为开关之一。默认情况下在画布中选中一个带有类的组件时传入 StyleManager 的目标是该类的 CSS Rule因此样式修改会影响所有使用该类的组件即使它们位于画布可见区域之外也可能被意外改动。开启componentFirst后即使组件已有关类StyleManager 拿到的目标仍是组件本身以组件 ID 规则形式样式改动只作用于当前组件界面 Selected 区域会优先展示组件 ID如#cmp-1而非类名当组件带类且又有内联/ID 样式时Selector Manager 工具栏会出现同步样式sync style按钮可将组件样式合并回类规则。从源码看setComponentFirst在更新配置的同时设置内部模型cFirst属性并触发selector:type事件视图层的checkSync逻辑ClassTagsView会根据config.componentFirst与组件是否存在样式来决定是否显示同步按钮。默认视图的同步操作会把各组件 ID 规则中的样式取出、清空再写入类规则syncStyle方法从而完成组件样式 → 类样式的合并。Selector 模型属性与实例方法通过add()等方法拿到的每个 Selector 实例其属性与实例方法定义于 docs/api/selector.md 与 packages/core/src/selector_manager/model/Selector.ts。属性属性类型说明namestring选择器名称如my-classlabelstring显示标签如My Classtypenumber类型1class| 2idactiveboolean是否可被 Style Manager 选中编辑为 false 时不可选但仍参与匹配privateboolean为 true 时对 Style Manager 不可见但仍会渲染到画布与导出代码中protectedboolean为 true 时无法从所属组件上被移除其中name是模型的主键idAttribute name且创建时若name为空会用label填充反之亦然名称还会经过escapeName处理内置策略为去掉首尾空白并将连续空白替换为-即my class→my-class。实例方法toString()—— 将 Selector 序列化为 CSS 字符串按类型自动添加前缀// 给定 { name: my-selector, type: 2 } console.log(selector.toString()); // - #my-selectorgetName()/getLabel()—— 获取名称与标签// 给定 { name: my-selector, label: My selector } console.log(selector.getName()); // - my-selector console.log(selector.getLabel()); // - My selectorsetLabel(label)—— 更新标签// 给定 { name: my-selector, label: My selector } selector.setLabel(New Label) console.log(selector.getLabel()); // - New LabelgetActive()/setActive(value)—— 读取与更新 active 状态。active直接决定该 Selector 是否出现在 Style Manager 的可编辑类列表中配合private属性不可见但生效可以实现隐藏式类名如仅供 JS 钩子使用。此外集合层面Selectors.getFullString()/getFullName()可以将一组 Selector 拼成完整字符串如.btn.btn-primary并支持sort、combination生成组合形式等选项这些是 CssRule 组装与导出代码时的底层工具见 packages/core/src/selector_manager/model/Selectors.ts。实战组合一个完整的样式管理流程综合以上 API一个典型的批量组件加类 状态样式流程如下const sm editor.Selectors; // 1. 选中画布中的多个组件伪代码示意 // editor.select(components); // 2. 为所有选中组件添加公共类 sm.addSelected(.featured); // 3. 取出公共选择器确认类已生效 console.log(sm.getSelected().map(s s.toString())); // [.featured] // 4. 为公共类切换 hover 状态 sm.setState(hover); // 5. 自定义状态集合 sm.setStates([ { name: hover, label: Hover }, { name: focus, label: Focus }, ]); // 6. 开启组件优先避免样式误伤其他同 class 组件 sm.setComponentFirst(true); // 7. 监听选择器增删 editor.on(selector:add, (sel) console.log(added, sel.toString())); editor.on(selector:remove, (sel) console.log(removed, sel.toString()));小结Selector Manager 通过单一实体、双处复用的设计把 CSS 类名/ID 在规则与组件之间统一起来componentFirst模式则进一步解决了类样式波及全局的痛点完整的事件体系与render自定义契约使其既能开箱即用也能深度集成进自研编辑器界面。本文涉及的所有配置与 API 均可在仓库源码中进一步查阅配置定义、模块实现、Selector 模型、默认视图以及官方文档 selector.md 与 state.md。【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表