@mortenbrudvik/waveui 0.15.0 → 0.16.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 (155) hide show
  1. package/README.md +274 -24
  2. package/dist/components/data-display/List.cjs +1 -1
  3. package/dist/components/data-display/List.cjs.map +1 -1
  4. package/dist/components/data-display/List.mjs +405 -219
  5. package/dist/components/data-display/List.mjs.map +1 -1
  6. package/dist/components/data-display/List.registry.cjs +1 -1
  7. package/dist/components/data-display/List.registry.cjs.map +1 -1
  8. package/dist/components/data-display/List.registry.mjs +12 -6
  9. package/dist/components/data-display/List.registry.mjs.map +1 -1
  10. package/dist/components/input/Combobox.cjs +1 -1
  11. package/dist/components/input/Combobox.cjs.map +1 -1
  12. package/dist/components/input/Combobox.mjs +163 -148
  13. package/dist/components/input/Combobox.mjs.map +1 -1
  14. package/dist/components/input/Dropdown.cjs +1 -1
  15. package/dist/components/input/Dropdown.cjs.map +1 -1
  16. package/dist/components/input/Dropdown.mjs +132 -117
  17. package/dist/components/input/Dropdown.mjs.map +1 -1
  18. package/dist/components/input/Listbox.cjs +1 -1
  19. package/dist/components/input/Listbox.cjs.map +1 -1
  20. package/dist/components/input/Listbox.mjs +90 -78
  21. package/dist/components/input/Listbox.mjs.map +1 -1
  22. package/dist/components/input/Option.cjs +1 -1
  23. package/dist/components/input/Option.cjs.map +1 -1
  24. package/dist/components/input/Option.data.cjs +2 -0
  25. package/dist/components/input/Option.data.cjs.map +1 -0
  26. package/dist/components/input/Option.data.mjs +114 -0
  27. package/dist/components/input/Option.data.mjs.map +1 -0
  28. package/dist/components/input/Option.mjs +21 -17
  29. package/dist/components/input/Option.mjs.map +1 -1
  30. package/dist/components/layout/FlatTree.cjs +2 -0
  31. package/dist/components/layout/FlatTree.cjs.map +1 -0
  32. package/dist/components/layout/FlatTree.context.cjs +2 -0
  33. package/dist/components/layout/FlatTree.context.cjs.map +1 -0
  34. package/dist/components/layout/FlatTree.context.mjs +20 -0
  35. package/dist/components/layout/FlatTree.context.mjs.map +1 -0
  36. package/dist/components/layout/FlatTree.item.cjs +2 -0
  37. package/dist/components/layout/FlatTree.item.cjs.map +1 -0
  38. package/dist/components/layout/FlatTree.item.mjs +90 -0
  39. package/dist/components/layout/FlatTree.item.mjs.map +1 -0
  40. package/dist/components/layout/FlatTree.mjs +276 -0
  41. package/dist/components/layout/FlatTree.mjs.map +1 -0
  42. package/dist/components/layout/Tree.core.cjs +1 -1
  43. package/dist/components/layout/Tree.core.cjs.map +1 -1
  44. package/dist/components/layout/Tree.core.mjs +166 -165
  45. package/dist/components/layout/Tree.core.mjs.map +1 -1
  46. package/dist/components/layout/Tree.selection.cjs +1 -1
  47. package/dist/components/layout/Tree.selection.cjs.map +1 -1
  48. package/dist/components/layout/Tree.selection.mjs +11 -57
  49. package/dist/components/layout/Tree.selection.mjs.map +1 -1
  50. package/dist/components/layout/useFlatTree.cjs +2 -0
  51. package/dist/components/layout/useFlatTree.cjs.map +1 -0
  52. package/dist/components/layout/useFlatTree.mjs +83 -0
  53. package/dist/components/layout/useFlatTree.mjs.map +1 -0
  54. package/dist/components/table/DataGrid.cjs +1 -1
  55. package/dist/components/table/DataGrid.cjs.map +1 -1
  56. package/dist/components/table/DataGrid.context.cjs +2 -0
  57. package/dist/components/table/DataGrid.context.cjs.map +1 -0
  58. package/dist/components/table/DataGrid.context.mjs +74 -0
  59. package/dist/components/table/DataGrid.context.mjs.map +1 -0
  60. package/dist/components/table/DataGrid.header.cjs +2 -0
  61. package/dist/components/table/DataGrid.header.cjs.map +1 -0
  62. package/dist/components/table/DataGrid.header.mjs +168 -0
  63. package/dist/components/table/DataGrid.header.mjs.map +1 -0
  64. package/dist/components/table/DataGrid.mjs +488 -591
  65. package/dist/components/table/DataGrid.mjs.map +1 -1
  66. package/dist/components/table/DataGrid.sizing.cjs +2 -0
  67. package/dist/components/table/DataGrid.sizing.cjs.map +1 -0
  68. package/dist/components/table/DataGrid.sizing.mjs +187 -0
  69. package/dist/components/table/DataGrid.sizing.mjs.map +1 -0
  70. package/dist/components/table/DataGrid.store.cjs +2 -0
  71. package/dist/components/table/DataGrid.store.cjs.map +1 -0
  72. package/dist/components/table/DataGrid.store.mjs +108 -0
  73. package/dist/components/table/DataGrid.store.mjs.map +1 -0
  74. package/dist/components/table/DataGrid.window.cjs +2 -0
  75. package/dist/components/table/DataGrid.window.cjs.map +1 -0
  76. package/dist/components/table/DataGrid.window.mjs +165 -0
  77. package/dist/components/table/DataGrid.window.mjs.map +1 -0
  78. package/dist/components/table/Table.cjs +1 -1
  79. package/dist/components/table/Table.cjs.map +1 -1
  80. package/dist/components/table/Table.mjs +74 -72
  81. package/dist/components/table/Table.mjs.map +1 -1
  82. package/dist/components/table/Table.resize.cjs +2 -0
  83. package/dist/components/table/Table.resize.cjs.map +1 -0
  84. package/dist/components/table/Table.resize.mjs +98 -0
  85. package/dist/components/table/Table.resize.mjs.map +1 -0
  86. package/dist/components/table/useGridNavigation.cjs +1 -1
  87. package/dist/components/table/useGridNavigation.cjs.map +1 -1
  88. package/dist/components/table/useGridNavigation.mjs +343 -140
  89. package/dist/components/table/useGridNavigation.mjs.map +1 -1
  90. package/dist/components/table/useTableColumnSizing.cjs +2 -0
  91. package/dist/components/table/useTableColumnSizing.cjs.map +1 -0
  92. package/dist/components/table/useTableColumnSizing.mjs +76 -0
  93. package/dist/components/table/useTableColumnSizing.mjs.map +1 -0
  94. package/dist/components/table/useTableFeatures.cjs +2 -0
  95. package/dist/components/table/useTableFeatures.cjs.map +1 -0
  96. package/dist/components/table/useTableFeatures.mjs +110 -0
  97. package/dist/components/table/useTableFeatures.mjs.map +1 -0
  98. package/dist/components/table/useTableSelection.cjs +2 -0
  99. package/dist/components/table/useTableSelection.cjs.map +1 -0
  100. package/dist/components/table/useTableSelection.mjs +39 -0
  101. package/dist/components/table/useTableSelection.mjs.map +1 -0
  102. package/dist/components/table/useTableSort.cjs +2 -0
  103. package/dist/components/table/useTableSort.cjs.map +1 -0
  104. package/dist/components/table/useTableSort.mjs +58 -0
  105. package/dist/components/table/useTableSort.mjs.map +1 -0
  106. package/dist/hooks/useActiveDescendant.cjs +1 -1
  107. package/dist/hooks/useActiveDescendant.cjs.map +1 -1
  108. package/dist/hooks/useActiveDescendant.mjs +33 -25
  109. package/dist/hooks/useActiveDescendant.mjs.map +1 -1
  110. package/dist/hooks/useListbox.cjs +1 -1
  111. package/dist/hooks/useListbox.cjs.map +1 -1
  112. package/dist/hooks/useListbox.mjs +140 -130
  113. package/dist/hooks/useListbox.mjs.map +1 -1
  114. package/dist/hooks/useRovingTabIndex.cjs +1 -1
  115. package/dist/hooks/useRovingTabIndex.cjs.map +1 -1
  116. package/dist/hooks/useRovingTabIndex.mjs +1 -1
  117. package/dist/hooks/useRovingTabIndex.mjs.map +1 -1
  118. package/dist/hooks/useSelection.cjs +2 -0
  119. package/dist/hooks/useSelection.cjs.map +1 -0
  120. package/dist/hooks/useSelection.mjs +57 -0
  121. package/dist/hooks/useSelection.mjs.map +1 -0
  122. package/dist/hooks/useVirtualWindow.cjs +2 -0
  123. package/dist/hooks/useVirtualWindow.cjs.map +1 -0
  124. package/dist/hooks/useVirtualWindow.mjs +102 -0
  125. package/dist/hooks/useVirtualWindow.mjs.map +1 -0
  126. package/dist/index.cjs +1 -1
  127. package/dist/index.d.cts +1542 -53
  128. package/dist/index.d.ts +1542 -53
  129. package/dist/index.mjs +147 -136
  130. package/dist/lib/classNames.cjs +1 -1
  131. package/dist/lib/classNames.cjs.map +1 -1
  132. package/dist/lib/classNames.mjs +68 -49
  133. package/dist/lib/classNames.mjs.map +1 -1
  134. package/dist/lib/columnSizing.cjs +2 -0
  135. package/dist/lib/columnSizing.cjs.map +1 -0
  136. package/dist/lib/columnSizing.mjs +27 -0
  137. package/dist/lib/columnSizing.mjs.map +1 -0
  138. package/dist/lib/flatTree.cjs +2 -0
  139. package/dist/lib/flatTree.cjs.map +1 -0
  140. package/dist/lib/flatTree.mjs +118 -0
  141. package/dist/lib/flatTree.mjs.map +1 -0
  142. package/dist/lib/selection.cjs +2 -0
  143. package/dist/lib/selection.cjs.map +1 -0
  144. package/dist/lib/selection.mjs +28 -0
  145. package/dist/lib/selection.mjs.map +1 -0
  146. package/dist/lib/treeCascade.cjs +2 -0
  147. package/dist/lib/treeCascade.cjs.map +1 -0
  148. package/dist/lib/treeCascade.mjs +52 -0
  149. package/dist/lib/treeCascade.mjs.map +1 -0
  150. package/dist/lib/virtual.cjs +2 -0
  151. package/dist/lib/virtual.cjs.map +1 -0
  152. package/dist/lib/virtual.mjs +54 -0
  153. package/dist/lib/virtual.mjs.map +1 -0
  154. package/dist/styles.css +1 -1
  155. package/package.json +1 -1
