dirvi-lib 0.7.0 → 0.8.0

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.
Files changed (72) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/tree-surfer/cursor.d.ts +52 -26
  5. package/dist/tree-surfer/cursor.d.ts.map +1 -1
  6. package/dist/tree-surfer/cursor.js +19 -44
  7. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
  8. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
  9. package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
  10. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
  11. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
  12. package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
  13. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +3 -5
  14. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
  15. package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
  16. package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
  17. package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
  18. package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
  19. package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
  20. package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
  21. package/dist/tree-surfer/index.d.ts +3 -1
  22. package/dist/tree-surfer/index.d.ts.map +1 -1
  23. package/dist/tree-surfer/index.js +3 -1
  24. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
  25. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
  26. package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
  27. package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
  28. package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
  29. package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
  30. package/dist/tree-surfer/state/state.impl.d.ts +7 -0
  31. package/dist/tree-surfer/state/state.impl.d.ts.map +1 -0
  32. package/dist/tree-surfer/state/state.impl.js +89 -0
  33. package/dist/tree-surfer/state/state.schemas.d.ts +9 -0
  34. package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -0
  35. package/dist/tree-surfer/state/state.schemas.js +8 -0
  36. package/dist/tree-surfer/state/state.types.d.ts +21 -0
  37. package/dist/tree-surfer/state/state.types.d.ts.map +1 -0
  38. package/dist/tree-surfer/state/state.types.js +1 -0
  39. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +82 -17
  40. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
  41. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
  42. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
  43. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.js +34 -0
  44. package/dist/tree-surfer/tree-node/tree-node.arb.js +164 -56
  45. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
  46. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
  47. package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
  48. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
  49. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
  50. package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
  51. package/dist/tree-surfer/tree-node/tree-node.test.js +144 -76
  52. package/dist/tree-surfer/tree-node/tree-node.types.d.ts +89 -107
  53. package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
  54. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
  55. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
  56. package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -6
  57. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
  58. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
  59. package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
  60. package/dist/tree-surfer-ui/domain/effect.d.ts +9 -8
  61. package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
  62. package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
  63. package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
  64. package/dist/tree-surfer-ui/domain/index.js +1 -1
  65. package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
  66. package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
  67. package/package.json +3 -2
  68. package/dist/tree-surfer/state.d.ts +0 -25
  69. package/dist/tree-surfer/state.d.ts.map +0 -1
  70. package/dist/tree-surfer/state.js +0 -118
  71. package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
  72. /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './tree-surfer-ui/application/user-input-to-intent.js';
2
2
  export * from './tree-surfer-ui/application/intent-to-effect.js';
3
3
  export * from './tree-surfer-ui/domain/effect.js';
4
- export * from './tree-surfer-ui/domain/input-state.js';
4
+ export * from './tree-surfer-ui/domain/input-mode-state.js';
5
5
  export * from './tree-surfer-ui/domain/intent.js';
6
6
  export * from './tree-surfer-ui/domain/user-input.js';
7
7
  export * from './tree-surfer/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sDAAsD,CAAC;AACrE,cAAc,kDAAkD,CAAC;AAEjE,cAAc,mCAAmC,CAAC;AAClD,cAAc,wCAAwC,CAAC;AACvD,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AAEtD,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sDAAsD,CAAC;AACrE,cAAc,kDAAkD,CAAC;AAEjE,cAAc,mCAAmC,CAAC;AAClD,cAAc,6CAA6C,CAAC;AAC5D,cAAc,mCAAmC,CAAC;AAClD,cAAc,uCAAuC,CAAC;AAEtD,cAAc,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './tree-surfer-ui/application/user-input-to-intent.js';
2
2
  export * from './tree-surfer-ui/application/intent-to-effect.js';
3
3
  export * from './tree-surfer-ui/domain/effect.js';
4
- export * from './tree-surfer-ui/domain/input-state.js';
4
+ export * from './tree-surfer-ui/domain/input-mode-state.js';
5
5
  export * from './tree-surfer-ui/domain/intent.js';
6
6
  export * from './tree-surfer-ui/domain/user-input.js';
7
7
  export * from './tree-surfer/index.js';
@@ -1,32 +1,58 @@
1
1
  import { z } from 'zod';
