nexus-shared 3.2.0 → 4.1.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 (271) hide show
  1. package/CHANGELOG.md +2359 -0
  2. package/README.md +1911 -1123
  3. package/dist/Client.Index.d.ts +27 -0
  4. package/dist/Client.Index.js +27 -0
  5. package/dist/Components/Chats/ChatComposer.d.ts +7 -1
  6. package/dist/Components/Chats/ChatComposer.js +9 -6
  7. package/dist/Components/Chats/ChatParts.d.ts +15 -2
  8. package/dist/Components/Chats/ChatParts.js +15 -4
  9. package/dist/Components/Chats/ChatPopup.d.ts +46 -0
  10. package/dist/Components/Chats/ChatPopup.js +34 -0
  11. package/dist/Components/Chats/ChatThread.d.ts +9 -1
  12. package/dist/Components/Chats/ChatThread.js +54 -6
  13. package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
  14. package/dist/Components/Dashboards/DashboardBoard.js +594 -0
  15. package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
  16. package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
  17. package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
  18. package/dist/Components/Dashboards/WidgetFrame.js +45 -0
  19. package/dist/Components/Documents/Button.d.ts +6 -1
  20. package/dist/Components/Documents/Button.js +2 -2
  21. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  22. package/dist/Components/Documents/DocumentFrame.js +83 -0
  23. package/dist/Components/Documents/Menu.d.ts +4 -0
  24. package/dist/Components/Documents/Menu.js +40 -24
  25. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  26. package/dist/Components/Documents/PrintDocument.js +62 -0
  27. package/dist/Components/Documents/TabButtons.d.ts +6 -0
  28. package/dist/Components/Documents/TabButtons.js +5 -4
  29. package/dist/Components/Documents/Tabs.d.ts +59 -0
  30. package/dist/Components/Documents/Tabs.js +70 -0
  31. package/dist/Components/Domains/DomainCard.d.ts +98 -0
  32. package/dist/Components/Domains/DomainCard.js +182 -0
  33. package/dist/Components/Forms/ApiForm.js +24 -3
  34. package/dist/Components/Forms/Crud.d.ts +2 -2
  35. package/dist/Components/Forms/Crud.js +628 -247
  36. package/dist/Components/Forms/CrudActions.d.ts +137 -0
  37. package/dist/Components/Forms/CrudActions.js +214 -0
  38. package/dist/Components/Forms/CrudHistory.d.ts +6 -1
  39. package/dist/Components/Forms/CrudHistory.js +14 -6
  40. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  41. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  42. package/dist/Components/Forms/CrudParts.d.ts +37 -2
  43. package/dist/Components/Forms/CrudParts.js +123 -7
  44. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  45. package/dist/Components/Forms/CrudRowForms.js +174 -0
  46. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  47. package/dist/Components/Forms/CrudScopes.js +76 -0
  48. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  49. package/dist/Components/Forms/CrudTree.js +363 -0
  50. package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
  51. package/dist/Components/Forms/CrudWorkspace.js +177 -0
  52. package/dist/Components/Forms/ExcelImport.js +3 -3
  53. package/dist/Components/Forms/Form.d.ts +6 -5
  54. package/dist/Components/Forms/Form.js +134 -21
  55. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  56. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  57. package/dist/Components/Forms/FormQuestion.d.ts +6 -0
  58. package/dist/Components/Forms/FormQuestion.js +28 -0
  59. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  60. package/dist/Components/Forms/RecordFlows.js +282 -0
  61. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  62. package/dist/Components/Forms/SubmitForm.js +182 -10
  63. package/dist/Components/Inputs/CheckBox.js +1 -1
  64. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  65. package/dist/Components/Inputs/ChoiceField.js +28 -2
  66. package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
  67. package/dist/Components/Inputs/ComputedInput.js +179 -0
  68. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  69. package/dist/Components/Inputs/Dropdown.js +8 -6
  70. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  71. package/dist/Components/Inputs/DropdownParts.js +16 -4
  72. package/dist/Components/Inputs/Field.d.ts +7 -1
  73. package/dist/Components/Inputs/Field.js +2 -2
  74. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  75. package/dist/Components/Inputs/GroupForm.js +12 -12
  76. package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
  77. package/dist/Components/Inputs/InputRenderer.js +37 -32
  78. package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
  79. package/dist/Components/Inputs/MarkdownEditor.js +32 -6
  80. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  81. package/dist/Components/Inputs/PictureInput.js +75 -0
  82. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  83. package/dist/Components/Inputs/RadioGroup.js +8 -3
  84. package/dist/Components/Inputs/RowsInput.d.ts +15 -2
  85. package/dist/Components/Inputs/RowsInput.js +96 -8
  86. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  87. package/dist/Components/Inputs/TabularForm.js +45 -32
  88. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  89. package/dist/Components/Inputs/TextArea.js +11 -2
  90. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  91. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  92. package/dist/Components/Inputs/TreeView.d.ts +76 -2
  93. package/dist/Components/Inputs/TreeView.js +227 -16
  94. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  95. package/dist/Components/Layouts/MessageBox.js +56 -7
  96. package/dist/Components/Layouts/Popup.d.ts +34 -7
  97. package/dist/Components/Layouts/Popup.js +143 -13
  98. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  99. package/dist/Components/Layouts/PopupDock.js +2 -1
  100. package/dist/Components/Layouts/PopupHost.js +5 -4
  101. package/dist/Components/Messages/MailClient.d.ts +50 -0
  102. package/dist/Components/Messages/MailClient.js +410 -0
  103. package/dist/Components/Messages/MailColumns.d.ts +21 -0
  104. package/dist/Components/Messages/MailColumns.js +92 -0
  105. package/dist/Components/Messages/MailCompose.d.ts +46 -0
  106. package/dist/Components/Messages/MailCompose.js +154 -0
  107. package/dist/Components/Messages/MailReader.d.ts +43 -0
  108. package/dist/Components/Messages/MailReader.js +207 -0
  109. package/dist/Components/Messages/MailThread.d.ts +71 -0
  110. package/dist/Components/Messages/MailThread.js +185 -0
  111. package/dist/Components/Messages/MessageBell.d.ts +41 -0
  112. package/dist/Components/Messages/MessageBell.js +76 -0
  113. package/dist/Components/Viewers/DataTable.js +153 -49
  114. package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
  115. package/dist/Components/Viewers/DataTableColumns.js +2 -1
  116. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  117. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  118. package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
  119. package/dist/Components/Viewers/DataTableParts.js +26 -14
  120. package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
  121. package/dist/Components/Viewers/OwnerFiles.js +700 -0
  122. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  123. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  124. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  125. package/dist/Components/Viewers/RecordCards.js +509 -0
  126. package/dist/Helpers/ApiModules.js +4 -0
  127. package/dist/Helpers/AuditEntries.d.ts +50 -0
  128. package/dist/Helpers/AuditEntries.js +96 -0
  129. package/dist/Helpers/BusyHooks.d.ts +6 -0
  130. package/dist/Helpers/BusyHooks.js +39 -0
  131. package/dist/Helpers/ChatBackend.d.ts +8 -1
  132. package/dist/Helpers/ChatBackend.js +18 -0
  133. package/dist/Helpers/ChatHelpers.d.ts +2 -0
  134. package/dist/Helpers/ChatHelpers.js +4 -1
  135. package/dist/Helpers/CrudBackend.d.ts +20 -2
  136. package/dist/Helpers/CrudBackend.js +46 -2
  137. package/dist/Helpers/CrudColumns.d.ts +91 -6
  138. package/dist/Helpers/CrudColumns.js +148 -7
  139. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  140. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  141. package/dist/Helpers/CrudHelpers.d.ts +116 -2
  142. package/dist/Helpers/CrudHelpers.js +256 -0
  143. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  144. package/dist/Helpers/CrudNarrowPick.js +157 -0
  145. package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
  146. package/dist/Helpers/CrudPickerPlan.js +46 -0
  147. package/dist/Helpers/CrudRowForms.d.ts +32 -0
  148. package/dist/Helpers/CrudRowForms.js +76 -0
  149. package/dist/Helpers/CrudScopes.d.ts +32 -0
  150. package/dist/Helpers/CrudScopes.js +79 -0
  151. package/dist/Helpers/DashboardHelpers.d.ts +115 -0
  152. package/dist/Helpers/DashboardHelpers.js +305 -0
  153. package/dist/Helpers/DomainCards.d.ts +140 -0
  154. package/dist/Helpers/DomainCards.js +370 -0
  155. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  156. package/dist/Helpers/DropdownHelpers.js +6 -2
  157. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  158. package/dist/Helpers/ExcelHelpers.js +12 -11
  159. package/dist/Helpers/FileAdapter.d.ts +158 -0
  160. package/dist/Helpers/FileAdapter.js +173 -0
  161. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  162. package/dist/Helpers/FilePrimaries.js +138 -0
  163. package/dist/Helpers/FormConfirm.d.ts +32 -0
  164. package/dist/Helpers/FormConfirm.js +40 -0
  165. package/dist/Helpers/FormStore.d.ts +100 -5
  166. package/dist/Helpers/FormStore.js +368 -25
  167. package/dist/Helpers/InputConditions.d.ts +56 -0
  168. package/dist/Helpers/InputConditions.js +192 -0
  169. package/dist/Helpers/InputMemory.d.ts +106 -0
  170. package/dist/Helpers/InputMemory.js +395 -0
  171. package/dist/Helpers/InputNarrowing.d.ts +50 -0
  172. package/dist/Helpers/InputNarrowing.js +148 -0
  173. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  174. package/dist/Helpers/InputParamsHelpers.js +157 -74
  175. package/dist/Helpers/MailClientHelpers.d.ts +96 -0
  176. package/dist/Helpers/MailClientHelpers.js +211 -0
  177. package/dist/Helpers/MessageBackend.d.ts +152 -0
  178. package/dist/Helpers/MessageBackend.js +329 -0
  179. package/dist/Helpers/MessageHelpers.d.ts +160 -0
  180. package/dist/Helpers/MessageHelpers.js +573 -0
  181. package/dist/Helpers/MessageUnread.d.ts +95 -0
  182. package/dist/Helpers/MessageUnread.js +245 -0
  183. package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
  184. package/dist/Helpers/NexusDriveAdapter.js +236 -0
  185. package/dist/Helpers/NumberHelpers.d.ts +156 -0
  186. package/dist/Helpers/NumberHelpers.js +242 -0
  187. package/dist/Helpers/PagedPicker.d.ts +42 -0
  188. package/dist/Helpers/PagedPicker.js +85 -0
  189. package/dist/Helpers/Permissions.js +2 -1
  190. package/dist/Helpers/QrCode.d.ts +26 -0
  191. package/dist/Helpers/QrCode.js +463 -0
  192. package/dist/Helpers/RecordCards.d.ts +205 -0
  193. package/dist/Helpers/RecordCards.js +378 -0
  194. package/dist/Helpers/RequestCache.d.ts +14 -2
  195. package/dist/Helpers/RequestCache.js +22 -3
  196. package/dist/Helpers/RowsStore.d.ts +61 -2
  197. package/dist/Helpers/RowsStore.js +203 -16
  198. package/dist/Helpers/ServerPreview.d.ts +69 -0
  199. package/dist/Helpers/ServerPreview.js +99 -0
  200. package/dist/Helpers/TableColumns.js +4 -3
  201. package/dist/Helpers/TableExport.d.ts +7 -1
  202. package/dist/Helpers/TableExport.js +9 -3
  203. package/dist/Helpers/TableFilters.d.ts +1 -1
  204. package/dist/Helpers/TableFilters.js +24 -23
  205. package/dist/Helpers/TableHelpers.d.ts +38 -4
  206. package/dist/Helpers/TableHelpers.js +78 -10
  207. package/dist/Helpers/TableJoins.d.ts +13 -0
  208. package/dist/Helpers/TableJoins.js +34 -0
  209. package/dist/Helpers/TableStore.d.ts +28 -4
  210. package/dist/Helpers/TableStore.js +73 -11
  211. package/dist/Helpers/ToolbarFit.d.ts +23 -0
  212. package/dist/Helpers/ToolbarFit.js +30 -0
  213. package/dist/Helpers/TreeStore.d.ts +156 -6
  214. package/dist/Helpers/TreeStore.js +547 -18
  215. package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
  216. package/dist/Interfaces/ApiInterfaces.js +10 -1
  217. package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
  218. package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
  219. package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
  220. package/dist/Interfaces/DashboardInterfaces.js +1 -0
  221. package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
  222. package/dist/Interfaces/DomainInterfaces.js +1 -0
  223. package/dist/Interfaces/FileInterfaces.d.ts +183 -0
  224. package/dist/Interfaces/FileInterfaces.js +6 -0
  225. package/dist/Interfaces/FormInterfaces.d.ts +460 -32
  226. package/dist/Interfaces/FormInterfaces.js +79 -1
  227. package/dist/Interfaces/InputInterfaces.d.ts +22 -3
  228. package/dist/Interfaces/MailInterfaces.d.ts +192 -0
  229. package/dist/Interfaces/MailInterfaces.js +1 -0
  230. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  231. package/dist/Interfaces/RecordInterfaces.js +1 -0
  232. package/dist/Interfaces/TableInterfaces.d.ts +102 -3
  233. package/dist/Services/ExcelApi.js +5 -0
  234. package/dist/Services/FileApi.d.ts +13 -0
  235. package/dist/Services/FileApi.js +29 -0
  236. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  237. package/dist/Services/ImageEditorLoader.js +51 -0
  238. package/dist/Services/JoinSources.d.ts +79 -6
  239. package/dist/Services/JoinSources.js +132 -21
  240. package/dist/Services/MessageApi.d.ts +18 -0
  241. package/dist/Services/MessageApi.js +64 -0
  242. package/dist/Services/ModuleImport.d.ts +7 -0
  243. package/dist/Services/ModuleImport.js +22 -0
  244. package/dist/Shared.Index.d.ts +28 -0
  245. package/dist/Shared.Index.js +28 -0
  246. package/package.json +13 -2
  247. package/src/Styles/Nexus.Avatar.css +6 -5
  248. package/src/Styles/Nexus.Button.css +39 -4
  249. package/src/Styles/Nexus.Chat.css +114 -22
  250. package/src/Styles/Nexus.Choice.css +16 -0
  251. package/src/Styles/Nexus.Crud.css +486 -21
  252. package/src/Styles/Nexus.Dashboard.css +763 -0
  253. package/src/Styles/Nexus.Domain.css +384 -0
  254. package/src/Styles/Nexus.Dropdown.css +8 -0
  255. package/src/Styles/Nexus.Editor.css +20 -0
  256. package/src/Styles/Nexus.Files.css +437 -0
  257. package/src/Styles/Nexus.Form.css +5 -2
  258. package/src/Styles/Nexus.Index.css +7 -0
  259. package/src/Styles/Nexus.Input.css +73 -0
  260. package/src/Styles/Nexus.Mail.css +1062 -0
  261. package/src/Styles/Nexus.Menu.css +10 -1
  262. package/src/Styles/Nexus.Popup.css +135 -1
  263. package/src/Styles/Nexus.Print.css +288 -0
  264. package/src/Styles/Nexus.QrCode.css +12 -0
  265. package/src/Styles/Nexus.Record.css +430 -0
  266. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  267. package/src/Styles/Nexus.Table.css +1458 -1387
  268. package/src/Styles/Nexus.Theme.Picker.css +1 -0
  269. package/src/Styles/Nexus.Themes.css +12 -6
  270. package/src/Styles/Nexus.Tokens.css +8 -3
  271. package/src/Styles/Nexus.Tree.css +41 -1
