nexus-shared 3.2.0 → 4.0.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 (187) hide show
  1. package/CHANGELOG.md +1564 -0
  2. package/README.md +1705 -1123
  3. package/dist/Client.Index.d.ts +14 -0
  4. package/dist/Client.Index.js +14 -0
  5. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  6. package/dist/Components/Documents/DocumentFrame.js +83 -0
  7. package/dist/Components/Documents/Menu.d.ts +4 -0
  8. package/dist/Components/Documents/Menu.js +40 -24
  9. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  10. package/dist/Components/Documents/PrintDocument.js +62 -0
  11. package/dist/Components/Documents/Tabs.d.ts +59 -0
  12. package/dist/Components/Documents/Tabs.js +70 -0
  13. package/dist/Components/Forms/ApiForm.js +19 -2
  14. package/dist/Components/Forms/Crud.d.ts +2 -2
  15. package/dist/Components/Forms/Crud.js +576 -243
  16. package/dist/Components/Forms/CrudActions.d.ts +136 -0
  17. package/dist/Components/Forms/CrudActions.js +184 -0
  18. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  19. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  20. package/dist/Components/Forms/CrudParts.d.ts +30 -2
  21. package/dist/Components/Forms/CrudParts.js +55 -5
  22. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  23. package/dist/Components/Forms/CrudRowForms.js +114 -0
  24. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  25. package/dist/Components/Forms/CrudScopes.js +76 -0
  26. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  27. package/dist/Components/Forms/CrudTree.js +363 -0
  28. package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
  29. package/dist/Components/Forms/CrudWorkspace.js +169 -0
  30. package/dist/Components/Forms/ExcelImport.js +3 -3
  31. package/dist/Components/Forms/Form.d.ts +4 -4
  32. package/dist/Components/Forms/Form.js +123 -17
  33. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  34. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  35. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  36. package/dist/Components/Forms/RecordFlows.js +282 -0
  37. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  38. package/dist/Components/Forms/SubmitForm.js +195 -10
  39. package/dist/Components/Inputs/CheckBox.js +1 -1
  40. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  41. package/dist/Components/Inputs/ChoiceField.js +28 -2
  42. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  43. package/dist/Components/Inputs/Dropdown.js +5 -5
  44. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  45. package/dist/Components/Inputs/DropdownParts.js +16 -4
  46. package/dist/Components/Inputs/Field.d.ts +7 -1
  47. package/dist/Components/Inputs/Field.js +2 -2
  48. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  49. package/dist/Components/Inputs/GroupForm.js +10 -11
  50. package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
  51. package/dist/Components/Inputs/InputRenderer.js +37 -32
  52. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  53. package/dist/Components/Inputs/PictureInput.js +70 -0
  54. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  55. package/dist/Components/Inputs/RadioGroup.js +8 -3
  56. package/dist/Components/Inputs/RowsInput.d.ts +1 -1
  57. package/dist/Components/Inputs/RowsInput.js +18 -3
  58. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  59. package/dist/Components/Inputs/TabularForm.js +29 -29
  60. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  61. package/dist/Components/Inputs/TextArea.js +11 -2
  62. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  63. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  64. package/dist/Components/Inputs/TreeView.d.ts +53 -2
  65. package/dist/Components/Inputs/TreeView.js +147 -13
  66. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  67. package/dist/Components/Layouts/MessageBox.js +56 -7
  68. package/dist/Components/Layouts/Popup.d.ts +13 -6
  69. package/dist/Components/Layouts/Popup.js +37 -9
  70. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  71. package/dist/Components/Layouts/PopupDock.js +2 -1
  72. package/dist/Components/Layouts/PopupHost.js +5 -4
  73. package/dist/Components/Viewers/DataTable.js +150 -48
  74. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  75. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  76. package/dist/Components/Viewers/DataTableParts.d.ts +21 -5
  77. package/dist/Components/Viewers/DataTableParts.js +26 -14
  78. package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
  79. package/dist/Components/Viewers/OwnerFiles.js +589 -0
  80. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  81. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  82. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  83. package/dist/Components/Viewers/RecordCards.js +509 -0
  84. package/dist/Helpers/BusyHooks.d.ts +6 -0
  85. package/dist/Helpers/BusyHooks.js +39 -0
  86. package/dist/Helpers/CrudBackend.d.ts +20 -2
  87. package/dist/Helpers/CrudBackend.js +46 -2
  88. package/dist/Helpers/CrudColumns.d.ts +53 -6
  89. package/dist/Helpers/CrudColumns.js +98 -7
  90. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  91. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  92. package/dist/Helpers/CrudHelpers.d.ts +81 -2
  93. package/dist/Helpers/CrudHelpers.js +195 -0
  94. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  95. package/dist/Helpers/CrudNarrowPick.js +157 -0
  96. package/dist/Helpers/CrudRowForms.d.ts +15 -0
  97. package/dist/Helpers/CrudRowForms.js +43 -0
  98. package/dist/Helpers/CrudScopes.d.ts +32 -0
  99. package/dist/Helpers/CrudScopes.js +79 -0
  100. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  101. package/dist/Helpers/DropdownHelpers.js +6 -2
  102. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  103. package/dist/Helpers/ExcelHelpers.js +12 -11
  104. package/dist/Helpers/FileAdapter.d.ts +78 -0
  105. package/dist/Helpers/FileAdapter.js +102 -0
  106. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  107. package/dist/Helpers/FilePrimaries.js +138 -0
  108. package/dist/Helpers/FormConfirm.d.ts +32 -0
  109. package/dist/Helpers/FormConfirm.js +40 -0
  110. package/dist/Helpers/FormStore.d.ts +90 -5
  111. package/dist/Helpers/FormStore.js +318 -22
  112. package/dist/Helpers/InputConditions.d.ts +50 -0
  113. package/dist/Helpers/InputConditions.js +168 -0
  114. package/dist/Helpers/InputMemory.d.ts +106 -0
  115. package/dist/Helpers/InputMemory.js +385 -0
  116. package/dist/Helpers/InputNarrowing.d.ts +18 -0
  117. package/dist/Helpers/InputNarrowing.js +67 -0
  118. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  119. package/dist/Helpers/InputParamsHelpers.js +157 -74
  120. package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
  121. package/dist/Helpers/NexusDriveAdapter.js +207 -0
  122. package/dist/Helpers/NumberHelpers.d.ts +95 -0
  123. package/dist/Helpers/NumberHelpers.js +126 -0
  124. package/dist/Helpers/Permissions.js +2 -1
  125. package/dist/Helpers/QrCode.d.ts +26 -0
  126. package/dist/Helpers/QrCode.js +463 -0
  127. package/dist/Helpers/RecordCards.d.ts +205 -0
  128. package/dist/Helpers/RecordCards.js +378 -0
  129. package/dist/Helpers/RequestCache.d.ts +14 -2
  130. package/dist/Helpers/RequestCache.js +22 -3
  131. package/dist/Helpers/RowsStore.d.ts +37 -2
  132. package/dist/Helpers/RowsStore.js +128 -10
  133. package/dist/Helpers/ServerPreview.d.ts +69 -0
  134. package/dist/Helpers/ServerPreview.js +99 -0
  135. package/dist/Helpers/TableColumns.js +4 -3
  136. package/dist/Helpers/TableExport.d.ts +7 -1
  137. package/dist/Helpers/TableExport.js +9 -3
  138. package/dist/Helpers/TableFilters.d.ts +1 -1
  139. package/dist/Helpers/TableFilters.js +24 -23
  140. package/dist/Helpers/TableHelpers.d.ts +23 -2
  141. package/dist/Helpers/TableHelpers.js +51 -10
  142. package/dist/Helpers/TableJoins.d.ts +13 -0
  143. package/dist/Helpers/TableJoins.js +34 -0
  144. package/dist/Helpers/TableStore.d.ts +21 -1
  145. package/dist/Helpers/TableStore.js +64 -7
  146. package/dist/Helpers/TreeStore.d.ts +81 -0
  147. package/dist/Helpers/TreeStore.js +365 -0
  148. package/dist/Interfaces/CrudInterfaces.d.ts +465 -5
  149. package/dist/Interfaces/FileInterfaces.d.ts +162 -0
  150. package/dist/Interfaces/FileInterfaces.js +4 -0
  151. package/dist/Interfaces/FormInterfaces.d.ts +391 -30
  152. package/dist/Interfaces/FormInterfaces.js +79 -1
  153. package/dist/Interfaces/InputInterfaces.d.ts +15 -3
  154. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  155. package/dist/Interfaces/RecordInterfaces.js +1 -0
  156. package/dist/Interfaces/TableInterfaces.d.ts +74 -1
  157. package/dist/Services/ExcelApi.js +5 -0
  158. package/dist/Services/FileApi.d.ts +13 -0
  159. package/dist/Services/FileApi.js +29 -0
  160. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  161. package/dist/Services/ImageEditorLoader.js +51 -0
  162. package/dist/Services/JoinSources.d.ts +37 -5
  163. package/dist/Services/JoinSources.js +90 -14
  164. package/dist/Services/ModuleImport.d.ts +7 -0
  165. package/dist/Services/ModuleImport.js +22 -0
  166. package/dist/Shared.Index.d.ts +16 -0
  167. package/dist/Shared.Index.js +16 -0
  168. package/package.json +7 -2
  169. package/src/Styles/Nexus.Avatar.css +6 -5
  170. package/src/Styles/Nexus.Button.css +10 -4
  171. package/src/Styles/Nexus.Choice.css +16 -0
  172. package/src/Styles/Nexus.Crud.css +383 -9
  173. package/src/Styles/Nexus.Dropdown.css +8 -0
  174. package/src/Styles/Nexus.Files.css +410 -0
  175. package/src/Styles/Nexus.Form.css +5 -2
  176. package/src/Styles/Nexus.Index.css +4 -0
  177. package/src/Styles/Nexus.Input.css +42 -0
  178. package/src/Styles/Nexus.Menu.css +10 -1
  179. package/src/Styles/Nexus.Popup.css +77 -1
  180. package/src/Styles/Nexus.Print.css +288 -0
  181. package/src/Styles/Nexus.QrCode.css +12 -0
  182. package/src/Styles/Nexus.Record.css +430 -0
  183. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  184. package/src/Styles/Nexus.Table.css +67 -5
  185. package/src/Styles/Nexus.Themes.css +12 -6
  186. package/src/Styles/Nexus.Tokens.css +8 -3
  187. package/src/Styles/Nexus.Tree.css +31 -1
