dirvi-lib 0.11.0-beta.5 → 0.11.0-beta.7
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/dist/app/app-api.d.ts +7 -25
- package/dist/app/app-api.d.ts.map +1 -1
- package/dist/app/app-api.js +1 -11
- package/dist/app/index.d.ts +1 -0
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +1 -0
- package/dist/app/node-presentation.d.ts +13 -0
- package/dist/app/node-presentation.d.ts.map +1 -0
- package/dist/app/reducer/effect-to-action.d.ts +2 -2
- package/dist/app/reducer/effect-to-action.d.ts.map +1 -1
- package/dist/app/reducer/effect-to-action.js +87 -32
- package/dist/app/reducer/reducer-action.d.ts +12 -6
- package/dist/app/reducer/reducer-action.d.ts.map +1 -1
- package/dist/app/reducer/reducer.d.ts +4 -2
- package/dist/app/reducer/reducer.d.ts.map +1 -1
- package/dist/app/reducer/reducer.js +102 -68
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/input/application/intent-to-effect.d.ts +2 -2
- package/dist/input/application/intent-to-effect.d.ts.map +1 -1
- package/dist/input/application/intent-to-effect.js +9 -10
- package/dist/input/application/parse-ex-command.d.ts +22 -12
- package/dist/input/application/parse-ex-command.d.ts.map +1 -1
- package/dist/input/application/parse-ex-command.js +61 -65
- package/dist/input/application/parse-normal-command.d.ts +1 -1
- package/dist/input/application/parse-normal-command.d.ts.map +1 -1
- package/dist/input/domain/effect.d.ts +9 -5
- package/dist/input/domain/effect.d.ts.map +1 -1
- package/dist/state-and-nav/cursor.d.ts +89 -0
- package/dist/state-and-nav/cursor.d.ts.map +1 -0
- package/dist/state-and-nav/cursor.js +84 -0
- package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.impl.d.ts +1 -1
- package/dist/state-and-nav/fold-node/deprecated/fold-node.impl.d.ts.map +1 -0
- package/dist/state-and-nav/fold-node/deprecated/fold-node.test.d.ts.map +1 -0
- package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.test.js +2 -2
- package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.types.d.ts +2 -2
- package/dist/state-and-nav/fold-node/deprecated/fold-node.types.d.ts.map +1 -0
- package/dist/{trees → state-and-nav}/fold-node/fold-node.impl.d.ts +3 -3
- package/dist/state-and-nav/fold-node/fold-node.impl.d.ts.map +1 -0
- package/dist/{trees → state-and-nav}/fold-node/fold-node.impl.js +1 -1
- package/dist/{trees → state-and-nav}/fold-node/fold-node.model.d.ts +1 -1
- package/dist/state-and-nav/fold-node/fold-node.model.d.ts.map +1 -0
- package/dist/{trees → state-and-nav}/fold-node/fold-node.types.d.ts +2 -2
- package/dist/state-and-nav/fold-node/fold-node.types.d.ts.map +1 -0
- package/dist/state-and-nav/fold-node/index.d.ts.map +1 -0
- package/dist/state-and-nav/index.d.ts +16 -0
- package/dist/state-and-nav/index.d.ts.map +1 -0
- package/dist/state-and-nav/index.js +6 -0
- package/dist/state-and-nav/nav/nav.ordered.impl.d.ts +12 -0
- package/dist/state-and-nav/nav/nav.ordered.impl.d.ts.map +1 -0
- package/dist/state-and-nav/nav/nav.ordered.impl.js +77 -0
- package/dist/state-and-nav/nav/nav.traversal.d.ts +11 -0
- package/dist/state-and-nav/nav/nav.traversal.d.ts.map +1 -0
- package/dist/state-and-nav/nav/nav.traversal.js +207 -0
- package/dist/{trees/nav-node/nav-node.types.d.ts → state-and-nav/nav/nav.types.d.ts} +32 -22
- package/dist/state-and-nav/nav/nav.types.d.ts.map +1 -0
- package/dist/state-and-nav/nav/nav.unordered.impl.d.ts +13 -0
- package/dist/state-and-nav/nav/nav.unordered.impl.d.ts.map +1 -0
- package/dist/state-and-nav/nav/nav.unordered.impl.js +77 -0
- package/dist/state-and-nav/state/state.impl.d.ts +9 -0
- package/dist/state-and-nav/state/state.impl.d.ts.map +1 -0
- package/dist/{trees → state-and-nav}/state/state.impl.js +62 -9
- package/dist/state-and-nav/state/state.schemas.d.ts +2 -0
- package/dist/state-and-nav/state/state.schemas.d.ts.map +1 -0
- package/dist/state-and-nav/state/state.schemas.js +39 -0
- package/dist/state-and-nav/state/state.types.d.ts +72 -0
- package/dist/state-and-nav/state/state.types.d.ts.map +1 -0
- package/dist/state-and-nav/tree/deprecated/tree-node.arb.d.ts.map +1 -0
- package/dist/state-and-nav/tree/deprecated/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/state-and-nav/tree/deprecated/tree-node.impl.d.ts.map +1 -0
- package/dist/state-and-nav/tree/deprecated/tree-node.schemas.d.ts.map +1 -0
- package/dist/state-and-nav/tree/deprecated/tree-node.test.d.ts.map +1 -0
- package/dist/state-and-nav/tree/deprecated/tree-node.types.d.ts.map +1 -0
- package/dist/state-and-nav/tree/index.d.ts +7 -0
- package/dist/state-and-nav/tree/index.d.ts.map +1 -0
- package/dist/state-and-nav/tree/index.js +1 -0
- package/dist/state-and-nav/tree/tree-node.arb.d.ts +94 -0
- package/dist/state-and-nav/tree/tree-node.arb.d.ts.map +1 -0
- package/dist/state-and-nav/tree/tree-node.arb.debug.test.d.ts +2 -0
- package/dist/state-and-nav/tree/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/state-and-nav/tree/tree-node.arb.debug.test.js +54 -0
- package/dist/state-and-nav/tree/tree-node.arb.js +188 -0
- package/dist/state-and-nav/tree/tree-node.model.d.ts +193 -0
- package/dist/state-and-nav/tree/tree-node.model.d.ts.map +1 -0
- package/dist/state-and-nav/tree/tree-node.model.js +110 -0
- package/dist/state-and-nav/tree/tree-node.test.d.ts +2 -0
- package/dist/state-and-nav/tree/tree-node.test.d.ts.map +1 -0
- package/dist/state-and-nav/tree/tree-node.test.js +234 -0
- package/dist/{trees/tree-node/tree-node.impl.d.ts → state-and-nav/tree/tree.impl.d.ts} +3 -3
- package/dist/state-and-nav/tree/tree.impl.d.ts.map +1 -0
- package/dist/{trees/tree-node/tree-node.impl.js → state-and-nav/tree/tree.impl.js} +19 -16
- package/dist/{trees/tree-node/tree-node.types.d.ts → state-and-nav/tree/tree.types.d.ts} +64 -44
- package/dist/state-and-nav/tree/tree.types.d.ts.map +1 -0
- package/dist/state-and-nav/tree/tree.types.js +1 -0
- package/package.json +1 -1
- package/dist/trees/cursor.d.ts +0 -59
- package/dist/trees/cursor.d.ts.map +0 -1
- package/dist/trees/cursor.js +0 -55
- package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts.map +0 -1
- package/dist/trees/fold-node/deprecated/fold-node.test.d.ts.map +0 -1
- package/dist/trees/fold-node/deprecated/fold-node.types.d.ts.map +0 -1
- package/dist/trees/fold-node/fold-node.impl.d.ts.map +0 -1
- package/dist/trees/fold-node/fold-node.model.d.ts.map +0 -1
- package/dist/trees/fold-node/fold-node.types.d.ts.map +0 -1
- package/dist/trees/fold-node/index.d.ts.map +0 -1
- package/dist/trees/index.d.ts +0 -9
- package/dist/trees/index.d.ts.map +0 -1
- package/dist/trees/index.js +0 -8
- package/dist/trees/nav-node/nav-node.impl.d.ts +0 -29
- package/dist/trees/nav-node/nav-node.impl.d.ts.map +0 -1
- package/dist/trees/nav-node/nav-node.impl.js +0 -223
- package/dist/trees/nav-node/nav-node.types.d.ts.map +0 -1
- package/dist/trees/state/state.impl.d.ts +0 -8
- package/dist/trees/state/state.impl.d.ts.map +0 -1
- package/dist/trees/state/state.schemas.d.ts +0 -25
- package/dist/trees/state/state.schemas.d.ts.map +0 -1
- package/dist/trees/state/state.schemas.js +0 -17
- package/dist/trees/state/state.types.d.ts +0 -36
- package/dist/trees/state/state.types.d.ts.map +0 -1
- package/dist/trees/tree-node/deprecated/tree-node.arb.d.ts.map +0 -1
- package/dist/trees/tree-node/deprecated/tree-node.arb.debug.test.d.ts.map +0 -1
- package/dist/trees/tree-node/deprecated/tree-node.impl.d.ts.map +0 -1
- package/dist/trees/tree-node/deprecated/tree-node.schemas.d.ts.map +0 -1
- package/dist/trees/tree-node/deprecated/tree-node.test.d.ts.map +0 -1
- package/dist/trees/tree-node/deprecated/tree-node.types.d.ts.map +0 -1
- package/dist/trees/tree-node/index.d.ts +0 -5
- package/dist/trees/tree-node/index.d.ts.map +0 -1
- package/dist/trees/tree-node/index.js +0 -2
- package/dist/trees/tree-node/tree-node.arb.d.ts +0 -104
- package/dist/trees/tree-node/tree-node.arb.d.ts.map +0 -1
- package/dist/trees/tree-node/tree-node.arb.js +0 -188
- package/dist/trees/tree-node/tree-node.impl.d.ts.map +0 -1
- package/dist/trees/tree-node/tree-node.model.d.ts +0 -78
- package/dist/trees/tree-node/tree-node.model.d.ts.map +0 -1
- package/dist/trees/tree-node/tree-node.model.js +0 -37
- package/dist/trees/tree-node/tree-node.types.d.ts.map +0 -1
- /package/dist/{trees/fold-node/deprecated/fold-node.types.js → app/node-presentation.js} +0 -0
- /package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.impl.js +0 -0
- /package/dist/{trees → state-and-nav}/fold-node/deprecated/fold-node.test.d.ts +0 -0
- /package/dist/{trees/fold-node → state-and-nav/fold-node/deprecated}/fold-node.types.js +0 -0
- /package/dist/{trees → state-and-nav}/fold-node/fold-node.model.js +0 -0
- /package/dist/{trees/nav-node/nav-node.types.js → state-and-nav/fold-node/fold-node.types.js} +0 -0
- /package/dist/{trees → state-and-nav}/fold-node/index.d.ts +0 -0
- /package/dist/{trees → state-and-nav}/fold-node/index.js +0 -0
- /package/dist/{trees/state/state.types.js → state-and-nav/nav/nav.types.js} +0 -0
- /package/dist/{trees/tree-node/deprecated/tree-node.types.js → state-and-nav/state/state.types.js} +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.d.ts +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.debug.test.d.ts +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.debug.test.js +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.arb.js +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.impl.d.ts +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.impl.js +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.schemas.d.ts +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.schemas.js +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.test.d.ts +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.test.js +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree}/deprecated/tree-node.types.d.ts +0 -0
- /package/dist/{trees/tree-node → state-and-nav/tree/deprecated}/tree-node.types.js +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-node.test.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/tree/tree-node.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import * as fc from 'fast-check';
|
|
3
|
+
import { createTreeMethods } from './tree.impl.js';
|
|
4
|
+
import { childrenStateModelArb, modelAndBranchPathArb, modelAndReachablePathArb, } from './tree-node.arb.js';
|
|
5
|
+
const api = createTreeMethods();
|
|
6
|
+
/**
|
|
7
|
+
* Fail rather than silently skipping assertions when an operation that should
|
|
8
|
+
* succeed returns undefined.
|
|
9
|
+
*/
|
|
10
|
+
function requireDefined(value) {
|
|
11
|
+
if (value === undefined) {
|
|
12
|
+
throw new Error('Expected a defined result');
|
|
13
|
+
}
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function fromModel(model) {
|
|
17
|
+
return requireDefined(api.fromModel(model));
|
|
18
|
+
}
|
|
19
|
+
function loadedBranch(id, children) {
|
|
20
|
+
return requireDefined(api.createLoadedBranch(id, 0, children));
|
|
21
|
+
}
|
|
22
|
+
describe('findChildById', () => {
|
|
23
|
+
it('returns the existing matching direct child', () => {
|
|
24
|
+
const child = api.createLeaf('child', 1);
|
|
25
|
+
const root = loadedBranch('root', [child]);
|
|
26
|
+
expect(api.findChildById(root, 'child')).toBe(child);
|
|
27
|
+
});
|
|
28
|
+
it('does not search descendants', () => {
|
|
29
|
+
const descendant = api.createLeaf('descendant', 1);
|
|
30
|
+
const child = loadedBranch('child', [descendant]);
|
|
31
|
+
const root = loadedBranch('root', [child]);
|
|
32
|
+
expect(api.findChildById(root, 'descendant')).toBeUndefined();
|
|
33
|
+
});
|
|
34
|
+
it.each([
|
|
35
|
+
{
|
|
36
|
+
name: 'an empty loaded branch',
|
|
37
|
+
root: loadedBranch('root', []),
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'a leaf',
|
|
41
|
+
root: api.createLeaf('root', 0),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: 'an unloaded branch',
|
|
45
|
+
root: api.createUnloadedBranch('root', 0),
|
|
46
|
+
},
|
|
47
|
+
])('returns undefined for a missing child in $name', ({ root }) => {
|
|
48
|
+
expect(api.findChildById(root, 'missing')).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('findAtPath and selectAtPath', () => {
|
|
52
|
+
it('resolves generated reachable paths', () => {
|
|
53
|
+
fc.assert(fc.property(modelAndReachablePathArb, ({ root: rootModel, path, expected }) => {
|
|
54
|
+
const root = fromModel(rootModel);
|
|
55
|
+
const selected = requireDefined(api.findAtPath(root, path));
|
|
56
|
+
expect(api.toModel(selected)).toStrictEqual(expected);
|
|
57
|
+
expect(api.selectAtPath(root, path, (node) => node)).toBe(selected);
|
|
58
|
+
expect(api.selectAtPath(root, path, (node) => api.id(node))).toBe(expected.id);
|
|
59
|
+
}));
|
|
60
|
+
});
|
|
61
|
+
it('calls the selector once with the existing resolved node', () => {
|
|
62
|
+
const child = api.createLeaf('child', 1);
|
|
63
|
+
const root = loadedBranch('root', [child]);
|
|
64
|
+
const selector = vi.fn((node) => api.id(node));
|
|
65
|
+
expect(api.selectAtPath(root, ['child'], selector)).toBe('child');
|
|
66
|
+
expect(selector).toHaveBeenCalledOnce();
|
|
67
|
+
expect(selector).toHaveBeenCalledWith(child);
|
|
68
|
+
});
|
|
69
|
+
it('resolves the root for an empty path', () => {
|
|
70
|
+
const root = api.createLeaf('root', 0);
|
|
71
|
+
const selector = vi.fn((node) => node);
|
|
72
|
+
expect(api.findAtPath(root, [])).toBe(root);
|
|
73
|
+
expect(api.selectAtPath(root, [], selector)).toBe(root);
|
|
74
|
+
expect(selector).toHaveBeenCalledOnce();
|
|
75
|
+
expect(selector).toHaveBeenCalledWith(root);
|
|
76
|
+
});
|
|
77
|
+
it.each([
|
|
78
|
+
{
|
|
79
|
+
name: 'a missing child',
|
|
80
|
+
root: loadedBranch('root', []),
|
|
81
|
+
path: ['missing'],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'a missing descendant',
|
|
85
|
+
root: loadedBranch('root', [loadedBranch('child', [])]),
|
|
86
|
+
path: ['child', 'missing'],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'a path through a leaf',
|
|
90
|
+
root: loadedBranch('root', [api.createLeaf('child', 1)]),
|
|
91
|
+
path: ['child', 'descendant'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'a path through an unloaded branch',
|
|
95
|
+
root: loadedBranch('root', [api.createUnloadedBranch('child', 1)]),
|
|
96
|
+
path: ['child', 'descendant'],
|
|
97
|
+
},
|
|
98
|
+
])('returns undefined for $name without calling the selector', ({ root, path }) => {
|
|
99
|
+
const selector = vi.fn((node) => node);
|
|
100
|
+
expect(api.findAtPath(root, path)).toBeUndefined();
|
|
101
|
+
expect(api.selectAtPath(root, path, selector)).toBeUndefined();
|
|
102
|
+
expect(selector).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
it('returns undefined when the selector returns undefined', () => {
|
|
105
|
+
const root = api.createLeaf('root', 0);
|
|
106
|
+
const selector = vi.fn((_node) => undefined);
|
|
107
|
+
expect(api.selectAtPath(root, [], selector)).toBeUndefined();
|
|
108
|
+
expect(selector).toHaveBeenCalledOnce();
|
|
109
|
+
expect(selector).toHaveBeenCalledWith(root);
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('updateAtPath', () => {
|
|
113
|
+
it('replaces a generated node without changing the original tree', () => {
|
|
114
|
+
fc.assert(fc.property(modelAndReachablePathArb, ({ root: rootModel, path, expected }) => {
|
|
115
|
+
const root = fromModel(rootModel);
|
|
116
|
+
const originalNode = requireDefined(api.findAtPath(root, path));
|
|
117
|
+
const replacement = api.createLeaf(expected.id, expected.value);
|
|
118
|
+
const updatedRoot = requireDefined(api.updateAtPath(root, path, () => replacement));
|
|
119
|
+
expect(api.findAtPath(updatedRoot, path)).toBe(replacement);
|
|
120
|
+
expect(api.findAtPath(root, path)).toBe(originalNode);
|
|
121
|
+
expect(api.toModel(root)).toStrictEqual(rootModel);
|
|
122
|
+
}));
|
|
123
|
+
});
|
|
124
|
+
it('replaces child loading state at a generated branch path', () => {
|
|
125
|
+
fc.assert(fc.property(modelAndBranchPathArb, childrenStateModelArb, ({ root: rootModel, path, expected }, newChildren) => {
|
|
126
|
+
const root = fromModel(rootModel);
|
|
127
|
+
const originalNode = requireDefined(api.findAtPath(root, path));
|
|
128
|
+
const updatedRoot = requireDefined(api.updateAtPath(root, path, (node) => newChildren === null
|
|
129
|
+
? api.toUnloadedBranch(node)
|
|
130
|
+
: api.withLoadedChildren(node, newChildren.map(fromModel))));
|
|
131
|
+
const updatedNode = requireDefined(api.findAtPath(updatedRoot, path));
|
|
132
|
+
expect(updatedNode).not.toBe(originalNode);
|
|
133
|
+
expect(api.toModel(updatedNode)).toStrictEqual({
|
|
134
|
+
id: expected.id,
|
|
135
|
+
value: expected.value,
|
|
136
|
+
children: newChildren,
|
|
137
|
+
});
|
|
138
|
+
expect(api.findAtPath(root, path)).toBe(originalNode);
|
|
139
|
+
expect(api.toModel(root)).toStrictEqual(rootModel);
|
|
140
|
+
}));
|
|
141
|
+
});
|
|
142
|
+
it('calls the updater once and preserves position when the ID changes', () => {
|
|
143
|
+
const before = api.createLeaf('before', 1);
|
|
144
|
+
const child = api.createLeaf('child', 2);
|
|
145
|
+
const after = api.createLeaf('after', 3);
|
|
146
|
+
const root = loadedBranch('root', [before, child, after]);
|
|
147
|
+
const replacement = api.createLeaf('replacement', 4);
|
|
148
|
+
const update = vi.fn(() => replacement);
|
|
149
|
+
const updatedRoot = requireDefined(api.updateAtPath(root, ['child'], update));
|
|
150
|
+
expect(update).toHaveBeenCalledOnce();
|
|
151
|
+
expect(update).toHaveBeenCalledWith(child);
|
|
152
|
+
expect(api.findAtPath(updatedRoot, ['replacement'])).toBe(replacement);
|
|
153
|
+
expect(api.findAtPath(updatedRoot, ['child'])).toBeUndefined();
|
|
154
|
+
const children = [...requireDefined(api.getLoadedChildren(updatedRoot))];
|
|
155
|
+
expect(children.map((node) => api.id(node))).toStrictEqual([
|
|
156
|
+
'before',
|
|
157
|
+
'replacement',
|
|
158
|
+
'after',
|
|
159
|
+
]);
|
|
160
|
+
expect(children[0]).toBe(before);
|
|
161
|
+
expect(children[1]).toBe(replacement);
|
|
162
|
+
expect(children[2]).toBe(after);
|
|
163
|
+
expect(api.findAtPath(root, ['child'])).toBe(child);
|
|
164
|
+
});
|
|
165
|
+
it('rejects a replacement ID that duplicates a sibling ID', () => {
|
|
166
|
+
const child = api.createLeaf('child', 1);
|
|
167
|
+
const sibling = api.createLeaf('sibling', 2);
|
|
168
|
+
const root = loadedBranch('root', [child, sibling]);
|
|
169
|
+
const replacement = api.createLeaf('sibling', 3);
|
|
170
|
+
const update = vi.fn(() => replacement);
|
|
171
|
+
expect(api.updateAtPath(root, ['child'], update)).toBeUndefined();
|
|
172
|
+
expect(update).toHaveBeenCalledOnce();
|
|
173
|
+
expect(update).toHaveBeenCalledWith(child);
|
|
174
|
+
expect(api.findAtPath(root, ['child'])).toBe(child);
|
|
175
|
+
expect(api.findAtPath(root, ['sibling'])).toBe(sibling);
|
|
176
|
+
});
|
|
177
|
+
it('rebuilds ancestors while preserving unrelated node identities', () => {
|
|
178
|
+
const target = api.createLeaf('target', 1);
|
|
179
|
+
const sibling = api.createLeaf('sibling', 2);
|
|
180
|
+
const branch = loadedBranch('branch', [target, sibling]);
|
|
181
|
+
const otherBranch = loadedBranch('other', []);
|
|
182
|
+
const root = loadedBranch('root', [branch, otherBranch]);
|
|
183
|
+
const replacement = api.createLeaf('target', 3);
|
|
184
|
+
const updatedRoot = requireDefined(api.updateAtPath(root, ['branch', 'target'], () => replacement));
|
|
185
|
+
expect(updatedRoot).not.toBe(root);
|
|
186
|
+
expect(api.findAtPath(updatedRoot, ['branch'])).not.toBe(branch);
|
|
187
|
+
expect(api.findAtPath(updatedRoot, ['branch', 'target'])).toBe(replacement);
|
|
188
|
+
expect(api.findAtPath(updatedRoot, ['branch', 'sibling'])).toBe(sibling);
|
|
189
|
+
expect(api.findAtPath(updatedRoot, ['other'])).toBe(otherBranch);
|
|
190
|
+
expect(api.findAtPath(root, ['branch', 'target'])).toBe(target);
|
|
191
|
+
});
|
|
192
|
+
it('replaces the root for an empty path', () => {
|
|
193
|
+
const root = api.createLeaf('root', 0);
|
|
194
|
+
const replacement = api.createLeaf('replacement', 1);
|
|
195
|
+
const update = vi.fn(() => replacement);
|
|
196
|
+
expect(api.updateAtPath(root, [], update)).toBe(replacement);
|
|
197
|
+
expect(update).toHaveBeenCalledOnce();
|
|
198
|
+
expect(update).toHaveBeenCalledWith(root);
|
|
199
|
+
});
|
|
200
|
+
it.each([
|
|
201
|
+
{
|
|
202
|
+
name: 'a missing child',
|
|
203
|
+
root: loadedBranch('root', []),
|
|
204
|
+
path: ['missing'],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'a path through a leaf',
|
|
208
|
+
root: api.createLeaf('root', 0),
|
|
209
|
+
path: ['child'],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'a path through an unloaded branch',
|
|
213
|
+
root: api.createUnloadedBranch('root', 0),
|
|
214
|
+
path: ['child'],
|
|
215
|
+
},
|
|
216
|
+
])('returns undefined for $name without calling the updater', ({ root, path }) => {
|
|
217
|
+
const update = vi.fn((node) => node);
|
|
218
|
+
expect(api.updateAtPath(root, path, update)).toBeUndefined();
|
|
219
|
+
expect(update).not.toHaveBeenCalled();
|
|
220
|
+
});
|
|
221
|
+
it.each([
|
|
222
|
+
{ name: 'the root', path: [] },
|
|
223
|
+
{ name: 'a descendant', path: ['child'] },
|
|
224
|
+
])('returns undefined when updating $name is aborted', ({ path }) => {
|
|
225
|
+
const child = api.createLeaf('child', 1);
|
|
226
|
+
const root = loadedBranch('root', [child]);
|
|
227
|
+
const selected = requireDefined(api.findAtPath(root, path));
|
|
228
|
+
const update = vi.fn(() => undefined);
|
|
229
|
+
expect(api.updateAtPath(root, path, update)).toBeUndefined();
|
|
230
|
+
expect(update).toHaveBeenCalledOnce();
|
|
231
|
+
expect(update).toHaveBeenCalledWith(selected);
|
|
232
|
+
expect(api.findAtPath(root, ['child'])).toBe(child);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TreeMethods } from './tree.types.js';
|
|
2
2
|
import type { SerializableKey } from './tree-node.model.js';
|
|
3
3
|
/**
|
|
4
4
|
* Creates an API for constructing, inspecting, and immutably updating opaque
|
|
@@ -11,5 +11,5 @@ import type { SerializableKey } from './tree-node.model.js';
|
|
|
11
11
|
* Tree updates preserve object identity for unrelated nodes. New ancestor
|
|
12
12
|
* nodes and child collections are created only along an updated path.
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
15
|
-
//# sourceMappingURL=tree
|
|
14
|
+
export declare function createTreeMethods<Id extends SerializableKey, Value>(): TreeMethods<Id, Value>;
|
|
15
|
+
//# sourceMappingURL=tree.impl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.impl.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/tree/tree.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EAGZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,sBAAsB,CAAC;AA2J3E;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,EAC1B,KAAK,KACF,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,CAgR1B"}
|
|
@@ -13,6 +13,9 @@ function toData(node) {
|
|
|
13
13
|
function fromData(data) {
|
|
14
14
|
return data;
|
|
15
15
|
}
|
|
16
|
+
// =============================================================================
|
|
17
|
+
// Helpers
|
|
18
|
+
// =============================================================================
|
|
16
19
|
function isLeafData(node) {
|
|
17
20
|
return !('children' in node);
|
|
18
21
|
}
|
|
@@ -23,11 +26,8 @@ function isLoadedBranchData(node) {
|
|
|
23
26
|
return 'children' in node && node.children !== null;
|
|
24
27
|
}
|
|
25
28
|
/**
|
|
26
|
-
* Returns a reusable iterable over child nodes
|
|
27
|
-
* child
|
|
28
|
-
*
|
|
29
|
-
* The implementation currently stores children in an array, but callers only
|
|
30
|
-
* receive an `Iterable` and must not rely on iteration order.
|
|
29
|
+
* Returns a reusable iterable over child nodes in their stored order without
|
|
30
|
+
* exposing the internal child array.
|
|
31
31
|
*/
|
|
32
32
|
function asChildrenIterable(children) {
|
|
33
33
|
return Object.freeze({
|
|
@@ -45,14 +45,15 @@ function asChildrenIterable(children) {
|
|
|
45
45
|
* for `createLoadedBranch` and `withLoadedChildren`.
|
|
46
46
|
*/
|
|
47
47
|
function copyUniqueChildren(children, getId) {
|
|
48
|
-
const copiedChildren = [
|
|
48
|
+
const copiedChildren = [];
|
|
49
49
|
const seenIds = new Set();
|
|
50
|
-
for (const child of
|
|
50
|
+
for (const child of children) {
|
|
51
51
|
const id = getId(child);
|
|
52
52
|
if (seenIds.has(id)) {
|
|
53
53
|
return undefined;
|
|
54
54
|
}
|
|
55
55
|
seenIds.add(id);
|
|
56
|
+
copiedChildren.push(child);
|
|
56
57
|
}
|
|
57
58
|
return Object.freeze(copiedChildren);
|
|
58
59
|
}
|
|
@@ -76,6 +77,9 @@ function createLoadedBranchData(id, value, children) {
|
|
|
76
77
|
children,
|
|
77
78
|
});
|
|
78
79
|
}
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// Generic Tree Methods Init
|
|
82
|
+
// =============================================================================
|
|
79
83
|
/**
|
|
80
84
|
* Creates an API for constructing, inspecting, and immutably updating opaque
|
|
81
85
|
* tree nodes.
|
|
@@ -87,8 +91,8 @@ function createLoadedBranchData(id, value, children) {
|
|
|
87
91
|
* Tree updates preserve object identity for unrelated nodes. New ancestor
|
|
88
92
|
* nodes and child collections are created only along an updated path.
|
|
89
93
|
*/
|
|
90
|
-
export function
|
|
91
|
-
function
|
|
94
|
+
export function createTreeMethods() {
|
|
95
|
+
function getLoadedChildrenData(node) {
|
|
92
96
|
const data = toData(node);
|
|
93
97
|
return isLoadedBranchData(data) ? data.children : undefined;
|
|
94
98
|
}
|
|
@@ -143,11 +147,11 @@ export function createTreeNodeApi() {
|
|
|
143
147
|
});
|
|
144
148
|
},
|
|
145
149
|
getLoadedChildren(node) {
|
|
146
|
-
const children =
|
|
150
|
+
const children = getLoadedChildrenData(node);
|
|
147
151
|
return children === undefined ? undefined : asChildrenIterable(children);
|
|
148
152
|
},
|
|
149
153
|
findChildById(node, id) {
|
|
150
|
-
const children =
|
|
154
|
+
const children = getLoadedChildrenData(node);
|
|
151
155
|
if (children === undefined) {
|
|
152
156
|
return undefined;
|
|
153
157
|
}
|
|
@@ -245,7 +249,7 @@ export function createTreeNodeApi() {
|
|
|
245
249
|
};
|
|
246
250
|
function modifyChildAtPath(node, path, modifier) {
|
|
247
251
|
const childId = path[0];
|
|
248
|
-
const children =
|
|
252
|
+
const children = getLoadedChildrenData(node);
|
|
249
253
|
if (childId === undefined || children === undefined) {
|
|
250
254
|
return undefined;
|
|
251
255
|
}
|
|
@@ -270,14 +274,13 @@ export function createTreeNodeApi() {
|
|
|
270
274
|
return undefined;
|
|
271
275
|
}
|
|
272
276
|
/*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
* this is solely an implementation detail.
|
|
277
|
+
* Replace the child at its existing position, preserving sibling order even
|
|
278
|
+
* when the replacement has a different ID.
|
|
276
279
|
*/
|
|
277
280
|
const updatedChildren = [...children];
|
|
278
281
|
updatedChildren[childIndex] = updatedChild;
|
|
279
282
|
/*
|
|
280
|
-
* `
|
|
283
|
+
* `withLoadedChildren` revalidates sibling-ID uniqueness. This matters when
|
|
281
284
|
* `modifier` replaces a node with another node having a different ID.
|
|
282
285
|
*/
|
|
283
286
|
return api.withLoadedChildren(node, updatedChildren);
|
|
@@ -25,7 +25,7 @@ declare const treeNodeBrand: unique symbol;
|
|
|
25
25
|
* @remarks
|
|
26
26
|
*
|
|
27
27
|
* `TreeNode` is intentionally opaque. Callers may hold, compare by reference,
|
|
28
|
-
* pass, store, and return nodes, but should use {@link
|
|
28
|
+
* pass, store, and return nodes, but should use {@link TreeMethods} operations
|
|
29
29
|
* to create, inspect, and update them.
|
|
30
30
|
*
|
|
31
31
|
* In particular, callers do not depend on:
|
|
@@ -111,14 +111,14 @@ export type TreeNodeKind = 'leaf' | 'unloaded-branch' | 'loaded-branch';
|
|
|
111
111
|
*
|
|
112
112
|
* @remarks
|
|
113
113
|
*
|
|
114
|
-
* `
|
|
115
|
-
*
|
|
116
|
-
*
|
|
114
|
+
* `MatchByKind` provides exhaustive pattern matching over the node's semantic
|
|
115
|
+
* state. It is the preferred operation when behavior differs between leaves,
|
|
116
|
+
* unloaded branches, and loaded branches:
|
|
117
117
|
*
|
|
118
118
|
* - `leaf` receives no `children`, because a leaf cannot have children;
|
|
119
119
|
* - `unloadedBranch` receives no `children`, because its children are not loaded;
|
|
120
|
-
* - `loadedBranch` receives an iterable of loaded direct child nodes
|
|
121
|
-
*
|
|
120
|
+
* - `loadedBranch` receives an iterable of loaded direct child nodes in
|
|
121
|
+
* child order.
|
|
122
122
|
*
|
|
123
123
|
* Every handler must return the same `Result` type.
|
|
124
124
|
*
|
|
@@ -168,12 +168,16 @@ export type MatchByKind<Id extends SerializableKey, Value, Result> = Readonly<{
|
|
|
168
168
|
*
|
|
169
169
|
* A `TreeNodeApi` is specialized to one ID type and one application-value
|
|
170
170
|
* type. It owns the structural tree rules: leaf/branch state, child loading
|
|
171
|
-
* state, sibling-ID uniqueness, and immutable ancestor
|
|
171
|
+
* state, child order, sibling-ID uniqueness, and immutable ancestor
|
|
172
|
+
* reconstruction.
|
|
172
173
|
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
174
|
+
* Loaded children form an ordered sequence. Construction and child-replacement
|
|
175
|
+
* operations preserve the supplied iterable's iteration order. Inspection
|
|
176
|
+
* operations expose children in that order, and model conversion preserves it.
|
|
177
|
+
* Replacing a descendant retains its position among siblings.
|
|
178
|
+
*
|
|
179
|
+
* The backing representation is private; these ordering guarantees do not
|
|
180
|
+
* require callers to depend on a particular storage structure.
|
|
177
181
|
*
|
|
178
182
|
* Tree nodes are plain immutable values at runtime. Update operations do not
|
|
179
183
|
* mutate supplied nodes or their loaded child collections. When an update
|
|
@@ -191,19 +195,19 @@ export type MatchByKind<Id extends SerializableKey, Value, Result> = Readonly<{
|
|
|
191
195
|
*
|
|
192
196
|
* the root is addressed by `[]`, and `child` is addressed by `['child']`.
|
|
193
197
|
*
|
|
194
|
-
* Paths cannot pass through leaves or
|
|
195
|
-
* selected as the final node in a path, but its unloaded descendants cannot
|
|
196
|
-
* addressed.
|
|
198
|
+
* Paths cannot pass through leaves or unloaded branches. An unloaded branch may
|
|
199
|
+
* be selected as the final node in a path, but its unloaded descendants cannot
|
|
200
|
+
* be addressed.
|
|
197
201
|
*
|
|
198
202
|
* @typeParam Id - The type used to identify nodes among siblings.
|
|
199
203
|
* @typeParam Value - The application-specific value stored at each node.
|
|
200
204
|
*/
|
|
201
|
-
export type
|
|
205
|
+
export type TreeMethods<Id extends SerializableKey, Value> = Readonly<{
|
|
202
206
|
/**
|
|
203
207
|
* Creates a leaf node.
|
|
204
208
|
*
|
|
205
209
|
* A leaf cannot contain children. To convert an existing node to a leaf while
|
|
206
|
-
* retaining its ID and value, use {@link
|
|
210
|
+
* retaining its ID and value, use {@link TreeMethods.toLeaf}.
|
|
207
211
|
*
|
|
208
212
|
* @param id - The ID that identifies this node among its siblings.
|
|
209
213
|
* @param value - Application-owned data stored on the node.
|
|
@@ -219,7 +223,13 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
219
223
|
*/
|
|
220
224
|
createUnloadedBranch(id: Id, value: Value): TreeNode<Id, Value>;
|
|
221
225
|
/**
|
|
222
|
-
* Creates a branch with
|
|
226
|
+
* Creates a branch with an ordered, loaded child collection.
|
|
227
|
+
*
|
|
228
|
+
* Children are copied in the supplied iterable's iteration order. Subsequent
|
|
229
|
+
* changes to the supplied collection do not affect the created branch.
|
|
230
|
+
*
|
|
231
|
+
* Direct siblings must have unique IDs. Returns `undefined` if the supplied
|
|
232
|
+
* children contain duplicate sibling IDs.
|
|
223
233
|
*
|
|
224
234
|
* An empty child collection creates a loaded branch with no current children;
|
|
225
235
|
* it does not create a leaf.
|
|
@@ -245,7 +255,7 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
245
255
|
/**
|
|
246
256
|
* Returns the semantic kind of a node.
|
|
247
257
|
*
|
|
248
|
-
* Prefer {@link
|
|
258
|
+
* Prefer {@link TreeMethods.matchByKind} when behavior differs by node kind and all
|
|
249
259
|
* three variants must be handled.
|
|
250
260
|
*
|
|
251
261
|
* @param node - The node to inspect.
|
|
@@ -256,24 +266,24 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
256
266
|
* Inspects a node by handling its semantic state exhaustively.
|
|
257
267
|
*
|
|
258
268
|
* @param node - The node to inspect.
|
|
259
|
-
* @param handlers - Case handlers for leaf,
|
|
269
|
+
* @param handlers - Case handlers for leaf, and unloaded/loaded branch
|
|
260
270
|
* states.
|
|
261
271
|
* @returns The value returned by the selected case handler.
|
|
262
272
|
*/
|
|
263
273
|
matchByKind<Result>(node: TreeNode<Id, Value>, handlers: MatchByKind<Id, Value, Result>): Result;
|
|
264
274
|
/**
|
|
265
|
-
* Returns the loaded direct children of a loaded branch.
|
|
275
|
+
* Returns the loaded direct children of a loaded branch in child order.
|
|
266
276
|
*
|
|
267
|
-
* The returned child nodes are existing values,
|
|
268
|
-
*
|
|
277
|
+
* The returned iterable is reusable. Its child nodes are existing values,
|
|
278
|
+
* not defensive clones.
|
|
269
279
|
*
|
|
270
280
|
* Returns `undefined` when `node` is a leaf or an unloaded branch. Use
|
|
271
|
-
* {@link
|
|
281
|
+
* {@link TreeMethods.kind} or {@link TreeMethods.matchByKind} when those cases need
|
|
272
282
|
* to be distinguished.
|
|
273
283
|
*
|
|
274
284
|
* @param node - The node whose direct children to inspect.
|
|
275
285
|
* @returns An iterable of loaded direct children, or `undefined` when children
|
|
276
|
-
* are unavailable because `node` is a leaf or
|
|
286
|
+
* are unavailable because `node` is a leaf or unloaded branch.
|
|
277
287
|
*/
|
|
278
288
|
getLoadedChildren(node: TreeNode<Id, Value>): Iterable<TreeNode<Id, Value>> | undefined;
|
|
279
289
|
/**
|
|
@@ -284,16 +294,16 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
284
294
|
*
|
|
285
295
|
* @param node - The node whose loaded direct children to search.
|
|
286
296
|
* @param id - The sibling ID to find.
|
|
287
|
-
* @returns The existing matching child, or `undefined` when `node` is not
|
|
288
|
-
*
|
|
297
|
+
* @returns The existing matching child, or `undefined` when `node` is not a
|
|
298
|
+
* loaded branch or does not have a direct child with `id`.
|
|
289
299
|
*/
|
|
290
300
|
findChildById(node: TreeNode<Id, Value>, id: Id): TreeNode<Id, Value> | undefined;
|
|
291
301
|
/**
|
|
292
302
|
* Returns an equivalent node with replacement application data.
|
|
293
303
|
*
|
|
294
304
|
* The returned node retains the original node's ID, semantic child state,
|
|
295
|
-
* and when applicable, existing child-node references.
|
|
296
|
-
* not mutated.
|
|
305
|
+
* and when applicable, child order and existing child-node references.
|
|
306
|
+
* The supplied node is not mutated.
|
|
297
307
|
*
|
|
298
308
|
* @param node - The node to update.
|
|
299
309
|
* @param value - Replacement application-owned data.
|
|
@@ -317,17 +327,18 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
317
327
|
* Any currently loaded child collection is discarded.
|
|
318
328
|
*
|
|
319
329
|
* @param node - The node to convert.
|
|
320
|
-
* @returns
|
|
330
|
+
* @returns An unloaded branch with the source node's ID and value.
|
|
321
331
|
*/
|
|
322
332
|
toUnloadedBranch(node: TreeNode<Id, Value>): TreeNode<Id, Value>;
|
|
323
333
|
/**
|
|
324
|
-
* Converts a node to
|
|
334
|
+
* Converts a node to a loaded branch with replacement children.
|
|
325
335
|
*
|
|
326
336
|
* The returned node retains the source node's ID and application value.
|
|
327
337
|
* Any previous child state is replaced.
|
|
328
338
|
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
339
|
+
* Children are copied in the supplied iterable's iteration order. That order
|
|
340
|
+
* becomes the replacement child order. Subsequent changes to the supplied
|
|
341
|
+
* collection do not affect the returned node.
|
|
331
342
|
*
|
|
332
343
|
* Direct siblings must have unique IDs. If two or more supplied children have
|
|
333
344
|
* the same ID, no replacement node is created and this method returns
|
|
@@ -338,8 +349,8 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
338
349
|
* @param node - The node to convert or update.
|
|
339
350
|
* @param children - Replacement loaded direct children. Their IDs must be
|
|
340
351
|
* unique among direct siblings.
|
|
341
|
-
* @returns
|
|
342
|
-
* when `children` contains duplicate sibling IDs.
|
|
352
|
+
* @returns A loaded branch with the source node's ID and value, or
|
|
353
|
+
* `undefined` when `children` contains duplicate sibling IDs.
|
|
343
354
|
*/
|
|
344
355
|
withLoadedChildren(node: TreeNode<Id, Value>, children: Iterable<TreeNode<Id, Value>>): TreeNode<Id, Value> | undefined;
|
|
345
356
|
/**
|
|
@@ -361,7 +372,7 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
361
372
|
* selected node is supplied as the existing node value and is not cloned.
|
|
362
373
|
*
|
|
363
374
|
* An empty path selects `root`. Resolution fails when a path refers to a
|
|
364
|
-
* missing child or attempts to pass through a leaf or
|
|
375
|
+
* missing child or attempts to pass through a leaf or an unloaded branch.
|
|
365
376
|
*
|
|
366
377
|
* @param root - The root node relative to which `path` is interpreted.
|
|
367
378
|
* @param path - A sibling-ID path relative to `root`.
|
|
@@ -373,40 +384,49 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
373
384
|
/**
|
|
374
385
|
* Immutably replaces the node addressed by a path relative to `root`.
|
|
375
386
|
*
|
|
376
|
-
* The
|
|
377
|
-
* `undefined` aborts the operation and causes this method to return
|
|
387
|
+
* The update callback runs only after the complete path has been resolved.
|
|
388
|
+
* Returning `undefined` aborts the operation and causes this method to return
|
|
378
389
|
* `undefined`.
|
|
379
390
|
*
|
|
380
391
|
* An empty path selects `root`. When a descendant is replaced, replacement
|
|
381
392
|
* nodes are created only along the path from `root` to that descendant. Nodes
|
|
382
393
|
* outside that path retain their original object identity.
|
|
383
394
|
*
|
|
384
|
-
* The
|
|
395
|
+
* The `update` may return a node with a different ID. If it does, the
|
|
385
396
|
* replacement is subsequently addressed by its new ID rather than the old
|
|
386
|
-
* path segment.
|
|
397
|
+
* path segment. The replacement retains the original node's position among
|
|
398
|
+
* siblings, and all other siblings retain their relative order.
|
|
387
399
|
*
|
|
388
400
|
* The update also fails when the replacement node's ID duplicates the ID of
|
|
389
|
-
* one of its new siblings. This can occur when `
|
|
401
|
+
* one of its new siblings. This can occur when `update` returns a node with
|
|
390
402
|
* a different ID.
|
|
391
403
|
*
|
|
392
404
|
* @param root - The root node relative to which `path` is interpreted.
|
|
393
405
|
* @param path - A sibling-ID path relative to `root`.
|
|
394
406
|
* @param update - Produces a replacement node, or `undefined` to abort.
|
|
395
407
|
* @returns A replacement root, or `undefined` when the path cannot be
|
|
396
|
-
* resolved, `
|
|
408
|
+
* resolved, `update` aborts, or the replacement would create duplicate
|
|
397
409
|
* sibling IDs.
|
|
398
410
|
*/
|
|
399
411
|
updateAtPath(root: TreeNode<Id, Value>, path: readonly Id[], update: (node: TreeNode<Id, Value>) => TreeNode<Id, Value> | undefined): TreeNode<Id, Value> | undefined;
|
|
400
412
|
/**
|
|
401
413
|
* Converts an opaque runtime node into a public structural model.
|
|
402
414
|
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
415
|
+
* Creates fresh model objects and child arrays recursively, preserving child
|
|
416
|
+
* order at every loaded branch. Application-owned values are retained by
|
|
417
|
+
* reference, not cloned; callers must treat those values as immutable.
|
|
418
|
+
*
|
|
419
|
+
* The model is suitable for JSON serialization when `Id` and `Value` are
|
|
420
|
+
* JSON-serializable.
|
|
405
421
|
*/
|
|
406
422
|
toModel(node: TreeNode<Id, Value>): TreeNodeModel<Id, Value>;
|
|
407
423
|
/**
|
|
408
424
|
* Constructs an opaque tree from a public structural model.
|
|
409
425
|
*
|
|
426
|
+
* Creates new runtime nodes and child collections, preserving child order
|
|
427
|
+
* at every loaded branch. Application-owned values are retained by reference,
|
|
428
|
+
* not cloned.
|
|
429
|
+
*
|
|
410
430
|
* Returns `undefined` if any loaded child collection contains duplicate
|
|
411
431
|
* sibling IDs.
|
|
412
432
|
*
|
|
@@ -416,4 +436,4 @@ export type TreeNodeApi<Id extends SerializableKey, Value> = Readonly<{
|
|
|
416
436
|
fromModel(model: TreeNodeModel<Id, Value>): TreeNode<Id, Value> | undefined;
|
|
417
437
|
}>;
|
|
418
438
|
export {};
|
|
419
|
-
//# sourceMappingURL=tree
|
|
439
|
+
//# sourceMappingURL=tree.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.types.d.ts","sourceRoot":"","sources":["../../../src/state-and-nav/tree/tree.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|