@@ -1,6 +1,6 @@
1
1
  import { errorMessage, isAbortError } from "./ApiResponses.js";
2
2
  import { activeFilters } from "./TableFilters.js";
3
- import { DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, MIN_COLUMN_WIDTH, clampPage, moveColumnKey, nextSort, queryRows, readTablePage, rowKeyText } from "./TableHelpers.js";
3
+ import { DEFAULT_MAX_SELECTION, DEFAULT_PAGE_SIZE, MIN_COLUMN_WIDTH, clampPage, columnPin, moveColumnKey, nextSort, queryRows, readTablePage, rowKeyText } from "./TableHelpers.js";
4
4
  /** How long a changed or added row stays highlighted. */
5
5
  export const TABLE_FLASH_MS = 1600;
6
6
  const EMPTY_MAP = new Map();
@@ -19,6 +19,7 @@ function initialState(pageSize) {
19
19
  matches: 0,
20
20
  query: { search: "", sort: null, page: 1, pageSize },
21
21
  selected: EMPTY_MAP,
22
+ picked: null,
22
23
  busy: EMPTY_SET,
23
24
  flashed: EMPTY_SET,
24
25
  hidden: EMPTY_SET,
@@ -103,13 +104,18 @@ export class TableStore {
103
104
  if (changes.selected && changes.selected !== previous.selected)
104
105
  this.options.onSelectionChange?.(this.getSelected());
105
106
  }
106
- /** Puts back what was saved on this device (page size, sort, columns), before the first load. Tells nobody. */
107
- restore(saved) {
107
+ /**
108
+ * Puts back what was saved on this device (page size, sort, columns): before the first load, telling nobody, or, with
109
+ * `notify`, over a table already shown (another key's settings), its rows shown again in them on the first page.
110
+ */
111
+ restore(saved, options = {}) {
108
112
  const query = { ...this.state.query };
109
113
  if (saved.pageSize && saved.pageSize > 0)
110
114
  query.pageSize = saved.pageSize;
111
115
  if (saved.sort !== undefined)
112
116
  query.sort = saved.sort;
117
+ if (options.notify)
118
+ query.page = 1;
113
119
  this.state = {
114
120
  ...this.state,
115
121
  query,
@@ -118,6 +124,8 @@ export class TableStore {
118
124
  widths: saved.widths ?? this.state.widths,
119
125
  pinned: saved.pinned ?? this.state.pinned,
120
126
  };
127
+ if (options.notify)
128
+ this.view();
121
129
  }
122
130
  /* Rows given by the page */
123
131
  /** Shows these rows: every row there is. The table searches, sorts, and pages them. */
@@ -127,7 +135,9 @@ export class TableStore {
127
135
  this.given = rows;
128
136
  this.all = [...rows];
129
137
  this.dropMissingSelection();
130
- this.view({ status: "ready", error: null, refreshing: false });
138
+ const picked = this.state.picked;
139
+ this.view({ status: "ready", error: null, refreshing: false, picked: this.followPicked(this.all, true) });
140
+ this.pickedChanged(picked);
131
141
  }
132
142
  /* Loading */
133
143
  /**
@@ -151,6 +161,9 @@ export class TableStore {
151
161
  this.state = { ...this.state, query: { ...this.state.query, search: "", page: 1 } };
152
162
  if (this.state.selected.size)
153
163
  this.set({ selected: EMPTY_MAP });
164
+ // Another list: the row picked in the one before is not in it.
165
+ if (this.state.picked)
166
+ this.pick(null);
154
167
  }
155
168
  const keepRows = quiet ?? (this.state.status === "ready" && !reset);
156
169
  const query = this.state.query;
@@ -197,6 +210,7 @@ export class TableStore {
197
210
  void this.reload({ quiet: true });
198
211
  return;
199
212
  }
213
+ const picked = this.state.picked;
200
214
  this.set({
201
215
  status: "ready",
202
216
  refreshing: false,
@@ -207,13 +221,17 @@ export class TableStore {
207
221
  offset: (query.page - 1) * query.pageSize,
208
222
  query,
209
223
  selected: this.refreshSelected(page.rows),
224
+ picked: this.followPicked(page.rows, false),
210
225
  });
226
+ this.pickedChanged(picked);
211
227
  return;
212
228
  }
213
229
  this.all = page.rows;
214
230
  this.given = null;
215
231
  this.dropMissingSelection();
216
- this.view({ status: "ready", refreshing: false, error: null });
232
+ const picked = this.state.picked;
233
+ this.view({ status: "ready", refreshing: false, error: null, picked: this.followPicked(this.all, true) });
234
+ this.pickedChanged(picked);
217
235
  }
218
236
  fail(error, seq) {
219
237
  if (seq !== this.seq)
@@ -301,6 +319,9 @@ export class TableStore {
301
319
  */
302
320
  upsert(row, { at = "start", flash = true } = {}) {
303
321
  const key = this.keyOf(row);
322
+ // The picked record's new copy is the one picked now.
323
+ const before = this.state.picked;
324
+ const picked = before && key && this.keyOf(before) === key ? row : before;
304
325
  if (this.options.paging === "server") {
305
326
  const index = this.state.rows.findIndex(candidate => this.keyOf(candidate) === key);
306
327
  const rows = [...this.state.rows];
@@ -315,7 +336,7 @@ export class TableStore {
315
336
  total++;
316
337
  matches++;
317
338
  }
318
- this.set({ rows, total, matches, selected: this.replaceSelected(key, row), status: "ready" });
339
+ this.set({ rows, total, matches, selected: this.replaceSelected(key, row), picked, status: "ready" });
319
340
  }
320
341
  else {
321
342
  const index = this.all.findIndex(candidate => this.keyOf(candidate) === key);
@@ -326,8 +347,9 @@ export class TableStore {
326
347
  else
327
348
  this.all.push(row);
328
349
  this.all = [...this.all];
329
- this.view({ selected: this.replaceSelected(key, row), status: "ready" });
350
+ this.view({ selected: this.replaceSelected(key, row), picked, status: "ready" });
330
351
  }
352
+ this.pickedChanged(before);
331
353
  if (flash && key)
332
354
  this.flash([key]);
333
355
  }
@@ -355,6 +377,9 @@ export class TableStore {
355
377
  for (const key of gone)
356
378
  selected.delete(key);
357
379
  const busy = new Set([...this.state.busy].filter(key => !gone.has(key)));
380
+ // A picked record that left the list is no longer picked.
381
+ const before = this.state.picked;
382
+ const picked = before && gone.has(this.keyOf(before)) ? null : before;
358
383
  if (this.options.paging === "server") {
359
384
  const rows = this.state.rows.filter(row => !gone.has(this.keyOf(row)));
360
385
  const removed = this.state.rows.length - rows.length;
@@ -363,15 +388,18 @@ export class TableStore {
363
388
  total: Math.max(0, this.state.total - removed),
364
389
  matches: Math.max(0, this.state.matches - removed),
365
390
  selected: selected.size === this.state.selected.size ? this.state.selected : selected,
391
+ picked,
366
392
  busy,
367
393
  });
394
+ this.pickedChanged(before);
368
395
  // A short wait lets several removals (a mass action's rows) share one load.
369
396
  clearTimeout(this.reloadTimer);
370
397
  this.reloadTimer = setTimeout(() => void this.reload({ quiet: true }), 60);
371
398
  return;
372
399
  }
373
400
  this.all = this.all.filter(row => !gone.has(this.keyOf(row)));
374
- this.view({ selected: selected.size === this.state.selected.size ? this.state.selected : selected, busy });
401
+ this.view({ selected: selected.size === this.state.selected.size ? this.state.selected : selected, picked, busy });
402
+ this.pickedChanged(before);
375
403
  }
376
404
  /* Selection */
377
405
  /** Selects or unselects rows, up to `maxSelection`. Returns how many rows it could not select. */
@@ -440,6 +468,35 @@ export class TableStore {
440
468
  this.state = { ...this.state, selected: selected.size ? selected : EMPTY_MAP };
441
469
  this.options.onSelectionChange?.(this.getSelected());
442
470
  }
471
+ /* The picked row */
472
+ /**
473
+ * Picks a row (`DataTable` `onPick`), or none: the one row whose own records the page shows beside the table. It
474
+ * follows the record from then on - its newest copy after an upsert, none once it is removed or gone after a load -
475
+ * and every change is told to `onPickChange`.
476
+ */
477
+ pick(row) {
478
+ const next = row !== null && this.keyOf(row) !== "" ? row : null;
479
+ if (next === this.state.picked)
480
+ return;
481
+ this.set({ picked: next });
482
+ this.options.onPickChange?.(next);
483
+ }
484
+ /**
485
+ * The picked row as these rows hold it: its newest copy, or none when they are every row there is (`whole`, a client
486
+ * table) and it is not among them. A server page without it keeps it, since it may be on another page.
487
+ */
488
+ followPicked(rows, whole) {
489
+ const picked = this.state.picked;
490
+ if (!picked)
491
+ return null;
492
+ const key = this.keyOf(picked);
493
+ return rows.find(row => this.keyOf(row) === key) ?? (whole ? null : picked);
494
+ }
495
+ /** Tells the page when a change left another copy of the picked row, or none. */
496
+ pickedChanged(before) {
497
+ if (this.state.picked !== before)
498
+ this.options.onPickChange?.(this.state.picked);
499
+ }
443
500
  /* Rows with an action running, and rows to highlight */
444
501
  setBusy(keys, on) {
445
502
  const busy = new Set(this.state.busy);
@@ -505,12 +562,17 @@ export class TableStore {
505
562
  return;
506
563
  this.set({ widths: NO_WIDTHS });
507
564
  }
508
- /** Keeps a column on the left or the right while the others scroll, or lets it scroll again (`null`). */
565
+ /**
566
+ * Keeps a column on the left or the right while the others scroll, or lets it scroll again (`null`). A choice equal
567
+ * to the column's own `pinned` is no change to remember; unpinning a column its params pin is remembered as `null`,
568
+ * which wins over them.
569
+ */
509
570
  setPinned(key, side) {
510
- if ((this.state.pinned[key] ?? null) === side)
571
+ const own = this.options.columns.find(column => column.key === key)?.pinned ?? null;
572
+ if (columnPin({ key, pinned: own ?? undefined }, this.state.pinned) === side)
511
573
  return;
512
574
  const pinned = { ...this.state.pinned };
513
- if (side === null)
575
+ if (side === own)
514
576
  delete pinned[key];
515
577
  else
516
578
  pinned[key] = side;
@@ -0,0 +1,23 @@
1
+ /** The sizes a toolbar is laid out with, in pixels. */
2
+ export interface ToolbarSizes {
3
+ /** A button's width. */
4
+ tool: number;
5
+ /** A divider's width with its margins. */
6
+ divider: number;
7
+ /** The gap between two items. */
8
+ gap: number;
9
+ /** The "More" button's width. Default `tool`. */
10
+ more?: number;
11
+ }
12
+ /** What a toolbar of a width shows: its items in order, dividers among them, and the tools left for the menu. */
13
+ export interface ToolbarFit<T extends string> {
14
+ shown: (T | "|")[];
15
+ hidden: T[];
16
+ }
17
+ /**
18
+ * The items of a toolbar that fit `width`, in order, and the rest for its "More" menu. All of them when they fit; else
19
+ * as many as fit beside the "More" button, with no divider left at the end of the row and none in the menu.
20
+ *
21
+ * @example fitToolbar(["bold", "italic", "|", "link"], 70, { tool: 28, divider: 11, gap: 2 }) // { shown: ["bold"], hidden: ["italic", "link"] }
22
+ */
23
+ export declare function fitToolbar<T extends string>(items: readonly (T | "|")[], width: number, sizes: ToolbarSizes): ToolbarFit<T>;
@@ -0,0 +1,30 @@
1
+ /*
2
+ * A toolbar that keeps to one row: which of its buttons fit the room it has, and which go into its "More" menu. Pure,
3
+ * so it is checked without a browser; an editor measures its row and its sizes and draws what this answers.
4
+ */
5
+ /**
6
+ * The items of a toolbar that fit `width`, in order, and the rest for its "More" menu. All of them when they fit; else
7
+ * as many as fit beside the "More" button, with no divider left at the end of the row and none in the menu.
8
+ *
9
+ * @example fitToolbar(["bold", "italic", "|", "link"], 70, { tool: 28, divider: 11, gap: 2 }) // { shown: ["bold"], hidden: ["italic", "link"] }
10
+ */
11
+ export function fitToolbar(items, width, sizes) {
12
+ const size = (item) => (item === "|" ? sizes.divider : sizes.tool);
13
+ const all = items.reduce((total, item, index) => total + size(item) + (index > 0 ? sizes.gap : 0), 0);
14
+ if (all <= width)
15
+ return { shown: [...items], hidden: [] };
16
+ const room = width - (sizes.more ?? sizes.tool) - sizes.gap;
17
+ let used = 0;
18
+ let count = 0;
19
+ for (const item of items) {
20
+ const next = size(item) + (count > 0 ? sizes.gap : 0);
21
+ if (used + next > room)
22
+ break;
23
+ used += next;
24
+ count++;
25
+ }
26
+ const shown = items.slice(0, count);
27
+ while (shown.length > 0 && shown[shown.length - 1] === "|")
28
+ shown.pop();
29
+ return { shown, hidden: items.slice(count).filter((item) => item !== "|") };
30
+ }
@@ -38,7 +38,34 @@ export interface TreeStoreOptions<TItem extends TreeItem = TreeItem> {
38
38
  * than 0). Without it, or when the field is empty, a node counts as having children until its first page answers.
39
39
  */
40
40
  hasChildrenKey?: string;
41
+ /** The leaves shown at the start: see `TreeStore.setFilter`. Default: all. */
42
+ filter?: TreeFilterMatch<TItem> | null;
41
43
  }
44
+ /**
45
+ * Which leaves a filtered tree shows (`TreeStore.setFilter`): asked of every item without children. A parent shows when it
46
+ * holds a leaf the filter shows; one whose children are still to load (a lazy tree) shows too, since they may.
47
+ */
48
+ export type TreeFilterMatch<TItem extends TreeItem = TreeItem> = (item: TItem) => boolean;
49
+ /** One tab of a tree's filters (TreeView `filters`): what it is called, and the leaves it shows. */
50
+ export interface TreeFilter<TItem extends TreeItem = TreeItem> {
51
+ /** Unique among the filters: what `defaultFilter`, the kept choice and `onFilterChange` name. */
52
+ id: string;
53
+ /** The tab's text, e.g. "View only". */
54
+ label: string;
55
+ /** The tab's tooltip. */
56
+ title?: string;
57
+ /** The leaves it shows, asked of every item without children. Left out: all of them (an "All" tab). */
58
+ match?: TreeFilterMatch<TItem>;
59
+ }
60
+ /** Where a tree keeps the filter chosen in this browser: per `key` and `viewer`, nothing without a viewer. */
61
+ export declare function treeFilterStorageKey(remember: {
62
+ key: string;
63
+ viewer?: string | null;
64
+ } | undefined): string | null;
65
+ /** The filter on: the one chosen, else the default, else the first; `undefined` without filters. An id no filter has is passed over. */
66
+ export declare function shownTreeFilter<TFilter extends {
67
+ id: string;
68
+ }>(filters: readonly TFilter[] | undefined, chosen: string | null | undefined, defaultId?: string): TFilter | undefined;
42
69
  /** A row that is not an item, under a parent of a lazy tree: its page is on its way, failed (Retry), or more can be loaded. */
43
70
  export type TreeStatusKind = "loading" | "error" | "more";
44
71
  /** Where a lazy parent's children stand: what was asked of the loader, and what it said. */
@@ -78,6 +105,40 @@ export interface TreeChildrenPage<TItem extends TreeItem = TreeItem> {
78
105
  /** Whether another page follows. Default: `total` says so, or the page was full. */
79
106
  hasMore?: boolean;
80
107
  }
108
+ /**
109
+ * A change to one item of a tree's source made somewhere else - a form saved, a record moved to the trash - written into
110
+ * the pages kept for the tree (`writeTreeChanges`) and into every tree on screen that shows them, so nothing is read
111
+ * from the server again for it.
112
+ */
113
+ export type TreeItemChange<TItem extends TreeItem = TreeItem> = {
114
+ /** Added, changed, or moved: the item as it is now, under the item whose value is `parent` (`null`: the top level). */
115
+ type: "put";
116
+ item: TItem;
117
+ parent: TreeValue | null;
118
+ /**
119
+ * Where it was: its parent's value (`null`: the top level), or `false` when it was not in the tree at all - just
120
+ * added, or back from the trash. Left out when not known: a tree that holds the item knows, and the kept pages are
121
+ * searched; an item found nowhere then changes no count.
122
+ */
123
+ before?: TreeValue | null | false;
124
+ } | {
125
+ /** Out of the tree - moved to the trash, deleted: the item's value, and its parent's when known. */
126
+ type: "remove";
127
+ value: TreeValue;
128
+ parent?: TreeValue | null;
129
+ };
130
+ /** How a tree reads its items, for changes written from outside it: the same keys the tree is given. */
131
+ export interface TreeChangeRules<TItem extends TreeItem = TreeItem> {
132
+ /** The item field that holds its value: the tree's `valueKey`. Default `id`. */
133
+ valueKey?: string;
134
+ /** The tree's `hasChildrenKey`: a parent's count there goes up and down with its children, and its chevron with it. */
135
+ hasChildrenKey?: string;
136
+ /**
137
+ * The order of siblings, as the server sorts them, so an item added or changed goes where a fresh read would put it.
138
+ * Without it an added item goes after the children loaded, and a changed one stays where it was.
139
+ */
140
+ compare?: (a: TItem, b: TItem) => number;
141
+ }
81
142
  /** Children's pages kept for every lazy tree on the page that names a `cacheKey`, for 5 minutes: see `RequestCache`. */
82
143
  export declare const treePages: RequestCache<TreeChildrenPage<object>>;
83
144
  /** The start of every key a tree keeps its pages under, or one parent's pages (`null`: the top level). */
@@ -90,6 +151,24 @@ export declare function treePageKey(cacheKey: string, parent: TreeValue | null,
90
151
  * its `refresh()` loads that again.
91
152
  */
92
153
  export declare function expireTreePages(cacheKey: string, parent?: TreeValue | null | readonly (TreeValue | null)[]): number;
154
+ /**
155
+ * Writes changes made elsewhere into the pages kept for a tree's source, and into every tree on screen that keeps its
156
+ * pages under `cacheKey` - so a record saved in a form, moved, or sent to the trash shows in the tree at once, and
157
+ * leaving the page and coming back reads nothing again for it. Items go where the parent's children are known (see
158
+ * `TreeStore.placeItem`); a parent whose children were never loaded only counts one more or one fewer, and its first page
159
+ * comes from the server when it is opened, as it would have anyway.
160
+ */
161
+ export declare function writeTreeChanges<TItem extends TreeItem>(cacheKey: string, changes: readonly TreeItemChange<TItem>[], rules?: TreeChangeRules<TItem>): void;
162
+ type TreeChangeListener = (changes: readonly TreeItemChange[], rules: TreeChangeRules) => void;
163
+ /** Hears the changes written for `cacheKey`: what a tree on screen does to write them into itself. Returns the way out. */
164
+ export declare function subscribeTreeChanges(cacheKey: string, listener: TreeChangeListener): () => void;
165
+ /**
166
+ * The kept pages under `cacheKey` with the changes written in (what `writeTreeChanges` does to `treePages`). A parent's
167
+ * pages are read as one list from its first child, the changes applied to it, and the list written back in pages of the
168
+ * same size, each with the parent's total - so a tree reading them again, page by page, asks the server for exactly the
169
+ * children it does not have. A page still on its way for a changed parent is dropped: its answer may predate the change.
170
+ */
171
+ export declare function patchTreePages<TItem extends TreeItem>(cache: RequestCache<TreeChildrenPage>, cacheKey: string, changes: readonly TreeItemChange<TItem>[], rules?: TreeChangeRules<TItem>): void;
93
172
  /** A has-children field read as the tree reads it: a boolean, or a count above 0; `undefined` when it says nothing. */
94
173
  export declare function treeHasChildren(value: unknown): boolean | undefined;
95
174
  /** The key of the status row under a parent (`null`: the top level), as `TreeNode.key` holds it. */
@@ -137,6 +216,21 @@ export interface TreeNode<TItem extends TreeItem = TreeItem> {
137
216
  * cannot be checked or unchecked as a whole (see `TreeStore.toggle`), and never shows checked unless the value names it.
138
217
  */
139
218
  partial: boolean;
219
+ /**
220
+ * A row of the filtered tree (`TreeStore.setFilter`): a leaf the filter shows, a parent holding one, a draft, or a lazy
221
+ * item whose children are still to load. Always true without a filter.
222
+ */
223
+ shown: boolean;
224
+ /** Children that are `shown`: all of them without a filter. */
225
+ shownChildren: number;
226
+ /**
227
+ * `leaves`, `checkedLeaves`, `openLeaves` and `openChecked` over the leaves the filter shows - what a box, a count and a
228
+ * check of a parent work on while a filter is on. The same numbers without a filter. The value never reads them.
229
+ */
230
+ shownLeaves: number;
231
+ shownChecked: number;
232
+ shownOpenLeaves: number;
233
+ shownOpenChecked: number;
140
234
  /**
141
235
  * Set only on a status row, a row that is not an item: under its `parent` (`null`: the top level) a page is on its
142
236
  * way, failed, or more can be loaded. A status row is in `rows` only - never in `children`, the value, or the items -
@@ -200,7 +294,23 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
200
294
  private openNodes;
201
295
  private openCheckedNodes;
202
296
  private indexCache;
297
+ /** The leaves shown (`setFilter`); `null`: all. */
298
+ private match;
299
+ /** Goes up whenever a node's counts are worked out again, so what is kept from them knows it is old. */
300
+ private counted;
301
+ private shownRoots;
203
302
  constructor(items?: readonly TItem[], options?: TreeStoreOptions<TItem>, value?: readonly TreeValue[]);
303
+ /** Whether a filter is on (`setFilter`). */
304
+ get filtered(): boolean;
305
+ /**
306
+ * Shows only the leaves `match` takes, and the parents that hold one (`null`: every item). Nothing is checked or
307
+ * unchecked by it, and the value is the same whatever filter is on. While one is on, a parent's box, its count, and a
308
+ * check of it - or of "select all" - work on the leaves shown only: checking a parent checks its shown leaves, and a
309
+ * leaf the filter hides keeps its check exactly as it was. Expanded items stay expanded.
310
+ */
311
+ setFilter(match: TreeFilterMatch<TItem> | null): void;
312
+ /** Children of a parent (`null`: the top level) that are rows of the filtered tree: all of them without a filter. */
313
+ shownChildCount(parent: TreeNode<TItem> | null): number;
204
314
  /** Items in the tree, drafts included. */
205
315
  get size(): number;
206
316
  /** The node of a value. */
@@ -228,11 +338,19 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
228
338
  * unloaded items are not in the value - unless the value names it (`valueMode: "checked"`, where it stands for all).
229
339
  */
230
340
  state(node: TreeNode<TItem>): TreeCheckState;
231
- /** Checked boxes and all boxes: leaves with `cascade`, every item without it. Drafts count. */
341
+ /** The state of a parent over all of its leaves, whatever the filter shows. */
342
+ private wholeState;
343
+ private stateOver;
344
+ /** Checked boxes and all boxes: leaves with `cascade`, every item without it. Drafts count. The filter does not narrow it. */
232
345
  counts(): {
233
346
  checked: number;
234
347
  total: number;
235
348
  };
349
+ /** `counts` over what the filter shows: the leaves shown with `cascade`, the rows of items without it. */
350
+ shownCounts(): {
351
+ checked: number;
352
+ total: number;
353
+ };
236
354
  /**
237
355
  * The checked values, in tree order, following `valueMode`. In a lazy tree, values whose items are not loaded yet
238
356
  * (or not settled) follow the others, so a saved value survives until its items come.
@@ -254,16 +372,19 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
254
372
  * silently leave the rest out. The items under it whose own items are all loaded can still be checked one by one.
255
373
  */
256
374
  toggle(node: TreeNode<TItem>): boolean;
257
- /** Checks or unchecks a node; with `cascade`, every item under it that is not locked. Returns whether anything changed. */
375
+ /**
376
+ * Checks or unchecks a node; with `cascade`, every item under it that is not locked - and, while a filter is on, that
377
+ * the filter shows: the hidden ones keep their checks. Returns whether anything changed.
378
+ */
258
379
  setChecked(node: TreeNode<TItem>, checked: boolean): boolean;
259
- /** The state of a "select all" box, over the items that are not locked; `null` when every item is locked. */
380
+ /** The state of a "select all" box, over the items that are not locked (and shown); `null` when every one is locked. */
260
381
  allState(): TreeCheckState | null;
261
382
  /** What a click on a "select all" box does: checks every item that is not locked, or unchecks them all when all are checked. */
262
383
  toggleAll(): boolean;
263
384
  /**
264
- * Checks or unchecks every item that is not locked. Returns whether anything changed. In a lazy tree with `cascade`,
265
- * a node whose items are not all loaded is passed over and the loaded parts under it are changed; values whose
266
- * items are not loaded stay as they are.
385
+ * Checks or unchecks every item that is not locked, and that the filter shows. Returns whether anything changed. In a
386
+ * lazy tree with `cascade`, a node whose items are not all loaded is passed over and the loaded parts under it are
387
+ * changed; values whose items are not loaded stay as they are.
267
388
  */
268
389
  checkAll(checked: boolean): boolean;
269
390
  /** Whether every parent of the node is expanded, so the node is a row. */
@@ -342,6 +463,34 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
342
463
  }): void;
343
464
  /** Removes a node and everything under it. Returns the removed nodes in tree order. */
344
465
  remove(node: TreeNode<TItem>): TreeNode<TItem>[];
466
+ /**
467
+ * Writes an item where a change made elsewhere left it (see `TreeItemChange`): in place under the same parent (moved
468
+ * among its siblings by `compare`), under its new parent when it moved - with what is loaded under it - and in for the
469
+ * first time when it is new or back. Only where the parent's children are known: given, loaded, or known to be none.
470
+ * Under a parent never opened it waits for that parent's first page, and one that sorts after the loaded children while
471
+ * more are to come waits for its page. A lazy parent's `loaded` and `total` follow, so its next page skips the right
472
+ * number, and so does the count in `hasChildrenKey` on the parent's own item, which its chevron goes by. Returns the
473
+ * item's node where it is now, or `undefined` when this tree does not show it.
474
+ */
475
+ placeItem(item: TItem, parentValue: TreeValue | null, options?: {
476
+ before?: TreeValue | null | false;
477
+ compare?: (a: TItem, b: TItem) => number;
478
+ }): TreeNode<TItem> | undefined;
479
+ /**
480
+ * Takes out an item a change made elsewhere took out of the tree - moved to the trash, deleted - with everything under
481
+ * it. Its parent counts one fewer (its pages' `loaded` and `total`, the count on its item) and loses its chevron when
482
+ * none are left. An item not loaded here still lowers its parent's counts when `parentValue` names the parent.
483
+ * Returns the nodes taken out.
484
+ */
485
+ removeItem(value: TreeValue, parentValue?: TreeValue | null): TreeNode<TItem>[];
486
+ /** Whether a parent's children are known here: given in the items, loaded (all or the first pages), or known to be none. */
487
+ private listsChildren;
488
+ /**
489
+ * One child more or fewer under a parent (`null`: the top level): its pages' total, and the count its own item
490
+ * carries in `hasChildrenKey` - a new copy of the item. A parent never opened reads its children from the new count,
491
+ * so one that has none left loses its chevron; one whose children were loaded knows already.
492
+ */
493
+ private countChild;
345
494
  private createNode;
346
495
  /** A lazy node's pages before anything is asked: none to ask for when its item says it has no children. */
347
496
  private newPages;
@@ -400,3 +549,4 @@ export declare class TreeStore<TItem extends TreeItem = TreeItem> {
400
549
  }
401
550
  /** Nodes in tree order: each node, then its children. No recursion, so even a chain 100,000 levels deep works. */
402
551
  export declare function preOrder<TItem extends TreeItem>(nodes: readonly TreeNode<TItem>[]): TreeNode<TItem>[];
552
+ export {};