@@ -36,6 +36,212 @@ export function expireTreePages(cacheKey, parent) {
36
36
  const parents = new Set(parent.map(value => treeKey(value) ?? ""));
37
37
  return treePages.drop(key => key.startsWith(base) && parents.has(key.slice(base.length, key.indexOf("\u0000", base.length))));
38
38
  }
39
+ /**
40
+ * Writes changes made elsewhere into the pages kept for a tree's source, and into every tree on screen that keeps its
41
+ * pages under `cacheKey` - so a record saved in a form, moved, or sent to the trash shows in the tree at once, and
42
+ * leaving the page and coming back reads nothing again for it. Items go where the parent's children are known (see
43
+ * `TreeStore.placeItem`); a parent whose children were never loaded only counts one more or one fewer, and its first page
44
+ * comes from the server when it is opened, as it would have anyway.
45
+ */
46
+ export function writeTreeChanges(cacheKey, changes, rules = {}) {
47
+ if (changes.length === 0)
48
+ return;
49
+ patchTreePages(treePages, cacheKey, changes, rules);
50
+ for (const listener of [...(treeListeners.get(cacheKey) ?? [])])
51
+ listener(changes, rules);
52
+ }
53
+ const treeListeners = new Map();
54
+ /** Hears the changes written for `cacheKey`: what a tree on screen does to write them into itself. Returns the way out. */
55
+ export function subscribeTreeChanges(cacheKey, listener) {
56
+ let set = treeListeners.get(cacheKey);
57
+ if (!set)
58
+ treeListeners.set(cacheKey, (set = new Set()));
59
+ set.add(listener);
60
+ return () => {
61
+ set.delete(listener);
62
+ if (set.size === 0 && treeListeners.get(cacheKey) === set)
63
+ treeListeners.delete(cacheKey);
64
+ };
65
+ }
66
+ /**
67
+ * The kept pages under `cacheKey` with the changes written in (what `writeTreeChanges` does to `treePages`). A parent's
68
+ * pages are read as one list from its first child, the changes applied to it, and the list written back in pages of the
69
+ * same size, each with the parent's total - so a tree reading them again, page by page, asks the server for exactly the
70
+ * children it does not have. A page still on its way for a changed parent is dropped: its answer may predate the change.
71
+ */
72
+ export function patchTreePages(cache, cacheKey, changes, rules = {}) {
73
+ const valueKey = rules.valueKey ?? TREE_DEFAULT_KEYS.value;
74
+ const keyOf = (item) => treeKey(read(item, valueKey));
75
+ const groups = keptChildren(cache, cacheKey);
76
+ const of = (parent) => groups.filter(group => group.parent === parent);
77
+ /** A parent's count, on its own item, wherever that item is kept. */
78
+ const count = (parent, delta) => {
79
+ if (parent === "" || rules.hasChildrenKey === undefined)
80
+ return;
81
+ const field = rules.hasChildrenKey;
82
+ const known = of(parent).find(group => group.total !== undefined)?.total;
83
+ for (const group of groups) {
84
+ const index = group.items.findIndex(item => keyOf(item) === parent);
85
+ if (index < 0)
86
+ continue;
87
+ const item = group.items[index];
88
+ const next = countedChildren(read(item, field), delta, known);
89
+ if (next === undefined)
90
+ continue;
91
+ group.items[index] = { ...item, [field]: next };
92
+ group.changed = true;
93
+ }
94
+ };
95
+ for (const change of changes) {
96
+ if (change.type === "put") {
97
+ const key = keyOf(change.item);
98
+ if (key === null)
99
+ continue;
100
+ const parent = treeKey(change.parent) ?? "";
101
+ let from = change.before === false ? false : change.before === undefined ? undefined : (treeKey(change.before) ?? "");
102
+ // Out of the list that holds it, remembering where, for an item that stays where it was.
103
+ const held = new Map();
104
+ for (const group of groups) {
105
+ const index = group.items.findIndex(item => keyOf(item) === key);
106
+ if (index < 0)
107
+ continue;
108
+ from ??= group.parent;
109
+ group.items.splice(index, 1);
110
+ group.changed = true;
111
+ held.set(group, index);
112
+ }
113
+ const moved = typeof from === "string" && from !== parent;
114
+ if (moved) {
115
+ for (const group of of(from)) {
116
+ if (group.total !== undefined)
117
+ group.total = Math.max(0, group.total - 1);
118
+ group.changed = true;
119
+ }
120
+ }
121
+ for (const group of of(parent)) {
122
+ if (!group.listed)
123
+ continue;
124
+ const at = rules.compare ? insertionIndex(group.items, change.item, rules.compare) : Math.min(held.get(group) ?? group.items.length, group.items.length);
125
+ // After the children kept while more are to come: it is in a page not read yet.
126
+ if (at < group.items.length || !group.hasMore)
127
+ group.items.splice(at, 0, change.item);
128
+ if ((moved || from === false) && group.total !== undefined)
129
+ group.total++;
130
+ group.changed = true;
131
+ }
132
+ if (moved)
133
+ count(from, -1);
134
+ if (moved || from === false)
135
+ count(parent, 1);
136
+ }
137
+ else {
138
+ const key = treeKey(change.value);
139
+ if (key === null)
140
+ continue;
141
+ let from = change.parent === undefined ? undefined : (treeKey(change.parent) ?? "");
142
+ let found = false;
143
+ for (const group of groups) {
144
+ const index = group.items.findIndex(item => keyOf(item) === key);
145
+ if (index < 0)
146
+ continue;
147
+ from ??= group.parent;
148
+ found = true;
149
+ group.items.splice(index, 1);
150
+ if (group.total !== undefined)
151
+ group.total = Math.max(0, group.total - 1);
152
+ group.changed = true;
153
+ }
154
+ if (from === undefined)
155
+ continue;
156
+ // Not among the children kept: one of those not read yet, which the total still counted.
157
+ if (!found) {
158
+ for (const group of of(from)) {
159
+ if (group.total !== undefined)
160
+ group.total = Math.max(0, group.total - 1);
161
+ group.changed = true;
162
+ }
163
+ }
164
+ count(from, -1);
165
+ }
166
+ }
167
+ for (const group of groups) {
168
+ if (!group.changed)
169
+ continue;
170
+ const prefix = treePagesKey(cacheKey, group.parent === "" ? null : group.parent);
171
+ const kept = new Set(group.keys);
172
+ cache.drop(key => kept.has(key) || (key.startsWith(prefix) && cache.isLoading(key)));
173
+ // A list emptied while more are to come has nothing to start from: the next open asks the server.
174
+ if (!group.listed || (group.items.length === 0 && group.hasMore))
175
+ continue;
176
+ const hasMore = group.total !== undefined ? group.items.length < group.total : group.hasMore;
177
+ const pages = Math.max(1, Math.ceil(group.items.length / group.take));
178
+ for (let page = 0; page < pages; page++) {
179
+ const items = group.items.slice(page * group.take, (page + 1) * group.take);
180
+ const last = page === pages - 1;
181
+ cache.set(treePageKey(cacheKey, group.parent === "" ? null : group.parent, page * group.take, group.take), { items, total: group.total, hasMore: last ? hasMore : true }, group.at);
182
+ }
183
+ }
184
+ }
185
+ /** The kept pages under `cacheKey`, by parent and page size, each read as one list from the parent's first child. */
186
+ function keptChildren(cache, cacheKey) {
187
+ const base = treePagesKey(cacheKey);
188
+ const found = new Map();
189
+ for (const { key, value, at } of cache.entries(base)) {
190
+ const [parent, skipText, takeText] = key.slice(base.length).split(NUL);
191
+ const skip = Number(skipText);
192
+ const take = Number(takeText);
193
+ if (parent === undefined || !Number.isInteger(skip) || !Number.isInteger(take) || take < 1)
194
+ continue;
195
+ const id = `${parent}${NUL}${take}`;
196
+ let group = found.get(id);
197
+ if (!group)
198
+ found.set(id, (group = { parent, take, pages: [] }));
199
+ group.pages.push({ key, skip, page: value, at });
200
+ }
201
+ const groups = [];
202
+ for (const { parent, take, pages } of found.values()) {
203
+ pages.sort((a, b) => a.skip - b.skip);
204
+ const items = [];
205
+ let total;
206
+ let hasMore = false;
207
+ let at = Infinity;
208
+ for (const { skip, page, at: loadedAt } of pages) {
209
+ // A page that does not follow the last one cannot be placed: the list ends before it.
210
+ if (skip !== items.length)
211
+ break;
212
+ const pageItems = Array.isArray(page?.items) ? page.items : [];
213
+ items.push(...pageItems);
214
+ if (typeof page.total === "number" && Number.isFinite(page.total))
215
+ total = page.total;
216
+ hasMore = pageItems.length === 0 ? false : typeof page.hasMore === "boolean" ? page.hasMore : total !== undefined ? items.length < total : pageItems.length >= take;
217
+ at = Math.min(at, loadedAt);
218
+ if (!hasMore)
219
+ break;
220
+ }
221
+ const listed = pages[0]?.skip === 0;
222
+ groups.push({ parent, take, keys: pages.map(page => page.key), listed, items, total, hasMore, at: Number.isFinite(at) ? at : Math.min(...pages.map(page => page.at)), changed: false });
223
+ }
224
+ return groups;
225
+ }
226
+ /** Where an item goes among siblings in `compare`'s order: before the first that sorts after it. */
227
+ function insertionIndex(items, item, compare) {
228
+ const index = items.findIndex(other => compare(item, other) < 0);
229
+ return index < 0 ? items.length : index;
230
+ }
231
+ /**
232
+ * A parent's has-children field after it gained (1) or lost (-1) a child: a count moves by one, `true` stays true and
233
+ * becomes it, and `false` comes only when the parent's total says none are left. `undefined` when nothing changes.
234
+ */
235
+ function countedChildren(value, delta, total) {
236
+ if (typeof value === "number" && Number.isFinite(value))
237
+ return Math.max(0, value + delta);
238
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value)))
239
+ return Math.max(0, Number(value) + delta);
240
+ if (delta > 0)
241
+ return value === true ? undefined : true;
242
+ return value === true && total === 0 ? false : undefined;
243
+ }
244
+ const NUL = "\u0000";
39
245
  /** A has-children field read as the tree reads it: a boolean, or a count above 0; `undefined` when it says nothing. */
