nexus-shared 1.1.20 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +903 -0
  3. package/dist/Client.Index.d.ts +44 -0
  4. package/dist/Client.Index.js +49 -0
  5. package/dist/Components/Documents/Button.d.ts +29 -0
  6. package/dist/Components/Documents/Button.js +11 -0
  7. package/dist/Components/Documents/Menu.d.ts +102 -0
  8. package/dist/Components/Documents/Menu.js +238 -0
  9. package/dist/Components/Documents/SplitButton.d.ts +64 -0
  10. package/dist/Components/Documents/SplitButton.js +127 -0
  11. package/dist/Components/Documents/TabButtons.d.ts +62 -0
  12. package/dist/Components/Documents/TabButtons.js +19 -0
  13. package/dist/Components/Forms/ApiForm.d.ts +21 -0
  14. package/dist/Components/Forms/ApiForm.js +99 -0
  15. package/dist/Components/Forms/Crud.d.ts +17 -0
  16. package/dist/Components/Forms/Crud.js +556 -0
  17. package/dist/Components/Forms/CrudParts.d.ts +15 -0
  18. package/dist/Components/Forms/CrudParts.js +36 -0
  19. package/dist/Components/Forms/Form.d.ts +52 -0
  20. package/dist/Components/Forms/Form.js +319 -0
  21. package/dist/Components/Forms/SubmitForm.d.ts +65 -0
  22. package/dist/Components/Forms/SubmitForm.js +198 -0
  23. package/dist/Components/Inputs/Calendar.d.ts +69 -0
  24. package/dist/Components/Inputs/Calendar.js +258 -0
  25. package/dist/Components/Inputs/CheckBox.d.ts +53 -0
  26. package/dist/Components/Inputs/CheckBox.js +87 -0
  27. package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
  28. package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
  29. package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
  30. package/dist/Components/Inputs/ChoiceField.js +169 -0
  31. package/dist/Components/Inputs/DatePicker.d.ts +54 -0
  32. package/dist/Components/Inputs/DatePicker.js +246 -0
  33. package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
  34. package/dist/Components/Inputs/DateRangePicker.js +283 -0
  35. package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
  36. package/dist/Components/Inputs/DateTimePicker.js +399 -0
  37. package/dist/Components/Inputs/Dropdown.d.ts +117 -0
  38. package/dist/Components/Inputs/Dropdown.js +839 -0
  39. package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
  40. package/dist/Components/Inputs/DropdownParts.js +102 -0
  41. package/dist/Components/Inputs/EditorShell.d.ts +45 -0
  42. package/dist/Components/Inputs/EditorShell.js +214 -0
  43. package/dist/Components/Inputs/Field.d.ts +53 -0
  44. package/dist/Components/Inputs/Field.js +16 -0
  45. package/dist/Components/Inputs/GroupForm.d.ts +20 -0
  46. package/dist/Components/Inputs/GroupForm.js +170 -0
  47. package/dist/Components/Inputs/InputField.d.ts +59 -0
  48. package/dist/Components/Inputs/InputField.js +169 -0
  49. package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
  50. package/dist/Components/Inputs/InputRenderer.js +99 -0
  51. package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
  52. package/dist/Components/Inputs/MarkdownEditor.js +289 -0
  53. package/dist/Components/Inputs/NumberBox.d.ts +53 -0
  54. package/dist/Components/Inputs/NumberBox.js +228 -0
  55. package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
  56. package/dist/Components/Inputs/RadioGroup.js +43 -0
  57. package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
  58. package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
  59. package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
  60. package/dist/Components/Inputs/RichTextEditor.js +1170 -0
  61. package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
  62. package/dist/Components/Inputs/RichTextParts.js +157 -0
  63. package/dist/Components/Inputs/RowsInput.d.ts +104 -0
  64. package/dist/Components/Inputs/RowsInput.js +259 -0
  65. package/dist/Components/Inputs/Slider.d.ts +87 -0
  66. package/dist/Components/Inputs/Slider.js +216 -0
  67. package/dist/Components/Inputs/TabularForm.d.ts +23 -0
  68. package/dist/Components/Inputs/TabularForm.js +283 -0
  69. package/dist/Components/Inputs/TextArea.d.ts +43 -0
  70. package/dist/Components/Inputs/TextArea.js +31 -0
  71. package/dist/Components/Inputs/TextBox.d.ts +52 -0
  72. package/dist/Components/Inputs/TextBox.js +58 -0
  73. package/dist/Components/Inputs/TextField.d.ts +31 -0
  74. package/dist/Components/Inputs/TextField.js +35 -0
  75. package/dist/Components/Inputs/TimePanel.d.ts +34 -0
  76. package/dist/Components/Inputs/TimePanel.js +288 -0
  77. package/dist/Components/Inputs/TimePicker.d.ts +54 -0
  78. package/dist/Components/Inputs/TimePicker.js +239 -0
  79. package/dist/Components/Inputs/TreeView.d.ts +160 -0
  80. package/dist/Components/Inputs/TreeView.js +947 -0
  81. package/dist/Components/Layouts/MessageBox.d.ts +71 -0
  82. package/dist/Components/Layouts/MessageBox.js +104 -0
  83. package/dist/Components/Layouts/Popup.d.ts +92 -0
  84. package/dist/Components/Layouts/Popup.js +376 -0
  85. package/dist/Components/Layouts/PopupContext.d.ts +2 -0
  86. package/dist/Components/Layouts/PopupContext.js +4 -0
  87. package/dist/Components/Layouts/PopupDock.d.ts +22 -0
  88. package/dist/Components/Layouts/PopupDock.js +121 -0
  89. package/dist/Components/Layouts/PopupHost.d.ts +56 -0
  90. package/dist/Components/Layouts/PopupHost.js +163 -0
  91. package/dist/Components/Layouts/PopupRules.d.ts +30 -0
  92. package/dist/Components/Layouts/PopupRules.js +63 -0
  93. package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
  94. package/dist/Components/Layouts/ThemePicker.js +22 -0
  95. package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
  96. package/dist/Components/Layouts/ThemeScript.js +13 -0
  97. package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
  98. package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
  99. package/dist/Components/Layouts/Toaster.d.ts +76 -0
  100. package/dist/Components/Layouts/Toaster.js +172 -0
  101. package/dist/Components/Viewers/DataTable.d.ts +25 -0
  102. package/dist/Components/Viewers/DataTable.js +542 -0
  103. package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
  104. package/dist/Components/Viewers/DataTableParts.js +145 -0
  105. package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
  106. package/dist/Components/Viewers/MarkdownView.js +79 -0
  107. package/dist/Components/Viewers/RichTextView.d.ts +18 -0
  108. package/dist/Components/Viewers/RichTextView.js +17 -0
  109. package/dist/Helpers/AnimationHelpers.d.ts +6 -0
  110. package/dist/Helpers/AnimationHelpers.js +35 -0
  111. package/dist/Helpers/ApiClient.d.ts +74 -0
  112. package/dist/Helpers/ApiClient.js +197 -0
  113. package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
  114. package/dist/Helpers/ApiFormHelpers.js +56 -0
  115. package/dist/Helpers/ApiModules.d.ts +135 -0
  116. package/dist/Helpers/ApiModules.js +194 -0
  117. package/dist/Helpers/ApiResponses.d.ts +41 -0
  118. package/dist/Helpers/ApiResponses.js +159 -0
  119. package/dist/Helpers/ApiRoutes.d.ts +150 -0
  120. package/dist/Helpers/ApiRoutes.js +191 -0
  121. package/dist/Helpers/ApiToasts.d.ts +21 -0
  122. package/dist/Helpers/ApiToasts.js +22 -0
  123. package/dist/Helpers/ClassHelpers.d.ts +3 -0
  124. package/dist/Helpers/ClassHelpers.js +4 -0
  125. package/dist/Helpers/CrudBackend.d.ts +56 -0
  126. package/dist/Helpers/CrudBackend.js +119 -0
  127. package/dist/Helpers/CrudHelpers.d.ts +76 -0
  128. package/dist/Helpers/CrudHelpers.js +202 -0
  129. package/dist/Helpers/DateHelpers.d.ts +88 -0
  130. package/dist/Helpers/DateHelpers.js +363 -0
  131. package/dist/Helpers/DatePreferences.d.ts +14 -0
  132. package/dist/Helpers/DatePreferences.js +94 -0
  133. package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
  134. package/dist/Helpers/DateTimeHelpers.js +286 -0
  135. package/dist/Helpers/DragHelpers.d.ts +129 -0
  136. package/dist/Helpers/DragHelpers.js +281 -0
  137. package/dist/Helpers/DropdownHelpers.d.ts +56 -0
  138. package/dist/Helpers/DropdownHelpers.js +180 -0
  139. package/dist/Helpers/FormDrafts.d.ts +18 -0
  140. package/dist/Helpers/FormDrafts.js +76 -0
  141. package/dist/Helpers/FormStore.d.ts +231 -0
  142. package/dist/Helpers/FormStore.js +734 -0
  143. package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
  144. package/dist/Helpers/InputParamsHelpers.js +388 -0
  145. package/dist/Helpers/MarkdownEditing.d.ts +27 -0
  146. package/dist/Helpers/MarkdownEditing.js +250 -0
  147. package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
  148. package/dist/Helpers/MarkdownHelpers.js +775 -0
  149. package/dist/Helpers/MessageBuilder.d.ts +74 -0
  150. package/dist/Helpers/MessageBuilder.js +254 -0
  151. package/dist/Helpers/NepaliCalendar.d.ts +25 -0
  152. package/dist/Helpers/NepaliCalendar.js +107 -0
  153. package/dist/Helpers/NumberHelpers.d.ts +71 -0
  154. package/dist/Helpers/NumberHelpers.js +202 -0
  155. package/dist/Helpers/Permissions.d.ts +62 -0
  156. package/dist/Helpers/Permissions.js +133 -0
  157. package/dist/Helpers/PopoverHelpers.d.ts +43 -0
  158. package/dist/Helpers/PopoverHelpers.js +91 -0
  159. package/dist/Helpers/RichTextHelpers.d.ts +77 -0
  160. package/dist/Helpers/RichTextHelpers.js +1072 -0
  161. package/dist/Helpers/RowsStore.d.ts +160 -0
  162. package/dist/Helpers/RowsStore.js +584 -0
  163. package/dist/Helpers/TableColumns.d.ts +40 -0
  164. package/dist/Helpers/TableColumns.js +254 -0
  165. package/dist/Helpers/TableExport.d.ts +30 -0
  166. package/dist/Helpers/TableExport.js +97 -0
  167. package/dist/Helpers/TableHelpers.d.ts +102 -0
  168. package/dist/Helpers/TableHelpers.js +281 -0
  169. package/dist/Helpers/TableStore.d.ts +142 -0
  170. package/dist/Helpers/TableStore.js +530 -0
  171. package/dist/Helpers/ThemeHelpers.d.ts +15 -0
  172. package/dist/Helpers/ThemeHelpers.js +16 -0
  173. package/dist/Helpers/TimeHelpers.d.ts +73 -0
  174. package/dist/Helpers/TimeHelpers.js +253 -0
  175. package/dist/Helpers/TreeStore.d.ts +188 -0
  176. package/dist/Helpers/TreeStore.js +577 -0
  177. package/dist/Helpers/ValidationHelpers.d.ts +124 -0
  178. package/dist/Helpers/ValidationHelpers.js +301 -0
  179. package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
  180. package/dist/Interfaces/ApiInterfaces.js +24 -0
  181. package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
  182. package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
  183. package/dist/Interfaces/DateInterfaces.d.ts +154 -0
  184. package/dist/Interfaces/DateInterfaces.js +29 -0
  185. package/dist/Interfaces/FormInterfaces.d.ts +352 -0
  186. package/dist/Interfaces/FormInterfaces.js +1 -0
  187. package/dist/Interfaces/InputInterfaces.d.ts +141 -0
  188. package/dist/Interfaces/InputInterfaces.js +1 -0
  189. package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
  190. package/dist/Interfaces/MessageInterfaces.js +5 -0
  191. package/dist/Interfaces/TableInterfaces.d.ts +291 -0
  192. package/dist/Interfaces/TableInterfaces.js +1 -0
  193. package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
  194. package/dist/Interfaces/ThemeInterfaces.js +17 -0
  195. package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
  196. package/dist/Interfaces/TypeInterfaces.js +1 -0
  197. package/dist/Server.Index.d.ts +2 -0
  198. package/dist/Server.Index.js +5 -0
  199. package/dist/Services/ApiProxy.d.ts +32 -0
  200. package/dist/Services/ApiProxy.js +111 -0
  201. package/dist/Services/BrowserApi.d.ts +78 -0
  202. package/dist/Services/BrowserApi.js +111 -0
  203. package/dist/Services/ServerApi.d.ts +49 -0
  204. package/dist/Services/ServerApi.js +75 -0
  205. package/dist/Services/ThemeService.d.ts +17 -0
  206. package/dist/Services/ThemeService.js +83 -0
  207. package/dist/Shared.Index.d.ts +43 -0
  208. package/dist/Shared.Index.js +47 -0
  209. package/package.json +82 -33
  210. package/src/Styles/Nexus.Base.css +70 -0
  211. package/src/Styles/Nexus.Button.css +151 -0
  212. package/src/Styles/Nexus.Calendar.css +275 -0
  213. package/src/Styles/Nexus.Choice.css +345 -0
  214. package/src/Styles/Nexus.Crud.css +84 -0
  215. package/src/Styles/Nexus.DateTime.css +81 -0
  216. package/src/Styles/Nexus.Dropdown.css +774 -0
  217. package/src/Styles/Nexus.Editor.css +297 -0
  218. package/src/Styles/Nexus.Form.css +298 -0
  219. package/src/Styles/Nexus.Index.css +34 -0
  220. package/src/Styles/Nexus.Input.css +637 -0
  221. package/src/Styles/Nexus.Markdown.css +265 -0
  222. package/src/Styles/Nexus.Menu.css +249 -0
  223. package/src/Styles/Nexus.Popup.css +731 -0
  224. package/src/Styles/Nexus.RichText.css +463 -0
  225. package/src/Styles/Nexus.Rows.css +566 -0
  226. package/src/Styles/Nexus.Slider.css +278 -0
  227. package/src/Styles/Nexus.Tab.Buttons.css +364 -0
  228. package/src/Styles/Nexus.Table.css +1014 -0
  229. package/src/Styles/Nexus.Theme.Picker.css +159 -0
  230. package/src/Styles/Nexus.Themes.css +194 -0
  231. package/src/Styles/Nexus.Time.css +147 -0
  232. package/src/Styles/Nexus.Toast.css +265 -0
  233. package/src/Styles/Nexus.Tokens.css +72 -0
  234. package/src/Styles/Nexus.Tree.css +435 -0
  235. package/src/api-services/authentication-service.tsx +0 -23
  236. package/src/api-services/preference-service.tsx +0 -5
  237. package/src/api-services/system-service.tsx +0 -34
  238. package/src/client.ts +0 -31
  239. package/src/components/documents/button.tsx +0 -142
  240. package/src/components/documents/icon-box.tsx +0 -93
  241. package/src/components/documents/page-title.tsx +0 -7
  242. package/src/components/documents/tab-button.tsx +0 -172
  243. package/src/components/documents/tag.tsx +0 -30
  244. package/src/components/index.js +0 -0
  245. package/src/components/inputs/checkbox-input.tsx +0 -66
  246. package/src/components/inputs/input-box.tsx +0 -48
  247. package/src/components/inputs/input-element.tsx +0 -65
  248. package/src/components/inputs/input-form.tsx +0 -164
  249. package/src/components/inputs/input.tsx +0 -181
  250. package/src/components/inputs/number-input.tsx +0 -108
  251. package/src/components/inputs/radiobox-input.tsx +0 -53
  252. package/src/components/inputs/textarea-input.tsx +0 -47
  253. package/src/components/inputs/textbox-input.tsx +0 -45
  254. package/src/components/layouts/global-dialogbox.tsx +0 -431
  255. package/src/components/layouts/global-layout.tsx +0 -59
  256. package/src/components/layouts/layout-helpers.tsx +0 -20
  257. package/src/components/layouts/panels/user-panel.tsx +0 -112
  258. package/src/components/layouts/utility-menu.tsx +0 -49
  259. package/src/components/panels/theme-panel.tsx +0 -46
  260. package/src/helpers/bitwise-helpers.tsx +0 -11
  261. package/src/helpers/browser-helpers.tsx +0 -156
  262. package/src/helpers/datasource-helpers.tsx +0 -99
  263. package/src/helpers/element-helpers.tsx +0 -57
  264. package/src/helpers/input-helpers.tsx +0 -74
  265. package/src/helpers/string-helpers.tsx +0 -28
  266. package/src/helpers/utility-helpers.tsx +0 -45
  267. package/src/helpers/validation-helpers.tsx +0 -302
  268. package/src/index.ts +0 -26
  269. package/src/interface.ts +0 -19
  270. package/src/interfaces/auth-token-interfaces.tsx +0 -6
  271. package/src/interfaces/browser-interfaces.tsx +0 -36
  272. package/src/interfaces/button-interfaces.tsx +0 -63
  273. package/src/interfaces/datasource-interfaces.tsx +0 -22
  274. package/src/interfaces/datatable-interfaces.tsx +0 -25
  275. package/src/interfaces/dialogbox-interfaces.tsx +0 -5
  276. package/src/interfaces/exception-interfaces.tsx +0 -99
  277. package/src/interfaces/http-interfaces.tsx +0 -129
  278. package/src/interfaces/icon-interfaces.tsx +0 -129
  279. package/src/interfaces/input-interfaces.tsx +0 -410
  280. package/src/interfaces/layout-interfaces.tsx +0 -191
  281. package/src/interfaces/menu-interfaces.tsx +0 -49
  282. package/src/interfaces/message-interfaces.tsx +0 -32
  283. package/src/interfaces/permission-interfaces.tsx +0 -9
  284. package/src/interfaces/storage-interfaces.tsx +0 -5
  285. package/src/interfaces/system-interfaces.tsx +0 -22
  286. package/src/interfaces/theme-interfaces.tsx +0 -123
  287. package/src/interfaces/type-interfaces.tsx +0 -28
  288. package/src/interfaces/user-interfaces.tsx +0 -47
  289. package/src/nexus-client.tsx +0 -21
  290. package/src/nexus.environments.tsx +0 -66
  291. package/src/proxy-api/proxy-backend.tsx +0 -60
  292. package/src/proxy-api/proxy-constants.tsx +0 -28
  293. package/src/proxy-api/proxy-helpers.tsx +0 -91
  294. package/src/proxy-api-client.tsx +0 -1
  295. package/src/proxy-api-server.tsx +0 -2
  296. package/src/services/http-client-services.tsx +0 -75
  297. package/src/services/http-services.tsx +0 -158
  298. package/src/services/loader-service.tsx +0 -185
  299. package/src/services/localstorage-service.tsx +0 -119
  300. package/src/services/message-services.tsx +0 -383
  301. package/src/services/theme-service.tsx +0 -161
  302. package/src/services/user-services.tsx +0 -10
  303. package/src/sso-config/auth-token-validation.ts +0 -16
  304. package/src/sso-config/callback-route.tsx +0 -35
  305. package/src/sso-config/cookie-encryption.ts +0 -51
  306. package/src/sso-config/cookie-helpers.tsx +0 -102
  307. package/src/sso-config/forgot-password-route.tsx +0 -36
  308. package/src/sso-config/oauth-callback-state.ts +0 -61
  309. package/src/sso-config/pkce-helpers.ts +0 -9
  310. package/src/sso-config/provider-complete-route.tsx +0 -18
  311. package/src/sso-config/redirect-context-actions.tsx +0 -45
  312. package/src/sso-config/redirect-context-helpers.tsx +0 -159
  313. package/src/sso-config/redirect-context-persist.tsx +0 -18
  314. package/src/sso-config/redirect-context-route.tsx +0 -45
  315. package/src/sso-config/refresh-route.tsx +0 -23
  316. package/src/sso-config/sign-in-route.tsx +0 -74
  317. package/src/sso-config/sign-out-route.tsx +0 -57
  318. package/src/sso-config/sign-up-route.tsx +0 -57
  319. package/src/sso-config/sso-backend-flow.ts +0 -38
  320. package/src/sso-config/sso-complete-route.tsx +0 -50
  321. package/src/sso-config/sso-config.ts +0 -59
  322. package/src/sso-config/sso-interfaces.tsx +0 -99
  323. package/src/sso-config/sso-response-helpers.tsx +0 -20
  324. package/src/sso-server.tsx +0 -21
  325. package/src/styles/nexus.animation.css +0 -269
  326. package/src/styles/nexus.core.css +0 -119
  327. package/src/styles/nexus.dialog.css +0 -144
  328. package/src/styles/nexus.icon.css +0 -51
  329. package/src/styles/nexus.input.css +0 -207
  330. package/src/styles/nexus.loader.css +0 -11
  331. package/src/styles/nexus.logic.css +0 -47
  332. package/src/styles/nexus.utility.css +0 -347
