@tmagic/editor 1.7.9 → 1.7.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/es/Editor.vue_vue_type_script_setup_true_lang.js +3 -1
  2. package/dist/es/components/CodeParams.vue_vue_type_script_setup_true_lang.js +17 -7
  3. package/dist/es/fields/DataSourceFieldSelect/FieldSelect.vue_vue_type_script_setup_true_lang.js +38 -9
  4. package/dist/es/fields/DataSourceFieldSelect/Index.vue_vue_type_script_setup_true_lang.js +3 -1
  5. package/dist/es/fields/DataSourceMethodSelect.vue_vue_type_script_setup_true_name_true_lang.js +38 -5
  6. package/dist/es/fields/DataSourceMethods.vue_vue_type_script_setup_true_lang.js +3 -2
  7. package/dist/es/fields/DisplayConds.vue_vue_type_script_setup_true_lang.js +1 -0
  8. package/dist/es/fields/StyleSetter/Index.vue_vue_type_script_setup_true_lang.js +5 -0
  9. package/dist/es/fields/StyleSetter/pro/Transform.js +5 -0
  10. package/dist/es/fields/StyleSetter/pro/Transform.vue_vue_type_script_setup_true_lang.js +54 -0
  11. package/dist/es/hooks/use-code-block-edit.js +3 -2
  12. package/dist/es/hooks/use-stage.js +5 -1
  13. package/dist/es/index.js +3 -3
  14. package/dist/es/initService.js +4 -4
  15. package/dist/es/layouts/NavMenu.vue_vue_type_script_setup_true_lang.js +2 -0
  16. package/dist/es/layouts/workspace/viewer/Stage.vue_vue_type_script_setup_true_lang.js +64 -1
  17. package/dist/es/layouts/workspace/viewer/StageOverlay.vue_vue_type_script_setup_true_lang.js +1 -5
  18. package/dist/es/services/editor.js +191 -174
  19. package/dist/es/services/history.js +1 -9
  20. package/dist/es/services/props.js +2 -0
  21. package/dist/es/services/ui.js +1 -0
  22. package/dist/es/type.js +28 -1
  23. package/dist/es/utils/dep/worker.js +1 -1
  24. package/dist/es/utils/editor-history.js +100 -0
  25. package/dist/es/utils/editor.js +178 -4
  26. package/dist/es/utils/props.js +6 -2
  27. package/dist/es/utils/undo-redo.js +7 -4
  28. package/dist/tmagic-editor.umd.cjs +768 -246
  29. package/package.json +9 -9
  30. package/src/Editor.vue +1 -0
  31. package/src/components/CodeBlockEditor.vue +1 -1
  32. package/src/components/CodeParams.vue +23 -7
  33. package/src/editorProps.ts +2 -0
  34. package/src/fields/DataSourceFieldSelect/FieldSelect.vue +39 -6
  35. package/src/fields/DataSourceFieldSelect/Index.vue +1 -0
  36. package/src/fields/DataSourceMethodSelect.vue +54 -13
  37. package/src/fields/DataSourceMethods.vue +9 -5
  38. package/src/fields/DisplayConds.vue +1 -0
  39. package/src/fields/StyleSetter/Index.vue +5 -1
  40. package/src/fields/StyleSetter/pro/Transform.vue +54 -0
  41. package/src/fields/StyleSetter/pro/index.ts +1 -0
  42. package/src/hooks/use-code-block-edit.ts +8 -4
  43. package/src/hooks/use-stage.ts +11 -4
  44. package/src/initService.ts +6 -6
  45. package/src/layouts/NavMenu.vue +2 -0
  46. package/src/layouts/workspace/viewer/Stage.vue +89 -1
  47. package/src/layouts/workspace/viewer/StageOverlay.vue +1 -6
  48. package/src/services/editor.ts +231 -270
  49. package/src/services/history.ts +1 -9
  50. package/src/services/ui.ts +1 -0
  51. package/src/type.ts +67 -5
  52. package/src/utils/editor-history.ts +138 -0
  53. package/src/utils/editor.ts +249 -4
  54. package/src/utils/props.ts +5 -0
  55. package/src/utils/undo-redo.ts +7 -6
  56. package/types/index.d.ts +611 -491
@@ -1,5 +1,5 @@
1
1
  //#region packages/editor/src/utils/dep/worker.ts?worker&inline
