@zcomponent/core 0.0.10 → 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/data.d.ts CHANGED
@@ -1,27 +1,26 @@
1
- import { BehaviorData, NodeData, ZComponentData } from "./interfaces";
2
- export declare function addNode(zcomp: ZComponentData, info: NodeDataCombined, parentIndex?: number): void;
3
- export declare function addBehavior(zcomp: ZComponentData, info: BehaviorDataCombined, parentIndex?: number): void;
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 addNodeToParent(zcomp: ZComponentData, id: string, parentID: string, indx?: number): void;
10
- export declare function removeNodeFromParent(zcomp: ZComponentData, id: string): void;
11
- export declare function addBehaviorToNode(zcomp: ZComponentData, id: string, nodeID: string, indx?: number): void;
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
- export function addNode(zcomp, info, parentIndex) {
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
- if (info.data.parent)
7
- addNodeToParent(zcomp, info.data.id, info.data.parent, parentIndex);
62
+ computedHierarchies.delete(zcomp.nodes);
8
63
  }
9
- export function addBehavior(zcomp, info, parentIndex) {
64
+ export function addBehavior(zcomp, info) {
10
65
  zcomp.behaviors[info.data.id] = info.data;
11
66
  addEntity(zcomp, info.data.id, info);
12
- if (info.data.nodeId)
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
- removeNodeFromParent(zcomp, id);
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 addNodeToParent(zcomp, id, parentID, indx) {
52
- const node = zcomp.nodes[id];
53
- if (!node)
133
+ export function fixAnyDuplicateIndex(zcomp, hierarchy, nodeID, indx) {
134
+ if (indx < 0)
54
135
  return;
55
- const parent = zcomp.nodes[parentID];
56
- if (typeof parent !== 'object' || !Array.isArray(parent.children))
136
+ const children = hierarchy[nodeID];
137
+ const a = children[indx];
138
+ const b = children[indx + 1];
139
+ if (!a || !b)
57
140
  return;
58
- parent.children.splice(indx ?? parent.children.length, 0, { id });
59
- node.parent = parentID;
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
- const parent = findNodeParentAndIndex(zcomp, id);
66
- if (!parent)
145
+ if (nodeA.parent.id !== nodeB.parent.id)
67
146
  return;
68
- const parentNode = zcomp.nodes[parent[0]];
69
- if (typeof parentNode !== 'object' || !Array.isArray(parentNode.children))
147
+ if (nodeA.parent.order !== nodeB.parent.order)
70
148
  return;
71
- parentNode.children.splice(parent[1], 1);
72
- delete node.parent;
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 addBehaviorToNode(zcomp, id, nodeID, indx) {
75
- const behavior = zcomp.behaviors[id];
76
- if (!behavior)
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 (!zcomp.behaviorsByNode[nodeID])
79
- zcomp.behaviorsByNode[nodeID] = [];
80
- if (!Array.isArray(zcomp.behaviorsByNode[nodeID]))
168
+ if (behaviorA.parent.order !== behaviorB.parent.order)
81
169
  return;
82
- zcomp.behaviorsByNode[nodeID].splice(indx ?? zcomp.behaviorsByNode[nodeID].length, 0, id);
83
- behavior.nodeId = nodeID;
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
- const parent = findNodeParentAndIndex(zcomp, id);
238
- fn(id, parent);
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
+ }
@@ -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?: string;
61
- children: Child[];
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
- nodeId?: string;
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' || !Array.isArray(node.children))
27
- return [{ text: 'Component structure ID has zero length' }];
25
+ if (typeof node !== 'object')
26
+ return [{ text: 'Node not object' }];
28
27
  if (node.id !== nodeId)
29
- return [{ text: 'Component ID mismatch' }];
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
- for (const child of node?.children ?? []) {
57
- const childInstance = that._constructorForNode(child.id);
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,7 +171,7 @@ export class ZComponent extends Component {
169
171
  this._behaviorsToInitialize = [];
170
172
  }
171
173
  _wrapBehaviors(nodeID, impl, ctx) {
172
- const behaviors = this._opts.data.behaviorsByNode?.[nodeID] ?? [];
174
+ const behaviors = computeBehaviorHierarchy(this._opts.data.behaviors)[nodeID] ?? [];
173
175
  for (const behaviorID of behaviors) {
174
176
  try {
175
177
  const constructor = this._constructorForBehavior(behaviorID);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zcomponent/core",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",