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,43 @@
1
+ import type { ChangeEvent, ComponentPropsWithRef, ReactNode } from "react";
2
+ import type { InputDesign, TextRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
3
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
4
+ type NativeTextAreaProps = Omit<ComponentPropsWithRef<"textarea">, "children" | keyof TextRules>;
5
+ export interface TextAreaProps extends NativeTextAreaProps, TextRules {
6
+ label?: ReactNode;
7
+ /** Help text under the box. */
8
+ hint?: ReactNode;
9
+ /** An error from outside, such as a server message. Shown before the field's own message. */
10
+ error?: string | null;
11
+ /** Default `standard`. */
12
+ design?: InputDesign;
13
+ /** Default `md`. */
14
+ size?: Size;
15
+ /** Decorative icon at the top left. */
16
+ icon?: ReactNode;
17
+ /** Visible lines. Default 3. */
18
+ rows?: number;
19
+ /** Grows with its text, from `rows` up to `maxRows`, then scrolls. Where the browser cannot, it keeps `rows` and a resize handle. */
20
+ autoResize?: boolean;
21
+ /** Tallest size with `autoResize`, in lines. Default 12. */
22
+ maxRows?: number;
23
+ /** Shows the character count, and the limit when `maxLength` is set. */
24
+ showCount?: boolean;
25
+ /** What a read-only field says when someone tries to change it. Default "Read-only: this field cannot be changed." */
26
+ readOnlyMessage?: string;
27
+ /** Name used in messages. Default: the label, when it is text. */
28
+ fieldName?: string;
29
+ /** When the field shows its own message. Default `blur`; submitting a form always shows it. */
30
+ validateOn?: ValidateOn;
31
+ /** The new text after each change. */
32
+ onValueChange?: (value: string, event: ChangeEvent<HTMLTextAreaElement>) => void;
33
+ /** Class of the outer field. `textAreaClassName` goes on the text area. */
34
+ className?: string;
35
+ textAreaClassName?: string;
36
+ }
37
+ /**
38
+ * A multi-line text input with the same label, hint, validation, and designs as `TextBox`.
39
+ *
40
+ * @example <TextArea label="Description" maxLength={500} showCount autoResize />
41
+ */
42
+ export declare function TextArea(props: TextAreaProps): import("react").JSX.Element;
43
+ export {};
@@ -0,0 +1,31 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { cx } from "../../Helpers/ClassHelpers.js";
4
+ import { Field } from "./Field.js";
5
+ import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
6
+ import { useTextField } from "./TextField.js";
7
+ /**
8
+ * A multi-line text input with the same label, hint, validation, and designs as `TextBox`.
9
+ *
10
+ * @example <TextArea label="Description" maxLength={500} showCount autoResize />
11
+ */
12
+ export function TextArea(props) {
13
+ const { label, hint, error, design = "standard", size = "md", icon, rows = 3, autoResize = false, maxRows = 12, showCount = false, readOnlyMessage = "Read-only: this field cannot be changed.", fieldName, validateOn, onValueChange, className, textAreaClassName, style, required, minLength, maxLength, pattern, patternMessage, validate, placeholder, disabled, readOnly, ref, onChange, onBlur, onInvalid, onKeyDown, onClick, ...textAreaProps } = props;
14
+ const field = useTextField(props);
15
+ const rowsStyle = { "--nx-field-rows": rows, "--nx-field-max-rows": Math.max(rows, maxRows) };
16
+ // A lock at the top end of the box, and a note under it when someone tries to change the text, say why nothing happens.
17
+ const readOnlyNotice = useReadOnlyNotice(Boolean(readOnly) && !disabled, field.elementRef, readOnlyMessage, { keys: ["Enter"] });
18
+ function handleKeyDown(event) {
19
+ readOnlyNotice.onKeyDown(event);
20
+ onKeyDown?.(event);
21
+ }
22
+ function handleClick(event) {
23
+ readOnlyNotice.onClick(event);
24
+ onClick?.(event);
25
+ }
26
+ function handleBlur(event) {
27
+ readOnlyNotice.hide();
28
+ field.controlProps.onBlur(event);
29
+ }
30
+ return (_jsxs(_Fragment, { children: [_jsx(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, icon: icon, actions: readOnlyNotice.lock, aside: field.count, disabled: disabled, readOnly: readOnly, raiseLabel: Boolean(placeholder), multiline: true, className: className, style: { ...rowsStyle, ...style }, children: _jsx("textarea", { ...textAreaProps, ...field.controlProps, className: cx("nx-field__control", autoResize && "nx-field__control--auto", textAreaClassName), rows: rows, placeholder: placeholder || " ", disabled: disabled, readOnly: readOnly, onClick: handleClick, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), readOnlyNotice.notice] }));
31
+ }
@@ -0,0 +1,52 @@
1
+ import { type ChangeEvent, type ComponentPropsWithRef, type ReactNode } from "react";
2
+ import type { InputDesign, TextRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
3
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
4
+ export type TextBoxType = "text" | "email" | "password" | "search" | "tel" | "url";
5
+ type NativeInputProps = Omit<ComponentPropsWithRef<"input">, "type" | "size" | "pattern" | "prefix" | "children" | keyof TextRules>;
6
+ export interface TextBoxProps extends NativeInputProps, TextRules {
7
+ /** Default `text`. `password` adds a show-password button. */
8
+ type?: TextBoxType;
9
+ label?: ReactNode;
10
+ /** Help text under the box. */
11
+ hint?: ReactNode;
12
+ /** An error from outside, such as the server's "Email is already registered". Shown before the field's own message. */
13
+ error?: string | null;
14
+ /** Default `standard`. */
15
+ design?: InputDesign;
16
+ /** Default `md`. */
17
+ size?: Size;
18
+ /** Decorative icon at the start, e.g. `<InlineIcon icon={iconMail} />`. */
19
+ icon?: ReactNode;
20
+ /** Decorative icon at the end. */
21
+ iconEnd?: ReactNode;
22
+ /** Text before the value inside the box, e.g. "https://". Read after the label by screen readers. */
23
+ prefix?: ReactNode;
24
+ /** Text after the value inside the box, e.g. "@nexus.com". */
25
+ suffix?: ReactNode;
26
+ /** Buttons inside the box before the value, such as the rows per page in a table search box. */
27
+ actionsStart?: ReactNode;
28
+ /** Shows a clear button while there is text. Esc clears too. */
29
+ clearable?: boolean;
30
+ /** Shows the character count, and the limit when `maxLength` is set. */
31
+ showCount?: boolean;
32
+ /** What a read-only field says when someone tries to change it. Default "Read-only: this field cannot be changed." */
33
+ readOnlyMessage?: string;
34
+ /** Name used in messages, e.g. "Email is required." Default: the label, when it is text. */
35
+ fieldName?: string;
36
+ /** When the field shows its own message. Default `blur`; submitting a form always shows it. */
37
+ validateOn?: ValidateOn;
38
+ /** The new text after each change. */
39
+ onValueChange?: (value: string, event: ChangeEvent<HTMLInputElement>) => void;
40
+ /** Class of the outer field. `inputClassName` goes on the input. */
41
+ className?: string;
42
+ inputClassName?: string;
43
+ }
44
+ /**
45
+ * A single-line text input with a label, hint, validation message, icons, and optional clear and show-password buttons.
46
+ * Uncontrolled by default (`defaultValue`), so typing does not re-render it; `value` with `onValueChange` also works.
47
+ * Inside a `<form>`, invalid fields block submit and show their messages.
48
+ *
49
+ * @example <TextBox label="Email" type="email" required pattern={TEXT_PATTERNS.email} icon={<InlineIcon icon={iconMail} />} />
50
+ */
51
+ export declare function TextBox(props: TextBoxProps): import("react").JSX.Element;
52
+ export {};
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { InlineIcon, iconEye, iconEyeOff, iconX } from "nexus-icons";
4
+ import { useState } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { Field, hasContent } from "./Field.js";
7
+ import { setInputValue } from "./InputField.js";
8
+ import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
9
+ import { useTextField } from "./TextField.js";
10
+ // Values that are not prose: no red spell-check underline, no capital first letter on phones.
11
+ const PLAIN_TYPES = new Set(["email", "password", "tel", "url"]);
12
+ /**
13
+ * A single-line text input with a label, hint, validation message, icons, and optional clear and show-password buttons.
14
+ * Uncontrolled by default (`defaultValue`), so typing does not re-render it; `value` with `onValueChange` also works.
15
+ * Inside a `<form>`, invalid fields block submit and show their messages.
16
+ *
17
+ * @example <TextBox label="Email" type="email" required pattern={TEXT_PATTERNS.email} icon={<InlineIcon icon={iconMail} />} />
18
+ */
19
+ export function TextBox(props) {
20
+ const { type = "text", label, hint, error, design = "standard", size = "md", icon, iconEnd, prefix, suffix, actionsStart, clearable = false, showCount = false, readOnlyMessage = "Read-only: this field cannot be changed.", fieldName, validateOn, onValueChange, className, inputClassName, style, required, minLength, maxLength, pattern, patternMessage, validate, placeholder, disabled, readOnly, ref, onChange, onBlur, onInvalid, onKeyDown, onClick, ...inputProps } = props;
21
+ const field = useTextField(props);
22
+ const [revealed, setRevealed] = useState(false);
23
+ const editable = !disabled && !readOnly;
24
+ const plain = PLAIN_TYPES.has(type);
25
+ // A lock at the end of the box, and a note under it when someone tries to change the text, say why nothing happens.
26
+ const readOnlyNotice = useReadOnlyNotice(Boolean(readOnly) && !disabled, field.elementRef, readOnlyMessage);
27
+ function clear() {
28
+ const input = field.elementRef.current;
29
+ if (!input)
30
+ return;
31
+ setInputValue(input, "");
32
+ input.focus();
33
+ }
34
+ function handleKeyDown(event) {
35
+ readOnlyNotice.onKeyDown(event);
36
+ // Esc clears first; a second Esc reaches the page, e.g. to close a dialog.
37
+ if (clearable && editable && event.key === "Escape" && event.currentTarget.value) {
38
+ event.preventDefault();
39
+ event.stopPropagation();
40
+ clear();
41
+ }
42
+ onKeyDown?.(event);
43
+ }
44
+ function handleClick(event) {
45
+ readOnlyNotice.onClick(event);
46
+ onClick?.(event);
47
+ }
48
+ function handleBlur(event) {
49
+ readOnlyNotice.hide();
50
+ field.controlProps.onBlur(event);
51
+ }
52
+ const actions = (clearable && editable) || type === "password" || readOnlyNotice.lock ? (_jsxs(_Fragment, { children: [clearable && editable && (_jsx("button", { type: "button", className: "nx-field__action nx-field__action--clear", tabIndex: -1, "aria-label": "Clear", title: "Clear", onMouseDown: event => event.preventDefault(), onClick: clear, children: _jsx(InlineIcon, { icon: iconX }) })), type === "password" && (_jsx("button", { type: "button", className: "nx-field__action", "aria-label": "Show password", "aria-controls": field.controlId, "aria-pressed": revealed, title: revealed ? "Hide password" : "Show password", disabled: disabled, onMouseDown: event => event.preventDefault(), onClick: () => setRevealed(current => !current), children: _jsx(InlineIcon, { icon: revealed ? iconEyeOff : iconEye }) })), readOnlyNotice.lock] })) : null;
53
+ return (_jsxs(_Fragment, { children: [_jsx(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, icon: icon, iconEnd: iconEnd, prefix: prefix, suffix: suffix, actionsStart: actionsStart, actions: actions, aside: field.count, disabled: disabled, readOnly: readOnly,
54
+ // A prefix sits where the resting label would, so the label stays raised.
55
+ raiseLabel: Boolean(placeholder) || hasContent(prefix), className: className, style: style, children: _jsx("input", { spellCheck: plain ? false : undefined, autoCapitalize: plain ? "none" : undefined, ...inputProps, ...field.controlProps, type: type === "password" && revealed ? "text" : type, className: cx("nx-field__control", inputClassName),
56
+ // Never empty, so CSS :placeholder-shown can tell an empty field (hides the clear button, lowers the label).
57
+ placeholder: placeholder || " ", disabled: disabled, readOnly: readOnly, onClick: handleClick, onBlur: handleBlur, onKeyDown: handleKeyDown }) }), readOnlyNotice.notice] }));
58
+ }
@@ -0,0 +1,31 @@
1
+ import { type ChangeEvent } from "react";
2
+ import type { TextRules } from "../../Interfaces/InputInterfaces.ts";
3
+ import { type InputFieldOptions, type TextElement } from "./InputField.tsx";
4
+ export interface TextFieldOptions<E extends TextElement> extends InputFieldOptions<E>, TextRules {
5
+ value?: string | number | readonly string[];
6
+ defaultValue?: string | number | readonly string[];
7
+ showCount?: boolean;
8
+ onValueChange?: (value: string, event: ChangeEvent<E>) => void;
9
+ }
10
+ /** Behavior shared by TextBox and TextArea: text rules, and a character count written straight to the DOM. */
11
+ export declare function useTextField<E extends TextElement>(options: TextFieldOptions<E>): {
12
+ controlProps: {
13
+ maxLength: number | undefined;
14
+ id: string;
15
+ ref: (node: E | null) => void;
16
+ "aria-invalid": boolean | undefined;
17
+ "aria-required": true | undefined;
18
+ "aria-describedby": string | undefined;
19
+ onChange: (event: ChangeEvent<E, Element>) => void;
20
+ onBlur: (event: import("react").FocusEvent<E, Element>) => void;
21
+ onInvalid: (event: import("react").FormEvent<E>) => void;
22
+ };
23
+ /** Character count for the field's `aside`, when `showCount` is on. */
24
+ count: import("react").JSX.Element | null;
25
+ controlId: string;
26
+ messageId: string;
27
+ elementRef: import("react").RefObject<E | null>;
28
+ error: string | null;
29
+ touch: () => void;
30
+ change: () => void;
31
+ };
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef } from "react";
4
+ import { validateText } from "../../Helpers/ValidationHelpers.js";
5
+ import { useInputField } from "./InputField.js";
6
+ /** Behavior shared by TextBox and TextArea: text rules, and a character count written straight to the DOM. */
7
+ export function useTextField(options) {
8
+ const { value, defaultValue, showCount = false, required, minLength, maxLength, pattern, patternMessage, validate } = options;
9
+ const countRef = useRef(null);
10
+ const rules = { required, minLength, maxLength, pattern, patternMessage, validate };
11
+ const patternKey = pattern instanceof RegExp ? pattern.toString() : typeof pattern === "object" ? pattern.source : pattern;
12
+ function updateCount(element) {
13
+ if (countRef.current)
14
+ countRef.current.textContent = String(element.value.length);
15
+ }
16
+ const field = useInputField({
17
+ ...options,
18
+ onChange: event => {
19
+ updateCount(event.currentTarget);
20
+ options.onChange?.(event);
21
+ options.onValueChange?.(event.currentTarget.value, event);
22
+ },
23
+ }, {
24
+ check: (element, name) => validateText(element.value, rules, name),
25
+ checkKeys: [value, required, minLength, maxLength, patternKey, patternMessage, validate],
26
+ onReset: updateCount,
27
+ });
28
+ const initialLength = String(value ?? defaultValue ?? "").length;
29
+ return {
30
+ ...field,
31
+ controlProps: { ...field.controlProps, maxLength },
32
+ /** Character count for the field's `aside`, when `showCount` is on. */
33
+ count: showCount ? (_jsxs("span", { className: "nx-field__count", children: [_jsx("span", { ref: countRef, children: initialLength }), maxLength ? `/${maxLength}` : null] })) : null,
34
+ };
35
+ }
@@ -0,0 +1,34 @@
1
+ import type { DateScript, TimeInput } from "../../Interfaces/DateInterfaces.ts";
2
+ /** `columns`: hours, minutes (seconds), and AM/PM side by side, for any time. `list`: one list of slots, for bookings. */
3
+ export type TimePanelView = "columns" | "list";
4
+ export interface TimePanelProps {
5
+ /** The chosen time, as an ISO time, or `null` for none. */
6
+ value: string | null;
7
+ /** Columns: after each pick in a column, with the whole new time. List: when a slot is picked, before `onDone`. */
8
+ onValueChange: (value: string) => void;
9
+ /** The pick is finished: Enter, Done, Now, or a slot of the list. A popover closes on this. */
10
+ onDone: () => void;
11
+ /** Default `columns`. */
12
+ view?: TimePanelView;
13
+ /** The field's time format, with seconds already added or removed. Decides a 12- or 24-hour clock. */
14
+ format: string;
15
+ script?: DateScript;
16
+ /** Whether values have seconds ("HH:mm:ss"). */
17
+ seconds?: boolean;
18
+ /** Seconds between allowed times, from midnight. A list offers a slot per step, or one every 30 minutes for steps under 5 minutes. */
19
+ step?: number;
20
+ min?: TimeInput;
21
+ max?: TimeInput;
22
+ isTimeDisabled?: (iso: string) => boolean;
23
+ /** Moves focus into the panel when it mounts, as after ↓ in the box. */
24
+ autoFocus?: boolean;
25
+ /** Now and Done buttons under the columns. Default true; a panel that has its own buttons, as in the date and time picker, turns them off. */
26
+ footer?: boolean;
27
+ className?: string;
28
+ }
29
+ /**
30
+ * The panel of a time picker: columns of hours, minutes, seconds, and AM/PM whose chosen items line up on the middle row, or
31
+ * a list of slots. Items outside `min` and `max`, off the `step`, or refused by `isTimeDisabled` are dimmed and cannot
32
+ * be picked. It holds no value of its own: the picker passes the value in and writes each pick to its box.
33
+ */
34
+ export declare function TimePanel(props: TimePanelProps): import("react").JSX.Element;
@@ -0,0 +1,288 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useLayoutEffect, useMemo, useRef } from "react";
4
+ import { cx } from "../../Helpers/ClassHelpers.js";
5
+ import { toDevanagariDigits } from "../../Helpers/DateHelpers.js";
6
+ import { formatTime, getMeridiemNames, isSecondsInRange, isTwelveHourFormat, listTimeSlots, normalizeTimeStep, nowIsoTime, SECONDS_PER_DAY, secondsToIsoTime, timeToSeconds, toIsoTime, } from "../../Helpers/TimeHelpers.js";
7
+ import { Button } from "../Documents/Button.js";
8
+ const PAGE = 5;
9
+ const gcd = (a, b) => (b === 0 ? a : gcd(b, a % b));
10
+ const COLUMN_LABELS = { hour: "Hours", minute: "Minutes", second: "Seconds", meridiem: "AM or PM" };
11
+ /**
12
+ * The panel of a time picker: columns of hours, minutes, seconds, and AM/PM whose chosen items line up on the middle row, or
13
+ * a list of slots. Items outside `min` and `max`, off the `step`, or refused by `isTimeDisabled` are dimmed and cannot
14
+ * be picked. It holds no value of its own: the picker passes the value in and writes each pick to its box.
15
+ */
16
+ export function TimePanel(props) {
17
+ const { value, onValueChange, onDone, view = "columns", format, script = "latin", seconds = false, min, max, isTimeDisabled, autoFocus = false, footer = true, className } = props;
18
+ const step = normalizeTimeStep(props.step);
19
+ const rootRef = useRef(null);
20
+ const scrolledRef = useRef(false);
21
+ const focusedRef = useRef(false);
22
+ const minIso = toIsoTime(min, true);
23
+ const maxIso = toIsoTime(max, true);
24
+ const from = minIso === null ? null : timeToSeconds(minIso);
25
+ const to = maxIso === null ? null : timeToSeconds(maxIso);
26
+ const devanagari = script === "devanagari";
27
+ const digits = (number) => (devanagari ? toDevanagariDigits(String(number).padStart(2, "0")) : String(number).padStart(2, "0"));
28
+ const allowed = (time) => isSecondsInRange(time, from, to) && time % step === 0 && !isTimeDisabled?.(secondsToIsoTime(time, seconds));
29
+ /** Whether any allowed time lies in a span of seconds, e.g. an hour. Stops at the first one. */
30
+ const anyAllowed = (start, end) => {
31
+ for (let time = Math.ceil(start / step) * step; time <= end; time += step)
32
+ if (allowed(time))
33
+ return true;
34
+ return false;
35
+ };
36
+ /** The first allowed time from a point, going forward through the day, or `null` when none is. */
37
+ const firstAllowedFrom = (start, end = start + SECONDS_PER_DAY - 1) => {
38
+ for (let time = Math.ceil(start / step) * step; time <= end; time += step) {
39
+ const inDay = time % SECONDS_PER_DAY;
40
+ if (allowed(inDay))
41
+ return inDay;
42
+ }
43
+ return null;
44
+ };
45
+ const selected = value === null ? null : timeToSeconds(value);
46
+ // An empty field shows the columns around the first time it allows, with nothing chosen.
47
+ const base = useMemo(() => selected ?? firstAllowedFrom(from ?? 0) ?? 0, [selected, from, to, step, isTimeDisabled, seconds]);
48
+ function pick(kind, itemValue) {
49
+ let hour = Math.floor(base / 3600);
50
+ let minute = Math.floor(base / 60) % 60;
51
+ let second = seconds ? base % 60 : 0;
52
+ let start;
53
+ let end;
54
+ if (kind === "hour") {
55
+ hour = itemValue;
56
+ [start, end] = [hour * 3600, hour * 3600 + 3599];
57
+ }
58
+ else if (kind === "minute") {
59
+ minute = itemValue;
60
+ [start, end] = [hour * 3600 + minute * 60, hour * 3600 + minute * 60 + 59];
61
+ }
62
+ else if (kind === "second") {
63
+ second = itemValue;
64
+ start = end = hour * 3600 + minute * 60 + second;
65
+ }
66
+ else {
67
+ hour = (hour % 12) + itemValue * 12;
68
+ [start, end] = [itemValue * 43200, itemValue * 43200 + 43199];
69
+ }
70
+ let time = hour * 3600 + minute * 60 + second;
71
+ // The part just picked stays; smaller parts move to the first time it allows, e.g. 09:45 → 10 o'clock → 10:00 when 10:45 is past `max`.
72
+ if (!allowed(time))
73
+ time = firstAllowedFrom(start, end) ?? time;
74
+ if (time !== selected)
75
+ onValueChange(secondsToIsoTime(time, seconds));
76
+ }
77
+ // Columns
78
+ const twelveHour = isTwelveHourFormat(format);
79
+ const half = Math.floor(base / 43200);
80
+ const baseHour = Math.floor(base / 3600);
81
+ const baseMinute = Math.floor(base / 60) % 60;
82
+ const hourInterval = step % 3600 === 0 ? gcd(step / 3600, 24) : 1;
83
+ const minuteInterval = step % 60 === 0 ? gcd(step / 60, 60) : 1;
84
+ const secondInterval = gcd(step, 60);
85
+ const isChosen = selected !== null;
86
+ const columns = useMemo(() => {
87
+ if (view !== "columns")
88
+ return [];
89
+ const hours = [];
90
+ const hourStart = twelveHour ? half * 12 : 0;
91
+ for (let hour = hourStart; hour < hourStart + (twelveHour ? 12 : 24); hour++) {
92
+ if (hour % hourInterval !== 0)
93
+ continue;
94
+ hours.push({
95
+ value: hour,
96
+ label: digits(twelveHour ? hour % 12 || 12 : hour),
97
+ selected: isChosen && hour === baseHour,
98
+ disabled: !anyAllowed(hour * 3600, hour * 3600 + 3599),
99
+ });
100
+ }
101
+ const minutes = [];
102
+ for (let minute = 0; minute < 60; minute += minuteInterval) {
103
+ const start = baseHour * 3600 + minute * 60;
104
+ minutes.push({
105
+ value: minute,
106
+ label: digits(minute),
107
+ selected: isChosen && minute === baseMinute,
108
+ disabled: seconds ? !anyAllowed(start, start + 59) : !allowed(start),
109
+ });
110
+ }
111
+ const result = [
112
+ { kind: "hour", items: hours },
113
+ { kind: "minute", items: minutes },
114
+ ];
115
+ if (seconds) {
116
+ const items = [];
117
+ for (let second = 0; second < 60; second += secondInterval) {
118
+ items.push({ value: second, label: digits(second), selected: isChosen && second === base % 60, disabled: !allowed(baseHour * 3600 + baseMinute * 60 + second) });
119
+ }
120
+ result.push({ kind: "second", items });
121
+ }
122
+ if (twelveHour) {
123
+ result.push({
124
+ kind: "meridiem",
125
+ items: getMeridiemNames(script).map((label, index) => ({
126
+ value: index,
127
+ label,
128
+ selected: isChosen && index === half,
129
+ disabled: !anyAllowed(index * 43200, index * 43200 + 43199),
130
+ })),
131
+ });
132
+ }
133
+ return result;
134
+ }, [view, base, isChosen, twelveHour, seconds, script, step, from, to, isTimeDisabled]);
135
+ // List
136
+ const slotStep = step >= 300 ? step : 1800;
137
+ const slots = useMemo(() => {
138
+ if (view !== "list")
139
+ return [];
140
+ return listTimeSlots(slotStep, minIso, maxIso).map(iso => {
141
+ const time = timeToSeconds(iso);
142
+ return { value: time, iso, label: formatTime(iso, { format, script }), disabled: !(time % step === 0 && !isTimeDisabled?.(secondsToIsoTime(time, seconds))) };
143
+ });
144
+ }, [view, slotStep, minIso, maxIso, format, script, step, isTimeDisabled, seconds]);
145
+ // The list's slot to scroll to and focus first: the chosen one, the one just before the chosen time, or the first free one.
146
+ const listAnchor = useMemo(() => {
147
+ if (view !== "list" || slots.length === 0)
148
+ return null;
149
+ if (selected !== null) {
150
+ const exact = slots.find(slot => slot.value === selected);
151
+ if (exact)
152
+ return exact.value;
153
+ const before = slots.filter(slot => slot.value <= selected).at(-1);
154
+ if (before)
155
+ return before.value;
156
+ }
157
+ return (slots.find(slot => !slot.disabled) ?? slots[0]).value;
158
+ }, [view, slots, selected]);
159
+ // Chosen items scroll to the middle row of their column, so they line up across columns with their neighbours above
160
+ // and below; the list centers its slot the same way. At once when the panel opens, smoothly after that, unless the
161
+ // user asked for reduced motion. (Columns are positioned, so an item's offsetTop is measured from the column.)
162
+ // With `autoFocus`, focus then moves in once.
163
+ useLayoutEffect(() => {
164
+ const root = rootRef.current;
165
+ if (!root)
166
+ return;
167
+ const align = () => {
168
+ const smooth = scrolledRef.current && !window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
169
+ for (const column of root.querySelectorAll("[data-column]")) {
170
+ const target = column.querySelector(view === "list" ? "[data-anchor]" : '[aria-selected="true"]');
171
+ if (!target)
172
+ continue;
173
+ const top = target.offsetTop - (column.clientHeight - target.offsetHeight) / 2;
174
+ if (Math.abs(column.scrollTop - top) > 1)
175
+ column.scrollTo({ top, behavior: smooth ? "smooth" : "instant" });
176
+ }
177
+ scrolledRef.current = true;
178
+ if (autoFocus && !focusedRef.current) {
179
+ focusedRef.current = true;
180
+ root.querySelector('[role="option"][tabindex="0"]')?.focus({ preventScroll: true });
181
+ }
182
+ };
183
+ if (root.getClientRects().length > 0) {
184
+ align();
185
+ return;
186
+ }
187
+ // On the first render the popover around the panel is still hidden (the picker shows it after this effect), and a
188
+ // hidden element can neither scroll nor take focus. The next frame comes before the first paint.
189
+ const frame = requestAnimationFrame(align);
190
+ return () => cancelAnimationFrame(frame);
191
+ });
192
+ function onColumnKeyDown(event, kind, items) {
193
+ const target = event.target.closest("[data-value]");
194
+ if (!target)
195
+ return;
196
+ const index = items.findIndex(item => String(item.value) === target.dataset.value);
197
+ const enabled = (from, direction) => {
198
+ for (let i = from; i >= 0 && i < items.length; i += direction)
199
+ if (!items[i].disabled)
200
+ return i;
201
+ return -1;
202
+ };
203
+ let next = -1;
204
+ switch (event.key) {
205
+ case "ArrowDown":
206
+ next = enabled(index + 1, 1);
207
+ break;
208
+ case "ArrowUp":
209
+ next = enabled(index - 1, -1);
210
+ break;
211
+ case "PageDown":
212
+ next = enabled(Math.min(index + PAGE, items.length - 1), 1);
213
+ if (next < 0)
214
+ next = enabled(items.length - 1, -1);
215
+ break;
216
+ case "PageUp":
217
+ next = enabled(Math.max(index - PAGE, 0), -1);
218
+ if (next < 0)
219
+ next = enabled(0, 1);
220
+ break;
221
+ case "Home":
222
+ next = enabled(0, 1);
223
+ break;
224
+ case "End":
225
+ next = enabled(items.length - 1, -1);
226
+ break;
227
+ case "ArrowLeft":
228
+ case "ArrowRight": {
229
+ if (kind === "slot")
230
+ return;
231
+ event.preventDefault();
232
+ const all = [...(rootRef.current?.querySelectorAll("[data-column]") ?? [])];
233
+ const column = all[all.indexOf(target.closest("[data-column]")) + (event.key === "ArrowRight" ? 1 : -1)];
234
+ column?.querySelector('[tabindex="0"]')?.focus({ preventScroll: true });
235
+ return;
236
+ }
237
+ case "Enter":
238
+ case " ":
239
+ event.preventDefault();
240
+ if (kind === "slot") {
241
+ const slot = items[index];
242
+ if (slot && !slot.disabled)
243
+ choose(slot.value);
244
+ }
245
+ else {
246
+ if (event.key === " " && index >= 0 && !items[index].disabled)
247
+ pick(kind, items[index].value);
248
+ else
249
+ onDone();
250
+ }
251
+ return;
252
+ default:
253
+ return;
254
+ }
255
+ event.preventDefault();
256
+ if (next < 0 || next === index)
257
+ return;
258
+ const item = items[next];
259
+ // Moving within a column keeps its items, so the element to focus is already there.
260
+ const element = target.parentElement?.querySelector(`[data-value="${item.value}"]`);
261
+ element?.focus({ preventScroll: true });
262
+ if (kind === "slot") {
263
+ // The list moves focus only; Enter or a click picks.
264
+ element?.scrollIntoView({ block: "nearest" });
265
+ }
266
+ else {
267
+ // A column is a spin button: the value follows focus, and the column scrolls it to the chosen row.
268
+ pick(kind, item.value);
269
+ }
270
+ }
271
+ function choose(time) {
272
+ if (time !== selected)
273
+ onValueChange(secondsToIsoTime(time, seconds));
274
+ onDone();
275
+ }
276
+ const now = useMemo(() => {
277
+ const time = Math.floor(timeToSeconds(nowIsoTime(true)) / step) * step;
278
+ return allowed(time) ? time : null;
279
+ // Read when the panel renders; a panel stays open for seconds, not hours.
280
+ }, [step, from, to, isTimeDisabled, seconds]);
281
+ if (view === "list") {
282
+ return (_jsx("div", { ref: rootRef, className: cx("nx-time", "nx-time--list", className), lang: devanagari ? "ne" : undefined, children: _jsxs("div", { role: "listbox", "aria-label": "Times", "data-column": "slot", className: "nx-time__list", tabIndex: -1, onKeyDown: event => onColumnKeyDown(event, "slot", slots), children: [slots.map(slot => (_jsx("div", { role: "option", "data-value": slot.value, "data-anchor": slot.value === listAnchor || undefined, "aria-selected": slot.value === selected, "aria-disabled": slot.disabled || undefined, tabIndex: slot.value === listAnchor ? 0 : -1, className: "nx-time__item nx-time__slot", onClick: () => !slot.disabled && choose(slot.value), children: slot.label }, slot.value))), slots.length === 0 && _jsx("div", { className: "nx-time__empty", children: "No times available" })] }) }));
283
+ }
284
+ return (_jsxs("div", { ref: rootRef, className: cx("nx-time", className), lang: devanagari ? "ne" : undefined, children: [_jsx("div", { className: "nx-time__columns", children: columns.map(({ kind, items }) => {
285
+ const reachable = items.find(item => item.selected && !item.disabled) ?? items.find(item => !item.disabled) ?? items[0];
286
+ return (_jsx("div", { role: "listbox", "aria-label": COLUMN_LABELS[kind], "data-column": kind, className: cx("nx-time__column", kind === "meridiem" && "nx-time__column--meridiem"), tabIndex: -1, onKeyDown: event => onColumnKeyDown(event, kind, items), children: items.map((item, index) => (_jsx("div", { role: "option", "data-value": item.value, "aria-selected": item.selected, "aria-disabled": item.disabled || undefined, tabIndex: item === reachable ? 0 : -1, className: "nx-time__item", onClick: () => !item.disabled && pick(kind, item.value), children: item.label }, kind === "hour" ? index : item.value))) }, kind));
287
+ }) }), footer && (_jsxs("div", { className: "nx-time__foot", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: now === null, onClick: () => now !== null && choose(now), children: "Now" }), _jsx(Button, { size: "sm", variant: "primary", onClick: onDone, children: "Done" })] }))] }));
288
+ }
@@ -0,0 +1,54 @@
1
+ import { type ChangeEvent, type ComponentPropsWithRef, type ReactNode } from "react";
2
+ import type { DateScript, TimeInput, TimeRules } from "../../Interfaces/DateInterfaces.ts";
3
+ import type { InputDesign, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
4
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
5
+ import { type TimePanelView } from "./TimePanel.tsx";
6
+ type NativeInputProps = Omit<ComponentPropsWithRef<"input">, "type" | "size" | "value" | "defaultValue" | "min" | "max" | "step" | "prefix" | "pattern" | "minLength" | "maxLength" | "children" | keyof TimeRules>;
7
+ export interface TimePickerProps extends NativeInputProps, TimeRules {
8
+ label?: ReactNode;
9
+ /** Help text under the box. */
10
+ hint?: ReactNode;
11
+ /** An error from outside, such as a server message. Shown before the field's own message. */
12
+ error?: string | null;
13
+ /** Default `standard`. */
14
+ design?: InputDesign;
15
+ /** Default `md`. */
16
+ size?: Size;
17
+ /** Decorative icon at the start. The clock button is always at the end. */
18
+ icon?: ReactNode;
19
+ /** Text before the value inside the box. */
20
+ prefix?: ReactNode;
21
+ /** How the time is written: `hh:mm A` (02:30 PM), `HH:mm` (14:30), ... Seconds are added when `step` needs them. Default: the user's setting. */
22
+ format?: string;
23
+ /** `devanagari` writes Nepali digits and पूर्वाह्न/अपराह्न. Default: Devanagari when the user's settings show Nepali dates in Devanagari. */
24
+ script?: DateScript;
25
+ /** `columns` (default): hours, minutes, and AM/PM, for any time. `list`: a slot every `step` (30 minutes for steps under 5), for bookings. */
26
+ view?: TimePanelView;
27
+ /** Times that cannot be picked, such as booked slots. Receives the ISO time. */
28
+ isTimeDisabled?: (iso: string) => boolean;
29
+ /** Shows a clear button while there is a value. Default true. */
30
+ clearable?: boolean;
31
+ /** What a read-only field says when someone tries to change it. Default "Read-only: this time cannot be changed." */
32
+ readOnlyMessage?: string;
33
+ /** Name used in messages, e.g. "Arrival is required." Default: the label, when it is text. */
34
+ fieldName?: string;
35
+ /** When the field shows its own message. Default `blur`; submitting a form always shows it. */
36
+ validateOn?: ValidateOn;
37
+ /** The time as an ISO string ("14:30"), or `null` for empty. A `Date` or an ISO date-time is accepted and read as its local time. */
38
+ value?: TimeInput;
39
+ defaultValue?: TimeInput;
40
+ /** After each change: the ISO time ("HH:mm", or "HH:mm:ss" when `step` has seconds), or `null` when the box is empty or holds text that is not a time. */
41
+ onValueChange?: (value: string | null, event: ChangeEvent<HTMLInputElement>) => void;
42
+ /** Class of the outer field. `inputClassName` goes on the input. */
43
+ className?: string;
44
+ inputClassName?: string;
45
+ }
46
+ /**
47
+ * A time input: type a time on either clock ("2:30 pm", "1430"), or pick one from the panel that opens under the box.
48
+ * Written in the user's time format, 12- or 24-hour, and always held as an ISO time with no date and no zone, which a
49
+ * form sends through a hidden input with the field's `name`. Uncontrolled by default, so typing re-renders nothing.
50
+ *
51
+ * @example <TimePicker name="arrival" label="Arrival" min="12:00" max="22:00" step={1800} view="list" />
52
+ */
53
+ export declare function TimePicker(props: TimePickerProps): import("react").JSX.Element;
54
+ export {};