2
- var jsContent = "(function() {\n //#region packages/schema/src/index.ts\n const NODE_CONDS_KEY = \"displayConds\";\n const NODE_DISABLE_DATA_SOURCE_KEY = \"_tmagic_node_disabled_data_source\";\n const NODE_DISABLE_CODE_BLOCK_KEY = \"_tmagic_node_disabled_code_block\";\n let HookType = /* @__PURE__ */ function(HookType) {\n /** 代码块钩子标识 */\n HookType[\"CODE\"] = \"code\";\n return HookType;\n }({});\n //#endregion\n //#region packages/utils/src/index.ts\n const isObject = (obj) => Object.prototype.toString.call(obj) === \"[object Object]\";\n const getKeysArray = (keys) => `${keys}`.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n const DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX = \"ds-field::\";\n const dataSourceTemplateRegExp = /\\$\\{([\\s\\S]+?)\\}/g;\n //#endregion\n //#region packages/dep/src/types.ts\n /** 依赖收集的目标类型 */\n let DepTargetType = /* @__PURE__ */ function(DepTargetType) {\n DepTargetType[\"DEFAULT\"] = \"default\";\n /** 代码块 */\n DepTargetType[\"CODE_BLOCK\"] = \"code-block\";\n /** 数据源 */\n DepTargetType[\"DATA_SOURCE\"] = \"data-source\";\n /** 数据源方法 */\n DepTargetType[\"DATA_SOURCE_METHOD\"] = \"data-source-method\";\n /** 数据源条件 */\n DepTargetType[\"DATA_SOURCE_COND\"] = \"data-source-cond\";\n return DepTargetType;\n }({});\n //#endregion\n //#region packages/dep/src/Target.ts\n /**\n * 需要收集依赖的目标\n * 例如:一个代码块可以为一个目标\n */\n var Target = class {\n /**\n * 如何识别目标\n */\n isTarget;\n /**\n * 目标id,不可重复\n * 例如目标是代码块,则为代码块id\n */\n id;\n /**\n * 目标名称,用于显示在依赖列表中\n */\n name;\n /**\n * 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type\n */\n type = DepTargetType.DEFAULT;\n /**\n * 依赖详情\n * 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } }\n */\n deps = {};\n /**\n * 是否默认收集,默认为true,当值为false时需要传入type参数给collect方法才会被收集\n */\n isCollectByDefault;\n constructor(options) {\n this.isTarget = options.isTarget;\n this.id = options.id;\n this.name = options.name;\n this.isCollectByDefault = options.isCollectByDefault ?? true;\n if (options.type) this.type = options.type;\n if (options.initialDeps) this.deps = options.initialDeps;\n }\n /**\n * 更新依赖\n * @param option 节点配置\n * @param key 哪个key配置了这个目标的id\n */\n updateDep({ id, name, key, data }) {\n const dep = this.deps[id] || {\n name,\n keys: []\n };\n dep.name = name;\n dep.data = data;\n this.deps[id] = dep;\n if (!dep.keys.includes(key)) dep.keys.push(key);\n }\n /**\n * 删除依赖\n * @param node 哪个节点的依赖需要移除,如果为空,则移除所有依赖\n * @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key\n * @returns void\n */\n removeDep(id, key) {\n if (typeof id === \"undefined\") {\n Object.keys(this.deps).forEach((depKey) => {\n delete this.deps[depKey];\n });\n return;\n }\n const dep = this.deps[id];\n if (!dep) return;\n if (key) {\n const index = dep.keys.indexOf(key);\n dep.keys.splice(index, 1);\n if (dep.keys.length === 0) delete this.deps[id];\n } else delete this.deps[id];\n }\n /**\n * 判断指定节点下的指定key是否存在在依赖列表中\n * @param node 哪个节点\n * @param key 哪个key\n * @returns boolean\n */\n hasDep(id, key) {\n return this.deps[id]?.keys.includes(key) ?? false;\n }\n destroy() {\n this.deps = {};\n }\n };\n //#endregion\n //#region packages/dep/src/utils.ts\n const INTEGER_REGEXP = /^\\d+$/;\n const createCodeBlockTarget = (id, codeBlock, initialDeps = {}) => new Target({\n type: DepTargetType.CODE_BLOCK,\n id,\n initialDeps,\n name: codeBlock.name,\n isTarget: (_key, value) => {\n if (id === value) return true;\n if (value?.hookType === HookType.CODE && Array.isArray(value.hookData)) return value.hookData.some((item) => item.codeId === id);\n return false;\n }\n });\n /**\n * ['array'] ['array', '0'] ['array', '0', 'a'] 这种返回false\n * ['array', 'a'] 这种返回true\n * @param keys\n * @param fields\n * @returns boolean\n */\n const isIncludeArrayField = (keys, fields) => {\n let f = fields;\n return keys.some((key, index) => {\n const field = f.find(({ name }) => name === key);\n f = field?.fields || [];\n return field?.type === \"array\" && index < keys.length - 1 && !INTEGER_REGEXP.test(keys[index + 1]);\n });\n };\n /**\n * 判断模板(value)是不是使用数据源Id(dsId),如:`xxx${dsId.field}xxx${dsId.field}`\n * @param value any\n * @param dsId string | number\n * @param hasArray boolean true: 一定要包含有需要迭代的模板; false: 一定要包含普通模板;\n * @returns boolean\n */\n const isDataSourceTemplate = (value, ds, hasArray = false) => {\n const templates = value.match(dataSourceTemplateRegExp) || [];\n if (templates.length <= 0) return false;\n for (const tpl of templates) {\n const keys = getKeysArray(tpl.substring(2, tpl.length - 1));\n const dsId = keys.shift();\n if (!dsId || dsId !== ds.id) continue;\n if (hasArray === isIncludeArrayField(keys, ds.fields)) return true;\n }\n return false;\n };\n /**\n * 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}\n * @param value any\n * @param dsId string | number\n * @returns boolean\n */\n const isSpecificDataSourceTemplate = (value, dsId) => value?.isBindDataSourceField && value.dataSourceId && value.dataSourceId === dsId && typeof value.template === \"string\";\n /**\n * 关联数据源字段,格式为 [前缀+数据源ID, 字段名]\n * 使用data-source-field-select value: 'value' 可以配置出来\n * @param value any[]\n * @param id string | number\n * @returns boolean\n */\n const isUseDataSourceField = (value, id) => {\n if (!Array.isArray(value) || typeof value[0] !== \"string\") return false;\n const [prefixId] = value;\n const prefixIndex = prefixId.indexOf(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);\n if (prefixIndex === -1) return false;\n return prefixId.substring(prefixIndex + DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX.length) === id;\n };\n const isDataSourceTarget = (ds, key, value, hasArray = false) => {\n if (!value) return false;\n const valueType = typeof value;\n if (valueType !== \"string\" && valueType !== \"object\") return false;\n if (`${key}`.startsWith(\"displayConds\")) return false;\n if (valueType === \"string\") return isDataSourceTemplate(value, ds, hasArray);\n if (isObject(value) && value.isBindDataSource && value.dataSourceId === ds.id) return true;\n if (isSpecificDataSourceTemplate(value, ds.id)) return true;\n if (isUseDataSourceField(value, ds.id)) {\n const [, ...keys] = value;\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const isDataSourceCondTarget = (ds, key, value, hasArray = false) => {\n if (!Array.isArray(value) || !ds) return false;\n const [dsId, ...keys] = value;\n if (dsId !== ds.id || !`${key}`.startsWith(\"displayConds\")) return false;\n if (ds.fields?.some((field) => field.name === keys[0])) {\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const createDataSourceTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE,\n id: ds.id,\n initialDeps,\n isTarget: (key, value) => isDataSourceTarget(ds, key, value)\n });\n const createDataSourceCondTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_COND,\n id: ds.id,\n initialDeps,\n isTarget: (key, value) => isDataSourceCondTarget(ds, key, value)\n });\n const createDataSourceMethodTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_METHOD,\n id: ds.id,\n initialDeps,\n isTarget: (_key, value) => {\n if (!Array.isArray(value)) return false;\n const [dsId, methodName] = value;\n if (!methodName || dsId !== ds.id) return false;\n if (ds.methods?.some((method) => method.name === methodName)) return true;\n if (ds.fields?.some((field) => field.name === methodName)) return false;\n return true;\n }\n });\n const traverseTarget = (targetsList, cb, type) => {\n if (type) {\n const targets = targetsList[type];\n if (targets) for (const target of Object.values(targets)) cb(target);\n return;\n }\n for (const targets of Object.values(targetsList)) for (const target of Object.values(targets)) cb(target);\n };\n //#endregion\n //#region packages/dep/src/Watcher.ts\n const DATA_SOURCE_TARGET_TYPES = new Set([\n DepTargetType.DATA_SOURCE,\n DepTargetType.DATA_SOURCE_COND,\n DepTargetType.DATA_SOURCE_METHOD\n ]);\n var Watcher = class {\n targetsList = {};\n childrenProp = \"items\";\n idProp = \"id\";\n nameProp = \"name\";\n constructor(options) {\n if (options?.initialTargets) this.targetsList = options.initialTargets;\n if (options?.childrenProp) this.childrenProp = options.childrenProp;\n }\n getTargetsList() {\n return this.targetsList;\n }\n /**\n * 获取指定类型中的所有target\n * @param type 分类\n * @returns Target[]\n */\n getTargets(type = DepTargetType.DEFAULT) {\n return this.targetsList[type] || {};\n }\n /**\n * 添加新的目标\n * @param target Target\n */\n addTarget(target) {\n const targets = this.getTargets(target.type) || {};\n this.targetsList[target.type] = targets;\n targets[target.id] = target;\n }\n /**\n * 获取指定id的target\n * @param id target id\n * @returns Target\n */\n getTarget(id, type = DepTargetType.DEFAULT) {\n return this.getTargets(type)[id];\n }\n /**\n * 判断是否存在指定id的target\n * @param id target id\n * @returns boolean\n */\n hasTarget(id, type = DepTargetType.DEFAULT) {\n return Boolean(this.getTarget(id, type));\n }\n /**\n * 判断是否存在指定类型的target\n * @param type target type\n * @returns boolean\n */\n hasSpecifiedTypeTarget(type = DepTargetType.DEFAULT) {\n return Object.keys(this.getTargets(type)).length > 0;\n }\n /**\n * 删除指定id的target\n * @param id target id\n */\n removeTarget(id, type = DepTargetType.DEFAULT) {\n const targets = this.getTargets(type);\n if (targets[id]) {\n targets[id].destroy();\n delete targets[id];\n }\n }\n /**\n * 删除指定分类的所有target\n * @param type 分类\n * @returns void\n */\n removeTargets(type = DepTargetType.DEFAULT) {\n const targets = this.targetsList[type];\n if (!targets) return;\n for (const target of Object.values(targets)) target.destroy();\n delete this.targetsList[type];\n }\n /**\n * 删除所有target\n */\n clearTargets() {\n for (const key of Object.keys(this.targetsList)) delete this.targetsList[key];\n }\n /**\n * 收集依赖\n * @param nodes 需要收集的节点\n * @param deep 是否需要收集子节点\n * @param type 强制收集指定类型的依赖\n */\n collect(nodes, depExtendedData = {}, deep = false, type) {\n this.collectByCallback(nodes, type, ({ node, target }) => {\n this.removeTargetDep(target, node);\n this.collectItem(node, target, depExtendedData, deep);\n });\n }\n collectByCallback(nodes, type, cb) {\n traverseTarget(this.targetsList, (target) => {\n if (!type && !target.isCollectByDefault) return;\n for (const node of nodes) cb({\n node,\n target\n });\n }, type);\n }\n /**\n * 清除所有目标的依赖\n * @param nodes 需要清除依赖的节点\n */\n clear(nodes, type) {\n let { targetsList } = this;\n if (type) targetsList = { [type]: this.getTargets(type) };\n const clearedItemsNodeIds = /* @__PURE__ */ new Set();\n traverseTarget(targetsList, (target) => {\n if (nodes) for (const node of nodes) {\n target.removeDep(node[this.idProp]);\n if (Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length && !clearedItemsNodeIds.has(node[this.idProp])) {\n clearedItemsNodeIds.add(node[this.idProp]);\n this.clear(node[this.childrenProp]);\n }\n }\n else target.removeDep();\n });\n }\n /**\n * 清除指定类型的依赖\n * @param type 类型\n * @param nodes 需要清除依赖的节点\n */\n clearByType(type, nodes) {\n this.clear(nodes, type);\n }\n collectItem(node, target, depExtendedData = {}, deep = false) {\n if (node[\"_tmagic_node_disabled_data_source\"] && DATA_SOURCE_TARGET_TYPES.has(target.type)) return;\n if (node[\"_tmagic_node_disabled_code_block\"] && target.type === DepTargetType.CODE_BLOCK) return;\n const collectTarget = (config, prop = \"\") => {\n const doCollect = (key, value) => {\n const keyIsItems = key === this.childrenProp;\n const fullKey = prop ? `${prop}.${key}` : key;\n if (target.isTarget(fullKey, value)) target.updateDep({\n id: node[this.idProp],\n name: `${node[this.nameProp] || node[this.idProp]}`,\n data: depExtendedData,\n key: fullKey\n });\n else if (!keyIsItems && Array.isArray(value)) for (let i = 0, l = value.length; i < l; i++) {\n const item = value[i];\n if (isObject(item)) collectTarget(item, `${fullKey}[${i}]`);\n }\n else if (isObject(value)) collectTarget(value, fullKey);\n if (keyIsItems && deep && Array.isArray(value)) for (const child of value) this.collectItem(child, target, depExtendedData, deep);\n };\n for (const [key, value] of Object.entries(config)) {\n if (typeof value === \"undefined\" || value === \"\") continue;\n doCollect(key, value);\n }\n };\n collectTarget(node);\n }\n removeTargetDep(target, node, key) {\n target.removeDep(node[this.idProp], key);\n if (typeof key === \"undefined\" && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) for (const item of node[this.childrenProp]) this.removeTargetDep(target, item, key);\n }\n };\n //#endregion\n //#region packages/editor/src/utils/logger.ts\n const error = (...args) => {};\n //#endregion\n //#region packages/editor/src/utils/dep/worker.ts\n onmessage = (e) => {\n const watcher = new Watcher({ initialTargets: {} });\n const { dsl } = e.data;\n try {\n const mApp = eval(`(${dsl})`);\n if (!mApp) postMessage({});\n watcher.clearTargets();\n if (mApp.codeBlocks) for (const [id, code] of Object.entries(mApp.codeBlocks)) watcher.addTarget(createCodeBlockTarget(id, code));\n if (mApp.dataSources) for (const ds of mApp.dataSources) {\n watcher.addTarget(createDataSourceTarget(ds, {}));\n watcher.addTarget(createDataSourceMethodTarget(ds, {}));\n watcher.addTarget(createDataSourceCondTarget(ds, {}));\n }\n watcher.collectByCallback(mApp.items, void 0, ({ node, target }) => {\n watcher.collectItem(node, target, { pageId: node.id }, true);\n });\n const data = {\n [DepTargetType.DATA_SOURCE]: {},\n [DepTargetType.DATA_SOURCE_METHOD]: {},\n [DepTargetType.DATA_SOURCE_COND]: {},\n [DepTargetType.CODE_BLOCK]: {}\n };\n traverseTarget(watcher.getTargetsList(), (target) => {\n data[target.type][target.id] = target.deps;\n });\n postMessage(data);\n } catch (e) {\n postMessage({});\n }\n };\n //#endregion\n})();\n";
2
+ var jsContent = "(function() {\n //#region packages/schema/src/index.ts\n const NODE_CONDS_KEY = \"displayConds\";\n const NODE_DISABLE_DATA_SOURCE_KEY = \"_tmagic_node_disabled_data_source\";\n const NODE_DISABLE_CODE_BLOCK_KEY = \"_tmagic_node_disabled_code_block\";\n let HookType = /* @__PURE__ */ function(HookType) {\n /** 代码块钩子标识 */\n HookType[\"CODE\"] = \"code\";\n return HookType;\n }({});\n //#endregion\n //#region packages/utils/src/const.ts\n const DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX = \"ds-field::\";\n //#endregion\n //#region packages/utils/src/index.ts\n const isObject = (obj) => Object.prototype.toString.call(obj) === \"[object Object]\";\n const getKeysArray = (keys) => `${keys}`.replace(/\\[(\\d+)\\]/g, \".$1\").split(\".\");\n const dataSourceTemplateRegExp = /\\$\\{([\\s\\S]+?)\\}/g;\n //#endregion\n //#region packages/dep/src/types.ts\n /** 依赖收集的目标类型 */\n let DepTargetType = /* @__PURE__ */ function(DepTargetType) {\n DepTargetType[\"DEFAULT\"] = \"default\";\n /** 代码块 */\n DepTargetType[\"CODE_BLOCK\"] = \"code-block\";\n /** 数据源 */\n DepTargetType[\"DATA_SOURCE\"] = \"data-source\";\n /** 数据源方法 */\n DepTargetType[\"DATA_SOURCE_METHOD\"] = \"data-source-method\";\n /** 数据源条件 */\n DepTargetType[\"DATA_SOURCE_COND\"] = \"data-source-cond\";\n return DepTargetType;\n }({});\n //#endregion\n //#region packages/dep/src/Target.ts\n /**\n * 需要收集依赖的目标\n * 例如:一个代码块可以为一个目标\n */\n var Target = class {\n /**\n * 如何识别目标\n */\n isTarget;\n /**\n * 目标id,不可重复\n * 例如目标是代码块,则为代码块id\n */\n id;\n /**\n * 目标名称,用于显示在依赖列表中\n */\n name;\n /**\n * 不同的目标可以进行分类,例如代码块,数据源可以为两个不同的type\n */\n type = DepTargetType.DEFAULT;\n /**\n * 依赖详情\n * 实例:{ 'node_id': { name: 'node_name', keys: [ created, mounted ] } }\n */\n deps = {};\n /**\n * 是否默认收集,默认为true,当值为false时需要传入type参数给collect方法才会被收集\n */\n isCollectByDefault;\n constructor(options) {\n this.isTarget = options.isTarget;\n this.id = options.id;\n this.name = options.name;\n this.isCollectByDefault = options.isCollectByDefault ?? true;\n if (options.type) this.type = options.type;\n if (options.initialDeps) this.deps = options.initialDeps;\n }\n /**\n * 更新依赖\n * @param option 节点配置\n * @param key 哪个key配置了这个目标的id\n */\n updateDep({ id, name, key, data }) {\n const dep = this.deps[id] || {\n name,\n keys: []\n };\n dep.name = name;\n dep.data = data;\n this.deps[id] = dep;\n if (!dep.keys.includes(key)) dep.keys.push(key);\n }\n /**\n * 删除依赖\n * @param node 哪个节点的依赖需要移除,如果为空,则移除所有依赖\n * @param key 节点下哪个key需要移除,如果为空,则移除改节点下的所有依赖key\n * @returns void\n */\n removeDep(id, key) {\n if (typeof id === \"undefined\") {\n Object.keys(this.deps).forEach((depKey) => {\n delete this.deps[depKey];\n });\n return;\n }\n const dep = this.deps[id];\n if (!dep) return;\n if (key) {\n const index = dep.keys.indexOf(key);\n dep.keys.splice(index, 1);\n if (dep.keys.length === 0) delete this.deps[id];\n } else delete this.deps[id];\n }\n /**\n * 判断指定节点下的指定key是否存在在依赖列表中\n * @param node 哪个节点\n * @param key 哪个key\n * @returns boolean\n */\n hasDep(id, key) {\n return this.deps[id]?.keys.includes(key) ?? false;\n }\n destroy() {\n this.deps = {};\n }\n };\n //#endregion\n //#region packages/dep/src/utils.ts\n const INTEGER_REGEXP = /^\\d+$/;\n const createCodeBlockTarget = (id, codeBlock, initialDeps = {}) => new Target({\n type: DepTargetType.CODE_BLOCK,\n id,\n initialDeps,\n name: codeBlock.name,\n isTarget: (_key, value) => {\n if (id === value) return true;\n if (value?.hookType === HookType.CODE && Array.isArray(value.hookData)) return value.hookData.some((item) => item.codeId === id);\n return false;\n }\n });\n /**\n * ['array'] ['array', '0'] ['array', '0', 'a'] 这种返回false\n * ['array', 'a'] 这种返回true\n * @param keys\n * @param fields\n * @returns boolean\n */\n const isIncludeArrayField = (keys, fields) => {\n let f = fields;\n return keys.some((key, index) => {\n const field = f.find(({ name }) => name === key);\n f = field?.fields || [];\n return field?.type === \"array\" && index < keys.length - 1 && !INTEGER_REGEXP.test(keys[index + 1]);\n });\n };\n /**\n * 判断模板(value)是不是使用数据源Id(dsId),如:`xxx${dsId.field}xxx${dsId.field}`\n * @param value any\n * @param dsId string | number\n * @param hasArray boolean true: 一定要包含有需要迭代的模板; false: 一定要包含普通模板;\n * @returns boolean\n */\n const isDataSourceTemplate = (value, ds, hasArray = false) => {\n const templates = value.match(dataSourceTemplateRegExp) || [];\n if (templates.length <= 0) return false;\n for (const tpl of templates) {\n const keys = getKeysArray(tpl.substring(2, tpl.length - 1));\n const dsId = keys.shift();\n if (!dsId || dsId !== ds.id) continue;\n if (hasArray === isIncludeArrayField(keys, ds.fields)) return true;\n }\n return false;\n };\n /**\n * 指定数据源的字符串模板,如:{ isBindDataSourceField: true, dataSourceId: 'id', template: `xxx${field}xxx`}\n * @param value any\n * @param dsId string | number\n * @returns boolean\n */\n const isSpecificDataSourceTemplate = (value, dsId) => value?.isBindDataSourceField && value.dataSourceId && value.dataSourceId === dsId && typeof value.template === \"string\";\n /**\n * 关联数据源字段,格式为 [前缀+数据源ID, 字段名]\n * 使用data-source-field-select value: 'value' 可以配置出来\n * @param value any[]\n * @param id string | number\n * @returns boolean\n */\n const isUseDataSourceField = (value, id) => {\n if (!Array.isArray(value) || typeof value[0] !== \"string\") return false;\n const [prefixId] = value;\n const prefixIndex = prefixId.indexOf(DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX);\n if (prefixIndex === -1) return false;\n return prefixId.substring(prefixIndex + DATA_SOURCE_FIELDS_SELECT_VALUE_PREFIX.length) === id;\n };\n const isDataSourceTarget = (ds, key, value, hasArray = false) => {\n if (!value) return false;\n const valueType = typeof value;\n if (valueType !== \"string\" && valueType !== \"object\") return false;\n if (`${key}`.startsWith(\"displayConds\")) return false;\n if (valueType === \"string\") return isDataSourceTemplate(value, ds, hasArray);\n if (isObject(value) && value.isBindDataSource && value.dataSourceId === ds.id) return true;\n if (isSpecificDataSourceTemplate(value, ds.id)) return true;\n if (isUseDataSourceField(value, ds.id)) {\n const [, ...keys] = value;\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const isDataSourceCondTarget = (ds, key, value, hasArray = false) => {\n if (!Array.isArray(value) || !ds) return false;\n const [dsId, ...keys] = value;\n if (dsId !== ds.id || !`${key}`.startsWith(\"displayConds\")) return false;\n if (ds.fields?.some((field) => field.name === keys[0])) {\n const includeArray = isIncludeArrayField(keys, ds.fields);\n return hasArray ? includeArray : !includeArray;\n }\n return false;\n };\n const createDataSourceTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE,\n id: ds.id,\n initialDeps,\n isTarget: (key, value) => isDataSourceTarget(ds, key, value)\n });\n const createDataSourceCondTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_COND,\n id: ds.id,\n initialDeps,\n isTarget: (key, value) => isDataSourceCondTarget(ds, key, value)\n });\n const createDataSourceMethodTarget = (ds, initialDeps = {}) => new Target({\n type: DepTargetType.DATA_SOURCE_METHOD,\n id: ds.id,\n initialDeps,\n isTarget: (_key, value) => {\n if (!Array.isArray(value)) return false;\n const [dsId, methodName] = value;\n if (!methodName || dsId !== ds.id) return false;\n if (ds.methods?.some((method) => method.name === methodName)) return true;\n if (ds.fields?.some((field) => field.name === methodName)) return false;\n return true;\n }\n });\n const traverseTarget = (targetsList, cb, type) => {\n if (type) {\n const targets = targetsList[type];\n if (targets) for (const target of Object.values(targets)) cb(target);\n return;\n }\n for (const targets of Object.values(targetsList)) for (const target of Object.values(targets)) cb(target);\n };\n //#endregion\n //#region packages/dep/src/Watcher.ts\n const DATA_SOURCE_TARGET_TYPES = new Set([\n DepTargetType.DATA_SOURCE,\n DepTargetType.DATA_SOURCE_COND,\n DepTargetType.DATA_SOURCE_METHOD\n ]);\n var Watcher = class {\n targetsList = {};\n childrenProp = \"items\";\n idProp = \"id\";\n nameProp = \"name\";\n constructor(options) {\n if (options?.initialTargets) this.targetsList = options.initialTargets;\n if (options?.childrenProp) this.childrenProp = options.childrenProp;\n }\n getTargetsList() {\n return this.targetsList;\n }\n /**\n * 获取指定类型中的所有target\n * @param type 分类\n * @returns Target[]\n */\n getTargets(type = DepTargetType.DEFAULT) {\n return this.targetsList[type] || {};\n }\n /**\n * 添加新的目标\n * @param target Target\n */\n addTarget(target) {\n const targets = this.getTargets(target.type) || {};\n this.targetsList[target.type] = targets;\n targets[target.id] = target;\n }\n /**\n * 获取指定id的target\n * @param id target id\n * @returns Target\n */\n getTarget(id, type = DepTargetType.DEFAULT) {\n return this.getTargets(type)[id];\n }\n /**\n * 判断是否存在指定id的target\n * @param id target id\n * @returns boolean\n */\n hasTarget(id, type = DepTargetType.DEFAULT) {\n return Boolean(this.getTarget(id, type));\n }\n /**\n * 判断是否存在指定类型的target\n * @param type target type\n * @returns boolean\n */\n hasSpecifiedTypeTarget(type = DepTargetType.DEFAULT) {\n return Object.keys(this.getTargets(type)).length > 0;\n }\n /**\n * 删除指定id的target\n * @param id target id\n */\n removeTarget(id, type = DepTargetType.DEFAULT) {\n const targets = this.getTargets(type);\n if (targets[id]) {\n targets[id].destroy();\n delete targets[id];\n }\n }\n /**\n * 删除指定分类的所有target\n * @param type 分类\n * @returns void\n */\n removeTargets(type = DepTargetType.DEFAULT) {\n const targets = this.targetsList[type];\n if (!targets) return;\n for (const target of Object.values(targets)) target.destroy();\n delete this.targetsList[type];\n }\n /**\n * 删除所有target\n */\n clearTargets() {\n for (const key of Object.keys(this.targetsList)) delete this.targetsList[key];\n }\n /**\n * 收集依赖\n * @param nodes 需要收集的节点\n * @param deep 是否需要收集子节点\n * @param type 强制收集指定类型的依赖\n */\n collect(nodes, depExtendedData = {}, deep = false, type) {\n this.collectByCallback(nodes, type, ({ node, target }) => {\n this.removeTargetDep(target, node);\n this.collectItem(node, target, depExtendedData, deep);\n });\n }\n collectByCallback(nodes, type, cb) {\n traverseTarget(this.targetsList, (target) => {\n if (!type && !target.isCollectByDefault) return;\n for (const node of nodes) cb({\n node,\n target\n });\n }, type);\n }\n /**\n * 清除所有目标的依赖\n * @param nodes 需要清除依赖的节点\n */\n clear(nodes, type) {\n let { targetsList } = this;\n if (type) targetsList = { [type]: this.getTargets(type) };\n const clearedItemsNodeIds = /* @__PURE__ */ new Set();\n traverseTarget(targetsList, (target) => {\n if (nodes) for (const node of nodes) {\n target.removeDep(node[this.idProp]);\n if (Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length && !clearedItemsNodeIds.has(node[this.idProp])) {\n clearedItemsNodeIds.add(node[this.idProp]);\n this.clear(node[this.childrenProp]);\n }\n }\n else target.removeDep();\n });\n }\n /**\n * 清除指定类型的依赖\n * @param type 类型\n * @param nodes 需要清除依赖的节点\n */\n clearByType(type, nodes) {\n this.clear(nodes, type);\n }\n collectItem(node, target, depExtendedData = {}, deep = false) {\n if (node[\"_tmagic_node_disabled_data_source\"] && DATA_SOURCE_TARGET_TYPES.has(target.type)) return;\n if (node[\"_tmagic_node_disabled_code_block\"] && target.type === DepTargetType.CODE_BLOCK) return;\n const collectTarget = (config, prop = \"\") => {\n const doCollect = (key, value) => {\n const keyIsItems = key === this.childrenProp;\n const fullKey = prop ? `${prop}.${key}` : key;\n if (target.isTarget(fullKey, value)) target.updateDep({\n id: node[this.idProp],\n name: `${node[this.nameProp] || node[this.idProp]}`,\n data: depExtendedData,\n key: fullKey\n });\n else if (!keyIsItems && Array.isArray(value)) for (let i = 0, l = value.length; i < l; i++) {\n const item = value[i];\n if (isObject(item)) collectTarget(item, `${fullKey}[${i}]`);\n }\n else if (isObject(value)) collectTarget(value, fullKey);\n if (keyIsItems && deep && Array.isArray(value)) for (const child of value) this.collectItem(child, target, depExtendedData, deep);\n };\n for (const [key, value] of Object.entries(config)) {\n if (typeof value === \"undefined\" || value === \"\") continue;\n doCollect(key, value);\n }\n };\n collectTarget(node);\n }\n removeTargetDep(target, node, key) {\n target.removeDep(node[this.idProp], key);\n if (typeof key === \"undefined\" && Array.isArray(node[this.childrenProp]) && node[this.childrenProp].length) for (const item of node[this.childrenProp]) this.removeTargetDep(target, item, key);\n }\n };\n //#endregion\n //#region packages/editor/src/utils/logger.ts\n const error = (...args) => {\n console.error(\"magic editor: \", ...args);\n };\n //#endregion\n //#region packages/editor/src/utils/dep/worker.ts\n onmessage = (e) => {\n const watcher = new Watcher({ initialTargets: {} });\n const { dsl } = e.data;\n try {\n const mApp = eval(`(${dsl})`);\n if (!mApp) postMessage({});\n watcher.clearTargets();\n if (mApp.codeBlocks) for (const [id, code] of Object.entries(mApp.codeBlocks)) watcher.addTarget(createCodeBlockTarget(id, code));\n if (mApp.dataSources) for (const ds of mApp.dataSources) {\n watcher.addTarget(createDataSourceTarget(ds, {}));\n watcher.addTarget(createDataSourceMethodTarget(ds, {}));\n watcher.addTarget(createDataSourceCondTarget(ds, {}));\n }\n watcher.collectByCallback(mApp.items, void 0, ({ node, target }) => {\n watcher.collectItem(node, target, { pageId: node.id }, true);\n });\n const data = {\n [DepTargetType.DATA_SOURCE]: {},\n [DepTargetType.DATA_SOURCE_METHOD]: {},\n [DepTargetType.DATA_SOURCE_COND]: {},\n [DepTargetType.CODE_BLOCK]: {}\n };\n traverseTarget(watcher.getTargetsList(), (target) => {\n data[target.type][target.id] = target.deps;\n });\n postMessage(data);\n } catch (e) {\n error(e);\n postMessage({});\n }\n };\n //#endregion\n})();\n";
3
3
  var blob = typeof self !== "undefined" && self.Blob && new Blob(["(self.URL || self.webkitURL).revokeObjectURL(self.location.href);", jsContent], { type: "text/javascript;charset=utf-8" });
