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,7 @@
1
1
  import { FormStore, type AnyFormStore, type FormStatus } from "../../Helpers/FormStore.ts";
2
2
  import type { DatePreferences } from "../../Interfaces/DateInterfaces.ts";
3
- import type { FormProps, FormValues, InputParams } from "../../Interfaces/FormInterfaces.ts";
3
+ import { type FormProps, type FormValues } from "../../Interfaces/FormInterfaces.ts";
4
+ import { formInputLabel } from "../Inputs/ComputedInput.tsx";
4
5
  /**
5
6
  * A form store for the page to hold: pass it to a form as `form`, then read, watch, set, and submit through it.
6
7
  *
@@ -19,7 +20,7 @@ export declare function useFormStatus(form: AnyFormStore): FormStatus;
19
20
  /** The form's own store, or the one given. */
20
21
  export declare function useFormStore<TValues extends FormValues>(given: FormStore<TValues> | undefined): FormStore<TValues>;
21
22
  /** An input's name in the form's messages: its `fieldName`, its label when it is text, or its name. */
22
- export declare function formInputLabel(params: InputParams | undefined, name?: string): string;
23
+ export { formInputLabel };
23
24
  /** The names a list shows: all of them up to one more than `most` ("and 1 more" says less than the name), else the first `most`. */
