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
@@ -63,6 +63,24 @@ export interface ChoiceItemProps<T extends OptionValue> {
63
63
  }
64
64
  /** One option of a group: a native input and its label, icon, and description, all inside one clickable label. */
65
65
  export declare function ChoiceItem<T extends OptionValue>({ type, groupId, name, option, index, size, checked, defaultChecked, invalid, readOnly, onInvalid }: ChoiceItemProps<T>): import("react").JSX.Element;
66
+ /**
67
+ * One option of a group drawn as tabs (`design="tabs"`): the native input out of sight inside a tab of the `TabButtons`
68
+ * track. Its description is its tooltip, and the input's description for a screen reader: `descriptionId` names the
69
+ * group's note for this option (`ChoiceTabNotes`), which a hidden note still gives.
70
+ */
71
+ export declare function ChoiceTab<T extends OptionValue>({ type, groupId, name, option, index, checked, defaultChecked, invalid, readOnly, onInvalid }: Omit<ChoiceItemProps<T>, "size">): import("react").JSX.Element;
72
+ /**
73
+ * The descriptions of a group drawn as tabs, under the track: the chosen option's shows (a phone has no tooltip), the
74
+ * rest stay hidden but still describe their inputs. `chosen` is the option chosen when the group is drawn; after that
75
+ * `showChosenNote` follows the inputs, as the group's `sync` rule.
76
+ */
77
+ export declare function ChoiceTabNotes<T extends OptionValue>({ groupId, options, chosen }: {
78
+ groupId: string;
79
+ options: readonly ChoiceOption<T>[];
80
+ chosen: number;
81
+ }): (import("react").JSX.Element | null)[];
82
+ /** Shows the note of the option checked now and hides the others (a `useChoiceField` `sync` rule). */
83
+ export declare function showChosenNote(inputs: HTMLInputElement[]): void;
66
84
  /** The legend of a choice group, with the required mark and, when read-only, a lock. */
67
85
  export declare function ChoiceLegend({ label, required, lock }: {
68
86
  label: ReactNode;
@@ -108,7 +126,9 @@ type GroupStyle = CSSProperties & {
108
126
  "--nx-choices-columns"?: number;
109
127
  };
110
128
  /** Class names and inline style of a group's fieldset and options list. */
111
- export declare function groupLayout(props: ChoiceGroupProps<OptionValue>): {
129
+ export declare function groupLayout(props: Omit<ChoiceGroupProps<OptionValue>, "design"> & {
130
+ design?: ChoiceDesign | "tabs";
131
+ }): {
112
132
  className: string;
113
133
  optionsStyle: GroupStyle | undefined;
114
134
  };
@@ -123,7 +123,7 @@ export function preventChange(event) {
123
123
  * reaches the input too), or pressed with Space or an arrow key, which browsers turn into a click.
124
124
  */
125
125
  export function optionAnchor(target) {
126
- return target instanceof HTMLInputElement ? target.closest(".nx-choice") : null;
126
+ return target instanceof HTMLInputElement ? target.closest(".nx-choice, .nx-tabs__option") : null;
127
127
  }
128
128
  /** Hides a field's read-only note when focus leaves the field, not when it moves between its options. */
129
129
  export function leavesField(event) {
@@ -146,6 +146,32 @@ export function ChoiceItem({ type, groupId, name, option, index, size, checked,
146
146
  onChange: checked === undefined ? undefined : noop }), hasContent(option.icon) && (_jsx("span", { className: "nx-choice__icon", "aria-hidden": "true", children: option.icon })), _jsxs("span", { className: "nx-choice__text", children: [_jsx("span", { id: labelId, className: "nx-choice__label", children: option.label }), descriptionId && (_jsx("span", { id: descriptionId, className: "nx-choice__description", children: option.description }))] })] }));
147
147
  }
148
148
  function noop() { }
149
+ /**
150
+ * One option of a group drawn as tabs (`design="tabs"`): the native input out of sight inside a tab of the `TabButtons`
151
+ * track. Its description is its tooltip, and the input's description for a screen reader: `descriptionId` names the
152
+ * group's note for this option (`ChoiceTabNotes`), which a hidden note still gives.
153
+ */
154
+ export function ChoiceTab({ type, groupId, name, option, index, checked, defaultChecked, invalid, readOnly, onInvalid }) {
155
+ const descriptionId = hasContent(option.description) ? `${groupId}-${index}-description` : undefined;
156
+ return (_jsxs("label", { className: "nx-tabs__option", title: typeof option.description === "string" ? option.description : undefined, children: [_jsx("input", { id: `${groupId}-${index}`, type: type, name: name, value: String(option.value), [OPTION_ATTRIBUTE]: index, className: "nx-visually-hidden", checked: checked, defaultChecked: checked === undefined ? defaultChecked : undefined, disabled: option.disabled, "aria-invalid": invalid || undefined, "aria-describedby": descriptionId, onClick: readOnly ? preventChange : undefined, onInvalid: onInvalid, onChange: checked === undefined ? undefined : noop }), option.icon, _jsx("span", { className: "nx-tabs__label", children: _jsx("span", { className: "nx-tabs__text", children: option.label }) })] }));
157
+ }
158
+ /**
159
+ * The descriptions of a group drawn as tabs, under the track: the chosen option's shows (a phone has no tooltip), the
160
+ * rest stay hidden but still describe their inputs. `chosen` is the option chosen when the group is drawn; after that
161
+ * `showChosenNote` follows the inputs, as the group's `sync` rule.
162
+ */
163
+ export function ChoiceTabNotes({ groupId, options, chosen }) {
164
+ return options.map((option, index) => hasContent(option.description) ? (_jsx("p", { id: `${groupId}-${index}-description`, className: "nx-choices__note", "data-nx-note": index, hidden: index !== chosen, children: option.description }, String(option.value))) : null);
165
+ }
166
+ /** Shows the note of the option checked now and hides the others (a `useChoiceField` `sync` rule). */
167
+ export function showChosenNote(inputs) {
168
+ const group = inputs[0]?.closest(".nx-choices");
169
+ if (!group)
170
+ return;
171
+ const chosen = inputs.find(input => input.checked)?.getAttribute(OPTION_ATTRIBUTE) ?? null;
172
+ for (const note of Array.from(group.querySelectorAll("[data-nx-note]")))
173
+ note.hidden = note.dataset.nxNote !== chosen;
174
+ }
149
175
  /** The legend of a choice group, with the required mark and, when read-only, a lock. */