@@ -0,0 +1,118 @@
1
+ //#region src/lib/flatTree.ts
2
+ function e(e) {
3
+ let t = [], n = /* @__PURE__ */ new Map();
4
+ e.forEach((e, r) => {
5
+ if (n.has(e.value)) {
6
+ t.push({
7
+ kind: "duplicate-value",
8
+ value: e.value,
9
+ index: r
10
+ });
11
+ return;
12
+ }
13
+ n.set(e.value, {
14
+ item: e,
15
+ index: r,
16
+ parent: e.parentValue
17
+ });
18
+ });
19
+ for (let [e, r] of n) r.parent !== void 0 && !n.has(r.parent) && (t.push({
20
+ kind: "unknown-parent",
21
+ value: e,
22
+ parentValue: r.parent
23
+ }), r.parent = void 0);
24
+ let r = /* @__PURE__ */ new Map();
25
+ for (let e of n.keys()) {
26
+ if (r.has(e)) continue;
27
+ let i = [], a = e;
28
+ for (; a !== void 0 && !r.has(a);) r.set(a, 1), i.push(a), a = n.get(a)?.parent;
29
+ if (a !== void 0 && r.get(a) === 1) {
30
+ t.push({
31
+ kind: "cycle",
32
+ value: a
33
+ });
34
+ let e = n.get(a);
35
+ e && (e.parent = void 0);
36
+ }
37
+ for (let e of i) r.set(e, 2);
38
+ }
39
+ let i = [], a = /* @__PURE__ */ new Map();
40
+ for (let [e, t] of n) {
41
+ if (t.parent === void 0) {
42
+ i.push(e);
43
+ continue;
44
+ }
45
+ let n = a.get(t.parent);
46
+ n ? n.push(e) : a.set(t.parent, [e]);
47
+ }
48
+ let o = /* @__PURE__ */ new Map(), s = [];
49
+ i.forEach((e, t) => s.push({
50
+ value: e,
51
+ level: 1,
52
+ position: t + 1,
53
+ setSize: i.length
54
+ }));
55
+ for (let e = 0; e < s.length; e += 1) {
56
+ let { value: t, level: r, position: i, setSize: c } = s[e], l = n.get(t), u = a.get(t) ?? [];
57
+ o.set(t, {
58
+ item: l.item,
59
+ index: l.index,
60
+ parentValue: l.parent,
61
+ level: r,
62
+ position: i,
63
+ setSize: c,
64
+ children: u,
65
+ hasChildren: u.length > 0,
66
+ isBranch: u.length > 0 || l.item.itemType === "branch"
67
+ }), u.forEach((e, t) => s.push({
68
+ value: e,
69
+ level: r + 1,
70
+ position: t + 1,
71
+ setSize: u.length
72
+ }));
73
+ }
74
+ let c = /* @__PURE__ */ new Map();
75
+ for (let e of n.keys()) c.set(e, o.get(e));
76
+ return {
77
+ byValue: c,
78
+ roots: i,
79
+ issues: t
80
+ };
81
+ }
82
+ function t(e, t) {
83
+ let n = [], r = [];
84
+ for (let t = e.roots.length - 1; t >= 0; --t) r.push(e.roots[t]);
85
+ for (; r.length > 0;) {
86
+ let i = e.byValue.get(r.pop());
87
+ if (!i) continue;
88
+ let a = i.isBranch && t.has(i.item.value);
89
+ if (n.push({
90
+ item: i.item,
91
+ level: i.level,
92
+ position: i.position,
93
+ setSize: i.setSize,
94
+ parentValue: i.parentValue,
95
+ hasChildren: i.hasChildren,
96
+ isBranch: i.isBranch,
97
+ expanded: a,
98
+ index: n.length
99
+ }), a) for (let e = i.children.length - 1; e >= 0; --e) r.push(i.children[e]);
100
+ }
101
+ return n;
102
+ }
103
+ function n(e) {
104
+ let t = /* @__PURE__ */ new Map();
105
+ for (let n of e.byValue.keys()) t.set(n, {
106
+ value: n,
107
+ children: []
108
+ });
109
+ for (let [n, r] of e.byValue) {
110
+ let e = t.get(n);
111
+ for (let n of r.children) e.children.push(t.get(n));
112
+ }
113
+ return e.roots.map((e) => t.get(e));
114
+ }
115
+ //#endregion
116
+ export { e as buildFlatTreeIndex, n as flatTreeForest, t as visibleFlatItems };
117
+
118
+ //# sourceMappingURL=flatTree.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flatTree.mjs","names":["buildFlatTreeIndex","issues","drafts","item","value","draft","state","start","path","current","repeated","roots","childValues","siblings","byValue","queue","at","children","child","level","childAt","ordered","visibleFlatItems","visible","stack","index","record","open","expanded","flatTreeForest","nodes","node"],"sources":["../../src/lib/flatTree.ts"],"sourcesContent":["// The pure model behind `useFlatTree` and `FlatTree`: server-safe, no React import. A flat list of\n// items with `parentValue` links becomes an index (one pass, no recursion), the visible rows for an\n// expanded set (a walk that enters only expanded branches) and a `TreeNode` forest for the check\n// cascade of `treeCascade.ts`. Every walk is iterative, so a chain thousands of levels deep cannot\n// overflow the stack.\nimport type { TreeNode } from './treeCascade';\n\n/** One item of a flat tree: `parentValue` places it, items are listed in any order. */\nexport interface FlatTreeItemData {\n /** The item's identity; it must be unique among the items (a later duplicate is ignored). */\n value: string;\n /** The `value` of the item this one is nested in; absent for a root. */\n parentValue?: string;\n /**\n * `'branch'` marks an item that can expand even while it has no child items (children loaded\n * on demand). An item with child items is a branch whatever its type says.\n */\n itemType?: 'leaf' | 'branch';\n /** The text the item shows. */\n label?: string;\n /**\n * Whether the item is disabled. `FlatTree` reads it (the keys and typeahead skip the item, it is\n * not the tab stop, and the user cannot activate, expand or check it); the model ignores it:\n * `expand`, `collapse`, `toggle` and `setChecked` change a disabled item too, and a check of its\n * parent includes it.\n */\n disabled?: boolean;\n}\n\n/**\n * One row of the visible list: an item under expanded branches only, with the facts a row needs\n * for its tree semantics. A row is a branch when `hasChildren` is true or when\n * `item.itemType === 'branch'`; `expanded` is true only for a branch whose value is in the\n * expanded set (so a childless `'branch'` item can be expanded).\n */\nexport interface VisibleFlatItem {\n /** The item data, as passed in. */\n item: FlatTreeItemData;\n /** The depth, 1 for a root (`aria-level`). */\n level: number;\n /** The 1-based place among its siblings, in the order of `items` (`aria-posinset`). */\n position: number;\n /** The number of siblings, itself included (`aria-setsize`). */\n setSize: number;\n /** The value of the parent as indexed (`undefined` for a root, an unknown parent included). */\n parentValue: string | undefined;\n /** Whether other items are nested in it. */\n hasChildren: boolean;\n /** Whether it can expand: `hasChildren`, or `item.itemType === 'branch'` (`aria-expanded` is set for these). */\n isBranch: boolean;\n /** Whether it is a branch in the expanded set. */\n expanded: boolean;\n /** The place in the visible list, 0-based. */\n index: number;\n}\n\n/** What the index knows about one item. */\nexport interface FlatTreeNodeRecord {\n /** The item data, as passed in. */\n item: FlatTreeItemData;\n /** The place of the item in `items`, 0-based. */\n index: number;\n /** The value of the parent as indexed: `undefined` for a root, also when the parent was unknown or the link was cut as a cycle. */\n parentValue: string | undefined;\n /** The depth, 1 for a root. */\n level: number;\n /** The 1-based place among its siblings. */\n position: number;\n /** The number of siblings, itself included. */\n setSize: number;\n /** The values of the items nested in it, in the order of `items`. */\n children: readonly string[];\n /** Whether other items are nested in it (`children` is not empty). */\n hasChildren: boolean;\n /** Whether it can expand: `hasChildren`, or `item.itemType === 'branch'`. */\n isBranch: boolean;\n}\n\n/** A problem the index found in the items and repaired. Stable: `useFlatTree` reports these in development. */\nexport type FlatTreeIssue =\n | {\n /** A later item with a value an earlier one has: it is ignored (the first is kept). */\n kind: 'duplicate-value';\n value: string;\n /** The place of the ignored item in `items`. */\n index: number;\n }\n | {\n /** An item whose `parentValue` matches no item: it is indexed as a root. */\n kind: 'unknown-parent';\n value: string;\n parentValue: string;\n }\n | {\n /** A loop of parents: the link of this value to its parent is cut, so it is a root. */\n kind: 'cycle';\n value: string;\n };\n\n/** The indexed items of a flat tree, from `buildFlatTreeIndex`. */\nexport interface FlatTreeIndex {\n /** Every item by value (one record per value). */\n byValue: Map<string, FlatTreeNodeRecord>;\n /** The values of the roots, in the order of `items`. */\n roots: readonly string[];\n /** What was repaired, in the order it was found: duplicates first, then unknown parents, then cycles. */\n issues: readonly FlatTreeIssue[];\n}\n\ninterface Draft {\n item: FlatTreeItemData;\n index: number;\n parent: string | undefined;\n}\n\n/**\n * Indexes `items` in one pass over them (linear time, no recursion). Repairs, each reported as an\n * issue: a repeated value keeps its first item; an item whose parent is unknown becomes a root;\n * a loop of parents is cut at the first of its values met by the walk up from the first item, in\n * `items` order, whose parent chain reaches the loop (an item outside the loop that leads into it\n * can start that walk: `[x→b, a→b, b→a]` cuts at `b`), and that value becomes a root. Siblings\n * keep the order of `items`.\n */\nexport function buildFlatTreeIndex(items: readonly FlatTreeItemData[]): FlatTreeIndex {\n const issues: FlatTreeIssue[] = [];\n const drafts = new Map<string, Draft>();\n items.forEach((item, index) => {\n if (drafts.has(item.value)) {\n issues.push({ kind: 'duplicate-value', value: item.value, index });\n return;\n }\n drafts.set(item.value, { item, index, parent: item.parentValue });\n });\n\n for (const [value, draft] of drafts) {\n if (draft.parent !== undefined && !drafts.has(draft.parent)) {\n issues.push({ kind: 'unknown-parent', value, parentValue: draft.parent });\n draft.parent = undefined;\n }\n }\n\n // Colour walk: 1 while on the current path, 2 once settled. A walk that reaches a value of its\n // own path has found a loop; cutting that value's parent link ends it.\n const state = new Map<string, 1 | 2>();\n for (const start of drafts.keys()) {\n if (state.has(start)) continue;\n const path: string[] = [];\n let current: string | undefined = start;\n while (current !== undefined && !state.has(current)) {\n state.set(current, 1);\n path.push(current);\n current = drafts.get(current)?.parent;\n }\n if (current !== undefined && state.get(current) === 1) {\n issues.push({ kind: 'cycle', value: current });\n const repeated = drafts.get(current);\n if (repeated) repeated.parent = undefined;\n }\n for (const value of path) state.set(value, 2);\n }\n\n const roots: string[] = [];\n const childValues = new Map<string, string[]>();\n for (const [value, draft] of drafts) {\n if (draft.parent === undefined) {\n roots.push(value);\n continue;\n }\n const siblings = childValues.get(draft.parent);\n if (siblings) siblings.push(value);\n else childValues.set(draft.parent, [value]);\n }\n\n const byValue = new Map<string, FlatTreeNodeRecord>();\n const queue: Array<{ value: string; level: number; position: number; setSize: number }> = [];\n roots.forEach((value, at) =>\n queue.push({ value, level: 1, position: at + 1, setSize: roots.length }),\n );\n // Every item is reachable from a root now: the loops are cut and the unknown parents are roots.\n for (let at = 0; at < queue.length; at += 1) {\n const { value, level, position, setSize } = queue[at];\n const draft = drafts.get(value) as Draft;\n const children = childValues.get(value) ?? [];\n byValue.set(value, {\n item: draft.item,\n index: draft.index,\n parentValue: draft.parent,\n level,\n position,\n setSize,\n children,\n hasChildren: children.length > 0,\n isBranch: children.length > 0 || draft.item.itemType === 'branch',\n });\n children.forEach((child, childAt) =>\n queue.push({\n value: child,\n level: level + 1,\n position: childAt + 1,\n setSize: children.length,\n }),\n );\n }\n // `byValue` is filled in breadth-first order; present it in the order of `items`.\n const ordered = new Map<string, FlatTreeNodeRecord>();\n for (const value of drafts.keys()) ordered.set(value, byValue.get(value) as FlatTreeNodeRecord);\n\n return { byValue: ordered, roots, issues };\n}\n\n/**\n * The rows visible with `expanded`: the roots, and the children of every branch in the set, in\n * tree order. Only expanded branches are entered, so the cost is the number of rows returned plus\n * the depth of the stack, never the number of items. A value in `expanded` that is no branch (a\n * leaf, or no item at all) has no effect.\n */\nexport function visibleFlatItems(\n index: FlatTreeIndex,\n expanded: ReadonlySet<string>,\n): readonly VisibleFlatItem[] {\n const visible: VisibleFlatItem[] = [];\n const stack: string[] = [];\n for (let at = index.roots.length - 1; at >= 0; at -= 1) stack.push(index.roots[at]);\n while (stack.length > 0) {\n const record = index.byValue.get(stack.pop() as string);\n if (!record) continue;\n const open = record.isBranch && expanded.has(record.item.value);\n visible.push({\n item: record.item,\n level: record.level,\n position: record.position,\n setSize: record.setSize,\n parentValue: record.parentValue,\n hasChildren: record.hasChildren,\n isBranch: record.isBranch,\n expanded: open,\n index: visible.length,\n });\n if (open) {\n for (let at = record.children.length - 1; at >= 0; at -= 1) stack.push(record.children[at]);\n }\n }\n return visible;\n}\n\n/**\n * The items as a `TreeNode` forest (one node per indexed item, children in the order of `items`),\n * the input of `computeCheckStates` and `toggleCheck`. Built without recursion; walking the result\n * with those recursive functions is as deep as the tree is.\n */\nexport function flatTreeForest(index: FlatTreeIndex): TreeNode[] {\n const nodes = new Map<string, TreeNode>();\n for (const value of index.byValue.keys()) nodes.set(value, { value, children: [] });\n for (const [value, record] of index.byValue) {\n const node = nodes.get(value) as TreeNode;\n for (const child of record.children) node.children.push(nodes.get(child) as TreeNode);\n }\n return index.roots.map((value) => nodes.get(value) as TreeNode);\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ function e(e,t,n,r){if(n===`single`)return e.has(t)&&r?.allowEmpty?new Set:new Set([t]);let i=new Set(e);return i.delete(t)||i.add(t),i}function t(e,t){let n=new Set(e);for(let e of t)n.add(e);return n}function n(e,t){let n=new Set(e);for(let e of t)n.delete(e);return n}function r(e,t,n=t){let r=n.length>0&&n.every(t=>e.has(t)),i=!r&&t.some(t=>e.has(t));return{all:r,some:i,none:!r&&!i}}exports.clearIds=n,exports.selectAllIds=t,exports.summarizeSelection=r,exports.toggleId=e;
2
+ //# sourceMappingURL=selection.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.cjs","names":["toggleId","mode","selected","id","options","next","selectAllIds","selectableIds","clearIds","universe","summarizeSelection","all","some"],"sources":["../../src/lib/selection.ts"],"sourcesContent":["import type { SelectionMode } from './types';\n\n/**\n * Toggles `id` in a selection and returns a new set; the argument is never mutated.\n *\n * `multiple` adds or removes the id. `single` replaces the selection with the id; when that id\n * is the selection already it stays selected, unless `options.allowEmpty` clears it.\n */\nexport function toggleId(\n selected: ReadonlySet<string>,\n id: string,\n mode: SelectionMode,\n options?: { allowEmpty?: boolean },\n): Set<string> {\n if (mode === 'single') {\n return selected.has(id) && options?.allowEmpty ? new Set() : new Set([id]);\n }\n const next = new Set(selected);\n if (!next.delete(id)) next.add(id);\n return next;\n}\n\n/** Adds `selectableIds` to a selection, keeping every id already in it (foreign ones too). */\nexport function selectAllIds(\n selected: ReadonlySet<string>,\n selectableIds: readonly string[],\n): Set<string> {\n const next = new Set(selected);\n for (const id of selectableIds) next.add(id);\n return next;\n}\n\n/** Removes the ids of `universe` from a selection; ids outside the universe stay selected. */\nexport function clearIds(selected: ReadonlySet<string>, universe: readonly string[]): Set<string> {\n const next = new Set(selected);\n for (const id of universe) next.delete(id);\n return next;\n}\n\n/**\n * Summarises a selection over `universe`. `all`: `selectableIds` (default `universe`) is\n * non-empty and every id in it is selected. `some`: an id of `universe` is selected and not\n * `all`, so a selected disabled id counts. `none`: neither. Ids outside `universe` count toward\n * nothing.\n */\nexport function summarizeSelection(\n selected: ReadonlySet<string>,\n universe: readonly string[],\n selectableIds: readonly string[] = universe,\n): { all: boolean; some: boolean; none: boolean } {\n const all = selectableIds.length > 0 && selectableIds.every((id) => selected.has(id));\n const some = !all && universe.some((id) => selected.has(id));\n return { all, some, none: !all && !some };\n}\n"],"mappings":"AAQA,SAAgBA,EACd,EACA,EACA,EACA,EACa,CACb,GAAIC,IAAS,SACX,OAAOC,EAAS,IAAIC,CAAE,GAAKC,GAAS,WAAa,IAAI,IAAQ,IAAI,IAAI,CAACD,CAAE,CAAC,EAE3E,IAAME,EAAO,IAAI,IAAIH,CAAQ,EAE7B,OADKG,EAAK,OAAOF,CAAE,GAAGE,EAAK,IAAIF,CAAE,EAC1BE,CACT,CAGA,SAAgBC,EACd,EACA,EACa,CACb,IAAMD,EAAO,IAAI,IAAIH,CAAQ,EAC7B,IAAK,IAAMC,KAAMI,EAAeF,EAAK,IAAIF,CAAE,EAC3C,OAAOE,CACT,CAGA,SAAgBG,EAAS,EAA+B,EAA0C,CAChG,IAAMH,EAAO,IAAI,IAAIH,CAAQ,EAC7B,IAAK,IAAMC,KAAMM,EAAUJ,EAAK,OAAOF,CAAE,EACzC,OAAOE,CACT,CAQA,SAAgBK,EACd,EACA,EACA,EAAmCD,EACa,CAChD,IAAME,EAAMJ,EAAc,OAAS,GAAKA,EAAc,MAAO,GAAOL,EAAS,IAAIC,CAAE,CAAC,EAC9ES,EAAO,CAACD,GAAOF,EAAS,KAAM,GAAOP,EAAS,IAAIC,CAAE,CAAC,EAC3D,MAAO,CAAE,MAAK,OAAM,KAAM,CAACQ,GAAO,CAACC,CAAK,CAC1C"}
@@ -0,0 +1,28 @@
1
+ //#region src/lib/selection.ts
2
+ function e(e, t, n, r) {
3
+ if (n === "single") return e.has(t) && r?.allowEmpty ? /* @__PURE__ */ new Set() : /* @__PURE__ */ new Set([t]);
4
+ let i = new Set(e);
5
+ return i.delete(t) || i.add(t), i;
6
+ }
7
+ function t(e, t) {
8
+ let n = new Set(e);
9
+ for (let e of t) n.add(e);
10
+ return n;
11
+ }
12
+ function n(e, t) {
13
+ let n = new Set(e);
14
+ for (let e of t) n.delete(e);
15
+ return n;
16
+ }
17
+ function r(e, t, n = t) {
18
+ let r = n.length > 0 && n.every((t) => e.has(t)), i = !r && t.some((t) => e.has(t));
19
+ return {
20
+ all: r,
21
+ some: i,
22
+ none: !r && !i
23
+ };
24
+ }
25
+ //#endregion
26
+ export { n as clearIds, t as selectAllIds, r as summarizeSelection, e as toggleId };
27
+
28
+ //# sourceMappingURL=selection.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selection.mjs","names":["toggleId","mode","selected","id","options","next","selectAllIds","selectableIds","clearIds","universe","summarizeSelection","all","some"],"sources":["../../src/lib/selection.ts"],"sourcesContent":["import type { SelectionMode } from './types';\n\n/**\n * Toggles `id` in a selection and returns a new set; the argument is never mutated.\n *\n * `multiple` adds or removes the id. `single` replaces the selection with the id; when that id\n * is the selection already it stays selected, unless `options.allowEmpty` clears it.\n */\nexport function toggleId(\n selected: ReadonlySet<string>,\n id: string,\n mode: SelectionMode,\n options?: { allowEmpty?: boolean },\n): Set<string> {\n if (mode === 'single') {\n return selected.has(id) && options?.allowEmpty ? new Set() : new Set([id]);\n }\n const next = new Set(selected);\n if (!next.delete(id)) next.add(id);\n return next;\n}\n\n/** Adds `selectableIds` to a selection, keeping every id already in it (foreign ones too). */\nexport function selectAllIds(\n selected: ReadonlySet<string>,\n selectableIds: readonly string[],\n): Set<string> {\n const next = new Set(selected);\n for (const id of selectableIds) next.add(id);\n return next;\n}\n\n/** Removes the ids of `universe` from a selection; ids outside the universe stay selected. */\nexport function clearIds(selected: ReadonlySet<string>, universe: readonly string[]): Set<string> {\n const next = new Set(selected);\n for (const id of universe) next.delete(id);\n return next;\n}\n\n/**\n * Summarises a selection over `universe`. `all`: `selectableIds` (default `universe`) is\n * non-empty and every id in it is selected. `some`: an id of `universe` is selected and not\n * `all`, so a selected disabled id counts. `none`: neither. Ids outside `universe` count toward\n * nothing.\n */\nexport function summarizeSelection(\n selected: ReadonlySet<string>,\n universe: readonly string[],\n selectableIds: readonly string[] = universe,\n): { all: boolean; some: boolean; none: boolean } {\n const all = selectableIds.length > 0 && selectableIds.every((id) => selected.has(id));\n const some = !all && universe.some((id) => selected.has(id));\n return { all, some, none: !all && !some };\n}\n"],"mappings":";AAQA,SAAgBA,EACd,GACA,GACA,GACA,GACa;CACb,IAAIC,MAAS,UACX,OAAOC,EAAS,IAAIC,CAAE,KAAKC,GAAS,6BAAa,IAAI,IAAI,oBAAI,IAAI,IAAI,CAACD,CAAE,CAAC;CAE3E,IAAME,IAAO,IAAI,IAAIH,CAAQ;CAE7B,OADKG,EAAK,OAAOF,CAAE,KAAGE,EAAK,IAAIF,CAAE,GAC1BE;AACT;AAGA,SAAgBC,EACd,GACA,GACa;CACb,IAAMD,IAAO,IAAI,IAAIH,CAAQ;CAC7B,KAAK,IAAMC,KAAMI,GAAe,EAAK,IAAIJ,CAAE;CAC3C,OAAOE;AACT;AAGA,SAAgBG,EAAS,GAA+B,GAA0C;CAChG,IAAMH,IAAO,IAAI,IAAIH,CAAQ;CAC7B,KAAK,IAAMC,KAAMM,GAAU,EAAK,OAAON,CAAE;CACzC,OAAOE;AACT;AAQA,SAAgBK,EACd,GACA,GACA,IAAmCD,GACa;CAChD,IAAME,IAAMJ,EAAc,SAAS,KAAKA,EAAc,OAAO,MAAOL,EAAS,IAAIC,CAAE,CAAC,GAC9ES,IAAO,CAACD,KAAOF,EAAS,MAAM,MAAOP,EAAS,IAAIC,CAAE,CAAC;CAC3D,OAAO;EAAE;EAAK;EAAM,MAAM,CAACQ,KAAO,CAACC;CAAK;AAC1C"}
@@ -0,0 +1,2 @@
1
+ function e(t,n){for(let r of t)n(r),e(r.children,n)}function t(e,n){for(let r of e){if(r.value===n)return[r];let e=t(r.children,n);if(e)return[r,...e]}return null}function n(t,n,r=!1){let i=new Map;e(t,e=>{i.has(e.value)||i.set(e.value,e)});let a=new Map,o=(e,t)=>{let r=t||n.has(e.value),s=e.children.map(e=>o(e,r)),c;return c=r?`checked`:s.length===0?`unchecked`:s.every(e=>e===`checked`)?`checked`:s.some(e=>e!==`unchecked`)?`mixed`:`unchecked`,i.get(e.value)===e&&a.set(e.value,c),c};for(let e of t)o(e,r);return a}function r(r,i,a){let o=n(r,new Set(i)),s=new Set;for(let[e,t]of o)t===`checked`&&s.add(e);let c=new Set;e(r,e=>c.add(e.value));let l=Array.from(new Set(i.filter(e=>!c.has(e)))),u=t(r,a);if(u){let t=u[u.length-1],n=o.get(a)!==`checked`;e([t],e=>{n?s.add(e.value):s.delete(e.value)});for(let e=u.length-2;e>=0;--e){let t=u[e];t.children.every(e=>s.has(e.value))?s.add(t.value):s.delete(t.value)}}else{let e=l.indexOf(a);e===-1?l.push(a):l.splice(e,1)}let d=[],f=new Set;return e(r,e=>{s.has(e.value)&&!f.has(e.value)&&(f.add(e.value),d.push(e.value))}),[...d,...l]}exports.computeCheckStates=n,exports.toggleCheck=r;
2
+ //# sourceMappingURL=treeCascade.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"treeCascade.cjs","names":["forEachNode","node","nodes","visit","findPath","value","below","computeCheckStates","first","candidate","states","forced","forcedAbove","listed","current","childStates","child","state","childState","top","ancestorListed","toggleCheck","checked","itemValue","known","unmatched","listedValue","path","target","turnOn","index","ancestor","at","ordered","seen"],"sources":["../../src/lib/treeCascade.ts"],"sourcesContent":["// The pure check-state cascade of the trees: server-safe, no React import. The nested Tree feeds it\n// a forest read from its elements (`Tree.selection.ts`), the flat tree a forest built from flat data\n// (`flatTree.ts`).\n\n/** One Tree.Item of the cascade model: its value and the items nested in it. */\nexport interface TreeNode {\n value: string;\n children: TreeNode[];\n}\n\n/** The check state of an item in a `selectionMode=\"multiple\"` tree. */\nexport type CheckState = 'checked' | 'mixed' | 'unchecked';\n\nfunction forEachNode(nodes: readonly TreeNode[], visit: (node: TreeNode) => void): void {\n for (const node of nodes) {\n visit(node);\n forEachNode(node.children, visit);\n }\n}\n\n/** The first item with `value` in tree order and its ancestors, from the top down; `null` if none. */\nfunction findPath(nodes: readonly TreeNode[], value: string): TreeNode[] | null {\n for (const node of nodes) {\n if (node.value === value) return [node];\n const below = findPath(node.children, value);\n if (below) return [node, ...below];\n }\n return null;\n}\n\n/**\n * The check state of every item: checked when its value is listed, when an ancestor's value is\n * listed (or `ancestorListed`), or when it has nested items and all of them are checked; mixed\n * when some of them are checked or mixed; else unchecked. For a value that several items share,\n * the first item in tree order decides the entry.\n */\nexport function computeCheckStates(\n nodes: readonly TreeNode[],\n listed: ReadonlySet<string>,\n ancestorListed = false,\n): Map<string, CheckState> {\n const first = new Map<string, TreeNode>();\n forEachNode(nodes, (candidate) => {\n if (!first.has(candidate.value)) first.set(candidate.value, candidate);\n });\n const states = new Map<string, CheckState>();\n const visit = (current: TreeNode, forcedAbove: boolean): CheckState => {\n const forced = forcedAbove || listed.has(current.value);\n const childStates = current.children.map((child) => visit(child, forced));\n let state: CheckState;\n if (forced) state = 'checked';\n else if (childStates.length === 0) state = 'unchecked';\n else if (childStates.every((childState) => childState === 'checked')) state = 'checked';\n else if (childStates.some((childState) => childState !== 'unchecked')) state = 'mixed';\n else state = 'unchecked';\n if (first.get(current.value) === current) states.set(current.value, state);\n return state;\n };\n for (const top of nodes) visit(top, ancestorListed);\n return states;\n}\n\n/**\n * The checked items after toggling `value`. A checked item and every item nested in it become\n * unchecked; an unchecked or mixed one and every item nested in it become checked; its ancestors\n * then follow their nested items. Returns every checked item in tree order (fully checked parents\n * included, mixed ones not), each value once, followed by the listed values that match no item,\n * in their order; a value that no item has is added to, or removed from, those.\n */\nexport function toggleCheck(\n nodes: readonly TreeNode[],\n listed: readonly string[],\n value: string,\n): string[] {\n const states = computeCheckStates(nodes, new Set(listed));\n const checked = new Set<string>();\n for (const [itemValue, state] of states) {\n if (state === 'checked') checked.add(itemValue);\n }\n const known = new Set<string>();\n forEachNode(nodes, (candidate) => known.add(candidate.value));\n const unmatched = Array.from(new Set(listed.filter((listedValue) => !known.has(listedValue))));\n\n const path = findPath(nodes, value);\n if (path) {\n const target = path[path.length - 1];\n const turnOn = states.get(value) !== 'checked';\n forEachNode([target], (candidate) => {\n if (turnOn) checked.add(candidate.value);\n else checked.delete(candidate.value);\n });\n for (let index = path.length - 2; index >= 0; index -= 1) {\n const ancestor = path[index];\n if (ancestor.children.every((child) => checked.has(child.value))) checked.add(ancestor.value);\n else checked.delete(ancestor.value);\n }\n } else {\n const at = unmatched.indexOf(value);\n if (at === -1) unmatched.push(value);\n else unmatched.splice(at, 1);\n }\n\n const ordered: string[] = [];\n const seen = new Set<string>();\n forEachNode(nodes, (candidate) => {\n if (checked.has(candidate.value) && !seen.has(candidate.value)) {\n seen.add(candidate.value);\n ordered.push(candidate.value);\n }\n });\n return [...ordered, ...unmatched];\n}\n"],"mappings":"AAaA,SAASA,EAAY,EAA4B,EAAuC,CACtF,IAAK,IAAMC,KAAQC,EACjBC,EAAMF,CAAI,EACVD,EAAYC,EAAK,SAAUE,CAAK,CAEpC,CAGA,SAASC,EAAS,EAA4B,EAAkC,CAC9E,IAAK,IAAMH,KAAQC,EAAO,CACxB,GAAID,EAAK,QAAUI,EAAO,MAAO,CAACJ,CAAI,EACtC,IAAMK,EAAQF,EAASH,EAAK,SAAUI,CAAK,EAC3C,GAAIC,EAAO,MAAO,CAACL,EAAM,GAAGK,CAAK,CACnC,CACA,OAAO,IACT,CAQA,SAAgBC,EACd,EACA,EACA,EAAiB,GACQ,CACzB,IAAMC,EAAQ,IAAI,IAClBR,EAAYE,EAAQ,GAAc,CAC3BM,EAAM,IAAIC,EAAU,KAAK,GAAGD,EAAM,IAAIC,EAAU,MAAOA,CAAS,CACvE,CAAC,EACD,IAAMC,EAAS,IAAI,IACbP,GAAS,EAAmB,IAAqC,CACrE,IAAMQ,EAASC,GAAeC,EAAO,IAAIC,EAAQ,KAAK,EAChDC,EAAcD,EAAQ,SAAS,IAAK,GAAUX,EAAMa,EAAOL,CAAM,CAAC,EACpEM,EAOJ,MANA,CAIKA,EAJDN,EAAgB,UACXI,EAAY,SAAW,EAAW,YAClCA,EAAY,MAAO,GAAeG,IAAe,SAAS,EAAW,UACrEH,EAAY,KAAM,GAAeG,IAAe,WAAW,EAAW,QAClE,YACTV,EAAM,IAAIM,EAAQ,KAAK,IAAMA,GAASJ,EAAO,IAAII,EAAQ,MAAOG,CAAK,EAClEA,CACT,EACA,IAAK,IAAME,KAAOjB,EAAOC,EAAMgB,EAAKC,CAAc,EAClD,OAAOV,CACT,CASA,SAAgBW,EACd,EACA,EACA,EACU,CACV,IAAMX,EAASH,EAAmBL,EAAO,IAAI,IAAIW,CAAM,CAAC,EAClDS,EAAU,IAAI,IACpB,IAAK,GAAM,CAACC,EAAWN,KAAUP,EAC3BO,IAAU,WAAWK,EAAQ,IAAIC,CAAS,EAEhD,IAAMC,EAAQ,IAAI,IAClBxB,EAAYE,EAAQ,GAAcsB,EAAM,IAAIf,EAAU,KAAK,CAAC,EAC5D,IAAMgB,EAAY,MAAM,KAAK,IAAI,IAAIZ,EAAO,OAAQ,GAAgB,CAACW,EAAM,IAAIE,CAAW,CAAC,CAAC,CAAC,EAEvFC,EAAOvB,EAASF,EAAOG,CAAK,EAClC,GAAIsB,EAAM,CACR,IAAMC,EAASD,EAAKA,EAAK,OAAS,GAC5BE,EAASnB,EAAO,IAAIL,CAAK,IAAM,UACrCL,EAAY,CAAC4B,CAAM,EAAI,GAAc,CAC/BC,EAAQP,EAAQ,IAAIb,EAAU,KAAK,EAClCa,EAAQ,OAAOb,EAAU,KAAK,CACrC,CAAC,EACD,IAAK,IAAIqB,EAAQH,EAAK,OAAS,EAAGG,GAAS,EAAG,IAAY,CACxD,IAAMC,EAAWJ,EAAKG,GAClBC,EAAS,SAAS,MAAO,GAAUT,EAAQ,IAAIN,EAAM,KAAK,CAAC,EAAGM,EAAQ,IAAIS,EAAS,KAAK,EACvFT,EAAQ,OAAOS,EAAS,KAAK,CACpC,CACF,KAAO,CACL,IAAMC,EAAKP,EAAU,QAAQpB,CAAK,EAC9B2B,IAAO,GAAIP,EAAU,KAAKpB,CAAK,EAC9BoB,EAAU,OAAOO,EAAI,CAAC,CAC7B,CAEA,IAAMC,EAAoB,CAAC,EACrBC,EAAO,IAAI,IAOjB,OANAlC,EAAYE,EAAQ,GAAc,CAC5BoB,EAAQ,IAAIb,EAAU,KAAK,GAAK,CAACyB,EAAK,IAAIzB,EAAU,KAAK,IAC3DyB,EAAK,IAAIzB,EAAU,KAAK,EACxBwB,EAAQ,KAAKxB,EAAU,KAAK,EAEhC,CAAC,EACM,CAAC,GAAGwB,EAAS,GAAGR,CAAS,CAClC"}
@@ -0,0 +1,52 @@
1
+ //#region src/lib/treeCascade.ts
2
+ function e(t, n) {
3
+ for (let r of t) n(r), e(r.children, n);
4
+ }
5
+ function t(e, n) {
6
+ for (let r of e) {
7
+ if (r.value === n) return [r];
8
+ let e = t(r.children, n);
9
+ if (e) return [r, ...e];
10
+ }
11
+ return null;
12
+ }
13
+ function n(t, n, r = !1) {
14
+ let i = /* @__PURE__ */ new Map();
15
+ e(t, (e) => {
16
+ i.has(e.value) || i.set(e.value, e);
17
+ });
18
+ let a = /* @__PURE__ */ new Map(), o = (e, t) => {
19
+ let r = t || n.has(e.value), s = e.children.map((e) => o(e, r)), c;
20
+ return c = r ? "checked" : s.length === 0 ? "unchecked" : s.every((e) => e === "checked") ? "checked" : s.some((e) => e !== "unchecked") ? "mixed" : "unchecked", i.get(e.value) === e && a.set(e.value, c), c;
21
+ };
22
+ for (let e of t) o(e, r);
23
+ return a;
24
+ }
25
+ function r(r, i, a) {
26
+ let o = n(r, new Set(i)), s = /* @__PURE__ */ new Set();
27
+ for (let [e, t] of o) t === "checked" && s.add(e);
28
+ let c = /* @__PURE__ */ new Set();
29
+ e(r, (e) => c.add(e.value));
30
+ let l = Array.from(new Set(i.filter((e) => !c.has(e)))), u = t(r, a);
31
+ if (u) {
32
+ let t = u[u.length - 1], n = o.get(a) !== "checked";
33
+ e([t], (e) => {
34
+ n ? s.add(e.value) : s.delete(e.value);
35
+ });
36
+ for (let e = u.length - 2; e >= 0; --e) {
37
+ let t = u[e];
38
+ t.children.every((e) => s.has(e.value)) ? s.add(t.value) : s.delete(t.value);
39
+ }
40
+ } else {
41
+ let e = l.indexOf(a);
42
+ e === -1 ? l.push(a) : l.splice(e, 1);
43
+ }
44
+ let d = [], f = /* @__PURE__ */ new Set();
45
+ return e(r, (e) => {
46
+ s.has(e.value) && !f.has(e.value) && (f.add(e.value), d.push(e.value));
47
+ }), [...d, ...l];
48
+ }
49
+ //#endregion
50
+ export { n as computeCheckStates, r as toggleCheck };
51
+
52
+ //# sourceMappingURL=treeCascade.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"treeCascade.mjs","names":["forEachNode","node","nodes","visit","findPath","value","below","computeCheckStates","first","candidate","states","forced","forcedAbove","listed","current","childStates","child","state","childState","top","ancestorListed","toggleCheck","checked","itemValue","known","unmatched","listedValue","path","target","turnOn","index","ancestor","at","ordered","seen"],"sources":["../../src/lib/treeCascade.ts"],"sourcesContent":["// The pure check-state cascade of the trees: server-safe, no React import. The nested Tree feeds it\n// a forest read from its elements (`Tree.selection.ts`), the flat tree a forest built from flat data\n// (`flatTree.ts`).\n\n/** One Tree.Item of the cascade model: its value and the items nested in it. */\nexport interface TreeNode {\n value: string;\n children: TreeNode[];\n}\n\n/** The check state of an item in a `selectionMode=\"multiple\"` tree. */\nexport type CheckState = 'checked' | 'mixed' | 'unchecked';\n\nfunction forEachNode(nodes: readonly TreeNode[], visit: (node: TreeNode) => void): void {\n for (const node of nodes) {\n visit(node);\n forEachNode(node.children, visit);\n }\n}\n\n/** The first item with `value` in tree order and its ancestors, from the top down; `null` if none. */\nfunction findPath(nodes: readonly TreeNode[], value: string): TreeNode[] | null {\n for (const node of nodes) {\n if (node.value === value) return [node];\n const below = findPath(node.children, value);\n if (below) return [node, ...below];\n }\n return null;\n}\n\n/**\n * The check state of every item: checked when its value is listed, when an ancestor's value is\n * listed (or `ancestorListed`), or when it has nested items and all of them are checked; mixed\n * when some of them are checked or mixed; else unchecked. For a value that several items share,\n * the first item in tree order decides the entry.\n */\nexport function computeCheckStates(\n nodes: readonly TreeNode[],\n listed: ReadonlySet<string>,\n ancestorListed = false,\n): Map<string, CheckState> {\n const first = new Map<string, TreeNode>();\n forEachNode(nodes, (candidate) => {\n if (!first.has(candidate.value)) first.set(candidate.value, candidate);\n });\n const states = new Map<string, CheckState>();\n const visit = (current: TreeNode, forcedAbove: boolean): CheckState => {\n const forced = forcedAbove || listed.has(current.value);\n const childStates = current.children.map((child) => visit(child, forced));\n let state: CheckState;\n if (forced) state = 'checked';\n else if (childStates.length === 0) state = 'unchecked';\n else if (childStates.every((childState) => childState === 'checked')) state = 'checked';\n else if (childStates.some((childState) => childState !== 'unchecked')) state = 'mixed';\n else state = 'unchecked';\n if (first.get(current.value) === current) states.set(current.value, state);\n return state;\n };\n for (const top of nodes) visit(top, ancestorListed);\n return states;\n}\n\n/**\n * The checked items after toggling `value`. A checked item and every item nested in it become\n * unchecked; an unchecked or mixed one and every item nested in it become checked; its ancestors\n * then follow their nested items. Returns every checked item in tree order (fully checked parents\n * included, mixed ones not), each value once, followed by the listed values that match no item,\n * in their order; a value that no item has is added to, or removed from, those.\n */\nexport function toggleCheck(\n nodes: readonly TreeNode[],\n listed: readonly string[],\n value: string,\n): string[] {\n const states = computeCheckStates(nodes, new Set(listed));\n const checked = new Set<string>();\n for (const [itemValue, state] of states) {\n if (state === 'checked') checked.add(itemValue);\n }\n const known = new Set<string>();\n forEachNode(nodes, (candidate) => known.add(candidate.value));\n const unmatched = Array.from(new Set(listed.filter((listedValue) => !known.has(listedValue))));\n\n const path = findPath(nodes, value);\n if (path) {\n const target = path[path.length - 1];\n const turnOn = states.get(value) !== 'checked';\n forEachNode([target], (candidate) => {\n if (turnOn) checked.add(candidate.value);\n else checked.delete(candidate.value);\n });\n for (let index = path.length - 2; index >= 0; index -= 1) {\n const ancestor = path[index];\n if (ancestor.children.every((child) => checked.has(child.value))) checked.add(ancestor.value);\n else checked.delete(ancestor.value);\n }\n } else {\n const at = unmatched.indexOf(value);\n if (at === -1) unmatched.push(value);\n else unmatched.splice(at, 1);\n }\n\n const ordered: string[] = [];\n const seen = new Set<string>();\n forEachNode(nodes, (candidate) => {\n if (checked.has(candidate.value) && !seen.has(candidate.value)) {\n seen.add(candidate.value);\n ordered.push(candidate.value);\n }\n });\n return [...ordered, ...unmatched];\n}\n"],"mappings":";AAaA,SAASA,EAAY,GAA4B,GAAuC;CACtF,KAAK,IAAMC,KAAQC,GAEjB,AADAC,EAAMF,CAAI,GACVD,EAAYC,EAAK,UAAUE,CAAK;AAEpC;AAGA,SAASC,EAAS,GAA4B,GAAkC;CAC9E,KAAK,IAAMH,KAAQC,GAAO;EACxB,IAAID,EAAK,UAAUI,GAAO,OAAO,CAACJ,CAAI;EACtC,IAAMK,IAAQF,EAASH,EAAK,UAAUI,CAAK;EAC3C,IAAIC,GAAO,OAAO,CAACL,GAAM,GAAGK,CAAK;CACnC;CACA,OAAO;AACT;AAQA,SAAgBC,EACd,GACA,GACA,IAAiB,IACQ;CACzB,IAAMC,oBAAQ,IAAI,IAAsB;CACxC,EAAYN,IAAQ,MAAc;EAChC,AAAKM,EAAM,IAAIC,EAAU,KAAK,KAAGD,EAAM,IAAIC,EAAU,OAAOA,CAAS;CACvE,CAAC;CACD,IAAMC,oBAAS,IAAI,IAAwB,GACrCP,KAAS,GAAmB,MAAqC;EACrE,IAAMQ,IAASC,KAAeC,EAAO,IAAIC,EAAQ,KAAK,GAChDC,IAAcD,EAAQ,SAAS,KAAK,MAAUX,EAAMa,GAAOL,CAAM,CAAC,GACpEM;EAOJ,OANA,AAIKA,IAJDN,IAAgB,YACXI,EAAY,WAAW,IAAW,cAClCA,EAAY,OAAO,MAAeG,MAAe,SAAS,IAAW,YACrEH,EAAY,MAAM,MAAeG,MAAe,WAAW,IAAW,UAClE,aACTV,EAAM,IAAIM,EAAQ,KAAK,MAAMA,KAASJ,EAAO,IAAII,EAAQ,OAAOG,CAAK,GAClEA;CACT;CACA,KAAK,IAAME,KAAOjB,GAAO,EAAMiB,GAAKC,CAAc;CAClD,OAAOV;AACT;AASA,SAAgBW,EACd,GACA,GACA,GACU;CACV,IAAMX,IAASH,EAAmBL,GAAO,IAAI,IAAIW,CAAM,CAAC,GAClDS,oBAAU,IAAI,IAAY;CAChC,KAAK,IAAM,CAACC,GAAWN,MAAUP,GAC/B,AAAIO,MAAU,aAAWK,EAAQ,IAAIC,CAAS;CAEhD,IAAMC,oBAAQ,IAAI,IAAY;CAC9B,EAAYtB,IAAQ,MAAcsB,EAAM,IAAIf,EAAU,KAAK,CAAC;CAC5D,IAAMgB,IAAY,MAAM,KAAK,IAAI,IAAIZ,EAAO,QAAQ,MAAgB,CAACW,EAAM,IAAIE,CAAW,CAAC,CAAC,CAAC,GAEvFC,IAAOvB,EAASF,GAAOG,CAAK;CAClC,IAAIsB,GAAM;EACR,IAAMC,IAASD,EAAKA,EAAK,SAAS,IAC5BE,IAASnB,EAAO,IAAIL,CAAK,MAAM;EACrC,EAAY,CAACuB,CAAM,IAAI,MAAc;GACnC,AAAIC,IAAQP,EAAQ,IAAIb,EAAU,KAAK,IAClCa,EAAQ,OAAOb,EAAU,KAAK;EACrC,CAAC;EACD,KAAK,IAAIqB,IAAQH,EAAK,SAAS,GAAGG,KAAS,GAAG,KAAY;GACxD,IAAMC,IAAWJ,EAAKG;GACtB,AAAIC,EAAS,SAAS,OAAO,MAAUT,EAAQ,IAAIN,EAAM,KAAK,CAAC,IAAGM,EAAQ,IAAIS,EAAS,KAAK,IACvFT,EAAQ,OAAOS,EAAS,KAAK;EACpC;CACF,OAAO;EACL,IAAMC,IAAKP,EAAU,QAAQpB,CAAK;EAClC,AAAI2B,MAAO,KAAIP,EAAU,KAAKpB,CAAK,IAC9BoB,EAAU,OAAOO,GAAI,CAAC;CAC7B;CAEA,IAAMC,IAAoB,CAAC,GACrBC,oBAAO,IAAI,IAAY;CAO7B,OANAlC,EAAYE,IAAQ,MAAc;EAChC,AAAIoB,EAAQ,IAAIb,EAAU,KAAK,KAAK,CAACyB,EAAK,IAAIzB,EAAU,KAAK,MAC3DyB,EAAK,IAAIzB,EAAU,KAAK,GACxBwB,EAAQ,KAAKxB,EAAU,KAAK;CAEhC,CAAC,GACM,CAAC,GAAGwB,GAAS,GAAGR,CAAS;AAClC"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){return Number.isFinite(e)?Math.max(0,Math.floor(e)):t}function t(t){let{itemSize:n,keep:r}=t,i=e(t.count,0);if(i===0)return[];let a=Number.isFinite(n)&&n>0?n:1,o=Math.max(1,Number.isFinite(t.viewportSize)?Math.ceil(t.viewportSize/a):1),s=e(t.overscan,0),c=Math.min(e(t.firstVisible,0),Math.max(0,i-o)),l=Math.max(0,c-s),u=Math.min(i,c+o+s),d=[...new Set(r??[])].filter(e=>Number.isInteger(e)&&e>=0&&e<i).filter(e=>e<l||e>=u).sort((e,t)=>e-t),f=[],p=(e,t)=>{let n=f[f.length-1];n&&e<=n[1]?n[1]=Math.max(n[1],t):f.push([e,t])},m=!1;for(let e of d)!m&&e>l&&(p(l,u),m=!0),p(e,e+1);m||p(l,u);let h=[],g=0;for(let[e,t]of f){e>g&&h.push({kind:`spacer`,size:(e-g)*a,key:`spacer-${e}`});for(let n=e;n<t;n++)h.push({kind:`item`,index:n});g=t}return g<i&&h.push({kind:`spacer`,size:(i-g)*a,key:`spacer-end`}),h}function n(t){let{align:n,scrollTop:r,viewportSize:i}=t,a=e(t.count,0),o=Number.isFinite(t.itemSize)&&t.itemSize>0?t.itemSize:0,s=Number.isFinite(i)?Math.max(0,i):0,c=Math.max(0,a*o-s);if(a===0||c===0)return 0;let l=Math.min(Math.max(0,Number.isFinite(t.index)?Math.floor(t.index):0),a-1)*o,u=l+o,d=Number.isFinite(r)?r:0,f;switch(n){case`start`:f=l;break;case`end`:f=u-s;break;case`center`:f=l+o/2-s/2;break;default:f=l<d?l:u>d+s?u-s:d}return Math.min(Math.max(0,f),c)}exports.computeVirtualLayout=t,exports.scrollOffsetForIndex=n;
2
+ //# sourceMappingURL=virtual.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual.cjs","names":["toCount","value","fallback","computeVirtualLayout","input","count","rowSize","itemSize","visibleCount","overscan","first","start","end","extra","keep","index","a","b","runs","push","last","from","to","windowPushed","entries","next","scrollOffsetForIndex","viewport","viewportSize","maxOffset","top","bottom","current","scrollTop","target","align"],"sources":["../../src/lib/virtual.ts"],"sourcesContent":["/**\n * One entry of a windowed list: a row to render, or the empty space that stands in for the rows\n * that are not rendered. A spacer's `key` is stable for React: `spacer-<index of the next item>`,\n * or `spacer-end` for the one after the last item.\n */\nexport type VirtualEntry =\n { kind: 'item'; index: number } | { kind: 'spacer'; size: number; key: string };\n\n/** Input of {@link computeVirtualLayout}. */\nexport interface VirtualLayoutInput {\n /** Number of rows in the whole list. */\n count: number;\n /** Height (or width) of one row, in pixels. */\n itemSize: number;\n /** Index of the first row in view. Clamped to `[0, max(0, count - rows in view)]`. */\n firstVisible: number;\n /** Height (or width) of the scroll viewport, in pixels. */\n viewportSize: number;\n /** Rows rendered beyond each edge of the view. */\n overscan: number;\n /**\n * Indexes that stay rendered outside the window (the focused row, the row a drag holds).\n * Entries that are not integers inside the list are ignored.\n */\n keep?: readonly number[];\n}\n\n/** An index run, `[from, to)`. */\ntype Run = readonly [from: number, to: number];\n\nfunction toCount(value: number, fallback: number): number {\n return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;\n}\n\n/**\n * The rows to render for a scrolled list and the spacers that keep its scroll height: the rows in\n * view with `overscan` rows on both sides, plus every `keep` index as a run of its own. The sizes\n * of the items and the spacers add up to `count * itemSize`; a spacer is never empty.\n */\nexport function computeVirtualLayout(input: VirtualLayoutInput): readonly VirtualEntry[] {\n const { itemSize, keep } = input;\n const count = toCount(input.count, 0);\n if (count === 0) return [];\n\n const rowSize = Number.isFinite(itemSize) && itemSize > 0 ? itemSize : 1;\n const visibleCount = Math.max(\n 1,\n Number.isFinite(input.viewportSize) ? Math.ceil(input.viewportSize / rowSize) : 1,\n );\n const overscan = toCount(input.overscan, 0);\n // A scroller never shows a first row past the last full page: a list that shrank under a\n // scrolled window is clamped the way the browser clamps `scrollTop`.\n const first = Math.min(toCount(input.firstVisible, 0), Math.max(0, count - visibleCount));\n const start = Math.max(0, first - overscan);\n const end = Math.min(count, first + visibleCount + overscan);\n\n // The window and the kept rows outside it, sorted; touching runs merge into one.\n const extra = [...new Set(keep ?? [])]\n .filter((index) => Number.isInteger(index) && index >= 0 && index < count)\n .filter((index) => index < start || index >= end)\n .sort((a, b) => a - b);\n const runs: [number, number][] = [];\n const push = (from: number, to: number) => {\n const last = runs[runs.length - 1];\n if (last && from <= last[1]) last[1] = Math.max(last[1], to);\n else runs.push([from, to]);\n };\n let windowPushed = false;\n for (const index of extra) {\n if (!windowPushed && index > start) {\n push(start, end);\n windowPushed = true;\n }\n push(index, index + 1);\n }\n if (!windowPushed) push(start, end);\n\n const entries: VirtualEntry[] = [];\n let next = 0;\n for (const [from, to] of runs as readonly Run[]) {\n if (from > next)\n entries.push({ kind: 'spacer', size: (from - next) * rowSize, key: `spacer-${from}` });\n for (let index = from; index < to; index++) entries.push({ kind: 'item', index });\n next = to;\n }\n if (next < count)\n entries.push({ kind: 'spacer', size: (count - next) * rowSize, key: 'spacer-end' });\n return entries;\n}\n\n/** Input of {@link scrollOffsetForIndex}. */\nexport interface ScrollOffsetInput {\n index: number;\n /**\n * Where the row ends up: `'start'`, `'center'` or `'end'` of the viewport, or `'auto'`, the\n * smallest move that shows it whole (the current offset when it already is).\n */\n align: 'auto' | 'start' | 'center' | 'end';\n itemSize: number;\n count: number;\n scrollTop: number;\n viewportSize: number;\n}\n\n/** The scroll offset, clamped to the content, that brings row `index` to the requested place. */\nexport function scrollOffsetForIndex(input: ScrollOffsetInput): number {\n const { align, scrollTop, viewportSize } = input;\n const count = toCount(input.count, 0);\n const rowSize = Number.isFinite(input.itemSize) && input.itemSize > 0 ? input.itemSize : 0;\n const viewport = Number.isFinite(viewportSize) ? Math.max(0, viewportSize) : 0;\n const maxOffset = Math.max(0, count * rowSize - viewport);\n if (count === 0 || maxOffset === 0) return 0;\n\n const index = Math.min(\n Math.max(0, Number.isFinite(input.index) ? Math.floor(input.index) : 0),\n count - 1,\n );\n const top = index * rowSize;\n const bottom = top + rowSize;\n const current = Number.isFinite(scrollTop) ? scrollTop : 0;\n\n let target: number;\n switch (align) {\n case 'start':\n target = top;\n break;\n case 'end':\n target = bottom - viewport;\n break;\n case 'center':\n target = top + rowSize / 2 - viewport / 2;\n break;\n default:\n target = top < current ? top : bottom > current + viewport ? bottom - viewport : current;\n }\n return Math.min(Math.max(0, target), maxOffset);\n}\n"],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ //#region src/lib/virtual.ts
2
+ function e(e, t) {
3
+ return Number.isFinite(e) ? Math.max(0, Math.floor(e)) : t;
4
+ }
5
+ function t(t) {
6
+ let { itemSize: n, keep: r } = t, i = e(t.count, 0);
7
+ if (i === 0) return [];
8
+ let a = Number.isFinite(n) && n > 0 ? n : 1, o = Math.max(1, Number.isFinite(t.viewportSize) ? Math.ceil(t.viewportSize / a) : 1), s = e(t.overscan, 0), c = Math.min(e(t.firstVisible, 0), Math.max(0, i - o)), l = Math.max(0, c - s), u = Math.min(i, c + o + s), d = [...new Set(r ?? [])].filter((e) => Number.isInteger(e) && e >= 0 && e < i).filter((e) => e < l || e >= u).sort((e, t) => e - t), f = [], p = (e, t) => {
9
+ let n = f[f.length - 1];
10
+ n && e <= n[1] ? n[1] = Math.max(n[1], t) : f.push([e, t]);
11
+ }, m = !1;
12
+ for (let e of d) !m && e > l && (p(l, u), m = !0), p(e, e + 1);
13
+ m || p(l, u);
14
+ let h = [], g = 0;
15
+ for (let [e, t] of f) {
16
+ e > g && h.push({
17
+ kind: "spacer",
18
+ size: (e - g) * a,
19
+ key: `spacer-${e}`
20
+ });
21
+ for (let n = e; n < t; n++) h.push({
22
+ kind: "item",
23
+ index: n
24
+ });
25
+ g = t;
26
+ }
27
+ return g < i && h.push({
28
+ kind: "spacer",
29
+ size: (i - g) * a,
30
+ key: "spacer-end"
31
+ }), h;
32
+ }
33
+ function n(t) {
34
+ let { align: n, scrollTop: r, viewportSize: i } = t, a = e(t.count, 0), o = Number.isFinite(t.itemSize) && t.itemSize > 0 ? t.itemSize : 0, s = Number.isFinite(i) ? Math.max(0, i) : 0, c = Math.max(0, a * o - s);
35
+ if (a === 0 || c === 0) return 0;
36
+ let l = Math.min(Math.max(0, Number.isFinite(t.index) ? Math.floor(t.index) : 0), a - 1) * o, u = l + o, d = Number.isFinite(r) ? r : 0, f;
37
+ switch (n) {
38
+ case "start":
39
+ f = l;
40
+ break;
41
+ case "end":
42
+ f = u - s;
43
+ break;
44
+ case "center":
45
+ f = l + o / 2 - s / 2;
46
+ break;
47
+ default: f = l < d ? l : u > d + s ? u - s : d;
48
+ }
49
+ return Math.min(Math.max(0, f), c);
50
+ }
51
+ //#endregion
52
+ export { t as computeVirtualLayout, n as scrollOffsetForIndex };
53
+
54
+ //# sourceMappingURL=virtual.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"virtual.mjs","names":["toCount","value","fallback","computeVirtualLayout","input","count","rowSize","itemSize","visibleCount","overscan","first","start","end","extra","keep","index","a","b","runs","push","last","from","to","windowPushed","entries","next","scrollOffsetForIndex","viewport","viewportSize","maxOffset","top","bottom","current","scrollTop","target","align"],"sources":["../../src/lib/virtual.ts"],"sourcesContent":["/**\n * One entry of a windowed list: a row to render, or the empty space that stands in for the rows\n * that are not rendered. A spacer's `key` is stable for React: `spacer-<index of the next item>`,\n * or `spacer-end` for the one after the last item.\n */\nexport type VirtualEntry =\n { kind: 'item'; index: number } | { kind: 'spacer'; size: number; key: string };\n\n/** Input of {@link computeVirtualLayout}. */\nexport interface VirtualLayoutInput {\n /** Number of rows in the whole list. */\n count: number;\n /** Height (or width) of one row, in pixels. */\n itemSize: number;\n /** Index of the first row in view. Clamped to `[0, max(0, count - rows in view)]`. */\n firstVisible: number;\n /** Height (or width) of the scroll viewport, in pixels. */\n viewportSize: number;\n /** Rows rendered beyond each edge of the view. */\n overscan: number;\n /**\n * Indexes that stay rendered outside the window (the focused row, the row a drag holds).\n * Entries that are not integers inside the list are ignored.\n */\n keep?: readonly number[];\n}\n\n/** An index run, `[from, to)`. */\ntype Run = readonly [from: number, to: number];\n\nfunction toCount(value: number, fallback: number): number {\n return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;\n}\n\n/**\n * The rows to render for a scrolled list and the spacers that keep its scroll height: the rows in\n * view with `overscan` rows on both sides, plus every `keep` index as a run of its own. The sizes\n * of the items and the spacers add up to `count * itemSize`; a spacer is never empty.\n */\nexport function computeVirtualLayout(input: VirtualLayoutInput): readonly VirtualEntry[] {\n const { itemSize, keep } = input;\n const count = toCount(input.count, 0);\n if (count === 0) return [];\n\n const rowSize = Number.isFinite(itemSize) && itemSize > 0 ? itemSize : 1;\n const visibleCount = Math.max(\n 1,\n Number.isFinite(input.viewportSize) ? Math.ceil(input.viewportSize / rowSize) : 1,\n );\n const overscan = toCount(input.overscan, 0);\n // A scroller never shows a first row past the last full page: a list that shrank under a\n // scrolled window is clamped the way the browser clamps `scrollTop`.\n const first = Math.min(toCount(input.firstVisible, 0), Math.max(0, count - visibleCount));\n const start = Math.max(0, first - overscan);\n const end = Math.min(count, first + visibleCount + overscan);\n\n // The window and the kept rows outside it, sorted; touching runs merge into one.\n const extra = [...new Set(keep ?? [])]\n .filter((index) => Number.isInteger(index) && index >= 0 && index < count)\n .filter((index) => index < start || index >= end)\n .sort((a, b) => a - b);\n const runs: [number, number][] = [];\n const push = (from: number, to: number) => {\n const last = runs[runs.length - 1];\n if (last && from <= last[1]) last[1] = Math.max(last[1], to);\n else runs.push([from, to]);\n };\n let windowPushed = false;\n for (const index of extra) {\n if (!windowPushed && index > start) {\n push(start, end);\n windowPushed = true;\n }\n push(index, index + 1);\n }\n if (!windowPushed) push(start, end);\n\n const entries: VirtualEntry[] = [];\n let next = 0;\n for (const [from, to] of runs as readonly Run[]) {\n if (from > next)\n entries.push({ kind: 'spacer', size: (from - next) * rowSize, key: `spacer-${from}` });\n for (let index = from; index < to; index++) entries.push({ kind: 'item', index });\n next = to;\n }\n if (next < count)\n entries.push({ kind: 'spacer', size: (count - next) * rowSize, key: 'spacer-end' });\n return entries;\n}\n\n/** Input of {@link scrollOffsetForIndex}. */\nexport interface ScrollOffsetInput {\n index: number;\n /**\n * Where the row ends up: `'start'`, `'center'` or `'end'` of the viewport, or `'auto'`, the\n * smallest move that shows it whole (the current offset when it already is).\n */\n align: 'auto' | 'start' | 'center' | 'end';\n itemSize: number;\n count: number;\n scrollTop: number;\n viewportSize: number;\n}\n\n/** The scroll offset, clamped to the content, that brings row `index` to the requested place. */\nexport function scrollOffsetForIndex(input: ScrollOffsetInput): number {\n const { align, scrollTop, viewportSize } = input;\n const count = toCount(input.count, 0);\n const rowSize = Number.isFinite(input.itemSize) && input.itemSize > 0 ? input.itemSize : 0;\n const viewport = Number.isFinite(viewportSize) ? Math.max(0, viewportSize) : 0;\n const maxOffset = Math.max(0, count * rowSize - viewport);\n if (count === 0 || maxOffset === 0) return 0;\n\n const index = Math.min(\n Math.max(0, Number.isFinite(input.index) ? Math.floor(input.index) : 0),\n count - 1,\n );\n const top = index * rowSize;\n const bottom = top + rowSize;\n const current = Number.isFinite(scrollTop) ? scrollTop : 0;\n\n let target: number;\n switch (align) {\n case 'start':\n target = top;\n break;\n case 'end':\n target = bottom - viewport;\n break;\n case 'center':\n target = top + rowSize / 2 - viewport / 2;\n break;\n default:\n target = top < current ? top : bottom > current + viewport ? bottom - viewport : current;\n }\n return Math.min(Math.max(0, target), maxOffset);\n}\n"],"mappings":"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"}