nexus-shared 1.1.21 → 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 (333) 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 -29
  238. package/src/client.ts +0 -31
  239. package/src/components/documents/button.tsx +0 -133
  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 -64
  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 -123
  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 -53
  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 -163
  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.grid-view.css +0 -42
  329. package/src/styles/nexus.icon.css +0 -51
  330. package/src/styles/nexus.input.css +0 -207
  331. package/src/styles/nexus.loader.css +0 -11
  332. package/src/styles/nexus.logic.css +0 -47
  333. package/src/styles/nexus.utility.css +0 -347
@@ -0,0 +1,363 @@
1
+ import { DEFAULT_DATE_PREFERENCES } from "../Interfaces/DateInterfaces.js";
2
+ import { BS_FIRST_YEAR, BS_LAST_YEAR, BS_MAX_DAY_NUMBER, BS_MIN_DAY_NUMBER, bsMonthLength, bsToDayNumber, dayNumberToBs } from "./NepaliCalendar.js";
3
+ /*
4
+ * Pure date helpers. A date is an ISO string "YYYY-MM-DD" in the Gregorian calendar, with no time and no zone, so the
5
+ * same value means the same day on every machine. Arithmetic runs on day numbers (days since 1970-01-01), never on
6
+ * `Date` objects, which carry the browser's zone and shift a day at midnight. The Nepali calendar (Bikram Sambat) is
7
+ * a second way of naming the same days: `toCalendar` and `fromCalendar` convert, and the value stays Gregorian.
8
+ */
9
+ export const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
10
+ const pad = (value, width) => String(value).padStart(width, "0");
11
+ /** Day number of a Gregorian date: days since 1970-01-01, proleptic, no zone (Howard Hinnant's algorithm). */
12
+ export function daysFromCivil(year, month, day) {
13
+ const y = month <= 2 ? year - 1 : year;
14
+ const era = Math.floor(y / 400);
15
+ const yearOfEra = y - era * 400;
16
+ const dayOfYear = Math.floor((153 * (month + (month > 2 ? -3 : 9)) + 2) / 5) + day - 1;
17
+ const dayOfEra = yearOfEra * 365 + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100) + dayOfYear;
18
+ return era * 146097 + dayOfEra - 719468;
19
+ }
20
+ /** The Gregorian date of a day number. */
21
+ export function civilFromDays(dayNumber) {
22
+ const z = dayNumber + 719468;
23
+ const era = Math.floor(z / 146097);
24
+ const dayOfEra = z - era * 146097;
25
+ const yearOfEra = Math.floor((dayOfEra - Math.floor(dayOfEra / 1460) + Math.floor(dayOfEra / 36524) - Math.floor(dayOfEra / 146096)) / 365);
26
+ const dayOfYear = dayOfEra - (365 * yearOfEra + Math.floor(yearOfEra / 4) - Math.floor(yearOfEra / 100));
27
+ const monthIndex = Math.floor((5 * dayOfYear + 2) / 153);
28
+ const day = dayOfYear - Math.floor((153 * monthIndex + 2) / 5) + 1;
29
+ const month = monthIndex + (monthIndex < 10 ? 3 : -9);
30
+ return { year: yearOfEra + era * 400 + (month <= 2 ? 1 : 0), month, day };
31
+ }
32
+ export const partsToIsoDate = ({ year, month, day }) => `${pad(year, 4)}-${pad(month, 2)}-${pad(day, 2)}`;
33
+ /** The parts of an ISO date. The text must be a valid date (`isIsoDate`). */
34
+ export function isoDateToParts(iso) {
35
+ return { year: Number(iso.slice(0, 4)), month: Number(iso.slice(5, 7)), day: Number(iso.slice(8, 10)) };
36
+ }
37
+ export const isoDateToDayNumber = (iso) => {
38
+ const { year, month, day } = isoDateToParts(iso);
39
+ return daysFromCivil(year, month, day);
40
+ };
41
+ export const dayNumberToIsoDate = (dayNumber) => partsToIsoDate(civilFromDays(dayNumber));
42
+ const isLeapYear = (year) => (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
43
+ const GREGORIAN_MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
44
+ export function gregorianMonthLength(year, month) {
45
+ if (month < 1 || month > 12)
46
+ return 0;
47
+ return month === 2 && isLeapYear(year) ? 29 : GREGORIAN_MONTHS[month - 1];
48
+ }
49
+ /** Whether text is an ISO date that exists: "2026-02-30" is not. */
50
+ export function isIsoDate(text) {
51
+ if (typeof text !== "string")
52
+ return false;
53
+ const match = ISO_DATE_PATTERN.exec(text);
54
+ if (!match)
55
+ return false;
56
+ const year = Number(match[1]);
57
+ const day = Number(match[3]);
58
+ return year >= 1 && day >= 1 && day <= gregorianMonthLength(year, Number(match[2]));
59
+ }
60
+ /**
61
+ * Turns anything a date can arrive as into an ISO date, or `null`:
62
+ * - "2026-09-17" (or "2026-9-7") stays that day;
63
+ * - an ISO date-time with a zone, "2026-09-16T18:15:00Z", becomes the browser's local date of that instant;
64
+ * - one without a zone, "2026-09-17T00:00:00", keeps its date part;
65
+ * - a `Date` is read in local time, like `new Date()` for today.
66
+ * Text that is not a date, or a day that does not exist, gives `null`.
67
+ */
68
+ export function toIsoDate(input) {
69
+ if (input === null || input === undefined || input === "")
70
+ return null;
71
+ if (input instanceof Date) {
72
+ return Number.isNaN(input.getTime()) ? null : partsToIsoDate({ year: input.getFullYear(), month: input.getMonth() + 1, day: input.getDate() });
73
+ }
74
+ const text = input.trim();
75
+ const dateOnly = /^(\d{4})-(\d{1,2})-(\d{1,2})$/.exec(text);
76
+ if (dateOnly) {
77
+ const iso = partsToIsoDate({ year: Number(dateOnly[1]), month: Number(dateOnly[2]), day: Number(dateOnly[3]) });
78
+ return isIsoDate(iso) ? iso : null;
79
+ }
80
+ const dateTime = /^(\d{4}-\d{2}-\d{2})[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?$/i.exec(text);
81
+ if (!dateTime)
82
+ return null;
83
+ if (!dateTime[2])
84
+ return isIsoDate(dateTime[1]) ? dateTime[1] : null;
85
+ return toIsoDate(new Date(text));
86
+ }
87
+ /** A `Date` at midnight of the day: in UTC (the default, for JSON and APIs) or in the browser's local time. */
88
+ export function isoDateToDate(iso, zone = "utc") {
89
+ const { year, month, day } = isoDateToParts(iso);
90
+ return zone === "utc" ? new Date(Date.UTC(year, month - 1, day)) : new Date(year, month - 1, day);
91
+ }
92
+ /** Today's date where the browser is. */
93
+ export function todayIsoDate() {
94
+ return toIsoDate(new Date());
95
+ }
96
+ export const addDays = (iso, days) => dayNumberToIsoDate(isoDateToDayNumber(iso) + days);
97
+ /** Negative when `a` is before `b`. ISO dates compare as text. */
98
+ export const compareIsoDates = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
99
+ /** Days from one date to another: `diffDays("2026-09-17", "2026-09-20")` is 3. */
100
+ export const diffDays = (from, to) => isoDateToDayNumber(to) - isoDateToDayNumber(from);
101
+ /** 0 for Sunday to 6 for Saturday. */
102
+ export const weekdayOf = (iso) => (((isoDateToDayNumber(iso) + 4) % 7) + 7) % 7;
103
+ export function clampIsoDate(iso, min, max) {
104
+ if (min && iso < min)
105
+ return min;
106
+ if (max && iso > max)
107
+ return max;
108
+ return iso;
109
+ }
110
+ /** The days the calendar covers: Gregorian years 1 to 9999, Bikram Sambat 1975 to 2100 (AD 1918 to 2044). */
111
+ export const CALENDAR_RANGE = {
112
+ ad: { min: "0001-01-01", max: "9999-12-31" },
113
+ bs: { min: dayNumberToIsoDate(BS_MIN_DAY_NUMBER), max: dayNumberToIsoDate(BS_MAX_DAY_NUMBER) },
114
+ };
115
+ export function daysInMonth(year, month, calendar) {
116
+ return calendar === "bs" ? bsMonthLength(year, month) : gregorianMonthLength(year, month);
117
+ }
118
+ /** Whether the parts name a day of the calendar. */
119
+ export function isValidParts(parts, calendar) {
120
+ const { year, month, day } = parts;
121
+ if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day))
122
+ return false;
123
+ if (calendar === "ad" ? year < 1 || year > 9999 : year < BS_FIRST_YEAR || year > BS_LAST_YEAR)
124
+ return false;
125
+ return day >= 1 && day <= daysInMonth(year, month, calendar);
126
+ }
127
+ /** The day as parts of the calendar, or `null` when the Nepali table does not reach it. */
128
+ export function toCalendar(iso, calendar) {
129
+ return calendar === "bs" ? dayNumberToBs(isoDateToDayNumber(iso)) : isoDateToParts(iso);
130
+ }
131
+ /** The ISO date of parts in the calendar, or `null` when the day does not exist. */
132
+ export function fromCalendar(parts, calendar) {
133
+ if (calendar === "bs") {
134
+ const dayNumber = bsToDayNumber(parts.year, parts.month, parts.day);
135
+ return dayNumber === null ? null : dayNumberToIsoDate(dayNumber);
136
+ }
137
+ return isValidParts(parts, "ad") ? partsToIsoDate(parts) : null;
138
+ }
139
+ /** The same day of another month, or the month's last day when it is shorter. Years stay inside the calendar. */
140
+ export function addMonths(parts, months, calendar) {
141
+ const index = parts.year * 12 + parts.month - 1 + months;
142
+ let year = Math.floor(index / 12);
143
+ let month = index - year * 12 + 1;
144
+ const first = calendar === "bs" ? BS_FIRST_YEAR : 1;
145
+ const last = calendar === "bs" ? BS_LAST_YEAR : 9999;
146
+ if (year < first)
147
+ [year, month] = [first, 1];
148
+ if (year > last)
149
+ [year, month] = [last, 12];
150
+ return { year, month, day: Math.min(parts.day, daysInMonth(year, month, calendar)) };
151
+ }
152
+ // Names
153
+ const EN_MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
154
+ const EN_MONTHS_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
155
+ const NE_MONTHS = ["Baisakh", "Jestha", "Asar", "Shrawan", "Bhadra", "Ashwin", "Kartik", "Mangsir", "Poush", "Magh", "Falgun", "Chaitra"];
156
+ const NE_MONTHS_SHORT = ["Bai", "Jes", "Asa", "Shr", "Bha", "Ash", "Kar", "Man", "Pou", "Mag", "Fal", "Cha"];
157
+ const NE_MONTHS_DEVA = ["बैशाख", "जेठ", "असार", "साउन", "भदौ", "असोज", "कात्तिक", "मंसिर", "पुष", "माघ", "फागुन", "चैत"];
158
+ const EN_WEEKDAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
159
+ const EN_WEEKDAYS_SHORT = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
160
+ const EN_WEEKDAYS_MIN = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
161
+ const NE_WEEKDAYS_DEVA = ["आइतबार", "सोमबार", "मङ्गलबार", "बुधबार", "बिहिबार", "शुक्रबार", "शनिबार"];
162
+ const NE_WEEKDAYS_DEVA_SHORT = ["आइत", "सोम", "मङ्गल", "बुध", "बिहि", "शुक्र", "शनि"];
163
+ const NE_WEEKDAYS_DEVA_MIN = ["आ", "सो", "मं", "बु", "बि", "शु", "श"];
164
+ // Other spellings a typed month name may use, in lower case.
165
+ const NE_MONTH_ALIASES = [
166
+ ["baishakh", "vaishakh", "vaisakh", "baisak", "वैशाख"],
167
+ ["jeth", "jyestha", "jyeshtha", "ज्येष्ठ", "जेष्ठ"],
168
+ ["ashar", "ashad", "ashadh", "asadh", "asadha", "आषाढ", "असाढ"],
169
+ ["sawan", "saun", "shravan", "sravan", "srawan", "श्रावण", "सावन"],
170
+ ["bhadau", "bhadow", "bhado", "भाद्र", "भदो"],
171
+ ["asoj", "aswin", "ashoj", "ashwini", "आश्विन", "आसोज"],
172
+ ["kattik", "karttik", "कार्तिक", "कातिक"],
173
+ ["marga", "mangshir", "mansir", "margashirsha", "मार्ग", "मङ्सिर", "मङ्गसिर"],
174
+ ["push", "pus", "paush", "pausa", "पौष", "पूस", "पुस"],
175
+ ["maagh"],
176
+ ["fagun", "phalgun", "phagun", "फाल्गुन", "फागुण"],
177
+ ["chait", "चैत्र"],
178
+ ];
179
+ /** Nepali dates use Devanagari digits and names only when both the calendar and the script say so. */
180
+ export const usesDevanagari = (calendar, script) => calendar === "bs" && script === "devanagari";
181
+ export function getMonthNames(calendar, script = "latin", short = false) {
182
+ if (calendar === "ad")
183
+ return short ? EN_MONTHS_SHORT : EN_MONTHS;
184
+ if (script === "devanagari")
185
+ return NE_MONTHS_DEVA;
186
+ return short ? NE_MONTHS_SHORT : NE_MONTHS;
187
+ }
188
+ export function getWeekdayNames(calendar, script = "latin", length = "long") {
189
+ if (usesDevanagari(calendar, script))
190
+ return length === "long" ? NE_WEEKDAYS_DEVA : length === "short" ? NE_WEEKDAYS_DEVA_SHORT : NE_WEEKDAYS_DEVA_MIN;
191
+ return length === "long" ? EN_WEEKDAYS : length === "short" ? EN_WEEKDAYS_SHORT : EN_WEEKDAYS_MIN;
192
+ }
193
+ // Devanagari digits: U+0966 (०) to U+096F (९).
194
+ const DEVANAGARI_ZERO = "०".charCodeAt(0);
195
+ export const toDevanagariDigits = (text) => text.replace(/[0-9]/g, digit => String.fromCharCode(DEVANAGARI_ZERO + Number(digit)));
196
+ export const toLatinDigits = (text) => text.replace(/[०-९]/g, digit => String(digit.charCodeAt(0) - DEVANAGARI_ZERO));
197
+ // Formatting and parsing
198
+ /** Tokens of a date format. Text between them is kept as it is. */
199
+ const FORMAT_TOKENS = /YYYY|YY|MMMM|MMM|MM|M|dddd|ddd|DD|D/g;
200
+ function resolve(options) {
201
+ return {
202
+ calendar: options.calendar ?? DEFAULT_DATE_PREFERENCES.calendar,
203
+ format: options.format || DEFAULT_DATE_PREFERENCES.format,
204
+ script: options.script ?? DEFAULT_DATE_PREFERENCES.script,
205
+ };
206
+ }
207
+ /**
208
+ * Writes a day with a format of tokens: `YYYY` `YY` year, `MMMM` `MMM` month name, `MM` `M` month number, `DD` `D` day,
209
+ * `dddd` `ddd` weekday name. "YYYY-MM-DD" gives 2026-09-17; "dddd, MMMM D, YYYY" gives Thursday, September 17, 2026.
210
+ * In the Nepali calendar with the Devanagari script, digits and names are Nepali: २०८३-०६-०१.
211
+ * Returns "" for an empty or invalid input. A day outside the Nepali table is written as its ISO date.
212
+ */
213
+ export function formatDate(input, options = {}) {
214
+ const iso = toIsoDate(input);
215
+ if (!iso)
216
+ return "";
217
+ const { calendar, format, script } = resolve(options);
218
+ const parts = toCalendar(iso, calendar);
219
+ return parts ? formatDateParts(parts, calendar, format, script, weekdayOf(iso)) : iso;
220
+ }
221
+ /** Writes parts of a calendar with a format. `weekday` (0 = Sunday) is needed only for the `dddd` and `ddd` tokens. */
222
+ export function formatDateParts(parts, calendar, format, script = "latin", weekday) {
223
+ const devanagari = usesDevanagari(calendar, script);
224
+ const digits = (value, width) => (devanagari ? toDevanagariDigits(pad(value, width)) : pad(value, width));
225
+ const weekdayIndex = weekday ?? weekdayOf(fromCalendar(parts, calendar) ?? "1970-01-04");
226
+ return format.replace(FORMAT_TOKENS, token => {
227
+ switch (token) {
228
+ case "YYYY":
229
+ return digits(parts.year, 4);
230
+ case "YY":
231
+ return digits(parts.year % 100, 2);
232
+ case "MMMM":
233
+ return getMonthNames(calendar, script)[parts.month - 1] ?? "";
234
+ case "MMM":
235
+ return getMonthNames(calendar, script, true)[parts.month - 1] ?? "";
236
+ case "MM":
237
+ return digits(parts.month, 2);
238
+ case "M":
239
+ return digits(parts.month, 1);
240
+ case "DD":
241
+ return digits(parts.day, 2);
242
+ case "D":
243
+ return digits(parts.day, 1);
244
+ case "dddd":
245
+ return getWeekdayNames(calendar, script)[weekdayIndex] ?? "";
246
+ default:
247
+ return getWeekdayNames(calendar, script, "short")[weekdayIndex] ?? "";
248
+ }
249
+ });
250
+ }
251
+ /** Finds the month a typed word names, by its start (three letters or more in Latin), in any spelling or script. */
252
+ function monthFromWord(word, calendar) {
253
+ const text = word.toLowerCase();
254
+ if (/^[a-z]+$/.test(text) && text.length < 3)
255
+ return null;
256
+ const names = calendar === "ad" ? [EN_MONTHS] : [NE_MONTHS, NE_MONTHS_DEVA];
257
+ for (const list of names) {
258
+ const index = list.findIndex(name => name.toLowerCase().startsWith(text));
259
+ if (index >= 0)
260
+ return index + 1;
261
+ }
262
+ if (calendar === "bs") {
263
+ const index = NE_MONTH_ALIASES.findIndex(aliases => aliases.some(alias => alias.startsWith(text)));
264
+ if (index >= 0)
265
+ return index + 1;
266
+ }
267
+ return null;
268
+ }
269
+ /**
270
+ * Reads a date the user typed, following the order of the format's tokens: with "DD/MM/YYYY", "17/9/2026" and
271
+ * "17-09-2026" both give "2026-09-17". Month names are matched by their start in any spelling ("Sep", "September",
272
+ * "Asoj", "असोज"), weekday names are ignored, Devanagari digits are read, and a four-digit number is the year wherever
273
+ * it stands. In the Nepali calendar the text is a Nepali date, and the result is still the Gregorian ISO date.
274
+ * Returns `null` for text that is not a date.
275
+ */
276
+ export function parseDateText(text, options = {}) {
277
+ const { calendar, format } = resolve(options);
278
+ const clean = toLatinDigits(text).trim();
279
+ if (!clean)
280
+ return null;
281
+ if (calendar === "ad" && /^\d{4}-\d{1,2}-\d{1,2}$/.test(clean))
282
+ return toIsoDate(clean);
283
+ const kinds = (format.match(/YYYY|YY|MMMM|MMM|MM|M|DD|D/g) ?? ["YYYY", "MM", "DD"]).map(token => token[0]);
284
+ const numbers = (clean.match(/\d+/g) ?? []).map(Number);
285
+ const words = clean.match(/[\p{L}\p{M}]+/gu) ?? [];
286
+ const values = {};
287
+ for (const word of words) {
288
+ const month = monthFromWord(word, calendar);
289
+ if (month) {
290
+ values.M = month;
291
+ break;
292
+ }
293
+ }
294
+ let rest = numbers;
295
+ let order = kinds.filter((kind, index) => kinds.indexOf(kind) === index && !(kind === "M" && values.M));
296
+ const yearIndex = rest.findIndex(value => value >= 1000);
297
+ if (yearIndex >= 0 && order.includes("Y")) {
298
+ values.Y = rest[yearIndex];
299
+ rest = rest.filter((_, index) => index !== yearIndex);
300
+ order = order.filter(kind => kind !== "Y");
301
+ }
302
+ if (rest.length !== order.length)
303
+ return null;
304
+ order.forEach((kind, index) => (values[kind] = rest[index]));
305
+ let { Y: year } = values;
306
+ const { M: month, D: day } = values;
307
+ if (year === undefined || month === undefined || day === undefined)
308
+ return null;
309
+ if (year < 100)
310
+ year += calendar === "bs" ? 2000 : year < 50 ? 2000 : 1900;
311
+ const parts = { year, month, day };
312
+ return isValidParts(parts, calendar) ? fromCalendar(parts, calendar) : null;
313
+ }
314
+ // Ranges
315
+ /** Between the start and the end of a range written as text: an en dash with a space on each side. */
316
+ export const DATE_RANGE_SEPARATOR = " – ";
317
+ /** Reads a range input as two ISO dates, `null` for an end not given. A start after the end is swapped. */
318
+ export function toDateRange(input) {
319
+ const start = toIsoDate(input?.[0]);
320
+ const end = toIsoDate(input?.[1]);
321
+ return start && end && start > end ? [end, start] : [start, end];
322
+ }
323
+ /**
324
+ * Writes a range as "start – end" in a calendar and format: "2026-09-17 – 2026-09-24". With only a start it is
325
+ * "2026-09-17 –", with only an end "– 2026-09-24", and "" when empty.
326
+ */
327
+ export function formatDateRange(input, options = {}) {
328
+ const [start, end] = toDateRange(input);
329
+ const from = formatDate(start, options);
330
+ const to = formatDate(end, options);
331
+ if (from && to)
332
+ return from + DATE_RANGE_SEPARATOR + to;
333
+ if (from)
334
+ return from + DATE_RANGE_SEPARATOR.trimEnd();
335
+ if (to)
336
+ return DATE_RANGE_SEPARATOR.trimStart() + to;
337
+ return "";
338
+ }
339
+ // What separates the two dates of a typed range: an en or em dash or an arrow, or a hyphen or "to" between spaces.
340
+ const RANGE_SEPARATOR_PATTERN = /\s*[–—→]+\s*|\s+(?:-+|to)\s+/i;
341
+ /**
342
+ * Reads a range the user typed: two dates, each read like `parseDateText`, around " – ", " - ", "—", "→", or " to ",
343
+ * e.g. "17/9/2026 - 24/9/2026" or "2026-09-17 to 2026-09-24". A date on its own, or with a separator after it, gives
344
+ * `[date, null]`; a separator before it gives `[null, date]`; an end before the start is swapped. "" gives an empty
345
+ * range, and text that is not a range gives `null`.
346
+ */
347
+ export function parseDateRangeText(text, options = {}) {
348
+ const clean = text.trim();
349
+ if (!clean)
350
+ return [null, null];
351
+ let parts = clean.split(RANGE_SEPARATOR_PATTERN);
352
+ // "17/09/2026-24/09/2026": a lone hyphen splits the dates when the format has no hyphen of its own.
353
+ if (parts.length === 1 && !resolve(options).format.includes("-") && clean.indexOf("-") === clean.lastIndexOf("-"))
354
+ parts = clean.split("-");
355
+ if (parts.length > 2)
356
+ return null;
357
+ const [from = "", to = ""] = parts.map(part => part.trim());
358
+ const start = from ? parseDateText(from, options) : null;
359
+ const end = to ? parseDateText(to, options) : null;
360
+ if ((from && !start) || (to && !end))
361
+ return null;
362
+ return toDateRange([start, end]);
363
+ }
@@ -0,0 +1,14 @@
1
+ import { type DatePreferences } from "../Interfaces/DateInterfaces.ts";
2
+ /** The saved date settings, with the defaults for anything missing or invalid. Safe to call on the server. */
3
+ export declare function readDatePreferences(): Required<DatePreferences>;
4
+ /** Saves date and time settings and updates every date and time component on the page. Settings left out keep their value. */
5
+ export declare function setDatePreferences(changes: Partial<DatePreferences>): void;
6
+ /** Calls back when the settings change here or in another tab. Returns the function that stops listening. */
7
+ export declare function subscribeDatePreferences(listener: () => void): () => void;
8
+ /** The current date and time settings; the component renders again when they change. */
9
+ export declare function useDatePreferences(): Required<DatePreferences>;
10
+ /**
11
+ * The browser's time zone, such as "Asia/Kathmandu", or `null` on the server and while the page hydrates, where it is
12
+ * not known yet: a date-time shows nothing until then rather than another zone's time.
13
+ */
14
+ export declare function useBrowserTimeZone(): string | null;
@@ -0,0 +1,94 @@
1
+ "use client";
2
+ import { useSyncExternalStore } from "react";
3
+ import { DATE_STORAGE_KEYS, DEFAULT_DATE_PREFERENCES } from "../Interfaces/DateInterfaces.js";
4
+ import { getBrowserTimeZone } from "./DateTimeHelpers.js";
5
+ /*
6
+ * The user's date settings: the calendar (English or Nepali), the format, the script of Nepali dates, and the time
7
+ * format. They are saved in local storage under the DATE_STORAGE_KEYS and read by every date and time component, so
8
+ * a settings page changes every date on the page at once, and in other open tabs. Without a saved value, the defaults
9
+ * apply. On the server there is no storage, so pages render with the defaults and take the saved settings right
10
+ * after hydration.
11
+ */
12
+ const listeners = new Set();
13
+ let snapshot = DEFAULT_DATE_PREFERENCES;
14
+ function read(key) {
15
+ try {
16
+ return localStorage.getItem(key);
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ /** The saved date settings, with the defaults for anything missing or invalid. Safe to call on the server. */
23
+ export function readDatePreferences() {
24
+ if (typeof localStorage === "undefined")
25
+ return DEFAULT_DATE_PREFERENCES;
26
+ const calendar = read(DATE_STORAGE_KEYS.calendar);
27
+ const format = read(DATE_STORAGE_KEYS.format)?.trim();
28
+ const script = read(DATE_STORAGE_KEYS.script);
29
+ const timeFormat = read(DATE_STORAGE_KEYS.timeFormat)?.trim();
30
+ const next = {
31
+ calendar: calendar === "bs" ? "bs" : DEFAULT_DATE_PREFERENCES.calendar,
32
+ format: format || DEFAULT_DATE_PREFERENCES.format,
33
+ script: script === "devanagari" ? "devanagari" : DEFAULT_DATE_PREFERENCES.script,
34
+ timeFormat: timeFormat || DEFAULT_DATE_PREFERENCES.timeFormat,
35
+ };
36
+ // The same object while nothing changed, so React sees a stable snapshot.
37
+ if (next.calendar !== snapshot.calendar ||
38
+ next.format !== snapshot.format ||
39
+ next.script !== snapshot.script ||
40
+ next.timeFormat !== snapshot.timeFormat) {
41
+ snapshot = next;
42
+ }
43
+ return snapshot;
44
+ }
45
+ /** Saves date and time settings and updates every date and time component on the page. Settings left out keep their value. */
46
+ export function setDatePreferences(changes) {
47
+ try {
48
+ if (changes.calendar !== undefined)
49
+ localStorage.setItem(DATE_STORAGE_KEYS.calendar, changes.calendar);
50
+ if (changes.format !== undefined)
51
+ localStorage.setItem(DATE_STORAGE_KEYS.format, changes.format);
52
+ if (changes.script !== undefined)
53
+ localStorage.setItem(DATE_STORAGE_KEYS.script, changes.script);
54
+ if (changes.timeFormat !== undefined)
55
+ localStorage.setItem(DATE_STORAGE_KEYS.timeFormat, changes.timeFormat);
56
+ }
57
+ catch {
58
+ // Storage blocked (private mode, for example): the components keep what they have.
59
+ }
60
+ for (const listener of listeners)
61
+ listener();
62
+ }
63
+ // One storage listener for the page, however many date components are mounted.
64
+ const STORAGE_KEY_LIST = Object.values(DATE_STORAGE_KEYS);
65
+ const onStorage = (event) => {
66
+ if (event.key === null || STORAGE_KEY_LIST.includes(event.key))
67
+ for (const listener of listeners)
68
+ listener();
69
+ };
70
+ /** Calls back when the settings change here or in another tab. Returns the function that stops listening. */
71
+ export function subscribeDatePreferences(listener) {
72
+ if (listeners.size === 0)
73
+ window.addEventListener("storage", onStorage);
74
+ listeners.add(listener);
75
+ return () => {
76
+ listeners.delete(listener);
77
+ if (listeners.size === 0)
78
+ window.removeEventListener("storage", onStorage);
79
+ };
80
+ }
81
+ const serverSnapshot = () => DEFAULT_DATE_PREFERENCES;
82
+ /** The current date and time settings; the component renders again when they change. */
83
+ export function useDatePreferences() {
84
+ return useSyncExternalStore(subscribeDatePreferences, readDatePreferences, serverSnapshot);
85
+ }
86
+ const noSubscription = () => () => { };
87
+ const unknownTimeZone = () => null;
88
+ /**
89
+ * The browser's time zone, such as "Asia/Kathmandu", or `null` on the server and while the page hydrates, where it is
90
+ * not known yet: a date-time shows nothing until then rather than another zone's time.
91
+ */
92
+ export function useBrowserTimeZone() {
93
+ return useSyncExternalStore(noSubscription, getBrowserTimeZone, unknownTimeZone);
94
+ }
@@ -0,0 +1,87 @@
1
+ import { type DatePreferences, type DateScript, type DateTimeInput, type LocalDateTime, type TimeInput } from "../Interfaces/DateInterfaces.ts";
2
+ /** A UTC date-time as `toISOString` writes it: "2026-09-17T08:45:00.000Z". */
3
+ export declare const UTC_DATE_TIME_PATTERN: RegExp;
4
+ /** Whether text is a date-time value in the shape the components give, "2026-09-17T08:45:00.000Z", on a day that exists. */
5
+ export declare function isUtcDateTime(text: unknown): text is string;
6
+ /** The browser's time zone as an IANA id, such as "Asia/Kathmandu"; "UTC" where Intl does not tell. Read once. */
7
+ export declare function getBrowserTimeZone(): string;
8
+ /** A zone's current IANA id ("asia/katmandu" gives "Asia/Kathmandu"), or `null` for a zone Intl does not know. */
9
+ export declare function toTimeZone(timeZone: string | null | undefined): string | null;
10
+ /** The zone to show moments in: `timeZone` when Intl knows it, otherwise the browser's. */
11
+ export declare const resolveTimeZone: (timeZone?: string | null) => string;
12
+ /** An offset in minutes as "UTC+05:45", "UTC-04:00", or "UTC+00:00". */
13
+ export declare function formatUtcOffset(minutes: number): string;
14
+ /** Minutes a zone's clocks are ahead of UTC at a moment (now by default): 345 in Kathmandu, -240 in New York in summer. */
15
+ export declare function getTimeZoneOffset(timeZone?: string, at?: DateTimeInput): number;
16
+ /** How a zone reads to a person at a moment. */
17
+ export interface TimeZoneInfo {
18
+ /** The IANA id: "Asia/Kathmandu". */
19
+ id: string;
20
+ /** The last part of the id, for short labels: "Kathmandu", "New York", "UTC". */
21
+ city: string;
22
+ /** "UTC+05:45" */
23
+ offset: string;
24
+ /** The long English name at that moment: "Nepal Time", "Eastern Daylight Time". */
25
+ name: string;
26
+ }
27
+ /** Names a zone (the browser's by default) at a moment (now by default): its id, a city, its offset, and its long name. */
28
+ export declare function describeTimeZone(timeZone?: string, at?: DateTimeInput): TimeZoneInfo;
29
+ /**
30
+ * Turns anything a moment can arrive as into the value the components hold, a UTC ISO string, or `null`:
31
+ * - "2026-09-17T08:45:00Z" or "2026-09-17T14:30:00+05:45" is that moment: "2026-09-17T08:45:00.000Z";
32
+ * - "2026-09-17T08:45:00", without a zone, is read as UTC (see `DateTimeInput`);
33
+ * - "2026-09-17" is the first moment of that day in `timeZone` (the browser's by default);
34
+ * - a `Date` or milliseconds since 1970 is that moment.
35
+ * Text that is not a date-time, a day that does not exist, or a year past 9999 gives `null`.
36
+ */
37
+ export declare function toUtcDateTime(input: DateTimeInput, timeZone?: string): string | null;
38
+ /** Now, as a UTC ISO string. */
39
+ export declare function nowUtcDateTime(): string;
40
+ /**
41
+ * The date and the time of day a moment shows in a zone (the browser's by default): "2026-09-17T08:45:00Z" is
42
+ * `{ date: "2026-09-17", time: "14:30:00" }` in Kathmandu. Milliseconds are dropped. `null` for an input that is not a moment.
43
+ */
44
+ export declare function toLocalDateTime(input: DateTimeInput, timeZone?: string): LocalDateTime | null;
45
+ /**
46
+ * The moment a date and a time of day name in a zone (the browser's by default), as a UTC ISO string:
47
+ * `fromLocalDateTime("2026-09-17", "14:30", "Asia/Kathmandu")` is "2026-09-17T08:45:00.000Z". When clocks go back, a
48
+ * time that happens twice is the first of the two, or, with `reference` (the moment being changed), the one at the
49
+ * reference's offset, so an edit near the second 01:30 stays in the second hour. When clocks go forward, a skipped time
50
+ * moves forward by the gap. `null` when the date or the time is not valid.
51
+ */
52
+ export declare function fromLocalDateTime(date: string, time: TimeInput, timeZone?: string, reference?: DateTimeInput): string | null;
53
+ /** Today's date in a zone (the browser's by default). */
54
+ export declare function todayIsoDateIn(timeZone?: string): string;
55
+ /** Options of `formatDateTime`, `parseDateTimeText`, and `validateDateTime`: the date settings, the time format, and the zone. */
56
+ export interface DateTimeFormatOptions extends Partial<DatePreferences> {
57
+ /** IANA id of the zone moments are shown in, such as "Asia/Kathmandu". Default: the browser's. */
58
+ timeZone?: string;
59
+ /** Whether times have seconds: `true` adds them to a time format without them, `false` removes them. Default: as the time format says. */
60
+ seconds?: boolean;
61
+ }
62
+ /** The script of a date-time's time: Devanagari digits when its date is Nepali in Devanagari, as `DatePicker` and `TimePicker` agree. */
63
+ export declare const timeScriptOf: (options: Partial<DatePreferences>) => DateScript;
64
+ /**
65
+ * Writes a moment as the date and the time it shows in a zone, in the date and time formats: "2026-09-17 02:30 PM"
66
+ * for "2026-09-17T08:45:00Z" in Kathmandu with the default settings, "२०८३-०६-०१ ०२:३० अपराह्न" in the Nepali calendar
67
+ * in Devanagari. Returns "" for an empty or invalid input.
68
+ */
69
+ export declare function formatDateTime(input: DateTimeInput, options?: DateTimeFormatOptions): string;
70
+ /**
71
+ * Reads a date and time the user typed as the wall clock they name: the date as `parseDateText` reads it in the
72
+ * calendar and format, then a time of day as `parseTimeText` reads it, on either clock. "17/09/2026 2:30 pm",
73
+ * "2026-09-17T14:30", "Sep 17, 2026 at 14:30", and "२०८३-०६-०१ ०२:३० अपराह्न" all work. The time has seconds
74
+ * ("14:30:00"); a date typed without a time gives `time: null`. Returns `null` for text that is not a date.
75
+ */
76
+ export declare function parseLocalDateTimeText(text: string, options?: DateTimeFormatOptions): {
77
+ date: string;
78
+ time: string | null;
79
+ } | null;
80
+ /**
81
+ * Reads a date and time the user typed (see `parseLocalDateTimeText`) as the moment it names in `options.timeZone`,
82
+ * a UTC ISO string. With `seconds: false`, typed seconds are dropped. A date typed without a time takes
83
+ * `defaultTime`, when there is one. Returns `null` for text that is not a date and time.
84
+ */
85
+ export declare function parseDateTimeText(text: string, options?: DateTimeFormatOptions & {
86
+ defaultTime?: TimeInput;
87
+ }): string | null;