@nepdatejs/react 0.0.0-stage → 0.1.1

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +74 -2
  3. package/dist/date-text-BX3Uag0O.cjs +84 -0
  4. package/dist/date-text-BX3Uag0O.cjs.map +1 -0
  5. package/dist/date-text-DLwhBmYj.js +61 -0
  6. package/dist/date-text-DLwhBmYj.js.map +1 -0
  7. package/dist/date-text-Dipetpbp.d.cts +37 -0
  8. package/dist/date-text-Dipetpbp.d.ts +37 -0
  9. package/dist/headless-AEI6dxTJ.cjs +619 -0
  10. package/dist/headless-AEI6dxTJ.cjs.map +1 -0
  11. package/dist/headless-CVj0DhD4.js +560 -0
  12. package/dist/headless-CVj0DhD4.js.map +1 -0
  13. package/dist/headless-ChE3zuYj.d.ts +195 -0
  14. package/dist/headless-CrIIPnOF.d.cts +195 -0
  15. package/dist/headless.cjs +11 -0
  16. package/dist/headless.d.cts +2 -0
  17. package/dist/headless.d.ts +2 -0
  18. package/dist/headless.js +2 -0
  19. package/dist/index.cjs +915 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +245 -0
  22. package/dist/index.d.ts +245 -0
  23. package/dist/index.js +895 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/server.cjs +6 -0
  26. package/dist/server.d.cts +2 -0
  27. package/dist/server.d.ts +2 -0
  28. package/dist/server.js +1 -0
  29. package/package.json +110 -3
  30. package/src/bindings.ts +247 -0
  31. package/src/calendar-body.tsx +133 -0
  32. package/src/calendar.tsx +34 -0
  33. package/src/date-picker.tsx +343 -0
  34. package/src/date-range-picker.tsx +225 -0
  35. package/src/date-text.tsx +110 -0
  36. package/src/date-time-picker.tsx +289 -0
  37. package/src/headless.ts +21 -0
  38. package/src/hooks.ts +476 -0
  39. package/src/icons.tsx +35 -0
  40. package/src/index.ts +21 -0
  41. package/src/multi-date-picker.tsx +245 -0
  42. package/src/page-locale.ts +23 -0
  43. package/src/panel.ts +67 -0
  44. package/src/presets.tsx +44 -0
  45. package/src/server.ts +8 -0
  46. package/src/time-columns.tsx +60 -0
  47. package/src/use-store.ts +196 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,915 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_headless = require("./headless-AEI6dxTJ.cjs");
