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
@@ -0,0 +1,96 @@
1
+ /*
2
+ * The words of a record's history entry (Components/Forms/CrudHistory.tsx) that are worked out rather than drawn, so Node
3
+ * tests run them, and the app's way of naming the people behind the entries' tokens. Only `setAuditActorNames` and its
4
+ * type are exported from the package.
5
+ */
6
+ /**
7
+ * A token, short. It names nobody and only tells two apart, so its first characters are enough. A token is the same
8
+ * text for one id on every entry this reader is given (the id is encrypted for the reader, not at random).
9
+ */
10
+ export const shortToken = (token) => (token.length > 8 ? `${token.slice(0, 6)}…` : token);
11
+ let actorResolver = null;
12
+ /** What the app answered, by token, for the page's life: a second history asks only for the people it has not seen. */
13
+ const actorNames = new Map();
14
+ /** Tokens the app answered nothing for: not asked again, so a history full of one stranger never asks on every page. */
15
+ const actorUnknown = new Set();
16
+ /**
17
+ * Sets, once for the app, how a record's history (`AuditHistory`) names the person behind an entry its answer names
18
+ * nobody for (no `creatorName`). The history collects the tokens of every page it shows (the first and each "Show
19
+ * more"), asks once per page for those it has had no answer for, and says "by Sita Rai (sita.rai)" for each one
20
+ * answered. A token is the same text for one person on every entry this reader is given, so the app can look it up (its
21
+ * own list of people, or a call to the server that made it). A call that fails keeps the line's "User 3fA9b2…" and is
22
+ * asked again by the next history; a token left out of the answer keeps it for the page's life. Answers are kept for the
23
+ * page's life; `null` stops asking and forgets them.
24
+ *
25
+ * @example
26
+ * setAuditActorNames(async (tokens, signal) => {
27
+ * const answer = await api.post<Record<string, string>>(USERS.names, tokens, { signal, toast: false });
28
+ * return answer.ok ? answer.result : {};
29
+ * });
30
+ */
31
+ export function setAuditActorNames(resolver) {
32
+ actorResolver = resolver;
33
+ actorNames.clear();
34
+ actorUnknown.clear();
35
+ }
36
+ /** The name the app gave for a token, once it has given one. */
37
+ export const knownAuditActorName = (token) => (token ? actorNames.get(token) : undefined);
38
+ /**
39
+ * The tokens of these entries the app is still to be asked for: entries with no name in the answer, whose token has had
40
+ * no answer yet, each token once. Empty without a resolver.
41
+ */
42
+ export function unnamedAuditActors(entries) {
43
+ if (!actorResolver)
44
+ return [];
45
+ const tokens = new Set();
46
+ for (const entry of entries) {
47
+ const token = entry.creatorUserId;
48
+ if (!token || entry.creatorName?.trim() || actorNames.has(token) || actorUnknown.has(token))
49
+ continue;
50
+ tokens.add(token);
51
+ }
52
+ return [...tokens];
53
+ }
54
+ /**
55
+ * Asks the app, in one call, for the names of these entries' unnamed people (`unnamedAuditActors`) and keeps the answer.
56
+ * Resolves `true` when it learnt a name. A failed or aborted call keeps nothing, so a later history asks again.
57
+ */
58
+ export async function resolveAuditActors(entries, signal) {
59
+ const resolver = actorResolver;
60
+ const tokens = unnamedAuditActors(entries);
61
+ if (!resolver || tokens.length === 0)
62
+ return false;
63
+ let answer;
64
+ try {
65
+ answer = await resolver(tokens, signal);
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ // The history went away, or the app set another resolver meanwhile: this answer is not kept.
71
+ if (signal.aborted || resolver !== actorResolver)
72
+ return false;
73
+ let learnt = false;
74
+ for (const token of tokens) {
75
+ const given = answer?.[token];
76
+ const name = typeof given === "string" ? given.trim() : "";
77
+ if (name) {
78
+ actorNames.set(token, name);
79
+ learnt = true;
80
+ }
81
+ else
82
+ actorUnknown.add(token);
83
+ }
84
+ return learnt;
85
+ }
86
+ /**
87
+ * Who made a change, as the entry's line says it: "by Sita Rai" when the history's answer names them (`creatorName`) or
88
+ * the app has named their token (`setAuditActorNames`), else the start of their token ("User 3fA9b2…"), which still
89
+ * tells two people apart; `null` when it says neither.
90
+ */
91
+ export function auditActorText(entry) {
92
+ const name = entry.creatorName?.trim() || knownAuditActorName(entry.creatorUserId);
93
+ if (name)
94
+ return `by ${name}`;
95
+ return entry.creatorUserId ? `User ${shortToken(entry.creatorUserId)}` : null;
96
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Whether to say that something is still under way: `true` only once `busy` has lasted 400ms, and then for at least
3
+ * 300ms, however soon `busy` ends. A status line that reads it says "Loading…" for a slow read and nothing for a quick
4
+ * one. A Crud's details footer and `DocumentFrame` use it.
5
+ */
6
+ export declare function useSlowBusy(busy: boolean): boolean;
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import { useEffect, useRef, useState } from "react";
3
+ /**
4
+ * A read that ends sooner than this says nothing: most backends answer sooner, and a word shown for a frame or two is
5
+ * the flicker this avoids. Longer than a popup's 160ms fade, so the word never lands on a half-faded popup.
6
+ */
7
+ const SLOW_DELAY = 400;
8
+ /** Once the word shows, it stays at least this long, so a read that ends just after the delay does not blink it. */
9
+ const SLOW_HOLD = 300;
10
+ /**
11
+ * Whether to say that something is still under way: `true` only once `busy` has lasted 400ms, and then for at least
12
+ * 300ms, however soon `busy` ends. A status line that reads it says "Loading…" for a slow read and nothing for a quick
13
+ * one. A Crud's details footer and `DocumentFrame` use it.
14
+ */
15
+ export function useSlowBusy(busy) {
16
+ const [said, setSaid] = useState(false);
17
+ const since = useRef(0);
18
+ useEffect(() => {
19
+ if (busy) {
20
+ if (said)
21
+ return;
22
+ const timer = window.setTimeout(() => {
23
+ since.current = performance.now();
24
+ setSaid(true);
25
+ }, SLOW_DELAY);
26
+ return () => window.clearTimeout(timer);
27
+ }
28
+ if (!said)
29
+ return;
30
+ const left = since.current + SLOW_HOLD - performance.now();
31
+ if (left <= 0) {
32
+ setSaid(false);
33
+ return;
34
+ }
35
+ const timer = window.setTimeout(() => setSaid(false), left);
36
+ return () => window.clearTimeout(timer);
37
+ }, [busy, said]);
38
+ return said;
39
+ }
@@ -1,5 +1,5 @@
1
1
  import { type ApiEndpoint, type ApiModuleValue } from "../Interfaces/ApiInterfaces.ts";
2
- import type { AppChatDomain, ChatConversation, ChatDomainInfo, ChatDomainValue, ChatDraft, ChatHistory, ChatHistoryRequest, ChatId, ChatMessage, ChatParticipant, ChatParticipantPage, ChatParticipantQuery, ChatScope, ChatSummary, NewChatRequest } from "../Interfaces/ChatInterfaces.ts";
2
+ import type { AppChatDomain, ChatConversation, ChatDomainInfo, ChatDomainValue, ChatDraft, ChatHistory, ChatHistoryRequest, ChatId, ChatMessage, ChatMessageKind, ChatParticipant, ChatParticipantPage, ChatParticipantQuery, ChatScope, ChatSummary, NewChatRequest } from "../Interfaces/ChatInterfaces.ts";
3
3
  /** The actions a chat needs from a backend. A backend that does not serve one takes that feature off the panel. */
4
4
  export declare const CHAT_ACTIONS: {
5
5
  /** The conversations a record holds, for this reader. */
@@ -106,6 +106,11 @@ export declare class NexusChatBackend<TDomain extends ChatDomainValue = AppChatD
106
106
  protected toConversation(row: Row): ChatConversation;
107
107
  /** One message row. The names are the ones a Nexus contract uses; override this alone for a backend that differs. */
108
108
  protected toMessage(row: Row): ChatMessage;
109
+ /**
110
+ * What a message is, from the backend's `EChatMessageTypes` (0 a chat line, 1 the mail a thread opens with, 2 a
111
+ * notification) or a word. Anything else is a chat line.
112
+ */
113
+ protected toKind(value: unknown): ChatMessageKind;
109
114
  /** One set of counts. */
110
115
  protected toSummary(row: Row): ChatSummary;
111
116
  /** One participant row. */
@@ -125,6 +130,8 @@ export declare function getChatBackend(): ChatBackend<any>;
125
130
  * { domain: EDomainTypes.Leave, key: "leave", label: "Leave request" },
126
131
  * { domain: EDomainTypes.Procurement, key: "procurement", label: "Purchase order" },
127
132
  * ]);
133
+ *
134
+ * A row's `card` shows a message's record as a card read live from its module (`domainCard`, `DomainCard`).
128
135
  */
129
136
  export declare function defineChatDomains<TDomain extends ChatDomainValue>(list: readonly ChatDomainInfo<TDomain>[]): readonly ChatDomainInfo<TDomain>[];
130
137
  /** The registered domains. Empty until an app calls `defineChatDomains`. */
@@ -254,10 +254,23 @@ export class NexusChatBackend extends ChatBackend {
254
254
  })),
