dirvi-lib 0.8.0 → 0.8.2
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 +74 -0
- package/dist/app/app-api.d.ts.map +1 -0
- package/dist/app/app-api.js +11 -0
- package/dist/app/effect-to-action.d.ts +6 -0
- package/dist/app/effect-to-action.d.ts.map +1 -0
- package/dist/app/effect-to-action.js +74 -0
- package/dist/app/index.d.ts +6 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +5 -0
- package/dist/app/reducer-action.d.ts +22 -0
- package/dist/app/reducer-action.d.ts.map +1 -0
- package/dist/app/reducer.d.ts +5 -0
- package/dist/app/reducer.d.ts.map +1 -0
- package/dist/app/reducer.js +79 -0
- package/dist/app/view-api.d.ts +11 -0
- package/dist/app/view-api.d.ts.map +1 -0
- package/dist/index.d.ts +4 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -7
- package/dist/input/application/index.d.ts.map +1 -0
- package/dist/input/application/intent-to-effect-no-state.test.d.ts.map +1 -0
- package/dist/input/application/intent-to-effect.d.ts +14 -0
- package/dist/input/application/intent-to-effect.d.ts.map +1 -0
- package/dist/{tree-surfer-ui → input}/application/intent-to-effect.js +36 -17
- package/dist/input/application/parse-command.d.ts +14 -0
- package/dist/input/application/parse-command.d.ts.map +1 -0
- package/dist/{tree-surfer-ui → input}/application/parse-command.js +13 -1
- package/dist/input/application/user-input-to-intent.d.ts.map +1 -0
- package/dist/input/application/user-input-to-intent.test.d.ts.map +1 -0
- package/dist/input/domain/effect.d.ts +55 -0
- package/dist/input/domain/effect.d.ts.map +1 -0
- package/dist/input/domain/index.d.ts.map +1 -0
- package/dist/input/domain/input-mode-state.d.ts.map +1 -0
- package/dist/input/domain/intent.d.ts.map +1 -0
- package/dist/input/domain/user-input.d.ts.map +1 -0
- package/dist/trees/cursor.d.ts.map +1 -0
- package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts +10 -0
- package/dist/trees/fold-node/deprecated/fold-node.impl.d.ts.map +1 -0
- package/dist/trees/fold-node/deprecated/fold-node.impl.js +248 -0
- package/dist/trees/fold-node/deprecated/fold-node.test.d.ts +2 -0
- package/dist/trees/fold-node/deprecated/fold-node.test.d.ts.map +1 -0
- package/dist/trees/fold-node/deprecated/fold-node.test.js +130 -0
- package/dist/{tree-surfer/fold-node → trees/fold-node/deprecated}/fold-node.types.d.ts +15 -76
- package/dist/trees/fold-node/deprecated/fold-node.types.d.ts.map +1 -0
- package/dist/trees/fold-node/fold-node.impl.d.ts +16 -0
- package/dist/trees/fold-node/fold-node.impl.d.ts.map +1 -0
- package/dist/trees/fold-node/fold-node.impl.js +451 -0
- package/dist/trees/fold-node/fold-node.model.d.ts +120 -0
- package/dist/trees/fold-node/fold-node.model.d.ts.map +1 -0
- package/dist/trees/fold-node/fold-node.model.js +80 -0
- package/dist/trees/fold-node/fold-node.types.d.ts +206 -0
- package/dist/trees/fold-node/fold-node.types.d.ts.map +1 -0
- package/dist/trees/fold-node/index.d.ts +4 -0
- package/dist/trees/fold-node/index.d.ts.map +1 -0
- package/dist/trees/fold-node/index.js +3 -0
- package/dist/trees/index.d.ts +9 -0
- package/dist/trees/index.d.ts.map +1 -0
- package/dist/trees/index.js +8 -0
- package/dist/trees/nav-node/nav-node.impl.d.ts +29 -0
- package/dist/trees/nav-node/nav-node.impl.d.ts.map +1 -0
- package/dist/{tree-surfer → trees}/nav-node/nav-node.impl.js +89 -42
- package/dist/trees/nav-node/nav-node.types.d.ts +103 -0
- package/dist/trees/nav-node/nav-node.types.d.ts.map +1 -0
- package/dist/trees/state/state.impl.d.ts +8 -0
- package/dist/trees/state/state.impl.d.ts.map +1 -0
- package/dist/trees/state/state.impl.js +128 -0
- package/dist/trees/state/state.schemas.d.ts +25 -0
- package/dist/trees/state/state.schemas.d.ts.map +1 -0
- package/dist/trees/state/state.schemas.js +17 -0
- package/dist/trees/state/state.types.d.ts +36 -0
- package/dist/trees/state/state.types.d.ts.map +1 -0
- package/dist/trees/state/state.types.js +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.arb.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.js +1 -1
- package/dist/trees/tree-node/deprecated/tree-node.impl.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.schemas.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.test.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.types.d.ts.map +1 -0
- package/dist/trees/tree-node/deprecated/tree-node.types.js +1 -0
- package/dist/trees/tree-node/index.d.ts +4 -0
- package/dist/trees/tree-node/index.d.ts.map +1 -0
- package/dist/trees/tree-node/index.js +3 -0
- package/dist/trees/tree-node/tree-node.arb.d.ts +104 -0
- package/dist/trees/tree-node/tree-node.arb.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.arb.js +191 -0
- package/dist/trees/tree-node/tree-node.impl.d.ts +15 -0
- package/dist/trees/tree-node/tree-node.impl.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.impl.js +286 -0
- package/dist/trees/tree-node/tree-node.model.d.ts +78 -0
- package/dist/trees/tree-node/tree-node.model.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.model.js +37 -0
- package/dist/trees/tree-node/tree-node.types.d.ts +419 -0
- package/dist/trees/tree-node/tree-node.types.d.ts.map +1 -0
- package/dist/trees/tree-node/tree-node.types.js +1 -0
- package/package.json +1 -1
- package/dist/tree-surfer/cursor.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +0 -2
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +0 -4
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.js +0 -149
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +0 -5
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/fold-node.schema.js +0 -9
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +0 -1
- package/dist/tree-surfer/fold-node/tree-node.test.js +0 -90
- package/dist/tree-surfer/index.d.ts +0 -13
- package/dist/tree-surfer/index.d.ts.map +0 -1
- package/dist/tree-surfer/index.js +0 -12
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +0 -6
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +0 -1
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts +0 -70
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +0 -1
- package/dist/tree-surfer/state/state.impl.d.ts +0 -7
- package/dist/tree-surfer/state/state.impl.d.ts.map +0 -1
- package/dist/tree-surfer/state/state.impl.js +0 -89
- package/dist/tree-surfer/state/state.schemas.d.ts +0 -9
- package/dist/tree-surfer/state/state.schemas.d.ts.map +0 -1
- package/dist/tree-surfer/state/state.schemas.js +0 -8
- package/dist/tree-surfer/state/state.types.d.ts +0 -21
- package/dist/tree-surfer/state/state.types.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.test.d.ts +0 -2
- package/dist/tree-surfer/tree-node/tree-node.test.d.ts.map +0 -1
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/index.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/intent-to-effect-no-state.test.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +0 -10
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/parse-command.d.ts +0 -4
- package/dist/tree-surfer-ui/application/parse-command.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +0 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.test.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/effect.d.ts +0 -43
- package/dist/tree-surfer-ui/domain/effect.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/index.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/intent.d.ts.map +0 -1
- package/dist/tree-surfer-ui/domain/user-input.d.ts.map +0 -1
- /package/dist/{tree-surfer-ui/domain/effect.js → app/reducer-action.js} +0 -0
- /package/dist/{tree-surfer-ui/domain/input-mode-state.js → app/view-api.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/application/index.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/index.js +0 -0
- /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/intent-to-effect-no-state.test.js +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.js +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/application/user-input-to-intent.test.js +0 -0
- /package/dist/{tree-surfer-ui/domain/intent.js → input/domain/effect.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/index.d.ts +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/index.js +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/input-mode-state.d.ts +0 -0
- /package/dist/{tree-surfer-ui/domain/user-input.js → input/domain/input-mode-state.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/intent.d.ts +0 -0
- /package/dist/{tree-surfer/fold-node/fold-node.arb.js → input/domain/intent.js} +0 -0
- /package/dist/{tree-surfer-ui → input}/domain/user-input.d.ts +0 -0
- /package/dist/{tree-surfer/fold-node/fold-node.types.js → input/domain/user-input.js} +0 -0
- /package/dist/{tree-surfer → trees}/cursor.d.ts +0 -0
- /package/dist/{tree-surfer → trees}/cursor.js +0 -0
- /package/dist/{tree-surfer/nav-node/nav-node.types.js → trees/fold-node/deprecated/fold-node.types.js} +0 -0
- /package/dist/{tree-surfer/state/state.types.js → trees/fold-node/fold-node.types.js} +0 -0
- /package/dist/{tree-surfer/tree-node/tree-node.types.js → trees/nav-node/nav-node.types.js} +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.debug.test.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.arb.js +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.impl.js +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.schemas.js +0 -0
- /package/dist/{tree-surfer/fold-node → trees/tree-node/deprecated}/tree-node.test.d.ts +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.test.js +0 -0
- /package/dist/{tree-surfer/tree-node → trees/tree-node/deprecated}/tree-node.types.d.ts +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates semantic operations for a fold-state tree.
|
|
3
|
+
*
|
|
4
|
+
* Structural children are owned by `FoldNodeApi`; folded children are
|
|
5
|
+
* application-owned FoldNode values stored in `FoldNodeValue`.
|
|
6
|
+
*/
|
|
7
|
+
export function createFoldNodeService(foldNodeApi) {
|
|
8
|
+
function createEmptyNode(id) {
|
|
9
|
+
const node = foldNodeApi.createLoadedBranch(id, { foldedChildren: [] }, []);
|
|
10
|
+
/*
|
|
11
|
+
* An empty child collection cannot contain duplicate sibling IDs, so this
|
|
12
|
+
* would indicate a broken TreeNodeApi implementation or changed contract.
|
|
13
|
+
*/
|
|
14
|
+
if (node === undefined) {
|
|
15
|
+
throw new Error('Could not create an empty fold node');
|
|
16
|
+
}
|
|
17
|
+
return node;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Returns an array snapshot of loaded structural children.
|
|
21
|
+
*
|
|
22
|
+
* Fold nodes are intended always to be open branches. `undefined` therefore
|
|
23
|
+
* signals an invalid FoldNode supplied to this service, for example one made
|
|
24
|
+
* with `asLeaf` or `asClosedBranch` through the lower-level tree API.
|
|
25
|
+
*/
|
|
26
|
+
function getStructuralChildren(node) {
|
|
27
|
+
const children = foldNodeApi.getLoadedChildren(node);
|
|
28
|
+
return children === undefined ? undefined : [...children];
|
|
29
|
+
}
|
|
30
|
+
function getFoldedChildren(node) {
|
|
31
|
+
return foldNodeApi.value(node).foldedChildren;
|
|
32
|
+
}
|
|
33
|
+
function getFoldedChildById(node, id) {
|
|
34
|
+
return getFoldedChildren(node).find((child) => foldNodeApi.id(child) === id);
|
|
35
|
+
}
|
|
36
|
+
function hasFoldedChildWithId(node, id) {
|
|
37
|
+
return getFoldedChildById(node, id) !== undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Replaces only fold-domain data, retaining the node's ID and structural
|
|
41
|
+
* child state.
|
|
42
|
+
*/
|
|
43
|
+
function withFoldedChildren(node, foldedChildren) {
|
|
44
|
+
return foldNodeApi.withValue(node, {
|
|
45
|
+
...foldNodeApi.value(node),
|
|
46
|
+
foldedChildren,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Replaces structural children while requiring that the source node is
|
|
51
|
+
* already an open branch.
|
|
52
|
+
*
|
|
53
|
+
* `withChildren` could convert any tree node into an open branch. That is
|
|
54
|
+
* useful in the generic API, but FoldNodeService wants to preserve its
|
|
55
|
+
* invariant that every FoldNode is already an open branch.
|
|
56
|
+
*/
|
|
57
|
+
function withStructuralChildren(node, children) {
|
|
58
|
+
if (foldNodeApi.getLoadedChildren(node) === undefined) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
return foldNodeApi.withLoadedChildren(node, children);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Returns whether a fold-state node carries state below itself.
|
|
65
|
+
*
|
|
66
|
+
* A folded node has nested state when it has either:
|
|
67
|
+
*
|
|
68
|
+
* - structural child fold nodes; or
|
|
69
|
+
* - folded direct children.
|
|
70
|
+
*/
|
|
71
|
+
function hasNestedFoldState(node) {
|
|
72
|
+
const structuralChildren = getStructuralChildren(node);
|
|
73
|
+
if (structuralChildren === undefined) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
return structuralChildren.length > 0 || getFoldedChildren(node).length > 0;
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
createEmptyNode,
|
|
80
|
+
getIfEntryFoldedAtPath(rootNode, path, entryId) {
|
|
81
|
+
return (foldNodeApi.selectAtPath(rootNode, path, (node) => hasFoldedChildWithId(node, entryId)) ?? false);
|
|
82
|
+
},
|
|
83
|
+
foldedEntriesAtPath(rootNode, path) {
|
|
84
|
+
return foldNodeApi.selectAtPath(rootNode, path, (node) => getFoldedChildren(node).map(foldNodeApi.id));
|
|
85
|
+
},
|
|
86
|
+
addFoldedEntryAtPath(rootNode, path, entryId) {
|
|
87
|
+
const rootWithPath = ensurePath(rootNode, path);
|
|
88
|
+
if (rootWithPath === undefined) {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
return foldNodeApi.updateAtPath(rootWithPath, path, (node) => addFoldedEntry(node, entryId));
|
|
92
|
+
},
|
|
93
|
+
removeFoldedEntryAtPath(rootNode, path, entryId) {
|
|
94
|
+
return foldNodeApi.updateAtPath(rootNode, path, (node) => removeFoldedEntry(node, entryId));
|
|
95
|
+
},
|
|
96
|
+
clearFoldedEntriesAtPath(rootNode, path) {
|
|
97
|
+
return foldNodeApi.updateAtPath(rootNode, path, clearFoldedEntries);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Folds a direct child at one node.
|
|
102
|
+
*
|
|
103
|
+
* If the child already exists as a structural child, it moves to
|
|
104
|
+
* `foldedChildren`, preserving any fold state beneath it. If it does not
|
|
105
|
+
* exist in either collection, a new empty fold node is created.
|
|
106
|
+
*/
|
|
107
|
+
function addFoldedEntry(node, entryId) {
|
|
108
|
+
if (hasFoldedChildWithId(node, entryId)) {
|
|
109
|
+
return node;
|
|
110
|
+
}
|
|
111
|
+
const children = getStructuralChildren(node);
|
|
112
|
+
if (children === undefined) {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
const childIndex = children.findIndex((child) => foldNodeApi.id(child) === entryId);
|
|
116
|
+
const foldedChild = childIndex === -1 ? createEmptyNode(entryId) : children[childIndex];
|
|
117
|
+
if (foldedChild === undefined) {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const remainingChildren = childIndex === -1
|
|
121
|
+
? children
|
|
122
|
+
: [...children.slice(0, childIndex), ...children.slice(childIndex + 1)];
|
|
123
|
+
const nodeWithoutStructuralChild = withStructuralChildren(node, remainingChildren);
|
|
124
|
+
if (nodeWithoutStructuralChild === undefined) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
return withFoldedChildren(nodeWithoutStructuralChild, [
|
|
128
|
+
...getFoldedChildren(nodeWithoutStructuralChild),
|
|
129
|
+
foldedChild,
|
|
130
|
+
]);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Unfolds a direct child at one node.
|
|
134
|
+
*
|
|
135
|
+
* A folded child that has nested fold state is moved into structural
|
|
136
|
+
* children. A folded child without nested state is omitted entirely.
|
|
137
|
+
*/
|
|
138
|
+
function removeFoldedEntry(node, entryId) {
|
|
139
|
+
const foldedChildren = getFoldedChildren(node);
|
|
140
|
+
const foldedChildIndex = foldedChildren.findIndex((child) => foldNodeApi.id(child) === entryId);
|
|
141
|
+
if (foldedChildIndex === -1) {
|
|
142
|
+
return node;
|
|
143
|
+
}
|
|
144
|
+
const foldedChild = foldedChildren[foldedChildIndex];
|
|
145
|
+
if (foldedChild === undefined) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const hasNestedState = hasNestedFoldState(foldedChild);
|
|
149
|
+
if (hasNestedState === undefined) {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
const remainingFoldedChildren = [
|
|
153
|
+
...foldedChildren.slice(0, foldedChildIndex),
|
|
154
|
+
...foldedChildren.slice(foldedChildIndex + 1),
|
|
155
|
+
];
|
|
156
|
+
const nodeWithoutFoldedChild = withFoldedChildren(node, remainingFoldedChildren);
|
|
157
|
+
if (!hasNestedState) {
|
|
158
|
+
return nodeWithoutFoldedChild;
|
|
159
|
+
}
|
|
160
|
+
const children = getStructuralChildren(nodeWithoutFoldedChild);
|
|
161
|
+
if (children === undefined) {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
return withStructuralChildren(nodeWithoutFoldedChild, [
|
|
165
|
+
...children,
|
|
166
|
+
foldedChild,
|
|
167
|
+
]);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Unfolds all directly folded children at one node.
|
|
171
|
+
*
|
|
172
|
+
* Folded children that contain nested state become structural children;
|
|
173
|
+
* empty folded children are omitted.
|
|
174
|
+
*/
|
|
175
|
+
function clearFoldedEntries(node) {
|
|
176
|
+
const foldedChildren = getFoldedChildren(node);
|
|
177
|
+
if (foldedChildren.length === 0) {
|
|
178
|
+
return node;
|
|
179
|
+
}
|
|
180
|
+
const children = getStructuralChildren(node);
|
|
181
|
+
if (children === undefined) {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const retainedFoldedChildren = [];
|
|
185
|
+
for (const foldedChild of foldedChildren) {
|
|
186
|
+
const nestedState = hasNestedFoldState(foldedChild);
|
|
187
|
+
if (nestedState === undefined) {
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
190
|
+
if (nestedState) {
|
|
191
|
+
retainedFoldedChildren.push(foldedChild);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const nodeWithoutFoldedChildren = withFoldedChildren(node, []);
|
|
195
|
+
return withStructuralChildren(nodeWithoutFoldedChildren, [
|
|
196
|
+
...children,
|
|
197
|
+
...retainedFoldedChildren,
|
|
198
|
+
]);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Ensures the path exists as structural fold nodes.
|
|
202
|
+
*
|
|
203
|
+
* A path may not pass through a folded child: folded children represent
|
|
204
|
+
* entries whose contents are collapsed, so they cannot simultaneously be
|
|
205
|
+
* part of the visible structural fold-state path.
|
|
206
|
+
*/
|
|
207
|
+
function ensurePath(rootNode, path) {
|
|
208
|
+
return ensurePathAtIndex(rootNode, path, 0);
|
|
209
|
+
}
|
|
210
|
+
function ensurePathAtIndex(node, path, pathIndex) {
|
|
211
|
+
if (pathIndex === path.length) {
|
|
212
|
+
return node;
|
|
213
|
+
}
|
|
214
|
+
const childId = path[pathIndex];
|
|
215
|
+
if (childId === undefined) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
/*
|
|
219
|
+
* A folded child is intentionally absent from the structural child tree.
|
|
220
|
+
* Do not create a duplicate FoldNode with the same ID in `children`.
|
|
221
|
+
*/
|
|
222
|
+
if (hasFoldedChildWithId(node, childId)) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
const children = getStructuralChildren(node);
|
|
226
|
+
if (children === undefined) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
const childIndex = children.findIndex((child) => foldNodeApi.id(child) === childId);
|
|
230
|
+
const child = childIndex === -1 ? createEmptyNode(childId) : children[childIndex];
|
|
231
|
+
if (child === undefined) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
const updatedChild = ensurePathAtIndex(child, path, pathIndex + 1);
|
|
235
|
+
if (updatedChild === undefined) {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
if (childIndex === -1) {
|
|
239
|
+
return withStructuralChildren(node, [...children, updatedChild]);
|
|
240
|
+
}
|
|
241
|
+
if (updatedChild === child) {
|
|
242
|
+
return node;
|
|
243
|
+
}
|
|
244
|
+
const updatedChildren = [...children];
|
|
245
|
+
updatedChildren[childIndex] = updatedChild;
|
|
246
|
+
return withStructuralChildren(node, updatedChildren);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold-node.test.d.ts","sourceRoot":"","sources":["../../../../src/trees/fold-node/deprecated/fold-node.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { createTreeNodeApi } from '../../tree-node/tree-node.impl.js';
|
|
3
|
+
import { createFoldNodeService } from './fold-node.impl.js';
|
|
4
|
+
const foldNodeApi = createTreeNodeApi();
|
|
5
|
+
const service = createFoldNodeService(foldNodeApi);
|
|
6
|
+
function createFoldNode(id, options = {}) {
|
|
7
|
+
const node = foldNodeApi.createLoadedBranch(id, {
|
|
8
|
+
foldedChildren: options.foldedChildren ?? [],
|
|
9
|
+
}, options.children ?? []);
|
|
10
|
+
if (node === undefined) {
|
|
11
|
+
throw new Error('Test fixture has duplicate structural child IDs');
|
|
12
|
+
}
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
function getStructuralChildren(node) {
|
|
16
|
+
const children = foldNodeApi.getLoadedChildren(node);
|
|
17
|
+
if (children === undefined) {
|
|
18
|
+
throw new Error('Expected test FoldNode to be an open branch');
|
|
19
|
+
}
|
|
20
|
+
return [...children];
|
|
21
|
+
}
|
|
22
|
+
function getFoldedChildren(node) {
|
|
23
|
+
return foldNodeApi.value(node).foldedChildren;
|
|
24
|
+
}
|
|
25
|
+
function expectFoldNode(node, expected) {
|
|
26
|
+
expect(foldNodeApi.id(node)).toBe(expected.id);
|
|
27
|
+
expect(getStructuralChildren(node).map(foldNodeApi.id)).toStrictEqual(expected.childIds);
|
|
28
|
+
expect(getFoldedChildren(node).map(foldNodeApi.id)).toStrictEqual(expected.foldedChildIds);
|
|
29
|
+
}
|
|
30
|
+
describe('createFoldNodeService', () => {
|
|
31
|
+
it('UT: adds a new entry to folded children', () => {
|
|
32
|
+
const root = service.createEmptyNode(0);
|
|
33
|
+
const updated = service.addFoldedEntryAtPath(root, [], 0);
|
|
34
|
+
expect(updated).toBeDefined();
|
|
35
|
+
if (updated === undefined) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
expectFoldNode(updated, {
|
|
39
|
+
id: 0,
|
|
40
|
+
childIds: [],
|
|
41
|
+
foldedChildIds: [0],
|
|
42
|
+
});
|
|
43
|
+
const foldedChild = getFoldedChildren(updated)[0];
|
|
44
|
+
expect(foldedChild).toBeDefined();
|
|
45
|
+
if (foldedChild === undefined) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
expectFoldNode(foldedChild, {
|
|
49
|
+
id: 0,
|
|
50
|
+
childIds: [],
|
|
51
|
+
foldedChildIds: [],
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it('UT: moves an existing structural entry to folded children', () => {
|
|
55
|
+
const child = createFoldNode(1);
|
|
56
|
+
const root = createFoldNode(0, {
|
|
57
|
+
children: [child],
|
|
58
|
+
});
|
|
59
|
+
const updated = service.addFoldedEntryAtPath(root, [], 1);
|
|
60
|
+
expect(updated).toBeDefined();
|
|
61
|
+
if (updated === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
expectFoldNode(updated, {
|
|
65
|
+
id: 0,
|
|
66
|
+
childIds: [],
|
|
67
|
+
foldedChildIds: [1],
|
|
68
|
+
});
|
|
69
|
+
/*
|
|
70
|
+
* Moving an existing child preserves the exact node value, including any
|
|
71
|
+
* fold state nested below it.
|
|
72
|
+
*/
|
|
73
|
+
expect(getFoldedChildren(updated)[0]).toBe(child);
|
|
74
|
+
/*
|
|
75
|
+
* The original immutable tree remains unchanged.
|
|
76
|
+
*/
|
|
77
|
+
expectFoldNode(root, {
|
|
78
|
+
id: 0,
|
|
79
|
+
childIds: [1],
|
|
80
|
+
foldedChildIds: [],
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
it('UT: preserves nested fold state when moving an entry', () => {
|
|
84
|
+
const nestedChild = createFoldNode(2);
|
|
85
|
+
const child = createFoldNode(1, {
|
|
86
|
+
foldedChildren: [nestedChild],
|
|
87
|
+
});
|
|
88
|
+
const root = createFoldNode(0, {
|
|
89
|
+
children: [child],
|
|
90
|
+
});
|
|
91
|
+
const updated = service.addFoldedEntryAtPath(root, [], 1);
|
|
92
|
+
expect(updated).toBeDefined();
|
|
93
|
+
if (updated === undefined) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
expectFoldNode(updated, {
|
|
97
|
+
id: 0,
|
|
98
|
+
childIds: [],
|
|
99
|
+
foldedChildIds: [1],
|
|
100
|
+
});
|
|
101
|
+
const movedChild = getFoldedChildren(updated)[0];
|
|
102
|
+
expect(movedChild).toBe(child);
|
|
103
|
+
if (movedChild === undefined) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
expectFoldNode(movedChild, {
|
|
107
|
+
id: 1,
|
|
108
|
+
childIds: [],
|
|
109
|
+
foldedChildIds: [2],
|
|
110
|
+
});
|
|
111
|
+
expect(getFoldedChildren(movedChild)[0]).toBe(nestedChild);
|
|
112
|
+
});
|
|
113
|
+
it('UT: rejects a path through a folded entry', () => {
|
|
114
|
+
const foldedChild = createFoldNode(1);
|
|
115
|
+
const root = createFoldNode(0, {
|
|
116
|
+
foldedChildren: [foldedChild],
|
|
117
|
+
});
|
|
118
|
+
const updated = service.addFoldedEntryAtPath(root, [1], 2);
|
|
119
|
+
expect(updated).toBeUndefined();
|
|
120
|
+
/*
|
|
121
|
+
* The failed immutable operation leaves the original root untouched.
|
|
122
|
+
*/
|
|
123
|
+
expectFoldNode(root, {
|
|
124
|
+
id: 0,
|
|
125
|
+
childIds: [],
|
|
126
|
+
foldedChildIds: [1],
|
|
127
|
+
});
|
|
128
|
+
expect(getFoldedChildren(root)[0]).toBe(foldedChild);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
@@ -1,84 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { TreeNode, TreeNodeApi, SerializableKey } from '../../tree-node/index.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Application-specific state stored in each fold-tree node.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* A fold-state tree is a sparse projection of a buffer tree. Apart from its
|
|
8
|
-
* root, it stores only nodes that are folded or that have fold information
|
|
9
|
-
* somewhere below them. It does not contain buffer entries or buffer-specific
|
|
10
|
-
* properties.
|
|
11
|
-
*
|
|
12
|
-
* The `id` identifies the corresponding buffer entry among its siblings.
|
|
13
|
-
*
|
|
14
|
-
* `children` contains fold-state nodes for direct buffer entries that are not
|
|
15
|
-
* folded at this node but have fold information below them.
|
|
16
|
-
*
|
|
17
|
-
* `foldedChildren` contains fold-state nodes for direct buffer entries whose contents
|
|
18
|
-
* are folded at this node. Each folded entry is represented by a complete
|
|
19
|
-
* `FoldNode`, allowing fold state within a folded entry to be preserved
|
|
20
|
-
* recursively.
|
|
21
|
-
*
|
|
22
|
-
* A direct buffer entry with no fold state at or below it is omitted from both
|
|
23
|
-
* collections. Consequently, the union of `children` and `foldedChildren` does not
|
|
24
|
-
* need to contain every direct buffer child.
|
|
25
|
-
*
|
|
26
|
-
* The following invariant holds for every fold-state node:
|
|
27
|
-
*
|
|
28
|
-
* - no ID occurs more than once in `children`;
|
|
29
|
-
* - no ID occurs more than once in `foldedChildren`;
|
|
30
|
-
* - no ID occurs in both collections;
|
|
31
|
-
* - every ID in either collection identifies a direct child in the
|
|
32
|
-
* corresponding buffer-tree node.
|
|
33
|
-
*
|
|
34
|
-
* Every fold node is an open branch from the perspective of `TreeNodeApi`.
|
|
35
|
-
* Missing paths may nevertheless be created lazily by `FoldNodeService`.
|
|
36
|
-
*
|
|
37
|
-
* @typeParam Id - The type of IDs in the actual tree nodes.
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
*
|
|
41
|
-
* ```ts
|
|
42
|
-
* type FileId = string;
|
|
43
|
-
* type FileFoldNode = FoldNode<FileId>;
|
|
44
|
-
*
|
|
45
|
-
* const root: FileFoldNode = {
|
|
46
|
-
* id: '/',
|
|
47
|
-
* children: [],
|
|
48
|
-
* foldedChildren: [],
|
|
49
|
-
* };
|
|
50
|
-
* ```
|
|
5
|
+
* Structural children are owned by TreeNode. `foldedChildren` are semantic
|
|
6
|
+
* fold-state children: direct buffer entries whose contents are folded.
|
|
51
7
|
*/
|
|
52
|
-
export type
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
foldedChildren: FoldNode<Id>[];
|
|
56
|
-
};
|
|
8
|
+
export type FoldNodeValue<Id extends SerializableKey> = Readonly<{
|
|
9
|
+
foldedChildren: readonly FoldNode<Id>[];
|
|
10
|
+
}>;
|
|
57
11
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* @remarks
|
|
61
|
-
*
|
|
62
|
-
* This API is `TreeNodeApi` specialized for `FoldNode`. It provides structural
|
|
63
|
-
* tree operations such as node lookup and immutable path updates.
|
|
64
|
-
*
|
|
65
|
-
* Paths are relative to the supplied root node:
|
|
66
|
-
*
|
|
67
|
-
* - `[]` identifies the root;
|
|
68
|
-
* - `['child']` identifies a direct child of the root;
|
|
69
|
-
* - `['child', 'grandchild']` identifies a descendant.
|
|
12
|
+
* A fold-state node.
|
|
70
13
|
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
* should be automatically created.
|
|
74
|
-
*
|
|
75
|
-
* The API does not interpret the `foldedChildren` collection. It only navigates and
|
|
76
|
-
* updates the structural `children` tree. The semantic operations for adding
|
|
77
|
-
* and removing foldedChildren belong to `FoldNodeService`.
|
|
78
|
-
*
|
|
79
|
-
* @typeParam Id - The type of IDs in the actual tree nodes.
|
|
14
|
+
* The structural child tree is represented by the opaque TreeNode. The value
|
|
15
|
+
* holds only fold-specific state.
|
|
80
16
|
*/
|
|
81
|
-
export type
|
|
17
|
+
export type FoldNode<Id extends SerializableKey> = TreeNode<Id, FoldNodeValue<Id>>;
|
|
18
|
+
/**
|
|
19
|
+
* Generic structural operations specialized to a fold-state tree.
|
|
20
|
+
*/
|
|
21
|
+
export type FoldNodeApi<Id extends SerializableKey> = TreeNodeApi<Id, FoldNodeValue<Id>>;
|
|
82
22
|
/**
|
|
83
23
|
* Semantic operations for managing fold state.
|
|
84
24
|
*
|
|
@@ -188,6 +128,5 @@ export type FoldNodeService<Id extends SerializableKey> = {
|
|
|
188
128
|
* `undefined` when the path does not exist.
|
|
189
129
|
*/
|
|
190
130
|
clearFoldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): FoldNode<Id> | undefined;
|
|
191
|
-
clearFoldedEntriesAtPath(rootNode: FoldNode<Id>, path: readonly Id[]): FoldNode<Id> | undefined;
|
|
192
131
|
};
|
|
193
132
|
//# sourceMappingURL=fold-node.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold-node.types.d.ts","sourceRoot":"","sources":["../../../../src/trees/fold-node/deprecated/fold-node.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEvF;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CAAC;IAC/D,cAAc,EAAE,SAAS,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;CACzC,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,IAAI,QAAQ,CACzD,EAAE,EACF,aAAa,CAAC,EAAE,CAAC,CAClB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,EAAE,SAAS,eAAe,IAAI,WAAW,CAC/D,EAAE,EACF,aAAa,CAAC,EAAE,CAAC,CAClB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,eAAe,CAAC,EAAE,SAAS,eAAe,IAAI;IACxD;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,EAAE,EAAE,GAAG,QAAQ,CAAC,EAAE,CAAC,CAAC;IAEtC;;;;;;;;;;;;;;OAcG;IACH,sBAAsB,CACpB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,OAAO,CAAC;IAEX;;;;OAIG;IACH,mBAAmB,CACjB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,EAAE,EAAE,GAAG,SAAS,CAAC;IAEpB;;;;;;;;;;;;;;;;;OAiBG;IACH,oBAAoB,CAClB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;;;;;;;;;;;;;;;;OAiBG;IACH,uBAAuB,CACrB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,OAAO,EAAE,EAAE,GACV,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;IAE5B;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,CACtB,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,EACtB,IAAI,EAAE,SAAS,EAAE,EAAE,GAClB,QAAQ,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC;CAC7B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SerializableKey, TreeNodeApi } from '../tree-node/index.js';
|
|
2
|
+
import type { FoldsApi } from './fold-node.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* IDs of direct source-tree entries hidden at one source-tree branch.
|
|
5
|
+
*/
|
|
6
|
+
type HiddenEntryIds<Id extends SerializableKey> = readonly Id[];
|
|
7
|
+
/**
|
|
8
|
+
* Creates operations for an immutable collection of independently active fold
|
|
9
|
+
* trees.
|
|
10
|
+
*
|
|
11
|
+
* Each fold tree preserves its own hidden-entry definition. An entry is hidden
|
|
12
|
+
* by the aggregate when at least one active fold tree hides it.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createFoldsApi<Id extends SerializableKey>(foldTreeApi: TreeNodeApi<Id, HiddenEntryIds<Id>>): FoldsApi<Id>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=fold-node.impl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fold-node.impl.d.ts","sourceRoot":"","sources":["../../../src/trees/fold-node/fold-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EAEf,WAAW,EACZ,MAAM,uBAAuB,CAAC;AAM/B,OAAO,KAAK,EAIV,QAAQ,EACT,MAAM,sBAAsB,CAAC;AAE9B;;GAEG;AACH,KAAK,cAAc,CAAC,EAAE,SAAS,eAAe,IAAI,SAAS,EAAE,EAAE,CAAC;AA0ChE;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,eAAe,EACvD,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,cAAc,CAAC,EAAE,CAAC,CAAC,GAC/C,QAAQ,CAAC,EAAE,CAAC,CAmmBd"}
|