24
25
  export declare function namesToShow<T>(names: readonly T[], most?: number): {
25
26
  shown: readonly T[];
@@ -41,9 +42,9 @@ export declare function formatFormTime(ms: number, preferences: DatePreferences)
41
42
  * @example
42
43
  * <Form
43
44
  * inputs={[
44
- * { type: "text", name: "fullName", label: "Full name", required: true, span: 6 },
45
- * { type: "email", name: "email", label: "Email", span: 6 },
46
- * { type: "dropdown", name: "role", label: "Role", options: roles, span: 4 },
45
+ * { type: EInputTypes.Text, name: "fullName", label: "Full name", required: true, span: 6 },
46
+ * { type: EInputTypes.Email, name: "email", label: "Email", span: 6 },
47
+ * { type: EInputTypes.Dropdown, name: "role", label: "Role", options: roles, span: 4 },
47
48
  * ]}
48
49
  * defaultValue={employee}
49
50
  * onValueChange={values => saveFilters(values)}
@@ -8,9 +8,13 @@ import { useInputDesign } from "../../Helpers/UserPreferences.js";
8
8
  import { formatDate, toIsoDate, usesDevanagari } from "../../Helpers/DateHelpers.js";
9
9
  import { useDatePreferences } from "../../Helpers/DatePreferences.js";
10
10
  import { FormStore } from "../../Helpers/FormStore.js";
11
- import { inputFieldName } from "../../Helpers/InputParamsHelpers.js";
11
+ import { withInputConditions } from "../../Helpers/InputConditions.js";
12
+ import { formNarrowingNames, narrowInputParamsBy } from "../../Helpers/InputNarrowing.js";
13
+ import { isBooleanInput, isRowsInput } from "../../Helpers/InputParamsHelpers.js";
12
14
  import { formatTime } from "../../Helpers/TimeHelpers.js";
15
+ import { EInputTypes } from "../../Interfaces/FormInterfaces.js";
13
16
  import { Button } from "../Documents/Button.js";
17
+ import { ComputedInput, formInputLabel } from "../Inputs/ComputedInput.js";
14
18
  import { isFirstInvalid } from "../Inputs/InputField.js";
15
19
  import { renderInput } from "../Inputs/InputRenderer.js";
16
20
  import { firstFocusable } from "../Inputs/RowsInput.js";
@@ -20,11 +24,21 @@ import { firstFocusable } from "../Inputs/RowsInput.js";
20
24
  * its own: SubmitForm checks on submit, and a plain <form> around a Form blocks its submit while any input, or the form's
21
25
  * own rule, has a problem.
22
26
  */
23
- /** Inputs without a box: they line up with the boxes beside them. */
24
- const CHOICE_TYPES = new Set(["checkbox", "switch", "radio"]);
25
27
  /** What takes focus in an input's cell: a control to type in or pick, before any button of its toolbar. */
26
28
  const TYPING_TARGETS = 'input:not([type="hidden"]):not(.nx-visually-hidden):not(:disabled), textarea:not(.nx-visually-hidden):not(:disabled), select:not(:disabled), [contenteditable="true"]';
27
29
  const INVALID_CONTROLS = ":is(input, textarea, select):invalid";
30
+ const NO_INPUTS = [];
31
+ const NO_SUBSCRIBE = () => () => { };
32
+ const NO_VERSION = () => 0;
33
+ /** This browser's local storage, or nothing where it cannot be reached (storage switched off). */
34
+ function browserStorage() {
35
+ try {
36
+ return window.localStorage;
37
+ }
38
+ catch {
39
+ return null;
40
+ }
41
+ }
28
42
  /* Hooks */
29
43
  /**
30
44
  * A form store for the page to hold: pass it to a form as `form`, then read, watch, set, and submit through it.
@@ -61,11 +75,7 @@ export function useFormStore(given) {
61
75
  }
62
76
  /* Text */
63
77
  /** An input's name in the form's messages: its `fieldName`, its label when it is text, or its name. */
64
- export function formInputLabel(params, name = "") {
65
- if (!params)
66
- return name;
67
- return inputFieldName(params) ?? (typeof params.label === "string" ? params.label : params.name);
68
- }
78
+ export { formInputLabel };
69
79
  /** The names a list shows: all of them up to one more than `most` ("and 1 more" says less than the name), else the first `most`. */
70
80
  export function namesToShow(names, most = 3) {
71
81
  return names.length <= most + 1 ? { shown: names, more: 0 } : { shown: names.slice(0, most), more: names.length - most };
@@ -90,13 +100,48 @@ export function formatFormTime(ms, preferences) {
90
100
  return time;
91
101
  return `${formatDate(date, preferences)}, ${time}`;
92
102
  }
103
+ /* Cells */
104
+ /**
105
+ * What the page passed as the form's `computeWith`, for the inputs worked out from it (`compute`): one object for the
106
+ * form's life, so the cells' context stays the same and only the worked-out inputs draw again when it changes.
107
+ */
108
+ class ComputeSource {
109
+ listeners = new Set();
110
+ version = 0;
111
+ value;
112
+ constructor(value) {
113
+ this.value = value;
114
+ }
115
+ set(value) {
116
+ if (Object.is(value, this.value))
117
+ return;
118
+ this.value = value;
119
+ this.version++;
120
+ for (const listener of [...this.listeners])
121
+ listener();
122
+ }
123
+ subscribe = (listener) => {
124
+ this.listeners.add(listener);
125
+ return () => {
126
+ this.listeners.delete(listener);
127
+ };
128
+ };
129
+ getVersion = () => this.version;
130
+ }
93
131
  const spanOf = (params) => Math.max(1, Math.min(12, Math.round(params.span ?? 12)));
94
132
  /** A checkbox, switch, or radio that shares a row with labeled boxes: it lines up with their boxes, below their labels. */
95
- const besideBoxes = (params, span, design) => CHOICE_TYPES.has(params.type) && span < 12 && (design ?? "standard") === "standard";
133
+ const besideBoxes = (params, span, design) => isBooleanInput(params) && span < 12 && (design ?? "standard") === "standard";
96
134
  /** The input. It starts from the store's value and reports each change; a new stamp mounts it again with a new value. */
97
- function FieldInput({ store, params, stamp, error, context }) {
135
+ function FieldInput({ store, params, stamp, error, context, cell, heldFocus }) {
98
136
  const [initial] = useState(() => store.get(params.name));
99
- const onValueChange = useCallback((value) => store.input(params.name, value, stamp), [store, params.name, stamp]);
137
+ // Runs before the input leaves the page (a new stamp: a value from code, a load, a remembered value a picker gave
138
+ // late): a focus inside it is noted, or it would fall to the page with nothing said.
139
+ useLayoutEffect(() => () => {
140
+ const active = typeof document === "undefined" ? null : document.activeElement;
141
+ if (active && cell.current?.contains(active))
142
+ heldFocus.current = true;
143
+ }, [cell, heldFocus]);
144
+ const onValueChange = useCallback((value, item) => store.input(params.name, value, stamp, item), [store, params.name, stamp]);
100
145
  const own = params;
101
146
  const overrides = {
102
147
  id: `${context.idPrefix}-${params.name}`,
@@ -108,13 +153,51 @@ function FieldInput({ store, params, stamp, error, context }) {
108
153
  };
109
154
  if (context.design && !own.design)
110
155
  overrides.design = context.design;
111
- if (context.scopeNames && params.type === "radio-group")
156
+ if (context.scopeNames && params.type === EInputTypes.RadioGroup)
112
157
  overrides.name = `${context.idPrefix}:${params.name}`;
113
158
  // Only a message from the form replaces the input's own.
114
159
  if (error !== undefined)
115
160
  overrides.error = error;
116
- return renderInput(params, overrides);
161
+ // A picker asked whether it still offers the remembered value (`remember`): its spinner shows until it answers.
162
+ if (store.isRemembering(params.name))
163
+ overrides.loading = true;
164
+ // Options another input narrows (`narrowedBy`): read with its value as it is now; the cell draws again when it changes.
165
+ // Copied afresh on every draw, never kept: the copy reads the params' getters, and one that `dependsOn` follows
166
+ // (readOnly, hint, label, options; span is read by the cell) must answer for the values of this draw. The box keeps its loaded pages by `loadKey`.
167
+ const shown = params.narrowedBy ? narrowInputParamsBy(params, narrowing => ({ value: store.get(narrowing.name), label: store.inputParams(narrowing.name)?.label })) : params;
168
+ // Rows whose inputs follow this form's values (`narrowedBy` `from: "form"`): handed them as they are now; the rows input
169
+ // draws again when one changes (it follows them as `dependsOn` would).
170
+ const rowInputs = isRowsInput(params) ? (params.inputs ?? []) : NO_INPUTS;
171
+ if (rowInputs.length) {
172
+ const names = formNarrowingNames(rowInputs);
173
+ if (names.length)
174
+ overrides.formValues = Object.fromEntries(names.map(name => [name, { value: store.get(name), label: store.inputParams(name)?.label }]));
175
+ }
176
+ // Rows with inputs worked out from others (`compute`) get the form's `computeWith`, unless their params name their own;
177
+ // the rows input draws again when the page passes a new one.
178
+ const handsCompute = rowInputs.some(input => input.compute) && !("computeWith" in params);
179
+ const source = context.computeWith;
180
+ useSyncExternalStore(handsCompute ? source.subscribe : NO_SUBSCRIBE, handsCompute ? source.getVersion : NO_VERSION, handsCompute ? source.getVersion : NO_VERSION);
181
+ if (handsCompute)
182
+ overrides.computeWith = source.value;
183
+ return renderInput(shown, overrides);
117
184
  }
185
+ /**
186
+ * An input worked out from others (`compute`) in a form: `ComputedInput` reading the form's values and its `computeWith`
187
+ * (it alone draws again when the page passes a new one), and writing a typed-in figure's answer as the person's change.
188
+ */
189
+ function ComputedField({ store, params, context }) {
190
+ const source = context.computeWith;
191
+ useSyncExternalStore(source.subscribe, source.getVersion, source.getVersion);
192
+ const { idPrefix } = context;
193
+ const read = useCallback(() => store.getValue(), [store]);
194
+ const write = useCallback((values) => void store.setFromPerson(values), [store]);
195
+ const inputParams = useCallback((name) => store.inputParams(name), [store]);
196
+ const sourceId = useCallback((name) => `${idPrefix}-${name}`, [idPrefix]);
197
+ return (_jsx(ComputedInput, { params: params, id: `${idPrefix}-${params.name}`, read: read, withValue: source.value, write: write, inputParams: inputParams, sourceId: sourceId, size: context.size, design: context.design, loading: context.loading, readOnly: context.readOnly, disabled: context.disabled }));
198
+ }
199
+ /** The control an input mounted again takes the focus back in: its box, a list's combobox, a choice, an editor. */
200
+ const REFOCUSABLE = '[role="combobox"]:not([disabled]), input:not([type="hidden"]):not([disabled]):not([tabindex="-1"]), textarea:not([disabled]), select:not([disabled]), [contenteditable="true"], button:not([disabled]):not([tabindex="-1"])';
118
201
  /** One input's place on the grid. It renders again only when its input must: a message from the form, or a value from code. */
119
202
  const FormCell = memo(function FormCell({ store, params, context }) {
120
203
  const { name } = params;
@@ -122,6 +205,23 @@ const FormCell = memo(function FormCell({ store, params, context }) {
122
205
  const version = () => store.fieldVersion(name);
123
206
  useSyncExternalStore(subscribe, version, version);
124
207
  const stamp = store.stamp(name);
208
+ const cellRef = useRef(null);
209
+ const heldFocus = useRef(false);
210
+ // An input mounted again while it had the focus gets it back in its new control (the checked choice of a group
211
+ // first): a person in the box stays there, and a screen reader says the box again with the value it now holds.
212
+ useLayoutEffect(() => {
213
+ if (!heldFocus.current)
214
+ return;
215
+ heldFocus.current = false;
216
+ const cell = cellRef.current;
217
+ const active = document.activeElement;
218
+ if (!cell || (active && active !== document.body && cell.contains(active)))
219
+ return;
220
+ const target = cell.querySelector("input:checked:not([disabled])") ?? cell.querySelector(REFOCUSABLE);
221
+ target?.focus({ preventScroll: true });
222
+ }, [stamp]);
223
+ // Read on every draw: a `span` getter, or `spanWhen`, answers for the values as they are now. The input below keeps
224
+ // its key, so a new width never mounts it again: what is typed, the caret and the focus stay.
125
225
  const span = spanOf(params);
126
226
  // The design the inputs draw in, the person's choice included: choice boxes sit beside others only in `standard`.
127
227
  const design = useInputDesign(context.design);
@@ -134,7 +234,7 @@ const FormCell = memo(function FormCell({ store, params, context }) {
134
234
  return;
135
235
  store.touch(name);
136
236
  }
137
- return (_jsx("div", { className: cx("nx-form__cell", CHOICE_TYPES.has(params.type) && "nx-form__cell--choice"), style: { "--nx-span": span }, "data-span": span, "data-beside": besideBoxes(params, span, design) || undefined, "data-nx-field": name, onBlur: onBlur, children: _jsx(FieldInput, { store: store, params: params, stamp: stamp, error: store.shownError(name), context: context }, stamp) }));
237
+ return (_jsx("div", { className: cx("nx-form__cell", isBooleanInput(params) && "nx-form__cell--choice"), style: { "--nx-span": span }, "data-span": span, "data-beside": besideBoxes(params, span, design) || undefined, "data-nx-field": name, onBlur: onBlur, ref: cellRef, children: params.compute ? (_jsx(ComputedField, { store: store, params: params, context: context })) : (_jsx(FieldInput, { store: store, params: params, stamp: stamp, error: store.shownError(name), context: context, cell: cellRef, heldFocus: heldFocus }, stamp)) }));
138
238
  });
139
239
  /** The note over a form whose unsaved changes came back from a draft. */
140
240
  function DraftNotice({ store, restored, inputs, preferences }) {
@@ -155,18 +255,24 @@ function DraftNotice({ store, restored, inputs, preferences }) {
155
255
  * @example
156
256
  * <Form
157
257
  * inputs={[
158
- * { type: "text", name: "fullName", label: "Full name", required: true, span: 6 },
159
- * { type: "email", name: "email", label: "Email", span: 6 },
160
- * { type: "dropdown", name: "role", label: "Role", options: roles, span: 4 },
258
+ * { type: EInputTypes.Text, name: "fullName", label: "Full name", required: true, span: 6 },
259
+ * { type: EInputTypes.Email, name: "email", label: "Email", span: 6 },
260
+ * { type: EInputTypes.Dropdown, name: "role", label: "Role", options: roles, span: 4 },
161
261
  * ]}
162
262
  * defaultValue={employee}
163
263
  * onValueChange={values => saveFilters(values)}
164
264
  * />
165
265
  */
166
266
  export function Form(props) {
167
- const { inputs, design, size = "md", readOnly = false, disabled = false, loading = false, draftKey, id, className, style, ref } = props;
267
+ const { design, size = "md", readOnly = false, disabled = false, loading = false, draftKey, id, className, style, ref } = props;
168
268
  const store = useFormStore(props.form);
169
269
  const anyStore = store;
270
+ // What the page passes for the worked-out inputs (`compute`): they alone draw again when it changes.
271
+ const [computeWith] = useState(() => new ComputeSource(props.computeWith));
272
+ useLayoutEffect(() => computeWith.set(props.computeWith), [computeWith, props.computeWith]);
273
+ // An input with `showWhen` gets a live `hidden`, one with `readOnlyWhen` a live `readOnly`, that reads this store; it
274
+ // follows the inputs they name (`dependsOn`).
275
+ const inputs = useMemo(() => withInputConditions(props.inputs, name => anyStore.get(name)), [props.inputs, anyStore]);
170
276
  const preferences = useDatePreferences();
171
277
  const idPrefix = useId();
172
278
  const rootRef = useRef(null);
@@ -190,6 +296,13 @@ export function Form(props) {
190
296
  rulesRef.current = rulesKey;
191
297
  store.configure(options, !previous || rulesKey.some((item, index) => !Object.is(item, previous[index])));
192
298
  });
299
+ // Remembered inputs (`remember`) read and keep their values in this browser: given once drawn - after the store has
300
+ // these inputs (a store a CRUD loaded first takes them in the effect above) - so the first draw is the same on a
301
+ // server and in the browser, and they open on what is kept before the first paint.
302
+ useLayoutEffect(() => {
303
+ if (inputs.some(params => params.remember))
304
+ store.attachMemory(browserStorage());
305
+ }, [store, inputs]);
193
306
  useLayoutEffect(() => store.setLoading(loading), [store, loading]);
194
307
  useImperativeHandle(ref, () => store, [store]);
195
308
  // Radio groups need names of their own when no <form> keeps them apart.
@@ -208,7 +321,7 @@ export function Form(props) {
208
321
  }
209
322
  }
210
323
  const fromValues = new Set(store.problems());
211
- const names = latest.current.inputs.filter(params => !params.hidden && (invalid.has(params.name) || fromValues.has(params.name))).map(params => params.name);
324
+ const names = anyStore.inputs.filter(params => !params.hidden && (invalid.has(params.name) || fromValues.has(params.name))).map(params => params.name);
212
325
  return { names, invalid, fromValues };
213
326
  }
214
327
  function focusInput(name) {
@@ -253,7 +366,7 @@ export function Form(props) {
253
366
  const proxy = proxyRef.current;
254
367
  if (!proxy)
255
368
  return;
256
- const first = latest.current.inputs.find(params => !params.hidden && store.formRuleError(params.name));
369
+ const first = anyStore.inputs.find(params => !params.hidden && store.formRuleError(params.name));
257
370
  proxy.setCustomValidity((first ? store.formRuleError(first.name) : store.formRuleMessage) ?? "");
258
371
  };
259
372
  const recount = () => {
@@ -311,7 +424,7 @@ export function Form(props) {
311
424
  }
312
425
  // While the values load, the real inputs show, disabled: the form has its final shape from the first frame, so a
313
426
  // popup around it never jumps when the record lands. The store refuses anything typed until then.
314
- const context = useMemo(() => ({ idPrefix, size, design, readOnly, disabled: disabled || loading, scopeNames: !inForm }), [idPrefix, size, design, readOnly, disabled, loading, inForm]);
427
+ const context = useMemo(() => ({ idPrefix, size, design, readOnly, disabled: disabled || loading, loading, scopeNames: !inForm, computeWith }), [idPrefix, size, design, readOnly, disabled, loading, inForm, computeWith]);
315
428
  // With FormButtons on the page, the form's message is in their status line.
316
429
  const ownMessage = status.buttons === 0 ? status.message : null;
317
430
  return (_jsxs("div", { ref: rootRef, id: id, className: cx("nx-form", `nx-form--${size}`, className), style: style, "data-nx-form": "", "aria-busy": loading || undefined, children: [status.restored && !loading && _jsx(DraftNotice, { store: anyStore, restored: status.restored, inputs: inputs, preferences: preferences }), _jsx("div", { className: "nx-form__grid", children: inputs.filter(params => params.dependsOn?.length || !params.hidden).map((params, index, visible) => (_jsxs(Fragment, { children: [params.section && (index === 0 || visible[index - 1]?.section !== params.section) && _jsx("h3", { className: "nx-form__section", children: params.section }), _jsx(FormCell, { store: anyStore, params: params, context: context })] }, params.name))) }), loading && (_jsx("p", { className: "nx-visually-hidden", role: "status", children: "Loading\u2026" })), _jsx("input", { ref: proxyRef, className: "nx-visually-hidden", tabIndex: -1, "aria-hidden": "true", disabled: locked, onInvalid: onProxyInvalid }), ownMessage && (_jsxs("p", { className: "nx-form__message nx-field__message nx-field__message--error", role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: ownMessage })] }))] }));
@@ -0,0 +1,44 @@
1
+ import type { AnyFormStore } from "../../Helpers/FormStore.ts";
2
+ /** One form a guard watches: its store, and whether it has changes that leaving would lose now. */
3
+ export interface GuardedForm {
4
+ form: AnyFormStore;
5
+ changed: () => boolean;
6
+ }
7
+ /** The forms in a place, as they come and go. */
8
+ export declare class FormLeaveGuard {
9
+ private readonly forms;
10
+ /** Watches a form until the function it answers is called (the form leaves). */
11
+ watch(entry: GuardedForm): () => void;
12
+ /** The first form with changes that leaving would lose, or `undefined`. */
13
+ changed(): AnyFormStore | undefined;
14
+ }
15
+ /** The guard of the place a form is drawn in; `null` outside one. */
16
+ export declare const FormLeaveGuardContext: import("react").Context<FormLeaveGuard | null>;
17
+ /**
18
+ * The page's own forms: the ones a navigation inside the app takes away with the page. A `SubmitForm` (and so an
19
+ * `ApiForm`) that asks before the page closes joins it too, unless it is drawn in a `PopupHost` popup, which stays open
20
+ * through a navigation. A link inside the app and `confirmPageLeave()` ask "Discard your changes?" while one has changes.
21
+ */
22
+ export declare class PageLeaveGuard extends FormLeaveGuard {
23
+ private allowedUntil;
24
+ private unloading;
25
+ /** Lets the page's forms go without asking for a moment: a navigation the person confirmed, until it has started. */
26
+ allow(ms?: number): void;
27
+ /**
28
+ * Lets every form go, the page's and the popups', until the page itself goes (`pagehide`): a full-page move the person
29
+ * confirmed (a sign-out, `location.assign`), however long its own call takes first. `end()` takes it back.
30
+ */
31
+ allowUnload(): void;
32
+ /** Asks again: a confirmed move that did not happen (its call failed, the page stays). */
33
+ end(): void;
34
+ /** Whether a confirmed navigation is under way, so neither a link nor a page form's close question asks again. */
35
+ get allowed(): boolean;
36
+ /** Whether a confirmed full-page move is under way: only then a popup form's close question is not asked either. */
37
+ get unloadAllowed(): boolean;
38
+ }
39
+ /** The one page guard of this browser tab. */
40
+ export declare const pageLeaveGuard: PageLeaveGuard;
41
+ /** Every form a full-page load takes away: the page's and the ones in popups (`confirmPageLeave({ unload: true })`). */
42
+ export declare const unloadLeaveGuard: FormLeaveGuard;
43
+ /** The page guard a form joins: the tab's, or `null` inside a place that outlives a navigation (a `PopupHost` popup). */
44
+ export declare const PageLeaveGuardContext: import("react").Context<PageLeaveGuard | null>;
@@ -0,0 +1,65 @@
1
+ "use client";
2
+ import { createContext } from "react";
3
+ /** The forms in a place, as they come and go. */
4
+ export class FormLeaveGuard {
5
+ forms = new Set();
6
+ /** Watches a form until the function it answers is called (the form leaves). */
7
+ watch(entry) {
8
+ this.forms.add(entry);
9
+ return () => {
10
+ this.forms.delete(entry);
11
+ };
12
+ }
13
+ /** The first form with changes that leaving would lose, or `undefined`. */
14
+ changed() {
15
+ for (const entry of this.forms)
16
+ if (entry.changed())
17
+ return entry.form;
18
+ return undefined;
19
+ }
20
+ }
21
+ /** The guard of the place a form is drawn in; `null` outside one. */
22
+ export const FormLeaveGuardContext = createContext(null);
23
+ /**
24
+ * The page's own forms: the ones a navigation inside the app takes away with the page. A `SubmitForm` (and so an
25
+ * `ApiForm`) that asks before the page closes joins it too, unless it is drawn in a `PopupHost` popup, which stays open
26
+ * through a navigation. A link inside the app and `confirmPageLeave()` ask "Discard your changes?" while one has changes.
27
+ */
28
+ export class PageLeaveGuard extends FormLeaveGuard {
29
+ allowedUntil = 0;
30
+ unloading = false;
31
+ /** Lets the page's forms go without asking for a moment: a navigation the person confirmed, until it has started. */
32
+ allow(ms = 1500) {
33
+ this.allowedUntil = Date.now() + ms;
34
+ }
35
+ /**
36
+ * Lets every form go, the page's and the popups', until the page itself goes (`pagehide`): a full-page move the person
37
+ * confirmed (a sign-out, `location.assign`), however long its own call takes first. `end()` takes it back.
38
+ */
39
+ allowUnload() {
40
+ if (this.unloading)
41
+ return;
42
+ this.unloading = true;
43
+ if (typeof window !== "undefined")
44
+ window.addEventListener("pagehide", () => this.end(), { once: true });
45
+ }
46
+ /** Asks again: a confirmed move that did not happen (its call failed, the page stays). */
47
+ end() {
48
+ this.unloading = false;
49
+ this.allowedUntil = 0;
50
+ }
51
+ /** Whether a confirmed navigation is under way, so neither a link nor a page form's close question asks again. */
52
+ get allowed() {
53
+ return this.unloading || Date.now() < this.allowedUntil;
54
+ }
55
+ /** Whether a confirmed full-page move is under way: only then a popup form's close question is not asked either. */
56
+ get unloadAllowed() {
57
+ return this.unloading;
58
+ }
59
+ }
60
+ /** The one page guard of this browser tab. */
61
+ export const pageLeaveGuard = new PageLeaveGuard();
62
+ /** Every form a full-page load takes away: the page's and the ones in popups (`confirmPageLeave({ unload: true })`). */
63
+ export const unloadLeaveGuard = new FormLeaveGuard();
64
+ /** The page guard a form joins: the tab's, or `null` inside a place that outlives a navigation (a `PopupHost` popup). */
65
+ export const PageLeaveGuardContext = createContext(pageLeaveGuard);
@@ -0,0 +1,6 @@
1
+ import type { FormConfirmQuestion } from "../../Helpers/FormConfirm.ts";
2
+ /**
3
+ * Asks a settled question (`formConfirmQuestion`): a question box with what will be saved drawn as a record's details,
4
+ * Confirm holding the focus, a press outside it changing nothing. Resolves whether to go on; an abort answers no.
5
+ */
6
+ export declare function askFormQuestion(question: FormConfirmQuestion, signal?: AbortSignal): Promise<boolean>;
@@ -0,0 +1,28 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { messageBox } from "../Layouts/MessageBox.js";
4
+ import { CrudDetails } from "./CrudParts.js";
5
+ /*
6
+ * The question asked before a save, drawn once for every place that asks it: a form's `confirm` (SubmitForm) and a row
7
+ * form's, including a row action that is its question alone (CrudRowForms.tsx). Not exported from the package: a page
8
+ * asks through those params.
9
+ */
10
+ /**
11
+ * Asks a settled question (`formConfirmQuestion`): a question box with what will be saved drawn as a record's details,
12
+ * Confirm holding the focus, a press outside it changing nothing. Resolves whether to go on; an abort answers no.
13
+ */
14
+ export function askFormQuestion(question, signal) {
15
+ const details = question.details;
16
+ return messageBox.confirm({
17
+ tone: "question",
18
+ closable: true,
19
+ closeOnBackdrop: false,
20
+ signal,
21
+ size: question.size,
22
+ title: question.title,
23
+ message: question.message,
24
+ details: details ? _jsx(CrudDetails, { row: details.row, columns: details.columns, currency: details.currency }) : null,
25
+ confirmLabel: question.confirmLabel,
26
+ cancelLabel: question.cancelLabel,
27
+ });
28
+ }
@@ -0,0 +1,50 @@
1
+ import { type ReactNode } from "react";
2
+ import { type RecordWriteRequest } from "../../Helpers/RecordCards.ts";
3
+ import type { ApiResult, ApiTarget } from "../../Interfaces/ApiInterfaces.ts";
4
+ import type { ApiRequest, InputParams } from "../../Interfaces/FormInterfaces.ts";
5
+ import type { RecordConfirmParams, RecordContext, RecordFlowParams } from "../../Interfaces/RecordInterfaces.ts";
6
+ import { type PopupHandle } from "../Layouts/PopupHost.tsx";
7
+ /** What a popup works with: the card's data as held now, and the page's ways to write it and to end the session. */
8
+ export interface RecordFlowHost<TData = any, TItem = any> {
9
+ /** The card's data and the item, as held now. */
10
+ context: () => RecordContext<TData, TItem>;
11
+ /** Draws the popup again when the page's data changes. */
12
+ subscribe: (listener: () => void) => () => void;
13
+ snapshot: () => unknown;
14
+ /** Writes an answer into the card's data, as `writes` says. */
15
+ write: (request: Pick<RecordWriteRequest, "writes" | "result" | "values" | "fallback">) => Promise<void>;
16
+ /** A refusal that ended the session: the page shows its words and closes its popups. Answers whether it was one. */
17
+ ended: (answer: ApiResult) => boolean;
18
+ /** Keeps the popup, so an ended session closes it, and the focus comes home after it. */
19
+ track: (popup: PopupHandle) => void;
20
+ }
21
+ export interface RecordFlowRequest<TData = any, TItem = any> {
22
+ flow: RecordFlowParams<TData, TItem>;
23
+ host: RecordFlowHost<TData, TItem>;
24
+ /** One popup per key: opened again, it comes back as it was. */
25
+ key: string;
26
+ /** The popup's title and line when the flow names neither. */
27
+ title: ReactNode;
28
+ description?: ReactNode;
29
+ /**
30
+ * `edit`: a card's Edit - its inputs when the flow names none, opened on the data, its values merged over the data as
31
+ * held now when sent and written back when the answer is no record. `add`: the values alone. `item`: opened on the item,
32
+ * its values merged over it.
33
+ */
34
+ kind: "edit" | "add" | "item";
35
+ inputs?: readonly InputParams[];
36
+ }
37
+ /** Where a change goes, sent through the app's `api` without its toasts: an endpoint, a URL (POST), a request, or a function. */
38
+ export declare function sendRecordCall(submit: ApiTarget | ApiRequest | ((body: unknown) => Promise<unknown>), body: unknown): Promise<ApiResult>;
39
+ /**
40
+ * Opens a record page's popup: one per key, brought back as it was when it is open. Each step is a form (none of its
41
+ * inputs is required: a step can be its sentence and its button); its answer is written into the card's data, then the
42
+ * step's `next` shows, or the popup closes and `done` runs. Closing with changes asks first, unless the step says there
43
+ * is nothing worth keeping.
44
+ */
45
+ export declare function openRecordFlow<TData, TItem>(request: RecordFlowRequest<TData, TItem>): PopupHandle;
46
+ /**
47
+ * A change that needs no form: the question first ("Remove ram@example.com?"), then the call, its answer written into
48
+ * the card's data. `busy` marks the item while it runs. A refusal is said in a toast, in the server's words.
49
+ */
50
+ export declare function runRecordConfirm<TData, TItem>(confirm: RecordConfirmParams<TData, TItem>, host: RecordFlowHost<TData, TItem>, busy: (on: boolean) => void): Promise<void>;