255
255
  }),
256
256
  ...(row.isSystem === true && { isSystem: true }),
257
+ ...((row.messageType ?? row.kind) !== undefined && { kind: this.toKind(row.messageType ?? row.kind) }),
257
258
  ...(Array.isArray(row.deliveredTo) && { deliveredTo: row.deliveredTo.map(id).filter((value) => value !== null) }),
258
259
  ...(Array.isArray(row.readBy) && { readBy: row.readBy.map(id).filter((value) => value !== null) }),
259
260
  };
260
261
  }
262
+ /**
263
+ * What a message is, from the backend's `EChatMessageTypes` (0 a chat line, 1 the mail a thread opens with, 2 a
264
+ * notification) or a word. Anything else is a chat line.
265
+ */
266
+ toKind(value) {
267
+ const written = String(value).toLowerCase();
268
+ if (written === "1" || written === "mail")
269
+ return "mail";
270
+ if (written === "2" || written === "notification")
271
+ return "notification";
272
+ return "chat";
273
+ }
261
274
  /** One set of counts. */
262
275
  toSummary(row) {
263
276
  const lastText = text(row.lastMessageText ?? row.lastMessage ?? row.lastBody);
@@ -309,6 +322,8 @@ let domains = [];
309
322
  * { domain: EDomainTypes.Leave, key: "leave", label: "Leave request" },
310
323
  * { domain: EDomainTypes.Procurement, key: "procurement", label: "Purchase order" },
311
324
  * ]);