2
- export declare const CursorKind: {
3
- readonly Entry: "entry";
4
- readonly Fold: "fold";
5
- };
6
- export type CursorKind = (typeof CursorKind)[keyof typeof CursorKind];
7
- export type CursorEntry<Id> = {
8
- kind: typeof CursorKind.Entry;
9
- parentPath: readonly Id[];
10
- entryId: Id;
11
- };
12
- export type CursorFold<Id> = {
13
- kind: typeof CursorKind.Fold;
14
- parentPath: readonly Id[];
15
- };
16
- export type Cursor<Id> = CursorEntry<Id> | CursorFold<Id>;
17
- export declare function createCursorSchema<IdSchema extends z.ZodTypeAny>(idSchema: IdSchema): z.ZodDiscriminatedUnion<[z.ZodObject<{
18
- kind: z.ZodLiteral<"entry">;
19
- parentPath: z.ZodArray<IdSchema>;
20
- entryId: IdSchema;
21
- }, z.core.$strip>, z.ZodObject<{
22
- kind: z.ZodLiteral<"fold">;
23
- parentPath: z.ZodArray<IdSchema>;
24
- }, z.core.$strip>], "kind">;
2
+ /**
3
+ * A root-relative path identifying the current tree position.
4
+ *
5
+ * The empty path identifies the tree root. A non-empty path identifies a
6
+ * descendant by following child IDs from the root.
7
+ *
8
+ * For example, given:
9
+ *
10
+ * ```
11
+ * root
12
+ * └── src
13
+ * └── main.ts
14
+ * ```
15
+ *
16
+ * the cursors are:
17
+ *
18
+ * ```ts
19
+ * [] // root
20
+ * ['src'] // src
21
+ * ['src', 'main.ts'] // main.ts
22
+ * ```
23
+ *
24
+ * @typeParam Id - The type of node IDs.
25
+ */
26
+ export type Cursor<Id> = readonly Id[];
27
+ /**
28
+ * Creates a schema for root-relative tree cursors.
29
+ *
30
+ * The schema validates the IDs in the path, but does not verify that the path
31
+ * exists in a particular tree.
32
+ */
33
+ export declare function createCursorSchema<IdSchema extends z.ZodTypeAny>(idSchema: IdSchema): z.ZodArray<IdSchema>;
34
+ /**
35
+ * Operations for comparing and inspecting root-relative tree cursors.
36
+ *
37
+ * @typeParam Id - The type of node IDs.
38
+ */
25
39
  export type CursorApi<Id> = {
26
- isEntry(cursor: Cursor<Id>): cursor is CursorEntry<Id>;
27
- isFold(cursor: Cursor<Id>): cursor is CursorFold<Id>;
40
+ /**
41
+ * Tests whether two cursors identify the same tree position.
42
+ */
28
43
  equal(left: Cursor<Id>, right: Cursor<Id>): boolean;
29
- getPath(cursor: Cursor<Id>): Id[] | undefined;
44
+ /**
45
+ * Returns a mutable copy of the cursor path.
46
+ *
47
+ * The empty path identifies the tree root.
48
+ */
49
+ getPath(cursor: Cursor<Id>): Id[];
50
+ /**
51
+ * Tests whether the cursor is within the subtree at `entryPath`.
52
+ *
53
+ * The subtree includes the node at `entryPath` itself. Therefore, equal
54
+ * paths return `true`, and an empty `entryPath` contains every cursor.
55
+ */
30
56
  cursorBelongsToSubtree(cursor: Cursor<Id>, entryPath: readonly Id[]): boolean;
31
57
  };