40
246
  export function treeHasChildren(value) {
41
247
  if (typeof value === "boolean")
@@ -697,7 +903,166 @@ export class TreeStore {
697
903
  this.rows = this.visibleRows(this.roots, null);
698
904
  return removed;
699
905
  }
906
+ /**
907
+ * Writes an item where a change made elsewhere left it (see `TreeItemChange`): in place under the same parent (moved
908
+ * among its siblings by `compare`), under its new parent when it moved - with what is loaded under it - and in for the
909
+ * first time when it is new or back. Only where the parent's children are known: given, loaded, or known to be none.
910
+ * Under a parent never opened it waits for that parent's first page, and one that sorts after the loaded children while
911
+ * more are to come waits for its page. A lazy parent's `loaded` and `total` follow, so its next page skips the right
912
+ * number, and so does the count in `hasChildrenKey` on the parent's own item, which its chevron goes by. Returns the
913
+ * item's node where it is now, or `undefined` when this tree does not show it.
914
+ */
915
+ placeItem(item, parentValue, options = {}) {
916
+ const key = treeKey(read(item, this.valueKey));
917
+ if (key === null)
918
+ return undefined;
919
+ const existing = this.byKey.get(key);
920
+ if (existing?.draft)
921
+ return undefined;
922
+ const parentKey = treeKey(parentValue);
923
+ const parent = parentKey === null ? null : this.byKey.get(parentKey);
924
+ // Where it was: its node knows, else the change says - or nobody does, and then no count moves.
925
+ let from;
926
+ if (existing)
927
+ from = existing.parent ? existing.parent.key : null;
928
+ else if (options.before === false)
929
+ from = false;
930
+ else if (options.before !== undefined)
931
+ from = treeKey(options.before);
932
+ const moved = from !== undefined && from !== false && from !== parentKey;
933
+ const valueBefore = this.value();
934
+ const oldParent = existing ? existing.parent : null;
935
+ let oldIndex = -1;
936
+ if (existing) {
937
+ const siblings = oldParent ? oldParent.children : this.roots;
938
+ oldIndex = siblings.indexOf(existing);
939
+ siblings.splice(oldIndex, 1);
940
+ const pages = this.pagesOf(oldParent);
941
+ if (pages && pages.loaded > 0)
942
+ pages.loaded--;
943
+ }
944
+ let node;
945
+ if (parent !== undefined && this.listsChildren(parent)) {
946
+ const siblings = parent ? parent.children : this.roots;
947
+ const pages = this.pagesOf(parent);
948
+ const at = options.compare ? insertionIndex(siblings.map(each => each.item), item, options.compare) : existing && !moved ? Math.min(oldIndex, siblings.length) : siblings.length;
949
+ if (at < siblings.length || !pages?.hasMore) {
950
+ node = existing ?? this.createNode(item, key);
951
+ node.parent = parent;
952
+ siblings.splice(at, 0, node);
953
+ if (pages)
954
+ pages.loaded++;
955
+ if (!existing) {
956
+ node.checked = this.arrivalChecked(node);
957
+ this.byKey.set(key, node);
958
+ }
959
+ }
960
+ }
961
+ if (existing && !node) {
962
+ // It went where this tree has not loaded: out, with everything under it.
963
+ for (const each of preOrder([existing])) {
964
+ this.byKey.delete(each.key);
965
+ this.statusRows.delete(each.key);
966
+ }
967
+ existing.parent = null;
968
+ }
969
+ if (node) {
970
+ node.item = item;
971
+ node.value = read(item, this.valueKey);
972
+ node.label = labelOf(item, this.labelKey);
973
+ node.disabled = this.isDisabled(item);
974
+ // Nothing loaded under it: what the item says about its children counts.
975
+ if (this.lazy && node.children.length === 0 && node.pages && node.pages.status !== "loading")
976
+ node.pages = this.newPages(item);
977
+ for (const each of preOrder([node]))
978
+ each.depth = each.parent ? each.parent.depth + 1 : 0;
979
+ }
980
+ if (moved)
981
+ this.countChild(from, -1);
982
+ if (moved || from === false)
983
+ this.countChild(parentKey, 1);
984
+ // A parent left with no children and nothing more to load closes, and loses its chevron.
985
+ if (oldParent && oldParent.children.length === 0 && !this.canExpand(oldParent))
986
+ oldParent.expanded = false;
987
+ this.recount(preOrder(this.roots));
988
+ // A checked value whose item went out of view waits in the value for it to come back, as after a refresh.
989
+ const valueAfter = new Set(this.value().map(String));
990
+ for (const value of valueBefore)
991
+ if (!valueAfter.has(String(value)))
992
+ this.pending.set(String(value), value);
993
+ this.settleAll();
994
+ this.rows = this.visibleRows(this.roots, null);
995
+ return node;
996
+ }
997
+ /**
998
+ * Takes out an item a change made elsewhere took out of the tree - moved to the trash, deleted - with everything under
999
+ * it. Its parent counts one fewer (its pages' `loaded` and `total`, the count on its item) and loses its chevron when
1000
+ * none are left. An item not loaded here still lowers its parent's counts when `parentValue` names the parent.
1001
+ * Returns the nodes taken out.
1002
+ */
1003
+ removeItem(value, parentValue) {
1004
+ const key = treeKey(value);
1005
+ if (key === null)
1006
+ return [];
1007
+ const node = this.byKey.get(key);
1008
+ if (node?.draft)
1009
+ return [];
1010
+ let removed = [];
1011
+ if (node) {
1012
+ const parent = node.parent;
1013
+ const siblings = parent ? parent.children : this.roots;
1014
+ siblings.splice(siblings.indexOf(node), 1);
1015
+ const pages = this.pagesOf(parent);
1016
+ if (pages && pages.loaded > 0)
1017
+ pages.loaded--;
1018
+ removed = preOrder([node]);
1019
+ for (const each of removed) {
1020
+ this.byKey.delete(each.key);
1021
+ this.statusRows.delete(each.key);
1022
+ this.pending.delete(each.key);
1023
+ }
1024
+ node.parent = null;
1025
+ this.countChild(parent ? parent.key : null, -1);
1026
+ if (parent && parent.children.length === 0 && !this.canExpand(parent))
1027
+ parent.expanded = false;
1028
+ }
1029
+ else if (parentValue !== undefined) {
1030
+ this.countChild(treeKey(parentValue), -1);
1031
+ }
1032
+ else {
1033
+ return [];
1034
+ }
1035
+ this.recount(preOrder(this.roots));
1036
+ this.settleAll();
1037
+ this.rows = this.visibleRows(this.roots, null);
1038
+ return removed;
1039
+ }
700
1040
  // Internals
1041
+ /** Whether a parent's children are known here: given in the items, loaded (all or the first pages), or known to be none. */
1042
+ listsChildren(parent) {
1043
+ const pages = this.pagesOf(parent);
1044
+ return !pages || pages.loaded > 0 || pages.status === "ready";
1045
+ }
1046
+ /**
1047
+ * One child more or fewer under a parent (`null`: the top level): its pages' total, and the count its own item
1048
+ * carries in `hasChildrenKey` - a new copy of the item. A parent never opened reads its children from the new count,
1049
+ * so one that has none left loses its chevron; one whose children were loaded knows already.
1050
+ */
1051
+ countChild(parentKey, delta) {
1052
+ const parent = parentKey === null ? null : this.byKey.get(parentKey);
1053
+ if (parent === undefined)
1054
+ return;
1055
+ const pages = this.pagesOf(parent);
1056
+ if (pages?.total !== undefined)
1057
+ pages.total = Math.max(0, pages.total + delta);
1058
+ if (!parent || this.hasChildrenKey === undefined)
1059
+ return;
1060
+ const next = countedChildren(read(parent.item, this.hasChildrenKey), delta, pages?.total);
1061
+ if (next !== undefined)
1062
+ parent.item = { ...parent.item, [this.hasChildrenKey]: next };
1063
+ if (this.lazy && parent.children.length === 0 && parent.pages?.status === "idle")
1064
+ parent.pages = this.newPages(parent.item);
1065
+ }
701
1066
  createNode(item, key) {
702
1067
  return {
703
1068
  uid: this.nextUid++,