325
+ *
326
+ * A row's `card` shows a message's record as a card read live from its module (`domainCard`, `DomainCard`).
312
327
  */
313
328
  export function defineChatDomains(list) {
314
329
  const seen = new Set();
@@ -317,6 +332,9 @@ export function defineChatDomains(list) {
317
332
  if (seen.has(key))
318
333
  throw new Error(`Two chat domains have the value ${key}.`);
319
334
  seen.add(key);
335
+ if (info.card && (typeof info.card.load !== "function" || typeof info.card.view !== "function")) {
336
+ throw new Error(`The chat domain ${key}'s card needs a load and a view.`);
337
+ }
320
338
  }
321
339
  domains = list;
322
340
  return list;
@@ -77,6 +77,8 @@ export interface ChatRun {
77
77
  mine: boolean;
78
78
  /** Written by the system: a centered note, with no avatar and no side. */
79
79
  system: boolean;
80
+ /** Notifications (`kind: "notification"`): event lines, with the author's name and no side. */
81
+ notification: boolean;
80
82
  /** The "New messages" line goes before this run. */
81
83
  startsUnread: boolean;
82
84
  messages: readonly ChatMessage[];
@@ -225,10 +225,12 @@ export function groupChatMessages(messages, options) {
225
225
  }
226
226
  const at = Date.parse(message.createdAt);
227
227
  const system = message.isSystem === true;
228
+ const notification = !system && message.kind === "notification";
228
229
  const unread = unreadFrom !== null && sameChatId(message.id, unreadFrom);
229
230
  const joins = run !== null &&
230
231
  !unread &&
231
232
  run.system === system &&
233
+ run.notification === notification &&
232
234
  sameChatId(run.authorId, message.authorId) &&
233
235
  Number.isFinite(at) &&
234
236
  Number.isFinite(run.last) &&
@@ -242,8 +244,9 @@ export function groupChatMessages(messages, options) {
242
244
  authorId: message.authorId,
243
245
  authorName: message.authorName?.trim() || (system ? "" : CHAT_UNKNOWN_AUTHOR),
244
246
  authorAvatar: message.authorAvatar ?? null,
245
- mine: !system && sameChatId(message.authorId, userId),
247
+ mine: !system && !notification && sameChatId(message.authorId, userId),
246
248
  system,
249
+ notification,
247
250
  startsUnread: unread,
248
251
  messages: [message],
249
252
  last: Number.isFinite(at) ? at : 0,
@@ -105,10 +105,19 @@ export declare abstract class CrudBackend {
105
105
  answeredRow<TRow extends TableRow>(result: unknown): Partial<TRow> | null;
106
106
  /**
107
107
  * The key an answer gives in place of the record: `Create` answering the new record's id. `null` when the answer
108
- * is the record itself, or says nothing. With a key the new record goes straight into the list, and where there is
109
- * a `details` endpoint it is read back on its own - one small request, never the whole list again.
108
+ * is the record itself, or says nothing. With a key the new record goes straight into the list, finished by `savedRow`;
109
+ * only a record whose list still cannot be told is read back on its own through `details` - never the whole list again.
110
110
  */
111
111
  answeredKey(result: unknown): TableKey | null;
112
+ /**
113
+ * A record a save wrote - what the form sent, under what the answer said - with the fields only the server keeps
114
+ * filled in as this backend writes them, so nothing has to be read back: a status, the times, the marks. `before` is
115
+ * the record as the page holds it now (an edit; `undefined` for a new one): an edit's form carries its own copy of those
116
+ * fields from when it opened, which a flag or a mark made since has left behind. Default: the record as it is - and a
117
+ * record whose list still cannot be told from it (`listMembership`) is read back once through `details`, where there is
118
+ * one.
119
+ */
120
+ savedRow<TRow extends TableRow>(action: "create" | "edit", record: TRow, before?: TRow): TRow;
112
121
  /**
113
122
  * Whether a record belongs in a list, read from the record itself. It is what lets one answer be written into the
114
123
  * lists that are **not** on screen: a flagged record joins the flagged list, a trashed one leaves the active list
@@ -176,6 +185,15 @@ export declare class NexusCrudBackend extends CrudBackend {
176
185
  answeredRow<TRow extends TableRow>(result: unknown): Partial<TRow> | null;
177
186
  /** `Create` and `Update` answer the record's id: a token when ids are encrypted, the number as text when not. */
178
187
  answeredKey(result: unknown): TableKey | null;
188
+ /**
189
+ * What a Nexus save leaves to the server, written as the server writes it, so nothing is read back (the owner,
190
+ * 2026-10-10: "the creationTime, recordStatus we will provide fake or just right now"). A new record lacking them is
191
+ * Active (every Create writes `NEXUS_ACTIVE_STATUS`), has no marks, was added now and never changed; what its answer
192
+ * said stays. An Update writes none of the status, the marks and the time added, so an edited record has the ones the
193
+ * page holds (`before`) - over its form's older copies - and was changed now. Times are UTC ISO strings, as a date-time
194
+ * column reads them.
195
+ */
196
+ savedRow<TRow extends TableRow>(action: "create" | "edit", record: TRow, before?: TRow): TRow;
179
197
  /**
180
198
  * The bands the owner settled on: under 40 is live, 25–29 of that is flagged, 40–59 is the trash, and 60 and up is
181
199
  * deleted. A row without a `recordStatus` says nothing, and the lists it cannot answer for load again.
@@ -1,4 +1,5 @@
1
1
  import { CRUD_MARK_BITS, hasMark, withMark } from "./CrudHelpers.js";
2
+ import { nowUtcDateTime } from "./DateTimeHelpers.js";
2
3
  const NO_AUDIT_WORDS = { commands: {}, directions: {}, parts: {} };
3
4
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4
5
  export class CrudBackend {
@@ -117,13 +118,26 @@ export class CrudBackend {
117
118
  }
118
119
  /**
119
120
  * The key an answer gives in place of the record: `Create` answering the new record's id. `null` when the answer
120
- * is the record itself, or says nothing. With a key the new record goes straight into the list, and where there is
121
- * a `details` endpoint it is read back on its own - one small request, never the whole list again.
121
+ * is the record itself, or says nothing. With a key the new record goes straight into the list, finished by `savedRow`;
122
+ * only a record whose list still cannot be told is read back on its own through `details` - never the whole list again.
122
123
  */
123
124
  answeredKey(result) {
124
125
  void result;
125
126
  return null;
126
127
  }
128
+ /**
129
+ * A record a save wrote - what the form sent, under what the answer said - with the fields only the server keeps
130
+ * filled in as this backend writes them, so nothing has to be read back: a status, the times, the marks. `before` is
131
+ * the record as the page holds it now (an edit; `undefined` for a new one): an edit's form carries its own copy of those
132
+ * fields from when it opened, which a flag or a mark made since has left behind. Default: the record as it is - and a
133
+ * record whose list still cannot be told from it (`listMembership`) is read back once through `details`, where there is
134
+ * one.
135
+ */
136
+ savedRow(action, record, before) {
137
+ void action;
138
+ void before;
139
+ return record;
140
+ }
127
141
  /**
128
142
  * Whether a record belongs in a list, read from the record itself. It is what lets one answer be written into the
129
143
  * lists that are **not** on screen: a flagged record joins the flagged list, a trashed one leaves the active list
@@ -328,6 +342,36 @@ export class NexusCrudBackend extends CrudBackend {
328
342
  answeredKey(result) {
329
343
  return typeof result === "string" && result !== "" ? result : null;
330
344
  }
345
+ /**
346
+ * What a Nexus save leaves to the server, written as the server writes it, so nothing is read back (the owner,
347
+ * 2026-10-10: "the creationTime, recordStatus we will provide fake or just right now"). A new record lacking them is
348
+ * Active (every Create writes `NEXUS_ACTIVE_STATUS`), has no marks, was added now and never changed; what its answer
349
+ * said stays. An Update writes none of the status, the marks and the time added, so an edited record has the ones the
350
+ * page holds (`before`) - over its form's older copies - and was changed now. Times are UTC ISO strings, as a date-time
351
+ * column reads them.
352
+ */
353
+ savedRow(action, record, before) {
354
+ const row = { ...record };
355
+ const now = nowUtcDateTime();
356
+ const fill = (field, value) => {
357
+ if (value !== undefined && (row[field] === undefined || row[field] === null))
358
+ row[field] = value;
359
+ };
360
+ if (action === "create") {
361
+ fill("recordStatus", NEXUS_ACTIVE_STATUS);
362
+ fill("bookmarkTypes", 0);
363
+ fill("creationTime", now);
364
+ fill("modificationTime", null);
365
+ return row;
366
+ }
367
+ const held = (before ?? {});
368
+ for (const field of ["recordStatus", "bookmarkTypes", "creationTime"]) {
369
+ if (held[field] !== undefined)
370
+ row[field] = held[field];
371
+ }
372
+ row.modificationTime = now;
373
+ return row;
374
+ }
331
375
  /**
332
376
  * The bands the owner settled on: under 40 is live, 25–29 of that is flagged, 40–59 is the trash, and 60 and up is
333
377
  * deleted. A row without a `recordStatus` says nothing, and the lists it cannot answer for load again.
@@ -1,11 +1,24 @@
1
1
  import type { ApiEndpoint } from "../Interfaces/ApiInterfaces.ts";
2
- import type { InputParams } from "../Interfaces/FormInterfaces.ts";
2
+ import { type InputParams } from "../Interfaces/FormInterfaces.ts";
3
3
  import type { TableColumn, TableJoin, TableRow } from "../Interfaces/TableInterfaces.ts";
4
4
  import { type CrudBackend } from "./CrudBackend.ts";
5
+ import { type PriceLadderOptions } from "./NumberHelpers.ts";
5
6
  /** The longest description a record keeps: the owner's rule for every model (the column holds 4000). */
6
7
  export declare const CRUD_DESCRIPTION_MAX = 1000;
8
+ /**
9
+ * The fields a record keeps its price's figures in (`crudInputs.netAmount`, `crudInputs.lineAmount`,
10
+ * `crudColumns.amounts`). Defaults: `price`, `discountPercentage` and `vatPercentage`, no quantity (one of it), and no
11
+ * `other`: an amount added whole after VAT, such as a line's carriage (`otherCostAmount`), rounded to 2 places.
12
+ */
13
+ export type PriceKeys = {
14
+ price?: string;
15
+ quantity?: string;
16
+ discount?: string;
17
+ vat?: string;
18
+ other?: string;
19
+ };
7
20
  export declare const crudColumns: {
8
- /** The record's status in words ("Active", "Flagged", "Trashed"), from the backend's own numbers; blank until a new record is read back. */
21
+ /** The record's status in words ("Active", "Flagged", "Trashed"), from the backend's own numbers; blank for a record that says none. */
9
22
  status<TRow extends TableRow>(changes?: Partial<TableColumn<TRow>>, backend?: CrudBackend): TableColumn<TRow>;
10
23
  /** The description, as Markdown: text in the table, formatted in the details. */
11
24
  description<TRow extends TableRow>(changes?: Partial<TableColumn<TRow>>): TableColumn<TRow>;
@@ -16,11 +29,14 @@ export declare const crudColumns: {
16
29
  /**
17
30
  * Another record's name the **database** joined into the row: the row carries the name (`key`) and that record's own
18
31
  * status (`status`), and the cell shows the name with a short tag when the record is no longer available ("Trashed",
19
- * "Deleted": `CrudBackend.unavailableTag`). One call, nothing looked up.
32
+ * "Deleted": `CrudBackend.unavailableTag`). One call, nothing looked up. Give it `from` - the field holding that
33
+ * record's key, an input of the form: a save answers what was sent, not the names the database joins, so a CRUD names
34
+ * it again from the record picked for the key, with no call (`crudJoinedNames`). Without `from` a CRUD reads a new
35
+ * record back once (`details`) to name it.
20
36
  *
21
- * @example crudColumns.dbJoin({ key: "regionName", label: "Region", status: "regionRecordStatus" })
37
+ * @example crudColumns.dbJoin({ key: "regionName", label: "Region", status: "regionRecordStatus", from: "regionId" })
22
38
  */
23
- dbJoin<TRow extends TableRow>({ status, ...column }: DbJoinColumn<TRow>, backend?: CrudBackend): TableColumn<TRow>;
39
+ dbJoin<TRow extends TableRow>({ status, from, ...column }: DbJoinColumn<TRow>, backend?: CrudBackend): TableColumn<TRow>;
24
40
  /**
25
41
  * Another record's name the **table** joins: the row carries only that record's key (`from`), and the table names it
26
42
  * from the other controller's `findListing` - every key the rows hold in one call, kept for every table and form on the
@@ -30,13 +46,51 @@ export declare const crudColumns: {
30
46
  * @example crudColumns.clientJoin(SUB_REGIONS, { key: "subRegionName", label: "Sub-region", from: "subRegionId" })
31
47
  */
32
48
  clientJoin<TRow extends TableRow>(api: JoinController, { from, field, ...column }: ClientJoinColumn<TRow>, backend?: CrudBackend): TableColumn<TRow>;
49
+ /**
50
+ * A price's figures under one band, "Amount details" (2026-06-20's): **Price** (money), **Discount** and **VAT**
51
+ * (percents), and the **Calculated amount** - the price less the discount, plus VAT on the rest, worked out from the
52
+ * row's own figures by the server's rule (`priceAmounts`), so a row a save just answered shows it at once. A page
53
+ * spreads them where its price columns go: `...crudColumns.amounts()`. The Calculated amount's key is `netAmount`.
54
+ *
55
+ * @example const COLUMNS = [crudColumns.dbJoin({ ... }), ...crudColumns.amounts(), crudColumns.status()];
56
+ */
57
+ amounts<TRow extends TableRow>(keys?: PriceKeys, changes?: {
58
+ group?: string;
59
+ }): TableColumn<TRow>[];
60
+ /**
61
+ * A receipt's money ladder as columns for a record's details (`CrudDetails`), in one section (`group`, default
62
+ * "Money"): Rate, Amount, Discount (5%), VAT (13%) - each left out when it is none - and the Calculated amount, money
63
+ * with 2 places, each read from the row's own field (`priceLadder`, on the server's `CounterAmounts` names unless
64
+ * `keys` says otherwise). Pass the person's number locale, so the percentages are written as they read numbers.
65
+ *
66
+ * @example ...crudColumns.priceLadder(line, { locale: useNumberLocale() })
67
+ */
68
+ priceLadder<TRow extends TableRow>(row: Readonly<Record<string, unknown>>, options?: PriceLadderOptions & {
69
+ group?: string;
70
+ }): TableColumn<TRow>[];
33
71
  };
34
- /** What `crudColumns.dbJoin` takes: a column, and the row's field that holds the joined record's status. */
72
+ /** What `crudColumns.dbJoin` takes: a column, the row's field that holds the joined record's status, and the one with its key. */
35
73
  export type DbJoinColumn<TRow extends TableRow> = Partial<TableColumn<TRow>> & Pick<TableColumn<TRow>, "key" | "label"> & {
36
74
  status: string;
75
+ from?: string;
37
76
  };
38
77
  /** What `crudColumns.clientJoin` takes: a column, the row's field that holds the other record's key, and its own field. */
39
78
  export type ClientJoinColumn<TRow extends TableRow> = Partial<TableColumn<TRow>> & Pick<TableColumn<TRow>, "key" | "label"> & Pick<TableJoin, "from" | "field">;
79
+ /** How `crudInputs.netAmount` works: whether it can be typed in, the price's places, and whether discount and VAT count. */
80
+ export interface NetAmountOptions {
81
+ /** The figure can be typed in, setting the price that comes to it (`priceFromNet`). Default `true`. */
82
+ editable?: boolean;
83
+ /** The places the price input keeps (its `decimals`), so the price set is one it can hold. Default 2. */
84
+ priceDecimals?: number;
85
+ /**
86
+ * Whether the discount and VAT count, asked with the form's (or the rows') `computeWith` and the values: a receipt's
87
+ * lines are discounted and taxed only for a purchase, not a donation. `false` counts both as 0, in the figure and when
88
+ * it is typed in. Default: they always count.
89
+ *
90
+ * @example crudInputs.lineAmount({}, {}, { taxed: (receipt: { purchase: boolean } | undefined) => receipt?.purchase !== false })
91
+ */
92
+ taxed?: (withValue: any, values: Readonly<Record<string, unknown>>) => boolean;
93
+ }
40
94
  /** The controller a client join names its records from: any with a `findListing` (`MODULES.controller(...)`). */
41
95
  export interface JoinController {
42
96
  findListing: ApiEndpoint;
@@ -48,4 +102,35 @@ export declare const crudInputs: {
48
102
  * in a form, such as `span`.
49
103
  */
50
104
  description(changes?: Partial<InputParams>): InputParams;
105
+ /**
106
+ * The **Calculated amount** in a price's form: a box after Price, Discount and VAT that follows them as they are typed
107
+ * (`compute`), by the server's rule (`priceAmounts`) - never sent and never a change of its own. Blank while the price
108
+ * is empty, so a cleared price never reads 0.00. Span 3, so Price | Discount | VAT | Calculated amount fill a row (two
109
+ * pairs on a narrow form).
110
+ *
111
+ * **It can be typed in** (2026-06-20's, the owner's 2026-11-10 ask): a person who knows the final figure (2,500.00) and
112
+ * the discount and VAT types it, and on leaving the box the price is set to the one that comes to it exactly by the
113
+ * server's rule (`priceFromNet`: 2,458.21 at 10 % and 13 %), or, when no price does, to the nearest, and the line under
114
+ * the box says so. The price is set as if the person typed it, so its rules, the unsaved-changes state and Save see it.
115
+ * `options.editable: false` keeps it read-only; `options.priceDecimals` is the price input's places (default 2).
116
+ *
117
+ * `changes` may replace any key, `compute` included: a counter's box shows the server's preview instead
118
+ * (`compute: { from: [], value: (_values, line) => line?.netAmount }` with the form's `computeWith`), which is read-only,
119
+ * since that `compute` has no `solve`.
120
+ *
121
+ * @example INPUTS = [..., price, discountPercentage, vatPercentage, crudInputs.netAmount()]
122
+ * @example crudInputs.netAmount({ price: "unitPrice", quantity: "quantity" }, { span: 4 }, { priceDecimals: 4 })
123
+ */
124
+ netAmount(keys?: PriceKeys, changes?: Partial<InputParams>, options?: NetAmountOptions): InputParams;
125
+ /**
126
+ * The **Calculated amount** of a line in a table of lines (a receipt's, an invoice's): `netAmount` on the line's own
127
+ * figures - the rate (`unitPrice`) times the `quantity`, less the discount, plus VAT, plus any `other` amount - worked
128
+ * out on each row as it is typed, sized as a table column (`width`, no growing). **It can be typed in**: a line's total
129
+ * typed in sets that line's rate to the one that comes to it exactly by the server's rule (`priceFromNet`, per row), or
130
+ * the nearest, and the line under the table says which. The rows hold no value under its name, so they never send it.
131
+ * `keys`, `changes` and `options` as `netAmount`'s.
132
+ *
133
+ * @example inputs: [item, quantity, unitPrice, discountPercentage, vatPercentage, otherCostAmount, crudInputs.lineAmount({ other: "otherCostAmount" })]
134
+ */
135
+ lineAmount(keys?: PriceKeys, changes?: Partial<InputParams>, options?: NetAmountOptions): InputParams;
51
136
  };