@dashforge/tw 0.9.0-beta → 0.10.0-beta

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 (58) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/index.esm.js +1634 -256
  3. package/dist/src/components/Calendar/Calendar.d.ts +14 -0
  4. package/dist/src/components/Calendar/Calendar.d.ts.map +1 -0
  5. package/dist/src/components/Calendar/calendar.types.d.ts +72 -0
  6. package/dist/src/components/Calendar/calendar.types.d.ts.map +1 -0
  7. package/dist/src/components/Calendar/calendar.variants.d.ts +99 -0
  8. package/dist/src/components/Calendar/calendar.variants.d.ts.map +1 -0
  9. package/dist/src/components/DatePicker/DatePicker.d.ts +14 -0
  10. package/dist/src/components/DatePicker/DatePicker.d.ts.map +1 -0
  11. package/dist/src/components/DatePicker/datePicker.types.d.ts +88 -0
  12. package/dist/src/components/DatePicker/datePicker.types.d.ts.map +1 -0
  13. package/dist/src/components/DatePicker/datePicker.variants.d.ts +97 -0
  14. package/dist/src/components/DatePicker/datePicker.variants.d.ts.map +1 -0
  15. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts +13 -0
  16. package/dist/src/components/DateRangePicker/DateRangePicker.d.ts.map +1 -0
  17. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts +85 -0
  18. package/dist/src/components/DateRangePicker/dateRangePicker.types.d.ts.map +1 -0
  19. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts +183 -0
  20. package/dist/src/components/DateRangePicker/dateRangePicker.variants.d.ts.map +1 -0
  21. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts +7 -43
  22. package/dist/src/components/DateTimePicker/DateTimePicker.d.ts.map +1 -1
  23. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts +50 -55
  24. package/dist/src/components/DateTimePicker/dateTimePicker.types.d.ts.map +1 -1
  25. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts +36 -94
  26. package/dist/src/components/DateTimePicker/dateTimePicker.variants.d.ts.map +1 -1
  27. package/dist/src/components/TimePicker/TimePicker.d.ts +12 -0
  28. package/dist/src/components/TimePicker/TimePicker.d.ts.map +1 -0
  29. package/dist/src/components/TimePicker/timePicker.types.d.ts +80 -0
  30. package/dist/src/components/TimePicker/timePicker.types.d.ts.map +1 -0
  31. package/dist/src/components/TimePicker/timePicker.variants.d.ts +103 -0
  32. package/dist/src/components/TimePicker/timePicker.variants.d.ts.map +1 -0
  33. package/dist/src/index.d.ts +15 -3
  34. package/dist/src/index.d.ts.map +1 -1
  35. package/package.json +4 -3
  36. package/src/components/Calendar/Calendar.test.tsx +75 -0
  37. package/src/components/Calendar/Calendar.tsx +264 -0
  38. package/src/components/Calendar/calendar.types.ts +65 -0
  39. package/src/components/Calendar/calendar.variants.ts +64 -0
  40. package/src/components/DatePicker/DatePicker.test.tsx +76 -0
  41. package/src/components/DatePicker/DatePicker.tsx +305 -0
  42. package/src/components/DatePicker/datePicker.types.ts +77 -0
  43. package/src/components/DatePicker/datePicker.variants.ts +50 -0
  44. package/src/components/DateRangePicker/DateRangePicker.test.tsx +92 -0
  45. package/src/components/DateRangePicker/DateRangePicker.tsx +510 -0
  46. package/src/components/DateRangePicker/dateRangePicker.types.ts +74 -0
  47. package/src/components/DateRangePicker/dateRangePicker.variants.ts +101 -0
  48. package/src/components/DateTimePicker/DateTimePicker.test.tsx +60 -186
  49. package/src/components/DateTimePicker/DateTimePicker.tsx +209 -181
  50. package/src/components/DateTimePicker/dateTimePicker.types.ts +50 -54
  51. package/src/components/DateTimePicker/dateTimePicker.variants.ts +22 -51
  52. package/src/components/TimePicker/TimePicker.test.tsx +65 -0
  53. package/src/components/TimePicker/TimePicker.tsx +274 -0
  54. package/src/components/TimePicker/timePicker.types.ts +69 -0
  55. package/src/components/TimePicker/timePicker.variants.ts +53 -0
  56. package/src/index.ts +38 -6
  57. package/tsconfig.lib.json +7 -4
  58. package/src/components/DateTimePicker/DateTimePicker.perf.test.tsx +0 -153
package/dist/index.esm.js CHANGED
@@ -11,10 +11,11 @@ import { useDashFieldMeta } from '@dashforge/forms';
11
11
  import * as RadixCheckbox from '@radix-ui/react-checkbox';
12
12
  import * as RadixSwitch from '@radix-ui/react-switch';
13
13
  import * as RadixRadioGroup from '@radix-ui/react-radio-group';
14
+ import { useCalendar, resolveCalendarKey, formatLongDate, parseISODate, formatTime, generateTimeOptions, timeStringToMinutes, getTodayISODate, useDateRange } from '@dashforge/calendar-core';
15
+ import * as RadixPopover from '@radix-ui/react-popover';
14
16
  import * as RadixDialog from '@radix-ui/react-dialog';
15
17
  import * as RadixTabs from '@radix-ui/react-tabs';
16
18
  import * as RadixTooltip from '@radix-ui/react-tooltip';
17
- import * as RadixPopover from '@radix-ui/react-popover';
18
19
  import * as RadixAccordion from '@radix-ui/react-accordion';
19
20
 
20
21
  function _extends() {
@@ -3427,193 +3428,1610 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3427
3428
  }
3428
3429
 
