feat: add getDOMNode api definition in node module

This commit is contained in:
liujuping 2023-02-28 17:41:19 +08:00 committed by 林熠
parent 9228a2baa7
commit 964833128b
21 changed files with 334 additions and 228 deletions

View File

@ -645,3 +645,14 @@ setConditionalVisible(): void;
``` ```
**@since v1.1.0** **@since v1.1.0**
### getDOMNode
获取节点实例对应的 dom 节点
```typescript
/**
* 获取节点实例对应的 dom 节点
*/
getDOMNode(): HTMLElement;
```

View File

@ -15,6 +15,7 @@ const jestConfig = {
// testMatch: ['**/document-model.test.ts'], // testMatch: ['**/document-model.test.ts'],
// testMatch: ['**/prop.test.ts'], // testMatch: ['**/prop.test.ts'],
// testMatch: ['(/tests?/.*(test))\\.[jt]s$'], // testMatch: ['(/tests?/.*(test))\\.[jt]s$'],
// testMatch: ['**/document/node/node.add.test.ts'],
transformIgnorePatterns: [ transformIgnorePatterns: [
`/node_modules/(?!${esModules})/`, `/node_modules/(?!${esModules})/`,
], ],

View File

@ -71,6 +71,8 @@ export interface IDesigner {
get editor(): IPublicModelEditor; get editor(): IPublicModelEditor;
get detecting(): Detecting;
createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller; createScroller(scrollable: IPublicTypeScrollable): IPublicModelScroller;
/** /**

View File

@ -17,6 +17,7 @@ function generateSessionId(nodes: INode[]) {
} }
export interface ISettingTopEntry extends ISettingEntry { export interface ISettingTopEntry extends ISettingEntry {
purge(): void;
} }
export class SettingTopEntry implements ISettingTopEntry { export class SettingTopEntry implements ISettingTopEntry {

View File

@ -87,6 +87,8 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel<
get active(): boolean; get active(): boolean;
get nodesMap(): Map<string, INode>;
/** /**
* id * id
*/ */
@ -114,6 +116,12 @@ export interface IDocumentModel extends Omit< IPublicModelDocumentModel<
onChangeNodeVisible(fn: (node: INode, visible: boolean) => void): IPublicTypeDisposable; onChangeNodeVisible(fn: (node: INode, visible: boolean) => void): IPublicTypeDisposable;
addWillPurge(node: INode): void; 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 { export class DocumentModel implements IDocumentModel {
@ -379,7 +387,7 @@ export class DocumentModel implements IDocumentModel {
* schema * schema
*/ */
@action @action
createNode<T extends INode = INode, C = undefined>(data: GetDataType<C, T>, checkId: boolean = true): T { createNode<T extends INode = INode, C = undefined>(data: GetDataType<C, T>): T {
let schema: any; let schema: any;
if (isDOMText(data) || isJSExpression(data)) { if (isDOMText(data) || isJSExpression(data)) {
schema = { schema = {
@ -410,7 +418,7 @@ export class DocumentModel implements IDocumentModel {
} }
} }
if (!node) { if (!node) {
node = new Node(this, schema, { checkId }); node = new Node(this, schema);
// will add // will add
// todo: this.activeNodes?.push(node); // 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); return insertChild(parent, thing, at, copy);
} }
@ -445,7 +453,7 @@ export class DocumentModel implements IDocumentModel {
*/ */
removeNode(idOrNode: string | INode) { removeNode(idOrNode: string | INode) {
let id: string; let id: string;
let node: INode | null; let node: INode | null = null;
if (typeof idOrNode === 'string') { if (typeof idOrNode === 'string') {
id = idOrNode; id = idOrNode;
node = this.getNode(id); node = this.getNode(id);
@ -859,7 +867,7 @@ export class DocumentModel implements IDocumentModel {
onReady(fn: Function) { onReady(fn: Function) {
this.designer.editor.eventBus.on('document-open', fn); this.designer.editor.eventBus.on('document-open', fn);
return () => { return () => {
this.designer.editor.removeListener('document-open', fn); this.designer.editor.eventBus.off('document-open', fn);
}; };
} }

View File

@ -1,18 +1,30 @@
import { obx, computed, makeObservable } from '@alilc/lowcode-editor-core'; import { obx, computed, makeObservable } from '@alilc/lowcode-editor-core';
import { uniqueId } from '@alilc/lowcode-utils'; import { uniqueId } from '@alilc/lowcode-utils';
import { IPublicTypeTitleContent, IPublicModelExclusiveGroup } from '@alilc/lowcode-types'; import { IPublicTypeTitleContent, IPublicModelExclusiveGroup } from '@alilc/lowcode-types';
import { Node } from './node'; import { INode } from './node';
import { intl } from '../../locale'; import { intl } from '../../locale';
export interface IExclusiveGroup extends IPublicModelExclusiveGroup<INode> {
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 // 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, // if-else-if assoc conditionGroup value, should be the same level,
// and siblings, need renderEngine support // and siblings, need renderEngine support
export class ExclusiveGroup implements IPublicModelExclusiveGroup { export class ExclusiveGroup implements IExclusiveGroup {
readonly isExclusiveGroup = true; readonly isExclusiveGroup = true;
readonly id = uniqueId('exclusive'); readonly id = uniqueId('exclusive');
@obx.shallow readonly children: Node[] = []; readonly title: IPublicTypeTitleContent;
@obx.shallow readonly children: INode[] = [];
@obx private visibleIndex = 0; @obx private visibleIndex = 0;
@ -28,11 +40,11 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
return this.children.length; return this.children.length;
} }
@computed get visibleNode(): Node { @computed get visibleNode(): INode {
return this.children[this.visibleIndex]; return this.children[this.visibleIndex];
} }
@computed get firstNode(): Node { @computed get firstNode(): INode {
return this.children[0]!; return this.children[0]!;
} }
@ -40,8 +52,16 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
return this.firstNode.index; return this.firstNode.index;
} }
add(node: Node) { constructor(readonly name: string, title?: IPublicTypeTitleContent) {
if (node.nextSibling && node.nextSibling.conditionGroup === this) { 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); const i = this.children.indexOf(node.nextSibling);
this.children.splice(i, 0, node); this.children.splice(i, 0, node);
} else { } else {
@ -49,7 +69,7 @@ export class ExclusiveGroup implements IPublicModelExclusiveGroup {
} }
} }
remove(node: Node) { remove(node: INode) {
const i = this.children.indexOf(node); const i = this.children.indexOf(node);
if (i > -1) { if (i > -1) {
this.children.splice(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); const i = this.children.indexOf(node);
if (i > -1) { if (i > -1) {
this.visibleIndex = i; this.visibleIndex = i;
} }
} }
isVisible(node: Node) { isVisible(node: INode) {
const i = this.children.indexOf(node); const i = this.children.indexOf(node);
return i === this.visibleIndex; 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 { export function isExclusiveGroup(obj: any): obj is ExclusiveGroup {

View File

@ -1,6 +1,6 @@
import { obx, computed, globalContext, makeObservable, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core'; import { obx, computed, globalContext, makeObservable, IEventBus, createModuleEventBus } from '@alilc/lowcode-editor-core';
import { Node, INode } from './node'; 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 { shallowEqual, compatStage, isNodeSchema } from '@alilc/lowcode-utils';
import { foreachReverse } from '../../utils/tree'; import { foreachReverse } from '../../utils/tree';
import { NodeRemoveOptions } from '../../types'; import { NodeRemoveOptions } from '../../types';
@ -18,6 +18,8 @@ export interface INodeChildren extends Omit<IPublicModelNodeChildren<INode>,
> { > {
get owner(): INode; get owner(): INode;
get length(): number;
unlinkChild(node: INode): void; unlinkChild(node: INode): void;
/** /**
@ -58,6 +60,8 @@ export interface INodeChildren extends Omit<IPublicModelNodeChildren<INode>,
internalInitParent(): void; internalInitParent(): void;
onChange(fn: (info?: IOnChangeOptions) => void): IPublicTypeDisposable;
/** overriding methods end */ /** overriding methods end */
} }
export class NodeChildren implements INodeChildren { 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); this.emitter.on('change', fn);
return () => { return () => {
this.emitter.removeListener('change', fn); this.emitter.removeListener('change', fn);

View File

@ -18,14 +18,14 @@ import {
IPublicTypeDisposable, IPublicTypeDisposable,
IBaseModelNode, IBaseModelNode,
} from '@alilc/lowcode-types'; } from '@alilc/lowcode-types';
import { compatStage, isDOMText, isJSExpression, isNode } from '@alilc/lowcode-utils'; import { compatStage, isDOMText, isJSExpression, isNode, isNodeSchema } from '@alilc/lowcode-utils';
import { ISettingTopEntry, SettingTopEntry } from '@alilc/lowcode-designer'; import { ISettingTopEntry } from '@alilc/lowcode-designer';
import { Props, getConvertedExtraKey, IProps } from './props/props'; 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 { NodeChildren, INodeChildren } from './node-children';
import { IProp, Prop } from './props/prop'; import { IProp, Prop } from './props/prop';
import { ComponentMeta, IComponentMeta } from '../../component-meta'; import { IComponentMeta } from '../../component-meta';
import { ExclusiveGroup, isExclusiveGroup } from './exclusive-group'; import { ExclusiveGroup, IExclusiveGroup, isExclusiveGroup } from './exclusive-group';
import { includeSlot, removeSlot } from '../../utils/slot'; import { includeSlot, removeSlot } from '../../utils/slot';
import { foreachReverse } from '../../utils/tree'; import { foreachReverse } from '../../utils/tree';
import { NodeRemoveOptions, EDITOR_EVENT } from '../../types'; import { NodeRemoveOptions, EDITOR_EVENT } from '../../types';
@ -42,14 +42,11 @@ export interface INode extends Omit<IBaseModelNode<
INode, INode,
INodeChildren, INodeChildren,
IComponentMeta, IComponentMeta,
ISettingTopEntry ISettingTopEntry,
IProps,
IProp,
IExclusiveGroup
>, >,
'slots' |
'slotFor' |
'props' |
'getProp' |
'getExtraProp' |
'replaceChild' |
'isRoot' | 'isRoot' |
'isPage' | 'isPage' |
'isComponent' | 'isComponent' |
@ -64,29 +61,21 @@ export interface INode extends Omit<IBaseModelNode<
'exportSchema' | 'exportSchema' |
'visible' | 'visible' |
'importSchema' | 'importSchema' |
'isEmptyNode' |
// 内外实现有差异 // 内外实现有差异
'isContainer' | 'isContainer' |
'isEmpty' 'isEmpty'
> { > {
get slots(): INode[]; isNode: boolean;
/**
*
*/
get slotFor(): IProp | null;
get props(): IProps;
get componentMeta(): IComponentMeta; get componentMeta(): IComponentMeta;
get settingEntry(): SettingTopEntry; get settingEntry(): ISettingTopEntry;
get isPurged(): boolean; 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<IBaseModelNode<
internalPurgeStart(): void; internalPurgeStart(): void;
unlinkSlot(slotNode: Node): void; unlinkSlot(slotNode: INode): void;
/** /**
* schema * schema
@ -117,15 +106,9 @@ export interface INode extends Omit<IBaseModelNode<
onVisibleChange(func: (flag: boolean) => any): () => void; onVisibleChange(func: (flag: boolean) => 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; 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; onPropChange(func: (info: IPublicTypePropChangeOptions) => void): IPublicTypeDisposable;
@ -153,13 +136,17 @@ export interface INode extends Omit<IBaseModelNode<
options?: NodeRemoveOptions, options?: NodeRemoveOptions,
): void; ): void;
didDropIn(dragment: Node): void; didDropIn(dragment: INode): void;
didDropOut(dragment: Node): void; didDropOut(dragment: INode): void;
get isPurging(): boolean;
purge(): void; purge(): void;
removeSlot(slotNode: INode): boolean;
setVisible(flag: boolean): void;
getVisible(): boolean;
} }
/** /**
@ -322,7 +309,11 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return !!this._isRGLContainer; return !!this._isRGLContainer;
} }
private _slotFor?: IProp | null = null; get isEmptyNode() {
return this.isEmpty();
}
private _slotFor?: IProp | null | undefined = null;
@obx.shallow _slots: INode[] = []; @obx.shallow _slots: INode[] = [];
@ -331,10 +322,10 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
} }
/* istanbul ignore next */ /* istanbul ignore next */
@obx.ref private _conditionGroup: IPublicModelExclusiveGroup | null = null; @obx.ref private _conditionGroup: IExclusiveGroup | null = null;
/* istanbul ignore next */ /* istanbul ignore next */
get conditionGroup(): IPublicModelExclusiveGroup | null { get conditionGroup(): IExclusiveGroup | null {
return this._conditionGroup; return this._conditionGroup;
} }
@ -518,7 +509,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
this.document.addWillPurge(this); this.document.addWillPurge(this);
} }
didDropIn(dragment: Node) { didDropIn(dragment: INode) {
const { callbacks } = this.componentMeta.advanced; const { callbacks } = this.componentMeta.advanced;
if (callbacks?.onNodeAdd) { if (callbacks?.onNodeAdd) {
const cbThis = this.internalToShellNode(); const cbThis = this.internalToShellNode();
@ -529,7 +520,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
} }
} }
didDropOut(dragment: Node) { didDropOut(dragment: INode) {
const { callbacks } = this.componentMeta.advanced; const { callbacks } = this.componentMeta.advanced;
if (callbacks?.onNodeRemove) { if (callbacks?.onNodeRemove) {
const cbThis = this.internalToShellNode(); const cbThis = this.internalToShellNode();
@ -590,7 +581,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/** /**
* *
*/ */
get slotFor(): IProp | null { get slotFor(): IProp | null | undefined {
return this._slotFor; return this._slotFor;
} }
@ -610,10 +601,10 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
}); });
} }
if (this.isSlot()) { if (this.isSlot()) {
this.parent.removeSlot(this, purge); this.parent.removeSlot(this);
this.parent.children.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true }); this.parent.children?.internalDelete(this, purge, useMutator, { suppressRemoveEvent: true });
} else { } 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<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/** /**
* *
*/ */
@computed get componentMeta(): ComponentMeta { @computed get componentMeta(): IComponentMeta {
return this.document.getComponentMeta(this.componentName); return this.document.getComponentMeta(this.componentName);
} }
@ -670,6 +661,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/* istanbul ignore next */ /* istanbul ignore next */
setConditionGroup(grp: IPublicModelExclusiveGroup | string | null) { setConditionGroup(grp: IPublicModelExclusiveGroup | string | null) {
let _grp: IExclusiveGroup | null = null;
if (!grp) { if (!grp) {
this.getExtraProp('conditionGroup', false)?.remove(); this.getExtraProp('conditionGroup', false)?.remove();
if (this._conditionGroup) { if (this._conditionGroup) {
@ -680,20 +672,20 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
} }
if (!isExclusiveGroup(grp)) { if (!isExclusiveGroup(grp)) {
if (this.prevSibling?.conditionGroup?.name === grp) { if (this.prevSibling?.conditionGroup?.name === grp) {
grp = this.prevSibling.conditionGroup; _grp = this.prevSibling.conditionGroup;
} else if (this.nextSibling?.conditionGroup?.name === grp) { } else if (this.nextSibling?.conditionGroup?.name === grp) {
grp = this.nextSibling.conditionGroup; _grp = this.nextSibling.conditionGroup;
} else { } else if (typeof grp === 'string') {
grp = new ExclusiveGroup(grp); _grp = new ExclusiveGroup(grp);
} }
} }
if (this._conditionGroup !== grp) { if (_grp && this._conditionGroup !== _grp) {
this.getExtraProp('conditionGroup', true)?.setValue(grp.name); this.getExtraProp('conditionGroup', true)?.setValue(_grp.name);
if (this._conditionGroup) { if (this._conditionGroup) {
this._conditionGroup.remove(this); this._conditionGroup.remove(this);
} }
this._conditionGroup = grp; this._conditionGroup = _grp;
grp.add(this); _grp?.add(this);
} }
} }
@ -749,13 +741,17 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
* @param {INode} node * @param {INode} node
* @param {object} data * @param {object} data
*/ */
replaceChild(node: INode, data: any): INode { replaceChild(node: INode, data: any): INode | null {
if (this.children?.has(node)) { if (this.children?.has(node)) {
const selected = this.document.selection.has(node.id); const selected = this.document.selection.has(node.id);
delete data.id; delete data.id;
const newNode = this.document.createNode(data); const newNode = this.document.createNode(data);
if (!isNode(newNode)) {
return null;
}
this.insertBefore(newNode, node, false); this.insertBefore(newNode, node, false);
node.remove(false); node.remove(false);
@ -838,39 +834,45 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/** /**
* *
*/ */
@computed get index(): number { @computed get index(): number | undefined {
if (!this.parent) { if (!this.parent) {
return -1; 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) { if (!this.parent) {
return null; return null;
} }
const { index } = this; const { index } = this;
if (typeof index !== 'number') {
return null;
}
if (index < 0) { if (index < 0) {
return null; 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) { if (!this.parent) {
return null; return null;
} }
const { index } = this; const { index } = this;
if (typeof index !== 'number') {
return null;
}
if (index < 1) { if (index < 1) {
return null; return null;
} }
return this.parent.children.get(index - 1); return this.parent.children?.get(index - 1);
} }
/** /**
@ -889,7 +891,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
if (this.isSlot()) { if (this.isSlot()) {
foreachReverse( foreachReverse(
this.children!, this.children!,
(subNode: Node) => { (subNode: INode) => {
subNode.remove(true, true); subNode.remove(true, true);
}, },
(iterable, idx) => (iterable as NodeChildren).get(idx), (iterable, idx) => (iterable as NodeChildren).get(idx),
@ -954,7 +956,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
...this.document.designer.transformProps(_extras_, this, stage), ...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); schema.children = this.children.export(stage);
} }
@ -971,7 +973,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/** /**
* *
*/ */
getZLevelTop(zLevel: number): Node | null { getZLevelTop(zLevel: number): INode | null {
return getZLevelTop(this, zLevel); return getZLevelTop(this, zLevel);
} }
@ -983,11 +985,11 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
* 2 thisNode before or after otherNode * 2 thisNode before or after otherNode
* 0 thisNode same as otherNode * 0 thisNode same as otherNode
*/ */
comparePosition(otherNode: Node): PositionNO { comparePosition(otherNode: INode): PositionNO {
return comparePosition(this, otherNode); return comparePosition(this, otherNode);
} }
unlinkSlot(slotNode: Node) { unlinkSlot(slotNode: INode) {
const i = this._slots.indexOf(slotNode); const i = this._slots.indexOf(slotNode);
if (i < 0) { if (i < 0) {
return false; return false;
@ -998,7 +1000,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
/** /**
* Slot节点 * Slot节点
*/ */
removeSlot(slotNode: Node): boolean { removeSlot(slotNode: INode): boolean {
// if (purge) { // if (purge) {
// // should set parent null // // should set parent null
// slotNode?.internalSetParent(null, false); // slotNode?.internalSetParent(null, false);
@ -1039,7 +1041,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
* *
* @param node * @param node
*/ */
removeChild(node: Node) { removeChild(node: INode) {
this.children?.delete(node); this.children?.delete(node);
} }
@ -1090,7 +1092,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.componentName; return this.componentName;
} }
insert(node: Node, ref?: INode, useMutator = true) { insert(node: INode, ref?: INode, useMutator = true) {
this.insertAfter(node, ref, useMutator); this.insertAfter(node, ref, useMutator);
} }
@ -1101,7 +1103,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
insertAfter(node: any, ref?: INode, useMutator = true) { insertAfter(node: any, ref?: INode, useMutator = true) {
const nodeInstance = ensureNode(node, this.document); 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() { getParent() {
@ -1128,7 +1130,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
return this.props; 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); const wrappedFunc = wrapWithEventSwitch(fn);
return this.children?.onChange(wrappedFunc); return this.children?.onChange(wrappedFunc);
} }
@ -1330,7 +1332,7 @@ export class Node<Schema extends IPublicTypeNodeSchema = IPublicTypeNodeSchema>
} }
} }
function ensureNode(node: any, document: DocumentModel): Node { function ensureNode(node: any, document: IDocumentModel): INode {
let nodeInstance = node; let nodeInstance = node;
if (!isNode(node)) { if (!isNode(node)) {
if (node.getComponentName) { if (node.getComponentName) {
@ -1443,20 +1445,24 @@ export function insertChild(
thing: INode | IPublicTypeNodeData, thing: INode | IPublicTypeNodeData,
at?: number | null, at?: number | null,
copy?: boolean, copy?: boolean,
): INode { ): INode | null {
let node: INode; let node: INode | null | RootNode | undefined;
if (isNode(thing) && (copy || thing.isSlot())) { let nodeSchema: IPublicTypeNodeSchema;
thing = thing.export(IPublicEnumTransformStage.Clone); if (isNode<INode>(thing) && (copy || thing.isSlot())) {
} nodeSchema = thing.export(IPublicEnumTransformStage.Clone);
if (isNode(thing)) { node = container.document?.createNode(nodeSchema);
} else if (isNode<INode>(thing)) {
node = thing; node = thing;
} else { } else if (isNodeSchema(thing)) {
node = container.document.createNode(thing); node = container.document?.createNode(thing);
} }
container.children.insert(node, at); if (isNode<INode>(node)) {
container.children?.insert(node, at);
return node;
}
return node; return null;
} }
export function insertChildren( export function insertChildren(

View File

@ -11,14 +11,14 @@ export const UNSET = Symbol.for('unset');
// eslint-disable-next-line no-redeclare // eslint-disable-next-line no-redeclare
export type UNSET = typeof UNSET; export type UNSET = typeof UNSET;
export interface IProp extends Omit<IPublicModelProp, 'exportSchema' | 'node' | 'slotNode' > { export interface IProp extends Omit<IPublicModelProp<
INode
>, 'exportSchema' | 'node' > {
readonly props: IProps; readonly props: IProps;
readonly owner: INode; readonly owner: INode;
get slotNode(): INode | null;
delete(prop: Prop): void; delete(prop: Prop): void;
export(stage: IPublicEnumTransformStage): IPublicTypeCompositeValue; export(stage: IPublicEnumTransformStage): IPublicTypeCompositeValue;
@ -26,6 +26,10 @@ export interface IProp extends Omit<IPublicModelProp, 'exportSchema' | 'node' |
getNode(): INode; getNode(): INode;
getAsString(): string; getAsString(): string;
unset(): void;
get value(): IPublicTypeCompositeValue | UNSET;
} }
export type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot'; export type ValueTypes = 'unset' | 'literal' | 'map' | 'list' | 'expression' | 'slot';

View File

@ -33,8 +33,6 @@ export interface IPropParent {
get path(): string[]; get path(): string[];
delete(prop: Prop): void; delete(prop: Prop): void;
query(path: string, createIfNone: boolean): Prop | null;
} }
export interface IProps extends Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'getExtraPropValue' | 'setExtraPropValue' | 'node'> { export interface IProps extends Omit<IBaseModelProps<IProp>, | 'getExtraProp' | 'getExtraPropValue' | 'setExtraPropValue' | 'node'> {
@ -52,6 +50,12 @@ export interface IProps extends Omit<IBaseModelProps<IProp>, | 'getExtraProp' |
}; };
merge(value: IPublicTypePropsMap, extras?: IPublicTypePropsMap): void; 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 { export class Props implements IProps, IPropParent {

View File

@ -23,7 +23,7 @@ describe('document-model 测试', () => {
it('empty schema', () => { it('empty schema', () => {
const doc = new DocumentModel(project); 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.currentRoot).toBe(doc.rootNode);
expect(doc.root).toBe(doc.rootNode); expect(doc.root).toBe(doc.rootNode);
expect(doc.modalNode).toBeUndefined(); expect(doc.modalNode).toBeUndefined();

View File

@ -1,8 +1,8 @@
import set from 'lodash/set'; import set from 'lodash/set';
import cloneDeep from 'lodash/cloneDeep'; import cloneDeep from 'lodash/cloneDeep';
import '../../fixtures/window'; import '../../fixtures/window';
import { Project } from '../../../src/project/project'; import { Project, IProject } from '../../../src/project/project';
import { Node } from '../../../src/document/node/node'; import { Node, INode } from '../../../src/document/node/node';
import { Designer } from '../../../src/designer/designer'; import { Designer } from '../../../src/designer/designer';
import formSchema from '../../fixtures/schema/form'; import formSchema from '../../fixtures/schema/form';
import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils'; import { getIdsFromSchema, getNodeFromSchemaById } from '../../utils';
@ -37,7 +37,7 @@ beforeAll(() => {
describe('schema 生成节点模型测试', () => { describe('schema 生成节点模型测试', () => {
describe('block ❌ | component ❌ | slot ❌', () => { describe('block ❌ | component ❌ | slot ❌', () => {
let project: Project; let project: IProject;
beforeEach(() => { beforeEach(() => {
project = new Project(designer, { project = new Project(designer, {
componentsTree: [ componentsTree: [
@ -52,12 +52,12 @@ describe('schema 生成节点模型测试', () => {
it('基本的节点模型初始化,模型导出', () => { it('基本的节点模型初始化,模型导出', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const expectedNodeCnt = ids.length; const expectedNodeCnt = ids.length;
expect(nodesMap.size).toBe(expectedNodeCnt); expect(nodesMap?.size).toBe(expectedNodeCnt);
ids.forEach(id => { 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'); const pageNode = currentDocument?.getNode('page');
@ -76,18 +76,18 @@ describe('schema 生成节点模型测试', () => {
it('基本的节点模型初始化,节点深度', () => { it('基本的节点模型初始化,节点深度', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const { currentDocument } = project; const { currentDocument } = project;
const getNode = currentDocument.getNode.bind(currentDocument); const getNode = currentDocument?.getNode.bind(currentDocument);
const pageNode = getNode('page'); const pageNode = getNode?.('page');
const rootHeaderNode = getNode('node_k1ow3cba'); const rootHeaderNode = getNode?.('node_k1ow3cba');
const rootContentNode = getNode('node_k1ow3cbb'); const rootContentNode = getNode?.('node_k1ow3cbb');
const rootFooterNode = getNode('node_k1ow3cbc'); const rootFooterNode = getNode?.('node_k1ow3cbc');
const formNode = getNode('form'); const formNode = getNode?.('form');
const cardNode = getNode('node_k1ow3cbj'); const cardNode = getNode?.('node_k1ow3cbj');
const cardContentNode = getNode('node_k1ow3cbk'); const cardContentNode = getNode?.('node_k1ow3cbk');
const columnsLayoutNode = getNode('node_k1ow3cbw'); const columnsLayoutNode = getNode?.('node_k1ow3cbw');
const columnNode = getNode('node_k1ow3cbx'); const columnNode = getNode?.('node_k1ow3cbx');
const textFieldNode = getNode('node_k1ow3cbz'); const textFieldNode = getNode?.('node_k1ow3cbz');
expect(pageNode?.zLevel).toBe(0); expect(pageNode?.zLevel).toBe(0);
expect(rootHeaderNode?.zLevel).toBe(1); expect(rootHeaderNode?.zLevel).toBe(1);
@ -131,7 +131,7 @@ describe('schema 生成节点模型测试', () => {
const textFieldNode = getNode('node_k1ow3cbz'); const textFieldNode = getNode('node_k1ow3cbz');
expect(pageNode?.index).toBe(-1); 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?.children?.get(1)).toBe(rootContentNode);
expect(pageNode?.getChildren()?.get(1)).toBe(rootContentNode); expect(pageNode?.getChildren()?.get(1)).toBe(rootContentNode);
expect(pageNode?.getNode()).toBe(pageNode); expect(pageNode?.getNode()).toBe(pageNode);
@ -162,20 +162,20 @@ describe('schema 生成节点模型测试', () => {
it('基本的节点模型初始化,节点新建、删除等事件', () => { it('基本的节点模型初始化,节点新建、删除等事件', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const { currentDocument } = project; const { currentDocument } = project;
const getNode = currentDocument.getNode.bind(currentDocument); const getNode = currentDocument?.getNode.bind(currentDocument);
const createNode = currentDocument.createNode.bind(currentDocument); const createNode = currentDocument?.createNode.bind(currentDocument);
const pageNode = getNode('page'); const pageNode = getNode?.('page');
const nodeCreateHandler = jest.fn(); const nodeCreateHandler = jest.fn();
const offCreate = currentDocument?.onNodeCreate(nodeCreateHandler); const offCreate = currentDocument?.onNodeCreate(nodeCreateHandler);
const node = createNode({ const node = createNode?.({
componentName: 'TextInput', componentName: 'TextInput',
props: { props: {
propA: 'haha', propA: 'haha',
}, },
}); });
currentDocument?.insertNode(pageNode, node); pageNode && node && currentDocument?.insertNode(pageNode, node);
expect(nodeCreateHandler).toHaveBeenCalledTimes(1); expect(nodeCreateHandler).toHaveBeenCalledTimes(1);
expect(nodeCreateHandler.mock.calls[0][0]).toBe(node); expect(nodeCreateHandler.mock.calls[0][0]).toBe(node);
@ -184,7 +184,7 @@ describe('schema 生成节点模型测试', () => {
const nodeDestroyHandler = jest.fn(); const nodeDestroyHandler = jest.fn();
const offDestroy = currentDocument?.onNodeDestroy(nodeDestroyHandler); const offDestroy = currentDocument?.onNodeDestroy(nodeDestroyHandler);
node.remove(); node?.remove();
expect(nodeDestroyHandler).toHaveBeenCalledTimes(1); expect(nodeDestroyHandler).toHaveBeenCalledTimes(1);
expect(nodeDestroyHandler.mock.calls[0][0]).toBe(node); expect(nodeDestroyHandler.mock.calls[0][0]).toBe(node);
expect(nodeDestroyHandler.mock.calls[0][0].componentName).toBe('TextInput'); expect(nodeDestroyHandler.mock.calls[0][0].componentName).toBe('TextInput');
@ -290,9 +290,9 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form'); const formNode = nodesMap?.get('form');
currentDocument?.insertNode(formNode, { formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput', componentName: 'TextInput',
id: 'nodeschema-id1', id: 'nodeschema-id1',
props: { props: {
@ -300,11 +300,11 @@ describe('schema 生成节点模型测试', () => {
propB: 3, propB: 3,
}, },
}, 0); }, 0);
expect(nodesMap.size).toBe(ids.length + 1); expect(nodesMap?.size).toBe(ids.length + 1);
expect(formNode.children.length).toBe(4); expect(formNode?.children?.length).toBe(4);
const insertedNode = formNode.children.get(0); const insertedNode = formNode?.children?.get(0);
expect(insertedNode.componentName).toBe('TextInput'); expect(insertedNode?.componentName).toBe('TextInput');
expect(insertedNode.propsData).toEqual({ expect(insertedNode?.propsData).toEqual({
propA: 'haha', propA: 'haha',
propB: 3, propB: 3,
}); });
@ -316,9 +316,9 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form'); const formNode = nodesMap?.get('form');
currentDocument?.insertNode(formNode, { formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput', componentName: 'TextInput',
id: 'nodeschema-id1', id: 'nodeschema-id1',
props: { props: {
@ -326,11 +326,11 @@ describe('schema 生成节点模型测试', () => {
propB: 3, propB: 3,
}, },
}, 1); }, 1);
expect(nodesMap.size).toBe(ids.length + 1); expect(nodesMap?.size).toBe(ids.length + 1);
expect(formNode.children.length).toBe(4); expect(formNode?.children?.length).toBe(4);
const insertedNode = formNode.children.get(1); const insertedNode = formNode?.children?.get(1);
expect(insertedNode.componentName).toBe('TextInput'); expect(insertedNode?.componentName).toBe('TextInput');
expect(insertedNode.propsData).toEqual({ expect(insertedNode?.propsData).toEqual({
propA: 'haha', propA: 'haha',
propB: 3, propB: 3,
}); });
@ -342,8 +342,8 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form') as Node; const formNode = nodesMap?.get('form') as INode;
currentDocument?.insertNode(formNode, { currentDocument?.insertNode(formNode, {
componentName: 'ParentNode', componentName: 'ParentNode',
props: { props: {
@ -367,8 +367,8 @@ describe('schema 生成节点模型测试', () => {
}, },
], ],
}); });
expect(nodesMap.size).toBe(ids.length + 3); expect(nodesMap?.size).toBe(ids.length + 3);
expect(formNode.children.length).toBe(4); expect(formNode.children?.length).toBe(4);
expect(formNode.children?.get(3)?.componentName).toBe('ParentNode'); 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(0)?.componentName).toBe('SubNode');
expect(formNode.children?.get(3)?.children?.get(1)?.componentName).toBe('SubNode2'); expect(formNode.children?.get(3)?.children?.get(1)?.componentName).toBe('SubNode2');
@ -378,9 +378,9 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form'); const formNode = nodesMap?.get('form');
currentDocument?.insertNode(formNode, { formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput', componentName: 'TextInput',
id: 'nodeschema-id1', id: 'nodeschema-id1',
props: { props: {
@ -388,17 +388,17 @@ describe('schema 生成节点模型测试', () => {
propB: 3, propB: 3,
}, },
}); });
expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput'); expect(nodesMap?.get('nodeschema-id1')?.componentName).toBe('TextInput');
expect(nodesMap.size).toBe(ids.length + 1); expect(nodesMap?.size).toBe(ids.length + 1);
}); });
it.skip('场景一:插入 NodeSchemaid 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => { it.skip('场景一:插入 NodeSchemaid 与现有 schema 里的 id 重复,但关闭了 id 检测器', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form'); const formNode = nodesMap?.get('form');
currentDocument?.insertNode(formNode, { formNode && currentDocument?.insertNode(formNode, {
componentName: 'TextInput', componentName: 'TextInput',
id: 'nodeschema-id1', id: 'nodeschema-id1',
props: { props: {
@ -406,16 +406,16 @@ describe('schema 生成节点模型测试', () => {
propB: 3, propB: 3,
}, },
}); });
expect(nodesMap.get('nodeschema-id1').componentName).toBe('TextInput'); expect(nodesMap?.get('nodeschema-id1')?.componentName).toBe('TextInput');
expect(nodesMap.size).toBe(ids.length + 1); expect(nodesMap?.size).toBe(ids.length + 1);
}); });
it('场景二:插入 Node 实例', () => { it('场景二:插入 Node 实例', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form'); const formNode = nodesMap?.get('form');
const inputNode = currentDocument?.createNode({ const inputNode = currentDocument?.createNode({
componentName: 'TextInput', componentName: 'TextInput',
id: 'nodeschema-id2', id: 'nodeschema-id2',
@ -424,22 +424,22 @@ describe('schema 生成节点模型测试', () => {
propB: 3, propB: 3,
}, },
}); });
currentDocument?.insertNode(formNode, inputNode); formNode && currentDocument?.insertNode(formNode, inputNode);
expect(formNode.children?.get(3)?.componentName).toBe('TextInput'); expect(formNode?.children?.get(3)?.componentName).toBe('TextInput');
expect(nodesMap.size).toBe(ids.length + 1); expect(nodesMap?.size).toBe(ids.length + 1);
}); });
it('场景三:插入 JSExpression', () => { it('场景三:插入 JSExpression', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form') as Node; const formNode = nodesMap?.get('form') as Node;
currentDocument?.insertNode(formNode, { currentDocument?.insertNode(formNode, {
type: 'JSExpression', type: 'JSExpression',
value: 'just a expression', 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)?.componentName).toBe('Leaf');
// expect(formNode.children?.get(3)?.children).toEqual({ // expect(formNode.children?.get(3)?.children).toEqual({
// type: 'JSExpression', // type: 'JSExpression',
@ -450,10 +450,10 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form') as Node; const formNode = nodesMap?.get('form') as Node;
currentDocument?.insertNode(formNode, 'just a string'); 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)?.componentName).toBe('Leaf');
// expect(formNode.children?.get(3)?.children).toBe('just a string'); // expect(formNode.children?.get(3)?.children).toBe('just a string');
}); });
@ -473,8 +473,8 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form') as Node; const formNode = nodesMap?.get('form') as Node;
const formNode2 = currentDocument?.getNode('form'); const formNode2 = currentDocument?.getNode('form');
expect(formNode).toEqual(formNode2); expect(formNode).toEqual(formNode2);
currentDocument?.insertNodes(formNode, [ currentDocument?.insertNodes(formNode, [
@ -493,17 +493,17 @@ describe('schema 生成节点模型测试', () => {
}, },
}, },
], 1); ], 1);
expect(nodesMap.size).toBe(ids.length + 2); expect(nodesMap?.size).toBe(ids.length + 2);
expect(formNode.children?.length).toBe(5); expect(formNode.children?.length).toBe(5);
const insertedNode1 = formNode.children.get(1); const insertedNode1 = formNode.children?.get(1);
const insertedNode2 = formNode.children.get(2); const insertedNode2 = formNode.children?.get(2);
expect(insertedNode1.componentName).toBe('TextInput'); expect(insertedNode1?.componentName).toBe('TextInput');
expect(insertedNode1.propsData).toEqual({ expect(insertedNode1?.propsData).toEqual({
propA: 'haha2', propA: 'haha2',
propB: 3, propB: 3,
}); });
expect(insertedNode2.componentName).toBe('TextInput2'); expect(insertedNode2?.componentName).toBe('TextInput2');
expect(insertedNode2.propsData).toEqual({ expect(insertedNode2?.propsData).toEqual({
propA: 'haha', propA: 'haha',
propB: 3, propB: 3,
}); });
@ -513,8 +513,8 @@ describe('schema 生成节点模型测试', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const formNode = nodesMap.get('form') as Node; const formNode = nodesMap?.get('form') as INode;
const formNode2 = currentDocument?.getNode('form'); const formNode2 = currentDocument?.getNode('form');
expect(formNode).toEqual(formNode2); expect(formNode).toEqual(formNode2);
const createdNode1 = currentDocument?.createNode({ const createdNode1 = currentDocument?.createNode({
@ -532,17 +532,17 @@ describe('schema 生成节点模型测试', () => {
}, },
}); });
currentDocument?.insertNodes(formNode, [createdNode1, createdNode2], 1); 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); expect(formNode.children?.length).toBe(5);
const insertedNode1 = formNode.children.get(1); const insertedNode1 = formNode.children?.get(1);
const insertedNode2 = formNode.children.get(2); const insertedNode2 = formNode.children?.get(2);
expect(insertedNode1.componentName).toBe('TextInput'); expect(insertedNode1?.componentName).toBe('TextInput');
expect(insertedNode1.propsData).toEqual({ expect(insertedNode1?.propsData).toEqual({
propA: 'haha2', propA: 'haha2',
propB: 3, propB: 3,
}); });
expect(insertedNode2.componentName).toBe('TextInput2'); expect(insertedNode2?.componentName).toBe('TextInput2');
expect(insertedNode2.propsData).toEqual({ expect(insertedNode2?.propsData).toEqual({
propA: 'haha', propA: 'haha',
propB: 3, propB: 3,
}); });
@ -561,13 +561,13 @@ describe('schema 生成节点模型测试', () => {
project.open(); project.open();
expect(project).toBeTruthy(); expect(project).toBeTruthy();
const { currentDocument } = project; const { currentDocument } = project;
const { nodesMap } = currentDocument; const nodesMap = currentDocument?.nodesMap;
const ids = getIdsFromSchema(formSchema); const ids = getIdsFromSchema(formSchema);
// 目前每个 slot 会新增1 + children.length个节点 // 目前每个 slot 会新增1 + children.length个节点
const expectedNodeCnt = ids.length + 2; const expectedNodeCnt = ids.length + 2;
expect(nodesMap.size).toBe(expectedNodeCnt); expect(nodesMap?.size).toBe(expectedNodeCnt);
// PageHeader // PageHeader
expect(nodesMap.get('node_k1ow3cbd').slots).toHaveLength(1); expect(nodesMap?.get('node_k1ow3cbd')?.slots).toHaveLength(1);
}); });
}); });
}); });

View File

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

View File

@ -27,6 +27,7 @@ import { ComponentMeta as ShellComponentMeta } from './component-meta';
import { SettingTopEntry as ShellSettingTopEntry } from './setting-top-entry'; import { SettingTopEntry as ShellSettingTopEntry } from './setting-top-entry';
import { documentSymbol, nodeSymbol } from '../symbols'; import { documentSymbol, nodeSymbol } from '../symbols';
import { ReactElement } from 'react'; import { ReactElement } from 'react';
import { ConditionGroup } from './condition-group';
const shellNodeSymbol = Symbol('shellNodeSymbol'); 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); return ShellProp.create(this[nodeSymbol].slotFor);
} }
@ -349,7 +350,6 @@ export class Node implements IPublicModelNode {
/** /**
* dom * dom
* @deprecated
*/ */
getDOMNode() { getDOMNode() {
return (this[nodeSymbol] as any).getDOMNode(); return (this[nodeSymbol] as any).getDOMNode();
@ -362,9 +362,9 @@ export class Node implements IPublicModelNode {
* @param sorter * @param sorter
*/ */
mergeChildren( mergeChildren(
remover: (node: Node, idx: number) => boolean, remover: (node: IPublicModelNode, idx: number) => boolean,
adder: (children: Node[]) => any, adder: (children: IPublicModelNode[]) => any,
sorter: (firstNode: Node, secondNode: Node) => number, sorter: (firstNode: IPublicModelNode, secondNode: IPublicModelNode) => number,
): any { ): any {
return this.children?.mergeChildren(remover, adder, sorter); return this.children?.mergeChildren(remover, adder, sorter);
} }
@ -641,7 +641,7 @@ export class Node implements IPublicModelNode {
* @since v1.1.0 * @since v1.1.0
*/ */
get conditionGroup(): IPublicModelExclusiveGroup | null { get conditionGroup(): IPublicModelExclusiveGroup | null {
return this[nodeSymbol].conditionGroup; return ConditionGroup.create(this[nodeSymbol].conditionGroup);
} }
/** /**

View File

@ -33,3 +33,4 @@ export const resourceTypeSymbol = Symbol('resourceType');
export const resourceSymbol = Symbol('resource'); export const resourceSymbol = Symbol('resource');
export const clipboardSymbol = Symbol('clipboard'); export const clipboardSymbol = Symbol('clipboard');
export const configSymbol = Symbol('configSymbol'); export const configSymbol = Symbol('configSymbol');
export const conditionGroupSymbol = Symbol('conditionGroup');

View File

@ -1,8 +1,10 @@
import { IPublicModelNode } from '..'; import { IPublicModelNode, IPublicTypeTitleContent } from '..';
export interface IPublicModelExclusiveGroup { export interface IPublicModelExclusiveGroup<
readonly id: string; Node = IPublicModelNode,
readonly title: string; > {
get firstNode(): IPublicModelNode; readonly id: string | undefined;
readonly title: IPublicTypeTitleContent | undefined;
get firstNode(): Node | null;
setVisible(node: Node): void; setVisible(node: Node): void;
} }

View File

@ -8,7 +8,10 @@ export interface IBaseModelNode<
Node = IPublicModelNode, Node = IPublicModelNode,
NodeChildren = IPublicModelNodeChildren, NodeChildren = IPublicModelNodeChildren,
ComponentMeta = IPublicModelComponentMeta, ComponentMeta = IPublicModelComponentMeta,
SettingTopEntry = IPublicModelSettingTopEntry SettingTopEntry = IPublicModelSettingTopEntry,
Props = IPublicModelProps,
Prop = IPublicModelProp,
ExclusiveGroup = IPublicModelExclusiveGroup
> { > {
/** /**
@ -167,7 +170,7 @@ export interface IBaseModelNode<
* *
* index * index
*/ */
get index(): number; get index(): number | undefined;
/** /**
* *
@ -203,13 +206,13 @@ export interface IBaseModelNode<
* *
* get previous sibling of this node * get previous sibling of this node
*/ */
get prevSibling(): Node | null; get prevSibling(): Node | null | undefined;
/** /**
* *
* get next sibling of this node * 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 * return coresponding prop when this node is a slot node
*/ */
get slotFor(): IPublicModelProp | null; get slotFor(): Prop | null | undefined;
/** /**
* *
* get props * get props
*/ */
get props(): IPublicModelProps | null; get props(): Props | null;
/** /**
* *
@ -250,7 +253,7 @@ export interface IBaseModelNode<
/** /**
* get conditionGroup * get conditionGroup
*/ */
get conditionGroup(): IPublicModelExclusiveGroup | null; get conditionGroup(): ExclusiveGroup | null;
/** /**
* - schema * - schema
@ -295,7 +298,7 @@ export interface IBaseModelNode<
* get prop by path * get prop by path
* @param path a / a.b / a.0 * @param path a / a.b / a.0
*/ */
getProp(path: string, createIfNone: boolean): IPublicModelProp | null; getProp(path: string, createIfNone: boolean): Prop | null;
/** /**
* path * path
@ -313,7 +316,7 @@ export interface IBaseModelNode<
* @param path a / a.b / a.0 * @param path a / a.b / a.0
* @param createIfNone * @param createIfNone
*/ */
getExtraProp(path: string, createIfNone?: boolean): IPublicModelProp | null; getExtraProp(path: string, createIfNone?: boolean): Prop | null;
/** /**
* path * path
@ -481,6 +484,11 @@ export interface IBaseModelNode<
* @since v1.1.0 * @since v1.1.0
*/ */
setConditionalVisible(): void; setConditionalVisible(): void;
/**
* dom
*/
getDOMNode(): HTMLElement;
} }
export interface IPublicModelNode extends IBaseModelNode<IPublicModelDocumentModel, IPublicModelNode> {} export interface IPublicModelNode extends IBaseModelNode<IPublicModelDocumentModel, IPublicModelNode> {}

View File

@ -2,7 +2,9 @@ import { IPublicEnumTransformStage } from '../enum';
import { IPublicTypeCompositeValue } from '../type'; import { IPublicTypeCompositeValue } from '../type';
import { IPublicModelNode } from './'; import { IPublicModelNode } from './';
export interface IPublicModelProp { export interface IPublicModelProp<
Node = IPublicModelNode
> {
/** /**
* id * id
@ -25,14 +27,14 @@ export interface IPublicModelProp {
* *
* get node instance, which this prop belongs to * get node instance, which this prop belongs to
*/ */
get node(): IPublicModelNode | null; get node(): Node | null;
/** /**
* prop Slot slotNode * prop Slot slotNode
* return the slot node (only if the current prop represents a slot) * return the slot node (only if the current prop represents a slot)
* @since v1.1.0 * @since v1.1.0
*/ */
get slotNode(): IPublicModelNode | undefined | null; get slotNode(): Node | undefined | null;
/** /**
* Prop , true * Prop , true

View File

@ -195,8 +195,8 @@ export interface IPublicTypeCallbacks {
onChildMoveHook?: (childNode: IPublicModelNode, currentNode: IPublicModelNode) => boolean; onChildMoveHook?: (childNode: IPublicModelNode, currentNode: IPublicModelNode) => boolean;
// events // events
onNodeRemove?: (removedNode: IPublicModelNode, currentNode: IPublicModelNode) => void; onNodeRemove?: (removedNode: IPublicModelNode | null, currentNode: IPublicModelNode | null) => void;
onNodeAdd?: (addedNode: IPublicModelNode, currentNode: IPublicModelNode) => void; onNodeAdd?: (addedNode: IPublicModelNode | null, currentNode: IPublicModelNode | null) => void;
onSubtreeModified?: (currentNode: IPublicModelNode, options: any) => void; onSubtreeModified?: (currentNode: IPublicModelNode, options: any) => void;
onResize?: ( onResize?: (
e: MouseEvent & { e: MouseEvent & {

View File

@ -1,3 +1,3 @@
export function isDOMText(data: any): boolean { export function isDOMText(data: any): data is string {
return typeof data === 'string'; return typeof data === 'string';
} }

View File

@ -1,5 +1,5 @@
import { IPublicTypeNodeSchema } from '@alilc/lowcode-types'; import { IPublicTypeNodeSchema } from '@alilc/lowcode-types';
export function isNodeSchema(data: any): data is IPublicTypeNodeSchema { export function isNodeSchema(data: any): data is IPublicTypeNodeSchema {
return data && data.componentName; return data && data.componentName && !data.isNode;
} }