diff --git a/packages/designer/src/builtin-simulator/live-editing/live-editing.ts b/packages/designer/src/builtin-simulator/live-editing/live-editing.ts index 0ae54b380..f1ea22577 100644 --- a/packages/designer/src/builtin-simulator/live-editing/live-editing.ts +++ b/packages/designer/src/builtin-simulator/live-editing/live-editing.ts @@ -1,4 +1,5 @@ import { obx } from '@ali/lowcode-editor-core'; +import { LiveTextEditingConfig } from '@ali/lowcode-types'; import { Node, Prop } from '../../document'; const EDITOR_KEY = 'data-setter-prop'; @@ -22,11 +23,45 @@ export class LiveEditing { const targetElement = event.target as HTMLElement; const liveTextEditing = node.componentMeta.getMetadata().experimental?.liveTextEditing || []; - const setterPropElement = getSetterPropElement(targetElement, rootElement); - const propTarget = setterPropElement?.dataset.setterProp; - if (setterPropElement && propTarget) { + let setterPropElement = getSetterPropElement(targetElement, rootElement); + let propTarget = setterPropElement?.dataset.setterProp; + let matched: LiveTextEditingConfig | undefined; + if (propTarget) { // 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置(mode|onSaveContent) - const config = liveTextEditing.find(config => config.propTarget == propTarget); + matched = liveTextEditing.find(config => config.propTarget == propTarget); + } else { + // 执行 embedTextEditing selector 规则,获得第一个节点 是否 contains e.target,若匹配,读取配置 + matched = liveTextEditing.find(config => { + if (!config.selector) { + return false; + } + setterPropElement = config.selector === ':root' ? rootElement : rootElement.querySelector(config.selector); + if (!setterPropElement) { + return false; + } + if (!setterPropElement.contains(targetElement)) { + // try selectorAll + setterPropElement = Array.from(rootElement.querySelectorAll(config.selector)).find(item => item.contains(targetElement)) as HTMLElement; + if (!setterPropElement) { + return false; + } + } + return true; + }); + propTarget = matched?.propTarget; + } + + if (!propTarget) { + // 自动纯文本编辑满足一下情况: + // 1. children 内容都是 Leaf 且都是文本(一期) + // 2. DOM 节点是单层容器,子集都是文本节点 (已满足) + const isAllText = node.children?.every(item => { + return item.isLeaf() && item.getProp('children')?.type === 'literal'; + }); + // TODO: + } + + if (propTarget && setterPropElement) { const prop = node.getProp(propTarget, true)!; if (this._editing === prop) { @@ -40,21 +75,21 @@ export class LiveEditing { // 4. 监听 blur 事件 // 5. 设置编辑锁定:disable hover | disable select | disable canvas drag - const onSaveContent = config?.onSaveContent || this.saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent; + const onSaveContent = matched?.onSaveContent || this.saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent; - setterPropElement.setAttribute('contenteditable', config?.mode && config.mode !== 'plaintext' ? 'true' : 'plaintext-only'); + setterPropElement.setAttribute('contenteditable', matched?.mode && matched.mode !== 'plaintext' ? 'true' : 'plaintext-only'); setterPropElement.classList.add('engine-live-editing'); // be sure setterPropElement.focus(); setCaret(event); this._save = () => { - onSaveContent(setterPropElement.innerText, prop); + onSaveContent(setterPropElement!.innerText, prop); }; this._dispose = () => { - setterPropElement.removeAttribute('contenteditable'); - setterPropElement.classList.remove('engine-live-editing'); + setterPropElement!.removeAttribute('contenteditable'); + setterPropElement!.classList.remove('engine-live-editing'); }; setterPropElement.addEventListener('focusout', (e) => { @@ -62,32 +97,9 @@ export class LiveEditing { }); this._editing = prop; - - } else { - } - // 1) 自动纯文本编辑满足一下情况: - // 1. children 内容都是 Leaf 且都是文本(一期) - // 2. DOM 节点是单层容器,子集都是文本节点 - // 2) children 内容都是 Leaf 且都是文本(一期), 且 children 命中 embedTextEditing 配置(必须配置 selector) - // 3) - // 4) 执行 embedTextEditing selector 规则,或得第一个节点 是否 contains e.target,若匹配,读取配置,若不匹配,parentNode,closeat? - /* - embedTextEditing: Array<{ - propTarget: string; - selector?: string; - // 编辑模式 纯文本|段落编辑|文章编辑(默认纯文本,无跟随工具条) - mode?: 'plaintext' | 'paragraph' | 'article'; - // 从 contentEditable 获取内容并设置到属性 - onSaveContent?: (content: string, prop: any) => any; - }>; - */ - // 进入编辑 - // 1. 设置contentEditable="plaintext|..." - // 2. 添加类名 - // 3. focus & cursor locate - // 4. 监听 blur 事件 - // 5. 设置编辑锁定:disable hover | disable select | disable canvas drag + + // TODO: upward testing for b/i/a html elements // 非文本编辑 // 国际化数据,改变当前 diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index 9d4ea42cc..ec038685b 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -213,6 +213,10 @@ export class NodeChildren { }); } + every(fn: (item: Node, index: number) => any): boolean { + return this.children.every((child, index) => fn(child, index)); + } + some(fn: (item: Node, index: number) => any): boolean { return this.children.some((child, index) => fn(child, index)); } diff --git a/packages/editor-skeleton/src/widget/panel.ts b/packages/editor-skeleton/src/widget/panel.ts index 2c2fb7edd..3f2699fe9 100644 --- a/packages/editor-skeleton/src/widget/panel.ts +++ b/packages/editor-skeleton/src/widget/panel.ts @@ -35,10 +35,21 @@ export default class Panel implements IWidget { readonly isPanel = true; - private _body?: ReactNode; get body() { - this.initBody(); - return this._body; + if (this.container) { + return createElement(TabsPanelView, { + container: this.container, + }); + } + + const { content, contentProps } = this.config; + return createContent(content, { + ...contentProps, + editor: this.skeleton.editor, + config: this.config, + panel: this, + pane: this, + }); } get content(): ReactNode { @@ -90,27 +101,6 @@ export default class Panel implements IWidget { // todo: process shortcut } - private initBody() { - if (this.inited) { - return; - } - this.inited = true; - if (this.container) { - this._body = createElement(TabsPanelView, { - container: this.container, - }); - } else { - const { content, contentProps } = this.config; - this._body = createContent(content, { - ...contentProps, - editor: this.skeleton.editor, - config: this.config, - panel: this, - pane: this, - }); - } - } - setParent(parent: WidgetContainer) { if (parent === this.parent) { return; @@ -155,11 +145,11 @@ export default class Panel implements IWidget { return; } if (flag) { - if (!this.inited) { - this.initBody(); - } this._actived = true; this.parent?.active(this); + if (!this.inited) { + this.inited = true; + } this.emitter.emit('activechange', true); } else if (this.inited) { this._actived = false; diff --git a/packages/types/src/metadata.ts b/packages/types/src/metadata.ts index 82382d128..b3b11aed0 100644 --- a/packages/types/src/metadata.ts +++ b/packages/types/src/metadata.ts @@ -77,14 +77,17 @@ export interface Experimental { // 纯文本编辑:如果 children 内容是 // 文本编辑:配置 - liveTextEditing?: Array<{ - propTarget: string; - selector?: string; - // 编辑模式 纯文本|段落编辑|文章编辑(默认纯文本,无跟随工具条) - mode?: 'plaintext' | 'paragraph' | 'article'; - // 从 contentEditable 获取内容并设置到属性 - onSaveContent?: (content: string, prop: any) => any; - }>; + liveTextEditing?: LiveTextEditingConfig[]; +} + +// thinkof Array +export interface LiveTextEditingConfig { + propTarget: string; + selector?: string; + // 编辑模式 纯文本|段落编辑|文章编辑(默认纯文本,无跟随工具条) + mode?: 'plaintext' | 'paragraph' | 'article'; + // 从 contentEditable 获取内容并设置到属性 + onSaveContent?: (content: string, prop: any) => any; } export interface Configure {