4
4
  function WorkerWrapper(options) {
5
5
  let objURL;
@@ -0,0 +1,100 @@
1
+ import { getNodeIndex } from "./editor.js";
2
+ import { isPage, isPageFragment } from "@tmagic/utils";
3
+ import { NodeType } from "@tmagic/core";
4
+ import { toRaw } from "vue";
5
+ import { cloneDeep } from "lodash-es";
6
+ //#region packages/editor/src/utils/editor-history.ts
7
+ /**
8
+ * 应用 add 类型的历史操作
9
+ * reverse=true(撤销):从父节点中移除已添加的节点
10
+ * reverse=false(重做):重新添加节点到父节点中
11
+ */
12
+ async function applyHistoryAddOp(step, reverse, ctx) {
13
+ const { root, stage } = ctx;
14
+ if (reverse) for (const node of step.nodes ?? []) {
15
+ const parent = ctx.getNodeById(step.parentId, false);
16
+ if (!parent?.items) continue;
17
+ const idx = getNodeIndex(node.id, parent);
18
+ if (typeof idx === "number" && idx !== -1) parent.items.splice(idx, 1);
19
+ await stage?.remove({
20
+ id: node.id,
21
+ parentId: parent.id,
22
+ root: cloneDeep(root)
23
+ });
24
+ }
25
+ else {
26
+ const parent = ctx.getNodeById(step.parentId, false);
27
+ if (parent?.items) for (const node of step.nodes ?? []) {
28
+ const idx = step.indexMap?.[node.id] ?? parent.items.length;
29
+ parent.items.splice(idx, 0, cloneDeep(node));
30
+ await stage?.add({
31
+ config: cloneDeep(node),
32
+ parent: cloneDeep(parent),
33
+ parentId: parent.id,
34
+ root: cloneDeep(root)
35
+ });
36
+ }
37
+ }
38
+ }
39
+ /**
40
+ * 应用 remove 类型的历史操作
41
+ * reverse=true(撤销):将已删除的节点按原位置重新插入
42
+ * reverse=false(重做):再次删除节点
43
+ */
44
+ async function applyHistoryRemoveOp(step, reverse, ctx) {
45
+ const { root, stage } = ctx;
46
+ if (reverse) {
47
+ const sorted = [...step.removedItems ?? []].sort((a, b) => a.index - b.index);
48
+ for (const { node, parentId, index } of sorted) {
49
+ const parent = ctx.getNodeById(parentId, false);
50
+ if (!parent?.items) continue;
51
+ parent.items.splice(index, 0, cloneDeep(node));
52
+ await stage?.add({
53
+ config: cloneDeep(node),
54
+ parent: cloneDeep(parent),
55
+ parentId,
56
+ root: cloneDeep(root)
57
+ });
58
+ }
59
+ } else for (const { node, parentId } of step.removedItems ?? []) {
60
+ const parent = ctx.getNodeById(parentId, false);
61
+ if (!parent?.items) continue;
62
+ const idx = getNodeIndex(node.id, parent);
63
+ if (typeof idx === "number" && idx !== -1) parent.items.splice(idx, 1);
64
+ await stage?.remove({
65
+ id: node.id,
66
+ parentId,
67
+ root: cloneDeep(root)
68
+ });
69
+ }
70
+ }
71
+ /**
72
+ * 应用 update 类型的历史操作
73
+ * reverse=true(撤销):将节点恢复为 oldNode
74
+ * reverse=false(重做):将节点更新为 newNode
75
+ */
76
+ async function applyHistoryUpdateOp(step, reverse, ctx) {
77
+ const { root, stage } = ctx;
78
+ const items = step.updatedItems ?? [];
79
+ for (const { oldNode, newNode } of items) {
80
+ const config = reverse ? oldNode : newNode;
81
+ if (config.type === NodeType.ROOT) {
82
+ ctx.setRoot(cloneDeep(config));
83
+ continue;
84
+ }
85
+ const info = ctx.getNodeInfo(config.id, false);
86
+ if (!info.parent) continue;
87
+ const idx = getNodeIndex(config.id, info.parent);
88
+ if (typeof idx !== "number" || idx === -1) continue;
89
+ info.parent.items[idx] = cloneDeep(config);
90
+ if (isPage(config) || isPageFragment(config)) ctx.setPage(config);
91
+ }
92
+ const curPage = ctx.getPage();
93
+ if (stage && curPage) await stage.update({
94
+ config: cloneDeep(toRaw(curPage)),
95
+ parentId: root.id,
96
+ root: cloneDeep(toRaw(root))
97
+ });
98
+ }
99
+ //#endregion
100
+ export { applyHistoryAddOp, applyHistoryRemoveOp, applyHistoryUpdateOp };
@@ -1,7 +1,8 @@
1
- import { Layout } from "../type.js";
1
+ import { LayerOffset, Layout } from "../type.js";
2
+ import { isFixed } from "@tmagic/stage";
2
3
  import { calcValueByFontsize, getElById, getNodePath, isNumber, isPage, isPageFragment, isPop, isValueIncludeDataSource } from "@tmagic/utils";
3
- import { NODE_CONDS_KEY } from "@tmagic/core";
4
- import { isObject } from "lodash-es";
4
+ import { NODE_CONDS_KEY, Target, Watcher } from "@tmagic/core";
5
+ import { cloneDeep, get, isObject } from "lodash-es";
5
6
  import serialize from "serialize-javascript";
6
7
  import { detailedDiff } from "deep-object-diff";
7
8
  //#region packages/editor/src/utils/editor.ts
@@ -257,5 +258,178 @@ var buildChangeRecords = (value, basePath) => {
257
258
  buildChangeRecords(value, basePath);
258
259
  return changeRecords;
259
260
  };
261
+ /**
262
+ * 根据节点配置或ID解析出选中节点信息,并进行合法性校验
263
+ * @param config 节点配置或节点ID
264
+ * @param getNodeInfoFn 获取节点信息的回调函数
265
+ * @param rootId 根节点ID,用于排除根节点被选中
266
+ * @returns 选中节点的完整信息(node、parent、page)
267
+ */
268
+ var resolveSelectedNode = (config, getNodeInfoFn, rootId) => {
269
+ const id = typeof config === "string" || typeof config === "number" ? config : config.id;
270
+ if (!id) throw new Error("没有ID,无法选中");
271
+ const { node, parent, page } = getNodeInfoFn(id);
272
+ if (!node) throw new Error("获取不到组件信息");
273
+ if (node.id === rootId) throw new Error("不能选根节点");
274
+ return {
275
+ node,
276
+ parent,
277
+ page
278
+ };
279
+ };
280
+ /**
281
+ * 处理节点在 fixed 定位与其他定位之间的切换
282
+ * 当节点从非 fixed 变为 fixed 时,根据节点路径累加偏移量;反之则还原偏移量
283
+ * @param dist 更新后的节点配置(目标状态)
284
+ * @param src 更新前的节点配置(原始状态)
285
+ * @param root 根节点配置,用于计算节点路径上的偏移量
286
+ * @param getLayoutFn 获取父节点布局方式的回调函数
287
+ * @returns 处理后的节点配置(深拷贝)
288
+ */
289
+ var toggleFixedPosition = async (dist, src, root, getLayoutFn) => {
290
+ const newConfig = cloneDeep(dist);
291
+ if (!isPop(src) && newConfig.style?.position) {
292
+ if (isFixed(newConfig.style) && !isFixed(src.style || {})) newConfig.style = change2Fixed(newConfig, root);
293
+ else if (!isFixed(newConfig.style) && isFixed(src.style || {})) newConfig.style = await Fixed2Other(newConfig, root, getLayoutFn);
294
+ }
295
+ return newConfig;
296
+ };
297
+ /**
298
+ * 根据键盘移动的偏移量计算节点的新样式
299
+ * 仅对 absolute 或 fixed 定位的节点生效;优先修改 top/left,若未设置则修改 bottom/right
300
+ * @param style 节点当前样式
301
+ * @param left 水平方向偏移量(正值向右,负值向左)
302
+ * @param top 垂直方向偏移量(正值向下,负值向上)
303
+ * @returns 计算后的新样式对象,若节点不支持移动则返回 null
304
+ */
305
+ var calcMoveStyle = (style, left, top) => {
306
+ if (!style || !["absolute", "fixed"].includes(style.position)) return null;
307
+ const newStyle = { ...style };
308
+ if (top) {
309
+ if (isNumber(style.top)) {
310
+ newStyle.top = Number(style.top) + Number(top);
311
+ newStyle.bottom = "";
312
+ } else if (isNumber(style.bottom)) {
313
+ newStyle.bottom = Number(style.bottom) - Number(top);
314
+ newStyle.top = "";
315
+ }
316
+ }
317
+ if (left) {
318
+ if (isNumber(style.left)) {
319
+ newStyle.left = Number(style.left) + Number(left);
320
+ newStyle.right = "";
321
+ } else if (isNumber(style.right)) {
322
+ newStyle.right = Number(style.right) - Number(left);
323
+ newStyle.left = "";
324
+ }
325
+ }
326
+ return newStyle;
327
+ };
328
+ /**
329
+ * 计算节点水平居中对齐后的样式
330
+ * 流式布局(relative)下不做处理;优先通过 DOM 元素实际宽度计算,回退到配置中的 width 值
331
+ * @param node 需要居中的节点配置
332
+ * @param parent 父容器节点配置
333
+ * @param layout 当前布局方式
334
+ * @param doc 画布 document 对象,用于获取 DOM 元素实际宽度
335
+ * @returns 计算后的新样式对象,若不支持居中则返回 null
336
+ */
337
+ var calcAlignCenterStyle = (node, parent, layout, doc) => {
338
+ if (layout === Layout.RELATIVE || !node.style) return null;
339
+ const style = { ...node.style };
340
+ if (doc) {
341
+ const el = getElById()(doc, node.id);
342
+ const parentEl = layout === Layout.FIXED ? doc.body : el?.offsetParent;
343
+ if (parentEl && el) {
344
+ style.left = calcValueByFontsize(doc, (parentEl.clientWidth - el.clientWidth) / 2);
345
+ style.right = "";
346
+ }
347
+ } else if (parent.style && isNumber(parent.style?.width) && isNumber(node.style?.width)) {
348
+ style.left = (parent.style.width - node.style.width) / 2;
349
+ style.right = "";
350
+ }
351
+ return style;
352
+ };
353
+ /**
354
+ * 计算图层移动后的目标索引
355
+ * 流式布局与绝对定位布局的移动方向相反:流式布局中"上移"对应索引减小,绝对定位中"上移"对应索引增大
356
+ * @param currentIndex 节点当前在兄弟列表中的索引
357
+ * @param offset 移动偏移量,支持数值或 LayerOffset.TOP / LayerOffset.BOTTOM
358
+ * @param brothersLength 兄弟节点总数
359
+ * @param isRelative 是否为流式布局
360
+ * @returns 目标索引位置
361
+ */
362
+ var calcLayerTargetIndex = (currentIndex, offset, brothersLength, isRelative) => {
363
+ if (offset === LayerOffset.TOP) return isRelative ? 0 : brothersLength;
364
+ if (offset === LayerOffset.BOTTOM) return isRelative ? brothersLength : 0;
365
+ return currentIndex + (isRelative ? -offset : offset);
366
+ };
367
+ /**
368
+ * 节点配置合并策略:用于 mergeWith 的自定义回调
369
+ * - undefined 且 source 拥有该 key 时返回空字符串
370
+ * - 原来是数组而新值是对象时,使用新值
371
+ * - 新值是数组时,直接替换而非逐元素合并
372
+ */
373
+ var editorNodeMergeCustomizer = (objValue, srcValue, key, _object, source) => {
374
+ if (typeof srcValue === "undefined" && Object.hasOwn(source, key)) return "";
375
+ if (isObject(srcValue) && Array.isArray(objValue)) return srcValue;
376
+ if (Array.isArray(srcValue)) return srcValue;
377
+ };
378
+ /**
379
+ * 收集复制节点关联的依赖节点,将关联节点追加到 copyNodes 数组中
380
+ * @param copyNodes 待复制的节点列表(会被就地修改)
381
+ * @param collectorOptions 依赖收集器配置
382
+ * @param getNodeById 根据 ID 获取节点的回调函数
383
+ */
384
+ var collectRelatedNodes = (copyNodes, collectorOptions, getNodeById) => {
385
+ const customTarget = new Target({ ...collectorOptions });
386
+ const coperWatcher = new Watcher();
387
+ coperWatcher.addTarget(customTarget);
388
+ coperWatcher.collect(copyNodes, {}, true, collectorOptions.type);
389
+ Object.keys(customTarget.deps).forEach((nodeId) => {
390
+ const node = getNodeById(nodeId);
391
+ if (!node) return;
392
+ customTarget.deps[nodeId].keys.forEach((key) => {
393
+ const relateNodeId = get(node, key);
394
+ if (!copyNodes.find((n) => n.id === relateNodeId)) {
395
+ const relateNode = getNodeById(relateNodeId);
396
+ if (relateNode) copyNodes.push(relateNode);
397
+ }
398
+ });
399
+ });
400
+ };
401
+ /**
402
+ * 对拖拽的节点进行分类:同父容器内移动 vs 跨容器移动
403
+ * @param configs 被拖拽的节点列表
404
+ * @param targetParent 目标父容器
405
+ * @param getNodeInfo 获取节点信息的回调
406
+ * @returns 分类结果,包含同容器索引列表和跨容器节点列表
407
+ */
408
+ var classifyDragSources = (configs, targetParent, getNodeInfo) => {
409
+ const sameParentIndices = [];
410
+ const crossParentConfigs = [];
411
+ for (const config of configs) {
412
+ const { parent, node: curNode } = getNodeInfo(config.id, false);
413
+ if (!parent || !curNode) continue;
414
+ if (getNodePath(curNode.id, parent.items).some((node) => `${targetParent.id}` === `${node.id}`)) continue;
415
+ const index = getNodeIndex(curNode.id, parent);
416
+ if (`${parent.id}` === `${targetParent.id}`) {
417
+ if (typeof index !== "number" || index === -1) return {
418
+ sameParentIndices,
419
+ crossParentConfigs,
420
+ aborted: true
421
+ };
422
+ sameParentIndices.push(index);
423
+ } else crossParentConfigs.push({
424
+ config,
425
+ parent
426
+ });
427
+ }
428
+ return {
429
+ sameParentIndices,
430
+ crossParentConfigs,
431
+ aborted: false
432
+ };
433
+ };
260
434
  //#endregion
261
- export { COPY_CODE_STORAGE_KEY, COPY_DS_STORAGE_KEY, COPY_STORAGE_KEY, Fixed2Other, buildChangeRecords, change2Fixed, fixNodeLeft, fixNodePosition, generatePageName, generatePageNameByApp, getGuideLineFromCache, getInitPositionStyle, getNodeIndex, getPageFragmentList, getPageList, getPageNameList, getRelativeStyle, isIncludeDataSource, moveItemsInContainer, serializeConfig, setChildrenLayout, setLayout };
435
+ export { COPY_CODE_STORAGE_KEY, COPY_DS_STORAGE_KEY, COPY_STORAGE_KEY, Fixed2Other, buildChangeRecords, calcAlignCenterStyle, calcLayerTargetIndex, calcMoveStyle, change2Fixed, classifyDragSources, collectRelatedNodes, editorNodeMergeCustomizer, fixNodeLeft, fixNodePosition, generatePageName, generatePageNameByApp, getGuideLineFromCache, getInitPositionStyle, getNodeIndex, getPageFragmentList, getPageList, getPageNameList, getRelativeStyle, isIncludeDataSource, moveItemsInContainer, resolveSelectedNode, serializeConfig, setChildrenLayout, setLayout, toggleFixedPosition };
@@ -98,8 +98,12 @@ var styleTabConfig = {
98
98
  "borderLeftColor",
99
99
  "borderWidth",
100
100
  "borderStyle",
101
- "borderColor"
102
- ] }]
101
+ "borderColor",
102
+ "opacity"
103
+ ] }, {
104
+ name: "transform",
105
+ defaultValue: () => ({})
106
+ }]
103
107
  }]
