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,399 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { iconCalendarTime, iconX, InlineIcon } from "nexus-icons";
4
+ import { useLayoutEffect, useRef, useState } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { clampIsoDate } from "../../Helpers/DateHelpers.js";
7
+ import { useBrowserTimeZone, useDatePreferences } from "../../Helpers/DatePreferences.js";
8
+ import { describeTimeZone, formatDateTime, fromLocalDateTime, parseLocalDateTimeText, timeScriptOf, toLocalDateTime, toTimeZone, toUtcDateTime } from "../../Helpers/DateTimeHelpers.js";
9
+ import { followAnchor, isPopoverOpen, placeUnderAnchor } from "../../Helpers/PopoverHelpers.js";
10
+ import { isSecondsInRange, normalizeTimeStep, SECONDS_PER_DAY, secondsToIsoTime, timeFormatWithSeconds, timeToSeconds, toIsoTime } from "../../Helpers/TimeHelpers.js";
11
+ import { DEFAULT_FIELD_NAME, validateDateTime } from "../../Helpers/ValidationHelpers.js";
12
+ import { Button } from "../Documents/Button.js";
13
+ import { Calendar } from "./Calendar.js";
14
+ import { Field, hasContent } from "./Field.js";
15
+ import { setInputValue, useInputField } from "./InputField.js";
16
+ import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
17
+ import { TimePanel } from "./TimePanel.js";
18
+ const PANEL_GAP = 4;
19
+ const PANEL_MARGIN = 8;
20
+ /**
21
+ * A date and time input for moments, such as a pickup, a deadline, or a reminder: type a date and time, or pick a day
22
+ * and a time from the panel that opens under the box. People see and pick their own local time, in their date and time
23
+ * formats (English or Nepali calendar, 12- or 24-hour clock); the value is always the UTC moment, which a form sends
24
+ * through a hidden input with the field's `name`, so no time zone ever moves it. A value is replaced only when the
25
+ * user changes what it shows: seconds and milliseconds the box does not show stay as they arrived.
26
+ *
27
+ * @example <DateTimePicker name="pickupAt" label="Pickup" required min={new Date()} step={900} />
28
+ */
29
+ export function DateTimePicker(props) {
30
+ const { label, hint, error, design = "standard", size = "md", icon, prefix, calendar: calendarProp, format: formatProp, timeFormat: timeFormatProp, script: scriptProp, timeZone: timeZoneProp, showTimeZone = Boolean(timeZoneProp), view = "columns", defaultTime, clearable = true, fieldName, readOnlyMessage = "Read-only: this date and time cannot be changed.", validateOn, value, defaultValue, onValueChange, isDateDisabled, isTimeDisabled, weekStart, weekendDays, showOutsideDays, className, inputClassName, style, required, min, max, minTime, maxTime, step, validate, name, form, placeholder, disabled, readOnly, ref, onChange, onBlur, onFocus, onInvalid, onKeyDown, onClick, ...inputProps } = props;
31
+ const preferences = useDatePreferences();
32
+ const browserZone = useBrowserTimeZone();
33
+ // Unknown on the server and while the page hydrates: the box stays empty until then rather than show another zone's time.
34
+ const zone = toTimeZone(timeZoneProp) ?? browserZone;
35
+ const stepSize = normalizeTimeStep(step);
36
+ const seconds = stepSize % 60 !== 0;
37
+ const options = {
38
+ calendar: calendarProp ?? preferences.calendar,
39
+ format: formatProp ?? preferences.format,
40
+ script: scriptProp ?? preferences.script,
41
+ timeFormat: timeFormatWithSeconds(timeFormatProp ?? preferences.timeFormat, seconds),
42
+ timeZone: zone ?? undefined,
43
+ seconds,
44
+ };
45
+ const editable = !disabled && !readOnly;
46
+ const initial = toUtcDateTime(value !== undefined ? value : defaultValue, zone ?? "UTC");
47
+ const text = (iso) => (zone ? formatDateTime(iso, options) : "");
48
+ /** A time of day at the precision the box shows: "14:30", or "14:30:15" when the step has seconds. */
49
+ const shownTime = (time) => (seconds ? time : time.slice(0, 5));
50
+ const messageName = fieldName ?? (typeof label === "string" ? label : DEFAULT_FIELD_NAME);
51
+ const startTime = shownTime(toIsoTime(defaultTime, true) ?? toIsoTime(minTime, true) ?? "00:00:00");
52
+ const zoneCity = zone && showTimeZone ? zone.slice(zone.lastIndexOf("/") + 1).replaceAll("_", " ") : null;
53
+ const valueRef = useRef(initial);
54
+ // The moment the box held before the text being typed: set by a pick, leaving the box, or a reset, and kept while
55
+ // the text is cleared or retyped, so every keystroke is read against it.
56
+ const committedRef = useRef(initial);
57
+ const editedRef = useRef(false);
58
+ // A value being picked: the text written for it reads back as this very moment.
59
+ const pendingRef = useRef(null);
60
+ const hiddenRef = useRef(null);
61
+ const panelRef = useRef(null);
62
+ const focusPanelRef = useRef(false);
63
+ const focusTimeRef = useRef(false);
64
+ const revealTimeRef = useRef(false);
65
+ const [open, setOpen] = useState(false);
66
+ // Re-renders the panel's selection while it is open; typing with it closed re-renders nothing.
67
+ const [panelValue, setPanelValue] = useState(initial);
68
+ const rules = {
69
+ required,
70
+ min,
71
+ max,
72
+ minTime,
73
+ maxTime,
74
+ step,
75
+ validate: iso => {
76
+ const local = iso && zone ? toLocalDateTime(iso, zone) : null;
77
+ if (local && (isDateDisabled?.(local.date) || isTimeDisabled?.(shownTime(local.time), local.date)))
78
+ return `${messageName} cannot be ${text(iso)}.`;
79
+ return validate?.(iso);
80
+ },
81
+ };
82
+ const field = useInputField({
83
+ ...props,
84
+ suffix: zoneCity,
85
+ onChange: event => {
86
+ const input = event.currentTarget;
87
+ const iso = input.value.trim() ? read(input.value) : null;
88
+ const changed = iso !== valueRef.current;
89
+ editedRef.current = true;
90
+ valueRef.current = iso;
91
+ sync(iso);
92
+ if (open)
93
+ setPanelValue(iso);
94
+ onChange?.(event);
95
+ if (changed)
96
+ onValueChange?.(iso, event);
97
+ },
98
+ }, {
99
+ // Text that is not a date and time is checked as itself, so the message says what to type.
100
+ check: (input, messageName) => validateDateTime(valueRef.current ?? (input.value.trim() ? input.value : null), rules, messageName, options),
101
+ checkKeys: [value, required, min, max, minTime, maxTime, step, validate, isDateDisabled, isTimeDisabled, zone, options.calendar, options.format, options.script, options.timeFormat],
102
+ onReset: input => {
103
+ // Back to the moment the field started with, written in the current settings.
104
+ editedRef.current = false;
105
+ valueRef.current = initial;
106
+ committedRef.current = initial;
107
+ input.value = text(initial);
108
+ sync(initial);
109
+ },
110
+ });
111
+ // A read-only box has no panel: a lock, and a note when someone tries to open or change it, say why.
112
+ const readOnlyNotice = useReadOnlyNotice(Boolean(readOnly) && !disabled, field.elementRef, readOnlyMessage, { keys: ["ArrowDown"] });
113
+ /**
114
+ * The moment typed text names in the zone. A date without a time takes the start time. Text that names what a pick
115
+ * being written, the value, or the committed value shows keeps that moment, with its hidden seconds and milliseconds,
116
+ * and the second of two equal times when clocks go back; other text in a repeated hour keeps the committed offset.
117
+ */
118
+ function read(typed) {
119
+ const local = zone ? parseLocalDateTimeText(typed, options) : null;
120
+ if (!zone || !local)
121
+ return null;
122
+ const time = local.time ? shownTime(local.time) : startTime;
123
+ for (const candidate of [pendingRef.current, valueRef.current, committedRef.current]) {
124
+ const shown = candidate ? toLocalDateTime(candidate, zone) : null;
125
+ if (shown && shown.date === local.date && shownTime(shown.time) === time)
126
+ return candidate;
127
+ }
128
+ return fromLocalDateTime(local.date, time, zone, pendingRef.current ?? committedRef.current);
129
+ }
130
+ /** Writes what depends on the value straight to the DOM: the hidden input a form sends. */
131
+ function sync(iso) {
132
+ if (hiddenRef.current)
133
+ hiddenRef.current.value = iso ?? "";
134
+ }
135
+ // A controlled value that differs from the box replaces its text. So does a default value that arrives after
136
+ // mount (from an API, say), as long as the user has not typed anything yet.
137
+ useLayoutEffect(() => {
138
+ const input = field.elementRef.current;
139
+ if (!input || (value === undefined && editedRef.current) || initial === valueRef.current)
140
+ return;
141
+ valueRef.current = initial;
142
+ if (initial)
143
+ committedRef.current = initial;
144
+ input.value = text(initial);
145
+ sync(initial);
146
+ setPanelValue(initial);
147
+ }, [initial]);
148
+ // The zone becoming known after hydration, another zone, or new settings rewrite the same moment.
149
+ useLayoutEffect(() => {
150
+ const input = field.elementRef.current;
151
+ if (input && valueRef.current)
152
+ input.value = text(valueRef.current);
153
+ // eslint-disable-next-line react-hooks/exhaustive-deps
154
+ }, [zone, options.calendar, options.format, options.script, options.timeFormat]);
155
+ // React sets a hidden input's default value again on every render, and for a hidden input that is its value.
156
+ // After each render, before paint, the hidden input takes the box's value back.
157
+ useLayoutEffect(() => {
158
+ sync(valueRef.current);
159
+ // A day picked by keyboard moves focus on to the time, once the panel shows the new day. In a panel too tall for
160
+ // the window, which scrolls, a picked day scrolls up to the time, and on to Done when there is room.
161
+ const panel = panelRef.current;
162
+ if (panel && revealTimeRef.current) {
163
+ revealTimeRef.current = false;
164
+ const time = panel.querySelector(".nx-datetime__time");
165
+ if (time && panel.scrollHeight > panel.clientHeight)
166
+ panel.scrollTop = Math.min(time.offsetTop, panel.scrollHeight - panel.clientHeight);
167
+ }
168
+ if (panel && focusTimeRef.current) {
169
+ focusTimeRef.current = false;
170
+ panel.querySelector('[data-column] [role="option"][tabindex="0"]')?.focus({ preventScroll: true });
171
+ }
172
+ });
173
+ // The panel is a manual popover under the box (above it when there is no room). It closes only through this
174
+ // component (Esc, Enter, Done, a slot of a list, focus or a press elsewhere), so the state always matches the screen.
175
+ useLayoutEffect(() => {
176
+ const panel = panelRef.current;
177
+ const box = field.elementRef.current?.closest(".nx-field__box");
178
+ if (!open || !panel || !box)
179
+ return;
180
+ if (!isPopoverOpen(panel))
181
+ panel.showPopover();
182
+ const place = () => {
183
+ const anchor = box.getBoundingClientRect();
184
+ // Too tall for either side, as on a phone: it takes the side with more room, and scrolls. The cap changes only
185
+ // when the room does, so scrolling inside the panel (which runs this again) keeps its place.
186
+ const natural = panel.scrollHeight + panel.offsetHeight - panel.clientHeight;
187
+ const room = Math.floor(Math.max(window.innerHeight - anchor.bottom, anchor.top) - PANEL_GAP - PANEL_MARGIN);
188
+ const cap = natural > room ? `${Math.max(0, room)}px` : "";
189
+ if (panel.style.maxHeight !== cap)
190
+ panel.style.maxHeight = cap;
191
+ placeUnderAnchor(panel, anchor, { gap: PANEL_GAP, margin: PANEL_MARGIN });
192
+ };
193
+ place();
194
+ if (focusPanelRef.current) {
195
+ focusPanelRef.current = false;
196
+ panel.querySelector('[role="gridcell"][tabindex="0"]')?.focus();
197
+ }
198
+ const stopFollowing = followAnchor(place);
199
+ const onPointerDown = (event) => {
200
+ const target = event.target;
201
+ if (!panel.contains(target) && !box.contains(target))
202
+ closePanel();
203
+ };
204
+ document.addEventListener("pointerdown", onPointerDown, true);
205
+ return () => {
206
+ stopFollowing();
207
+ document.removeEventListener("pointerdown", onPointerDown, true);
208
+ if (isPopoverOpen(panel))
209
+ panel.hidePopover();
210
+ };
211
+ }, [open]);
212
+ function openPanel(focusCalendar) {
213
+ if (!editable || !zone)
214
+ return;
215
+ // The box is the control: it keeps focus while the panel is open, so Esc and Enter reach it.
216
+ field.elementRef.current?.focus();
217
+ focusPanelRef.current = focusCalendar;
218
+ // Shown before its content renders, so the time columns can scroll as they mount. Nothing is painted in between.
219
+ const panel = panelRef.current;
220
+ if (panel && !isPopoverOpen(panel))
221
+ panel.showPopover();
222
+ setPanelValue(valueRef.current);
223
+ setOpen(true);
224
+ }
225
+ /** Closes the panel. With `returnFocus`, focus that was inside the panel goes back to the box (Esc, Enter, Done). */
226
+ function closePanel(returnFocus = false) {
227
+ const panel = panelRef.current;
228
+ const focusInPanel = Boolean(panel?.contains(document.activeElement));
229
+ if (isPopoverOpen(panel))
230
+ panel.hidePopover();
231
+ setOpen(false);
232
+ if (returnFocus && focusInPanel)
233
+ field.elementRef.current?.focus();
234
+ }
235
+ function select(iso) {
236
+ const input = field.elementRef.current;
237
+ if (!input || !iso)
238
+ return;
239
+ pendingRef.current = iso;
240
+ setInputValue(input, text(iso));
241
+ pendingRef.current = null;
242
+ committedRef.current = valueRef.current;
243
+ field.touch();
244
+ }
245
+ function clear() {
246
+ const input = field.elementRef.current;
247
+ if (!input)
248
+ return;
249
+ setInputValue(input, "");
250
+ input.focus();
251
+ }
252
+ function handleKeyDown(event) {
253
+ const { key } = event;
254
+ readOnlyNotice.onKeyDown(event);
255
+ if (editable && key === "ArrowDown") {
256
+ event.preventDefault();
257
+ if (open)
258
+ panelRef.current?.querySelector('[role="gridcell"][tabindex="0"]')?.focus();
259
+ else
260
+ openPanel(true);
261
+ }
262
+ else if (key === "Escape" && open) {
263
+ // Closes the panel only: the page (a dialog, say) does not see this Esc.
264
+ event.preventDefault();
265
+ event.stopPropagation();
266
+ closePanel();
267
+ }
268
+ else if (clearable && editable && key === "Escape" && event.currentTarget.value) {
269
+ event.preventDefault();
270
+ event.stopPropagation();
271
+ clear();
272
+ }
273
+ else if (key === "Enter" && open) {
274
+ event.preventDefault();
275
+ closePanel();
276
+ }
277
+ else if (key === "Tab" && open) {
278
+ // Tab moves on to the next field, not into the panel; ↓ is the way in.
279
+ closePanel();
280
+ }
281
+ onKeyDown?.(event);
282
+ }
283
+ function handleClick(event) {
284
+ if (!open)
285
+ openPanel(false);
286
+ readOnlyNotice.onClick(event);
287
+ onClick?.(event);
288
+ }
289
+ function handleBlur(event) {
290
+ // Typed text takes the canonical form once it is a date and time.
291
+ const input = event.currentTarget;
292
+ if (valueRef.current && input.value !== text(valueRef.current))
293
+ input.value = text(valueRef.current);
294
+ if (valueRef.current)
295
+ committedRef.current = valueRef.current;
296
+ // Focus moving anywhere but the panel closes it.
297
+ if (open && !(event.relatedTarget instanceof Node && panelRef.current?.contains(event.relatedTarget)))
298
+ closePanel();
299
+ readOnlyNotice.hide();
300
+ field.controlProps.onBlur(event);
301
+ }
302
+ // Focus leaving the panel for anything but the box (Tab past Done, a click elsewhere) closes it.
303
+ function handlePanelBlur(event) {
304
+ const next = event.relatedTarget;
305
+ if (next instanceof Node && (event.currentTarget.contains(next) || next === field.elementRef.current))
306
+ return;
307
+ closePanel();
308
+ }
309
+ function handlePanelKeyDown(event) {
310
+ if (event.key !== "Escape")
311
+ return;
312
+ event.preventDefault();
313
+ event.stopPropagation();
314
+ closePanel(true);
315
+ }
316
+ /** The panel's content: the calendar, the time on the day shown, and Now, the zone, and Done. Built only while open. */
317
+ function renderPanel(zone) {
318
+ const now = Date.now();
319
+ const today = toLocalDateTime(now, zone).date;
320
+ const minValue = toUtcDateTime(min, zone);
321
+ const maxValue = toUtcDateTime(max, zone);
322
+ const from = minValue ? toLocalDateTime(minValue, zone) : null;
323
+ const to = maxValue ? toLocalDateTime(maxValue, zone) : null;
324
+ const chosen = panelValue ? toLocalDateTime(panelValue, zone) : null;
325
+ const minTimeIso = toIsoTime(minTime, true);
326
+ const maxTimeIso = toIsoTime(maxTime, true);
327
+ const dailyFrom = minTimeIso === null ? null : timeToSeconds(minTimeIso);
328
+ const dailyTo = maxTimeIso === null ? null : timeToSeconds(maxTimeIso);
329
+ // The day the time columns work on: the chosen one, or today kept inside the limits.
330
+ const day = chosen?.date ?? clampIsoDate(today, from?.date, to?.date);
331
+ /** The seconds of a day inside `min` and `max`, or `null` when none are. */
332
+ const boundsOf = (date) => {
333
+ let low = 0;
334
+ let high = SECONDS_PER_DAY - 1;
335
+ if (from && date < from.date)
336
+ return null;
337
+ // A limit with milliseconds starts at the next whole second.
338
+ if (from && date === from.date)
339
+ low = timeToSeconds(from.time) + (minValue.endsWith(".000Z") ? 0 : 1);
340
+ if (to && date > to.date)
341
+ return null;
342
+ if (to && date === to.date)
343
+ high = timeToSeconds(to.time);
344
+ return low <= high ? [low, high] : null;
345
+ };
346
+ const allowedIn = (date, bounds, time) => bounds !== null &&
347
+ time >= bounds[0] &&
348
+ time <= bounds[1] &&
349
+ isSecondsInRange(time, dailyFrom, dailyTo) &&
350
+ time % stepSize === 0 &&
351
+ !isTimeDisabled?.(secondsToIsoTime(time, seconds), date);
352
+ /** The allowed time of a day nearest after a wanted time, or before it when none comes after; `null` when the day has none. */
353
+ const nearestAllowed = (date, wanted) => {
354
+ const bounds = boundsOf(date);
355
+ if (!bounds || isDateDisabled?.(date))
356
+ return null;
357
+ for (let time = Math.ceil(wanted / stepSize) * stepSize; time < SECONDS_PER_DAY; time += stepSize)
358
+ if (allowedIn(date, bounds, time))
359
+ return time;
360
+ for (let time = Math.floor(wanted / stepSize) * stepSize - stepSize; time >= 0; time -= stepSize)
361
+ if (allowedIn(date, bounds, time))
362
+ return time;
363
+ return null;
364
+ };
365
+ function pickDay(date) {
366
+ const wanted = timeToSeconds(chosen ? shownTime(chosen.time) : startTime);
367
+ const time = nearestAllowed(date, wanted) ?? wanted;
368
+ // Picked by keyboard: focus moves on to the time.
369
+ focusTimeRef.current = Boolean(panelRef.current?.querySelector(".nx-cal")?.contains(document.activeElement));
370
+ revealTimeRef.current = true;
371
+ select(fromLocalDateTime(date, secondsToIsoTime(time, seconds), zone, panelValue));
372
+ }
373
+ // Now, on the step: the step before it when allowed, else the step after, e.g. with `min` set to the page's now.
374
+ const nowLocal = toLocalDateTime(now, zone);
375
+ const nowSeconds = timeToSeconds(nowLocal.time);
376
+ const nowBounds = isDateDisabled?.(nowLocal.date) ? null : boundsOf(nowLocal.date);
377
+ const nowTime = [Math.floor(nowSeconds / stepSize) * stepSize, Math.ceil(nowSeconds / stepSize) * stepSize].find(time => time < SECONDS_PER_DAY && allowedIn(nowLocal.date, nowBounds, time));
378
+ const dayBounds = isDateDisabled?.(day) ? null : boundsOf(day);
379
+ const info = describeTimeZone(zone, panelValue ?? now);
380
+ return (_jsxs("div", { className: "nx-datetime", children: [_jsxs("div", { className: "nx-datetime__body", children: [_jsx(Calendar, { size: "sm", footer: false, className: "nx-datetime__calendar", calendar: options.calendar, script: options.script, value: chosen?.date ?? null, defaultMonth: day, today: today, onSelect: pickDay, min: from?.date, max: to?.date, isDateDisabled: isDateDisabled, weekStart: weekStart, weekendDays: weekendDays, showOutsideDays: showOutsideDays, "aria-label": "Calendar" }), _jsx(TimePanel, { className: "nx-datetime__time", view: view, footer: false, value: chosen ? shownTime(chosen.time) : null, onValueChange: time => select(fromLocalDateTime(day, time, zone, panelValue)), onDone: () => closePanel(true), format: options.timeFormat, script: timeScriptOf(options), seconds: seconds, step: step, min: minTime, max: maxTime, isTimeDisabled: time => {
381
+ const total = timeToSeconds(time);
382
+ return dayBounds === null || total < dayBounds[0] || total > dayBounds[1] || Boolean(isTimeDisabled?.(time, day));
383
+ } })] }), _jsxs("div", { className: "nx-datetime__foot", children: [_jsx(Button, { size: "sm", variant: "ghost", hoverTone: "primary", disabled: nowTime === undefined, onClick: () => {
384
+ if (nowTime === undefined)
385
+ return;
386
+ select(fromLocalDateTime(nowLocal.date, secondsToIsoTime(nowTime, seconds), zone));
387
+ closePanel(true);
388
+ }, children: "Now" }), _jsx("span", { className: "nx-datetime__zone", title: info.name, children: info.id === "UTC" ? "UTC" : `${info.city} · ${info.offset}` }), _jsx(Button, { size: "sm", variant: "primary", onClick: () => closePanel(true), children: "Done" })] })] }));
389
+ }
390
+ const showClear = clearable && editable;
391
+ return (_jsxs(_Fragment, { children: [_jsxs(Field, { controlId: field.controlId, messageId: field.messageId, label: label, required: required, design: design, size: size, hint: hint, error: field.error, icon: icon, prefix: prefix, suffix: zoneCity, actions: _jsxs(_Fragment, { children: [showClear && (_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 }) })), readOnlyNotice.lock, !readOnly && (_jsx("button", { type: "button", className: "nx-field__action nx-field__action--datetime", tabIndex: -1, "aria-label": "Choose a date and time", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? `${field.controlId}-datetime` : undefined, title: "Choose a date and time", disabled: disabled, onMouseDown: event => event.preventDefault(), onClick: () => (open ? closePanel() : openPanel(false)), children: _jsx(InlineIcon, { icon: iconCalendarTime }) }))] }), disabled: disabled, readOnly: readOnly, raiseLabel: Boolean(placeholder) || hasContent(prefix), className: cx("nx-field--datetime", className), style: style, children: [_jsx("input", { autoComplete: "off", spellCheck: false, ...inputProps, ...field.controlProps, type: "text", name: undefined, form: form, lang: timeScriptOf(options) === "devanagari" ? "ne" : undefined, defaultValue: text(initial), className: cx("nx-field__control", "nx-field__control--datetime", inputClassName), placeholder: placeholder || " ", disabled: disabled, readOnly: readOnly, onBlur: handleBlur, onFocus: onFocus, onKeyDown: handleKeyDown, onClick: handleClick }), name && _jsx("input", { ref: hiddenRef, type: "hidden", name: name, form: form, disabled: disabled, defaultValue: initial ?? "" })] }), readOnlyNotice.notice, !readOnly && (_jsx("div", { ref: panelRef, id: `${field.controlId}-datetime`, popover: "manual", role: "dialog", "aria-label": "Choose a date and time", className: cx("nx-popover", "nx-datetime-popover"), onKeyDown: handlePanelKeyDown, onBlur: handlePanelBlur,
392
+ // The box keeps focus while days, times, and buttons are clicked. The month and year menus need focus to
393
+ // open, and a press on a time column itself is left alone, so its scroll bar can be dragged.
394
+ onMouseDown: event => {
395
+ const target = event.target;
396
+ if (!target.closest("select") && !target.matches("[data-column]"))
397
+ event.preventDefault();
398
+ }, children: open && zone && renderPanel(zone) }))] }));
399
+ }
@@ -0,0 +1,117 @@
1
+ import { type ComponentPropsWithRef, type ReactNode } from "react";
2
+ import type { ChoiceOption, DropdownColumn, DropdownItem, DropdownPage, DropdownPageRequest, InputDesign, OptionValue, SelectionRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
3
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
4
+ type NativeInputProps = Omit<ComponentPropsWithRef<"input">, "type" | "size" | "value" | "defaultValue" | "min" | "max" | "step" | "prefix" | "pattern" | "minLength" | "maxLength" | "children" | "multiple" | "list" | "onChange" | "onInput">;
5
+ interface DropdownBaseProps<TItem extends DropdownItem, TValue extends OptionValue> extends NativeInputProps, Pick<SelectionRules<TValue>, "required" | "validate"> {
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 start of the box. */
16
+ icon?: ReactNode;
17
+ /** The options, for a list the page already has. Searched in the browser. Ignored for picking when `loadOptions` is set. */
18
+ options?: readonly TItem[];
19
+ /** Loads the options page by page from a server, for the search typed: the first page when the list opens, the next near its end. */
20
+ loadOptions?: (request: DropdownPageRequest) => Promise<DropdownPage<TItem>>;
21
+ /** Change it to drop the loaded pages and load again, e.g. the id of the country whose cities `loadOptions` loads. */
22
+ loadKey?: unknown;
23
+ /** Items per page for `loadOptions`. Default 50. */
24
+ pageSize?: number;
25
+ /**
26
+ * The items of the current value, when the list may not hold them: a saved record's country on page 7 of a server list,
27
+ * or a user who has since become inactive. They show the value, and are never offered for picking unless the list has them.
28
+ */
29
+ selectedOptions?: readonly TItem[];
30
+ /** Loads the items of values that no list or `selectedOptions` has, e.g. by id. Values it does not return show as "Not found". */
31
+ resolveOptions?: (values: TValue[]) => Promise<readonly TItem[]>;
32
+ /** The item field that holds the value. Default `value`. */
33
+ valueKey?: string;
34
+ /** The item field shown as its name, in the box and in the list. Default `label`. */
35
+ labelKey?: string;
36
+ /** A second line under the label, in rows without columns. Default `description`. */
37
+ descriptionKey?: string;
38
+ /** A Tabler icon name or an element before the label, in rows without columns. Default `icon`. */
39
+ iconKey?: string;
40
+ /** Rows as columns, like a data table: an avatar, a username, and a full name; or a flag, a code, and a country. */
41
+ columns?: readonly DropdownColumn<TItem>[];
42
+ /** The fields a typed search looks in. Default: the label, the description, the value, and the text and code columns. */
43
+ searchKeys?: readonly string[];
44
+ /** Items shown but not pickable, such as a sold-out room. Default: `disabled: true`. */
45
+ isOptionDisabled?: (item: TItem) => boolean;
46
+ /** Items never offered: they show only as a current value, and cannot be picked again once removed. Default: `inactive: true` or `isActive: false`. */
47
+ isOptionInactive?: (item: TItem) => boolean;
48
+ /** Typing in the box searches the list. Default: on with `loadOptions` or more than 8 options; off, typed letters jump to an option. */
49
+ searchable?: boolean;
50
+ /** A button that clears the value. Default true. */
51
+ clearable?: boolean;
52
+ /** Options are on their way (the page loads them itself): a spinner in the box, and placeholder rows in the list. */
53
+ loading?: boolean;
54
+ /** Shown in an empty list. Default "No options". */
55
+ emptyMessage?: string;
56
+ /** Draws the chosen item in the box instead of its picture and label. */
57
+ renderValue?: (item: TItem) => ReactNode;
58
+ /** Width of the list's panel, e.g. 420 or "28rem". Default: the box's width, or wider for its content. */
59
+ panelWidth?: number | string;
60
+ /** What a read-only field says when someone tries to change it. Default "Read-only: this choice cannot be changed." */
61
+ readOnlyMessage?: string;
62
+ /** Name used in messages, e.g. "Country is required." Default: the label, when it is text. */
63
+ fieldName?: string;
64
+ /** When the field shows its own message. Default `blur`; submitting a form always shows it. */
65
+ validateOn?: ValidateOn;
66
+ /** Class of the outer field. `inputClassName` goes on the input. */
67
+ className?: string;
68
+ inputClassName?: string;
69
+ }
70
+ export interface SingleDropdownProps<TItem extends DropdownItem, TValue extends OptionValue> extends DropdownBaseProps<TItem, TValue> {
71
+ multiple?: false;
72
+ /** The chosen value, or `null` for none. With `value`, the dropdown is controlled; pair it with `onValueChange`. */
73
+ value?: TValue | null;
74
+ defaultValue?: TValue | null;
75
+ /** After each change: the value and its item, or `null` when cleared. The item is `null` for a value nothing is known about. */
76
+ onValueChange?: (value: TValue | null, item: TItem | null) => void;
77
+ min?: never;
78
+ max?: never;
79
+ selectAll?: never;
80
+ maxChips?: never;
81
+ }
82
+ export interface MultipleDropdownProps<TItem extends DropdownItem, TValue extends OptionValue> extends DropdownBaseProps<TItem, TValue> {
83
+ multiple: true;
84
+ /** The chosen values, in the order they were picked. With `value`, the dropdown is controlled. */
85
+ value?: readonly TValue[];
86
+ defaultValue?: readonly TValue[];
87
+ /** After each change: the values, and their items in the same order (`null` for values nothing is known about). */
88
+ onValueChange?: (values: TValue[], items: (TItem | null)[]) => void;
89
+ /** Fewest choices. Not checked when nothing is chosen; use `required` for that. */
90
+ min?: number;
91
+ /** Most choices. The other options are disabled at this count. */
92
+ max?: number;
93
+ /**
94
+ * Select all and Clear buttons in the list's footer, beside the count and Done. They act on what the list shows: every option, or the matches of a
95
+ * typed search. A server list with more pages to load selects the rows loaded ("Select 30 shown"). Default true.
96
+ */
97
+ selectAll?: boolean;
98
+ /** The most chips the box shows: the rest count in one "+12 more" chip, so a long selection keeps the box small. 0 shows "12 selected". Default 10. */
99
+ maxChips?: number;
100
+ }
101
+ export type DropdownProps<TItem extends DropdownItem = ChoiceOption<OptionValue>, TValue extends OptionValue = OptionValue> = SingleDropdownProps<TItem, TValue> | MultipleDropdownProps<TItem, TValue>;
102
+ /**
103
+ * Pick one value, or several with `multiple`, from options the page has or pages a server sends while the user
104
+ * searches and scrolls. Rows can be columns like a data table (avatar, username, full name; flag, code, country).
105
+ *
106
+ * The value always shows, even when the list cannot offer it: a saved value on a page not loaded yet (pass
107
+ * `selectedOptions` or `resolveOptions` for its label), an inactive item, or an item no longer among the options.
108
+ * Such values sit at the top of the list with a tag, can be removed, and cannot be picked again.
109
+ *
110
+ * A form gets the value through hidden inputs named `name` (one per value with `multiple`). The list renders only
111
+ * while it is open, so a form of many dropdowns mounts light.
112
+ *
113
+ * @example <Dropdown name="country" label="Country" options={countries} valueKey="code" labelKey="name" columns={[{ key: "flag", type: "image" }, { key: "code", type: "code" }, { key: "name" }]} />
114
+ */
115
+ export declare function Dropdown<TItem extends DropdownItem = ChoiceOption<OptionValue>, TValue extends OptionValue = OptionValue>(props: MultipleDropdownProps<TItem, TValue>): ReactNode;
116
+ export declare function Dropdown<TItem extends DropdownItem = ChoiceOption<OptionValue>, TValue extends OptionValue = OptionValue>(props: SingleDropdownProps<TItem, TValue>): ReactNode;
117
+ export {};