32
58
  export declare function createCursorApi<Id>(): CursorApi<Id>;
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/tree-surfer/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,UAAU;;;CAGb,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC;AAEtE,MAAM,MAAM,WAAW,CAAC,EAAE,IAAI;IAC5B,IAAI,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;IAC9B,UAAU,EAAE,SAAS,EAAE,EAAE,CAAC;IAC1B,OAAO,EAAE,EAAE,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,EAAE,IAAI;IAC3B,IAAI,EAAE,OAAO,UAAU,CAAC,IAAI,CAAC;IAC7B,UAAU,EAAE,SAAS,EAAE,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,MAAM,CAAC,EAAE,IAAI,WAAW,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,EAAE,CAAC,CAAC;AAE1D,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,CAAC,CAAC,UAAU,EAC9D,QAAQ,EAAE,QAAQ;;;;;;;4BAgBnB;AAED,MAAM,MAAM,SAAS,CAAC,EAAE,IAAI;IAC1B,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,WAAW,CAAC,EAAE,CAAC,CAAC;IAEvD,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,MAAM,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC;IAErD,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;IAEpD,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,GAAG,SAAS,CAAC;IAE9C,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,GAAG,OAAO,CAAC;CAC/E,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,CAAC,CA0CnD"}
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/tree-surfer/cursor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,MAAM,CAAC,EAAE,IAAI,SAAS,EAAE,EAAE,CAAC;AAEvC;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,SAAS,CAAC,CAAC,UAAU,EAC9D,QAAQ,EAAE,QAAQ,wBAGnB;AAED;;;;GAIG;AACH,MAAM,MAAM,SAAS,CAAC,EAAE,IAAI;IAC1B;;OAEG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;IAEpD;;;;OAIG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC;IAElC;;;;;OAKG;IACH,sBAAsB,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,GAAG,OAAO,CAAC;CAC/E,CAAC;AAEF,wBAAgB,eAAe,CAAC,EAAE,KAAK,SAAS,CAAC,EAAE,CAAC,CAcnD"}
@@ -1,57 +1,29 @@
1
1
  import { z } from 'zod';
2
- export const CursorKind = {
3
- Entry: 'entry',
4
- Fold: 'fold',
5
- };
2
+ /**
3
+ * Creates a schema for root-relative tree cursors.
4
+ *
5
+ * The schema validates the IDs in the path, but does not verify that the path
6
+ * exists in a particular tree.
7
+ */
6
8
  export function createCursorSchema(idSchema) {
7
- const parentPathSchema = z.array(idSchema);
8
- return z.discriminatedUnion('kind', [
9
- z.object({
10
- kind: z.literal(CursorKind.Entry),
11
- parentPath: parentPathSchema,
12
- entryId: idSchema,
13
- }),
14
- z.object({
15
- kind: z.literal(CursorKind.Fold),
16
- parentPath: parentPathSchema,
17
- }),
18
- ]);
9
+ return z.array(idSchema);
19
10
  }