4
+ const require_date_text = require("./date-text-BX3Uag0O.cjs");
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ let react = require("react");
7
+ let _nepdatejs_core = require("@nepdatejs/core");
8
+ let _nepdatejs_dom = require("@nepdatejs/dom");
9
+ //#region src/icons.tsx
10
+ const PATHS = {
11
+ "chevron-left": ["M10 3.5 5.5 8l4.5 4.5"],
12
+ "chevron-right": ["M6 3.5 10.5 8 6 12.5"],
13
+ calendar: [
14
+ "M2 6.5h12",
15
+ "M5.5 1.5v3",
16
+ "M10.5 1.5v3",
17
+ "M4 3h8a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z"
18
+ ],
19
+ close: ["M4 4l8 8", "M12 4l-8 8"]
20
+ };
21
+ /** The Figma icon set, 16px, stroked with currentColor. */
22
+ function Icon({ name }) {
23
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
24
+ viewBox: "0 0 16 16",
25
+ fill: "none",
26
+ stroke: "currentColor",
27
+ strokeWidth: 1.5,
28
+ strokeLinecap: "round",
29
+ strokeLinejoin: "round",
30
+ "aria-hidden": "true",
31
+ focusable: "false",
32
+ children: PATHS[name].map((d) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d }, d))
33
+ });
34
+ }
35
+ //#endregion
36
+ //#region src/calendar-body.tsx
37
+ /** The calendar markup (@nepdatejs/styles contract) for any set of bindings. */
38
+ function CalendarBody({ bindings, className, theme }) {
39
+ const { state } = bindings;
40
+ const tiles = state.view === "month" ? state.months : state.view === "year" ? state.years : state.fiscalYears;
41
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
42
+ className: className ? `ndp-calendar ${className}` : "ndp-calendar",
43
+ "data-ndp-theme": theme,
44
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
45
+ className: "ndp-calendar__header",
46
+ children: [
47
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
48
+ className: "ndp-icon-button",
49
+ ...bindings.getPrevProps(),
50
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "chevron-left" })
51
+ }),
52
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
53
+ className: "ndp-calendar__title",
54
+ "aria-live": "polite",
55
+ children: bindings.getTitleButtons().map((button) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
56
+ className: "ndp-calendar__title-button",
57
+ ...button.props,
58
+ children: button.label
59
+ }, button.key))
60
+ }),
61
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
62
+ className: "ndp-icon-button",
63
+ ...bindings.getNextProps(),
64
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "chevron-right" })
65
+ })
66
+ ]
67
+ }), state.view === "day" ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
68
+ className: "ndp-grid",
69
+ ...bindings.getGridProps(),
70
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
71
+ role: "row",
72
+ children: state.weekdays.map((weekday) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {
73
+ className: "ndp-weekday",
74
+ scope: "col",
75
+ role: "columnheader",
76
+ abbr: weekday.longLabel,
77
+ "data-weekend": weekday.isWeekend || void 0,
78
+ children: weekday.label
79
+ }, weekday.index))
80
+ }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tbody", { children: state.weeks.map((week, row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
81
+ role: "row",
82
+ children: week.map((cell, column) => cell ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
83
+ role: "gridcell",
84
+ "aria-selected": cell.isSelected || void 0,
85
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
86
+ className: "ndp-day",
87
+ ...bindings.getDayProps(cell),
88
+ children: [
89
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
90
+ className: "ndp-sr-only",
91
+ children: cell.ariaLabel
92
+ }),
93
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
94
+ className: "ndp-day__label",
95
+ "aria-hidden": "true",
96
+ children: cell.label
97
+ }),
98
+ cell.subLabel !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
99
+ className: "ndp-day__sub",
100
+ "aria-hidden": "true",
101
+ children: cell.subLabel
102
+ }),
103
+ cell.events[0] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
104
+ className: "ndp-day__dot",
105
+ "aria-hidden": "true",
106
+ style: cell.events[0].color ? { "--ndp-dot-color": cell.events[0].color } : void 0
107
+ })
108
+ ]
109
+ })
110
+ }, cell.key) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { role: "gridcell" }, `empty-${column}`))
111
+ }, row)) })]
112
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
113
+ className: "ndp-options",
114
+ role: "group",
115
+ "aria-label": state.title,
116
+ children: tiles.map((tile, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
117
+ className: "ndp-option",
118
+ ...bindings.getOptionProps(tile, index, tiles),
119
+ children: [
120
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
121
+ className: "ndp-sr-only",
122
+ children: tile.subLabel ? `${tile.label} (${tile.subLabel})` : tile.label
123
+ }),
124
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
125
+ className: "ndp-option__label",
126
+ "aria-hidden": "true",
127
+ children: tile.label
128
+ }),
129
+ tile.subLabel !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
130
+ className: "ndp-option__sub",
131
+ "aria-hidden": "true",
132
+ children: tile.subLabel
133
+ })
134
+ ]
135
+ }, tile.value))
136
+ })]
137
+ });
138
+ }
139
+ //#endregion
140
+ //#region src/calendar.tsx
141
+ /** A standalone BS/AD month view with keyboard support. */
142
+ function Calendar({ month, onMonthChange, className, theme, ...options }) {
143
+ const bindings = require_headless.useCalendar({
144
+ ...options,
145
+ visibleMonth: month,
146
+ onVisibleMonthChange: onMonthChange
147
+ });
148
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarBody, {
149
+ bindings,
150
+ className,
151
+ theme
152
+ });
153
+ }
154
+ //#endregion
155
+ //#region src/page-locale.ts
156
+ const neverChanges = () => () => void 0;
157
+ /** Used on the server and while hydrating, so both render the same text. */
158
+ function defaultLocale() {
159
+ return (0, _nepdatejs_core.getConfig)().defaultLocale;
160
+ }
161
+ /** Nepali names and digits when the page language is Nepali. Browser only. */
162
+ function pageLocale() {
163
+ return document.documentElement.lang.toLowerCase().startsWith("ne") ? "ne" : defaultLocale();
164
+ }
165
+ /** The given locale, or else the page's language. */
166
+ function usePageLocale(given) {
167
+ const page = (0, react.useSyncExternalStore)(neverChanges, pageLocale, defaultLocale);
168
+ return given ?? page;
169
+ }
170
+ //#endregion
171
+ //#region src/panel.ts
172
+ /**
173
+ * While `open`: show the panel in the top layer beside the anchor, close on an
174
+ * outside pointer, a tap on a bottom sheet's backdrop, or Escape (returning focus to `returnFocus`), and move focus
175
+ * into the calendar.
176
+ */
177
+ function usePanel(open, refs, close, focusGrid, width = 320, layout = "auto") {
178
+ const closeRef = (0, react.useRef)(close);
179
+ closeRef.current = close;
180
+ require_headless.useIsomorphicLayoutEffect(() => {
181
+ const anchor = refs.anchor.current;
182
+ const panel = refs.panel.current;
183
+ if (!open || !anchor || !panel) return;
184
+ const stopTracking = (0, _nepdatejs_dom.trackPosition)(anchor, panel, {
185
+ width,
186
+ layout
187
+ });
188
+ const stopOutside = (0, _nepdatejs_dom.onOutsidePointer)(() => [refs.field.current, refs.panel.current], () => {
189
+ closeRef.current();
190
+ });
191
+ const stopBackdrop = (0, _nepdatejs_dom.onBackdropPointer)(panel, () => {
192
+ closeRef.current();
193
+ });
194
+ const stopEscape = (0, _nepdatejs_dom.onEscape)(() => {
195
+ closeRef.current();
196
+ (0, _nepdatejs_dom.restoreFocus)(refs.returnFocus.current);
197
+ });
198
+ return () => {
199
+ stopTracking();
200
+ stopOutside();
201
+ stopBackdrop();
202
+ stopEscape();
203
+ };
204
+ }, [
205
+ open,
206
+ width,
207
+ layout
208
+ ]);
209
+ (0, react.useEffect)(() => {
210
+ if (open) focusGrid();
211
+ }, [open]);
212
+ }
213
+ //#endregion
214
+ //#region src/presets.tsx
215
+ /** The panel body: the shortcut list (when there are presets), then the calendar and anything else. */
216
+ function PanelBody({ presets, label, onPreset, children }) {
217
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
218
+ className: "ndp-panel__body",
219
+ children: [presets.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
220
+ className: "ndp-presets",
221
+ role: "group",
222
+ "aria-label": label,
223
+ children: presets.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
224
+ type: "button",
225
+ className: "ndp-preset",
226
+ "aria-pressed": preset.isActive,
227
+ disabled: preset.isDisabled,
228
+ onClick: () => {
229
+ onPreset(preset.id);
230
+ },
231
+ children: preset.label
232
+ }, preset.id))
233
+ }), children]
234
+ });
235
+ }
236
+ //#endregion
237
+ //#region src/date-picker.tsx
238
+ /**
239
+ * A BS/AD date field: type a date or pick it from a calendar. The value is
240
+ * always the AD day as "YYYY-MM-DD", so forms, validation and storage are
241
+ * unchanged and a BS date can never be sent as if it were AD.
242
+ */
243
+ function DatePicker(props) {
244
+ const { name, placeholder, required, disabled = false, clearable = true, showEquivalent = true, persistCalendar, panelLayout, theme, className, "aria-label": ariaLabel, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy, locale: localeProp, onCalendarChange, ...options } = props;
245
+ const locale = usePageLocale(localeProp);
246
+ const picker = require_headless.useDatePicker({
247
+ ...options,
248
+ locale,
249
+ onCalendarChange: (calendar) => {
250
+ remember(persistCalendar, calendar);
251
+ onCalendarChange?.(calendar);
252
+ }
253
+ });
254
+ const { state, store, calendar } = picker;
255
+ const labels = (0, _nepdatejs_core.getLocale)(locale);
256
+ const field = (0, react.useRef)(null);
257
+ const box = (0, react.useRef)(null);
258
+ const panel = (0, react.useRef)(null);
259
+ const input = (0, react.useRef)(null);
260
+ const hidden = (0, react.useRef)(null);
261
+ const panelWidth = state.presets.length > 0 ? 492 : 320;
262
+ usePanel(state.open && !state.inline, {
263
+ anchor: box,
264
+ panel,
265
+ field,
266
+ returnFocus: input
267
+ }, () => {
268
+ store.actions.close();
269
+ }, calendar.focusGrid, panelWidth, panelLayout);
270
+ (0, react.useEffect)(() => {
271
+ const stored = read(persistCalendar);
272
+ if (stored) store.actions.switchCalendar(stored);
273
+ }, [persistCalendar, store]);
274
+ (0, react.useEffect)(() => {
275
+ const form = hidden.current?.form ?? input.current?.form;
276
+ if (!form) return;
277
+ const reset = () => {
278
+ store.actions.reset();
279
+ };
280
+ form.addEventListener("reset", reset);
281
+ return () => {
282
+ form.removeEventListener("reset", reset);
283
+ };
284
+ }, [store]);
285
+ (0, react.useEffect)(() => {
286
+ input.current?.setCustomValidity(state.error?.message ?? "");
287
+ }, [state.error]);
288
+ const hint = state.error?.message ?? (showEquivalent ? state.equivalentText : null);
289
+ const inputProps = picker.getInputProps();
290
+ const describedBy = [ariaDescribedBy, hint ? picker.ids.hint : void 0].filter(Boolean).join(" ") || void 0;
291
+ const invalid = ariaInvalid === true || state.error !== null;
292
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
293
+ ref: field,
294
+ className: className ? `ndp-field ${className}` : "ndp-field",
295
+ "data-ndp-theme": theme,
296
+ children: [
297
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
298
+ ref: box,
299
+ className: "ndp-input",
300
+ "data-invalid": invalid || void 0,
301
+ "data-disabled": disabled || void 0,
302
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
303
+ ref: input,
304
+ className: "ndp-input__control",
305
+ ...inputProps,
306
+ "aria-describedby": describedBy,
307
+ "aria-invalid": invalid || void 0,
308
+ "aria-label": ariaLabel,
309
+ placeholder: placeholder ?? state.placeholder,
310
+ required,
311
+ disabled
312
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
313
+ className: "ndp-input__actions",
314
+ children: [
315
+ clearable && state.text !== "" && !disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
316
+ className: "ndp-icon-button",
317
+ ...picker.getClearProps(),
318
+ onClick: () => {
319
+ store.actions.clear();
320
+ input.current?.focus();
321
+ },
322
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "close" })
323
+ }),
324
+ state.canSwitchCalendar && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
325
+ className: "ndp-input__switch",
326
+ disabled,
327
+ ...picker.getSwitchProps(),
328
+ children: labels.calendarNames[state.calendar]
329
+ }),
330
+ !state.inline && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
331
+ className: "ndp-icon-button",
332
+ disabled,
333
+ ...picker.getToggleProps(),
334
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "calendar" })
335
+ })
336
+ ]
337
+ })]
338
+ }),
339
+ hint !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
340
+ id: picker.ids.hint,
341
+ className: "ndp-hint",
342
+ "data-error": state.error ? true : void 0,
343
+ "aria-live": "polite",
344
+ children: hint
345
+ }),
346
+ name !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
347
+ ref: hidden,
348
+ type: "hidden",
349
+ name,
350
+ value: state.value,
351
+ disabled
352
+ }),
353
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
354
+ ref: panel,
355
+ className: "ndp-panel",
356
+ popover: state.inline ? void 0 : "manual",
357
+ "data-layout": state.inline ? "inline" : void 0,
358
+ style: state.inline ? { "--ndp-panel-width": `${String(panelWidth)}px` } : void 0,
359
+ "data-ndp-theme": theme,
360
+ ...picker.getPanelProps(),
361
+ "aria-label": ariaLabel ? `${ariaLabel}: ${labels.labels.calendar}` : labels.labels.chooseDate,
362
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelBody, {
363
+ presets: state.presets,
364
+ label: labels.labels.presets,
365
+ onPreset: (id) => {
366
+ if (store.actions.applyPreset(id) && !store.getState().open) (0, _nepdatejs_dom.restoreFocus)(input.current);
367
+ },
368
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarBody, { bindings: {
369
+ ...calendar,
370
+ getDayProps: (cell) => ({
371
+ ...calendar.getDayProps(cell),
372
+ onClick: () => {
373
+ if (!cell.isDisabled && store.actions.select(cell.date) && !store.getState().open) (0, _nepdatejs_dom.restoreFocus)(input.current);
374
+ }
375
+ })
376
+ } })
377
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
378
+ className: "ndp-panel__footer",
379
+ children: [state.canSwitchCalendar ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
380
+ className: "ndp-segment",
381
+ role: "group",
382
+ "aria-label": labels.labels.calendar,
383
+ children: ["BS", "AD"].map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
384
+ className: "ndp-segment__item",
385
+ ...picker.getCalendarOptionProps(option),
386
+ children: labels.calendarNames[option]
387
+ }, option))
388
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
389
+ className: "ndp-button",
390
+ ...picker.getTodayProps(),
391
+ onClick: () => {
392
+ if (store.actions.selectToday()) (0, _nepdatejs_dom.restoreFocus)(input.current);
393
+ },
394
+ children: labels.labels.today
395
+ })]
396
+ })]
397
+ })
398
+ ]
399
+ });
400
+ }
401
+ function read(key) {
402
+ if (!key) return null;
403
+ try {
404
+ const stored = window.localStorage.getItem(key);
405
+ return stored === "AD" || stored === "BS" ? stored : null;
406
+ } catch {
407
+ return null;
408
+ }
409
+ }
410
+ function remember(key, calendar) {
411
+ if (!key) return;
412
+ try {
413
+ window.localStorage.setItem(key, calendar);
414
+ } catch {}
415
+ }
416
+ //#endregion
417
+ //#region src/date-range-picker.tsx
418
+ /** Two BS/AD date fields sharing one calendar panel in range mode. */
419
+ function DateRangePicker({ startName, endName, startLabel, endLabel, fiscalYearView = false, disabled = false, panelLayout, theme, className, ...options }) {
420
+ const locale = usePageLocale(options.locale);
421
+ const picker = require_headless.useDateRangePicker({
422
+ ...options,
423
+ locale
424
+ });
425
+ const { state, store, calendar } = picker;
426
+ const labels = (0, _nepdatejs_core.getLocale)(locale);
427
+ const field = (0, react.useRef)(null);
428
+ const row = (0, react.useRef)(null);
429
+ const panel = (0, react.useRef)(null);
430
+ const start = (0, react.useRef)(null);
431
+ const panelWidth = state.presets.length > 0 ? 492 : 320;
432
+ usePanel(state.open && !state.inline, {
433
+ anchor: row,
434
+ panel,
435
+ field,
436
+ returnFocus: start
437
+ }, () => {
438
+ store.actions.close();
439
+ }, calendar.focusGrid, panelWidth, panelLayout);
440
+ const invalid = (which) => state.error !== null && (state.error.field === which || state.error.field === "range");
441
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
442
+ ref: field,
443
+ className: className ? `ndp-field ${className}` : "ndp-field",
444
+ "data-ndp-theme": theme,
445
+ children: [
446
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
447
+ ref: row,
448
+ className: "ndp-range",
449
+ children: [
450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
451
+ className: "ndp-input",
452
+ "data-invalid": invalid("start") || void 0,
453
+ "data-disabled": disabled || void 0,
454
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
455
+ ref: start,
456
+ className: "ndp-input__control",
457
+ ...picker.getInputProps("start"),
458
+ "aria-label": startLabel,
459
+ disabled
460
+ })
461
+ }),
462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
463
+ className: "ndp-range__separator",
464
+ "aria-hidden": "true",
465
+ children: "→"
466
+ }),
467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
468
+ className: "ndp-input",
469
+ "data-invalid": invalid("end") || void 0,
470
+ "data-disabled": disabled || void 0,
471
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
472
+ className: "ndp-input__control",
473
+ ...picker.getInputProps("end"),
474
+ "aria-label": endLabel,
475
+ disabled
476
+ }), !state.inline && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
477
+ className: "ndp-input__actions",
478
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
479
+ className: "ndp-icon-button",
480
+ disabled,
481
+ ...picker.getToggleProps(),
482
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "calendar" })
483
+ })
484
+ })]
485
+ })
486
+ ]
487
+ }),
488
+ state.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
489
+ id: picker.ids.hint,
490
+ className: "ndp-hint",
491
+ "data-error": true,
492
+ "aria-live": "polite",
493
+ children: state.error.message
494
+ }),
495
+ startName !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
496
+ type: "hidden",
497
+ name: startName,
498
+ value: state.value.start,
499
+ disabled
500
+ }),
501
+ endName !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
502
+ type: "hidden",
503
+ name: endName,
504
+ value: state.value.end,
505
+ disabled
506
+ }),
507
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
508
+ ref: panel,
509
+ className: "ndp-panel",
510
+ popover: state.inline ? void 0 : "manual",
511
+ "data-layout": state.inline ? "inline" : void 0,
512
+ style: state.inline ? { "--ndp-panel-width": `${String(panelWidth)}px` } : void 0,
513
+ "data-ndp-theme": theme,
514
+ ...picker.getPanelProps(),
515
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PanelBody, {
516
+ presets: state.presets,
517
+ label: labels.labels.presets,
518
+ onPreset: (id) => {
519
+ if (store.actions.applyPreset(id) && !store.getState().open) (0, _nepdatejs_dom.restoreFocus)(start.current);
520
+ },
521
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarBody, { bindings: calendar })
522
+ }), (state.canSwitchCalendar || fiscalYearView) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
523
+ className: "ndp-panel__footer",
524
+ children: [state.canSwitchCalendar ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
525
+ className: "ndp-segment",
526
+ role: "group",
527
+ "aria-label": labels.labels.calendar,
528
+ children: ["BS", "AD"].map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
529
+ className: "ndp-segment__item",
530
+ ...picker.getCalendarOptionProps(option),
531
+ children: labels.calendarNames[option]
532
+ }, option))
533
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), fiscalYearView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
534
+ type: "button",
535
+ className: "ndp-button",
536
+ "aria-pressed": state.calendarState.view === "fiscal",
537
+ onClick: () => {
538
+ store.calendar.actions.setView(state.calendarState.view === "fiscal" ? "day" : "fiscal");
539
+ calendar.focusGrid();
540
+ },
541
+ children: labels.labels.fiscalYear
542
+ })]
543
+ })]
544
+ })
545
+ ]
546
+ });
547
+ }
548
+ //#endregion
549
+ //#region src/time-columns.tsx
550
+ /** Hour and minute columns (Figma "Time Item"); the selected items scroll into view. */
551
+ function TimeColumns({ hours, minutes, locale, onHour, onMinute }) {
552
+ const root = (0, react.useRef)(null);
553
+ const labels = (0, _nepdatejs_core.getLocale)(locale).labels;
554
+ (0, react.useEffect)(() => {
555
+ for (const item of root.current?.querySelectorAll("[aria-pressed=\"true\"]") ?? []) if (typeof item.scrollIntoView === "function") item.scrollIntoView({ block: "nearest" });
556
+ }, []);
557
+ const column = (label, cells, pick) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
558
+ className: "ndp-time__column",
559
+ role: "group",
560
+ "aria-label": label,
561
+ children: cells.map((cell) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
562
+ type: "button",
563
+ className: "ndp-time-item",
564
+ "aria-pressed": cell.isSelected,
565
+ onClick: () => {
566
+ pick(cell.value);
567
+ },
568
+ children: cell.label
569
+ }, cell.value))
570
+ });
571
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
572
+ ref: root,
573
+ className: "ndp-time",
574
+ role: "group",
575
+ "aria-label": labels.time,
576
+ children: [column(labels.hour, hours, onHour), column(labels.minute, minutes, onMinute)]
577
+ });
578
+ }
579
+ //#endregion
580
+ //#region src/date-time-picker.tsx
581
+ /** A BS/AD date and time field. The value is one instant, with the zone's offset. */
582
+ function DateTimePicker({ name, required, disabled = false, panelLayout, theme, className, "aria-label": ariaLabel, timeLabel, ...options }) {
583
+ const locale = usePageLocale(options.locale);
584
+ const { state, store, calendar, ids } = require_headless.useDateTimePicker({
585
+ ...options,
586
+ locale
587
+ });
588
+ const labels = (0, _nepdatejs_core.getLocale)(locale);
589
+ const field = (0, react.useRef)(null);
590
+ const row = (0, react.useRef)(null);
591
+ const panel = (0, react.useRef)(null);
592
+ const dateInput = (0, react.useRef)(null);
593
+ const panelWidth = (state.presets.length > 0 ? 492 : 320) + 136;
594
+ usePanel(state.open && !state.inline, {
595
+ anchor: row,
596
+ panel,
597
+ field,
598
+ returnFocus: dateInput
599
+ }, () => {
600
+ store.actions.close();
601
+ }, calendar.focusGrid, panelWidth, panelLayout);
602
+ const hint = state.error?.message ?? state.equivalentText;
603
+ const invalid = (which) => state.error?.field === which;
604
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
605
+ ref: field,
606
+ className: className ? `ndp-field ${className}` : "ndp-field",
607
+ "data-ndp-theme": theme,
608
+ children: [
609
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
610
+ ref: row,
611
+ className: "ndp-datetime",
612
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
613
+ className: "ndp-input",
614
+ "data-invalid": invalid("date") || void 0,
615
+ "data-disabled": disabled || void 0,
616
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
617
+ ref: dateInput,
618
+ id: ids.date,
619
+ className: "ndp-input__control",
620
+ ...require_headless.popupProps(state),
621
+ type: "text",
622
+ inputMode: "numeric",
623
+ autoComplete: "off",
624
+ value: state.dateText,
625
+ placeholder: state.placeholder,
626
+ "aria-label": ariaLabel,
627
+ "aria-controls": ids.panel,
628
+ "aria-invalid": invalid("date") || void 0,
629
+ "aria-describedby": hint ? ids.hint : void 0,
630
+ required,
631
+ disabled,
632
+ onChange: (event) => {
633
+ store.actions.typeDate(event.target.value);
634
+ },
635
+ onBlur: () => {
636
+ store.actions.blurDate();
637
+ },
638
+ onKeyDown: (event) => {
639
+ if (event.key === "ArrowDown" && (event.altKey || state.dateText === "")) {
640
+ event.preventDefault();
641
+ store.actions.open();
642
+ }
643
+ }
644
+ }), !state.inline && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
645
+ className: "ndp-input__actions",
646
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
647
+ type: "button",
648
+ className: "ndp-icon-button",
649
+ disabled,
650
+ "aria-label": labels.labels.openCalendar,
651
+ "aria-expanded": state.open,
652
+ "aria-controls": ids.panel,
653
+ onClick: () => {
654
+ store.actions.toggle();
655
+ },
656
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "calendar" })
657
+ })
658
+ })]
659
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
660
+ className: "ndp-input ndp-input--time",
661
+ "data-invalid": invalid("time") || void 0,
662
+ "data-disabled": disabled || void 0,
663
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
664
+ id: ids.time,
665
+ className: "ndp-input__control",
666
+ type: "text",
667
+ inputMode: "text",
668
+ autoComplete: "off",
669
+ value: state.timeText,
670
+ placeholder: state.timePlaceholder,
671
+ "aria-label": timeLabel ?? labels.labels.time,
672
+ "aria-invalid": invalid("time") || void 0,
673
+ "aria-describedby": hint ? ids.hint : void 0,
674
+ disabled,
675
+ onChange: (event) => {
676
+ store.actions.typeTime(event.target.value);
677
+ },
678
+ onBlur: () => {
679
+ store.actions.blurTime();
680
+ }
681
+ })
682
+ })]
683
+ }),
684
+ hint !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
685
+ id: ids.hint,
686
+ className: "ndp-hint",
687
+ "data-error": state.error ? true : void 0,
688
+ "aria-live": "polite",
689
+ children: hint
690
+ }),
691
+ name !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
692
+ type: "hidden",
693
+ name,
694
+ value: state.value,
695
+ disabled
696
+ }),
697
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
698
+ ref: panel,
699
+ id: ids.panel,
700
+ className: "ndp-panel",
701
+ popover: state.inline ? void 0 : "manual",
702
+ "data-layout": state.inline ? "inline" : void 0,
703
+ style: state.inline ? { "--ndp-panel-width": `${String(panelWidth)}px` } : void 0,
704
+ role: state.inline ? "group" : "dialog",
705
+ "aria-label": labels.labels.chooseDate,
706
+ "data-ndp-theme": theme,
707
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(PanelBody, {
708
+ presets: state.presets,
709
+ label: labels.labels.presets,
710
+ onPreset: (presetId) => {
711
+ store.actions.applyPreset(presetId);
712
+ },
713
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarBody, { bindings: calendar }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TimeColumns, {
714
+ hours: state.hours,
715
+ minutes: state.minutes,
716
+ locale: options.locale,
717
+ onHour: (hour) => {
718
+ store.actions.selectHour(hour);
719
+ },
720
+ onMinute: (minute) => {
721
+ store.actions.selectMinute(minute);
722
+ }
723
+ })]
724
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
725
+ className: "ndp-panel__footer",
726
+ children: [state.canSwitchCalendar ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
727
+ className: "ndp-segment",
728
+ role: "group",
729
+ "aria-label": labels.labels.calendar,
730
+ children: ["BS", "AD"].map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
731
+ type: "button",
732
+ className: "ndp-segment__item",
733
+ "aria-pressed": state.calendar === option,
734
+ onClick: () => {
735
+ store.actions.switchCalendar(option);
736
+ },
737
+ children: labels.calendarNames[option]
738
+ }, option))
739
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
740
+ type: "button",
741
+ className: "ndp-button",
742
+ onClick: () => {
743
+ store.actions.selectNow();
744
+ },
745
+ children: labels.labels.now
746
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
747
+ type: "button",
748
+ className: "ndp-button",
749
+ "data-variant": "primary",
750
+ onClick: () => {
751
+ store.actions.close();
752
+ dateInput.current?.focus();
753
+ },
754
+ children: labels.labels.done
755
+ })] })]
756
+ })]
757
+ })
758
+ ]
759
+ });
760
+ }
761
+ //#endregion
762
+ //#region src/multi-date-picker.tsx
763
+ /**
764
+ * Several separate BS/AD days in one field (PICK-001): click days in the
765
+ * panel to add or remove them, or type them separated by commas. The value is
766
+ * the sorted AD days as "YYYY-MM-DD" strings.
767
+ */
768
+ function MultiDatePicker({ name, placeholder, required, disabled = false, clearable = true, panelLayout, theme, className, "aria-label": ariaLabel, ...options }) {
769
+ const locale = usePageLocale(options.locale);
770
+ const picker = require_headless.useMultiDatePicker({
771
+ ...options,
772
+ locale
773
+ });
774
+ const { state, store, calendar } = picker;
775
+ const labels = (0, _nepdatejs_core.getLocale)(locale);
776
+ const field = (0, react.useRef)(null);
777
+ const box = (0, react.useRef)(null);
778
+ const panel = (0, react.useRef)(null);
779
+ const input = (0, react.useRef)(null);
780
+ usePanel(state.open && !state.inline, {
781
+ anchor: box,
782
+ panel,
783
+ field,
784
+ returnFocus: input
785
+ }, () => {
786
+ store.actions.close();
787
+ }, calendar.focusGrid, 320, panelLayout);
788
+ (0, react.useEffect)(() => {
789
+ const form = input.current?.form;
790
+ if (!form) return;
791
+ const reset = () => {
792
+ store.actions.reset();
793
+ };
794
+ form.addEventListener("reset", reset);
795
+ return () => {
796
+ form.removeEventListener("reset", reset);
797
+ };
798
+ }, [store]);
799
+ (0, react.useEffect)(() => {
800
+ input.current?.setCustomValidity(state.error?.message ?? "");
801
+ }, [state.error]);
802
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
803
+ ref: field,
804
+ className: className ? `ndp-field ${className}` : "ndp-field",
805
+ "data-ndp-theme": theme,
806
+ children: [
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
808
+ ref: box,
809
+ className: "ndp-input",
810
+ "data-invalid": state.error !== null || void 0,
811
+ "data-disabled": disabled || void 0,
812
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
813
+ ref: input,
814
+ className: "ndp-input__control",
815
+ ...picker.getInputProps(),
816
+ "aria-label": ariaLabel,
817
+ placeholder: placeholder ?? state.placeholder,
818
+ required,
819
+ disabled
820
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
821
+ className: "ndp-input__actions",
822
+ children: [clearable && state.text !== "" && !disabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
823
+ className: "ndp-icon-button",
824
+ ...picker.getClearProps(),
825
+ onClick: () => {
826
+ store.actions.clear();
827
+ input.current?.focus();
828
+ },
829
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "close" })
830
+ }), !state.inline && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
831
+ className: "ndp-icon-button",
832
+ disabled,
833
+ ...picker.getToggleProps(),
834
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "calendar" })
835
+ })]
836
+ })]
837
+ }),
838
+ state.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
839
+ id: picker.ids.hint,
840
+ className: "ndp-hint",
841
+ "data-error": true,
842
+ "aria-live": "polite",
843
+ children: state.error.message
844
+ }),
845
+ name !== void 0 && state.value.map((day) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
846
+ type: "hidden",
847
+ name,
848
+ value: day,
849
+ disabled
850
+ }, day)),
851
+ state.open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
852
+ ref: panel,
853
+ className: "ndp-panel",
854
+ popover: state.inline ? void 0 : "manual",
855
+ "data-layout": state.inline ? "inline" : void 0,
856
+ style: state.inline ? { "--ndp-panel-width": `${String(320)}px` } : void 0,
857
+ "data-ndp-theme": theme,
858
+ ...picker.getPanelProps(),
859
+ "aria-label": ariaLabel ? `${ariaLabel}: ${labels.labels.calendar}` : labels.labels.chooseDate,
860
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CalendarBody, { bindings: {
861
+ ...calendar,
862
+ getDayProps: (cell) => ({
863
+ ...calendar.getDayProps(cell),
864
+ onClick: () => {
865
+ if (!cell.isDisabled && store.actions.select(cell.date) && !store.getState().open) (0, _nepdatejs_dom.restoreFocus)(input.current);
866
+ }
867
+ })
868
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
869
+ className: "ndp-panel__footer",
870
+ children: [state.canSwitchCalendar ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
871
+ className: "ndp-segment",
872
+ role: "group",
873
+ "aria-label": labels.labels.calendar,
874
+ children: ["BS", "AD"].map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
875
+ className: "ndp-segment__item",
876
+ ...picker.getCalendarOptionProps(option),
877
+ children: labels.calendarNames[option]
878
+ }, option))
879
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}), !state.inline && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
880
+ type: "button",
881
+ className: "ndp-button",
882
+ onClick: () => {
883
+ store.actions.close();
884
+ (0, _nepdatejs_dom.restoreFocus)(input.current);
885
+ },
886
+ children: labels.labels.done
887
+ })]
888
+ })]
889
+ })
890
+ ]
891
+ });
892
+ }
893
+ //#endregion
894
+ exports.Calendar = Calendar;
895
+ exports.CalendarBody = CalendarBody;
896
+ exports.DatePicker = DatePicker;
897
+ exports.DateRangePicker = DateRangePicker;
898
+ exports.DateText = require_date_text.DateText;
899
+ exports.DateTimePicker = DateTimePicker;
900
+ exports.Icon = Icon;
901
+ exports.MultiDatePicker = MultiDatePicker;
902
+ exports.ShortDate = require_date_text.ShortDate;
903
+ exports.TimeColumns = TimeColumns;
904
+ exports.bothDates = require_date_text.bothDates;
905
+ exports.deepEqual = require_headless.deepEqual;
906
+ exports.shortDate = require_date_text.shortDate;
907
+ exports.useCalendar = require_headless.useCalendar;
908
+ exports.useCalendarBindings = require_headless.useCalendarBindings;
909
+ exports.useDatePicker = require_headless.useDatePicker;
910
+ exports.useDateRangePicker = require_headless.useDateRangePicker;
911
+ exports.useDateTimePicker = require_headless.useDateTimePicker;
912
+ exports.useMultiDatePicker = require_headless.useMultiDatePicker;
913
+ exports.useStore = require_headless.useStore;
914
+
915
+ //# sourceMappingURL=index.cjs.map