104
108
  };
105
109
  var eventTabConfig = {
@@ -4,7 +4,7 @@ var UndoRedo = class {
4
4
  elementList;
5
5
  listCursor;
6
6
  listMaxSize;
7
- constructor(listMaxSize = 20) {
7
+ constructor(listMaxSize = 100) {
8
8
  const minListMaxSize = 2;
9
9
  this.elementList = [];
10
10
  this.listCursor = 0;
@@ -19,20 +19,23 @@ var UndoRedo = class {
19
19
  }
20
20
  }
21
21
  canUndo() {
22
- return this.listCursor > 1;
22
+ return this.listCursor > 0;
23
23
  }
24
+ /** 返回被撤销的操作 */
24
25
  undo() {
25
26
  if (!this.canUndo()) return null;
26
27
  this.listCursor -= 1;
27
- return this.getCurrentElement();
28
+ return cloneDeep(this.elementList[this.listCursor]);
28
29
  }
29
30
  canRedo() {
30
31
  return this.elementList.length > this.listCursor;
31
32
  }
33
+ /** 返回被重做的操作 */
32
34
  redo() {
33
35
  if (!this.canRedo()) return null;
36
+ const element = cloneDeep(this.elementList[this.listCursor]);
34
37
  this.listCursor += 1;
35
- return this.getCurrentElement();
38
+ return element;
36
39
  }
37
40
  getCurrentElement() {
38
41
  if (this.listCursor < 1) return null;