3429
3430
  /**
3430
- * Tailwind-variants recipe for `<DateTimePicker>` with 6 slots.
3431
+ * Structural slots for `<Calendar>`.
3432
+ *
3433
+ * Theme identity: every class is a bare neutral / brand token-backed
3434
+ * Tailwind class. The `dashforgePreset()` CSS-variable swap auto-inverts
3435
+ * the neutral palette, so there are NO `dark:` variants here (a `dark:` on
3436
+ * a neutral class would double-invert — see `_shared/themeIdentity.test.ts`).
3437
+ */ const calendarVariants = tv({
3438
+ slots: {
3439
+ root: 'inline-flex w-fit flex-col gap-2 rounded-lg border border-neutral-200 bg-neutral-50 p-3 select-none',
3440
+ header: 'flex items-center justify-between gap-1',
3441
+ navButton: 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-[1rem] text-neutral-600 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500 disabled:pointer-events-none disabled:opacity-40',
3442
+ monthLabel: 'flex-1 text-center text-sm font-semibold text-neutral-900',
3443
+ grid: 'flex flex-col gap-1 focus:outline-none',
3444
+ weekdayRow: 'grid grid-cols-7 gap-1',
3445
+ weekday: 'flex h-8 items-center justify-center text-xs font-semibold text-neutral-500',
3446
+ weekRow: 'grid grid-cols-7 gap-1'
3447
+ },
3448
+ variants: {
3449
+ disabled: {
3450
+ true: {
3451
+ root: 'opacity-60'
3452
+ }
3453
+ }
3454
+ },
3455
+ defaultVariants: {
3456
+ disabled: false
3457
+ }
3458
+ });
3459
+ /**
3460
+ * Per-day-cell recipe. Each of the 42 grid cells is rendered with its own
3461
+ * state (`siblingMonth` / `today` / `selected` / `disabled`).
3462
+ */ const calendarDayVariants = tv({
3463
+ base: 'flex h-9 w-9 items-center justify-center rounded-md text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500',
3464
+ variants: {
3465
+ siblingMonth: {
3466
+ true: 'text-neutral-400'
3467
+ },
3468
+ today: {
3469
+ true: 'font-bold ring-1 ring-inset ring-primary-400'
3470
+ },
3471
+ selected: {
3472
+ true: 'bg-primary-600 text-white hover:bg-primary-600'
3473
+ },
3474
+ disabled: {
3475
+ true: 'cursor-default text-neutral-300 opacity-60 hover:bg-transparent'
3476
+ }
3477
+ },
3478
+ compoundVariants: [
3479
+ // A selected cell drops the "today" ring — the solid fill is enough.
3480
+ {
3481
+ selected: true,
3482
+ today: true,
3483
+ class: 'ring-0'
3484
+ }
3485
+ ],
3486
+ defaultVariants: {
3487
+ siblingMonth: false,
3488
+ today: false,
3489
+ selected: false,
3490
+ disabled: false
3491
+ }
3492
+ });
3493
+
3494
+ // Inline 16×16 stroke chevrons — no icon dependency (tw convention).
3495
+ function ChevronLeftIcon$1() {
3496
+ return jsx("svg", {
3497
+ width: "1em",
3498
+ height: "1em",
3499
+ viewBox: "0 0 16 16",
3500
+ fill: "none",
3501
+ "aria-hidden": "true",
3502
+ children: jsx("path", {
3503
+ d: "M10 4 6 8l4 4",
3504
+ stroke: "currentColor",
3505
+ strokeWidth: "1.5",
3506
+ strokeLinecap: "round",
3507
+ strokeLinejoin: "round"
3508
+ })
3509
+ });
3510
+ }
3511
+ function ChevronRightIcon$2() {
3512
+ return jsx("svg", {
3513
+ width: "1em",
3514
+ height: "1em",
3515
+ viewBox: "0 0 16 16",
3516
+ fill: "none",
3517
+ "aria-hidden": "true",
3518
+ children: jsx("path", {
3519
+ d: "m6 4 4 4-4 4",
3520
+ stroke: "currentColor",
3521
+ strokeWidth: "1.5",
3522
+ strokeLinecap: "round",
3523
+ strokeLinejoin: "round"
3524
+ })
3525
+ });
3526
+ }
3527
+ /**
3528
+ * `<Calendar>` — a standalone, inline month-grid date picker.
3529
+ *
3530
+ * The Tailwind presentation layer over the headless `useCalendar` engine
3531
+ * from `@dashforge/calendar-core` (the same engine the MUI `@dashforge/ui`
3532
+ * calendar consumes — only the skin differs). Implements the WCAG grid
3533
+ * pattern with roving tab-index focus.
3534
+ *
3535
+ * It is a primitive: no form-bridge / RBAC integration — use `<DatePicker>`
3536
+ * for a form-bound date field.
3537
+ */ function Calendar(props) {
3538
+ var _slotProps_root, _slotProps_header, _slotProps_grid;
3539
+ const { value, defaultValue, onChange, month, year, defaultMonth, defaultYear, onMonthChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, today, disabled = false, autoFocus = false, sx, slotProps, testId, 'aria-label': ariaLabel = 'Calendar' } = props;
3540
+ const isValueControlled = value !== undefined;
3541
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
3542
+ const selectedValue = isValueControlled ? value : internalValue;
3543
+ const handleSelect = useCallback((iso)=>{
3544
+ if (!isValueControlled) {
3545
+ setInternalValue(iso);
3546
+ }
3547
+ onChange == null ? void 0 : onChange(iso);
3548
+ }, [
3549
+ isValueControlled,
3550
+ onChange
3551
+ ]);
3552
+ const calendar = useCalendar({
3553
+ selected: selectedValue,
3554
+ month,
3555
+ year,
3556
+ defaultMonth,
3557
+ defaultYear,
3558
+ onMonthChange,
3559
+ minDate,
3560
+ maxDate,
3561
+ disabledDates,
3562
+ isDateDisabled,
3563
+ weekStartDay,
3564
+ locale,
3565
+ today,
3566
+ onSelect: handleSelect
3567
+ });
3568
+ // Roving focus: the active day cell holds DOM focus; it is moved only
3569
+ // after a keyboard interaction (or on mount when `autoFocus`). `focus`
3570
+ // uses `preventScroll` so opening the calendar inside a popup never
3571
+ // scroll-jumps the page before the popup is positioned.
3572
+ const focusedCellRef = useRef(null);
3573
+ const shouldFocusRef = useRef(autoFocus);
3574
+ useEffect(()=>{
3575
+ if (shouldFocusRef.current && focusedCellRef.current) {
3576
+ focusedCellRef.current.focus({
3577
+ preventScroll: true
3578
+ });
3579
+ shouldFocusRef.current = false;
3580
+ }
3581
+ }, [
3582
+ calendar.focusedDate
3583
+ ]);
3584
+ const handleKeyDown = useCallback((event)=>{
3585
+ if (disabled) {
3586
+ return;
3587
+ }
3588
+ const action = resolveCalendarKey(event.key);
3589
+ if (action === null) {
3590
+ return;
3591
+ }
3592
+ event.preventDefault();
3593
+ shouldFocusRef.current = true;
3594
+ switch(action){
3595
+ case 'select':
3596
+ calendar.selectDate(calendar.focusedDate);
3597
+ break;
3598
+ case 'monthForward':
3599
+ calendar.goToNextMonth();
3600
+ break;
3601
+ case 'monthBackward':
3602
+ calendar.goToPreviousMonth();
3603
+ break;
3604
+ default:
3605
+ calendar.navigate(action);
3606
+ }
3607
+ }, [
3608
+ disabled,
3609
+ calendar
3610
+ ]);
3611
+ const resolvedLocale = locale != null ? locale : 'en-US';
3612
+ const v = calendarVariants({
3613
+ disabled
3614
+ });
3615
+ return jsxs("div", {
3616
+ "data-testid": testId,
3617
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
3618
+ children: [
3619
+ jsxs("div", {
3620
+ className: cn(v.header(), slotProps == null ? void 0 : (_slotProps_header = slotProps.header) == null ? void 0 : _slotProps_header.className),
3621
+ children: [
3622
+ jsx("button", {
3623
+ type: "button",
3624
+ className: v.navButton(),
3625
+ disabled: disabled,
3626
+ "aria-label": "Previous month",
3627
+ onClick: ()=>{
3628
+ calendar.goToPreviousMonth();
3629
+ },
3630
+ children: jsx(ChevronLeftIcon$1, {})
3631
+ }),
3632
+ jsx("span", {
3633
+ "aria-live": "polite",
3634
+ className: v.monthLabel(),
3635
+ children: calendar.monthLabel
3636
+ }),
3637
+ jsx("button", {
3638
+ type: "button",
3639
+ className: v.navButton(),
3640
+ disabled: disabled,
3641
+ "aria-label": "Next month",
3642
+ onClick: ()=>{
3643
+ calendar.goToNextMonth();
3644
+ },
3645
+ children: jsx(ChevronRightIcon$2, {})
3646
+ })
3647
+ ]
3648
+ }),
3649
+ jsxs("div", {
3650
+ role: "grid",
3651
+ "aria-label": ariaLabel,
3652
+ onKeyDown: handleKeyDown,
3653
+ className: cn(v.grid(), slotProps == null ? void 0 : (_slotProps_grid = slotProps.grid) == null ? void 0 : _slotProps_grid.className),
3654
+ children: [
3655
+ jsx("div", {
3656
+ role: "row",
3657
+ className: v.weekdayRow(),
3658
+ children: calendar.weekdayNames.map((dayName, index)=>jsx("span", {
3659
+ role: "columnheader",
3660
+ className: v.weekday(),
3661
+ children: dayName
3662
+ }, `weekday-${String(index)}`))
3663
+ }),
3664
+ calendar.weeks.map((week)=>jsx("div", {
3665
+ role: "row",
3666
+ className: v.weekRow(),
3667
+ children: week.days.map((day)=>{
3668
+ var _slotProps_day;
3669
+ const isActiveCell = day.iso === calendar.focusedDate;
3670
+ return jsx("span", {
3671
+ role: "gridcell",
3672
+ className: "flex",
3673
+ children: jsx("button", {
3674
+ type: "button",
3675
+ ref: isActiveCell ? focusedCellRef : null,
3676
+ tabIndex: isActiveCell ? 0 : -1,
3677
+ disabled: disabled,
3678
+ "aria-label": formatLongDate({
3679
+ year: day.year,
3680
+ month: day.month,
3681
+ day: day.day
3682
+ }, resolvedLocale),
3683
+ "aria-current": day.isToday ? 'date' : undefined,
3684
+ "aria-pressed": day.isSelected,
3685
+ "aria-disabled": day.isDisabled,
3686
+ onClick: ()=>{
3687
+ calendar.selectDate(day.iso);
3688
+ },
3689
+ className: cn(calendarDayVariants({
3690
+ siblingMonth: day.isSiblingMonth,
3691
+ today: day.isToday,
3692
+ selected: day.isSelected,
3693
+ disabled: day.isDisabled
3694
+ }), slotProps == null ? void 0 : (_slotProps_day = slotProps.day) == null ? void 0 : _slotProps_day.className),
3695
+ children: day.day
3696
+ })
3697
+ }, day.iso);
3698
+ })
3699
+ }, week.key))
3700
+ ]
3701
+ })
3702
+ ]
3703
+ });
3704
+ }
3705
+
3706
+ const popoverVariants = tv({
3707
+ slots: {
3708
+ content: [
3709
+ // Neutral border auto-inverts via CSS-var swap.
3710
+ 'z-50 rounded-md border border-neutral-200',
3711
+ // bg-white hardcoded (no auto-invert) so `dark:` is required.
3712
+ // Target `dark:bg-neutral-100` resolves to #171717 in dark mode
3713
+ // (one elevation tier above the page surface).
3714
+ 'bg-white dark:bg-neutral-100 p-4 shadow-lg',
3715
+ // Text auto-inverts.
3716
+ 'text-sm text-neutral-900',
3717
+ 'focus:outline-none',
3718
+ 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
3719
+ 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
3720
+ 'motion-reduce:transition-none motion-reduce:duration-0'
3721
+ ],
3722
+ // Arrow follows content bg: `fill-white` in light + matching dark target.
3723
+ arrow: 'fill-white dark:fill-neutral-100'
3724
+ }
3725
+ });
3726
+
3727
+ /**
3728
+ * Dashforge TW `<Popover>` — clickable floating content panel.
3431
3729
  *
3432
- * Styling mirrors `<TextField>` for layout / sizes / error states so
3433
- * the two field types compose visually in the same form.
3730
+ * Built on `@radix-ui/react-popover`:
3731
+ * - Focus trap inside the popover (Tab cycles within).
3732
+ * - Outside-click + Escape dismiss.
3733
+ * - APG dialog-like semantics (`role="dialog"` on content).
3734
+ * - Portal-rendered.
3434
3735
  *
3435
- * Native `<input type="date|time|datetime-local">` quirks handled here:
3736
+ * Use `<Popover>` for menus / pickers / settings panels with
3737
+ * interactive children. Use `<Tooltip>` for read-only hints.
3738
+ */ function Popover(props) {
3739
+ var _slotProps_content, _slotProps_arrow;
3740
+ const { children, content, side = 'bottom', align = 'center', showArrow = false, open, onOpenChange, sideOffset = 8, width, slotProps } = props;
3741
+ const v = popoverVariants();
3742
+ return jsxs(RadixPopover.Root, {
3743
+ open: open,
3744
+ onOpenChange: onOpenChange,
3745
+ children: [
3746
+ jsx(RadixPopover.Trigger, {
3747
+ asChild: true,
3748
+ children: children
3749
+ }),
3750
+ jsx(RadixPopover.Portal, {
3751
+ children: jsxs(RadixPopover.Content, {
3752
+ side: side,
3753
+ align: align,
3754
+ sideOffset: sideOffset,
3755
+ style: width != null ? {
3756
+ width
3757
+ } : undefined,
3758
+ className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
3759
+ children: [
3760
+ content,
3761
+ showArrow && jsx(RadixPopover.Arrow, {
3762
+ className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
3763
+ })
3764
+ ]
3765
+ })
3766
+ })
3767
+ ]
3768
+ });
3769
+ }
3770
+
3771
+ /**
3772
+ * Slots for `<DatePicker>` — a bridge-integrated date field whose trigger
3773
+ * is a `<button>` styled as an input, opening a `<Calendar>` popover.
3436
3774
  *
3437
- * - The browser-rendered placeholder mask (e.g., `mm/dd/yyyy`) is
3438
- * invisible on `bg-neutral-50` by default in some themes; we keep
3439
- * the same neutral palette and rely on the input's own contrast
3440
- * rules (no extra `::placeholder` styling — browsers don't honor it
3441
- * on these input types).
3442
- * - The native picker indicator (calendar / clock icon) is
3443
- * browser-supplied; we ensure it stays visible in dark mode via
3444
- * `color-scheme: light dark` on the input (set inline below).
3445
- */ const dateTimePickerVariants = tv({
3775
+ * Theme identity: bare neutral / brand token-backed classes only — no
3776
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
3777
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
3778
+ */ const datePickerVariants = tv({
3446
3779
  slots: {
3447
- root: 'flex',
3780
+ root: 'flex flex-col',
3448
3781
  label: 'block text-sm font-medium text-neutral-900 mb-1',
3449
3782
  requiredMark: 'text-danger-500 ml-0.5',
3450
- inputWrapper: [
3451
- 'relative flex items-center',
3452
- 'rounded-md border bg-neutral-50',
3453
- 'border-neutral-300',
3454
- 'transition-colors',
3455
- 'focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500/30'
3456
- ],
3457
- input: [
3458
- 'block w-full bg-transparent outline-none',
3459
- 'text-neutral-900 placeholder:text-neutral-400',
3460
- 'disabled:cursor-not-allowed disabled:opacity-60',
3461
- 'read-only:cursor-default',
3462
- // `color-scheme` lets the native calendar / clock icon respect
3463
- // the dark mode (otherwise it stays light-themed against dark bg)
3464
- '[color-scheme:light_dark]'
3465
- ],
3783
+ trigger: 'flex items-center gap-2 h-10 min-w-[12rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
3784
+ value: 'flex-1 truncate text-left',
3785
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
3786
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
3787
+ helperText: 'mt-1 text-sm text-neutral-600',
3788
+ errorText: 'mt-1 text-sm text-danger-600'
3789
+ },
3790
+ variants: {
3791
+ layout: {
3792
+ stacked: {},
3793
+ inline: {
3794
+ root: 'flex-row items-start gap-3',
3795
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
3796
+ }
3797
+ },
3798
+ error: {
3799
+ true: {
3800
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
3801
+ }
3802
+ },
3803
+ fullWidth: {
3804
+ true: {
3805
+ root: 'w-full',
3806
+ trigger: 'w-full min-w-0'
3807
+ }
3808
+ }
3809
+ },
3810
+ defaultVariants: {
3811
+ layout: 'stacked',
3812
+ error: false,
3813
+ fullWidth: false
3814
+ }
3815
+ });
3816
+
3817
+ // Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
3818
+ function CalendarIcon$2() {
3819
+ return jsxs("svg", {
3820
+ width: "1em",
3821
+ height: "1em",
3822
+ viewBox: "0 0 16 16",
3823
+ fill: "none",
3824
+ "aria-hidden": "true",
3825
+ children: [
3826
+ jsx("rect", {
3827
+ x: "2.5",
3828
+ y: "3.5",
3829
+ width: "11",
3830
+ height: "10",
3831
+ rx: "1.5",
3832
+ stroke: "currentColor",
3833
+ strokeWidth: "1.5"
3834
+ }),
3835
+ jsx("path", {
3836
+ d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
3837
+ stroke: "currentColor",
3838
+ strokeWidth: "1.5",
3839
+ strokeLinecap: "round"
3840
+ })
3841
+ ]
3842
+ });
3843
+ }
3844
+ /**
3845
+ * Formats a stored ISO date for the trigger display, localized to a medium
3846
+ * date style (e.g. `"May 20, 2026"`). Formatting is done in UTC so the
3847
+ * displayed day always matches the stored `YYYY-MM-DD`.
3848
+ */ function formatDisplayDate(iso, locale) {
3849
+ if (iso == null || iso === '') {
3850
+ return '';
3851
+ }
3852
+ const parts = parseISODate(iso);
3853
+ if (parts === null) {
3854
+ return iso;
3855
+ }
3856
+ return new Intl.DateTimeFormat(locale, {
3857
+ dateStyle: 'medium',
3858
+ timeZone: 'UTC'
3859
+ }).format(Date.UTC(parts.year, parts.month - 1, parts.day));
3860
+ }
3861
+ /**
3862
+ * Dashforge TW `<DatePicker>` — a form-bound single-date field.
3863
+ *
3864
+ * A read-only trigger button paired with a `<Calendar>` popover (Radix
3865
+ * Popover — toggle / Escape / outside-click / focus-trap are Radix-managed).
3866
+ * Integrates with the Dashforge form bridge + RBAC, mirroring the other
3867
+ * `@dashforge/tw` form fields.
3868
+ *
3869
+ * Storage contract: an ISO `YYYY-MM-DD` string, or `null` — shared with the
3870
+ * MUI `@dashforge/ui` `DatePicker`.
3871
+ */ function DatePicker(props) {
3872
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
3873
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, fullWidth, sx, slotProps, testId } = props;
3874
+ // ───── Hooks (unconditional, before any early return) ─────
3875
+ const bridge = useContext(DashFormContext);
3876
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
3877
+ const fieldMeta = useDashFieldMeta(name);
3878
+ const accessState = useAccessState(access);
3879
+ const fieldId = useId();
3880
+ const helperId = `${fieldId}-help`;
3881
+ const [isOpen, setIsOpen] = useState(false);
3882
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
3883
+ // StrictMode-safe unregister-on-unmount.
3884
+ const unregisterRef = useRef({
3885
+ bridge,
3886
+ name
3887
+ });
3888
+ unregisterRef.current = {
3889
+ bridge,
3890
+ name
3891
+ };
3892
+ const isMountedRef = useRef(false);
3893
+ useEffect(()=>{
3894
+ isMountedRef.current = true;
3895
+ return ()=>{
3896
+ isMountedRef.current = false;
3897
+ const { bridge: cap, name: capName } = unregisterRef.current;
3898
+ queueMicrotask(()=>{
3899
+ var _cap_unregister;
3900
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
3901
+ });
3902
+ };
3903
+ }, []);
3904
+ // ───── Derived ─────
3905
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
3906
+ const effectiveReadOnly = accessState.readonly;
3907
+ const isInteractive = !effectiveDisabled && !effectiveReadOnly;
3908
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
3909
+ let resolvedError = error;
3910
+ let resolvedHelperText = helperText;
3911
+ let registration = null;
3912
+ let resolvedValue;
3913
+ if (isFormMode && bridge) {
3914
+ registration = bridge.register(name, rules);
3915
+ const validation = resolveValidationState(name, bridge, error, helperText);
3916
+ resolvedError = validation.error;
3917
+ resolvedHelperText = validation.helperText;
3918
+ if (explicitValue !== undefined) {
3919
+ resolvedValue = explicitValue;
3920
+ } else {
3921
+ const bv = fieldMeta.value;
3922
+ resolvedValue = bv == null ? null : String(bv);
3923
+ }
3924
+ } else if (explicitValue !== undefined) {
3925
+ resolvedValue = explicitValue;
3926
+ } else {
3927
+ resolvedValue = internalValue;
3928
+ }
3929
+ // RHF threads its own callback ref through here in form mode.
3930
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
3931
+ const triggerRef = useCallback((instance)=>{
3932
+ if (typeof registrationRefFn === 'function') {
3933
+ registrationRefFn(instance);
3934
+ }
3935
+ }, [
3936
+ registrationRefFn
3937
+ ]);
3938
+ // ───── Render-time guards (after all hooks) ─────
3939
+ if (!isVisible) return null;
3940
+ if (!accessState.visible) return null;
3941
+ const v = datePickerVariants({
3942
+ layout,
3943
+ error: resolvedError,
3944
+ fullWidth
3945
+ });
3946
+ const resolvedLocale = locale != null ? locale : 'en-US';
3947
+ const displayValue = formatDisplayDate(resolvedValue, resolvedLocale);
3948
+ const commitValue = (next)=>{
3949
+ if (isFormMode && bridge) {
3950
+ var _registration_onChange;
3951
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
3952
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
3953
+ target: {
3954
+ name,
3955
+ value: next != null ? next : ''
3956
+ },
3957
+ type: 'change'
3958
+ }));
3959
+ } else if (explicitValue === undefined) {
3960
+ setInternalValue(next);
3961
+ }
3962
+ onChange == null ? void 0 : onChange(next);
3963
+ };
3964
+ // Mark the field touched on popover close. Reads the freshest committed
3965
+ // value straight from the bridge so the touched-blur never reverts a
3966
+ // value just chosen in the calendar.
3967
+ const markTouched = ()=>{
3968
+ if (isFormMode && bridge) {
3969
+ var _registration_onBlur;
3970
+ const committed = bridge.getValue(name);
3971
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
3972
+ target: {
3973
+ name,
3974
+ value: committed == null ? '' : String(committed)
3975
+ },
3976
+ type: 'blur'
3977
+ }));
3978
+ }
3979
+ };
3980
+ const handleOpenChange = (next)=>{
3981
+ if (next && !isInteractive) {
3982
+ return;
3983
+ }
3984
+ setIsOpen(next);
3985
+ if (!next) {
3986
+ markTouched();
3987
+ }
3988
+ };
3989
+ const handleCalendarChange = (next)=>{
3990
+ commitValue(next);
3991
+ setIsOpen(false);
3992
+ markTouched();
3993
+ };
3994
+ return jsxs("div", {
3995
+ "data-testid": testId,
3996
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
3997
+ children: [
3998
+ label && jsxs("label", {
3999
+ htmlFor: fieldId,
4000
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4001
+ children: [
4002
+ label,
4003
+ required && jsx("span", {
4004
+ "aria-hidden": "true",
4005
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4006
+ children: "*"
4007
+ })
4008
+ ]
4009
+ }),
4010
+ jsx(Popover, {
4011
+ open: isOpen,
4012
+ onOpenChange: handleOpenChange,
4013
+ side: "bottom",
4014
+ align: "start",
4015
+ content: jsx(Calendar, {
4016
+ value: resolvedValue,
4017
+ onChange: handleCalendarChange,
4018
+ minDate: minDate,
4019
+ maxDate: maxDate,
4020
+ disabledDates: disabledDates,
4021
+ isDateDisabled: isDateDisabled,
4022
+ weekStartDay: weekStartDay,
4023
+ locale: locale,
4024
+ "aria-label": "Choose date",
4025
+ sx: "border-0 bg-transparent p-0"
4026
+ }),
4027
+ children: jsxs("button", {
4028
+ type: "button",
4029
+ id: fieldId,
4030
+ ref: triggerRef,
4031
+ disabled: effectiveDisabled,
4032
+ "aria-invalid": resolvedError ? true : undefined,
4033
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4034
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4035
+ children: [
4036
+ jsx("span", {
4037
+ className: displayValue ? v.value() : v.placeholder(),
4038
+ children: displayValue || placeholder || 'Select a date'
4039
+ }),
4040
+ jsx("span", {
4041
+ className: v.icon(),
4042
+ children: jsx(CalendarIcon$2, {})
4043
+ })
4044
+ ]
4045
+ })
4046
+ }),
4047
+ resolvedHelperText && jsx("p", {
4048
+ id: helperId,
4049
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4050
+ children: resolvedHelperText
4051
+ })
4052
+ ]
4053
+ });
4054
+ }
4055
+
4056
+ /**
4057
+ * Slots for `<TimePicker>` — a bridge-integrated time field whose trigger
4058
+ * is a `<button>` styled as an input, opening a time-list popover.
4059
+ *
4060
+ * Theme identity: bare neutral / brand token-backed classes only — no
4061
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
4062
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
4063
+ */ const timePickerVariants = tv({
4064
+ slots: {
4065
+ root: 'flex flex-col',
4066
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
4067
+ requiredMark: 'text-danger-500 ml-0.5',
4068
+ trigger: 'flex items-center gap-2 h-10 min-w-[10rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4069
+ value: 'flex-1 truncate text-left',
4070
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
4071
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
4072
+ list: 'flex max-h-64 min-w-[8rem] flex-col gap-0.5 overflow-y-auto p-1',
4073
+ option: 'flex w-full cursor-pointer items-center rounded px-2.5 py-1.5 text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500 aria-selected:bg-primary-600 aria-selected:text-white aria-selected:hover:bg-primary-600 aria-disabled:cursor-default aria-disabled:text-neutral-300 aria-disabled:opacity-60 aria-disabled:hover:bg-transparent',
4074
+ helperText: 'mt-1 text-sm text-neutral-600',
4075
+ errorText: 'mt-1 text-sm text-danger-600'
4076
+ },
4077
+ variants: {
4078
+ layout: {
4079
+ stacked: {},
4080
+ inline: {
4081
+ root: 'flex-row items-start gap-3',
4082
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
4083
+ }
4084
+ },
4085
+ error: {
4086
+ true: {
4087
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
4088
+ }
4089
+ },
4090
+ fullWidth: {
4091
+ true: {
4092
+ root: 'w-full',
4093
+ trigger: 'w-full min-w-0'
4094
+ }
4095
+ }
4096
+ },
4097
+ defaultVariants: {
4098
+ layout: 'stacked',
4099
+ error: false,
4100
+ fullWidth: false
4101
+ }
4102
+ });
4103
+
4104
+ // Inline 16×16 stroke clock glyph — no icon dependency (tw convention).
4105
+ function ClockIcon() {
4106
+ return jsxs("svg", {
4107
+ width: "1em",
4108
+ height: "1em",
4109
+ viewBox: "0 0 16 16",
4110
+ fill: "none",
4111
+ "aria-hidden": "true",
4112
+ children: [
4113
+ jsx("circle", {
4114
+ cx: "8",
4115
+ cy: "8",
4116
+ r: "6",
4117
+ stroke: "currentColor",
4118
+ strokeWidth: "1.5"
4119
+ }),
4120
+ jsx("path", {
4121
+ d: "M8 4.5V8l2.4 1.4",
4122
+ stroke: "currentColor",
4123
+ strokeWidth: "1.5",
4124
+ strokeLinecap: "round",
4125
+ strokeLinejoin: "round"
4126
+ })
4127
+ ]
4128
+ });
4129
+ }
4130
+ /**
4131
+ * Dashforge TW `<TimePicker>` — a form-bound time-of-day field.
4132
+ *
4133
+ * A read-only trigger button paired with a time-list popover (Radix
4134
+ * Popover). Integrates with the Dashforge form bridge + RBAC.
4135
+ *
4136
+ * Storage contract: a canonical 24-hour `"HH:mm"` string, or `null` —
4137
+ * shared with the MUI `@dashforge/ui` `TimePicker`. `hour12` is display-only.
4138
+ */ function TimePicker(props) {
4139
+ var _timeStringToMinutes, _timeStringToMinutes1;
4140
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
4141
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minTime, maxTime, stepMinutes, hour12 = false, fullWidth, sx, slotProps, testId } = props;
4142
+ const bridge = useContext(DashFormContext);
4143
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
4144
+ const fieldMeta = useDashFieldMeta(name);
4145
+ const accessState = useAccessState(access);
4146
+ const fieldId = useId();
4147
+ const helperId = `${fieldId}-help`;
4148
+ const [isOpen, setIsOpen] = useState(false);
4149
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
4150
+ const unregisterRef = useRef({
4151
+ bridge,
4152
+ name
4153
+ });
4154
+ unregisterRef.current = {
4155
+ bridge,
4156
+ name
4157
+ };
4158
+ const isMountedRef = useRef(false);
4159
+ useEffect(()=>{
4160
+ isMountedRef.current = true;
4161
+ return ()=>{
4162
+ isMountedRef.current = false;
4163
+ const { bridge: cap, name: capName } = unregisterRef.current;
4164
+ queueMicrotask(()=>{
4165
+ var _cap_unregister;
4166
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
4167
+ });
4168
+ };
4169
+ }, []);
4170
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
4171
+ const isInteractive = !effectiveDisabled && !accessState.readonly;
4172
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
4173
+ let resolvedError = error;
4174
+ let resolvedHelperText = helperText;
4175
+ let registration = null;
4176
+ let resolvedValue;
4177
+ if (isFormMode && bridge) {
4178
+ registration = bridge.register(name, rules);
4179
+ const validation = resolveValidationState(name, bridge, error, helperText);
4180
+ resolvedError = validation.error;
4181
+ resolvedHelperText = validation.helperText;
4182
+ if (explicitValue !== undefined) {
4183
+ resolvedValue = explicitValue;
4184
+ } else {
4185
+ const bv = fieldMeta.value;
4186
+ resolvedValue = bv == null ? null : String(bv);
4187
+ }
4188
+ } else if (explicitValue !== undefined) {
4189
+ resolvedValue = explicitValue;
4190
+ } else {
4191
+ resolvedValue = internalValue;
4192
+ }
4193
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
4194
+ const triggerRef = useCallback((instance)=>{
4195
+ if (typeof registrationRefFn === 'function') {
4196
+ registrationRefFn(instance);
4197
+ }
4198
+ }, [
4199
+ registrationRefFn
4200
+ ]);
4201
+ if (!isVisible) return null;
4202
+ if (!accessState.visible) return null;
4203
+ const v = timePickerVariants({
4204
+ layout,
4205
+ error: resolvedError,
4206
+ fullWidth
4207
+ });
4208
+ const displayValue = resolvedValue != null && resolvedValue !== '' ? formatTime(resolvedValue, {
4209
+ hour12
4210
+ }) : '';
4211
+ const options = generateTimeOptions(_extends({}, minTime !== undefined && {
4212
+ start: minTime
4213
+ }, maxTime !== undefined && {
4214
+ end: maxTime
4215
+ }, stepMinutes !== undefined && {
4216
+ stepMinutes
4217
+ }));
4218
+ const minMinutes = (_timeStringToMinutes = timeStringToMinutes(minTime != null ? minTime : '00:00')) != null ? _timeStringToMinutes : 0;
4219
+ const maxMinutes = (_timeStringToMinutes1 = timeStringToMinutes(maxTime != null ? maxTime : '23:59')) != null ? _timeStringToMinutes1 : 1439;
4220
+ const isTimeDisabled = (time)=>{
4221
+ const minutes = timeStringToMinutes(time);
4222
+ return minutes === null || minutes < minMinutes || minutes > maxMinutes;
4223
+ };
4224
+ const commitValue = (next)=>{
4225
+ if (isFormMode && bridge) {
4226
+ var _registration_onChange;
4227
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
4228
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
4229
+ target: {
4230
+ name,
4231
+ value: next != null ? next : ''
4232
+ },
4233
+ type: 'change'
4234
+ }));
4235
+ } else if (explicitValue === undefined) {
4236
+ setInternalValue(next);
4237
+ }
4238
+ onChange == null ? void 0 : onChange(next);
4239
+ };
4240
+ const markTouched = ()=>{
4241
+ if (isFormMode && bridge) {
4242
+ var _registration_onBlur;
4243
+ const committed = bridge.getValue(name);
4244
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
4245
+ target: {
4246
+ name,
4247
+ value: committed == null ? '' : String(committed)
4248
+ },
4249
+ type: 'blur'
4250
+ }));
4251
+ }
4252
+ };
4253
+ const handleOpenChange = (next)=>{
4254
+ if (next && !isInteractive) {
4255
+ return;
4256
+ }
4257
+ setIsOpen(next);
4258
+ if (!next) {
4259
+ markTouched();
4260
+ }
4261
+ };
4262
+ const handleSelect = (time)=>{
4263
+ if (isTimeDisabled(time)) {
4264
+ return;
4265
+ }
4266
+ commitValue(time);
4267
+ setIsOpen(false);
4268
+ markTouched();
4269
+ };
4270
+ return jsxs("div", {
4271
+ "data-testid": testId,
4272
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
4273
+ children: [
4274
+ label && jsxs("label", {
4275
+ htmlFor: fieldId,
4276
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4277
+ children: [
4278
+ label,
4279
+ required && jsx("span", {
4280
+ "aria-hidden": "true",
4281
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4282
+ children: "*"
4283
+ })
4284
+ ]
4285
+ }),
4286
+ jsx(Popover, {
4287
+ open: isOpen,
4288
+ onOpenChange: handleOpenChange,
4289
+ side: "bottom",
4290
+ align: "start",
4291
+ content: jsx("div", {
4292
+ role: "listbox",
4293
+ "aria-label": "Time options",
4294
+ className: v.list(),
4295
+ children: options.map((time)=>jsx("button", {
4296
+ type: "button",
4297
+ role: "option",
4298
+ "aria-selected": time === resolvedValue,
4299
+ "aria-disabled": isTimeDisabled(time),
4300
+ className: v.option(),
4301
+ onClick: ()=>{
4302
+ handleSelect(time);
4303
+ },
4304
+ children: formatTime(time, {
4305
+ hour12
4306
+ })
4307
+ }, time))
4308
+ }),
4309
+ children: jsxs("button", {
4310
+ type: "button",
4311
+ id: fieldId,
4312
+ ref: triggerRef,
4313
+ disabled: effectiveDisabled,
4314
+ "aria-invalid": resolvedError ? true : undefined,
4315
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4316
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4317
+ children: [
4318
+ jsx("span", {
4319
+ className: displayValue ? v.value() : v.placeholder(),
4320
+ children: displayValue || placeholder || 'Select a time'
4321
+ }),
4322
+ jsx("span", {
4323
+ className: v.icon(),
4324
+ children: jsx(ClockIcon, {})
4325
+ })
4326
+ ]
4327
+ })
4328
+ }),
4329
+ resolvedHelperText && jsx("p", {
4330
+ id: helperId,
4331
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4332
+ children: resolvedHelperText
4333
+ })
4334
+ ]
4335
+ });
4336
+ }
4337
+
4338
+ /**
4339
+ * Slots for `<DateTimePicker>` — a bridge-integrated date+time field whose
4340
+ * trigger is a `<button>` styled as an input, opening a popover that
4341
+ * combines a `<Calendar>` with a time list.
4342
+ *
4343
+ * Theme identity: bare neutral / brand token-backed classes only — no
4344
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
4345
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
4346
+ */ const dateTimePickerVariants = tv({
4347
+ slots: {
4348
+ root: 'flex flex-col',
4349
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
4350
+ requiredMark: 'text-danger-500 ml-0.5',
4351
+ trigger: 'flex items-center gap-2 h-10 min-w-[13rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4352
+ value: 'flex-1 truncate text-left',
4353
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
4354
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
4355
+ panel: 'flex items-stretch',
4356
+ list: 'flex max-h-[19rem] min-w-[6.5rem] flex-col gap-0.5 overflow-y-auto border-l border-neutral-200 p-1',
4357
+ option: 'flex w-full cursor-pointer items-center rounded px-2.5 py-1.5 text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500 aria-selected:bg-primary-600 aria-selected:text-white aria-selected:hover:bg-primary-600',
4358
+ helperText: 'mt-1 text-sm text-neutral-600',
4359
+ errorText: 'mt-1 text-sm text-danger-600'
4360
+ },
4361
+ variants: {
4362
+ layout: {
4363
+ stacked: {},
4364
+ inline: {
4365
+ root: 'flex-row items-start gap-3',
4366
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
4367
+ }
4368
+ },
4369
+ error: {
4370
+ true: {
4371
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
4372
+ }
4373
+ },
4374
+ fullWidth: {
4375
+ true: {
4376
+ root: 'w-full',
4377
+ trigger: 'w-full min-w-0'
4378
+ }
4379
+ }
4380
+ },
4381
+ defaultVariants: {
4382
+ layout: 'stacked',
4383
+ error: false,
4384
+ fullWidth: false
4385
+ }
4386
+ });
4387
+
4388
+ // Inline 16×16 stroke calendar glyph — no icon dependency (tw convention).
4389
+ function CalendarIcon$1() {
4390
+ return jsxs("svg", {
4391
+ width: "1em",
4392
+ height: "1em",
4393
+ viewBox: "0 0 16 16",
4394
+ fill: "none",
4395
+ "aria-hidden": "true",
4396
+ children: [
4397
+ jsx("rect", {
4398
+ x: "2.5",
4399
+ y: "3.5",
4400
+ width: "11",
4401
+ height: "10",
4402
+ rx: "1.5",
4403
+ stroke: "currentColor",
4404
+ strokeWidth: "1.5"
4405
+ }),
4406
+ jsx("path", {
4407
+ d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
4408
+ stroke: "currentColor",
4409
+ strokeWidth: "1.5",
4410
+ strokeLinecap: "round"
4411
+ })
4412
+ ]
4413
+ });
4414
+ }
4415
+ const DATETIME_PATTERN = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2})/;
4416
+ /** Splits a stored `YYYY-MM-DDTHH:mm` value into its date and time parts. */ function splitDateTime(value) {
4417
+ if (value == null || value === '') {
4418
+ return {
4419
+ date: null,
4420
+ time: null
4421
+ };
4422
+ }
4423
+ const match = DATETIME_PATTERN.exec(value);
4424
+ if (match !== null) {
4425
+ var _match_, _match_1, _match_2;
4426
+ return {
4427
+ date: (_match_ = match[1]) != null ? _match_ : null,
4428
+ time: `${(_match_1 = match[2]) != null ? _match_1 : '00'}:${(_match_2 = match[3]) != null ? _match_2 : '00'}`
4429
+ };
4430
+ }
4431
+ return {
4432
+ date: parseISODate(value) !== null ? value : null,
4433
+ time: null
4434
+ };
4435
+ }
4436
+ /** Combines a date and a time into a `YYYY-MM-DDTHH:mm` value (or `null`). */ function joinDateTime(date, time) {
4437
+ if (date === null && time === null) {
4438
+ return null;
4439
+ }
4440
+ return `${date != null ? date : getTodayISODate()}T${time != null ? time : '00:00'}`;
4441
+ }
4442
+ /** Formats a stored datetime for the trigger display. */ function formatDateTime(value, locale, hour12) {
4443
+ const { date, time } = splitDateTime(value);
4444
+ const parts = [];
4445
+ if (date !== null) {
4446
+ const parsed = parseISODate(date);
4447
+ parts.push(parsed === null ? date : new Intl.DateTimeFormat(locale, {
4448
+ dateStyle: 'medium',
4449
+ timeZone: 'UTC'
4450
+ }).format(Date.UTC(parsed.year, parsed.month - 1, parsed.day)));
4451
+ }
4452
+ if (time !== null) {
4453
+ parts.push(formatTime(time, {
4454
+ hour12
4455
+ }));
4456
+ }
4457
+ return parts.join(', ');
4458
+ }
4459
+ /**
4460
+ * Dashforge TW `<DateTimePicker>` — a form-bound date + time field.
4461
+ *
4462
+ * A read-only trigger button paired with a popover combining a `<Calendar>`
4463
+ * and a time list (Radix Popover). Integrates with the Dashforge form
4464
+ * bridge + RBAC.
4465
+ *
4466
+ * Storage contract: a naive ISO datetime `"YYYY-MM-DDTHH:mm"` — no seconds,
4467
+ * no timezone. Shared with the MUI `@dashforge/ui` `DateTimePicker`.
4468
+ */ function DateTimePicker(props) {
4469
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
4470
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, stepMinutes, hour12 = false, fullWidth, sx, slotProps, testId } = props;
4471
+ const bridge = useContext(DashFormContext);
4472
+ const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
4473
+ const fieldMeta = useDashFieldMeta(name);
4474
+ const accessState = useAccessState(access);
4475
+ const fieldId = useId();
4476
+ const helperId = `${fieldId}-help`;
4477
+ const [isOpen, setIsOpen] = useState(false);
4478
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : null);
4479
+ const unregisterRef = useRef({
4480
+ bridge,
4481
+ name
4482
+ });
4483
+ unregisterRef.current = {
4484
+ bridge,
4485
+ name
4486
+ };
4487
+ const isMountedRef = useRef(false);
4488
+ useEffect(()=>{
4489
+ isMountedRef.current = true;
4490
+ return ()=>{
4491
+ isMountedRef.current = false;
4492
+ const { bridge: cap, name: capName } = unregisterRef.current;
4493
+ queueMicrotask(()=>{
4494
+ var _cap_unregister;
4495
+ if (!isMountedRef.current) cap == null ? void 0 : (_cap_unregister = cap.unregister) == null ? void 0 : _cap_unregister.call(cap, capName);
4496
+ });
4497
+ };
4498
+ }, []);
4499
+ const effectiveDisabled = Boolean(disabled) || accessState.disabled;
4500
+ const isInteractive = !effectiveDisabled && !accessState.readonly;
4501
+ const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
4502
+ let resolvedError = error;
4503
+ let resolvedHelperText = helperText;
4504
+ let registration = null;
4505
+ let resolvedValue;
4506
+ if (isFormMode && bridge) {
4507
+ registration = bridge.register(name, rules);
4508
+ const validation = resolveValidationState(name, bridge, error, helperText);
4509
+ resolvedError = validation.error;
4510
+ resolvedHelperText = validation.helperText;
4511
+ if (explicitValue !== undefined) {
4512
+ resolvedValue = explicitValue;
4513
+ } else {
4514
+ const bv = fieldMeta.value;
4515
+ resolvedValue = bv == null ? null : String(bv);
4516
+ }
4517
+ } else if (explicitValue !== undefined) {
4518
+ resolvedValue = explicitValue;
4519
+ } else {
4520
+ resolvedValue = internalValue;
4521
+ }
4522
+ const registrationRefFn = registration == null ? void 0 : registration.ref;
4523
+ const triggerRef = useCallback((instance)=>{
4524
+ if (typeof registrationRefFn === 'function') {
4525
+ registrationRefFn(instance);
4526
+ }
4527
+ }, [
4528
+ registrationRefFn
4529
+ ]);
4530
+ if (!isVisible) return null;
4531
+ if (!accessState.visible) return null;
4532
+ const v = dateTimePickerVariants({
4533
+ layout,
4534
+ error: resolvedError,
4535
+ fullWidth
4536
+ });
4537
+ const resolvedLocale = locale != null ? locale : 'en-US';
4538
+ const { date, time } = splitDateTime(resolvedValue);
4539
+ const displayValue = formatDateTime(resolvedValue, resolvedLocale, hour12);
4540
+ const options = generateTimeOptions(_extends({}, stepMinutes !== undefined && {
4541
+ stepMinutes
4542
+ }));
4543
+ const commitValue = (next)=>{
4544
+ if (isFormMode && bridge) {
4545
+ var _registration_onChange;
4546
+ bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
4547
+ void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
4548
+ target: {
4549
+ name,
4550
+ value: next != null ? next : ''
4551
+ },
4552
+ type: 'change'
4553
+ }));
4554
+ } else if (explicitValue === undefined) {
4555
+ setInternalValue(next);
4556
+ }
4557
+ onChange == null ? void 0 : onChange(next);
4558
+ };
4559
+ const markTouched = ()=>{
4560
+ if (isFormMode && bridge) {
4561
+ var _registration_onBlur;
4562
+ const committed = bridge.getValue(name);
4563
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
4564
+ target: {
4565
+ name,
4566
+ value: committed == null ? '' : String(committed)
4567
+ },
4568
+ type: 'blur'
4569
+ }));
4570
+ }
4571
+ };
4572
+ const handleOpenChange = (next)=>{
4573
+ if (next && !isInteractive) {
4574
+ return;
4575
+ }
4576
+ setIsOpen(next);
4577
+ if (!next) {
4578
+ markTouched();
4579
+ }
4580
+ };
4581
+ // Picking a date keeps the popover open — the user still needs a time.
4582
+ const handleDateSelect = (nextDate)=>{
4583
+ commitValue(joinDateTime(nextDate, time));
4584
+ };
4585
+ // Picking a time completes the value and closes the popover.
4586
+ const handleTimeSelect = (nextTime)=>{
4587
+ commitValue(joinDateTime(date, nextTime));
4588
+ setIsOpen(false);
4589
+ markTouched();
4590
+ };
4591
+ return jsxs("div", {
4592
+ "data-testid": testId,
4593
+ className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
4594
+ children: [
4595
+ label && jsxs("label", {
4596
+ htmlFor: fieldId,
4597
+ className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
4598
+ children: [
4599
+ label,
4600
+ required && jsx("span", {
4601
+ "aria-hidden": "true",
4602
+ className: cn(v.requiredMark(), slotProps == null ? void 0 : (_slotProps_requiredMark = slotProps.requiredMark) == null ? void 0 : _slotProps_requiredMark.className),
4603
+ children: "*"
4604
+ })
4605
+ ]
4606
+ }),
4607
+ jsx(Popover, {
4608
+ open: isOpen,
4609
+ onOpenChange: handleOpenChange,
4610
+ side: "bottom",
4611
+ align: "start",
4612
+ content: jsxs("div", {
4613
+ className: v.panel(),
4614
+ children: [
4615
+ jsx(Calendar, {
4616
+ value: date,
4617
+ onChange: handleDateSelect,
4618
+ minDate: minDate,
4619
+ maxDate: maxDate,
4620
+ disabledDates: disabledDates,
4621
+ isDateDisabled: isDateDisabled,
4622
+ weekStartDay: weekStartDay,
4623
+ locale: locale,
4624
+ "aria-label": "Choose date",
4625
+ sx: "border-0 bg-transparent p-0"
4626
+ }),
4627
+ jsx("div", {
4628
+ role: "listbox",
4629
+ "aria-label": "Time options",
4630
+ className: v.list(),
4631
+ children: options.map((option)=>jsx("button", {
4632
+ type: "button",
4633
+ role: "option",
4634
+ "aria-selected": option === time,
4635
+ className: v.option(),
4636
+ onClick: ()=>{
4637
+ handleTimeSelect(option);
4638
+ },
4639
+ children: formatTime(option, {
4640
+ hour12
4641
+ })
4642
+ }, option))
4643
+ })
4644
+ ]
4645
+ }),
4646
+ children: jsxs("button", {
4647
+ type: "button",
4648
+ id: fieldId,
4649
+ ref: triggerRef,
4650
+ disabled: effectiveDisabled,
4651
+ "aria-invalid": resolvedError ? true : undefined,
4652
+ "aria-describedby": resolvedHelperText ? helperId : undefined,
4653
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
4654
+ children: [
4655
+ jsx("span", {
4656
+ className: displayValue ? v.value() : v.placeholder(),
4657
+ children: displayValue || placeholder || 'Select date and time'
4658
+ }),
4659
+ jsx("span", {
4660
+ className: v.icon(),
4661
+ children: jsx(CalendarIcon$1, {})
4662
+ })
4663
+ ]
4664
+ })
4665
+ }),
4666
+ resolvedHelperText && jsx("p", {
4667
+ id: helperId,
4668
+ className: cn(resolvedError ? v.errorText() : v.helperText(), resolvedError ? slotProps == null ? void 0 : (_slotProps_errorText = slotProps.errorText) == null ? void 0 : _slotProps_errorText.className : slotProps == null ? void 0 : (_slotProps_helperText = slotProps.helperText) == null ? void 0 : _slotProps_helperText.className),
4669
+ children: resolvedHelperText
4670
+ })
4671
+ ]
4672
+ });
4673
+ }
4674
+
4675
+ /**
4676
+ * Slots for `<DateRangePicker>` — a bridge-integrated start/end date field
4677
+ * whose trigger is a `<button>` styled as an input, opening a dual-month
4678
+ * range calendar popover.
4679
+ *
4680
+ * Theme identity: bare neutral / brand token-backed classes only — no
4681
+ * `dark:` variants on the neutral palette (the preset CSS-var swap
4682
+ * auto-inverts). See `_shared/themeIdentity.test.ts`.
4683
+ */ const dateRangePickerVariants = tv({
4684
+ slots: {
4685
+ root: 'flex flex-col',
4686
+ label: 'block text-sm font-medium text-neutral-900 mb-1',
4687
+ requiredMark: 'text-danger-500 ml-0.5',
4688
+ trigger: 'flex items-center gap-2 h-10 min-w-[15rem] rounded-md border border-neutral-300 bg-neutral-50 px-3 text-base text-neutral-900 cursor-pointer transition-colors hover:border-neutral-400 focus-visible:outline-none focus-visible:border-primary-500 focus-visible:ring-2 focus-visible:ring-primary-500/30 disabled:cursor-not-allowed disabled:opacity-60 disabled:bg-neutral-200',
4689
+ value: 'flex-1 truncate text-left',
4690
+ placeholder: 'flex-1 truncate text-left text-neutral-400',
4691
+ icon: 'shrink-0 text-[1rem] text-neutral-500',
4692
+ panel: 'flex gap-3 p-3 select-none',
4693
+ month: 'flex flex-col gap-2',
4694
+ header: 'flex items-center justify-between gap-1',
4695
+ navButton: 'inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-[1rem] text-neutral-600 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500',
4696
+ navSpacer: 'h-7 w-7 shrink-0',
4697
+ monthLabel: 'flex-1 text-center text-sm font-semibold text-neutral-900',
4698
+ grid: 'flex flex-col gap-1',
4699
+ weekdayRow: 'grid grid-cols-7 gap-1',
4700
+ weekday: 'flex h-8 items-center justify-center text-xs font-semibold text-neutral-500',
4701
+ weekRow: 'grid grid-cols-7 gap-1',
3466
4702
  helperText: 'mt-1 text-sm text-neutral-600',
3467
4703
  errorText: 'mt-1 text-sm text-danger-600'
3468
4704
  },
3469
4705
  variants: {
3470
- size: {
3471
- sm: {
3472
- inputWrapper: 'h-8 px-2',
3473
- input: 'text-sm'
3474
- },
3475
- md: {
3476
- inputWrapper: 'h-10 px-3',
3477
- input: 'text-base'
3478
- },
3479
- lg: {
3480
- inputWrapper: 'h-12 px-4',
3481
- input: 'text-lg'
3482
- }
3483
- },
3484
4706
  layout: {
3485
- stacked: {
3486
- root: 'flex-col'
3487
- },
4707
+ stacked: {},
3488
4708
  inline: {
3489
4709
  root: 'flex-row items-start gap-3',
3490
- label: 'mb-0 pt-2 whitespace-nowrap shrink-0'
4710
+ label: 'mb-0 pt-2.5 whitespace-nowrap shrink-0'
3491
4711
  }
3492
4712
  },
3493
4713
  error: {
3494
4714
  true: {
3495
- inputWrapper: 'border-danger-500 focus-within:border-danger-500 focus-within:ring-danger-500/30'
4715
+ trigger: 'border-danger-500 hover:border-danger-500 focus-visible:border-danger-500 focus-visible:ring-danger-500/30'
3496
4716
  }
3497
4717
  },
3498
4718
  fullWidth: {
3499
4719
  true: {
3500
4720
  root: 'w-full',
3501
- inputWrapper: 'w-full'
3502
- }
3503
- },
3504
- disabled: {
3505
- true: {
3506
- inputWrapper: 'opacity-60 cursor-not-allowed bg-neutral-200'
4721
+ trigger: 'w-full min-w-0'
3507
4722
  }
3508
4723
  }
3509
4724
  },
3510
4725
  defaultVariants: {
3511
- size: 'md',
3512
4726
  layout: 'stacked',
3513
4727
  error: false,
3514
- fullWidth: false,
4728
+ fullWidth: false
4729
+ }
4730
+ });
4731
+ /**
4732
+ * Per-day-cell recipe for the range grid. `endpoint` (a committed start/end)
4733
+ * is the solid fill; `inBand` (inside the committed or previewed range) is
4734
+ * the light tint. Declared `inBand` before `endpoint` so an endpoint cell —
4735
+ * which is also in-band — resolves to the solid fill.
4736
+ */ const dateRangeDayVariants = tv({
4737
+ base: 'flex h-9 w-9 items-center justify-center rounded-md text-sm text-neutral-900 transition-colors hover:bg-neutral-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary-500',
4738
+ variants: {
4739
+ inBand: {
4740
+ true: 'bg-primary-100 hover:bg-primary-100'
4741
+ },
4742
+ endpoint: {
4743
+ true: 'bg-primary-600 text-white hover:bg-primary-600'
4744
+ },
4745
+ siblingMonth: {
4746
+ true: 'text-neutral-400'
4747
+ },
4748
+ today: {
4749
+ true: 'font-bold ring-1 ring-inset ring-primary-400'
4750
+ },
4751
+ disabled: {
4752
+ true: 'cursor-default text-neutral-300 opacity-60 hover:bg-transparent'
4753
+ }
4754
+ },
4755
+ compoundVariants: [
4756
+ {
4757
+ endpoint: true,
4758
+ today: true,
4759
+ class: 'ring-0'
4760
+ },
4761
+ {
4762
+ endpoint: true,
4763
+ siblingMonth: true,
4764
+ class: 'text-white'
4765
+ }
4766
+ ],
4767
+ defaultVariants: {
4768
+ inBand: false,
4769
+ endpoint: false,
4770
+ siblingMonth: false,
4771
+ today: false,
3515
4772
  disabled: false
3516
4773
  }
3517
4774
  });
