diff --git a/packages/designer/src/builtin-simulator/bem-tools/border-hovering.tsx b/packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx similarity index 81% rename from packages/designer/src/builtin-simulator/bem-tools/border-hovering.tsx rename to packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx index fa0d3b1dc..f6bd20460 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/border-hovering.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/border-detecting.tsx @@ -1,11 +1,10 @@ import { Component, Fragment, PureComponent } from 'react'; import classNames from 'classnames'; import { computed, observer, Title } from '@ali/lowcode-editor-core'; -import { SimulatorContext } from '../context'; import { BuiltinSimulatorHost } from '../host'; import { TitleContent } from '@ali/lowcode-types'; -export class BorderHoveringInstance extends PureComponent<{ +export class BorderDetectingInstance extends PureComponent<{ title: TitleContent; rect: DOMRect | null; scale: number; @@ -24,7 +23,7 @@ export class BorderHoveringInstance extends PureComponent<{ transform: `translate(${(scrollX + rect.left) * scale}px, ${(scrollY + rect.top) * scale}px)`, }; - const className = classNames('lc-borders lc-borders-hovering'); + const className = classNames('lc-borders lc-borders-detecting'); // TODO: // 1. thinkof icon @@ -39,7 +38,7 @@ export class BorderHoveringInstance extends PureComponent<{ } @observer -export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> { +export class BorderDetecting extends Component<{ host: BuiltinSimulatorHost }> { shouldComponentUpdate() { return false; } @@ -60,7 +59,7 @@ export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> { const host = this.props.host; const doc = host.document; const selection = doc.selection; - const current = host.designer.hovering.current; + const current = host.designer.detecting.current; if (!current || current.document !== doc || selection.has(current.id)) { return null; } @@ -70,36 +69,36 @@ export class BorderHovering extends Component<{ host: BuiltinSimulatorHost }> { render() { const host = this.props.host; const current = this.current; - if (!current || host.viewport.scrolling) { - return ; + if (!current || host.viewport.scrolling || host.liveEditing.editing) { + return null; } const instances = host.getComponentInstances(current); if (!instances || instances.length < 1) { - return ; + return null; } if (instances.length === 1) { return ( - ); } return ( {instances.map((inst, i) => ( - ))} diff --git a/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx b/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx index 53b051ca5..209fce326 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/border-selecting.tsx @@ -12,7 +12,6 @@ import classNames from 'classnames'; import { observer, computed, Tip } from '@ali/lowcode-editor-core'; import { createIcon, isReactComponent } from '@ali/lowcode-utils'; import { ActionContentObject, isActionContentObject } from '@ali/lowcode-types'; -import { SimulatorContext } from '../context'; import { BuiltinSimulatorHost } from '../host'; import { OffsetObserver } from '../../designer'; import { Node } from '../../document'; @@ -186,7 +185,7 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> { @computed get selecting() { const doc = this.host.document; - if (doc.suspensed) { + if (doc.suspensed || this.host.liveEditing.editing) { return null; } const selection = doc.selection; @@ -200,8 +199,7 @@ export class BorderSelecting extends Component<{ host: BuiltinSimulatorHost }> { render() { const selecting = this.selecting; if (!selecting || selecting.length < 1) { - // DIRTY FIX, recore has a bug! - return ; + return null; } return ( diff --git a/packages/designer/src/builtin-simulator/bem-tools/borders.less b/packages/designer/src/builtin-simulator/bem-tools/borders.less index b602193b5..7effde36a 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/borders.less +++ b/packages/designer/src/builtin-simulator/bem-tools/borders.less @@ -48,7 +48,7 @@ } } - &&-hovering { + &&-detecting { z-index: 1; border-style: dashed; background: rgba(0,121,242,.04); diff --git a/packages/designer/src/builtin-simulator/bem-tools/index.tsx b/packages/designer/src/builtin-simulator/bem-tools/index.tsx index 99fbc57c9..881ff9396 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/index.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/index.tsx @@ -1,6 +1,6 @@ import { Component } from 'react'; import { observer } from '@ali/lowcode-editor-core'; -import { BorderHovering } from './border-hovering'; +import { BorderDetecting } from './border-detecting'; import { BuiltinSimulatorHost } from '../host'; import { BorderSelecting } from './border-selecting'; import { InsertionView } from './insertion'; @@ -18,7 +18,7 @@ export class BemTools extends Component<{ host: BuiltinSimulatorHost }> { const { scrollX, scrollY, scale } = host.viewport; return (
- +
diff --git a/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx b/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx index f253d843b..90b65ecc8 100644 --- a/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx +++ b/packages/designer/src/builtin-simulator/bem-tools/insertion.tsx @@ -106,7 +106,7 @@ function processDetail({ target, detail, document }: DropLocation): InsertionDat if (!instances) { return {}; } - const edge = sim.computeComponentInstanceRect(instances[0], target.componentMeta.rectSelector); + const edge = sim.computeComponentInstanceRect(instances[0], target.componentMeta.rootSelector); return edge ? { edge, insertType: 'cover', coverRect: edge } : {}; } } diff --git a/packages/designer/src/builtin-simulator/host.ts b/packages/designer/src/builtin-simulator/host.ts index 82e779759..6f9a2d9da 100644 --- a/packages/designer/src/builtin-simulator/host.ts +++ b/packages/designer/src/builtin-simulator/host.ts @@ -25,6 +25,7 @@ import { parseMetadata } from './utils/parse-metadata'; import { ComponentMetadata } from '@ali/lowcode-types'; import { BuiltinSimulatorRenderer } from './renderer'; import clipboard from '../designer/clipboard'; +import { LiveEditing } from './live-editing/live-editing'; export interface LibraryItem { package: string; @@ -224,7 +225,8 @@ export class BuiltinSimulatorHost implements ISimulatorHost { + if (this.liveEditing.editing) { + return; + } // stop response document focus event downEvent.stopPropagation(); downEvent.preventDefault(); @@ -250,7 +255,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost { - // stop response document dblclick event - e.stopPropagation(); - e.preventDefault(); - // todo: quick editing - }, - true, - ); } private disableHovering?: () => void; /** * 设置悬停处理 */ - setupHovering() { + setupDetecting() { const doc = this.contentDocument!; - const hovering = this.document.designer.hovering; + const detecting = this.document.designer.detecting; const hover = (e: MouseEvent) => { - if (!hovering.enable) { + if (!detecting.enable) { return; } const nodeInst = this.getNodeInstanceFromElement(e.target as Element); - hovering.hover(nodeInst?.node || null); + detecting.capture(nodeInst?.node || null); e.stopPropagation(); }; - const leave = () => hovering.leave(this.document); + const leave = () => detecting.leave(this.document); doc.addEventListener('mouseover', hover, true); doc.addEventListener('mouseleave', leave, false); @@ -349,13 +342,47 @@ export class BuiltinSimulatorHost implements ISimulatorHost { - hovering.leave(this.document); + detecting.leave(this.document); doc.removeEventListener('mouseover', hover, true); doc.removeEventListener('mouseleave', leave, false); this.disableHovering = undefined; }; } + readonly liveEditing = new LiveEditing(); + setupLiveEditing() { + const doc = this.contentDocument!; + // cause edit + doc.addEventListener( + 'dblclick', + (e: MouseEvent) => { + // stop response document dblclick event + e.stopPropagation(); + e.preventDefault(); + + const targetElement = e.target as HTMLElement; + const nodeInst = this.getNodeInstanceFromElement(targetElement); + if (!nodeInst) { + return; + } + const node = nodeInst.node || this.document.rootNode; + + const rootElement = this.findDOMNodes(nodeInst.instance, node.componentMeta.rootSelector)?.find(item => item.contains(targetElement)) as HTMLElement; + if (!rootElement) { + return; + } + + this.liveEditing.apply({ + node, + rootElement, + event: e, + }); + }, + true, + ); + + } + /** * @see ISimulator */ @@ -368,7 +395,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost | null { - return this._renderer?.findDOMNodes(instance) || null; + findDOMNodes(instance: ComponentInstance, selector?: string): Array | null { + const elements = this._renderer?.findDOMNodes(instance); + if (!elements) { + return null; + } + + if (selector) { + const matched = getMatched(elements, selector); + if (!matched) { + return null; + } + return [matched]; + } + return elements; } /** @@ -717,7 +749,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost this.getClosestNodeInstance(inst, container.id)?.instance === containerInstance) : instances[0] : null; - const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rectSelector) : null; + const rect = inst ? this.computeComponentInstanceRect(inst, node.componentMeta.rootSelector) : null; if (!rect) { continue; diff --git a/packages/designer/src/builtin-simulator/live-editing/live-editing.ts b/packages/designer/src/builtin-simulator/live-editing/live-editing.ts new file mode 100644 index 000000000..0ae54b380 --- /dev/null +++ b/packages/designer/src/builtin-simulator/live-editing/live-editing.ts @@ -0,0 +1,145 @@ +import { obx } from '@ali/lowcode-editor-core'; +import { Node, Prop } from '../../document'; + +const EDITOR_KEY = 'data-setter-prop'; + +function getSetterPropElement(ele: HTMLElement, root: HTMLElement): HTMLElement | null { + const box = ele.closest(`[${EDITOR_KEY}]`); + if (!box || !root.contains(box)) { + return null; + } + return box as HTMLElement; +} + +function defaultSaveContent(content: string, prop: Prop) { + prop.setValue(content); +} + +export class LiveEditing { + @obx.ref private _editing: Prop | null = null; + apply(target: { node: Node; rootElement: HTMLElement; event: MouseEvent }) { + const { node, event, rootElement } = target; + 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) { + // 已埋点命中 data-setter-prop="proptarget", 从 liveTextEditing 读取配置(mode|onSaveContent) + const config = liveTextEditing.find(config => config.propTarget == propTarget); + const prop = node.getProp(propTarget, true)!; + + if (this._editing === prop) { + return; + } + + // 进入编辑 + // 1. 设置contentEditable="plaintext|..." + // 2. 添加类名 + // 3. focus & cursor locate + // 4. 监听 blur 事件 + // 5. 设置编辑锁定:disable hover | disable select | disable canvas drag + + const onSaveContent = config?.onSaveContent || this.saveHandlers.find(item => item.condition(prop))?.onSaveContent || defaultSaveContent; + + setterPropElement.setAttribute('contenteditable', config?.mode && config.mode !== 'plaintext' ? 'true' : 'plaintext-only'); + setterPropElement.classList.add('engine-live-editing'); + // be sure + setterPropElement.focus(); + setCaret(event); + + this._save = () => { + onSaveContent(setterPropElement.innerText, prop); + }; + + this._dispose = () => { + setterPropElement.removeAttribute('contenteditable'); + setterPropElement.classList.remove('engine-live-editing'); + }; + + setterPropElement.addEventListener('focusout', (e) => { + this.saveAndDispose(); + }); + + 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 + + // 非文本编辑 + // 国际化数据,改变当前 + // JSExpression, 改变 mock 或 弹出绑定变量 + } + + get editing() { + return this._editing; + } + + private _dispose?: () => void; + private _save?: () => void; + saveAndDispose() { + if (this._save) { + this._save(); + this._save = undefined; + } + this.dispose(); + } + + dispose() { + if (this._dispose) { + this._dispose(); + this._dispose = undefined; + } + this._editing = null; + } + + private saveHandlers: SaveHandler[] = []; + setSaveHandler(handler: SaveHandler) { + this.saveHandlers.push(handler); + } +} + +export interface SaveHandler { + condition: (prop: Prop) => boolean; + onSaveContent: (content: string, prop: Prop) => void; +} + +function setCaret(event: MouseEvent) { + const doc = event.view?.document!; + const range = doc.caretRangeFromPoint(event.clientX, event.clientY); + if (range) { + selectRange(doc, range); + setTimeout(() => selectRange(doc, range), 1); + } +} + +function selectRange(doc: Document, range: Range) { + const selection = doc.getSelection(); + if (selection) { + selection.removeAllRanges(); + selection.addRange(range); + } +} diff --git a/packages/designer/src/component-meta.ts b/packages/designer/src/component-meta.ts index 1410ea334..d389586fa 100644 --- a/packages/designer/src/component-meta.ts +++ b/packages/designer/src/component-meta.ts @@ -81,9 +81,9 @@ export class ComponentMeta { get descriptor(): string | undefined { return this._descriptor; } - private _rectSelector?: string; - get rectSelector(): string | undefined { - return this._rectSelector; + private _rootSelector?: string; + get rootSelector(): string | undefined { + return this._rootSelector; } private _transformedMetadata?: TransformedComponentMetadata; get configure() { @@ -158,7 +158,7 @@ export class ComponentMeta { this._isContainer = component.isContainer ? true : false; this._isModal = component.isModal ? true : false; this._descriptor = component.descriptor; - this._rectSelector = component.rectSelector; + this._rootSelector = component.rootSelector; if (component.nestingRule) { const { parentWhitelist, childWhitelist } = component.nestingRule; this.parentWhitelist = buildFilter(parentWhitelist); diff --git a/packages/designer/src/designer/active-tracker.ts b/packages/designer/src/designer/active-tracker.ts index 5f287dfed..078969b65 100644 --- a/packages/designer/src/designer/active-tracker.ts +++ b/packages/designer/src/designer/active-tracker.ts @@ -1,17 +1,38 @@ import { EventEmitter } from 'events'; import { LocationDetail } from './location'; import { Node, isNode } from '../document/node/node'; +import { ComponentInstance } from '../simulator'; +import { obx } from '@ali/lowcode-editor-core'; export interface ActiveTarget { node: Node; detail?: LocationDetail; + instance?: ComponentInstance; } export class ActiveTracker { private emitter = new EventEmitter(); + @obx.ref private _target?: ActiveTarget; + track(target: ActiveTarget | Node) { - this.emitter.emit('change', isNode(target) ? { node: target } : target); + if (isNode(target)) { + target = { node: target }; + } + this._target = target; + this.emitter.emit('change', target); + } + + get currentNode() { + return this._target?.node; + } + + get detail() { + return this._target?.detail; + } + + get intance() { + return this._target?.instance; } onChange(fn: (target: ActiveTarget) => void): () => void { diff --git a/packages/designer/src/designer/builtin-hotkey.ts b/packages/designer/src/designer/builtin-hotkey.ts index 8508f7479..8e01bbfff 100644 --- a/packages/designer/src/designer/builtin-hotkey.ts +++ b/packages/designer/src/designer/builtin-hotkey.ts @@ -66,6 +66,7 @@ function getPrevForSelect(prev: any, head?: any, parent?: any): any { // hotkey binding hotkey.bind(['backspace', 'del'], (e: KeyboardEvent) => { + // TODO: use focus-tracker const doc = focusing.focusDesigner?.currentDocument; if (isFormEvent(e) || !doc) { return; @@ -101,14 +102,6 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => { } e.preventDefault(); - /* - const doc = getCurrentDocument(); - if (isFormEvent(e) || !doc || !(focusing.id === 'outline' || focusing.id === 'canvas')) { - return; - } - e.preventDefault(); - */ - const selected = doc.selection.getTopNodes(true); if (!selected || selected.length < 1) return; @@ -119,7 +112,7 @@ hotkey.bind(['command+c', 'ctrl+c', 'command+x', 'ctrl+x'], (e, action) => { clipboard.setData(data); - const cutMode = action.indexOf('x') > 0; + const cutMode = action && action.indexOf('x') > 0; if (cutMode) { selected.forEach((node) => { const parentNode = node.getParent(); @@ -230,7 +223,7 @@ hotkey.bind(['option+left', 'option+right'], (e, action) => { const parent = firstNode.getParent(); if (!parent) return; - const isPrev = /(left)$/.test(action); + const isPrev = action && /(left)$/.test(action); const silbing = isPrev ? firstNode.prevSibling : firstNode.nextSibling; if (silbing) { diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index e91592413..7bee2289b 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -16,7 +16,7 @@ import { INodeSelector, Component } from '../simulator'; import { Scroller, IScrollable } from './scroller'; import { Dragon, isDragNodeObject, isDragNodeDataObject, LocateEvent, DragObject } from './dragon'; import { ActiveTracker } from './active-tracker'; -import { Hovering } from './hovering'; +import { Detecting } from './detecting'; import { DropLocation, LocationData, isLocationChildrenDetail } from './location'; import { OffsetObserver, createOffsetObserver } from './offset-observer'; import { focusing } from './focusing'; @@ -44,7 +44,7 @@ export interface DesignerProps { export class Designer { readonly dragon = new Dragon(this); readonly activeTracker = new ActiveTracker(); - readonly hovering = new Hovering(); + readonly detecting = new Detecting(); readonly project: Project; readonly editor: IEditor; @@ -68,7 +68,7 @@ export class Designer { this.project = new Project(this, props.defaultSchema); this.dragon.onDragstart((e) => { - this.hovering.enable = false; + this.detecting.enable = false; const { dragObject } = e; if (isDragNodeObject(dragObject)) { if (dragObject.nodes.length === 1) { @@ -118,7 +118,7 @@ export class Designer { this.props.onDragend(e, loc); } this.postEvent('dragend', e, loc); - this.hovering.enable = true; + this.detecting.enable = true; }); this.activeTracker.onChange(({ node, detail }) => { diff --git a/packages/designer/src/designer/hovering.ts b/packages/designer/src/designer/detecting.ts similarity index 89% rename from packages/designer/src/designer/hovering.ts rename to packages/designer/src/designer/detecting.ts index 9c27801a7..11492f937 100644 --- a/packages/designer/src/designer/hovering.ts +++ b/packages/designer/src/designer/detecting.ts @@ -1,7 +1,7 @@ import { obx } from '@ali/lowcode-editor-core'; import { Node, DocumentModel } from '../document'; -export class Hovering { +export class Detecting { @obx.ref private _enable = true; get enable() { return this._enable; @@ -19,11 +19,11 @@ export class Hovering { return this._current; } - hover(node: Node | null) { + capture(node: Node | null) { this._current = node; } - unhover(node: Node) { + release(node: Node) { if (this._current === node) { this._current = null; } diff --git a/packages/designer/src/designer/focusing.ts b/packages/designer/src/designer/focusing.ts index 06a1495f3..66816bc03 100644 --- a/packages/designer/src/designer/focusing.ts +++ b/packages/designer/src/designer/focusing.ts @@ -1,7 +1,6 @@ import { Designer } from './designer'; -// TODO: -// 当前激活区域管理 +// TODO: use focus-tracker replace class Focusing { focusDesigner?: Designer; } diff --git a/packages/designer/src/designer/index.ts b/packages/designer/src/designer/index.ts index 1de0fb2ee..8bcf028c9 100644 --- a/packages/designer/src/designer/index.ts +++ b/packages/designer/src/designer/index.ts @@ -2,7 +2,7 @@ import './builtin-hotkey'; export * from './designer'; export * from './designer-view'; export * from './dragon'; -export * from './hovering'; +export * from './detecting'; export * from './location'; export * from './offset-observer'; export * from './scroller'; diff --git a/packages/designer/src/designer/offset-observer.ts b/packages/designer/src/designer/offset-observer.ts index a6601373e..4f72691af 100644 --- a/packages/designer/src/designer/offset-observer.ts +++ b/packages/designer/src/designer/offset-observer.ts @@ -105,7 +105,7 @@ export class OffsetObserver { return; } - const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rectSelector); + const rect = host.computeComponentInstanceRect(instance!, node.componentMeta.rootSelector); if (!rect) { this.hasOffset = false; diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index eb326fd14..63a75421d 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -284,9 +284,9 @@ export class Node { */ hover(flag = true) { if (flag) { - this.document.designer.hovering.hover(this); + this.document.designer.detecting.capture(this); } else { - this.document.designer.hovering.unhover(this); + this.document.designer.detecting.release(this); } } diff --git a/packages/designer/src/simulator.ts b/packages/designer/src/simulator.ts index 9fe2af78a..fd3e7dcaf 100644 --- a/packages/designer/src/simulator.ts +++ b/packages/designer/src/simulator.ts @@ -134,7 +134,7 @@ export interface ISimulatorHost

extends ISensor { computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null; - findDOMNodes(instance: ComponentInstance): Array | null; + findDOMNodes(instance: ComponentInstance, selector?: string): Array | null; /** * 销毁 diff --git a/packages/plugin-outline-pane/src/tree-node.ts b/packages/plugin-outline-pane/src/tree-node.ts index 47b667c14..893dda619 100644 --- a/packages/plugin-outline-pane/src/tree-node.ts +++ b/packages/plugin-outline-pane/src/tree-node.ts @@ -65,8 +65,8 @@ export default class TreeNode { this._expanded = value; } - @computed get hovering() { - return this.designer.hovering.current === this.node; + @computed get detecting() { + return this.designer.detecting.current === this.node; } @computed get hidden(): boolean { diff --git a/packages/plugin-outline-pane/src/views/style.less b/packages/plugin-outline-pane/src/views/style.less index 056061f11..c7c571174 100644 --- a/packages/plugin-outline-pane/src/views/style.less +++ b/packages/plugin-outline-pane/src/views/style.less @@ -260,7 +260,7 @@ } } - &.hovering > .tree-node-title { + &.detecting > .tree-node-title { background: var(--color-block-background-light); } diff --git a/packages/plugin-outline-pane/src/views/tree-node.tsx b/packages/plugin-outline-pane/src/views/tree-node.tsx index 7688b67c6..95728bdca 100644 --- a/packages/plugin-outline-pane/src/views/tree-node.tsx +++ b/packages/plugin-outline-pane/src/views/tree-node.tsx @@ -17,7 +17,7 @@ export default class TreeNodeView extends Component<{ treeNode: TreeNode }> { // 是否展开 expanded: treeNode.expanded, // 是否悬停中 - hovering: treeNode.hovering, + detecting: treeNode.detecting, // 是否选中的 selected: treeNode.selected, // 是否隐藏的 diff --git a/packages/plugin-outline-pane/src/views/tree.tsx b/packages/plugin-outline-pane/src/views/tree.tsx index 04d98c496..b629d6c44 100644 --- a/packages/plugin-outline-pane/src/views/tree.tsx +++ b/packages/plugin-outline-pane/src/views/tree.tsx @@ -25,12 +25,12 @@ export default class TreeView extends Component<{ tree: Tree }> { const { tree } = this.props; const doc = tree.document; - const hovering = doc.designer.hovering; - if (!hovering.enable) { + const detecting = doc.designer.detecting; + if (!detecting.enable) { return; } const node = this.getTreeNodeFromEvent(e)?.node; - hovering.hover(node || null); + detecting.capture(node || null); } private onClick = (e: ReactMouseEvent) => { @@ -129,7 +129,7 @@ export default class TreeView extends Component<{ tree: Tree }> { private onMouseLeave = () => { const { tree } = this.props; const doc = tree.document; - doc.designer.hovering.leave(doc); + doc.designer.detecting.leave(doc); }; render() { diff --git a/packages/react-simulator-renderer/src/renderer.less b/packages/react-simulator-renderer/src/renderer.less index b22015e96..e2abf1153 100644 --- a/packages/react-simulator-renderer/src/renderer.less +++ b/packages/react-simulator-renderer/src/renderer.less @@ -85,6 +85,13 @@ body.engine-document { } } +.engine-live-editing { + cursor: text; + outline: none; + box-shadow: 0 0 0px 4px rgba(23, 141, 247, 0.2); + user-select: text; +} + #app { height: 100vh; } diff --git a/packages/types/src/metadata.ts b/packages/types/src/metadata.ts index b6fc5f0fc..82382d128 100644 --- a/packages/types/src/metadata.ts +++ b/packages/types/src/metadata.ts @@ -29,7 +29,7 @@ export interface ComponentConfigure { descriptor?: string; nestingRule?: NestingRule; - rectSelector?: string; + rootSelector?: string; // copy,move,delete | * disableBehaviors?: string[] | string; actions?: ComponentAction[]; @@ -74,6 +74,17 @@ export interface Experimental { // 请求 hud 显示 // drag 时 计算 并 设置效果 // 更新控制柄位置 + + // 纯文本编辑:如果 children 内容是 + // 文本编辑:配置 + liveTextEditing?: Array<{ + propTarget: string; + selector?: string; + // 编辑模式 纯文本|段落编辑|文章编辑(默认纯文本,无跟随工具条) + mode?: 'plaintext' | 'paragraph' | 'article'; + // 从 contentEditable 获取内容并设置到属性 + onSaveContent?: (content: string, prop: any) => any; + }>; } export interface Configure { diff --git a/packages/vision-preset/src/bundle/upgrade-metadata.ts b/packages/vision-preset/src/bundle/upgrade-metadata.ts index 45a1662ac..af3646d1f 100644 --- a/packages/vision-preset/src/bundle/upgrade-metadata.ts +++ b/packages/vision-preset/src/bundle/upgrade-metadata.ts @@ -583,7 +583,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) { const component: any = { isContainer, - rectSelector, + rootSelector: rectSelector, isModal, isFloating, descriptor,