@@ -0,0 +1,170 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { iconAlertCircle, iconArrowDown, iconArrowUp, iconChevronDown, iconCopy, iconTrash, InlineIcon } from "nexus-icons";
4
+ import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { useDatePreferences } from "../../Helpers/DatePreferences.js";
7
+ import { formatInputValue } from "../../Helpers/InputParamsHelpers.js";
8
+ import { Button } from "../Documents/Button.js";
9
+ import { firstFocusable, labelText, RowCell, RowsFooter, RowsLegend, useRowsFrame, useRowsInput, useRowVersion } from "./RowsInput.js";
10
+ /** Inputs that read as a line of text in a collapsed section's summary. */
11
+ const SUMMARY_SKIP = new Set(["checkbox", "switch", "radio", "tree", "tabular", "group", "markdown"]);
12
+ /** Inputs without a box: they line up with the boxes beside them. */
13
+ const CHOICE_TYPES = new Set(["checkbox", "switch", "radio"]);
14
+ function defaultSummary(row, inputs, preferences) {
15
+ const parts = [];
16
+ for (const params of inputs) {
17
+ if (parts.length === 3)
18
+ break;
19
+ if (params.hidden || SUMMARY_SKIP.has(params.type))
20
+ continue;
21
+ const text = formatInputValue(params, row[params.name], { preferences });
22
+ if (text)
23
+ parts.push(text);
24
+ }
25
+ return parts.join(" · ");
26
+ }
27
+ /** One row as a section: a header that collapses it, and its inputs on a 12-column grid. */
28
+ const GroupSection = memo(function GroupSection({ store, rowKey, index, count, inputs, context, isNew, onAction }) {
29
+ useRowVersion(store, rowKey);
30
+ // A title made from the row's values follows every change of the row; the inputs themselves do not render again.
31
+ const watchValues = Boolean(context.sectionTitle);
32
+ const subscribeValues = useCallback((listener) => (watchValues ? store.subscribeValue(listener) : () => undefined), [store, watchValues]);
33
+ const readRow = () => (watchValues ? store.row(rowKey) : null);
34
+ useSyncExternalStore(subscribeValues, readRow, readRow);
35
+ const [collapsed, setCollapsed] = useState(context.collapsible && context.defaultCollapsed && !isNew);
36
+ const problems = context.showAll ? store.rowProblems(rowKey) : 0;
37
+ const row = store.row(rowKey) ?? {};
38
+ const title = context.sectionTitle?.(row, index) ?? `${store.names.title} ${index + 1}`;
39
+ const name = `${store.names.item} ${index + 1}`;
40
+ const titleId = `${context.idPrefix}-${index}-title`;
41
+ const bodyId = `${context.idPrefix}-${index}-body`;
42
+ const open = !collapsed || problems > 0;
43
+ // A check that finds problems in a collapsed section opens it.
44
+ useLayoutEffect(() => {
45
+ if (problems > 0 && collapsed)
46
+ setCollapsed(false);
47
+ }, [problems, collapsed]);
48
+ // Radio columns this row has on show as tags: "Primary".
49
+ const tags = inputs.filter(params => params.type === "radio" && !params.hidden && row[params.name] === true).map(labelText);
50
+ let summary = null;
51
+ if (!open) {
52
+ const { summary: pick } = context;
53
+ if (typeof pick === "function")
54
+ summary = pick(row, index);
55
+ else if (pick)
56
+ summary = defaultSummary(row, inputs.filter(params => pick.includes(params.name)), context.preferences);
57
+ else
58
+ summary = defaultSummary(row, inputs, context.preferences);
59
+ }
60
+ function onBlur(event) {
61
+ const cell = event.target.closest("[data-nx-cell]");
62
+ if (!cell || (event.relatedTarget instanceof Node && cell.contains(event.relatedTarget)))
63
+ return;
64
+ store.touch(rowKey, cell.dataset.nxCell ?? "");
65
+ }
66
+ const heading = (_jsxs(_Fragment, { children: [_jsx("span", { id: titleId, className: "nx-group-form__title", children: title }), tags.map(tag => (_jsx("span", { className: "nx-group-form__tag", children: tag }, tag))), summary && _jsx("span", { className: "nx-group-form__summary", children: summary }), problems > 0 && (_jsxs("span", { className: "nx-group-form__problems", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), problems, " to fix"] }))] }));
67
+ return (_jsxs("li", { className: "nx-group-form__section", "data-nx-row": rowKey, "data-open": open || undefined, "data-problem": problems > 0 || undefined, children: [_jsxs("div", { className: "nx-group-form__head", children: [context.collapsible ? (_jsxs("button", { type: "button", className: "nx-group-form__toggle", "aria-expanded": open, "aria-controls": open ? bodyId : undefined, "data-nx-row-actions": "", onClick: () => setCollapsed(open), children: [_jsx(InlineIcon, { icon: iconChevronDown, className: "nx-group-form__chevron" }), heading] })) : (_jsx("div", { className: "nx-group-form__heading", children: heading })), (context.sortable || context.canAdd || context.removable) && (_jsxs("div", { className: "nx-group-form__actions", children: [context.sortable && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "sm", icon: _jsx(InlineIcon, { icon: iconArrowUp }), "aria-label": `Move ${name} up`, title: "Move up", disabled: index === 0, onClick: () => onAction("move-up", rowKey) }), _jsx(Button, { variant: "ghost", size: "sm", icon: _jsx(InlineIcon, { icon: iconArrowDown }), "aria-label": `Move ${name} down`, title: "Move down", disabled: index === count - 1, onClick: () => onAction("move-down", rowKey) })] })), context.canAdd && (_jsx(Button, { variant: "ghost", size: "sm", icon: _jsx(InlineIcon, { icon: iconCopy }), "aria-label": `Duplicate ${name}`, title: "Duplicate", onClick: () => onAction("duplicate", rowKey) })), context.removable && (_jsx(Button, { variant: "ghost", size: "sm", hoverTone: "danger", icon: _jsx(InlineIcon, { icon: iconTrash }), "aria-label": `Remove ${name}`, title: "Remove", onClick: () => onAction("remove", rowKey) }))] }))] }), open && (_jsx("div", { id: bodyId, role: "group", "aria-labelledby": titleId, className: "nx-group-form__body", onBlur: onBlur, children: inputs.map(params => params.hidden ? null : (_jsx("div", { className: cx("nx-group-form__cell", CHOICE_TYPES.has(params.type) && "nx-group-form__cell--choice"), style: { "--nx-span": Math.max(1, Math.min(12, params.span ?? 12)) }, "data-nx-cell": params.name, children: _jsx(RowCell, { store: store, rowKey: rowKey, params: params, index: index, error: store.shownError(rowKey, params.name), layout: "group", idPrefix: context.idPrefix, size: context.size, design: context.design, readOnly: context.readOnly, disabled: context.disabled }, store.revision(rowKey, params.name)) }, params.name))) }))] }));
68
+ });
69
+ /**
70
+ * The rows of a `TabularForm` as sections: each row's inputs with their own labels, on a 12-column grid (`span` in
71
+ * each input's params). The same params, value, rules, and form behavior as `TabularForm`; only the layout differs. It
72
+ * suits rows with more or wider inputs than a table row holds, such as addresses, guardians, or work history, when there
73
+ * are a few of them. For hundreds of rows, use `TabularForm`, which renders only the rows in view.
74
+ *
75
+ * A section collapses to one line that lists its first values, and tags the radios it has on ("Primary"). Sections can
76
+ * move, be copied, and be removed (with Undo). A check that finds problems opens their sections and says how many.
77
+ *
78
+ * @example
79
+ * <GroupForm name="addresses" label="Addresses" itemLabel="Address" max={5} inputs={[
80
+ * { type: "dropdown", name: "country", label: "Country", required: true, options: countries, span: 6 },
81
+ * { type: "text", name: "city", label: "City", required: true, span: 6 },
82
+ * { type: "text", name: "zipCode", label: "Zip code", span: 4 },
83
+ * { type: "dropdown", name: "type", label: "Type", options: ADDRESS_TYPES, span: 4 },
84
+ * { type: "radio", name: "isPrimary", label: "Primary address", span: 4, required: true },
85
+ * ]} />
86
+ */
87
+ export function GroupForm(props) {
88
+ const { inputs, label, hint, error, size = "md", design, readOnly = false, disabled = false, required, max, collapsible = true, defaultCollapsed = false, className, style } = props;
89
+ const addable = props.addable !== false && !readOnly;
90
+ const removable = props.removable !== false && !readOnly && !disabled;
91
+ const sortable = Boolean(props.sortable) && !readOnly && !disabled;
92
+ const preferences = useDatePreferences();
93
+ const layoutRef = useRef(null);
94
+ const rows = useRowsInput(props, "Item", layoutRef);
95
+ const store = rows.store;
96
+ const { snapshot } = rows;
97
+ const frame = useRowsFrame(props, store);
98
+ const count = snapshot.keys.length;
99
+ const listRef = useRef(null);
100
+ const pendingRef = useRef(null);
101
+ // Rows on the page when it first rendered; any other row is new and starts open.
102
+ const [loadedKeys] = useState(() => new Set(snapshot.keys));
103
+ function tryFocus() {
104
+ const pending = pendingRef.current;
105
+ if (!pending)
106
+ return;
107
+ const section = listRef.current?.querySelector(`:scope > [data-nx-row="${CSS.escape(pending.key)}"]`);
108
+ if (!section)
109
+ return;
110
+ const { target } = pending;
111
+ let element;
112
+ if (typeof target === "string") {
113
+ const cell = section.querySelector(`[data-nx-cell="${CSS.escape(target)}"]`);
114
+ // A collapsed section opens first; its inputs are on the page after the next render.
115
+ if (!cell) {
116
+ section.querySelector('.nx-group-form__toggle[aria-expanded="false"]')?.click();
117
+ return;
118
+ }
119
+ element = firstFocusable(cell);
120
+ }
121
+ else if ("actions" in target)
122
+ element = section.querySelector("[data-nx-row-actions]") ?? firstFocusable(section);
123
+ else
124
+ element = firstFocusable(section.querySelector(".nx-group-form__body")) ?? firstFocusable(section);
125
+ pendingRef.current = null;
126
+ element?.focus();
127
+ if (element)
128
+ section.scrollIntoView({ block: "nearest" });
129
+ }
130
+ layoutRef.current = {
131
+ focusRow(key, target) {
132
+ pendingRef.current = { key, target };
133
+ requestAnimationFrame(tryFocus);
134
+ },
135
+ };
136
+ useLayoutEffect(tryFocus);
137
+ const actionsRef = useRef(rows.actions);
138
+ actionsRef.current = rows.actions;
139
+ const onAction = useCallback((action, key) => {
140
+ const run = actionsRef.current;
141
+ if (action === "move-up")
142
+ run.move(key, -1);
143
+ else if (action === "move-down")
144
+ run.move(key, 1);
145
+ else if (action === "duplicate")
146
+ run.duplicate(key);
147
+ else
148
+ run.remove(key);
149
+ }, []);
150
+ const sectionTitle = props.sectionTitle;
151
+ const summary = props.summary;
152
+ const context = useMemo(() => ({
153
+ idPrefix: frame.id,
154
+ size,
155
+ design,
156
+ readOnly,
157
+ disabled,
158
+ removable,
159
+ sortable,
160
+ canAdd: rows.canAdd,
161
+ collapsible,
162
+ defaultCollapsed,
163
+ showAll: snapshot.showAll,
164
+ preferences,
165
+ sectionTitle,
166
+ summary,
167
+ }), [frame.id, size, design, readOnly, disabled, removable, sortable, rows.canAdd, collapsible, defaultCollapsed, snapshot.showAll, preferences, sectionTitle, summary]);
168
+ const invalid = Boolean(error || snapshot.message || snapshot.invalidRows);
169
+ return (_jsxs("fieldset", { id: frame.id, className: cx("nx-rows", "nx-group-form", `nx-group-form--${size}`, className), style: style, disabled: disabled, "data-problem": invalid || undefined, "data-readonly": readOnly || undefined, "aria-label": props["aria-label"], "aria-describedby": frame.messageId, children: [_jsx(RowsLegend, { id: frame.legendId, label: label, required: required || (props.min ?? 0) > 0, lock: frame.lock }), _jsx("input", { ref: rows.proxyRef, className: "nx-visually-hidden", tabIndex: -1, "aria-hidden": "true", form: props.form, onInvalid: rows.onInvalid }), count === 0 ? (_jsx("p", { className: "nx-group-form__empty", children: frame.emptyMessage })) : (_jsx("ol", { ref: listRef, className: "nx-group-form__list", children: snapshot.keys.map((key, index) => (_jsx(GroupSection, { store: store, rowKey: key, index: index, count: count, inputs: inputs, context: context, isNew: !loadedKeys.has(key), onAction: onAction }, `${snapshot.generation}:${key}`))) })), _jsx(RowsFooter, { messageId: frame.messageId, store: store, snapshot: snapshot, error: error, hint: hint, addLabel: frame.addLabel, addable: addable, canAdd: rows.canAdd, max: max, maxMessage: frame.maxMessage, size: size, onAdd: () => rows.actions.add(), onUndo: () => rows.actions.undo() })] }));
170
+ }
@@ -0,0 +1,59 @@
1
+ import { type ChangeEvent, type FocusEvent, type FormEvent, type ReactNode, type Ref } from "react";
2
+ import type { ValidateOn } from "../../Interfaces/InputInterfaces.ts";
3
+ export type TextElement = HTMLInputElement | HTMLTextAreaElement;
4
+ export interface InputFieldOptions<E extends TextElement> {
5
+ id?: string;
6
+ label?: ReactNode;
7
+ hint?: ReactNode;
8
+ fieldName?: string;
9
+ error?: string | null;
10
+ validateOn?: ValidateOn;
11
+ required?: boolean;
12
+ /** Text shown before and after the value. Screen readers read it with the field, through `aria-describedby`. */
13
+ prefix?: ReactNode;
14
+ suffix?: ReactNode;
15
+ ref?: Ref<E>;
16
+ "aria-describedby"?: string;
17
+ onChange?: (event: ChangeEvent<E>) => void;
18
+ onBlur?: (event: FocusEvent<E>) => void;
19
+ onInvalid?: (event: FormEvent<E>) => void;
20
+ }
21
+ export interface InputFieldRules<E extends TextElement> {
22
+ /** The element's message under its rules, or `null` when valid. `name` is the field name for messages. */
23
+ check: (element: E, name: string | undefined) => string | null;
24
+ /** Everything `check` depends on (rules, a controlled value). The element is checked again when one changes. */
25
+ checkKeys: readonly unknown[];
26
+ /** Runs after a form reset has put the element's default text back. */
27
+ onReset?: (element: E) => void;
28
+ }
29
+ /**
30
+ * Behavior shared by every text-like input: ids, validation messages, and form integration. Typing never re-renders
31
+ * the field; the error state changes only when its message does. Validity is also set on the element with
32
+ * `setCustomValidity`, so a plain `<form>` blocks submit and reports each invalid field with its own message.
33
+ */
34
+ export declare function useInputField<E extends TextElement>(options: InputFieldOptions<E>, rules: InputFieldRules<E>): {
35
+ controlId: string;
36
+ messageId: string;
37
+ elementRef: import("react").RefObject<E | null>;
38
+ error: string | null;
39
+ touch: () => void;
40
+ change: () => void;
41
+ /** Props for the input or text area. */
42
+ controlProps: {
43
+ id: string;
44
+ ref: (node: E | null) => void;
45
+ "aria-invalid": boolean | undefined;
46
+ "aria-required": true | undefined;
47
+ "aria-describedby": string | undefined;
48
+ onChange: (event: ChangeEvent<E>) => void;
49
+ onBlur: (event: FocusEvent<E>) => void;
50
+ onInvalid: (event: FormEvent<E>) => void;
51
+ };
52
+ };
53
+ /**
54
+ * Whether a control that failed its check is the first that failed around it, and so takes focus: in its `<form>`, or
55
+ * in the `Form` component around it (`data-nx-form`) when there is no `<form>`. A control in neither is always first.
56
+ */
57
+ export declare function isFirstInvalid(element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement): boolean;
58
+ /** Sets a value the way typing does, so React's onChange and every listener run. */
59
+ export declare function setInputValue(element: TextElement, value: string): void;
@@ -0,0 +1,169 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useEffectEvent, useId, useRef, useState } from "react";
3
+ import { hasContent } from "./Field.js";
4
+ /**
5
+ * Behavior shared by every text-like input: ids, validation messages, and form integration. Typing never re-renders
6
+ * the field; the error state changes only when its message does. Validity is also set on the element with
7
+ * `setCustomValidity`, so a plain `<form>` blocks submit and reports each invalid field with its own message.
8
+ */
9
+ export function useInputField(options, rules) {
10
+ const { id, label, hint, fieldName, error, validateOn = "blur", required, ref } = options;
11
+ const generatedId = useId();
12
+ const controlId = id ?? generatedId;
13
+ const messageId = `${controlId}-message`;
14
+ const name = fieldName ?? (typeof label === "string" ? label : undefined);
15
+ const elementRef = useRef(null);
16
+ // Shown after the first blur with a value, a change when validateOn is "change", or a submit.
17
+ const touchedRef = useRef(false);
18
+ const editedRef = useRef(false);
19
+ const shownRef = useRef(null);
20
+ const [shownError, setShownError] = useState(null);
21
+ function check(element) {
22
+ const message = rules.check(element, name);
23
+ element.setCustomValidity(message ?? "");
24
+ return message;
25
+ }
26
+ function show(message) {
27
+ if (shownRef.current === message)
28
+ return;
29
+ shownRef.current = message;
30
+ setShownError(message);
31
+ }
32
+ const setRef = useCallback((node) => {
33
+ elementRef.current = node;
34
+ if (typeof ref === "function")
35
+ ref(node);
36
+ else if (ref)
37
+ ref.current = node;
38
+ }, [ref]);
39
+ // Rules or a controlled value changed: refresh the element's validity, and the message once it shows.
40
+ // Cross-field rules (confirm password) re-run here when the parent renders with a new `validate`.
41
+ useEffect(() => {
42
+ const element = elementRef.current;
43
+ if (!element)
44
+ return;
45
+ const message = check(element);
46
+ if (touchedRef.current)
47
+ show(message);
48
+ }, [...rules.checkKeys, name]);
49
+ // A form reset restores default values in the DOM; bring validity and the message back with them.
50
+ const onFormReset = useEffectEvent(() => {
51
+ const element = elementRef.current;
52
+ if (!element)
53
+ return;
54
+ touchedRef.current = false;
55
+ editedRef.current = false;
56
+ // React fires onChange only when the text differs from the last text it saw, and a reset changes the DOM without
57
+ // telling it. Assigning the restored text through the property lets React see it, so typing what was there
58
+ // before the reset still reports the change.
59
+ element.value = element.value;
60
+ rules.onReset?.(element);
61
+ check(element);
62
+ show(null);
63
+ });
64
+ useEffect(() => {
65
+ const form = elementRef.current?.form;
66
+ if (!form)
67
+ return;
68
+ // The reset event fires before the browser restores the values.
69
+ const onReset = () => setTimeout(() => onFormReset());
70
+ form.addEventListener("reset", onReset);
71
+ return () => form.removeEventListener("reset", onReset);
72
+ }, []);
73
+ /** Marks the field as used and shows its message now, e.g. after a +/− button changed the value. */
74
+ function touch() {
75
+ const element = elementRef.current;
76
+ if (!element)
77
+ return;
78
+ touchedRef.current = true;
79
+ show(check(element));
80
+ }
81
+ /**
82
+ * Counts a change made without typing, such as a pick from a dropdown's list, the way typing counts: the field is
83
+ * checked again, and its message shows at the same moments (at once once shown, otherwise after blur or submit).
84
+ */
85
+ function change() {
86
+ const element = elementRef.current;
87
+ if (!element)
88
+ return;
89
+ editedRef.current = true;
90
+ if (validateOn === "change")
91
+ touchedRef.current = true;
92
+ const message = check(element);
93
+ if (touchedRef.current)
94
+ show(message);
95
+ }
96
+ function onChange(event) {
97
+ const element = event.currentTarget;
98
+ editedRef.current = true;
99
+ if (validateOn === "change")
100
+ touchedRef.current = true;
101
+ const message = check(element);
102
+ if (touchedRef.current)
103
+ show(message);
104
+ options.onChange?.(event);
105
+ }
106
+ function onBlur(event) {
107
+ const element = event.currentTarget;
108
+ // Tabbing through an empty field does not flag it; typing something, or submitting, does.
109
+ if (validateOn !== "submit" && (editedRef.current || element.value)) {
110
+ touchedRef.current = true;
111
+ show(check(element));
112
+ }
113
+ options.onBlur?.(event);
114
+ }
115
+ function onInvalid(event) {
116
+ // Our message replaces the browser's bubble.
117
+ event.preventDefault();
118
+ const element = event.currentTarget;
119
+ touchedRef.current = true;
120
+ show(check(element) ?? element.validationMessage);
121
+ // The browser focuses nothing once the bubble is cancelled, so the first invalid field takes focus.
122
+ if (isFirstInvalid(element))
123
+ element.focus();
124
+ options.onInvalid?.(event);
125
+ }
126
+ const shownMessage = error || shownError;
127
+ const describedBy = [
128
+ options["aria-describedby"],
129
+ hasContent(options.prefix) && `${controlId}-prefix`,
130
+ hasContent(options.suffix) && `${controlId}-suffix`,
131
+ (shownMessage || hint) && messageId,
132
+ ]
133
+ .filter(Boolean)
134
+ .join(" ") || undefined;
135
+ return {
136
+ controlId,
137
+ messageId,
138
+ elementRef,
139
+ error: shownMessage,
140
+ touch,
141
+ change,
142
+ /** Props for the input or text area. */
143
+ controlProps: {
144
+ id: controlId,
145
+ ref: setRef,
146
+ "aria-invalid": shownMessage ? true : undefined,
147
+ "aria-required": required || undefined,
148
+ "aria-describedby": describedBy,
149
+ onChange,
150
+ onBlur,
151
+ onInvalid,
152
+ },
153
+ };
154
+ }
155
+ /**
156
+ * Whether a control that failed its check is the first that failed around it, and so takes focus: in its `<form>`, or
157
+ * in the `Form` component around it (`data-nx-form`) when there is no `<form>`. A control in neither is always first.
158
+ */
159
+ export function isFirstInvalid(element) {
160
+ const scope = element.form ?? element.closest("[data-nx-form]");
161
+ const first = scope?.querySelector(":is(input, textarea, select):invalid");
162
+ return !first || first === element;
163
+ }
164
+ /** Sets a value the way typing does, so React's onChange and every listener run. */
165
+ export function setInputValue(element, value) {
166
+ const prototype = element instanceof HTMLTextAreaElement ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype;
167
+ Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(element, value);
168
+ element.dispatchEvent(new Event("input", { bubbles: true }));
169
+ }
@@ -0,0 +1,35 @@
1
+ import type { ReactNode } from "react";
2
+ import type { InputParams } from "../../Interfaces/FormInterfaces.ts";
3
+ import type { InputDesign } from "../../Interfaces/InputInterfaces.ts";
4
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
5
+ /** What a form sets on an input it renders from params. Keys given here replace the params' own, even when `undefined`. */
6
+ export interface InputOverrides {
7
+ id?: string;
8
+ /** The form field name. Leave it out to send nothing, as a tabular form's inputs do. */
9
+ name?: string;
10
+ form?: string;
11
+ /** The value the input starts with: `defaultChecked` for a checkbox, switch, or radio, `defaultValue` for the rest. */
12
+ defaultValue?: unknown;
13
+ /** The new value after each change, before the params' own `onValueChange`. */
14
+ onValueChange?: (value: unknown) => void;
15
+ error?: string | null;
16
+ /** Replaces the params' own, e.g. `false` for a radio in rows, where "required" means one row has it. */
17
+ required?: boolean;
18
+ label?: ReactNode;
19
+ hint?: ReactNode;
20
+ size?: Size;
21
+ design?: InputDesign;
22
+ /** On top of the params: `true` makes the input read-only even when its params do not. */
23
+ readOnly?: boolean;
24
+ /** On top of the params, like `readOnly`. */
25
+ disabled?: boolean;
26
+ fieldName?: string;
27
+ className?: string;
28
+ "aria-label"?: string;
29
+ }
30
+ /**
31
+ * Renders an input from its params: `{ type: "email", name: "email", label: "Email", required: true }` gives a
32
+ * `TextBox` of type email. Forms render their `inputs` with it, and tabular and group forms render each row's inputs.
33
+ * Layout keys (`span`, `width`, `grow`) stay out of the component's props; `hidden` renders nothing.
34
+ */
35
+ export declare function renderInput(params: InputParams, overrides?: InputOverrides): ReactNode;
@@ -0,0 +1,99 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { isBooleanInput } from "../../Helpers/InputParamsHelpers.js";
3
+ import { CheckBox, RadioButton, Switch } from "./CheckBox.js";
4
+ import { CheckBoxGroup } from "./CheckBoxGroup.js";
5
+ import { DatePicker } from "./DatePicker.js";
6
+ import { DateRangePicker } from "./DateRangePicker.js";
7
+ import { DateTimePicker } from "./DateTimePicker.js";
8
+ import { Dropdown } from "./Dropdown.js";
9
+ import { GroupForm } from "./GroupForm.js";
10
+ import { MarkdownEditor } from "./MarkdownEditor.js";
11
+ import { NumberBox } from "./NumberBox.js";
12
+ import { RadioGroup } from "./RadioGroup.js";
13
+ import { RichTextEditor } from "./RichTextEditor.js";
14
+ import { RangeSlider, Slider } from "./Slider.js";
15
+ import { TabularForm } from "./TabularForm.js";
16
+ import { TextArea } from "./TextArea.js";
17
+ import { TextBox } from "./TextBox.js";
18
+ import { TimePicker } from "./TimePicker.js";
19
+ import { TreeView } from "./TreeView.js";
20
+ /**
21
+ * Renders an input from its params: `{ type: "email", name: "email", label: "Email", required: true }` gives a
22
+ * `TextBox` of type email. Forms render their `inputs` with it, and tabular and group forms render each row's inputs.
23
+ * Layout keys (`span`, `width`, `grow`) stay out of the component's props; `hidden` renders nothing.
24
+ */
25
+ export function renderInput(params, overrides = {}) {
26
+ if (params.hidden)
27
+ return null;
28
+ const { type, span: _span, width: _width, grow: _grow, hidden: _hidden, onValueChange: ownChange, ...rest } = params;
29
+ const { defaultValue, onValueChange, readOnly, disabled, ...set } = overrides;
30
+ const props = { ...rest, ...set };
31
+ if (readOnly)
32
+ props.readOnly = true;
33
+ if (disabled)
34
+ props.disabled = true;
35
+ if ("defaultValue" in overrides) {
36
+ if (isBooleanInput(params)) {
37
+ delete props.defaultValue;
38
+ props.defaultChecked = defaultValue === true;
39
+ }
40
+ else
41
+ props.defaultValue = defaultValue;
42
+ }
43
+ if (onValueChange || ownChange) {
44
+ props.onValueChange = (value, ...more) => {
45
+ onValueChange?.(value);
46
+ ownChange?.(value, ...more);
47
+ };
48
+ }
49
+ switch (type) {
50
+ case "text":
51
+ case "email":
52
+ case "password":
53
+ case "search":
54
+ case "tel":
55
+ case "url":
56
+ return _jsx(TextBox, { ...props, type: type });
57
+ case "textarea":
58
+ return _jsx(TextArea, { ...props });
59
+ case "number":
60
+ return _jsx(NumberBox, { ...props });
61
+ case "checkbox":
62
+ return _jsx(CheckBox, { ...props });
63
+ case "switch":
64
+ return _jsx(Switch, { ...props });
65
+ case "radio":
66
+ return _jsx(RadioButton, { ...props });
67
+ case "checkbox-group":
68
+ return _jsx(CheckBoxGroup, { ...props });
69
+ case "radio-group":
70
+ return _jsx(RadioGroup, { ...props });
71
+ case "dropdown":
72
+ // Either overload: `multiple` travels in the props.
73
+ return _jsx(Dropdown, { ...props });
74
+ case "date":
75
+ return _jsx(DatePicker, { ...props });
76
+ case "date-range":
77
+ return _jsx(DateRangePicker, { ...props });
78
+ case "time":
79
+ return _jsx(TimePicker, { ...props });
80
+ case "date-time":
81
+ return _jsx(DateTimePicker, { ...props });
82
+ case "slider":
83
+ return _jsx(Slider, { ...props });
84
+ case "range-slider":
85
+ return _jsx(RangeSlider, { ...props });
86
+ case "markdown":
87
+ return _jsx(MarkdownEditor, { ...props });
88
+ case "rich-text":
89
+ return _jsx(RichTextEditor, { ...props });
90
+ case "tree":
91
+ return _jsx(TreeView, { ...props });
92
+ case "tabular":
93
+ return _jsx(TabularForm, { ...props });
94
+ case "group":
95
+ return _jsx(GroupForm, { ...props });
96
+ default:
97
+ return null;
98
+ }
99
+ }
@@ -0,0 +1,67 @@
1
+ import { type ChangeEvent, type ComponentPropsWithRef, type ReactNode } from "react";
2
+ import { type MarkdownTool } from "../../Helpers/MarkdownEditing.ts";
3
+ import type { TextRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
4
+ /** `write`: the text only. `split`: the text and its preview side by side (stacked when narrow). `preview`: the formatted text only. */
5
+ export type MarkdownEditorMode = "write" | "split" | "preview";
6
+ /** A toolbar button, or `"|"` for a divider between groups. */
7
+ export type MarkdownToolbarItem = MarkdownTool | "|";
8
+ /** The default toolbar: text marks, then inserts, then lists, then blocks. */
9
+ export declare const MARKDOWN_TOOLBAR: readonly MarkdownToolbarItem[];
10
+ type NativeTextAreaProps = Omit<ComponentPropsWithRef<"textarea">, "children" | "rows" | keyof TextRules>;
11
+ export interface MarkdownEditorProps extends NativeTextAreaProps, TextRules {
12
+ label?: ReactNode;
13
+ /** Help text under the editor. */
14
+ hint?: ReactNode;
15
+ /** An error from outside, such as a server message. Shown before the field's own message. */
16
+ error?: string | null;
17
+ /** The view it opens in. Default `write`. */
18
+ defaultMode?: MarkdownEditorMode;
19
+ /** The view, when the page controls it; use with `onModeChange`. */
20
+ mode?: MarkdownEditorMode;
21
+ onModeChange?: (mode: MarkdownEditorMode) => void;
22
+ /** The views to switch between, in order. Default all three; a single view hides the switch. */
23
+ modes?: readonly MarkdownEditorMode[];
24
+ /** Toolbar buttons in order, `"|"` for a divider. Default `MARKDOWN_TOOLBAR`; `[]` hides them. Shortcuts work for the tools listed. */
25
+ toolbar?: readonly MarkdownToolbarItem[];
26
+ /** Height of the text and preview, in pixels. Default 280. A double-click on the handle goes back to it. */
27
+ height?: number;
28
+ /** Smallest height the handle allows. Default 120. */
29
+ minHeight?: number;
30
+ /** Largest height the handle allows. Default 960. */
31
+ maxHeight?: number;
32
+ /** The handle under the editor that drags its height. Default true. */
33
+ resizable?: boolean;
34
+ /** Remembers the height the user picks, in local storage under `nx-md-height:<heightKey>`. */
35
+ heightKey?: string;
36
+ /** A button that opens the editor over the whole window. Default true. */
37
+ fullscreen?: boolean;
38
+ /** Every line break inside a paragraph shows as a line break, as in GitHub comments. Default true. */
39
+ breaks?: boolean;
40
+ /** Writes in a monospace font, so tables and code line up. Default false. */
41
+ monospace?: boolean;
42
+ /** Word and character counts in the status bar. Default true. */
43
+ showCount?: boolean;
44
+ /** Shown in the preview while the text is empty. Default "Nothing to preview". */
45
+ emptyPreview?: ReactNode;
46
+ /** What a read-only editor says when someone tries to change it. Default "Read-only: this text cannot be changed." */
47
+ readOnlyMessage?: string;
48
+ /** Name used in messages. Default: the label, when it is text. */
49
+ fieldName?: string;
50
+ /** When the field shows its own message. Default `blur`; submitting a form always shows it. */
51
+ validateOn?: ValidateOn;
52
+ /** The new text after each change, typed or from the toolbar. */
53
+ onValueChange?: (value: string, event: ChangeEvent<HTMLTextAreaElement>) => void;
54
+ /** Class of the outer field. `textAreaClassName` goes on the text area. */
55
+ className?: string;
56
+ textAreaClassName?: string;
57
+ }
58
+ /**
59
+ * A Markdown editor: write, split, and preview views, a toolbar with shortcuts, lists that continue on Enter, a pasted
60
+ * URL that links the selected text, a height handle that drags, steps with the arrow keys, and can remember its height,
61
+ * and a full-screen view. The text area is uncontrolled by default, so typing re-renders nothing but the preview, and
62
+ * the preview renders at low priority, so typing stays smooth in long documents. Toolbar edits keep the browser's undo.
63
+ *
64
+ * @example <MarkdownEditor name="description" label="Description" defaultMode="split" heightKey="room-description" maxLength={5000} />
65
+ */
66
+ export declare function MarkdownEditor(props: MarkdownEditorProps): import("react").JSX.Element;
67
+ export {};