150
176
  export function ChoiceLegend({ label, required, lock }) {
151
177
  if (!hasContent(label))
@@ -156,7 +182,7 @@ export function ChoiceLegend({ label, required, lock }) {
156
182
  export function groupLayout(props) {
157
183
  const { size = "md", design = "default", orientation = "vertical", columns } = props;
158
184
  return {
159
- className: cx("nx-choices", `nx-choices--${size}`, design === "cards" && "nx-choices--cards", orientation === "horizontal" && "nx-choices--horizontal", props.className),
185
+ className: cx("nx-choices", `nx-choices--${size}`, design === "cards" && "nx-choices--cards", design === "tabs" && "nx-choices--tabs", orientation === "horizontal" && "nx-choices--horizontal", props.className),
160
186
  optionsStyle: (columns && columns > 1 ? { "--nx-choices-columns": columns } : undefined),
161
187
  };
162
188
  }
@@ -0,0 +1,51 @@
1
+ import { type FormValues, type InputParams } from "../../Interfaces/FormInterfaces.ts";
2
+ import type { InputDesign } from "../../Interfaces/InputInterfaces.ts";
3
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
4
+ /** An input's name in messages: its `fieldName`, its label when it is text, or its name. */
5
+ export declare function formInputLabel(params: InputParams | undefined, name?: string): string;
6
+ /** What the line under a worked-out input that was typed in says: what was set, or why nothing was. */
7
+ export interface ComputedNote {
8
+ text: string;
9
+ /** The figure was refused: nothing was set. */
10
+ refused: boolean;
11
+ }
12
+ export interface ComputedInputProps {
13
+ params: InputParams;
14
+ /** The control's id. */
15
+ id: string;
16
+ /** The values it is worked out from as they are now: the form's, or the row's. Read on every draw and commit. */
17
+ read: () => FormValues;
18
+ /** The page's `computeWith`, handed to `compute.value` and `compute.solve`. */
19
+ withValue?: unknown;
20
+ /** Takes the values `compute.solve` answered as the person's own change. Not called for a refusal or for nothing new. */
21
+ write: (values: FormValues) => void;
22
+ /** Another input's params by name, as they are now: its name in the note, its number format, and whether it may change. */
23
+ inputParams: (name: string) => InputParams | undefined;
24
+ /** The control id of an input it is worked out from, for the `<output>`'s `for`. */
25
+ sourceId?: (name: string) => string;
26
+ size: Size;
27
+ /** The design around it (a form's); its params' own wins. */
28
+ design?: InputDesign;
29
+ /** The values are loading: it shows nothing and takes nothing. */
30
+ loading?: boolean;
31
+ readOnly?: boolean;
32
+ disabled?: boolean;
33
+ /** Why a box that can be typed in is read-only here, for its note. */
34
+ readOnlyMessage?: string;
35
+ /**
36
+ * Drawn in a table cell: no label (the column header is it), named `aria-label` for screen readers, the standard
37
+ * design, and the line under the box handed to `onNote` - a table cell has no line under it.
38
+ */
39
+ cell?: {
40
+ "aria-label": string;
41
+ };
42
+ /** Hears the note as it comes and goes (`cell`): a table shows it in the line under the rows. */
43
+ onNote?: (note: ComputedNote | null) => void;
44
+ }
45
+ /**
46
+ * An input worked out from others (`compute`): the field's frame (label, hint, prefix, suffix, size, design) around an
47
+ * `<output>` that says the value as it is now, in the person's number format, on the read-only tint - never typed in, so
48
+ * no lock, no note, no steppers and no Tab stop. Blank while the values load, and for a value that is no figure. A Number
49
+ * input with `compute.solve` is a box that can be typed in instead (`SolvableField`).
50
+ */
51
+ export declare function ComputedInput(props: ComputedInputProps): import("react").JSX.Element;
@@ -0,0 +1,179 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { iconAlertCircle, InlineIcon } from "nexus-icons";
4
+ import { useLayoutEffect, useState } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { computedText, computedValue } from "../../Helpers/InputConditions.js";
7
+ import { inputFieldName } from "../../Helpers/InputParamsHelpers.js";
8
+ import { formatNumber, parseNumber, roundDecimal } from "../../Helpers/NumberHelpers.js";
9
+ import { useInputDesign, useNumberLocale } from "../../Helpers/UserPreferences.js";
10
+ import { EInputTypes } from "../../Interfaces/FormInterfaces.js";
11
+ import { Field } from "./Field.js";
12
+ import { NumberBox } from "./NumberBox.js";
13
+ /*
14
+ * An input worked out from others (`compute`), wherever it is drawn: a form's cell or a row's. It holds no value of its
15
+ * own; it reads the values it comes from through `read` and, when it can be typed in (`compute.solve`), writes the ones
16
+ * the typed figure gives through `write`, as the person's own change (`FormStore.setFromPerson`, `RowsStore.setFromPerson`).
17
+ */
18
+ /** An input's name in messages: its `fieldName`, its label when it is text, or its name. */
19
+ export function formInputLabel(params, name = "") {
20
+ if (!params)
21
+ return name;
22
+ return inputFieldName(params) ?? (typeof params.label === "string" ? params.label : params.name);
23
+ }
24
+ /**
25
+ * An input worked out from others (`compute`): the field's frame (label, hint, prefix, suffix, size, design) around an
26
+ * `<output>` that says the value as it is now, in the person's number format, on the read-only tint - never typed in, so
27
+ * no lock, no note, no steppers and no Tab stop. Blank while the values load, and for a value that is no figure. A Number
28
+ * input with `compute.solve` is a box that can be typed in instead (`SolvableField`).
29
+ */
30
+ export function ComputedInput(props) {
31
+ const { params, id, read, withValue, sourceId, size, loading = false, cell } = props;
32
+ const locale = useNumberLocale();
33
+ const own = params;
34
+ const design = useInputDesign(cell ? "standard" : (own.design ?? props.design));
35
+ const disabled = Boolean(props.disabled) || Boolean(own.disabled);
36
+ const text = loading ? "" : computedText(params, computedValue(params, read(), withValue), locale);
37
+ if (params.compute?.solve && params.type === EInputTypes.Number) {
38
+ return _jsx(SolvableField, { ...props, text: text, design: design, disabled: disabled });
39
+ }
40
+ const messageId = `${id}-message`;
41
+ const hint = cell ? undefined : own.hint;
42
+ // The inputs it is worked out from, as an output names them.
43
+ const from = sourceId ? params.compute?.from.map(sourceId).join(" ") : "";
44
+ return (_jsx(Field, { controlId: id, messageId: messageId, label: cell ? undefined : params.label, hint: hint, design: design, size: own.size ?? size, icon: own.icon, iconEnd: own.iconEnd, prefix: own.prefix, suffix: own.suffix, readOnly: true, disabled: disabled, raiseLabel: true, className: own.className, children: _jsx("output", { id: id, htmlFor: from || undefined, className: cx("nx-field__control", "nx-field__control--output", params.type === EInputTypes.Number && "nx-field__control--number"), "aria-live": "polite", "aria-label": cell?.["aria-label"], "aria-describedby": hint ? messageId : undefined, "aria-disabled": disabled || undefined, children: _jsx("span", { className: "nx-field__output-text", children: text }) }) }));
45
+ }
46
+ /**
47
+ * A worked-out Number input that can be typed in (`compute.solve`): a NumberBox showing the figure worked forward, its
48
+ * own Tab stop, in the person's number format. Typing does nothing until the person leaves the box or presses Enter;
49
+ * then `solve` answers the inputs' values that give the figure, `write` takes them as the person's change, and the box
50
+ * shows the figure worked forward again, with a line under it that says what was set (or why nothing was). The hint and
51
+ * that line share one place, so nothing below moves when it comes and goes. Escape puts back what the box showed.
52
+ */
53
+ function SolvableField(props) {
54
+ const { params, id, read, withValue, write, inputParams, size, loading = false, cell, onNote, readOnlyMessage, text, design, disabled } = props;
55
+ const own = params;
56
+ const preferred = useNumberLocale();
57
+ const [note, setNote] = useState(null);
58
+ const format = { decimals: own.decimals, fixedDecimals: own.fixedDecimals ?? own.decimals !== undefined, grouping: own.grouping ?? true, locale: own.locale ?? preferred };
59
+ const readOnly = Boolean(props.readOnly) || Boolean(own.readOnly);
60
+ const shownNote = note && note.at === text ? note : null;
61
+ const shownText = shownNote?.text ?? null;
62
+ const shownRefused = Boolean(shownNote?.refused);
63
+ // A table hears the note as it comes and goes, and that it went with the cell (its row scrolled away).
64
+ useLayoutEffect(() => {
65
+ if (!onNote)
66
+ return;
67
+ onNote(shownText === null ? null : { text: shownText, refused: shownRefused });
68
+ }, [onNote, shownText, shownRefused]);
69
+ useLayoutEffect(() => (onNote ? () => onNote(null) : undefined), [onNote]);
70
+ /** The figure worked forward from the values as they are now: as a number, and as the box writes it. */
71
+ const forward = () => {
72
+ const value = computedValue(params, read(), withValue);
73
+ const number = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
74
+ return Number.isFinite(number) ? { number, text: formatNumber(number, format) } : { number: null, text: "" };
75
+ };
76
+ const number = loading ? null : forward().number;
77
+ /** The box shows the figure worked forward: without group marks while it is being typed in. */
78
+ function showForward(input) {
79
+ const now = forward();
80
+ input.value = now.number === null ? "" : formatNumber(now.number, document.activeElement === input ? { ...format, grouping: false } : format);
81
+ }
82
+ /** A source input's value as its box writes it, for the line under the box. */
83
+ function written(name, value) {
84
+ const sourceParams = inputParams(name);
85
+ if (typeof value === "number" && (!sourceParams || sourceParams.type === EInputTypes.Number)) {
86
+ const decimals = sourceParams?.decimals;
87
+ return formatNumber(value, { decimals, fixedDecimals: sourceParams?.fixedDecimals ?? decimals !== undefined, grouping: sourceParams?.grouping ?? true, locale: sourceParams?.locale ?? preferred });
88
+ }
89
+ return value === null || value === undefined ? "nothing" : String(value);
90
+ }
91
+ /** Works the typed figure back into the inputs it comes from. Answers whether the box had a new figure to take. */
92
+ function commit(input) {
93
+ const solve = params.compute?.solve;
94
+ const now = forward();
95
+ const parsed = parseNumber(input.value, format.locale);
96
+ if (!solve || loading || readOnly || disabled || parsed === null || Number.isNaN(parsed)) {
97
+ showForward(input);
98
+ return false;
99
+ }
100
+ const typed = own.decimals === undefined ? parsed : roundDecimal(parsed, own.decimals);
101
+ const typedText = formatNumber(typed, format);
102
+ if (typedText === now.text) {
103
+ showForward(input);
104
+ return false;
105
+ }
106
+ let answer;
107
+ try {
108
+ answer = solve.call(params.compute, typed, read(), withValue);
109
+ }
110
+ catch (error) {
111
+ console.error(`Working "${params.name}" backwards failed:`, error);
112
+ answer = `${typedText} could not be worked back into the figures it comes from.`;
113
+ }
114
+ if (answer && typeof answer === "object") {
115
+ // A figure is never written into an input the person could not change themselves.
116
+ const locked = Object.keys(answer.values)
117
+ .map(name => inputParams(name))
118
+ .find(found => found && (found.readOnly || found.disabled));
119
+ if (locked)
120
+ answer = `${formInputLabel(locked, locked.name)} cannot be changed here, so ${typedText} cannot be set.`;
121
+ }
122
+ if (typeof answer === "string") {
123
+ setNote({ text: answer, refused: true, at: now.text });
124
+ showForward(input);
125
+ return true;
126
+ }
127
+ if (!answer) {
128
+ showForward(input);
129
+ return true;
130
+ }
131
+ const before = read();
132
+ write(answer.values);
133
+ const after = read();
134
+ const reached = forward();
135
+ const set = Object.keys(answer.values)
136
+ .filter(name => !Object.is(before[name], after[name]))
137
+ .map(name => `${formInputLabel(inputParams(name), name)} set to ${written(name, after[name])}`);
138
+ const said = set.length ? `${set.join(", ")}.` : "";
139
+ const words = answer.note ?? (reached.text === typedText ? said : `${typedText} cannot be reached exactly; nearest is ${reached.text}.${said ? ` ${said}` : ""}`);
140
+ setNote(words ? { text: words, refused: false, at: reached.text } : null);
141
+ showForward(input);
142
+ return true;
143
+ }
144
+ const hint = own.hint;
145
+ // In a table the line goes under the rows (`onNote`); a hidden live region still says it from the cell.
146
+ const footer = cell ? undefined : (_jsxs("span", { className: "nx-field__notes", children: [_jsx("span", { className: cx(shownNote && "nx-field__note--off"), children: hint }), _jsxs("span", { className: cx("nx-field__note", shownNote?.refused && "nx-field__note--refused"), "aria-live": "polite", children: [shownNote?.refused && _jsx(InlineIcon, { icon: iconAlertCircle }), shownNote?.text] })] }));
147
+ const box = (_jsx(NumberBox, { id: id, label: cell ? undefined : params.label, "aria-label": cell?.["aria-label"], hint: footer, design: design, size: own.size ?? size, icon: own.icon, iconEnd: own.iconEnd, prefix: own.prefix, suffix: own.suffix, placeholder: own.placeholder, decimals: format.decimals, fixedDecimals: format.fixedDecimals, grouping: format.grouping, locale: own.locale, value: number, readOnly: readOnly, readOnlyMessage: readOnlyMessage, disabled: disabled || loading, className: own.className, onBlur: event => {
148
+ // Where the focus was going: a box the answer sets (a click into Price) mounts again with its value, so the
149
+ // focus would fall to the page; it goes to the box that took its place (the same id) instead.
150
+ const next = event.relatedTarget instanceof HTMLElement ? event.relatedTarget : null;
151
+ if (!commit(event.currentTarget) || !next?.id)
152
+ return;
153
+ const nextId = next.id;
154
+ setTimeout(() => {
155
+ const active = document.activeElement;
156
+ if (next.isConnected || (active && active !== document.body))
157
+ return;
158
+ document.getElementById(nextId)?.focus();
159
+ });
160
+ }, onKeyDown: event => {
161
+ if (event.key !== "Enter" && event.key !== "Escape")
162
+ return;
163
+ const input = event.currentTarget;
164
+ const parsed = parseNumber(input.value, format.locale);
165
+ const shown = parsed === null || Number.isNaN(parsed) ? "" : formatNumber(own.decimals === undefined ? parsed : roundDecimal(parsed, own.decimals), format);
166
+ // Nothing new in the box: Enter submits the form (or moves down a table's column) and Escape closes a popup.
167
+ if (shown === forward().text)
168
+ return;
169
+ event.preventDefault();
170
+ event.stopPropagation();
171
+ if (event.key === "Enter")
172
+ commit(input);
173
+ else
174
+ showForward(input);
175
+ } }));
176
+ if (!cell)
177
+ return box;
178
+ return (_jsxs(_Fragment, { children: [box, _jsx("span", { className: "nx-visually-hidden", "aria-live": "polite", children: shownNote?.text })] }));
179
+ }
@@ -8,6 +8,11 @@ interface DropdownBaseProps<TItem extends DropdownItem, TValue extends OptionVal
8
8
  hint?: ReactNode;
9
9
  /** An error from outside, such as a server message. Shown before the field's own message. */
10
10
  error?: string | null;
11
+ /**
12
+ * Drawn as invalid with no message line, for a box whose reason the page says elsewhere (`Field` `invalid`); a screen
13
+ * reader hears that reason through `aria-describedby`. A CRUD's narrowing box is drawn so while it asks for a pick.
14
+ */
15
+ invalid?: boolean;
11
16
  /** Default `standard`. */
12
17
  design?: InputDesign;
13
18
  /** Default `md`. */
@@ -29,6 +34,14 @@ interface DropdownBaseProps<TItem extends DropdownItem, TValue extends OptionVal
29
34
  selectedOptions?: readonly TItem[];
30
35
  /** Loads the items of values that no list or `selectedOptions` has, e.g. by id. Values it does not return show as "Not found". */
31
36
  resolveOptions?: (values: TValue[]) => Promise<readonly TItem[]>;
37
+ /**
38
+ * The item of a value (as text) that the page already holds, at once and with no call: a list kept for the page, a
39
+ * record looked up before. Asked before `resolveOptions`, so a value it knows is named from the first draw - and a form
40
+ * can name the values it holds without a call (a CRUD's save does). It answers only what is fresh: a record marked old
41
+ * since may have changed, and the dropdown then asks `resolveOptions`. With `aged` it answers an old one too, which a
42
+ * CRUD's save takes over no name at all. `crudPicker` gives one.
43
+ */
44
+ peekOption?: (key: string, aged?: boolean) => TItem | null | undefined;
32
45
  /** The item field that holds the value. Default `value`. */
33
46
  valueKey?: string;
34
47
  /** The item field shown as its name, in the box and in the list. Default `label`. */
@@ -7,7 +7,7 @@ import { columnTrack, defaultSearchKeys, filterItems, foldSearchText, indexItems
7
7
  import { followAnchor, isPopoverOpen, placeListPanel } from "../../Helpers/PopoverHelpers.js";
8
8
  import { DEFAULT_FIELD_NAME, validateSelection } from "../../Helpers/ValidationHelpers.js";
9
9
  import { Button } from "../Documents/Button.js";
10
- import { DropdownChip, DropdownMedia, DropdownOption, DropdownTag, hasIcon } from "./DropdownParts.js";
10
+ import { columnAlignStyle, DropdownChip, DropdownMedia, DropdownOption, DropdownTag, hasIcon } from "./DropdownParts.js";
11
11
  import { Field } from "./Field.js";
12
12
  import { useInputField } from "./InputField.js";
13
13
  import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
@@ -28,7 +28,7 @@ const NAMES_LISTED = 20;
28
28
  const SKELETON_WIDTHS = ["72%", "54%", "64%", "46%"];
29
29
  const IDLE_LIST = { search: "", loadKey: undefined, items: [], page: 0, hasMore: false, total: undefined, status: "idle", errorPage: 1, firstKeys: new Set() };
30
30
  export function Dropdown(props) {
31
- const { label, hint, error, design: ownDesign, size = "md", icon, options, loadOptions, loadKey, pageSize = 50, selectedOptions, resolveOptions, valueKey = "value", labelKey = "label", descriptionKey = "description", iconKey = "icon", columns, searchKeys, isOptionDisabled = isDisabledItem, isOptionInactive = isInactiveItem, searchable: searchableProp, clearable = true, loading = false, emptyMessage = "No options", renderValue, panelWidth, readOnlyMessage, fieldName, validateOn, className, inputClassName, style, required, validate, name, form, placeholder, disabled, readOnly, ref, onBlur, onFocus, onKeyDown, onClick, onInvalid, multiple: multipleProp, value: valueProp, defaultValue, onValueChange, min, max, selectAll = true, maxChips = 10, ...inputProps } = props;
31
+ const { label, hint, error, invalid = false, design: ownDesign, size = "md", icon, options, loadOptions, loadKey, pageSize = 50, selectedOptions, resolveOptions, peekOption, valueKey = "value", labelKey = "label", descriptionKey = "description", iconKey = "icon", columns, searchKeys, isOptionDisabled = isDisabledItem, isOptionInactive = isInactiveItem, searchable: searchableProp, clearable = true, loading = false, emptyMessage = "No options", renderValue, panelWidth, readOnlyMessage, fieldName, validateOn, className, inputClassName, style, required, validate, name, form, placeholder, disabled, readOnly, ref, onBlur, onFocus, onKeyDown, onClick, onInvalid, multiple: multipleProp, value: valueProp, defaultValue, onValueChange, min, max, selectAll = true, maxChips = 10, ...inputProps } = props;
32
32
  // The person's input design (the quick preferences) when this page and its form name none, for a labelled input.
33
33
  const design = useInputDesign(ownDesign, Boolean(label));
34
34
  const multiple = multipleProp === true;
@@ -48,7 +48,7 @@ export function Dropdown(props) {
48
48
  const optionIndex = useMemo(() => (options ? indexItems(options, valueKey) : null), [options, valueKey]);
49
49
  const selectedIndex = useMemo(() => (selectedOptions ? indexItems(selectedOptions, valueKey) : null), [selectedOptions, valueKey]);
50
50
  const knownRef = useRef(new Map());
51
- const findItem = (key) => optionIndex?.get(key) ?? knownRef.current.get(key) ?? selectedIndex?.get(key) ?? null;
51
+ const findItem = (key) => optionIndex?.get(key) ?? knownRef.current.get(key) ?? selectedIndex?.get(key) ?? peekOption?.(key) ?? null;
52
52
  const labelOf = (item) => itemLabel(item, labelKey, valueKey);
53
53
  const listId = `${useId()}-list`;
54
54
  const selectionId = `${listId}-selection`;
@@ -305,7 +305,9 @@ export function Dropdown(props) {
305
305
  const activeIndex = !open ? -1 : active >= 0 && active < rows.length ? active : fallbackActive();
306
306
  const staleRows = Boolean(loadOptions) && server.status === "loading" && server.items.length > 0;
307
307
  // An open server list that has not started its first request yet is loading too: its placeholder rows already show.
308
- const busy = loading || pendingRef.current.size > 0 || (Boolean(loadOptions) && (server.status === "loading" || server.status === "more" || (open && server.status === "idle")));
308
+ // The value itself is still coming (`loading`: a form asking a picker for a remembered value; or its items resolving).
309
+ const valueBusy = loading || pendingRef.current.size > 0;
310
+ const busy = valueBusy || (Boolean(loadOptions) && (server.status === "loading" || server.status === "more" || (open && server.status === "idle")));
309
311
  // Select all and Clear act on every option the list holds for the search, rendered yet or not, and on the current
310
312
  // values shown at its top. The options are split once per list and selection, not on every highlight move.
311
313
  const showBulk = open && multiple && selectAll;
@@ -777,7 +779,7 @@ export function Dropdown(props) {
777
779
  const failed = Boolean(loadOptions) && server.status === "error";
778
780
  const empty = rows.length === 0 && !skeleton && !failed;
779
781
  const typed = deferredSearch.trim();
780
- listContent = (_jsxs(_Fragment, { children: [showHeader && (_jsxs("div", { className: "nx-dropdown-list__header", "aria-hidden": "true", children: [_jsx("span", {}), typedColumns.map((column, index) => (_jsx("span", { className: "nx-dropdown-list__heading", style: column.align && column.align !== "start" ? { justifySelf: column.align, textAlign: column.align } : undefined, children: column.header }, index)))] })), rows.map((row, index) => {
782
+ listContent = (_jsxs(_Fragment, { children: [showHeader && (_jsxs("div", { className: "nx-dropdown-list__header", "aria-hidden": "true", children: [_jsx("span", {}), typedColumns.map((column, index) => (_jsx("span", { className: "nx-dropdown-list__heading", style: columnAlignStyle(column), children: column.header }, index)))] })), rows.map((row, index) => {
781
783
  const labelField = row.item ? readField(row.item, labelKey) : undefined;
782
784
  const description = row.item && !typedColumns ? readField(row.item, descriptionKey) : undefined;
783
785
  const pending = isPending(row.key, row.item);
@@ -809,7 +811,7 @@ export function Dropdown(props) {
809
811
  // The check column, the columns, and an empty last track whose gap keeps text off the row's end.
810
812
  const gridColumns = columns ? `auto ${columns.map(column => columnTrack(column)).join(" ")} 0px` : undefined;
811
813
  const panelStyle = panelWidth === undefined ? undefined : { width: typeof panelWidth === "number" ? `${panelWidth}px` : panelWidth };
812
- return (_jsxs(_Fragment, { children: [_jsxs(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, icon: icon, actions: _jsxs(_Fragment, { children: [busy && (_jsx("span", { className: "nx-dropdown__spinner", title: "Loading", "aria-hidden": "true", children: _jsx(InlineIcon, { icon: iconLoader2, className: "nx-spin" }) })), showClear && (_jsx("button", { type: "button", className: "nx-field__action nx-dropdown__clear", tabIndex: -1, "aria-label": "Clear", title: "Clear", onMouseDown: event => event.preventDefault(), onClick: clear, children: _jsx(InlineIcon, { icon: iconX }) })), readOnlyNotice.lock, !readOnly && (_jsx("button", { type: "button", className: "nx-field__action nx-dropdown__toggle", tabIndex: -1, "aria-label": open ? "Close the list" : "Open the list", "aria-expanded": open, "aria-controls": open ? listId : undefined, disabled: disabled, onMouseDown: event => event.preventDefault(), onClick: () => (open ? closePanel() : openPanel()), children: _jsx(InlineIcon, { icon: iconChevronDown }) }))] }), disabled: disabled, readOnly: readOnly, raiseLabel: values.length > 0 || Boolean(placeholder), className: cx("nx-field--dropdown", className), style: style, children: [_jsxs("div", { className: cx("nx-dropdown__control", multiple ? "nx-dropdown__control--multiple" : "nx-dropdown__control--single"), "data-searchable": searchable || undefined, "data-has-value": values.length > 0 || undefined, onMouseDown: handleControlMouseDown, children: [boxContent, _jsx("input", { autoComplete: "off", spellCheck: false, ...inputProps, ...field.controlProps, type: "text", role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": open ? listId : undefined, "aria-activedescendant": activeIndex >= 0 ? `${listId}-${activeIndex}` : undefined, "aria-autocomplete": searchable ? "list" : "none", inputMode: searchable ? inputProps.inputMode : "none", name: undefined, form: form, className: cx("nx-field__control", "nx-dropdown__input", inputClassName), placeholder: values.length > 0 ? " " : placeholder || " ", disabled: disabled, readOnly: readOnly, onBlur: handleBlur, onFocus: onFocus, onKeyDown: handleKeyDown, onClick: handleClick }), _jsx("span", { id: selectionId, className: "nx-visually-hidden", children: values.length > 0 ? `Selected: ${namesOf(values)}` : "" })] }), name &&
814
+ return (_jsxs(_Fragment, { children: [_jsxs(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, invalid: invalid, icon: icon, actions: _jsxs(_Fragment, { children: [busy && (_jsx("span", { className: "nx-dropdown__spinner", title: "Loading", "aria-hidden": "true", children: _jsx(InlineIcon, { icon: iconLoader2, className: "nx-spin" }) })), showClear && (_jsx("button", { type: "button", className: "nx-field__action nx-dropdown__clear", tabIndex: -1, "aria-label": "Clear", title: "Clear", onMouseDown: event => event.preventDefault(), onClick: clear, children: _jsx(InlineIcon, { icon: iconX }) })), readOnlyNotice.lock, !readOnly && (_jsx("button", { type: "button", className: "nx-field__action nx-dropdown__toggle", tabIndex: -1, "aria-label": open ? "Close the list" : "Open the list", "aria-expanded": open, "aria-controls": open ? listId : undefined, disabled: disabled, onMouseDown: event => event.preventDefault(), onClick: () => (open ? closePanel() : openPanel()), children: _jsx(InlineIcon, { icon: iconChevronDown }) }))] }), disabled: disabled, readOnly: readOnly, raiseLabel: values.length > 0 || Boolean(placeholder), className: cx("nx-field--dropdown", className), style: style, children: [_jsxs("div", { className: cx("nx-dropdown__control", multiple ? "nx-dropdown__control--multiple" : "nx-dropdown__control--single"), "data-searchable": searchable || undefined, "data-has-value": values.length > 0 || undefined, onMouseDown: handleControlMouseDown, children: [boxContent, _jsx("input", { autoComplete: "off", spellCheck: false, ...inputProps, ...field.controlProps, type: "text", role: "combobox", "aria-expanded": open, "aria-haspopup": "listbox", "aria-controls": open ? listId : undefined, "aria-activedescendant": activeIndex >= 0 ? `${listId}-${activeIndex}` : undefined, "aria-autocomplete": searchable ? "list" : "none", "aria-busy": valueBusy || undefined, inputMode: searchable ? inputProps.inputMode : "none", name: undefined, form: form, className: cx("nx-field__control", "nx-dropdown__input", inputClassName), placeholder: values.length > 0 ? " " : placeholder || " ", disabled: disabled, readOnly: readOnly, onBlur: handleBlur, onFocus: onFocus, onKeyDown: handleKeyDown, onClick: handleClick }), _jsx("span", { id: selectionId, className: "nx-visually-hidden", children: values.length > 0 ? `Selected: ${namesOf(values)}` : valueBusy ? "Loading" : "" })] }), name &&
813
815
  (multiple ? (values.map(value => _jsx("input", { type: "hidden", name: name, form: form, disabled: disabled, value: optionKey(value) }, optionKey(value)))) : (_jsx("input", { type: "hidden", name: name, form: form, disabled: disabled, value: optionKey(values[0]) })))] }), readOnlyNotice.notice, !readOnly && (_jsx("div", { ref: panelRef, popover: "manual", className: cx("nx-popover", "nx-dropdown-panel", `nx-dropdown-panel--${size}`), style: panelStyle, onKeyDown: handlePanelKeyDown, onBlur: handlePanelBlur,
814
816
  // The box keeps focus while rows and buttons are pressed. A press on the list itself is left alone, so its
815
817
  // scroll bar can be dragged.
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from "react";
1
+ import { type CSSProperties, type ReactNode } from "react";
2
2
  import type { DropdownColumn, DropdownItem } from "../../Interfaces/InputInterfaces.ts";
3
3
  /**
4
4
  * Why a current value cannot be picked again:
@@ -24,6 +24,8 @@ export declare function DropdownImage({ src }: {
24
24
  export declare function iconNode(icon: unknown): ReactNode;
25
25
  /** Whether an icon field has something to draw. */
26
26
  export declare function hasIcon(icon: unknown): boolean;
27
+ /** A column's cells and heading where they sit (`dropdownColumnAlign`): nothing to say for `start`. */
28
+ export declare const columnAlignStyle: (column: Pick<AnyColumn, "type" | "align">) => CSSProperties | undefined;
27
29
  /** One cell of a row with columns. `label` names the avatar's initials; `tag` follows the text. */
28
30
  export declare function DropdownCell({ column, item, label, tag }: {
29
31
  column: AnyColumn;
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { Icon, iconCheck, iconX, InlineIcon } from "nexus-icons";
4
4
  import { isValidElement, memo, useState } from "react";
5
5
  import { cx } from "../../Helpers/ClassHelpers.js";
6
- import { isMediaColumn, readField } from "../../Helpers/DropdownHelpers.js";
6
+ import { dropdownColumnAlign, isMediaColumn, readField } from "../../Helpers/DropdownHelpers.js";
7
+ import { formatMoney } from "../../Helpers/NumberHelpers.js";
8
+ import { useNumberLocale } from "../../Helpers/UserPreferences.js";
7
9
  import { Avatar } from "../Viewers/Avatar.js";
8
10
  export const DROPDOWN_STATE_LABELS = {
9
11
  inactive: "Inactive",
@@ -38,11 +40,21 @@ export function iconNode(icon) {
38
40
  export function hasIcon(icon) {
39
41
  return (typeof icon === "string" && icon !== "") || isValidElement(icon);
40
42
  }
41
- const alignStyle = (align) => align && align !== "start" ? { justifySelf: align, textAlign: align } : undefined;
43
+ /** A column's cells and heading where they sit (`dropdownColumnAlign`): nothing to say for `start`. */
44
+ export const columnAlignStyle = (column) => {
45
+ const align = dropdownColumnAlign(column);
46
+ return align !== "start" ? { justifySelf: align, textAlign: align } : undefined;
47
+ };
48
+ /** A money column's figure: 2 places in the person's number format; nothing for a missing one or one that is no figure. */
49
+ function DropdownMoney({ value }) {
50
+ const locale = useNumberLocale();
51
+ const figure = typeof value === "number" ? value : typeof value === "string" && value.trim() !== "" ? Number(value) : Number.NaN;
52
+ return _jsx("span", { className: "nx-dropdown-cell__text", children: formatMoney(Number.isFinite(figure) ? figure : null, { locale }) });
53
+ }
42
54
  /** One cell of a row with columns. `label` names the avatar's initials; `tag` follows the text. */
43
55
  export function DropdownCell({ column, item, label, tag }) {
44
56
  const type = column.type ?? "text";
45
- const style = alignStyle(column.align);
57
+ const style = columnAlignStyle(column);
46
58
  if (column.render) {
47
59
  return (_jsxs("span", { className: cx("nx-dropdown-cell", `nx-dropdown-cell--${type}`), style: style, children: [column.render(item), tag] }));
48
60
  }
@@ -51,7 +63,7 @@ export function DropdownCell({ column, item, label, tag }) {
51
63
  return (_jsx("span", { className: "nx-dropdown-cell nx-dropdown-cell--media", style: style, "aria-hidden": "true", children: type === "avatar" ? _jsx(DropdownAvatar, { src: value, name: label }) : type === "image" ? _jsx(DropdownImage, { src: value }) : _jsx("span", { className: "nx-dropdown-icon", children: iconNode(value) }) }));
52
64
  }
53
65
  const text = value === null || value === undefined ? "" : String(value);
54
- return (_jsxs("span", { className: cx("nx-dropdown-cell", `nx-dropdown-cell--${type}`), style: style, children: [_jsx("span", { className: "nx-dropdown-cell__text", children: text }), tag] }));
66
+ return (_jsxs("span", { className: cx("nx-dropdown-cell", `nx-dropdown-cell--${type}`), style: style, children: [type === "money" ? _jsx(DropdownMoney, { value: value }) : _jsx("span", { className: "nx-dropdown-cell__text", children: text }), tag] }));
55
67
  }
56
68
  /** The picture that leads a value in the box or a chip: the first avatar, image, or icon column, or the icon field. */
57
69
  export function DropdownMedia({ item, label, columns, iconKey }) {
@@ -17,6 +17,12 @@ export interface FieldProps {
17
17
  hint?: ReactNode;
18
18
  /** Error message. Marks the field invalid and replaces the hint. */
19
19
  error?: ReactNode;
20
+ /**
21
+ * Draws the field as invalid, the look an error gives it, with no message line of its own: for a field the page says
22
+ * why about elsewhere, such as a CRUD's narrowing box while its list asks for a pick. Nothing is said to a screen
23
+ * reader by it: point the control's `aria-describedby` at the words that say why.
24
+ */
25
+ invalid?: boolean;
20
26
  /** Id of the hint or error line, for the control's `aria-describedby`. */
21
27
  messageId?: string;
22
28
  /** Decorative icon at the start of the box. */
@@ -48,6 +54,6 @@ export interface FieldProps {
48
54
  * The frame around every input: label, box, icons, and the hint or error line. It has no state, so it renders
49
55
  * anywhere; inputs such as `TextBox` build on it, and so can custom controls.
50
56
  */
51
- export declare function Field({ controlId, label, labelId, required, design, size, hint, error, messageId, icon, iconEnd, prefix, suffix, actionsStart, actions, aside, disabled, readOnly, raiseLabel, multiline, className, style, children, }: FieldProps): import("react").JSX.Element;
57
+ export declare function Field({ controlId, label, labelId, required, design, size, hint, error, invalid, messageId, icon, iconEnd, prefix, suffix, actionsStart, actions, aside, disabled, readOnly, raiseLabel, multiline, className, style, children, }: FieldProps): import("react").JSX.Element;
52
58
  /** Whether a node renders something: not `undefined`, `null`, `false`, or "". */
53
59
  export declare const hasContent: (node: ReactNode) => boolean;
@@ -5,12 +5,12 @@ import { cx } from "../../Helpers/ClassHelpers.js";
5
5
  * The frame around every input: label, box, icons, and the hint or error line. It has no state, so it renders
6
6
  * anywhere; inputs such as `TextBox` build on it, and so can custom controls.
7
7
  */
8
- export function Field({ controlId, label, labelId, required = false, design = "standard", size = "md", hint, error, messageId, icon, iconEnd, prefix, suffix, actionsStart, actions, aside, disabled = false, readOnly = false, raiseLabel = false, multiline = false, className, style, children, }) {
8
+ export function Field({ controlId, label, labelId, required = false, design = "standard", size = "md", hint, error, invalid = false, messageId, icon, iconEnd, prefix, suffix, actionsStart, actions, aside, disabled = false, readOnly = false, raiseLabel = false, multiline = false, className, style, children, }) {
9
9
  const hasLabel = hasContent(label);
10
10
  const hasError = hasContent(error);
11
11
  const message = hasError ? error : hint;
12
12
  const labelElement = hasLabel && (_jsxs("label", { id: labelId, className: "nx-field__label", htmlFor: controlId, children: [label, required && (_jsx("span", { className: "nx-field__required", "aria-hidden": "true", children: "*" }))] }));
13
- return (_jsxs("div", { className: cx("nx-field", `nx-field--${design}`, `nx-field--${size}`, multiline && "nx-field--multiline", className), "data-invalid": hasError || undefined, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-raised": raiseLabel || undefined, style: style, children: [design === "standard" && labelElement, _jsxs("div", { className: "nx-field__box", children: [actionsStart && _jsx("span", { className: "nx-field__actions nx-field__actions--start", children: actionsStart }), icon && (_jsx("span", { className: "nx-field__icon", "aria-hidden": "true", children: icon })), hasContent(prefix) && (_jsx("span", { id: `${controlId}-prefix`, className: "nx-field__affix", children: prefix })), children, design !== "standard" && labelElement, hasContent(suffix) && (_jsx("span", { id: `${controlId}-suffix`, className: "nx-field__affix nx-field__affix--end", children: suffix })), actions && _jsx("span", { className: "nx-field__actions", children: actions }), iconEnd && (_jsx("span", { className: "nx-field__icon nx-field__icon--end", "aria-hidden": "true", children: iconEnd }))] }), (message || aside) && (_jsxs("div", { className: "nx-field__footer", children: [message && (_jsxs("p", { id: messageId, className: cx("nx-field__message", hasError && "nx-field__message--error"), children: [hasError && _jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: message })] })), aside && _jsx("span", { className: "nx-field__aside", children: aside })] }))] }));
13
+ return (_jsxs("div", { className: cx("nx-field", `nx-field--${design}`, `nx-field--${size}`, multiline && "nx-field--multiline", className), "data-invalid": hasError || invalid || undefined, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "data-raised": raiseLabel || undefined, style: style, children: [design === "standard" && labelElement, _jsxs("div", { className: "nx-field__box", children: [actionsStart && _jsx("span", { className: "nx-field__actions nx-field__actions--start", children: actionsStart }), icon && (_jsx("span", { className: "nx-field__icon", "aria-hidden": "true", children: icon })), hasContent(prefix) && (_jsx("span", { id: `${controlId}-prefix`, className: "nx-field__affix", children: prefix })), children, design !== "standard" && labelElement, hasContent(suffix) && (_jsx("span", { id: `${controlId}-suffix`, className: "nx-field__affix nx-field__affix--end", children: suffix })), actions && _jsx("span", { className: "nx-field__actions", children: actions }), iconEnd && (_jsx("span", { className: "nx-field__icon nx-field__icon--end", "aria-hidden": "true", children: iconEnd }))] }), (message || aside) && (_jsxs("div", { className: "nx-field__footer", children: [message && (_jsxs("p", { id: messageId, className: cx("nx-field__message", hasError && "nx-field__message--error"), children: [hasError && _jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: message })] })), aside && _jsx("span", { className: "nx-field__aside", children: aside })] }))] }));
14
14
  }
15
15
  /** Whether a node renders something: not `undefined`, `null`, `false`, or "". */
16
16
  export const hasContent = (node) => node !== undefined && node !== null && node !== false && node !== "";
@@ -1,4 +1,4 @@
1
- import type { GroupFormProps, RowValue } from "../../Interfaces/FormInterfaces.ts";
1
+ import { type GroupFormProps, type RowValue } from "../../Interfaces/FormInterfaces.ts";
2
2
  /**
3
3
  * The rows of a `TabularForm` as sections: each row's inputs with their own labels, on a 12-column grid (`span` in
4
4
  * each input's params). The same params, value, rules, and form behavior as `TabularForm`; only the layout differs. It
@@ -10,11 +10,11 @@ import type { GroupFormProps, RowValue } from "../../Interfaces/FormInterfaces.t
10
10
  *
11
11
  * @example
12
12
  * <GroupForm name="addresses" label="Addresses" itemLabel="Address" max={5} inputs={[
13
- * { type: "dropdown", name: "country", label: "Country", required: true, options: countries, span: 6 },
14
- * { type: "text", name: "city", label: "City", required: true, span: 6 },
15
- * { type: "text", name: "zipCode", label: "Zip code", span: 4 },
16
- * { type: "dropdown", name: "type", label: "Type", options: ADDRESS_TYPES, span: 4 },
17
- * { type: "radio", name: "isPrimary", label: "Primary address", span: 4, required: true },
13
+ * { type: EInputTypes.Dropdown, name: "country", label: "Country", required: true, options: countries, span: 6 },
14
+ * { type: EInputTypes.Text, name: "city", label: "City", required: true, span: 6 },
15
+ * { type: EInputTypes.Text, name: "zipCode", label: "Zip code", span: 4 },
16
+ * { type: EInputTypes.Dropdown, name: "type", label: "Type", options: ADDRESS_TYPES, span: 4 },
17
+ * { type: EInputTypes.Radio, name: "isPrimary", label: "Primary address", span: 4, required: true },
18
18
  * ]} />
19
19
  */
20
20
  export declare function GroupForm<TRow extends RowValue = RowValue>(props: GroupFormProps<TRow>): import("react").JSX.Element;