feat: support plaintext liveediting

This commit is contained in:
kangwei 2020-05-11 04:50:21 +08:00
parent f51d496860
commit ea62f12343
24 changed files with 300 additions and 95 deletions

View File

@ -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 <Fragment />;
if (!current || host.viewport.scrolling || host.liveEditing.editing) {
return null;
}
const instances = host.getComponentInstances(current);
if (!instances || instances.length < 1) {
return <Fragment />;
return null;
}
if (instances.length === 1) {
return (
<BorderHoveringInstance
<BorderDetectingInstance
key="line-h"
title={current.title}
scale={this.scale}
scrollX={this.scrollX}
scrollY={this.scrollY}
rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rectSelector)}
rect={host.computeComponentInstanceRect(instances[0], current.componentMeta.rootSelector)}
/>
);
}
return (
<Fragment>
{instances.map((inst, i) => (
<BorderHoveringInstance
<BorderDetectingInstance
key={`line-h-${i}`}
title={current.title}
scale={this.scale}
scrollX={this.scrollX}
scrollY={this.scrollY}
rect={host.computeComponentInstanceRect(inst, current.componentMeta.rectSelector)}
rect={host.computeComponentInstanceRect(inst, current.componentMeta.rootSelector)}
/>
))}
</Fragment>

View File

@ -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 <Fragment />;
return null;
}
return (

View File

@ -48,7 +48,7 @@
}
}
&&-hovering {
&&-detecting {
z-index: 1;
border-style: dashed;
background: rgba(0,121,242,.04);

View File

@ -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 (
<div className="lc-bem-tools" style={{ transform: `translate(${-scrollX * scale}px,${-scrollY * scale}px)` }}>
<BorderHovering key="hovering" host={host} />
<BorderDetecting key="hovering" host={host} />
<BorderSelecting key="selecting" host={host} />
<InsertionView key="insertion" host={host} />
</div>

View File

@ -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 } : {};
}
}

View File

@ -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<BuiltinSimulatorProp
// just listen special callback
// because iframe maybe reload
this.setupDragAndClick();
this.setupHovering();
this.setupDetecting();
this.setupLiveEditing();
}
setupDragAndClick() {
@ -238,6 +240,9 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.addEventListener(
'mousedown',
(downEvent: MouseEvent) => {
if (this.liveEditing.editing) {
return;
}
// stop response document focus event
downEvent.stopPropagation();
downEvent.preventDefault();
@ -250,7 +255,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
doc.removeEventListener('mouseup', checkSelect, true);
if (!isShaken(downEvent, e)) {
const id = node.id;
designer.activeTracker.track(node);
designer.activeTracker.track({ node, instance: nodeInst?.instance });
if (isMulti && !isRootNode(node) && selection.has(id)) {
selection.remove(id);
} else {
@ -265,7 +270,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (isMulti) {
// multi select mode, directily add
if (!selection.has(node.id)) {
designer.activeTracker.track(node);
designer.activeTracker.track({ node, instance: nodeInst?.instance });
selection.add(node.id);
ignoreUpSelected = true;
}
@ -305,36 +310,24 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
},
true,
);
// cause edit
doc.addEventListener(
'dblclick',
(e: MouseEvent) => {
// 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<BuiltinSimulatorProp
);
this.disableHovering = () => {
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<BuiltinSimulatorProp
} else {
// weekup some autorun reaction
if (!this.disableHovering) {
this.setupHovering();
this.setupDetecting();
}
}
}
@ -455,7 +482,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
if (!instances) {
return null;
}
return this.computeComponentInstanceRect(instances[0], node.componentMeta.rectSelector);
return this.computeComponentInstanceRect(instances[0], node.componentMeta.rootSelector);
}
/**
@ -463,19 +490,12 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
*/
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): Rect | null {
const renderer = this.renderer!;
const elements = renderer.findDOMNodes(instance);
const elements = this.findDOMNodes(instance, selector);
if (!elements) {
return null;
}
let elems = elements.slice();
if (selector) {
const matched = getMatched(elems, selector);
if (!matched) {
return null;
}
elems = [matched];
}
const elems = elements.slice();
let rects: DOMRect[] | undefined;
let last: { x: number; y: number; r: number; b: number } | undefined;
let computed = false;
@ -534,8 +554,20 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
/**
* @see ISimulator
*/
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null {
return this._renderer?.findDOMNodes(instance) || null;
findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | 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<BuiltinSimulatorProp
const { container, instance: containerInstance } = dropContainer;
const edge = this.computeComponentInstanceRect(containerInstance, container.componentMeta.rectSelector);
const edge = this.computeComponentInstanceRect(containerInstance, container.componentMeta.rootSelector);
if (!edge) {
return null;
@ -758,7 +790,7 @@ export class BuiltinSimulatorHost implements ISimulatorHost<BuiltinSimulatorProp
? instances.find((inst) => 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;

View File

@ -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若匹配读取配置若不匹配parentNodecloseat?
/*
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);
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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) {

View File

@ -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 }) => {

View File

@ -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;
}

View File

@ -1,7 +1,6 @@
import { Designer } from './designer';
// TODO:
// 当前激活区域管理
// TODO: use focus-tracker replace
class Focusing {
focusDesigner?: Designer;
}

View File

@ -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';

View File

@ -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;

View File

@ -284,9 +284,9 @@ export class Node<Schema extends NodeSchema = NodeSchema> {
*/
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);
}
}

View File

@ -134,7 +134,7 @@ export interface ISimulatorHost<P = object> extends ISensor {
computeComponentInstanceRect(instance: ComponentInstance, selector?: string): DOMRect | null;
findDOMNodes(instance: ComponentInstance): Array<Element | Text> | null;
findDOMNodes(instance: ComponentInstance, selector?: string): Array<Element | Text> | null;
/**
*

View File

@ -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 {

View File

@ -260,7 +260,7 @@
}
}
&.hovering > .tree-node-title {
&.detecting > .tree-node-title {
background: var(--color-block-background-light);
}

View File

@ -17,7 +17,7 @@ export default class TreeNodeView extends Component<{ treeNode: TreeNode }> {
// 是否展开
expanded: treeNode.expanded,
// 是否悬停中
hovering: treeNode.hovering,
detecting: treeNode.detecting,
// 是否选中的
selected: treeNode.selected,
// 是否隐藏的

View File

@ -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() {

View File

@ -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;
}

View File

@ -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 {

View File

@ -583,7 +583,7 @@ export function upgradeMetadata(oldConfig: OldPrototypeConfig) {
const component: any = {
isContainer,
rectSelector,
rootSelector: rectSelector,
isModal,
isFloating,
descriptor,