20
11
  export function createCursorApi() {
21
- const cursorApi = {
22
- isEntry(cursor) {
23
- return cursor.kind === CursorKind.Entry;
24
- },
25
- isFold(cursor) {
26
- return cursor.kind === CursorKind.Fold;
27
- },
12
+ return {
28
13
  equal(left, right) {
29
- if (!idPathEqual(left.parentPath, right.parentPath)) {
30
- return false;
31
- }
32
- if (cursorApi.isFold(left)) {
33
- return cursorApi.isFold(right);
34
- }
35
- return cursorApi.isEntry(right) && left.entryId === right.entryId;
14
+ return idPathEqual(left, right);
36
15
  },
37
16
  getPath(cursor) {
38
- if (cursorApi.isFold(cursor)) {
39
- return undefined;
40
- }
41
- return [...cursor.parentPath, cursor.entryId];
17
+ return [...cursor];
42
18
  },
43
19
  cursorBelongsToSubtree(cursor, entryPath) {
44
- if (cursorApi.isFold(cursor)) {
45
- return isStrictPathPrefix(entryPath, cursor.parentPath);
46
- }
47
- const cursorPath = [...cursor.parentPath, cursor.entryId];
48
- return isStrictPathPrefix(entryPath, cursorPath);
20
+ return isPathPrefix(entryPath, cursor);
49
21
  },
50
22
  };
51
- return cursorApi;
52
23
  }
53
24
  /**
54
- * Returns true when both paths contain the same IDs in the same order.
25
+ * Returns true when both root-relative paths contain the same IDs in the same
26
+ * order.
55
27
  */
56
28
  function idPathEqual(left, right) {
57
29
  if (left.length !== right.length) {
@@ -65,10 +37,13 @@ function idPathEqual(left, right) {
65
37
  return true;
66
38
  }
67
39
  /**
68
- * Returns true when `prefix` is a strict prefix of `path`.
40
+ * Returns true when `prefix` is a prefix of `path`.
41
+ *
42
+ * Equal paths are included. An empty prefix is therefore a prefix of every
43
+ * path.
69
44
  */
70
- function isStrictPathPrefix(prefix, path) {
71
- if (prefix.length >= path.length) {
45
+ function isPathPrefix(prefix, path) {
46
+ if (prefix.length > path.length) {
72
47
  return false;
73
48
  }
74
49
  for (let index = 0; index < prefix.length; index += 1) {
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=fold-node.arb.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold-node.arb.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.arb.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,4 @@
1
- import { FoldNode, FoldNodeApi, FoldNodeService } from './fold-node.types.js';
2
- import { SerializableKey } from '../tree-node/tree-node.types.js';
3
- export declare function createFoldNodeApi<Id extends SerializableKey>(): FoldNodeApi<Id>;
4
- export declare function createFoldNodeService<Id extends SerializableKey>(foldNodeApi: FoldNodeApi<Id>, createChild: (id: Id) => FoldNode<Id>): FoldNodeService<Id>;
1
+ import type { FoldNodeApi, FoldNodeService } from './fold-node.types.js';
2
+ import type { SerializableKey } from '../tree-node/tree-node.types.js';
3
+ export declare function createFoldNodeService<Id extends SerializableKey>(foldNodeApi: FoldNodeApi<Id>): FoldNodeService<Id>;
5
4
  //# sourceMappingURL=fold-node.impl.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fold-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.impl.ts"],"names":[],"mappings":"AACA,OAAO,EACL,QAAQ,EACR,WAAW,EAEX,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,KACvB,WAAW,CAAC,EAAE,CAAC,CAiCnB;AAED,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,eAAe,EAC9D,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,EAC5B,WAAW,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,QAAQ,CAAC,EAAE,CAAC,GACpC,eAAe,CAAC,EAAE,CAAC,CAiLrB"}
1
+ {"version":3,"file":"fold-node.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.impl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EACX,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAgBvE,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,eAAe,EAC9D,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,GAC3B,eAAe,CAAC,EAAE,CAAC,CAwNrB"}
@@ -1,110 +1,109 @@
1
- import { createTreeNodeApi } from '../tree-node/tree-node.impl.js';
2
- export function createFoldNodeApi() {
3
- const treeNodeApi = createTreeNodeApi();
4
- return {
5
- getChildren(node) {
6
- return node.children;
7
- },
8
- getChildById(node, id) {
9
- return node.children.find((child) => child.id === id);
10
- },
11
- getAtPath(rootNode, path, selector) {
12
- return treeNodeApi.getAtPath(rootNode.children, path, selector);
13
- },
14
- modifyAtPath(rootNode, path, modifier) {
15
- const children = treeNodeApi.modifyAtPath(rootNode.children, path, modifier);
16
- if (children === undefined) {
17
- return undefined;
18
- }
19
- return {
20
- ...rootNode,
21
- children,
22
- };
23
- },
24
- };
1
+ function getFoldedChildById(node, id) {
2
+ return node.foldedChildren.find((child) => child.id === id);
3
+ }
4
+ function hasFoldedChildWithId(node, id) {
5
+ return getFoldedChildById(node, id) !== undefined;
25
6
  }
26
- export function createFoldNodeService(foldNodeApi, createChild) {
7
+ export function createFoldNodeService(foldNodeApi) {
8
+ function createEmptyNode(id) {
9
+ return {
10
+ id,
11
+ children: [],
12
+ foldedChildren: [],
13
+ };
14
+ }
27
15
  return {
28
- createEmptyRoot() {
29
- return {
30
- children: [],
31
- folds: new Set(),
32
- };
33
- },
16
+ createEmptyNode,
34
17
  getIfEntryFoldedAtPath(rootNode, path, entryId) {
35
- if (path.length === 0) {
36
- return rootNode.folds.has(entryId);
37
- }
38
18
  const node = foldNodeApi.getAtPath(rootNode, path, (candidate) => candidate);
39
- return node !== undefined && node.folds.has(entryId);
19
+ return node !== undefined && hasFoldedChildWithId(node, entryId);
20
+ },
21
+ foldedEntriesAtPath(rootNode, path) {
22
+ const node = foldNodeApi.getAtPath(rootNode, path, (candidate) => candidate);
23
+ return node?.foldedChildren.map((child) => child.id);
40
24
  },
41
25
  addFoldedEntryAtPath(rootNode, path, entryId) {
42
- const ensuredRoot = ensurePath(rootNode, path);
43
- if (path.length === 0) {
44
- return addFoldedEntry(ensuredRoot, entryId);
26
+ const rootWithPath = ensurePath(rootNode, path);
27
+ if (rootWithPath === undefined) {
28
+ return undefined;
45
29
  }
46
- return foldNodeApi.modifyAtPath(ensuredRoot, path, (node) => addFoldedEntry(node, entryId));
30
+ return foldNodeApi.modifyAtPath(rootWithPath, path, (node) => addFoldedEntry(node, entryId));
47
31
  },
48
32
  removeFoldedEntryAtPath(rootNode, path, entryId) {
49
- if (path.length === 0) {
50
- return removeFoldedEntry(rootNode, entryId);
51
- }
52
33
  return foldNodeApi.modifyAtPath(rootNode, path, (node) => removeFoldedEntry(node, entryId));
53
34
  },
54
35
  clearFoldedEntriesAtPath(rootNode, path) {
55
- if (path.length === 0) {
56
- return clearFoldedEntries(rootNode);
57
- }
58
- return foldNodeApi.modifyAtPath(rootNode, path, clearFoldedEntries);
36
+ return foldNodeApi.modifyAtPath(rootNode, path, (node) => clearFoldedEntries(node));
59
37
  },
60
38
  };
61
- // Helpers:
62
- // `Node extends FoldNodeRoot<Id> | FoldNode<Id>` is used to create two
63
- // versions of functions: one for the root and one for the children.
64
- // "Flat" functions
39
+ // Node-local operations.
65
40
  //
66
- // Adds entry name to the given node.
41
+ // These functions update only the selected node. Path traversal and immutable
42
+ // ancestor updates are handled by FoldNodeApi.
67
43
  function addFoldedEntry(node, entryId) {
68
- if (node.folds.has(entryId)) {
44
+ const existingFoldedChild = getFoldedChildById(node, entryId);
45
+ if (existingFoldedChild !== undefined) {
69
46
  return node;
70
47
  }
71
- const folds = new Set(node.folds);
72
- folds.add(entryId);
48
+ const childIndex = node.children.findIndex((child) => child.id === entryId);
49
+ const child = childIndex === -1 ? createEmptyNode(entryId) : node.children[childIndex];
50
+ const children = childIndex === -1
51
+ ? node.children
52
+ : node.children.filter((_, index) => index !== childIndex);
73
53
  return {
74
54
  ...node,
75
- folds,
55
+ children,
56
+ foldedChildren: [...node.foldedChildren, child],
76
57
  };
77
58
  }
78
59
  function removeFoldedEntry(node, entryId) {
79
- if (!node.folds.has(entryId)) {
60
+ const foldedChildIndex = node.foldedChildren.findIndex((child) => child.id === entryId);
61
+ if (foldedChildIndex === -1) {
80
62
  return node;
81
63
  }
82
- const folds = new Set(node.folds);
83
- folds.delete(entryId);
64
+ const foldedChild = node.foldedChildren[foldedChildIndex];
65
+ const foldedChildren = node.foldedChildren.filter((_, index) => index !== foldedChildIndex);
66
+ const hasNestedFoldState = foldedChild.children.length > 0 || foldedChild.foldedChildren.length > 0;
84
67
  return {
85
68
  ...node,
86
- folds,
69
+ children: hasNestedFoldState
70
+ ? [...node.children, foldedChild]
71
+ : node.children,
72
+ foldedChildren,
87
73
  };
88
74
  }
89
75
  function clearFoldedEntries(node) {
90
- if (node.folds.size === 0) {
76
+ if (node.foldedChildren.length === 0) {
91
77
  return node;
92
78
  }
79
+ const children = [...node.children];
80
+ for (const foldedChild of node.foldedChildren) {
81
+ const hasNestedFoldState = foldedChild.children.length > 0 ||
82
+ foldedChild.foldedChildren.length > 0;
83
+ if (hasNestedFoldState) {
84
+ children.push(foldedChild);
85
+ }
86
+ }
93
87
  return {
94
88
  ...node,
95
- folds: new Set(),
89
+ children,
90
+ foldedChildren: [],
96
91
  };
97
92
  }
98
93
  /**
99
94
  * Ensures that the requested path exists in the fold-state tree.
100
95
  *
101
- * This creates fold nodes only; it does not modify the buffer tree.
96
+ * The path is relative to `rootNode`; an empty path selects the root.
97
+ * This creates fold nodes only and does not modify the buffer tree.
102
98
  */
103
99
  function ensurePath(rootNode, path) {
104
100
  if (path.length === 0) {
105
101
  return rootNode;
106
102
  }
107
- const children = ensureChildPath(rootNode.children, path, 0);
103
+ const children = ensureChildPath(rootNode, path, 0);
104
+ if (children === undefined) {
105
+ return undefined;
106
+ }
108
107
  if (children === rootNode.children) {
109
108
  return rootNode;
110
109
  }
@@ -113,16 +112,23 @@ export function createFoldNodeService(foldNodeApi, createChild) {
113
112
  children,
114
113
  };
115
114
  }
116
- function ensureChildPath(children, path, pathIndex) {
115
+ function ensureChildPath(parent, path, pathIndex) {
117
116
  const id = path[pathIndex];
118
117
  if (id === undefined) {
119
- return children;
118
+ return parent.children;
120
119
  }
120
+ if (hasFoldedChildWithId(parent, id)) {
121
+ return undefined;
122
+ }
123
+ const children = parent.children;
121
124
  const childIndex = children.findIndex((child) => child.id === id);
122
- const child = childIndex === -1 ? createChild(id) : children[childIndex];
125
+ const child = childIndex === -1 ? createEmptyNode(id) : children[childIndex];
123
126
  let updatedChild = child;
124
127
  if (pathIndex < path.length - 1) {
125
- const updatedChildren = ensureChildPath(child.children, path, pathIndex + 1);
128
+ const updatedChildren = ensureChildPath(child, path, pathIndex + 1);
129
+ if (updatedChildren === undefined) {
130
+ return undefined;
131
+ }
126
132
  if (updatedChildren !== child.children) {
127
133
  updatedChild = {
128
134
  ...child,
@@ -1,7 +1,5 @@
1
1
  import { z } from 'zod';
2
- import type { FoldNode, FoldNodeRoot } from './fold-node.types.js';
3
- export declare function createFoldNodeSchemas<Id extends PropertyKey>(idSchema: z.ZodType<Id>): {
4
- foldNodeSchema: z.ZodType<FoldNode<Id>, unknown, z.core.$ZodTypeInternals<FoldNode<Id>, unknown>>;
5
- foldNodeRootSchema: z.ZodType<FoldNodeRoot<Id>, unknown, z.core.$ZodTypeInternals<FoldNodeRoot<Id>, unknown>>;
6
- };
2
+ import type { FoldNode } from './fold-node.types.js';
3
+ import { SerializableKey } from '../tree-node/tree-node.types.js';
4
+ export declare function createFoldNodeSchemas<Id extends SerializableKey>(idSchema: z.ZodType<Id>): z.ZodType<FoldNode<Id>>;
7
5
  //# sourceMappingURL=fold-node.schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fold-node.schema.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEnE,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,WAAW,EAC1D,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;;;EAsBxB"}
1
+ {"version":3,"file":"fold-node.schema.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/fold-node/fold-node.schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,wBAAgB,qBAAqB,CAAC,EAAE,SAAS,eAAe,EAC9D,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,GACtB,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAYzB"}
@@ -3,14 +3,7 @@ export function createFoldNodeSchemas(idSchema) {
3
3
  const foldNodeSchema = z.lazy(() => z.object({
4
4
  id: idSchema,
5
5
  children: z.array(foldNodeSchema),
6
- folds: z.array(idSchema).transform((ids) => new Set(ids)),
6
+ foldedChildren: z.array(foldNodeSchema),
7
7
  }));
8
- const foldNodeRootSchema = z.object({
9
- children: z.array(foldNodeSchema),
10
- folds: z.array(idSchema).transform((ids) => new Set(ids)),
11
- });
12
- return {
13
- foldNodeSchema,
14
- foldNodeRootSchema,
15
- };
8
+ return foldNodeSchema;
16
9
  }