diff --git a/docs/docs/api/model/node.md b/docs/docs/api/model/node.md index 333e973f0..bbf8e559f 100644 --- a/docs/docs/api/model/node.md +++ b/docs/docs/api/model/node.md @@ -645,3 +645,14 @@ setConditionalVisible(): void; ``` **@since v1.1.0** + +### getDOMNode +获取节点实例对应的 dom 节点 + +```typescript +/** + * 获取节点实例对应的 dom 节点 + */ +getDOMNode(): HTMLElement; + +``` \ No newline at end of file diff --git a/packages/designer/jest.config.js b/packages/designer/jest.config.js index 788c0ac79..594eb6f36 100644 --- a/packages/designer/jest.config.js +++ b/packages/designer/jest.config.js @@ -15,6 +15,7 @@ const jestConfig = { // testMatch: ['**/document-model.test.ts'], // testMatch: ['**/prop.test.ts'], // testMatch: ['(/tests?/.*(test))\\.[jt]s$'], + // testMatch: ['**/document/node/node.add.test.ts'], transformIgnorePatterns: [ `/node_modules/(?!${esModules})/`, ], diff --git a/packages/designer/src/designer/designer.ts b/packages/designer/src/designer/designer.ts index d40082d82..3e2ff6dd2 100644 --- a/packages/designer/src/designer/designer.ts +++ b/packages/designer/src/designer/designer.ts @@ -71,6 +71,8 @@ export interface IDesigner { get editor(): IPublicModelEditor; + get detecting(): Detecting; + createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller; /** diff --git a/packages/designer/src/designer/setting/setting-top-entry.ts b/packages/designer/src/designer/setting/setting-top-entry.ts index 5c3c1c250..04f00afc3 100644 --- a/packages/designer/src/designer/setting/setting-top-entry.ts +++ b/packages/designer/src/designer/setting/setting-top-entry.ts @@ -17,6 +17,7 @@ function generateSessionId(nodes: INode[]) { } export interface ISettingTopEntry extends ISettingEntry { + purge(): void; } export class SettingTopEntry implements ISettingTopEntry { diff --git a/packages/designer/src/document/document-model.ts b/packages/designer/src/document/document-model.ts index 63fe094db..7c4344c31 100644 --- a/packages/designer/src/document/document-model.ts +++ b/packages/designer/src/document/document-model.ts @@ -87,6 +87,8 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel< get active(): boolean; + get nodesMap(): Map; + /** * 根据 id 获取节点 */ @@ -114,6 +116,12 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel< onChangeNodeVisible(fn: (node: INode, visible: boolean) => void): IPublicTypeDisposable; addWillPurge(node: INode): void; + + removeWillPurge(node: INode): void; + + getComponentMeta(componentName: string): IComponentMeta; + + insertNodes(parent: INode, thing: INode[] | IPublicTypeNodeData[], at?: number | null, copy?: boolean): INode[]; } export class DocumentModel implements IDocumentModel { @@ -379,7 +387,7 @@ export class DocumentModel implements IDocumentModel { * 根据 schema 创建一个节点 */ @action - createNode(data: GetDataType, checkId: boolean = true): T { + createNode(data: GetDataType): T { let schema: any; if (isDOMText(data) || isJSExpression(data)) { schema = { @@ -410,7 +418,7 @@ export class DocumentModel implements IDocumentModel { } } if (!node) { - node = new Node(this, schema, { checkId }); + node = new Node(this, schema); // will add // todo: this.activeNodes?.push(node); } @@ -429,7 +437,7 @@ export class DocumentModel implements IDocumentModel { /** * 插入一个节点 */ - insertNode(parent: INode, thing: INode | IPublicTypeNodeData, at?: number | null, copy?: boolean): INode { + insertNode(parent: INode, thing: INode | IPublicTypeNodeData, at?: number | null, copy?: boolean): INode | null { return insertChild(parent, thing, at, copy); } @@ -445,7 +453,7 @@ export class DocumentModel implements IDocumentModel { */ removeNode(idOrNode: string | INode) { let id: string; - let node: INode | null; + let node: INode | null = null; if (typeof idOrNode === 'string') { id = idOrNode; node = this.getNode(id); @@ -859,7 +867,7 @@ export class DocumentModel implements IDocumentModel { onReady(fn: Function) { this.designer.editor.eventBus.on('document-open', fn); return () => { - this.designer.editor.removeListener('document-open', fn); + this.designer.editor.eventBus.off('document-open', fn); }; } diff --git a/packages/designer/src/document/node/exclusive-group.ts b/packages/designer/src/document/node/exclusive-group.ts index 014715fd1..8cf993095 100644 --- a/packages/designer/src/document/node/exclusive-group.ts +++ b/packages/designer/src/document/node/exclusive-group.ts @@ -1,18 +1,30 @@ import { obx, computed, makeObservable } from '@alilc/lowcode-editor-core'; import { uniqueId } from '@alilc/lowcode-utils'; import { IPublicTypeTitleContent, IPublicModelExclusiveGroup } from '@alilc/lowcode-types'; -import { Node } from './node'; +import { INode } from './node'; import { intl } from '../../locale'; +export interface IExclusiveGroup extends IPublicModelExclusiveGroup { + readonly name: string; + + remove(node: INode): void; + + add(node: INode): void; + + isVisible(node: INode): boolean; +} + // modals assoc x-hide value, initial: check is Modal, yes will put it in modals, cross levels // if-else-if assoc conditionGroup value, should be the same level, // and siblings, need renderEngine support -export class ExclusiveGroup implements IPublicModelExclusiveGroup { +export class ExclusiveGroup implements IExclusiveGroup { readonly isExclusiveGroup = true; readonly id = uniqueId('exclusive'); - @obx.shallow readonly children: Node[] = []; + readonly title: IPublicTypeTitleContent; + + @obx.shallow readonly children: INode[] = []; @obx private visibleIndex = 0; @@ -28,11 +40,11 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup { return this.children.length; } - @computed get visibleNode(): Node { + @computed get visibleNode(): INode { return this.children[this.visibleIndex]; } - @computed get firstNode(): Node { + @computed get firstNode(): INode { return this.children[0]!; } @@ -40,8 +52,16 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup { return this.firstNode.index; } - add(node: Node) { - if (node.nextSibling && node.nextSibling.conditionGroup === this) { + constructor(readonly name: string, title?: IPublicTypeTitleContent) { + makeObservable(this); + this.title = title || { + type: 'i18n', + intl: intl('Condition Group'), + }; + } + + add(node: INode) { + if (node.nextSibling && node.nextSibling.conditionGroup?.id === this.id) { const i = this.children.indexOf(node.nextSibling); this.children.splice(i, 0, node); } else { @@ -49,7 +69,7 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup { } } - remove(node: Node) { + remove(node: INode) { const i = this.children.indexOf(node); if (i > -1) { this.children.splice(i, 1); @@ -61,27 +81,17 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup { } } - setVisible(node: Node) { + setVisible(node: INode) { const i = this.children.indexOf(node); if (i > -1) { this.visibleIndex = i; } } - isVisible(node: Node) { + isVisible(node: INode) { const i = this.children.indexOf(node); return i === this.visibleIndex; } - - readonly title: IPublicTypeTitleContent; - - constructor(readonly name: string, title?: IPublicTypeTitleContent) { - makeObservable(this); - this.title = title || { - type: 'i18n', - intl: intl('Condition Group'), - }; - } } export function isExclusiveGroup(obj: any): obj is ExclusiveGroup { diff --git a/packages/designer/src/document/node/node-children.ts b/packages/designer/src/document/node/node-children.ts index ff85b2231..51e921a93 100644 --- a/packages/designer/src/document/node/node-children.ts +++ b/packages/designer/src/document/node/node-children.ts @@ -1,6 +1,6 @@ import { obx, computed, globalContext, makeObservable, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core'; import { Node, INode } from './node'; -import { IPublicTypeNodeData, IPublicModelNodeChildren, IPublicEnumTransformStage } from '@alilc/lowcode-types'; +import { IPublicTypeNodeData, IPublicModelNodeChildren, IPublicEnumTransformStage, IPublicTypeDisposable } from '@alilc/lowcode-types'; import { shallowEqual, compatStage, isNodeSchema } from '@alilc/lowcode-utils'; import { foreachReverse } from '../../utils/tree'; import { NodeRemoveOptions } from '../../types'; @@ -18,6 +18,8 @@ export interface INodeChildren extends Omit, > { get owner(): INode; + get length(): number; + unlinkChild(node: INode): void; /** @@ -58,6 +60,8 @@ export interface INodeChildren extends Omit, internalInitParent(): void; + onChange(fn: (info?: IOnChangeOptions) => void): IPublicTypeDisposable; + /** overriding methods end */ } export class NodeChildren implements INodeChildren { @@ -478,7 +482,7 @@ export class NodeChildren implements INodeChildren { } } - onChange(fn: (info?: IOnChangeOptions) => void): () => void { + onChange(fn: (info?: IOnChangeOptions) => void): IPublicTypeDisposable { this.emitter.on('change', fn); return () => { this.emitter.removeListener('change', fn); diff --git a/packages/designer/src/document/node/node.ts b/packages/designer/src/document/node/node.ts index efa860c8d..77f5bddd4 100644 --- a/packages/designer/src/document/node/node.ts +++ b/packages/designer/src/document/node/node.ts @@ -18,14 +18,14 @@ import { IPublicTypeDisposable, IBaseModelNode, } from '@alilc/lowcode-types'; -import { compatStage, isDOMText, isJSExpression, isNode } from '@alilc/lowcode-utils'; -import { ISettingTopEntry, SettingTopEntry } from '@alilc/lowcode-designer'; +import { compatStage, isDOMText, isJSExpression, isNode, isNodeSchema } from '@alilc/lowcode-utils'; +import { ISettingTopEntry } from '@alilc/lowcode-designer'; import { Props, getConvertedExtraKey, IProps } from './props/props'; -import { DocumentModel, IDocumentModel } from '../document-model'; +import { IDocumentModel } from '../document-model'; import { NodeChildren, INodeChildren } from './node-children'; import { IProp, Prop } from './props/prop'; -import { ComponentMeta, IComponentMeta } from '../../component-meta'; -import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group'; +import { IComponentMeta } from '../../component-meta'; +import { ExclusiveGroup, IExclusiveGroup, isExclusiveGroup } from './exclusive-group'; import { includeSlot, removeSlot } from '../../utils/slot'; import { foreachReverse } from '../../utils/tree'; import { NodeRemoveOptions, EDITOR_EVENT } from '../../types'; @@ -42,14 +42,11 @@ export interface INode extends Omit, - 'slots' | - 'slotFor' | - 'props' | - 'getProp' | - 'getExtraProp' | - 'replaceChild' | 'isRoot' | 'isPage' | 'isComponent' | @@ -64,29 +61,21 @@ export interface INode extends Omit { - get slots(): INode[]; - - /** - * 关联属性 - */ - get slotFor(): IProp | null; - - get props(): IProps; + isNode: boolean; get componentMeta(): IComponentMeta; - get settingEntry(): SettingTopEntry; + get settingEntry(): ISettingTopEntry; get isPurged(): boolean; - setVisible(flag: boolean): void; + get index(): number | undefined; - getVisible(): boolean; + get isPurging(): boolean; /** * 内部方法,请勿使用 @@ -100,7 +89,7 @@ export interface INode extends Omit any): () => void; - getProp(path: string, createIfNone?: boolean): IProp | null; - - getExtraProp(key: string, createIfNone?: boolean): IProp | null; - - replaceChild(node: INode, data: any): INode; - getSuitablePlace(node: INode, ref: any): any; - onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable; + onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable | undefined; onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable; @@ -153,13 +136,17 @@ export interface INode extends Omit return !!this._isRGLContainer; } - private _slotFor?: IProp | null = null; + get isEmptyNode() { + return this.isEmpty(); + } + + private _slotFor?: IProp | null | undefined = null; @obx.shallow _slots: INode[] = []; @@ -331,10 +322,10 @@ export class Node } /* istanbul ignore next */ - @obx.ref private _conditionGroup: IPublicModelExclusiveGroup | null = null; + @obx.ref private _conditionGroup: IExclusiveGroup | null = null; /* istanbul ignore next */ - get conditionGroup(): IPublicModelExclusiveGroup | null { + get conditionGroup(): IExclusiveGroup | null { return this._conditionGroup; } @@ -518,7 +509,7 @@ export class Node this.document.addWillPurge(this); } - didDropIn(dragment: Node) { + didDropIn(dragment: INode) { const { callbacks } = this.componentMeta.advanced; if (callbacks?.onNodeAdd) { const cbThis = this.internalToShellNode(); @@ -529,7 +520,7 @@ export class Node } } - didDropOut(dragment: Node) { + didDropOut(dragment: INode) { const { callbacks } = this.componentMeta.advanced; if (callbacks?.onNodeRemove) { const cbThis = this.internalToShellNode(); @@ -590,7 +581,7 @@ export class Node /** * 关联属性 */ - get slotFor(): IProp | null { + get slotFor(): IProp | null | undefined { return this._slotFor; } @@ -610,10 +601,10 @@ export class Node }); } if (this.isSlot()) { - this.parent.removeSlot(this, purge); - this.parent.children.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true }); + this.parent.removeSlot(this); + this.parent.children?.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true }); } else { - this.parent.children.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true }); + this.parent.children?.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true }); } } } @@ -653,7 +644,7 @@ export class Node /** * 节点组件描述 */ - @computed get componentMeta(): ComponentMeta { + @computed get componentMeta(): IComponentMeta { return this.document.getComponentMeta(this.componentName); } @@ -670,6 +661,7 @@ export class Node /* istanbul ignore next */ setConditionGroup(grp: IPublicModelExclusiveGroup | string | null) { + let _grp: IExclusiveGroup | null = null; if (!grp) { this.getExtraProp('conditionGroup', false)?.remove(); if (this._conditionGroup) { @@ -680,20 +672,20 @@ export class Node } if (!isExclusiveGroup(grp)) { if (this.prevSibling?.conditionGroup?.name === grp) { - grp = this.prevSibling.conditionGroup; + _grp = this.prevSibling.conditionGroup; } else if (this.nextSibling?.conditionGroup?.name === grp) { - grp = this.nextSibling.conditionGroup; - } else { - grp = new ExclusiveGroup(grp); + _grp = this.nextSibling.conditionGroup; + } else if (typeof grp === 'string') { + _grp = new ExclusiveGroup(grp); } } - if (this._conditionGroup !== grp) { - this.getExtraProp('conditionGroup', true)?.setValue(grp.name); + if (_grp && this._conditionGroup !== _grp) { + this.getExtraProp('conditionGroup', true)?.setValue(_grp.name); if (this._conditionGroup) { this._conditionGroup.remove(this); } - this._conditionGroup = grp; - grp.add(this); + this._conditionGroup = _grp; + _grp?.add(this); } } @@ -749,13 +741,17 @@ export class Node * @param {INode} node * @param {object} data */ - replaceChild(node: INode, data: any): INode { + replaceChild(node: INode, data: any): INode | null { if (this.children?.has(node)) { const selected = this.document.selection.has(node.id); delete data.id; const newNode = this.document.createNode(data); + if (!isNode(newNode)) { + return null; + } + this.insertBefore(newNode, node, false); node.remove(false); @@ -838,39 +834,45 @@ export class Node /** * 获取节点在父容器中的索引 */ - @computed get index(): number { + @computed get index(): number | undefined { if (!this.parent) { return -1; } - return this.parent.children.indexOf(this); + return this.parent.children?.indexOf(this); } /** * 获取下一个兄弟节点 */ - get nextSibling(): INode | null { + get nextSibling(): INode | null | undefined { if (!this.parent) { return null; } const { index } = this; + if (typeof index !== 'number') { + return null; + } if (index < 0) { return null; } - return this.parent.children.get(index + 1); + return this.parent.children?.get(index + 1); } /** * 获取上一个兄弟节点 */ - get prevSibling(): INode | null { + get prevSibling(): INode | null | undefined { if (!this.parent) { return null; } const { index } = this; + if (typeof index !== 'number') { + return null; + } if (index < 1) { return null; } - return this.parent.children.get(index - 1); + return this.parent.children?.get(index - 1); } /** @@ -889,7 +891,7 @@ export class Node if (this.isSlot()) { foreachReverse( this.children!, - (subNode: Node) => { + (subNode: INode) => { subNode.remove(true, true); }, (iterable, idx) => (iterable as NodeChildren).get(idx), @@ -954,7 +956,7 @@ export class Node ...this.document.designer.transformProps(_extras_, this, stage), }; - if (this.isParental() && this.children.size > 0 && !options.bypassChildren) { + if (this.isParental() && this.children && this.children.size > 0 && !options.bypassChildren) { schema.children = this.children.export(stage); } @@ -971,7 +973,7 @@ export class Node /** * 获取特定深度的父亲节点 */ - getZLevelTop(zLevel: number): Node | null { + getZLevelTop(zLevel: number): INode | null { return getZLevelTop(this, zLevel); } @@ -983,11 +985,11 @@ export class Node * 2 thisNode before or after otherNode * 0 thisNode same as otherNode */ - comparePosition(otherNode: Node): PositionNO { + comparePosition(otherNode: INode): PositionNO { return comparePosition(this, otherNode); } - unlinkSlot(slotNode: Node) { + unlinkSlot(slotNode: INode) { const i = this._slots.indexOf(slotNode); if (i < 0) { return false; @@ -998,7 +1000,7 @@ export class Node /** * 删除一个Slot节点 */ - removeSlot(slotNode: Node): boolean { + removeSlot(slotNode: INode): boolean { // if (purge) { // // should set parent null // slotNode?.internalSetParent(null, false); @@ -1039,7 +1041,7 @@ export class Node * 删除一个节点 * @param node */ - removeChild(node: Node) { + removeChild(node: INode) { this.children?.delete(node); } @@ -1090,7 +1092,7 @@ export class Node return this.componentName; } - insert(node: Node, ref?: INode, useMutator = true) { + insert(node: INode, ref?: INode, useMutator = true) { this.insertAfter(node, ref, useMutator); } @@ -1101,7 +1103,7 @@ export class Node insertAfter(node: any, ref?: INode, useMutator = true) { const nodeInstance = ensureNode(node, this.document); - this.children?.internalInsert(nodeInstance, ref ? ref.index + 1 : null, useMutator); + this.children?.internalInsert(nodeInstance, ref ? (ref.index || 0) + 1 : null, useMutator); } getParent() { @@ -1128,7 +1130,7 @@ export class Node return this.props; } - onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable { + onChildrenChange(fn: (param?: { type: string; node: INode }) => void): IPublicTypeDisposable | undefined { const wrappedFunc = wrapWithEventSwitch(fn); return this.children?.onChange(wrappedFunc); } @@ -1330,7 +1332,7 @@ export class Node } } -function ensureNode(node: any, document: DocumentModel): Node { +function ensureNode(node: any, document: IDocumentModel): INode { let nodeInstance = node; if (!isNode(node)) { if (node.getComponentName) { @@ -1443,20 +1445,24 @@ export function insertChild( thing: INode | IPublicTypeNodeData, at?: number | null, copy?: boolean, -): INode { - let node: INode; - if (isNode(thing) && (copy || thing.isSlot())) { - thing = thing.export(IPublicEnumTransformStage.Clone); - } - if (isNode(thing)) { +): INode | null { + let node: INode | null | RootNode | undefined; + let nodeSchema: IPublicTypeNodeSchema; + if (isNode(thing) && (copy || thing.isSlot())) { + nodeSchema = thing.export(IPublicEnumTransformStage.Clone); + node = container.document?.createNode(nodeSchema); + } else if (isNode(thing)) { node = thing; - } else { - node = container.document.createNode(thing); + } else if (isNodeSchema(thing)) { + node = container.document?.createNode(thing); } - container.children.insert(node, at); + if (isNode(node)) { + container.children?.insert(node, at); + return node; + } - return node; + return null; } export function insertChildren( diff --git a/packages/designer/src/document/node/props/prop.ts b/packages/designer/src/document/node/props/prop.ts index 90d72668a..bb6797d45 100644 --- a/packages/designer/src/document/node/props/prop.ts +++ b/packages/designer/src/document/node/props/prop.ts @@ -11,14 +11,14 @@ export const UNSET = Symbol.for('unset'); // eslint-disable-next-line no-redeclare export type UNSET = typeof UNSET; -export interface IProp extends Omit { +export interface IProp extends Omit, 'exportSchema' | 'node' > { readonly props: IProps; readonly owner: INode; - get slotNode(): INode | null; - delete(prop: Prop): void; export(stage: IPublicEnumTransformStage): IPublicTypeCompositeValue; @@ -26,6 +26,10 @@ export interface IProp extends Omit, | 'getExtraProp' | 'getExtraPropValue' | 'setExtraPropValue' | 'node'> { @@ -52,6 +50,12 @@ export interface IProps extends Omit, | 'getExtraProp' | }; merge(value: IPublicTypePropsMap, extras?: IPublicTypePropsMap): void; + + purge(): void; + + query(path: string, createIfNone: boolean): Prop | null; + + import(value?: IPublicTypePropsMap | IPublicTypePropsList | null, extras?: ExtrasObject): void; } export class Props implements IProps, IPropParent { diff --git a/packages/designer/tests/document/document-model/document-model.test.ts b/packages/designer/tests/document/document-model/document-model.test.ts index 753c840a5..b47200cba 100644 --- a/packages/designer/tests/document/document-model/document-model.test.ts +++ b/packages/designer/tests/document/document-model/document-model.test.ts @@ -23,7 +23,7 @@ describe('document-model 测试', () => { it('empty schema', () => { const doc = new DocumentModel(project); - expect(doc.rootNode.id).toBe('root'); + expect(doc.rootNode?.id).toBe('root'); expect(doc.currentRoot).toBe(doc.rootNode); expect(doc.root).toBe(doc.rootNode); expect(doc.modalNode).toBeUndefined(); diff --git a/packages/designer/tests/document/node/node.add.test.ts b/packages/designer/tests/document/node/node.add.test.ts index bede02196..87a4222cd 100644 --- a/packages/designer/tests/document/node/node.add.test.ts +++ b/packages/designer/tests/document/node/node.add.test.ts @@ -1,8 +1,8 @@ import set from 'lodash/set'; import cloneDeep from 'lodash/cloneDeep'; import '../../fixtures/window'; -import { Project } from '../../../src/project/project'; -import { Node } from '../../../src/document/node/node'; +import { Project, IProject } from '../../../src/project/project'; +import { Node, INode } from '../../../src/document/node/node'; import { Designer } from '../../../src/designer/designer'; import formSchema from '../../fixtures/schema/form'; import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils'; @@ -37,7 +37,7 @@ beforeAll(() => { describe('schema 生成节点模型测试', () => { describe('block ❌ | component ❌ | slot ❌', () => { - let project: Project; + let project: IProject; beforeEach(() => { project = new Project(designer, { componentsTree: [ @@ -52,12 +52,12 @@ describe('schema 生成节点模型测试', () => { it('基本的节点模型初始化,模型导出', () => { expect(project).toBeTruthy(); const { currentDocument } = project; - const { nodesMap } = currentDocument; + const nodesMap = currentDocument?.nodesMap; const ids = getIdsFromSchema(formSchema); const expectedNodeCnt = ids.length; - expect(nodesMap.size).toBe(expectedNodeCnt); + expect(nodesMap?.size).toBe(expectedNodeCnt); ids.forEach(id => { - expect(nodesMap.get(id).componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName); + expect(nodesMap?.get(id)?.componentName).toBe(getNodeFromSchemaById(formSchema, id).componentName); }); const pageNode = currentDocument?.getNode('page'); @@ -76,18 +76,18 @@ describe('schema 生成节点模型测试', () => { it('基本的节点模型初始化,节点深度', () => { expect(project).toBeTruthy(); const { currentDocument } = project; - const getNode = currentDocument.getNode.bind(currentDocument); + const getNode = currentDocument?.getNode.bind(currentDocument); - const pageNode = getNode('page'); - const rootHeaderNode = getNode('node_k1ow3cba'); - const rootContentNode = getNode('node_k1ow3cbb'); - const rootFooterNode = getNode('node_k1ow3cbc'); - const formNode = getNode('form'); - const cardNode = getNode('node_k1ow3cbj'); - const cardContentNode = getNode('node_k1ow3cbk'); - const columnsLayoutNode = getNode('node_k1ow3cbw'); - const columnNode = getNode('node_k1ow3cbx'); - const textFieldNode = getNode('node_k1ow3cbz'); + const pageNode = getNode?.('page'); + const rootHeaderNode = getNode?.('node_k1ow3cba'); + const rootContentNode = getNode?.('node_k1ow3cbb'); + const rootFooterNode = getNode?.('node_k1ow3cbc'); + const formNode = getNode?.('form'); + const cardNode = getNode?.('node_k1ow3cbj'); + const cardContentNode = getNode?.('node_k1ow3cbk'); + const columnsLayoutNode = getNode?.('node_k1ow3cbw'); + const columnNode = getNode?.('node_k1ow3cbx'); + const textFieldNode = getNode?.('node_k1ow3cbz'); expect(pageNode?.zLevel).toBe(0); expect(rootHeaderNode?.zLevel).toBe(1); @@ -131,7 +131,7 @@ describe('schema 生成节点模型测试', () => { const textFieldNode = getNode('node_k1ow3cbz'); expect(pageNode?.index).toBe(-1); - expect(pageNode?.children.toString()).toBe('[object Array]'); + expect(pageNode?.children?.toString()).toBe('[object Array]'); expect(pageNode?.children?.get(1)).toBe(rootContentNode); expect(pageNode?.getChildren()?.get(1)).toBe(rootContentNode); expect(pageNode?.getNode()).toBe(pageNode); @@ -162,20 +162,20 @@ describe('schema 生成节点模型测试', () => { it('基本的节点模型初始化,节点新建、删除等事件', () => { expect(project).toBeTruthy(); const { currentDocument } = project; - const getNode = currentDocument.getNode.bind(currentDocument); - const createNode = currentDocument.createNode.bind(currentDocument); + const getNode = currentDocument?.getNode.bind(currentDocument); + const createNode = currentDocument?.createNode.bind(currentDocument); - const pageNode = getNode('page'); + const pageNode = getNode?.('page'); const nodeCreateHandler = jest.fn(); const offCreate = currentDocument?.onNodeCreate(nodeCreateHandler); - const node = createNode({ + const node = createNode?.({ componentName: 'TextInput', props: { propA: 'haha', }, }); - currentDocument?.insertNode(pageNode, node); + pageNode && node && currentDocument?.insertNode(pageNode, node); expect(nodeCreateHandler).toHaveBeenCalledTimes(1); expect(nodeCreateHandler.mock.calls[0][0]).toBe(node); @@ -184,7 +184,7 @@ describe('schema 生成节点模型测试', () => { const nodeDestroyHandler = jest.fn(); const offDestroy = currentDocument?.onNodeDestroy(nodeDestroyHandler); - node.remove(); + node?.remove(); expect(nodeDestroyHandler).toHaveBeenCalledTimes(1); expect(nodeDestroyHandler.mock.calls[0][0]).toBe(node); expect(nodeDestroyHandler.mock.calls[0][0].componentName).toBe('TextInput'); @@ -290,9 +290,9 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form'); - currentDocument?.insertNode(formNode, { + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form'); + formNode && currentDocument?.insertNode(formNode, { componentName: 'TextInput', id: 'nodeschema-id1', props: { @@ -300,11 +300,11 @@ describe('schema 生成节点模型测试', () => { propB: 3, }, }, 0); - expect(nodesMap.size).toBe(ids.length + 1); - expect(formNode.children.length).toBe(4); - const insertedNode = formNode.children.get(0); - expect(insertedNode.componentName).toBe('TextInput'); - expect(insertedNode.propsData).toEqual({ + expect(nodesMap?.size).toBe(ids.length + 1); + expect(formNode?.children?.length).toBe(4); + const insertedNode = formNode?.children?.get(0); + expect(insertedNode?.componentName).toBe('TextInput'); + expect(insertedNode?.propsData).toEqual({ propA: 'haha', propB: 3, }); @@ -316,9 +316,9 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form'); - currentDocument?.insertNode(formNode, { + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form'); + formNode && currentDocument?.insertNode(formNode, { componentName: 'TextInput', id: 'nodeschema-id1', props: { @@ -326,11 +326,11 @@ describe('schema 生成节点模型测试', () => { propB: 3, }, }, 1); - expect(nodesMap.size).toBe(ids.length + 1); - expect(formNode.children.length).toBe(4); - const insertedNode = formNode.children.get(1); - expect(insertedNode.componentName).toBe('TextInput'); - expect(insertedNode.propsData).toEqual({ + expect(nodesMap?.size).toBe(ids.length + 1); + expect(formNode?.children?.length).toBe(4); + const insertedNode = formNode?.children?.get(1); + expect(insertedNode?.componentName).toBe('TextInput'); + expect(insertedNode?.propsData).toEqual({ propA: 'haha', propB: 3, }); @@ -342,8 +342,8 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form') as Node; + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form') as INode; currentDocument?.insertNode(formNode, { componentName: 'ParentNode', props: { @@ -367,8 +367,8 @@ describe('schema 生成节点模型测试', () => { }, ], }); - expect(nodesMap.size).toBe(ids.length + 3); - expect(formNode.children.length).toBe(4); + expect(nodesMap?.size).toBe(ids.length + 3); + expect(formNode.children?.length).toBe(4); expect(formNode.children?.get(3)?.componentName).toBe('ParentNode'); expect(formNode.children?.get(3)?.children?.get(0)?.componentName).toBe('SubNode'); expect(formNode.children?.get(3)?.children?.get(1)?.componentName).toBe('SubNode2'); @@ -378,9 +378,9 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form'); - currentDocument?.insertNode(formNode, { + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form'); + formNode && currentDocument?.insertNode(formNode, { componentName: 'TextInput', id: 'nodeschema-id1', props: { @@ -388,17 +388,17 @@ describe('schema 生成节点模型测试', () => { propB: 3, }, }); - expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput'); - expect(nodesMap.size).toBe(ids.length + 1); + expect(nodesMap?.get('nodeschema-id1')?.componentName).toBe('TextInput'); + expect(nodesMap?.size).toBe(ids.length + 1); }); it.skip('场景一:插入 NodeSchema,id 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form'); - currentDocument?.insertNode(formNode, { + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form'); + formNode && currentDocument?.insertNode(formNode, { componentName: 'TextInput', id: 'nodeschema-id1', props: { @@ -406,16 +406,16 @@ describe('schema 生成节点模型测试', () => { propB: 3, }, }); - expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput'); - expect(nodesMap.size).toBe(ids.length + 1); + expect(nodesMap?.get('nodeschema-id1')?.componentName).toBe('TextInput'); + expect(nodesMap?.size).toBe(ids.length + 1); }); it('场景二:插入 Node 实例', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form'); + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form'); const inputNode = currentDocument?.createNode({ componentName: 'TextInput', id: 'nodeschema-id2', @@ -424,22 +424,22 @@ describe('schema 生成节点模型测试', () => { propB: 3, }, }); - currentDocument?.insertNode(formNode, inputNode); - expect(formNode.children?.get(3)?.componentName).toBe('TextInput'); - expect(nodesMap.size).toBe(ids.length + 1); + formNode && currentDocument?.insertNode(formNode, inputNode); + expect(formNode?.children?.get(3)?.componentName).toBe('TextInput'); + expect(nodesMap?.size).toBe(ids.length + 1); }); it('场景三:插入 JSExpression', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form') as Node; + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form') as Node; currentDocument?.insertNode(formNode, { type: 'JSExpression', value: 'just a expression', }); - expect(nodesMap.size).toBe(ids.length + 1); + expect(nodesMap?.size).toBe(ids.length + 1); expect(formNode.children?.get(3)?.componentName).toBe('Leaf'); // expect(formNode.children?.get(3)?.children).toEqual({ // type: 'JSExpression', @@ -450,10 +450,10 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form') as Node; + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form') as Node; currentDocument?.insertNode(formNode, 'just a string'); - expect(nodesMap.size).toBe(ids.length + 1); + expect(nodesMap?.size).toBe(ids.length + 1); expect(formNode.children?.get(3)?.componentName).toBe('Leaf'); // expect(formNode.children?.get(3)?.children).toBe('just a string'); }); @@ -473,8 +473,8 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form') as Node; + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form') as Node; const formNode2 = currentDocument?.getNode('form'); expect(formNode).toEqual(formNode2); currentDocument?.insertNodes(formNode, [ @@ -493,17 +493,17 @@ describe('schema 生成节点模型测试', () => { }, }, ], 1); - expect(nodesMap.size).toBe(ids.length + 2); + expect(nodesMap?.size).toBe(ids.length + 2); expect(formNode.children?.length).toBe(5); - const insertedNode1 = formNode.children.get(1); - const insertedNode2 = formNode.children.get(2); - expect(insertedNode1.componentName).toBe('TextInput'); - expect(insertedNode1.propsData).toEqual({ + const insertedNode1 = formNode.children?.get(1); + const insertedNode2 = formNode.children?.get(2); + expect(insertedNode1?.componentName).toBe('TextInput'); + expect(insertedNode1?.propsData).toEqual({ propA: 'haha2', propB: 3, }); - expect(insertedNode2.componentName).toBe('TextInput2'); - expect(insertedNode2.propsData).toEqual({ + expect(insertedNode2?.componentName).toBe('TextInput2'); + expect(insertedNode2?.propsData).toEqual({ propA: 'haha', propB: 3, }); @@ -513,8 +513,8 @@ describe('schema 生成节点模型测试', () => { expect(project).toBeTruthy(); const ids = getIdsFromSchema(formSchema); const { currentDocument } = project; - const { nodesMap } = currentDocument; - const formNode = nodesMap.get('form') as Node; + const nodesMap = currentDocument?.nodesMap; + const formNode = nodesMap?.get('form') as INode; const formNode2 = currentDocument?.getNode('form'); expect(formNode).toEqual(formNode2); const createdNode1 = currentDocument?.createNode({ @@ -532,17 +532,17 @@ describe('schema 生成节点模型测试', () => { }, }); currentDocument?.insertNodes(formNode, [createdNode1, createdNode2], 1); - expect(nodesMap.size).toBe(ids.length + 2); + expect(nodesMap?.size).toBe(ids.length + 2); expect(formNode.children?.length).toBe(5); - const insertedNode1 = formNode.children.get(1); - const insertedNode2 = formNode.children.get(2); - expect(insertedNode1.componentName).toBe('TextInput'); - expect(insertedNode1.propsData).toEqual({ + const insertedNode1 = formNode.children?.get(1); + const insertedNode2 = formNode.children?.get(2); + expect(insertedNode1?.componentName).toBe('TextInput'); + expect(insertedNode1?.propsData).toEqual({ propA: 'haha2', propB: 3, }); - expect(insertedNode2.componentName).toBe('TextInput2'); - expect(insertedNode2.propsData).toEqual({ + expect(insertedNode2?.componentName).toBe('TextInput2'); + expect(insertedNode2?.propsData).toEqual({ propA: 'haha', propB: 3, }); @@ -561,13 +561,13 @@ describe('schema 生成节点模型测试', () => { project.open(); expect(project).toBeTruthy(); const { currentDocument } = project; - const { nodesMap } = currentDocument; + const nodesMap = currentDocument?.nodesMap; const ids = getIdsFromSchema(formSchema); // 目前每个 slot 会新增(1 + children.length)个节点 const expectedNodeCnt = ids.length + 2; - expect(nodesMap.size).toBe(expectedNodeCnt); + expect(nodesMap?.size).toBe(expectedNodeCnt); // PageHeader - expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1); + expect(nodesMap?.get('node_k1ow3cbd')?.slots).toHaveLength(1); }); }); }); diff --git a/packages/shell/src/model/condition-group.ts b/packages/shell/src/model/condition-group.ts new file mode 100644 index 000000000..22b926615 --- /dev/null +++ b/packages/shell/src/model/condition-group.ts @@ -0,0 +1,42 @@ +import { IExclusiveGroup } from '@alilc/lowcode-designer'; +import { IPublicModelExclusiveGroup, IPublicModelNode } from '@alilc/lowcode-types'; +import { conditionGroupSymbol, nodeSymbol } from '../symbols'; +import { Node } from './node'; + +export class ConditionGroup implements IPublicModelExclusiveGroup { + private [conditionGroupSymbol]: IExclusiveGroup | null; + + constructor(conditionGroup: IExclusiveGroup | null) { + this[conditionGroupSymbol] = conditionGroup; + } + + get id() { + return this[conditionGroupSymbol]?.id; + } + + get title() { + return this[conditionGroupSymbol]?.title; + } + + get firstNode() { + return Node.create(this[conditionGroupSymbol]?.firstNode); + } + + setVisible(node: IPublicModelNode) { + this[conditionGroupSymbol]?.setVisible((node as any)[nodeSymbol] ? (node as any)[nodeSymbol] : node); + } + + static create(conditionGroup: IExclusiveGroup | null) { + if (!conditionGroup) { + return null; + } + // @ts-ignore + if (conditionGroup[conditionGroupSymbol]) { + return (conditionGroup as any)[conditionGroupSymbol]; + } + const shellConditionGroup = new ConditionGroup(conditionGroup); + // @ts-ignore + shellConditionGroup[conditionGroupSymbol] = shellConditionGroup; + return shellConditionGroup; + } +} \ No newline at end of file diff --git a/packages/shell/src/model/node.ts b/packages/shell/src/model/node.ts index 9b407de5c..249e87f46 100644 --- a/packages/shell/src/model/node.ts +++ b/packages/shell/src/model/node.ts @@ -27,6 +27,7 @@ import { ComponentMeta as ShellComponentMeta } from './component-meta'; import { SettingTopEntry as ShellSettingTopEntry } from './setting-top-entry'; import { documentSymbol, nodeSymbol } from '../symbols'; import { ReactElement } from 'react'; +import { ConditionGroup } from './condition-group'; const shellNodeSymbol = Symbol('shellNodeSymbol'); @@ -289,7 +290,7 @@ export class Node implements IPublicModelNode { /** * 当前节点为插槽节点时,返回节点对应的属性实例 */ - get slotFor(): IPublicModelProp | null { + get slotFor(): IPublicModelProp | null | undefined { return ShellProp.create(this[nodeSymbol].slotFor); } @@ -349,7 +350,6 @@ export class Node implements IPublicModelNode { /** * 获取节点实例对应的 dom 节点 - * @deprecated */ getDOMNode() { return (this[nodeSymbol] as any).getDOMNode(); @@ -362,9 +362,9 @@ export class Node implements IPublicModelNode { * @param sorter */ mergeChildren( - remover: (node: Node, idx: number) => boolean, - adder: (children: Node[]) => any, - sorter: (firstNode: Node, secondNode: Node) => number, + remover: (node: IPublicModelNode, idx: number) => boolean, + adder: (children: IPublicModelNode[]) => any, + sorter: (firstNode: IPublicModelNode, secondNode: IPublicModelNode) => number, ): any { return this.children?.mergeChildren(remover, adder, sorter); } @@ -641,7 +641,7 @@ export class Node implements IPublicModelNode { * @since v1.1.0 */ get conditionGroup(): IPublicModelExclusiveGroup | null { - return this[nodeSymbol].conditionGroup; + return ConditionGroup.create(this[nodeSymbol].conditionGroup); } /** diff --git a/packages/shell/src/symbols.ts b/packages/shell/src/symbols.ts index 91ad609ac..83d831747 100644 --- a/packages/shell/src/symbols.ts +++ b/packages/shell/src/symbols.ts @@ -33,3 +33,4 @@ export const resourceTypeSymbol = Symbol('resourceType'); export const resourceSymbol = Symbol('resource'); export const clipboardSymbol = Symbol('clipboard'); export const configSymbol = Symbol('configSymbol'); +export const conditionGroupSymbol = Symbol('conditionGroup'); diff --git a/packages/types/src/shell/model/exclusive-group.ts b/packages/types/src/shell/model/exclusive-group.ts index 1fbfe089a..b930a1344 100644 --- a/packages/types/src/shell/model/exclusive-group.ts +++ b/packages/types/src/shell/model/exclusive-group.ts @@ -1,8 +1,10 @@ -import { IPublicModelNode } from '..'; +import { IPublicModelNode, IPublicTypeTitleContent } from '..'; -export interface IPublicModelExclusiveGroup { - readonly id: string; - readonly title: string; - get firstNode(): IPublicModelNode; +export interface IPublicModelExclusiveGroup< + Node = IPublicModelNode, +> { + readonly id: string | undefined; + readonly title: IPublicTypeTitleContent | undefined; + get firstNode(): Node | null; setVisible(node: Node): void; } diff --git a/packages/types/src/shell/model/node.ts b/packages/types/src/shell/model/node.ts index dd6db71bf..ba924d6d5 100644 --- a/packages/types/src/shell/model/node.ts +++ b/packages/types/src/shell/model/node.ts @@ -8,7 +8,10 @@ export interface IBaseModelNode< Node = IPublicModelNode, NodeChildren = IPublicModelNodeChildren, ComponentMeta = IPublicModelComponentMeta, - SettingTopEntry = IPublicModelSettingTopEntry + SettingTopEntry = IPublicModelSettingTopEntry, + Props = IPublicModelProps, + Prop = IPublicModelProp, + ExclusiveGroup = IPublicModelExclusiveGroup > { /** @@ -167,7 +170,7 @@ export interface IBaseModelNode< * 下标 * index */ - get index(): number; + get index(): number | undefined; /** * 图标 @@ -203,13 +206,13 @@ export interface IBaseModelNode< * 获取当前节点的前一个兄弟节点 * get previous sibling of this node */ - get prevSibling(): Node | null; + get prevSibling(): Node | null | undefined; /** * 获取当前节点的后一个兄弟节点 * get next sibling of this node */ - get nextSibling(): Node | null; + get nextSibling(): Node | null | undefined; /** * 获取当前节点的父亲节点 @@ -233,13 +236,13 @@ export interface IBaseModelNode< * 当前节点为插槽节点时,返回节点对应的属性实例 * return coresponding prop when this node is a slot node */ - get slotFor(): IPublicModelProp | null; + get slotFor(): Prop | null | undefined; /** * 返回节点的属性集 * get props */ - get props(): IPublicModelProps | null; + get props(): Props | null; /** * 返回节点的属性集 @@ -250,7 +253,7 @@ export interface IBaseModelNode< /** * get conditionGroup */ - get conditionGroup(): IPublicModelExclusiveGroup | null; + get conditionGroup(): ExclusiveGroup | null; /** * 获取符合搭建协议 - 节点 schema 结构 @@ -295,7 +298,7 @@ export interface IBaseModelNode< * get prop by path * @param path 属性路径,支持 a / a.b / a.0 等格式 */ - getProp(path: string, createIfNone: boolean): IPublicModelProp | null; + getProp(path: string, createIfNone: boolean): Prop | null; /** * 获取指定 path 的属性模型实例值 @@ -313,7 +316,7 @@ export interface IBaseModelNode< * @param path 属性路径,支持 a / a.b / a.0 等格式 * @param createIfNone 当没有属性的时候,是否创建一个属性 */ - getExtraProp(path: string, createIfNone?: boolean): IPublicModelProp | null; + getExtraProp(path: string, createIfNone?: boolean): Prop | null; /** * 获取指定 path 的属性模型实例, @@ -481,6 +484,11 @@ export interface IBaseModelNode< * @since v1.1.0 */ setConditionalVisible(): void; + + /** + * 获取节点实例对应的 dom 节点 + */ + getDOMNode(): HTMLElement; } export interface IPublicModelNode extends IBaseModelNode {} \ No newline at end of file diff --git a/packages/types/src/shell/model/prop.ts b/packages/types/src/shell/model/prop.ts index 7ac906762..71442e64a 100644 --- a/packages/types/src/shell/model/prop.ts +++ b/packages/types/src/shell/model/prop.ts @@ -2,7 +2,9 @@ import { IPublicEnumTransformStage } from '../enum'; import { IPublicTypeCompositeValue } from '../type'; import { IPublicModelNode } from './'; -export interface IPublicModelProp { +export interface IPublicModelProp< + Node = IPublicModelNode +> { /** * id @@ -25,14 +27,14 @@ export interface IPublicModelProp { * 返回所属的节点实例 * get node instance, which this prop belongs to */ - get node(): IPublicModelNode | null; + get node(): Node | null; /** * 当本 prop 代表一个 Slot 时,返回对应的 slotNode * return the slot node (only if the current prop represents a slot) * @since v1.1.0 */ - get slotNode(): IPublicModelNode | undefined | null; + get slotNode(): Node | undefined | null; /** * 是否是 Prop , 固定返回 true diff --git a/packages/types/src/shell/type/metadata.ts b/packages/types/src/shell/type/metadata.ts index 7ee0228c9..39022d48f 100644 --- a/packages/types/src/shell/type/metadata.ts +++ b/packages/types/src/shell/type/metadata.ts @@ -195,8 +195,8 @@ export interface IPublicTypeCallbacks { onChildMoveHook?: (childNode: IPublicModelNode, currentNode: IPublicModelNode) => boolean; // events - onNodeRemove?: (removedNode: IPublicModelNode, currentNode: IPublicModelNode) => void; - onNodeAdd?: (addedNode: IPublicModelNode, currentNode: IPublicModelNode) => void; + onNodeRemove?: (removedNode: IPublicModelNode | null, currentNode: IPublicModelNode | null) => void; + onNodeAdd?: (addedNode: IPublicModelNode | null, currentNode: IPublicModelNode | null) => void; onSubtreeModified?: (currentNode: IPublicModelNode, options: any) => void; onResize?: ( e: MouseEvent & { diff --git a/packages/utils/src/check-types/is-dom-text.ts b/packages/utils/src/check-types/is-dom-text.ts index 47edec139..950954440 100644 --- a/packages/utils/src/check-types/is-dom-text.ts +++ b/packages/utils/src/check-types/is-dom-text.ts @@ -1,3 +1,3 @@ -export function isDOMText(data: any): boolean { +export function isDOMText(data: any): data is string { return typeof data === 'string'; } diff --git a/packages/utils/src/check-types/is-node-schema.ts b/packages/utils/src/check-types/is-node-schema.ts index 0bfb4d035..bfc3ff3f2 100644 --- a/packages/utils/src/check-types/is-node-schema.ts +++ b/packages/utils/src/check-types/is-node-schema.ts @@ -1,5 +1,5 @@ import { IPublicTypeNodeSchema } from '@alilc/lowcode-types'; export function isNodeSchema(data: any): data is IPublicTypeNodeSchema { - return data && data.componentName; + return data && data.componentName && !data.isNode; }