3518
4775
 
3519
- /**
3520
- * Resolve the native HTML input `type` for a given picker mode.
3521
- */ function inputTypeFor(mode) {
3522
- switch(mode){
3523
- case 'time':
3524
- return 'time';
3525
- case 'datetime':
3526
- return 'datetime-local';
3527
- case 'date':
3528
- default:
3529
- return 'date';
4776
+ const EMPTY_RANGE = {
4777
+ start: null,
4778
+ end: null
4779
+ };
4780
+ function CalendarIcon() {
4781
+ return jsxs("svg", {
4782
+ width: "1em",
4783
+ height: "1em",
4784
+ viewBox: "0 0 16 16",
4785
+ fill: "none",
4786
+ "aria-hidden": "true",
4787
+ children: [
4788
+ jsx("rect", {
4789
+ x: "2.5",
4790
+ y: "3.5",
4791
+ width: "11",
4792
+ height: "10",
4793
+ rx: "1.5",
4794
+ stroke: "currentColor",
4795
+ strokeWidth: "1.5"
4796
+ }),
4797
+ jsx("path", {
4798
+ d: "M2.5 6.5h11M5.5 2v3M10.5 2v3",
4799
+ stroke: "currentColor",
4800
+ strokeWidth: "1.5",
4801
+ strokeLinecap: "round"
4802
+ })
4803
+ ]
4804
+ });
4805
+ }
4806
+ function ChevronLeftIcon() {
4807
+ return jsx("svg", {
4808
+ width: "1em",
4809
+ height: "1em",
4810
+ viewBox: "0 0 16 16",
4811
+ fill: "none",
4812
+ "aria-hidden": "true",
4813
+ children: jsx("path", {
4814
+ d: "M10 4 6 8l4 4",
4815
+ stroke: "currentColor",
4816
+ strokeWidth: "1.5",
4817
+ strokeLinecap: "round",
4818
+ strokeLinejoin: "round"
4819
+ })
4820
+ });
4821
+ }
4822
+ function ChevronRightIcon$1() {
4823
+ return jsx("svg", {
4824
+ width: "1em",
4825
+ height: "1em",
4826
+ viewBox: "0 0 16 16",
4827
+ fill: "none",
4828
+ "aria-hidden": "true",
4829
+ children: jsx("path", {
4830
+ d: "m6 4 4 4-4 4",
4831
+ stroke: "currentColor",
4832
+ strokeWidth: "1.5",
4833
+ strokeLinecap: "round",
4834
+ strokeLinejoin: "round"
4835
+ })
4836
+ });
4837
+ }
4838
+ /** Formats a stored ISO date for the trigger display (medium style). */ function formatEndpoint(iso, locale) {
4839
+ if (iso === null || iso === '') {
4840
+ return '';
3530
4841
  }
4842
+ const parts = parseISODate(iso);
4843
+ if (parts === null) {
4844
+ return iso;
4845
+ }
4846
+ return new Intl.DateTimeFormat(locale, {
4847
+ dateStyle: 'medium',
4848
+ timeZone: 'UTC'
4849
+ }).format(Date.UTC(parts.year, parts.month - 1, parts.day));
3531
4850
  }
3532
- /**
3533
- * Coerce a stored/exposed value (ISO 8601-ish string or `null`/`undefined`)
3534
- * into the format the native input expects.
3535
- *
3536
- * Contract:
3537
- * - `date` → `YYYY-MM-DD`
3538
- * - `time` → `HH:mm` (or `HH:mm:ss` if seconds present)
3539
- * - `datetime` → `YYYY-MM-DDTHH:mm` (no timezone, datetime-local
3540
- * rejects `Z` / offset suffixes)
3541
- *
3542
- * The function is intentionally lenient:
3543
- * - Pass-through when the input already matches the expected shape.
3544
- * - Truncates a richer ISO string (e.g. `2026-05-16T10:23:45.000Z`)
3545
- * down to the mode-appropriate prefix.
3546
- * - Returns `''` for null / undefined / unparseable inputs (native
3547
- * inputs use `''` to represent "no value").
3548
- */ function isoToInputValue(mode, raw) {
3549
- if (raw == null || raw === '') return '';
3550
- // Fast path: already in the expected shape.
3551
- if (mode === 'date' && /^\d{4}-\d{2}-\d{2}$/.test(raw)) return raw;
3552
- if (mode === 'time' && /^\d{2}:\d{2}(:\d{2})?$/.test(raw)) return raw;
3553
- if (mode === 'datetime' && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?$/.test(raw)) {
3554
- return raw;
4851
+ function formatRange(range, locale) {
4852
+ const start = formatEndpoint(range.start, locale);
4853
+ const end = formatEndpoint(range.end, locale);
4854
+ if (start === '') {
4855
+ return '';
3555
4856
  }
3556
- // Slow path: parse via Date and re-format locally.
3557
- const d = new Date(raw);
3558
- if (isNaN(d.getTime())) return '';
3559
- const pad = (n)=>String(n).padStart(2, '0');
3560
- const yyyy = d.getFullYear();
3561
- const mm = pad(d.getMonth() + 1);
3562
- const dd = pad(d.getDate());
3563
- const hh = pad(d.getHours());
3564
- const mi = pad(d.getMinutes());
3565
- if (mode === 'date') return `${yyyy}-${mm}-${dd}`;
3566
- if (mode === 'time') return `${hh}:${mi}`;
3567
- return `${yyyy}-${mm}-${dd}T${hh}:${mi}`;
4857
+ return end === '' ? start : `${start} – ${end}`;
3568
4858
  }
3569
- /**
3570
- * Take a native input string and return the canonical "stored" value:
3571
- * the same string for non-empty input, or `null` for empty (so the
3572
- * bridge / consumer never receives the ambiguous empty string).
3573
- */ function inputValueToStored(raw) {
3574
- return raw === '' ? null : raw;
4859
+ /** The dual-month range grid rendered inside the popover. */ function RangeCalendar(props) {
4860
+ const { value, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale } = props;
4861
+ const range = useDateRange(_extends({
4862
+ value,
4863
+ onChange,
4864
+ monthCount: 2
4865
+ }, minDate !== undefined && {
4866
+ minDate
4867
+ }, maxDate !== undefined && {
4868
+ maxDate
4869
+ }, disabledDates !== undefined && {
4870
+ disabledDates
4871
+ }, isDateDisabled !== undefined && {
4872
+ isDateDisabled
4873
+ }, weekStartDay !== undefined && {
4874
+ weekStartDay
4875
+ }, locale !== undefined && {
4876
+ locale
4877
+ }));
4878
+ const focusedCellRef = useRef(null);
4879
+ const shouldFocusRef = useRef(false);
4880
+ useEffect(()=>{
4881
+ if (shouldFocusRef.current && focusedCellRef.current) {
4882
+ focusedCellRef.current.focus({
4883
+ preventScroll: true
4884
+ });
4885
+ shouldFocusRef.current = false;
4886
+ }
4887
+ }, [
4888
+ range.focusedDate
4889
+ ]);
4890
+ const handleKeyDown = (event)=>{
4891
+ const action = resolveCalendarKey(event.key);
4892
+ if (action === null) {
4893
+ return;
4894
+ }
4895
+ event.preventDefault();
4896
+ shouldFocusRef.current = true;
4897
+ switch(action){
4898
+ case 'select':
4899
+ range.selectDate(range.focusedDate);
4900
+ break;
4901
+ case 'monthForward':
4902
+ range.goToNextMonth();
4903
+ break;
4904
+ case 'monthBackward':
4905
+ range.goToPreviousMonth();
4906
+ break;
4907
+ default:
4908
+ range.navigate(action);
4909
+ }
4910
+ };
4911
+ const v = dateRangePickerVariants();
4912
+ const resolvedLocale = locale != null ? locale : 'en-US';
4913
+ const lastIndex = range.months.length - 1;
4914
+ return jsx("div", {
4915
+ className: v.panel(),
4916
+ onMouseLeave: ()=>{
4917
+ range.hoverDate(null);
4918
+ },
4919
+ children: range.months.map((monthView, monthIndex)=>jsxs("div", {
4920
+ className: v.month(),
4921
+ children: [
4922
+ jsxs("div", {
4923
+ className: v.header(),
4924
+ children: [
4925
+ monthIndex === 0 ? jsx("button", {
4926
+ type: "button",
4927
+ className: v.navButton(),
4928
+ "aria-label": "Previous month",
4929
+ onClick: ()=>{
4930
+ range.goToPreviousMonth();
4931
+ },
4932
+ children: jsx(ChevronLeftIcon, {})
4933
+ }) : jsx("span", {
4934
+ className: v.navSpacer()
4935
+ }),
4936
+ jsx("span", {
4937
+ "aria-live": "polite",
4938
+ className: v.monthLabel(),
4939
+ children: monthView.monthLabel
4940
+ }),
4941
+ monthIndex === lastIndex ? jsx("button", {
4942
+ type: "button",
4943
+ className: v.navButton(),
4944
+ "aria-label": "Next month",
4945
+ onClick: ()=>{
4946
+ range.goToNextMonth();
4947
+ },
4948
+ children: jsx(ChevronRightIcon$1, {})
4949
+ }) : jsx("span", {
4950
+ className: v.navSpacer()
4951
+ })
4952
+ ]
4953
+ }),
4954
+ jsxs("div", {
4955
+ role: "grid",
4956
+ "aria-label": "Choose date range",
4957
+ onKeyDown: handleKeyDown,
4958
+ className: v.grid(),
4959
+ children: [
4960
+ jsx("div", {
4961
+ role: "row",
4962
+ className: v.weekdayRow(),
4963
+ children: range.weekdayNames.map((dayName, index)=>jsx("span", {
4964
+ role: "columnheader",
4965
+ className: v.weekday(),
4966
+ children: dayName
4967
+ }, `weekday-${String(index)}`))
4968
+ }),
4969
+ monthView.weeks.map((week)=>jsx("div", {
4970
+ role: "row",
4971
+ className: v.weekRow(),
4972
+ children: week.days.map((day)=>{
4973
+ const isActiveCell = day.iso === range.focusedDate;
4974
+ const endpoint = day.isRangeStart || day.isRangeEnd;
4975
+ const inBand = day.isInRange || day.isRangePreview;
4976
+ return jsx("span", {
4977
+ role: "gridcell",
4978
+ className: "flex",
4979
+ children: jsx("button", {
4980
+ type: "button",
4981
+ ref: isActiveCell ? focusedCellRef : null,
4982
+ tabIndex: isActiveCell ? 0 : -1,
4983
+ "aria-label": formatLongDate({
4984
+ year: day.year,
4985
+ month: day.month,
4986
+ day: day.day
4987
+ }, resolvedLocale),
4988
+ "aria-current": day.isToday ? 'date' : undefined,
4989
+ "aria-pressed": endpoint,
4990
+ "aria-disabled": day.isDisabled,
4991
+ onClick: ()=>{
4992
+ range.selectDate(day.iso);
4993
+ },
4994
+ onMouseEnter: ()=>{
4995
+ range.hoverDate(day.iso);
4996
+ },
4997
+ className: dateRangeDayVariants({
4998
+ inBand,
4999
+ endpoint,
5000
+ siblingMonth: day.isSiblingMonth,
5001
+ today: day.isToday,
5002
+ disabled: day.isDisabled
5003
+ }),
5004
+ children: day.day
5005
+ })
5006
+ }, day.iso);
5007
+ })
5008
+ }, week.key))
5009
+ ]
5010
+ })
5011
+ ]
5012
+ }, `${String(monthView.year)}-${String(monthView.month)}`))
5013
+ });
3575
5014
  }
3576
5015
  /**
3577
- * Dashforge TW DateTimePicker — bridge-integrated native date/time input.
5016
+ * Dashforge TW `<DateRangePicker>` — a form-bound start/end date field.
3578
5017
  *
3579
- * Three modes (`mode` prop):
3580
- * - `date` — `<input type="date">` → `YYYY-MM-DD`
3581
- * - `time` — `<input type="time">` → `HH:mm` (`HH:mm:ss` with `step={1}`)
3582
- * - `datetime` — `<input type="datetime-local">` → `YYYY-MM-DDTHH:mm`
5018
+ * A read-only trigger button paired with a dual-month range calendar popover
5019
+ * (Radix Popover), built on the headless `useDateRange` engine. Integrates
5020
+ * with the Dashforge form bridge + RBAC.
3583
5021
  *
3584
- * **Why native inputs instead of React Aria `<DatePicker>`?**
3585
- *
3586
- * 1. Zero new deps (`@internationalized/date` would add ~30 KB).
3587
- * 2. Mirrors `@dashforge/ui/DateTimePicker` (MUI side) for API parity.
3588
- * 3. Native inputs are AAA-grade a11y out of the box — screen readers,
3589
- * keyboard nav, OS-provided calendar/clock UI.
3590
- * 4. Predictable state (no internal Aria state machine that fights
3591
- * controlled props — the F5-A lesson learned in `<Autocomplete>`).
3592
- *
3593
- * A richer custom-calendar version (range mode, locale awareness,
3594
- * custom popover styling) can land as F5-B-bis built on Aria
3595
- * `<DatePicker>` / `<Calendar>`.
3596
- *
3597
- * **A11y**:
3598
- * - `aria-invalid` driven by the resolved validation state.
3599
- * - `aria-describedby` links input ↔ helper/error.
3600
- * - Required fields get the native `required` attribute + a visual `*`.
3601
- * - `color-scheme: light dark` keeps the OS calendar icon readable
3602
- * in both color schemes.
3603
- */ function DateTimePicker(props) {
3604
- var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_inputWrapper, _slotProps_input, _slotProps_errorText, _slotProps_helperText;
3605
- const { name, rules, mode = 'date', visibleWhen, layout = 'stacked', size, label, helperText, required, error, disabled, fullWidth, access, sx, slotProps, placeholder, min, max, step, value: explicitValue, defaultValue, onValueChange } = props;
3606
- // ───── Hooks (unconditional) ─────
5022
+ * Storage contract: a `{ start, end }` pair of ISO `YYYY-MM-DD` dates —
5023
+ * shared with the MUI `@dashforge/ui` `DateRangePicker`.
5024
+ */ function DateRangePicker(props) {
5025
+ var _slotProps_root, _slotProps_label, _slotProps_requiredMark, _slotProps_trigger, _slotProps_errorText, _slotProps_helperText;
5026
+ const { name, rules, label, helperText, error, required, disabled, placeholder, layout = 'stacked', visibleWhen, access, value: explicitValue, defaultValue, onChange, minDate, maxDate, disabledDates, isDateDisabled, weekStartDay, locale, fullWidth, sx, slotProps, testId } = props;
3607
5027
  const bridge = useContext(DashFormContext);
3608
5028
  const isVisible = useEngineVisibility(bridge == null ? void 0 : bridge.engine, visibleWhen);
3609
- // Per-field reactive snapshot. Same pattern as <TextField> — re-renders
3610
- // this component when its OWN bridge value changes, but not when a
3611
- // sibling field changes (perf contract).
3612
5029
  const fieldMeta = useDashFieldMeta(name);
3613
5030
  const accessState = useAccessState(access);
3614
- const inputId = useId();
3615
- const helperId = `${inputId}-help`;
3616
- // StrictMode-safe unregister-on-unmount.
5031
+ const fieldId = useId();
5032
+ const helperId = `${fieldId}-help`;
5033
+ const [isOpen, setIsOpen] = useState(false);
5034
+ const [internalValue, setInternalValue] = useState(defaultValue != null ? defaultValue : EMPTY_RANGE);
3617
5035
  const unregisterRef = useRef({
3618
5036
  bridge,
3619
5037
  name
@@ -3634,9 +5052,8 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3634
5052
  });
3635
5053
  };
3636
5054
  }, []);
3637
- // ───── Derived ─────
3638
5055
  const effectiveDisabled = Boolean(disabled) || accessState.disabled;
3639
- const effectiveReadOnly = accessState.readonly;
5056
+ const isInteractive = !effectiveDisabled && !accessState.readonly;
3640
5057
  const isFormMode = Boolean(bridge == null ? void 0 : bridge.register);
3641
5058
  let resolvedError = error;
3642
5059
  let resolvedHelperText = helperText;
@@ -3651,65 +5068,81 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3651
5068
  resolvedValue = explicitValue;
3652
5069
  } else {
3653
5070
  const bv = fieldMeta.value;
3654
- resolvedValue = bv == null ? null : String(bv);
5071
+ resolvedValue = bv != null ? bv : EMPTY_RANGE;
3655
5072
  }
3656
5073
  } else if (explicitValue !== undefined) {
3657
5074
  resolvedValue = explicitValue;
3658
5075
  } else {
3659
- // Pure uncontrolled mode: defer to native input's defaultValue.
3660
- resolvedValue = undefined;
5076
+ resolvedValue = internalValue;
3661
5077
  }
3662
- // Registration-supplied callback ref (RHF threads its own ref through
3663
- // here in form mode). Stable callback to keep React 19 happy.
3664
5078
  const registrationRefFn = registration == null ? void 0 : registration.ref;
3665
- const inputRef = useCallback((instance)=>{
5079
+ const triggerRef = useCallback((instance)=>{
3666
5080
  if (typeof registrationRefFn === 'function') {
3667
5081
  registrationRefFn(instance);
3668
5082
  }
3669
5083
  }, [
3670
5084
  registrationRefFn
3671
5085
  ]);
3672
- // ───── Render-time guards (after all hooks) ─────
3673
5086
  if (!isVisible) return null;
3674
5087
  if (!accessState.visible) return null;
3675
- const v = dateTimePickerVariants({
3676
- size,
5088
+ const v = dateRangePickerVariants({
3677
5089
  layout,
3678
5090
  error: resolvedError,
3679
- fullWidth,
3680
- disabled: effectiveDisabled
5091
+ fullWidth
3681
5092
  });
3682
- const inputType = inputTypeFor(mode);
3683
- const nativeValue = resolvedValue === undefined ? undefined : isoToInputValue(mode, resolvedValue);
3684
- const nativeDefault = !isFormMode && explicitValue === undefined && defaultValue !== undefined ? isoToInputValue(mode, defaultValue) : undefined;
3685
- const nativeMin = min != null ? isoToInputValue(mode, min) : undefined;
3686
- const nativeMax = max != null ? isoToInputValue(mode, max) : undefined;
3687
- const handleChange = (event)=>{
3688
- const next = inputValueToStored(event.target.value);
5093
+ const resolvedLocale = locale != null ? locale : 'en-US';
5094
+ const displayValue = formatRange(resolvedValue, resolvedLocale);
5095
+ const commitValue = (next)=>{
3689
5096
  if (isFormMode && bridge) {
3690
5097
  var _registration_onChange;
3691
5098
  bridge.setValue == null ? void 0 : bridge.setValue.call(bridge, name, next);
3692
5099
  void (registration == null ? void 0 : (_registration_onChange = registration.onChange) == null ? void 0 : _registration_onChange.call(registration, {
3693
5100
  target: {
3694
5101
  name,
3695
- value: next != null ? next : ''
5102
+ value: next
3696
5103
  },
3697
5104
  type: 'change'
3698
5105
  }));
5106
+ } else if (explicitValue === undefined) {
5107
+ setInternalValue(next);
3699
5108
  }
3700
- onValueChange == null ? void 0 : onValueChange(next);
5109
+ onChange == null ? void 0 : onChange(next);
3701
5110
  };
3702
- const handleBlur = (event)=>{
5111
+ const markTouched = ()=>{
3703
5112
  if (isFormMode && bridge) {
3704
5113
  var _registration_onBlur;
3705
- registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, event);
5114
+ const committed = bridge.getValue(name);
5115
+ void (registration == null ? void 0 : (_registration_onBlur = registration.onBlur) == null ? void 0 : _registration_onBlur.call(registration, {
5116
+ target: {
5117
+ name,
5118
+ value: committed
5119
+ },
5120
+ type: 'blur'
5121
+ }));
5122
+ }
5123
+ };
5124
+ const handleOpenChange = (next)=>{
5125
+ if (next && !isInteractive) {
5126
+ return;
5127
+ }
5128
+ setIsOpen(next);
5129
+ if (!next) {
5130
+ markTouched();
5131
+ }
5132
+ };
5133
+ const handleRangeChange = (next)=>{
5134
+ commitValue(next);
5135
+ if (next.start !== null && next.end !== null) {
5136
+ setIsOpen(false);
5137
+ markTouched();
3706
5138
  }
3707
5139
  };
3708
5140
  return jsxs("div", {
5141
+ "data-testid": testId,
3709
5142
  className: cn(v.root(), sx, slotProps == null ? void 0 : (_slotProps_root = slotProps.root) == null ? void 0 : _slotProps_root.className),
3710
5143
  children: [
3711
5144
  label && jsxs("label", {
3712
- htmlFor: inputId,
5145
+ htmlFor: fieldId,
3713
5146
  className: cn(v.label(), slotProps == null ? void 0 : (_slotProps_label = slotProps.label) == null ? void 0 : _slotProps_label.className),
3714
5147
  children: [
3715
5148
  label,
@@ -3720,29 +5153,39 @@ const defaultGetOptionDisabled = (option)=>Boolean(option.disabled);
3720
5153
  })
3721
5154
  ]
3722
5155
  }),
3723
- jsx("div", {
3724
- className: cn(v.inputWrapper(), slotProps == null ? void 0 : (_slotProps_inputWrapper = slotProps.inputWrapper) == null ? void 0 : _slotProps_inputWrapper.className),
3725
- children: jsx("input", {
3726
- id: inputId,
3727
- ref: inputRef,
3728
- name: name,
3729
- type: inputType,
3730
- // Controlled in form / explicit-value mode; uncontrolled with
3731
- // `defaultValue` otherwise. Mirrors the TextField pattern.
3732
- value: nativeValue,
3733
- defaultValue: nativeDefault,
3734
- min: nativeMin,
3735
- max: nativeMax,
3736
- step: step,
3737
- placeholder: placeholder,
5156
+ jsx(Popover, {
5157
+ open: isOpen,
5158
+ onOpenChange: handleOpenChange,
5159
+ side: "bottom",
5160
+ align: "start",
5161
+ content: jsx(RangeCalendar, {
5162
+ value: resolvedValue,
5163
+ onChange: handleRangeChange,
5164
+ minDate: minDate,
5165
+ maxDate: maxDate,
5166
+ disabledDates: disabledDates,
5167
+ isDateDisabled: isDateDisabled,
5168
+ weekStartDay: weekStartDay,
5169
+ locale: locale
5170
+ }),
5171
+ children: jsxs("button", {
5172
+ type: "button",
5173
+ id: fieldId,
5174
+ ref: triggerRef,
3738
5175
  disabled: effectiveDisabled,
3739
- readOnly: effectiveReadOnly,
3740
- required: required,
3741
5176
  "aria-invalid": resolvedError ? true : undefined,
3742
5177
  "aria-describedby": resolvedHelperText ? helperId : undefined,
3743
- onChange: handleChange,
3744
- onBlur: handleBlur,
3745
- className: cn(v.input(), slotProps == null ? void 0 : (_slotProps_input = slotProps.input) == null ? void 0 : _slotProps_input.className)
5178
+ className: cn(v.trigger(), slotProps == null ? void 0 : (_slotProps_trigger = slotProps.trigger) == null ? void 0 : _slotProps_trigger.className),
5179
+ children: [
5180
+ jsx("span", {
5181
+ className: displayValue ? v.value() : v.placeholder(),
5182
+ children: displayValue || placeholder || 'Select a date range'
5183
+ }),
5184
+ jsx("span", {
5185
+ className: v.icon(),
5186
+ children: jsx(CalendarIcon, {})
5187
+ })
5188
+ ]
3746
5189
  })
3747
5190
  }),
3748
5191
  resolvedHelperText && jsx("p", {
@@ -7176,71 +8619,6 @@ const tooltipVariants = tv({
7176
8619
  });
7177
8620
  }
7178
8621
 
7179
- const popoverVariants = tv({
7180
- slots: {
7181
- content: [
7182
- // Neutral border auto-inverts via CSS-var swap.
7183
- 'z-50 rounded-md border border-neutral-200',
7184
- // bg-white hardcoded (no auto-invert) so `dark:` is required.
7185
- // Target `dark:bg-neutral-100` resolves to #171717 in dark mode
7186
- // (one elevation tier above the page surface).
7187
- 'bg-white dark:bg-neutral-100 p-4 shadow-lg',
7188
- // Text auto-inverts.
7189
- 'text-sm text-neutral-900',
7190
- 'focus:outline-none',
7191
- 'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',
7192
- 'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',
7193
- 'motion-reduce:transition-none motion-reduce:duration-0'
7194
- ],
7195
- // Arrow follows content bg: `fill-white` in light + matching dark target.
7196
- arrow: 'fill-white dark:fill-neutral-100'
7197
- }
7198
- });
7199
-
7200
- /**
7201
- * Dashforge TW `<Popover>` — clickable floating content panel.
7202
- *
7203
- * Built on `@radix-ui/react-popover`:
7204
- * - Focus trap inside the popover (Tab cycles within).
7205
- * - Outside-click + Escape dismiss.
7206
- * - APG dialog-like semantics (`role="dialog"` on content).
7207
- * - Portal-rendered.
7208
- *
7209
- * Use `<Popover>` for menus / pickers / settings panels with
7210
- * interactive children. Use `<Tooltip>` for read-only hints.
7211
- */ function Popover(props) {
7212
- var _slotProps_content, _slotProps_arrow;
7213
- const { children, content, side = 'bottom', align = 'center', showArrow = false, open, onOpenChange, sideOffset = 8, width, slotProps } = props;
7214
- const v = popoverVariants();
7215
- return jsxs(RadixPopover.Root, {
7216
- open: open,
7217
- onOpenChange: onOpenChange,
7218
- children: [
7219
- jsx(RadixPopover.Trigger, {
7220
- asChild: true,
7221
- children: children
7222
- }),
7223
- jsx(RadixPopover.Portal, {
7224
- children: jsxs(RadixPopover.Content, {
7225
- side: side,
7226
- align: align,
7227
- sideOffset: sideOffset,
7228
- style: width != null ? {
7229
- width
7230
- } : undefined,
7231
- className: cn(v.content(), slotProps == null ? void 0 : (_slotProps_content = slotProps.content) == null ? void 0 : _slotProps_content.className),
7232
- children: [
7233
- content,
7234
- showArrow && jsx(RadixPopover.Arrow, {
7235
- className: cn(v.arrow(), slotProps == null ? void 0 : (_slotProps_arrow = slotProps.arrow) == null ? void 0 : _slotProps_arrow.className)
7236
- })
7237
- ]
7238
- })
7239
- })
7240
- ]
7241
- });
7242
- }
7243
-
7244
8622
  const accordionVariants = tv({
7245
8623
  slots: {
7246
8624
  root: 'w-full',
@@ -11417,6 +12795,6 @@ function SearchIcon(props) {
11417
12795
  */ // ───── Components ─────
11418
12796
  /**
11419
12797
  * Package version (synced with `package.json` at publish time).
11420
- */ const VERSION = '0.7.0-beta';
12798
+ */ const VERSION = '0.9.1-beta';
11421
12799
 
11422
- export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Checkbox, ConfirmDialogProvider, Container, DataGrid, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, isoToInputValue, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };
12800
+ export { Accordion, AppShell, AspectRatio, Autocomplete, Box, Breadcrumbs, Button, Calendar, Checkbox, ConfirmDialogProvider, Container, DataGrid, DatePicker, DateRangePicker, DateTimePicker, Dialog, Divider, Grid, LeftNav, NumberField, OTPField, Pagination, Popover, RadioGroup, RenderButton, RenderChip, RenderText, RenderTwoLine, RowActionsMenu, Skeleton, SnackbarProvider, Stack, Switch, Table, Tabs, TextField, Textarea, TimePicker, Tooltip, TopBar, Typography, VERSION, VisuallyHidden, accordionVariants, appShellVariants, autocompleteVariants, boxVariants, breadcrumbsVariants, buttonVariants, calendarDayVariants, calendarVariants, checkboxVariants, cn, confirmDialogVariants, containerVariants, dataGridVariants, datePickerVariants, dateRangeDayVariants, dateRangePickerVariants, dateTimePickerVariants, dialogVariants, dividerLineVariants, dividerVariants, getNestedValue, gridVariants, leftNavVariants, numberFieldVariants, otpFieldVariants, paginationVariants, popoverVariants, radioGroupVariants, skeletonVariants, snackbarVariants, stackVariants, switchVariants, tableVariants, tabsVariants, textFieldVariants, textareaVariants, timePickerVariants, tooltipVariants, topBarVariants, typographyVariants, useAccessState, useConfirm, useSnackbar };