@zcomponent/core 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/behavior.d.ts +1 -1
- package/lib/behavior.js +5 -1
- package/lib/component.js +14 -5
- package/lib/data.d.ts +10 -11
- package/lib/data.js +148 -113
- package/lib/fractionalindexing.d.ts +23 -0
- package/lib/fractionalindexing.js +300 -0
- package/lib/interfaces.d.ts +11 -6
- package/lib/validators.js +9 -4
- package/lib/zcomponent.js +15 -6
- package/package.json +1 -1
package/lib/behavior.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class Behavior<InstanceType extends Component = Component> {
|
|
|
16
16
|
private _registered;
|
|
17
17
|
private _zcomponent;
|
|
18
18
|
constructor(contextManager: ContextManager, instance: InstanceType);
|
|
19
|
-
getZComponentInstance<T extends ZComponent = ZComponent>(type?: ConstructorForComponent<T>): T
|
|
19
|
+
getZComponentInstance<T extends ZComponent = ZComponent>(type?: ConstructorForComponent<T>): T;
|
|
20
20
|
private _updateEnabledResolved;
|
|
21
21
|
/**
|
|
22
22
|
* @zprop
|
package/lib/behavior.js
CHANGED
|
@@ -28,9 +28,13 @@ export class Behavior {
|
|
|
28
28
|
this.register(instance.enabledResolved, this._updateEnabledResolved);
|
|
29
29
|
}
|
|
30
30
|
getZComponentInstance(type) {
|
|
31
|
+
if (this._zcomponent === undefined)
|
|
32
|
+
throw new Error("getZComponentInstance called in behavior that's not part of a ZComponent");
|
|
31
33
|
if (!type)
|
|
32
34
|
return this._zcomponent;
|
|
33
|
-
|
|
35
|
+
if (this._zcomponent instanceof type)
|
|
36
|
+
return this._zcomponent;
|
|
37
|
+
throw new Error("getZComponentInstance called in behavior passing wrong kind of ZComponent");
|
|
34
38
|
}
|
|
35
39
|
register(e, fn) {
|
|
36
40
|
if (e instanceof Event)
|
package/lib/component.js
CHANGED
|
@@ -48,16 +48,25 @@ export class Component {
|
|
|
48
48
|
constructChildren(children, contextManager) {
|
|
49
49
|
contextManager = contextManager ?? this.contextManager;
|
|
50
50
|
for (const [constructor, props, ref] of children) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ref
|
|
51
|
+
try {
|
|
52
|
+
const instance = new constructor(contextManager, props);
|
|
53
|
+
this.appendChild(instance);
|
|
54
|
+
if (ref)
|
|
55
|
+
ref.value = instance;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
console.log('An error occurred when constructing component child of type:', constructor, err);
|
|
59
|
+
}
|
|
55
60
|
}
|
|
56
61
|
}
|
|
57
62
|
getZComponentInstance(type) {
|
|
63
|
+
if (this._zcomponent === undefined)
|
|
64
|
+
throw new Error("getZComponentInstance called in component that's not part of a ZComponent");
|
|
58
65
|
if (!type)
|
|
59
66
|
return this._zcomponent;
|
|
60
|
-
|
|
67
|
+
if (this._zcomponent instanceof type)
|
|
68
|
+
return this._zcomponent;
|
|
69
|
+
throw new Error("getZComponentInstance called in component passing wrong kind of ZComponent");
|
|
61
70
|
}
|
|
62
71
|
appendChild(c) {
|
|
63
72
|
c.remove();
|
package/lib/data.d.ts
CHANGED
|
@@ -1,27 +1,26 @@
|
|
|
1
|
-
import { BehaviorData, NodeData, ZComponentData } from "./interfaces";
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function
|
|
1
|
+
import { BehaviorByID, BehaviorData, ComputedHierarchy, NodeByID, NodeData, ZComponentData } from "./interfaces";
|
|
2
|
+
export declare function computeNodeHierarchy(nodes: NodeByID): ComputedHierarchy;
|
|
3
|
+
export declare function computeBehaviorHierarchy(behaviors: BehaviorByID): ComputedHierarchy;
|
|
4
|
+
export declare function addNode(zcomp: ZComponentData, info: NodeDataCombined): void;
|
|
5
|
+
export declare function addBehavior(zcomp: ZComponentData, info: BehaviorDataCombined): void;
|
|
4
6
|
export declare function addEntity(zcomp: ZComponentData, id: string, info: EntityDataCombined): void;
|
|
7
|
+
export declare function moveNodes(zcomp: ZComponentData, nodeIDs: string[], newParentID: string, indx: number): void;
|
|
5
8
|
export declare function deleteNode(zcomp: ZComponentData, id: string): void;
|
|
6
9
|
export declare function deleteBehavior(zcomp: ZComponentData, id: string): void;
|
|
7
10
|
export declare function getNodeDataCombined(zcomp: ZComponentData, id: string): NodeDataCombined | undefined;
|
|
8
11
|
export declare function getBehaviorDataCombined(zcomp: ZComponentData, id: string): BehaviorDataCombined | undefined;
|
|
9
|
-
export declare function
|
|
10
|
-
export declare function
|
|
11
|
-
export declare function
|
|
12
|
+
export declare function fixAnyDuplicateIndex(zcomp: ZComponentData, hierarchy: ComputedHierarchy, nodeID: string, indx: number): void;
|
|
13
|
+
export declare function fixAnyDuplicateBehaviorsIndex(zcomp: ZComponentData, behaviors: ComputedHierarchy, nodeID: string, indx: number): void;
|
|
14
|
+
export declare function nodeOrderForParentIndex(zcomp: ZComponentData, parentID: string, indx?: number): string;
|
|
15
|
+
export declare function behaviorOrderForParentIndex(zcomp: ZComponentData, parentID: string, indx?: number): string;
|
|
12
16
|
export declare function addNodeLabelToRegister(zcomp: ZComponentData, id: string): void;
|
|
13
17
|
export declare function removeNodeLabelFromRegister(zcomp: ZComponentData, id: string): void;
|
|
14
18
|
export declare function addNodeScriptNameToRegister(zcomp: ZComponentData, id: string): void;
|
|
15
19
|
export declare function removeNodeScriptNameFromRegister(zcomp: ZComponentData, id: string): void;
|
|
16
20
|
export declare function changeNodeLabel(zcomp: ZComponentData, id: string, newLabel: string | undefined): void;
|
|
17
21
|
export declare function changeNodeScriptName(zcomp: ZComponentData, id: string, newScriptName: string | undefined): void;
|
|
18
|
-
export declare function removeBehaviorFromNode(zcomp: ZComponentData, id: string): void;
|
|
19
|
-
export declare function findNodeParentAndIndex(zcomp: ZComponentData, id: string): [string, number] | undefined;
|
|
20
|
-
export declare function findBehaviorNodeAndIndex(zcomp: ZComponentData, id: string): [string, number] | undefined;
|
|
21
22
|
export declare function deleteEntity(zcomp: ZComponentData, id: string): void;
|
|
22
|
-
export declare function changeNodeID(zcomp: ZComponentData, id: string, newID: string): void;
|
|
23
23
|
export declare function forEachNodeAncestor(zcomp: ZComponentData, id: string, fn: (ancestorID: string) => void): void;
|
|
24
|
-
export declare function forEachNodeAndDescendant(zcomp: ZComponentData, id: string, fn: (nodeID: string, parent: [string, number] | undefined) => void): void;
|
|
25
24
|
export interface EntityDataCombined {
|
|
26
25
|
props: {
|
|
27
26
|
[id: string]: any;
|
package/lib/data.js
CHANGED
|
@@ -1,32 +1,114 @@
|
|
|
1
|
-
|
|
1
|
+
import { generateKeyBetween, generateNKeysBetween } from "./fractionalindexing";
|
|
2
|
+
// Simple memoizing of computed hierarchy
|
|
3
|
+
const computedHierarchies = new Map();
|
|
4
|
+
const computedBehaviors = new Map();
|
|
5
|
+
export function computeNodeHierarchy(nodes) {
|
|
6
|
+
let existing = computedHierarchies.get(nodes);
|
|
7
|
+
if (!existing) {
|
|
8
|
+
existing = {};
|
|
9
|
+
for (const [id, node] of Object.entries(nodes)) {
|
|
10
|
+
if (!node || !node.parent)
|
|
11
|
+
continue;
|
|
12
|
+
const parent = existing[node.parent.id] || [];
|
|
13
|
+
existing[node.parent.id] = parent;
|
|
14
|
+
parent.push(id);
|
|
15
|
+
}
|
|
16
|
+
for (const children of Object.values(existing)) {
|
|
17
|
+
children.sort((nodeAID, nodeBID) => {
|
|
18
|
+
let a = nodes[nodeAID]?.parent?.order ?? 'A0';
|
|
19
|
+
let b = nodes[nodeBID]?.parent?.order ?? 'A0';
|
|
20
|
+
if (a === b) {
|
|
21
|
+
a = nodeAID;
|
|
22
|
+
b = nodeBID;
|
|
23
|
+
}
|
|
24
|
+
return a < b ? -1 : 1;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
computedHierarchies.set(nodes, existing);
|
|
28
|
+
}
|
|
29
|
+
return existing;
|
|
30
|
+
}
|
|
31
|
+
export function computeBehaviorHierarchy(behaviors) {
|
|
32
|
+
let existing = computedBehaviors.get(behaviors);
|
|
33
|
+
if (!existing) {
|
|
34
|
+
existing = {};
|
|
35
|
+
for (const [id, entity] of Object.entries(behaviors)) {
|
|
36
|
+
if (!entity || !entity.parent)
|
|
37
|
+
continue;
|
|
38
|
+
const parent = existing[entity.parent.id] || [];
|
|
39
|
+
existing[entity.parent.id] = parent;
|
|
40
|
+
parent.push(id);
|
|
41
|
+
}
|
|
42
|
+
for (const children of Object.values(existing)) {
|
|
43
|
+
children.sort((entityAID, entityBID) => {
|
|
44
|
+
let a = behaviors[entityAID]?.parent?.order ?? 'A0';
|
|
45
|
+
let b = behaviors[entityBID]?.parent?.order ?? 'A0';
|
|
46
|
+
if (a === b) {
|
|
47
|
+
a = entityAID;
|
|
48
|
+
b = entityBID;
|
|
49
|
+
}
|
|
50
|
+
return a < b ? -1 : 1;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
computedBehaviors.set(behaviors, existing);
|
|
54
|
+
}
|
|
55
|
+
return existing;
|
|
56
|
+
}
|
|
57
|
+
export function addNode(zcomp, info) {
|
|
2
58
|
zcomp.nodes[info.data.id] = info.data;
|
|
3
59
|
addNodeLabelToRegister(zcomp, info.data.id);
|
|
4
60
|
addNodeScriptNameToRegister(zcomp, info.data.id);
|
|
5
61
|
addEntity(zcomp, info.data.id, info);
|
|
6
|
-
|
|
7
|
-
addNodeToParent(zcomp, info.data.id, info.data.parent, parentIndex);
|
|
62
|
+
computedHierarchies.delete(zcomp.nodes);
|
|
8
63
|
}
|
|
9
|
-
export function addBehavior(zcomp, info
|
|
64
|
+
export function addBehavior(zcomp, info) {
|
|
10
65
|
zcomp.behaviors[info.data.id] = info.data;
|
|
11
66
|
addEntity(zcomp, info.data.id, info);
|
|
12
|
-
|
|
13
|
-
addBehaviorToNode(zcomp, info.data.id, info.data.nodeId, parentIndex);
|
|
67
|
+
computedBehaviors.delete(zcomp.behaviors);
|
|
14
68
|
}
|
|
15
69
|
export function addEntity(zcomp, id, info) {
|
|
16
70
|
zcomp.entityProps[id] = info.props;
|
|
17
71
|
zcomp.entityConstructorProps[id] = info.constructorProps;
|
|
18
72
|
}
|
|
73
|
+
export function moveNodes(zcomp, nodeIDs, newParentID, indx) {
|
|
74
|
+
const parent = zcomp.nodes[newParentID];
|
|
75
|
+
if (!parent)
|
|
76
|
+
return;
|
|
77
|
+
const computed = computeNodeHierarchy(zcomp.nodes);
|
|
78
|
+
fixAnyDuplicateIndex(zcomp, computed, newParentID, indx - 1);
|
|
79
|
+
const parentChildren = computed[newParentID] ?? [];
|
|
80
|
+
const leftID = parentChildren[indx - 1];
|
|
81
|
+
const rightID = parentChildren[indx];
|
|
82
|
+
const leftOrder = zcomp.nodes[leftID]?.parent?.order ?? null;
|
|
83
|
+
const rightOrder = zcomp.nodes[rightID]?.parent?.order ?? null;
|
|
84
|
+
const newOrders = generateNKeysBetween(leftOrder, rightOrder, nodeIDs.length);
|
|
85
|
+
for (let i = 0; i < nodeIDs.length; i++) {
|
|
86
|
+
const nodeID = nodeIDs[i];
|
|
87
|
+
const node = zcomp.nodes[nodeID];
|
|
88
|
+
if (!node)
|
|
89
|
+
continue;
|
|
90
|
+
node.parent = { id: newParentID, order: newOrders[i] };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
19
93
|
export function deleteNode(zcomp, id) {
|
|
20
|
-
|
|
94
|
+
// Delete child nodes
|
|
95
|
+
const children = computeNodeHierarchy(zcomp.nodes)[id] ?? [];
|
|
96
|
+
for (const child of children)
|
|
97
|
+
deleteNode(zcomp, child);
|
|
98
|
+
// Delete behaviors
|
|
99
|
+
const behaviors = computeBehaviorHierarchy(zcomp.behaviors)[id] ?? [];
|
|
100
|
+
for (const id of behaviors)
|
|
101
|
+
deleteBehavior(zcomp, id);
|
|
21
102
|
deleteEntity(zcomp, id);
|
|
22
103
|
removeNodeLabelFromRegister(zcomp, id);
|
|
23
104
|
removeNodeScriptNameFromRegister(zcomp, id);
|
|
24
105
|
delete zcomp.nodes[id];
|
|
106
|
+
computedHierarchies.delete(zcomp.nodes);
|
|
25
107
|
}
|
|
26
108
|
export function deleteBehavior(zcomp, id) {
|
|
27
|
-
removeBehaviorFromNode(zcomp, id);
|
|
28
109
|
deleteEntity(zcomp, id);
|
|
29
110
|
delete zcomp.behaviors[id];
|
|
111
|
+
computedBehaviors.delete(zcomp.behaviors);
|
|
30
112
|
}
|
|
31
113
|
export function getNodeDataCombined(zcomp, id) {
|
|
32
114
|
const data = zcomp.nodes[id];
|
|
@@ -48,39 +130,69 @@ export function getBehaviorDataCombined(zcomp, id) {
|
|
|
48
130
|
constructorProps: zcomp.entityConstructorProps[id] ?? {}
|
|
49
131
|
};
|
|
50
132
|
}
|
|
51
|
-
export function
|
|
52
|
-
|
|
53
|
-
if (!node)
|
|
133
|
+
export function fixAnyDuplicateIndex(zcomp, hierarchy, nodeID, indx) {
|
|
134
|
+
if (indx < 0)
|
|
54
135
|
return;
|
|
55
|
-
const
|
|
56
|
-
|
|
136
|
+
const children = hierarchy[nodeID];
|
|
137
|
+
const a = children[indx];
|
|
138
|
+
const b = children[indx + 1];
|
|
139
|
+
if (!a || !b)
|
|
57
140
|
return;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
export function removeNodeFromParent(zcomp, id) {
|
|
62
|
-
const node = zcomp.nodes[id];
|
|
63
|
-
if (!node)
|
|
141
|
+
const nodeA = zcomp.nodes[a];
|
|
142
|
+
const nodeB = zcomp.nodes[b];
|
|
143
|
+
if (!nodeA?.parent || !nodeB?.parent)
|
|
64
144
|
return;
|
|
65
|
-
|
|
66
|
-
if (!parent)
|
|
145
|
+
if (nodeA.parent.id !== nodeB.parent.id)
|
|
67
146
|
return;
|
|
68
|
-
|
|
69
|
-
if (typeof parentNode !== 'object' || !Array.isArray(parentNode.children))
|
|
147
|
+
if (nodeA.parent.order !== nodeB.parent.order)
|
|
70
148
|
return;
|
|
71
|
-
|
|
72
|
-
|
|
149
|
+
fixAnyDuplicateIndex(zcomp, hierarchy, nodeID, indx + 1);
|
|
150
|
+
const c = children[indx + 2];
|
|
151
|
+
const corder = c ? zcomp.nodes[c]?.parent?.order : undefined;
|
|
152
|
+
nodeB.parent.order = generateKeyBetween(nodeA.parent.order, corder ?? null);
|
|
73
153
|
}
|
|
74
|
-
export function
|
|
75
|
-
|
|
76
|
-
|
|
154
|
+
export function fixAnyDuplicateBehaviorsIndex(zcomp, behaviors, nodeID, indx) {
|
|
155
|
+
if (indx < 0)
|
|
156
|
+
return;
|
|
157
|
+
const children = behaviors[nodeID];
|
|
158
|
+
const a = children[indx];
|
|
159
|
+
const b = children[indx + 1];
|
|
160
|
+
if (!a || !b)
|
|
161
|
+
return;
|
|
162
|
+
const behaviorA = zcomp.behaviors[a];
|
|
163
|
+
const behaviorB = zcomp.behaviors[b];
|
|
164
|
+
if (!behaviorA?.parent || !behaviorB?.parent)
|
|
165
|
+
return;
|
|
166
|
+
if (behaviorA.parent.id !== behaviorB.parent.id)
|
|
77
167
|
return;
|
|
78
|
-
if (
|
|
79
|
-
zcomp.behaviorsByNode[nodeID] = [];
|
|
80
|
-
if (!Array.isArray(zcomp.behaviorsByNode[nodeID]))
|
|
168
|
+
if (behaviorA.parent.order !== behaviorB.parent.order)
|
|
81
169
|
return;
|
|
82
|
-
zcomp
|
|
83
|
-
|
|
170
|
+
fixAnyDuplicateBehaviorsIndex(zcomp, behaviors, nodeID, indx + 1);
|
|
171
|
+
const c = children[indx + 2];
|
|
172
|
+
const corder = c ? zcomp.nodes[c]?.parent?.order : undefined;
|
|
173
|
+
behaviorB.parent.order = generateKeyBetween(behaviorA.parent.order, corder ?? null);
|
|
174
|
+
}
|
|
175
|
+
export function nodeOrderForParentIndex(zcomp, parentID, indx) {
|
|
176
|
+
const computed = computeNodeHierarchy(zcomp.nodes);
|
|
177
|
+
const parentChildren = computed[parentID] ?? [];
|
|
178
|
+
indx = indx ?? parentChildren.length;
|
|
179
|
+
fixAnyDuplicateIndex(zcomp, computed, parentID, indx - 1);
|
|
180
|
+
const leftID = parentChildren[indx - 1];
|
|
181
|
+
const rightID = parentChildren[indx];
|
|
182
|
+
const leftOrder = zcomp.nodes[leftID]?.parent?.order ?? null;
|
|
183
|
+
const rightOrder = zcomp.nodes[rightID]?.parent?.order ?? null;
|
|
184
|
+
return generateKeyBetween(leftOrder, rightOrder);
|
|
185
|
+
}
|
|
186
|
+
export function behaviorOrderForParentIndex(zcomp, parentID, indx) {
|
|
187
|
+
const computed = computeBehaviorHierarchy(zcomp.behaviors);
|
|
188
|
+
const parentChildren = computed[parentID] ?? [];
|
|
189
|
+
indx = indx ?? parentChildren.length;
|
|
190
|
+
fixAnyDuplicateBehaviorsIndex(zcomp, computed, parentID, indx - 1);
|
|
191
|
+
const leftID = parentChildren[indx - 1];
|
|
192
|
+
const rightID = parentChildren[indx];
|
|
193
|
+
const leftOrder = zcomp.behaviors[leftID]?.parent?.order ?? null;
|
|
194
|
+
const rightOrder = zcomp.behaviors[rightID]?.parent?.order ?? null;
|
|
195
|
+
return generateKeyBetween(leftOrder, rightOrder);
|
|
84
196
|
}
|
|
85
197
|
export function addNodeLabelToRegister(zcomp, id) {
|
|
86
198
|
const node = zcomp.nodes[id];
|
|
@@ -150,91 +262,14 @@ export function changeNodeScriptName(zcomp, id, newScriptName) {
|
|
|
150
262
|
node.scriptName = newScriptName;
|
|
151
263
|
addNodeScriptNameToRegister(zcomp, id);
|
|
152
264
|
}
|
|
153
|
-
export function removeBehaviorFromNode(zcomp, id) {
|
|
154
|
-
const behavior = zcomp.behaviors[id];
|
|
155
|
-
if (!behavior)
|
|
156
|
-
return;
|
|
157
|
-
const node = findBehaviorNodeAndIndex(zcomp, id);
|
|
158
|
-
if (!node)
|
|
159
|
-
return;
|
|
160
|
-
const nodeBehaviors = zcomp.behaviorsByNode[node[0]];
|
|
161
|
-
if (Array.isArray(nodeBehaviors))
|
|
162
|
-
nodeBehaviors.splice(node[1], 1);
|
|
163
|
-
delete behavior.nodeId;
|
|
164
|
-
}
|
|
165
|
-
export function findNodeParentAndIndex(zcomp, id) {
|
|
166
|
-
const node = zcomp.nodes[id];
|
|
167
|
-
if (!node)
|
|
168
|
-
return;
|
|
169
|
-
const parentID = node.parent;
|
|
170
|
-
if (!parentID)
|
|
171
|
-
return;
|
|
172
|
-
const parent = zcomp.nodes[parentID];
|
|
173
|
-
if (!parent)
|
|
174
|
-
return;
|
|
175
|
-
if (!Array.isArray(parent.children))
|
|
176
|
-
return;
|
|
177
|
-
const indx = parent.children.findIndex(val => val.id === id);
|
|
178
|
-
if (indx < 0)
|
|
179
|
-
return;
|
|
180
|
-
return [parentID, indx];
|
|
181
|
-
}
|
|
182
|
-
export function findBehaviorNodeAndIndex(zcomp, id) {
|
|
183
|
-
const behavior = zcomp.behaviors[id];
|
|
184
|
-
if (!behavior)
|
|
185
|
-
return;
|
|
186
|
-
const nodeID = behavior.nodeId;
|
|
187
|
-
if (!nodeID)
|
|
188
|
-
return;
|
|
189
|
-
const nodeBehaviors = zcomp.behaviorsByNode[nodeID];
|
|
190
|
-
if (!Array.isArray(nodeBehaviors))
|
|
191
|
-
return;
|
|
192
|
-
const indx = nodeBehaviors.indexOf(id);
|
|
193
|
-
if (indx < 0)
|
|
194
|
-
return;
|
|
195
|
-
return [nodeID, indx];
|
|
196
|
-
}
|
|
197
265
|
export function deleteEntity(zcomp, id) {
|
|
198
266
|
delete zcomp.entityProps[id];
|
|
199
267
|
delete zcomp.entityConstructorProps[id];
|
|
200
268
|
}
|
|
201
|
-
export function changeNodeID(zcomp, id, newID) {
|
|
202
|
-
if (id === newID)
|
|
203
|
-
return;
|
|
204
|
-
const node = zcomp.nodes[id];
|
|
205
|
-
if (!node)
|
|
206
|
-
return;
|
|
207
|
-
const parent = findNodeParentAndIndex(zcomp, id);
|
|
208
|
-
if (parent) {
|
|
209
|
-
const parentNode = zcomp.nodes[parent[0]];
|
|
210
|
-
if (parentNode) {
|
|
211
|
-
parentNode.children[parent[1]].id = newID;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
node.id = newID;
|
|
215
|
-
zcomp.entityProps[newID] = zcomp.entityProps[id];
|
|
216
|
-
delete zcomp.entityProps[id];
|
|
217
|
-
zcomp.entityConstructorProps[newID] = zcomp.entityConstructorProps[id];
|
|
218
|
-
delete zcomp.entityConstructorProps[id];
|
|
219
|
-
for (const child of node.children) {
|
|
220
|
-
const childNode = zcomp.nodes[child.id];
|
|
221
|
-
if (!childNode)
|
|
222
|
-
continue;
|
|
223
|
-
childNode.parent = newID;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
269
|
export function forEachNodeAncestor(zcomp, id, fn) {
|
|
227
|
-
const parent = findNodeParentAndIndex(zcomp, id);
|
|
228
|
-
if (!parent)
|
|
229
|
-
return;
|
|
230
|
-
fn(parent[0]);
|
|
231
|
-
forEachNodeAncestor(zcomp, parent[0], fn);
|
|
232
|
-
}
|
|
233
|
-
export function forEachNodeAndDescendant(zcomp, id, fn) {
|
|
234
270
|
const node = zcomp.nodes[id];
|
|
235
|
-
if (!node)
|
|
271
|
+
if (!node || !node.parent)
|
|
236
272
|
return;
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
node.children.forEach(v => forEachNodeAndDescendant(zcomp, v.id, fn));
|
|
273
|
+
fn(node.parent.id);
|
|
274
|
+
forEachNodeAncestor(zcomp, node.parent.id, fn);
|
|
240
275
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const BASE_62_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
2
|
+
/**
|
|
3
|
+
* @param {string | null | undefined} a
|
|
4
|
+
* @param {string | null | undefined} b
|
|
5
|
+
* @param {string=} digits
|
|
6
|
+
* @return {string}
|
|
7
|
+
*/
|
|
8
|
+
export declare function generateKeyBetween(a: string | null, b: string | null, digits?: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* same preconditions as generateKeysBetween.
|
|
11
|
+
* n >= 0.
|
|
12
|
+
* Returns an array of n distinct keys in sorted order.
|
|
13
|
+
* If a and b are both null, returns [a0, a1, ...]
|
|
14
|
+
* If one or the other is null, returns consecutive "integer"
|
|
15
|
+
* keys. Otherwise, returns relatively short keys between
|
|
16
|
+
* a and b.
|
|
17
|
+
* @param {string | null | undefined} a
|
|
18
|
+
* @param {string | null | undefined} b
|
|
19
|
+
* @param {number} n
|
|
20
|
+
* @param {string} digits
|
|
21
|
+
* @return {string[]}
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateNKeysBetween(a: string | null, b: string | null, n: number, digits?: string): string[];
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// License: CC0 (no rights reserved).
|
|
2
|
+
// From https://github.com/rocicorp/fractional-indexing
|
|
3
|
+
// This is based on https://observablehq.com/@dgreensp/implementing-fractional-indexing
|
|
4
|
+
export const BASE_62_DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
5
|
+
const SMALLEST_INTEGER = "A00000000000000000000000000";
|
|
6
|
+
const INTEGER_ZERO = "a0";
|
|
7
|
+
// `a` may be empty string, `b` is null or non-empty string.
|
|
8
|
+
// `a < b` lexicographically if `b` is non-null.
|
|
9
|
+
// no trailing zeros allowed.
|
|
10
|
+
// digits is a string such as '0123456789' for base 10. Digits must be in
|
|
11
|
+
// ascending character code order!
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} a
|
|
14
|
+
* @param {string | null | undefined} b
|
|
15
|
+
* @param {string} digits
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function midpoint(a, b, digits) {
|
|
19
|
+
if (b != null && a >= b) {
|
|
20
|
+
throw new Error(a + " >= " + b);
|
|
21
|
+
}
|
|
22
|
+
if (a.slice(-1) === "0" || (b && b.slice(-1) === "0")) {
|
|
23
|
+
throw new Error("trailing zero");
|
|
24
|
+
}
|
|
25
|
+
if (b) {
|
|
26
|
+
// remove longest common prefix. pad `a` with 0s as we
|
|
27
|
+
// go. note that we don't need to pad `b`, because it can't
|
|
28
|
+
// end before `a` while traversing the common prefix.
|
|
29
|
+
let n = 0;
|
|
30
|
+
while ((a[n] || "0") === b[n]) {
|
|
31
|
+
n++;
|
|
32
|
+
}
|
|
33
|
+
if (n > 0) {
|
|
34
|
+
return b.slice(0, n) + midpoint(a.slice(n), b.slice(n), digits);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// first digits (or lack of digit) are different
|
|
38
|
+
const digitA = a ? digits.indexOf(a[0]) : 0;
|
|
39
|
+
const digitB = b != null ? digits.indexOf(b[0]) : digits.length;
|
|
40
|
+
if (digitB - digitA > 1) {
|
|
41
|
+
const midDigit = Math.round(0.5 * (digitA + digitB));
|
|
42
|
+
return digits[midDigit];
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// first digits are consecutive
|
|
46
|
+
if (b && b.length > 1) {
|
|
47
|
+
return b.slice(0, 1);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// `b` is null or has length 1 (a single digit).
|
|
51
|
+
// the first digit of `a` is the previous digit to `b`,
|
|
52
|
+
// or 9 if `b` is null.
|
|
53
|
+
// given, for example, midpoint('49', '5'), return
|
|
54
|
+
// '4' + midpoint('9', null), which will become
|
|
55
|
+
// '4' + '9' + midpoint('', null), which is '495'
|
|
56
|
+
return digits[digitA] + midpoint(a.slice(1), null, digits);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* @param {string} int
|
|
62
|
+
* @return {void}
|
|
63
|
+
*/
|
|
64
|
+
function validateInteger(int) {
|
|
65
|
+
if (int.length !== getIntegerLength(int[0])) {
|
|
66
|
+
throw new Error("invalid integer part of order key: " + int);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* @param {string} head
|
|
71
|
+
* @return {number}
|
|
72
|
+
*/
|
|
73
|
+
function getIntegerLength(head) {
|
|
74
|
+
if (head >= "a" && head <= "z") {
|
|
75
|
+
return head.charCodeAt(0) - "a".charCodeAt(0) + 2;
|
|
76
|
+
}
|
|
77
|
+
else if (head >= "A" && head <= "Z") {
|
|
78
|
+
return "Z".charCodeAt(0) - head.charCodeAt(0) + 2;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error("invalid order key head: " + head);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* @param {string} key
|
|
86
|
+
* @return {string}
|
|
87
|
+
*/
|
|
88
|
+
function getIntegerPart(key) {
|
|
89
|
+
const integerPartLength = getIntegerLength(key[0]);
|
|
90
|
+
if (integerPartLength > key.length) {
|
|
91
|
+
throw new Error("invalid order key: " + key);
|
|
92
|
+
}
|
|
93
|
+
return key.slice(0, integerPartLength);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* @param {string} key
|
|
97
|
+
* @return {void}
|
|
98
|
+
*/
|
|
99
|
+
function validateOrderKey(key) {
|
|
100
|
+
if (key === SMALLEST_INTEGER) {
|
|
101
|
+
throw new Error("invalid order key: " + key);
|
|
102
|
+
}
|
|
103
|
+
// getIntegerPart will throw if the first character is bad,
|
|
104
|
+
// or the key is too short. we'd call it to check these things
|
|
105
|
+
// even if we didn't need the result
|
|
106
|
+
const i = getIntegerPart(key);
|
|
107
|
+
const f = key.slice(i.length);
|
|
108
|
+
if (f.slice(-1) === "0") {
|
|
109
|
+
throw new Error("invalid order key: " + key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// note that this may return null, as there is a largest integer
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} x
|
|
115
|
+
* @param {string} digits
|
|
116
|
+
* @return {string | null}
|
|
117
|
+
*/
|
|
118
|
+
function incrementInteger(x, digits) {
|
|
119
|
+
validateInteger(x);
|
|
120
|
+
const [head, ...digs] = x.split("");
|
|
121
|
+
let carry = true;
|
|
122
|
+
for (let i = digs.length - 1; carry && i >= 0; i--) {
|
|
123
|
+
const d = digits.indexOf(digs[i]) + 1;
|
|
124
|
+
if (d === digits.length) {
|
|
125
|
+
digs[i] = "0";
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
digs[i] = digits[d];
|
|
129
|
+
carry = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (carry) {
|
|
133
|
+
if (head === "Z") {
|
|
134
|
+
return "a0";
|
|
135
|
+
}
|
|
136
|
+
if (head === "z") {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
const h = String.fromCharCode(head.charCodeAt(0) + 1);
|
|
140
|
+
if (h > "a") {
|
|
141
|
+
digs.push("0");
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
digs.pop();
|
|
145
|
+
}
|
|
146
|
+
return h + digs.join("");
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return head + digs.join("");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// note that this may return null, as there is a smallest integer
|
|
153
|
+
/**
|
|
154
|
+
* @param {string} x
|
|
155
|
+
* @param {string} digits
|
|
156
|
+
* @return {string | null}
|
|
157
|
+
*/
|
|
158
|
+
function decrementInteger(x, digits) {
|
|
159
|
+
validateInteger(x);
|
|
160
|
+
const [head, ...digs] = x.split("");
|
|
161
|
+
let borrow = true;
|
|
162
|
+
for (let i = digs.length - 1; borrow && i >= 0; i--) {
|
|
163
|
+
const d = digits.indexOf(digs[i]) - 1;
|
|
164
|
+
if (d === -1) {
|
|
165
|
+
digs[i] = digits.slice(-1);
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
digs[i] = digits[d];
|
|
169
|
+
borrow = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (borrow) {
|
|
173
|
+
if (head === "a") {
|
|
174
|
+
return "Z" + digits.slice(-1);
|
|
175
|
+
}
|
|
176
|
+
if (head === "A") {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
const h = String.fromCharCode(head.charCodeAt(0) - 1);
|
|
180
|
+
if (h < "Z") {
|
|
181
|
+
digs.push(digits.slice(-1));
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
digs.pop();
|
|
185
|
+
}
|
|
186
|
+
return h + digs.join("");
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
return head + digs.join("");
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// `a` is an order key or null (START).
|
|
193
|
+
// `b` is an order key or null (END).
|
|
194
|
+
// `a < b` lexicographically if both are non-null.
|
|
195
|
+
// digits is a string such as '0123456789' for base 10. Digits must be in
|
|
196
|
+
// ascending character code order!
|
|
197
|
+
/**
|
|
198
|
+
* @param {string | null | undefined} a
|
|
199
|
+
* @param {string | null | undefined} b
|
|
200
|
+
* @param {string=} digits
|
|
201
|
+
* @return {string}
|
|
202
|
+
*/
|
|
203
|
+
export function generateKeyBetween(a, b, digits = BASE_62_DIGITS) {
|
|
204
|
+
if (a != null) {
|
|
205
|
+
validateOrderKey(a);
|
|
206
|
+
}
|
|
207
|
+
if (b != null) {
|
|
208
|
+
validateOrderKey(b);
|
|
209
|
+
}
|
|
210
|
+
if (a != null && b != null && a >= b) {
|
|
211
|
+
throw new Error(a + " >= " + b);
|
|
212
|
+
}
|
|
213
|
+
if (a == null) {
|
|
214
|
+
if (b == null) {
|
|
215
|
+
return INTEGER_ZERO;
|
|
216
|
+
}
|
|
217
|
+
const ib = getIntegerPart(b);
|
|
218
|
+
const fb = b.slice(ib.length);
|
|
219
|
+
if (ib === SMALLEST_INTEGER) {
|
|
220
|
+
return ib + midpoint("", fb, digits);
|
|
221
|
+
}
|
|
222
|
+
if (ib < b) {
|
|
223
|
+
return ib;
|
|
224
|
+
}
|
|
225
|
+
const res = decrementInteger(ib, digits);
|
|
226
|
+
if (res == null) {
|
|
227
|
+
throw new Error("cannot decrement any more");
|
|
228
|
+
}
|
|
229
|
+
return res;
|
|
230
|
+
}
|
|
231
|
+
if (b == null) {
|
|
232
|
+
const ia = getIntegerPart(a);
|
|
233
|
+
const fa = a.slice(ia.length);
|
|
234
|
+
const i = incrementInteger(ia, digits);
|
|
235
|
+
return i == null ? ia + midpoint(fa, null, digits) : i;
|
|
236
|
+
}
|
|
237
|
+
const ia = getIntegerPart(a);
|
|
238
|
+
const fa = a.slice(ia.length);
|
|
239
|
+
const ib = getIntegerPart(b);
|
|
240
|
+
const fb = b.slice(ib.length);
|
|
241
|
+
if (ia === ib) {
|
|
242
|
+
return ia + midpoint(fa, fb, digits);
|
|
243
|
+
}
|
|
244
|
+
const i = incrementInteger(ia, digits);
|
|
245
|
+
if (i == null) {
|
|
246
|
+
throw new Error("cannot increment any more");
|
|
247
|
+
}
|
|
248
|
+
if (i < b) {
|
|
249
|
+
return i;
|
|
250
|
+
}
|
|
251
|
+
return ia + midpoint(fa, null, digits);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* same preconditions as generateKeysBetween.
|
|
255
|
+
* n >= 0.
|
|
256
|
+
* Returns an array of n distinct keys in sorted order.
|
|
257
|
+
* If a and b are both null, returns [a0, a1, ...]
|
|
258
|
+
* If one or the other is null, returns consecutive "integer"
|
|
259
|
+
* keys. Otherwise, returns relatively short keys between
|
|
260
|
+
* a and b.
|
|
261
|
+
* @param {string | null | undefined} a
|
|
262
|
+
* @param {string | null | undefined} b
|
|
263
|
+
* @param {number} n
|
|
264
|
+
* @param {string} digits
|
|
265
|
+
* @return {string[]}
|
|
266
|
+
*/
|
|
267
|
+
export function generateNKeysBetween(a, b, n, digits = BASE_62_DIGITS) {
|
|
268
|
+
if (n === 0) {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
if (n === 1) {
|
|
272
|
+
return [generateKeyBetween(a, b, digits)];
|
|
273
|
+
}
|
|
274
|
+
if (b == null) {
|
|
275
|
+
let c = generateKeyBetween(a, b, digits);
|
|
276
|
+
const result = [c];
|
|
277
|
+
for (let i = 0; i < n - 1; i++) {
|
|
278
|
+
c = generateKeyBetween(c, b, digits);
|
|
279
|
+
result.push(c);
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
if (a == null) {
|
|
284
|
+
let c = generateKeyBetween(a, b, digits);
|
|
285
|
+
const result = [c];
|
|
286
|
+
for (let i = 0; i < n - 1; i++) {
|
|
287
|
+
c = generateKeyBetween(a, c, digits);
|
|
288
|
+
result.push(c);
|
|
289
|
+
}
|
|
290
|
+
result.reverse();
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
const mid = Math.floor(n / 2);
|
|
294
|
+
const c = generateKeyBetween(a, b, digits);
|
|
295
|
+
return [
|
|
296
|
+
...generateNKeysBetween(a, c, mid, digits),
|
|
297
|
+
c,
|
|
298
|
+
...generateNKeysBetween(c, b, n - mid - 1, digits),
|
|
299
|
+
];
|
|
300
|
+
}
|
package/lib/interfaces.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ export type BehaviorByID = {
|
|
|
17
17
|
export type Props = {
|
|
18
18
|
[id: string]: Prop;
|
|
19
19
|
};
|
|
20
|
+
export type ComputedHierarchy = {
|
|
21
|
+
[id: string]: string[];
|
|
22
|
+
};
|
|
20
23
|
export type Import = string;
|
|
21
24
|
export type ParsedImport = [string, string];
|
|
22
25
|
export type ElementType = Import;
|
|
@@ -37,9 +40,6 @@ export interface ZComponentData {
|
|
|
37
40
|
};
|
|
38
41
|
};
|
|
39
42
|
behaviors: BehaviorByID;
|
|
40
|
-
behaviorsByNode: {
|
|
41
|
-
[nodeId: string]: string[];
|
|
42
|
-
};
|
|
43
43
|
animation?: Animation;
|
|
44
44
|
entitiesByLabel?: {
|
|
45
45
|
[id: string]: {
|
|
@@ -57,11 +57,16 @@ export interface NodeData {
|
|
|
57
57
|
label?: string;
|
|
58
58
|
scriptName?: string;
|
|
59
59
|
type: ElementType;
|
|
60
|
-
parent?:
|
|
61
|
-
|
|
60
|
+
parent?: {
|
|
61
|
+
id: string;
|
|
62
|
+
order: string;
|
|
63
|
+
};
|
|
62
64
|
}
|
|
63
65
|
export interface BehaviorData {
|
|
64
66
|
id: string;
|
|
65
67
|
type: Import;
|
|
66
|
-
|
|
68
|
+
parent: {
|
|
69
|
+
id: string;
|
|
70
|
+
order: string;
|
|
71
|
+
};
|
|
67
72
|
}
|
package/lib/validators.js
CHANGED
|
@@ -11,7 +11,6 @@ export const validatorZcomponentTopLevel = (url, component) => {
|
|
|
11
11
|
typeof component.entityConstructorProps !== 'object' ||
|
|
12
12
|
typeof component.entityProps !== 'object' ||
|
|
13
13
|
typeof component.behaviors !== 'object' ||
|
|
14
|
-
typeof component.behaviorsByNode !== 'object' ||
|
|
15
14
|
typeof component.id !== 'string' ||
|
|
16
15
|
typeof component.root !== 'string') {
|
|
17
16
|
return [{ text: 'Component structure is invalid' }];
|
|
@@ -23,10 +22,16 @@ export const validatorZcomponentTopLevel = (url, component) => {
|
|
|
23
22
|
return [];
|
|
24
23
|
};
|
|
25
24
|
export const validatorZcomponentComponent = (node, nodeId) => {
|
|
26
|
-
if (typeof node !== 'object'
|
|
27
|
-
return [{ text: '
|
|
25
|
+
if (typeof node !== 'object')
|
|
26
|
+
return [{ text: 'Node not object' }];
|
|
28
27
|
if (node.id !== nodeId)
|
|
29
|
-
return [{ text: '
|
|
28
|
+
return [{ text: 'Node ID mismatch' }];
|
|
29
|
+
if (node.parent !== undefined && typeof node.parent !== 'object')
|
|
30
|
+
return [{ text: 'Node parent invalid' }];
|
|
31
|
+
if (node.parent !== undefined && typeof node.parent.id !== 'string')
|
|
32
|
+
return [{ text: 'Node parent ID invalid' }];
|
|
33
|
+
if (node.parent !== undefined && typeof node.parent.order !== 'string')
|
|
34
|
+
return [{ text: 'Node parent order invalid' }];
|
|
30
35
|
const issues = validateImport(node.type);
|
|
31
36
|
return issues;
|
|
32
37
|
};
|
package/lib/zcomponent.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Component } from './component';
|
|
|
3
3
|
import { Context } from './context';
|
|
4
4
|
import { isDesignTime } from './contexts/environmentcontext';
|
|
5
5
|
import { TagContext } from './contexts/tagcontext';
|
|
6
|
+
import { computeBehaviorHierarchy, computeNodeHierarchy } from './data';
|
|
6
7
|
import { Observable } from './observable';
|
|
7
8
|
import { setCurrentZComponentConstruction } from './zcomponentconstruction';
|
|
8
9
|
export class ZComponentContext extends Context {
|
|
@@ -53,8 +54,9 @@ export class ZComponent extends Component {
|
|
|
53
54
|
constructor(contextManager, constructorProps) {
|
|
54
55
|
that._opts.onConstructingNode?.(nodeId);
|
|
55
56
|
const children = constructorProps.children ?? [];
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
const childIDs = computeNodeHierarchy(that._opts.data.nodes)[nodeId] ?? [];
|
|
58
|
+
for (const childID of childIDs) {
|
|
59
|
+
const childInstance = that._constructorForNode(childID);
|
|
58
60
|
if (childInstance)
|
|
59
61
|
children.push([childInstance, {}]);
|
|
60
62
|
}
|
|
@@ -169,11 +171,18 @@ export class ZComponent extends Component {
|
|
|
169
171
|
this._behaviorsToInitialize = [];
|
|
170
172
|
}
|
|
171
173
|
_wrapBehaviors(nodeID, impl, ctx) {
|
|
172
|
-
const behaviors = this._opts.data.
|
|
174
|
+
const behaviors = computeBehaviorHierarchy(this._opts.data.behaviors)[nodeID] ?? [];
|
|
173
175
|
for (const behaviorID of behaviors) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
176
|
+
try {
|
|
177
|
+
const constructor = this._constructorForBehavior(behaviorID);
|
|
178
|
+
if (constructor)
|
|
179
|
+
new constructor(ctx, impl);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
const nodeName = this._opts.data.nodes[nodeID]?.label ?? "Unknown node";
|
|
183
|
+
const behaviorType = this._opts.data.behaviors[behaviorID]?.type;
|
|
184
|
+
console.log('An error occurred when constructing behavior of type:', behaviorType, 'for node:', nodeName, err);
|
|
185
|
+
}
|
|
177
186
|
}
|
|
178
187
|
}
|
|
179
188
|
notifyPropsChanged(entries) {
|