@enonic/ui 0.13.0 → 0.13.2

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.
@@ -1,9 +1,8 @@
1
1
  import { options, Fragment } from "preact";
2
- import { createContext, useContext, useId, useCallback, forwardRef, useState, useMemo, useEffect, createElement, useRef, isValidElement, Children, createPortal as createPortal$1 } from "react";
2
+ import { createContext, useContext, useId, useCallback, forwardRef, useState, useMemo, useEffect, useRef, createElement, isValidElement, Children, createPortal as createPortal$1 } from "react";
3
3
  import { Slot, Root } from "@radix-ui/react-slot";
4
4
  import { FocusTrap } from "focus-trap-react";
5
5
  import { createPortal } from "react-dom";
6
- import { useState as useState$1 } from "preact/hooks";
7
6
  var f = 0;
8
7
  function u(e, t, n, o, i, u2) {
9
8
  t || (t = {});
@@ -13,7 +12,7 @@ function u(e, t, n, o, i, u2) {
13
12
  if ("function" == typeof e && (a = e.defaultProps)) for (c in a) void 0 === p[c] && (p[c] = a[c]);
14
13
  return options.vnode && options.vnode(l), l;
15
14
  }
16
- const AvatarContext = createContext(null);
15
+ const AvatarContext = createContext(void 0);
17
16
  const AvatarProvider = ({ value, children }) => {
18
17
  return /* @__PURE__ */ u(AvatarContext.Provider, { value, children });
19
18
  };
@@ -25,7 +24,7 @@ const useAvatar = () => {
25
24
  }
26
25
  return ctx;
27
26
  };
28
- const ComboboxContext = createContext(null);
27
+ const ComboboxContext = createContext(void 0);
29
28
  const ComboboxProvider = ({ value, children }) => {
30
29
  return /* @__PURE__ */ u(ComboboxContext.Provider, { value, children });
31
30
  };
@@ -59,7 +58,7 @@ const usePrefixedId = (providedId, prefix) => {
59
58
  const context = useContext(IdContext);
60
59
  return [context?.prefix, prefix, baseId].filter(Boolean).join("-");
61
60
  };
62
- const ListboxContext = createContext(null);
61
+ const ListboxContext = createContext(void 0);
63
62
  const ListboxProvider = ({ value, children }) => {
64
63
  return /* @__PURE__ */ u(ListboxContext.Provider, { value, children });
65
64
  };
@@ -82,6 +81,27 @@ const useMenu = () => {
82
81
  }
83
82
  return context;
84
83
  };
84
+ function generateAriaId(baseId, suffix) {
85
+ return `${baseId}-${suffix}`;
86
+ }
87
+ function generateAriaIds(baseId, parts) {
88
+ return parts.reduce(
89
+ (acc, part) => {
90
+ acc[part] = generateAriaId(baseId, part);
91
+ return acc;
92
+ },
93
+ {}
94
+ );
95
+ }
96
+ function generateItemId(baseId, componentType, itemValue) {
97
+ return `${baseId}-${componentType}-item-${itemValue}`;
98
+ }
99
+ function getActiveDescendantId(baseId, componentType, activeValue) {
100
+ if (!activeValue) {
101
+ return void 0;
102
+ }
103
+ return generateItemId(baseId, componentType, activeValue);
104
+ }
85
105
  function r(e) {
86
106
  var t, f2, n = "";
87
107
  if ("string" == typeof e || "number" == typeof e) n += e;
@@ -3233,7 +3253,7 @@ const Avatar = Object.assign(AvatarRoot, {
3233
3253
  const buttonVariants = cva(
3234
3254
  [
3235
3255
  "inline-flex items-center justify-center",
3236
- "text-main font-medium",
3256
+ "text-main font-semibold",
3237
3257
  "box-border rounded-sm transition-highlight",
3238
3258
  "focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:ring-offset-0",
3239
3259
  "disabled:select-none disabled:pointer-events-none disabled:opacity-30",
@@ -3262,11 +3282,11 @@ const buttonVariants = cva(
3262
3282
  const getIconSize = (size) => {
3263
3283
  switch (size) {
3264
3284
  case "sm":
3265
- return 16;
3285
+ return 14;
3266
3286
  case "md":
3267
- return 18;
3268
- case "lg":
3269
3287
  return 20;
3288
+ case "lg":
3289
+ return 24;
3270
3290
  }
3271
3291
  };
3272
3292
  const Button = forwardRef(
@@ -3307,6 +3327,159 @@ const Button = forwardRef(
3307
3327
  }
3308
3328
  );
3309
3329
  Button.displayName = "Button";
3330
+ const useScrollLock = (lock, element) => {
3331
+ useEffect(() => {
3332
+ if (!lock) {
3333
+ return;
3334
+ }
3335
+ const target = element ?? document.body;
3336
+ const originalOverflow = target.style.overflow;
3337
+ target.style.overflow = "hidden";
3338
+ return () => {
3339
+ target.style.overflow = originalOverflow;
3340
+ };
3341
+ }, [lock, element]);
3342
+ };
3343
+ function useControlledState(controlledValue, defaultValue, onChange) {
3344
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
3345
+ const isControlled = controlledValue !== void 0;
3346
+ const value = isControlled ? controlledValue : uncontrolledValue;
3347
+ const setValue = useCallback(
3348
+ (nextValue) => {
3349
+ if (!isControlled) {
3350
+ setUncontrolledValue(nextValue);
3351
+ }
3352
+ onChange?.(nextValue);
3353
+ },
3354
+ [isControlled, onChange]
3355
+ );
3356
+ return [value, setValue];
3357
+ }
3358
+ function useItemRegistry() {
3359
+ const itemsRef = useRef(/* @__PURE__ */ new Map());
3360
+ const registerItem = useCallback((id, disabled = false) => {
3361
+ itemsRef.current.set(id, { disabled });
3362
+ }, []);
3363
+ const unregisterItem = useCallback((id) => {
3364
+ itemsRef.current.delete(id);
3365
+ }, []);
3366
+ const getItems = useCallback(() => {
3367
+ return Array.from(itemsRef.current.keys());
3368
+ }, []);
3369
+ const isItemDisabled = useCallback((id) => {
3370
+ return itemsRef.current.get(id)?.disabled ?? false;
3371
+ }, []);
3372
+ return {
3373
+ registerItem,
3374
+ unregisterItem,
3375
+ getItems,
3376
+ isItemDisabled
3377
+ };
3378
+ }
3379
+ function useKeyboardNavigation(config) {
3380
+ const {
3381
+ getItems,
3382
+ isItemDisabled,
3383
+ active,
3384
+ setActive,
3385
+ loop = false,
3386
+ orientation = "vertical",
3387
+ onSelect,
3388
+ onEscape
3389
+ } = config;
3390
+ const moveActive = useCallback(
3391
+ (delta) => {
3392
+ const items = getItems();
3393
+ if (!items.length) {
3394
+ return;
3395
+ }
3396
+ const currentIndex = active ? items.indexOf(active) : -1;
3397
+ let newIndex;
3398
+ let attempts = 0;
3399
+ const maxAttempts = items.length;
3400
+ if (currentIndex === -1) {
3401
+ newIndex = delta > 0 ? 0 : items.length - 1;
3402
+ } else {
3403
+ newIndex = currentIndex + delta;
3404
+ }
3405
+ while (attempts < maxAttempts) {
3406
+ if (loop) {
3407
+ if (newIndex < 0) {
3408
+ newIndex = items.length - 1;
3409
+ } else if (newIndex >= items.length) {
3410
+ newIndex = 0;
3411
+ }
3412
+ } else {
3413
+ if (newIndex < 0 || newIndex >= items.length) {
3414
+ return;
3415
+ }
3416
+ }
3417
+ if (!isItemDisabled(items[newIndex])) {
3418
+ setActive(items[newIndex]);
3419
+ return;
3420
+ }
3421
+ newIndex += delta;
3422
+ attempts++;
3423
+ }
3424
+ },
3425
+ [getItems, active, setActive, loop, isItemDisabled]
3426
+ );
3427
+ const handleKeyDown = useCallback(
3428
+ (e) => {
3429
+ const items = getItems();
3430
+ if (!items.length) {
3431
+ return;
3432
+ }
3433
+ const isVertical = orientation === "vertical";
3434
+ const nextKey = isVertical ? "ArrowDown" : "ArrowRight";
3435
+ const prevKey = isVertical ? "ArrowUp" : "ArrowLeft";
3436
+ switch (e.key) {
3437
+ case nextKey:
3438
+ e.preventDefault();
3439
+ moveActive(1);
3440
+ break;
3441
+ case prevKey:
3442
+ e.preventDefault();
3443
+ moveActive(-1);
3444
+ break;
3445
+ case "Home":
3446
+ e.preventDefault();
3447
+ {
3448
+ const firstEnabled = items.find((id) => !isItemDisabled(id));
3449
+ if (firstEnabled) {
3450
+ setActive(firstEnabled);
3451
+ }
3452
+ }
3453
+ break;
3454
+ case "End":
3455
+ e.preventDefault();
3456
+ {
3457
+ const lastEnabled = [...items].reverse().find((id) => !isItemDisabled(id));
3458
+ if (lastEnabled) {
3459
+ setActive(lastEnabled);
3460
+ }
3461
+ }
3462
+ break;
3463
+ case "Enter":
3464
+ case " ":
3465
+ e.preventDefault();
3466
+ if (active && !isItemDisabled(active)) {
3467
+ onSelect?.(active);
3468
+ }
3469
+ break;
3470
+ case "Escape":
3471
+ e.preventDefault();
3472
+ onEscape?.();
3473
+ break;
3474
+ }
3475
+ },
3476
+ [getItems, active, moveActive, setActive, isItemDisabled, orientation, onSelect, onEscape]
3477
+ );
3478
+ return {
3479
+ moveActive,
3480
+ handleKeyDown
3481
+ };
3482
+ }
3310
3483
  /**
3311
3484
  * @license lucide-react v0.545.0 - ISC
3312
3485
  *
@@ -3570,9 +3743,7 @@ const Checkbox = forwardRef(
3570
3743
  const inputId = usePrefixedId(unwrap(id));
3571
3744
  const state = error ? "error" : "default";
3572
3745
  const editable = !disabled && !readOnly;
3573
- const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);
3574
- const isControlled = checked !== void 0;
3575
- const checkedState = isControlled ? checked : uncontrolledChecked;
3746
+ const [checkedState, setCheckedState] = useControlledState(checked, defaultChecked, onCheckedChange);
3576
3747
  const isIndeterminate = checkedState === "indeterminate";
3577
3748
  const isChecked = checkedState === true;
3578
3749
  const handleChange = (e) => {
@@ -3583,10 +3754,7 @@ const Checkbox = forwardRef(
3583
3754
  } else {
3584
3755
  nextValue = e.currentTarget.checked;
3585
3756
  }
3586
- if (!isControlled) {
3587
- setUncontrolledChecked(nextValue);
3588
- }
3589
- onCheckedChange?.(nextValue);
3757
+ setCheckedState(nextValue);
3590
3758
  };
3591
3759
  return /* @__PURE__ */ u("div", { children: [
3592
3760
  /* @__PURE__ */ u(
@@ -3687,141 +3855,103 @@ const IconButton = forwardRef(
3687
3855
  }
3688
3856
  );
3689
3857
  IconButton.displayName = "IconButton";
3858
+ const EMPTY_SELECTION$1 = [];
3690
3859
  const ComboboxRoot = ({
3691
3860
  children,
3692
3861
  open: controlledOpen,
3862
+ defaultOpen = false,
3693
3863
  onOpenChange,
3694
3864
  closeOnBlur = true,
3695
3865
  value,
3866
+ defaultValue = "",
3696
3867
  onChange,
3697
3868
  disabled = false,
3698
3869
  error = false,
3699
3870
  selectionMode = "single",
3700
3871
  selection: controlledSelection,
3701
- onSelectionChange
3872
+ defaultSelection = EMPTY_SELECTION$1,
3873
+ onSelectionChange,
3874
+ active: controlledActive,
3875
+ defaultActive,
3876
+ setActive
3702
3877
  }) => {
3703
3878
  const baseId = usePrefixedId();
3704
- const [uncontrolledOpen, setUncontrolledOpen] = useState(false);
3705
- const isOpenControlled = controlledOpen !== void 0;
3706
- const open = isOpenControlled ? controlledOpen : uncontrolledOpen;
3707
- const setOpen = useCallback(
3708
- (next) => {
3709
- if (!isOpenControlled) {
3710
- setUncontrolledOpen(next);
3711
- }
3712
- onOpenChange?.(next);
3713
- },
3714
- [isOpenControlled, onOpenChange]
3715
- );
3716
- const [uncontrolledSelection, setUncontrolledSelection] = useState([]);
3879
+ const [open, setOpenInternal] = useControlledState(controlledOpen, defaultOpen, onOpenChange);
3880
+ const [inputValue, setInputValueInternal] = useControlledState(value, defaultValue, onChange);
3881
+ const [uncontrolledSelection, setUncontrolledSelection] = useState(() => new Set(defaultSelection));
3717
3882
  const isSelectionControlled = controlledSelection !== void 0;
3718
- const selectedItems = isSelectionControlled ? controlledSelection : uncontrolledSelection;
3883
+ const selectionSet = useMemo(
3884
+ () => isSelectionControlled ? new Set(controlledSelection) : uncontrolledSelection,
3885
+ [isSelectionControlled, controlledSelection, uncontrolledSelection]
3886
+ );
3719
3887
  const onSelectionChangeInner = useCallback(
3720
3888
  (newSelection) => {
3889
+ const newSet = new Set(newSelection);
3721
3890
  if (!isSelectionControlled) {
3722
- setUncontrolledSelection(newSelection);
3891
+ setUncontrolledSelection(newSet);
3723
3892
  }
3724
3893
  onSelectionChange?.(newSelection);
3725
3894
  if (selectionMode === "single") {
3726
- setOpen(false);
3895
+ setOpenInternal(false);
3727
3896
  }
3728
3897
  },
3729
- [isSelectionControlled, selectionMode, setOpen, onSelectionChange, selectedItems]
3898
+ [isSelectionControlled, selectionMode, setOpenInternal, onSelectionChange]
3730
3899
  );
3731
- const [uncontrolledInput, setUncontrolledInput] = useState("");
3732
- const isInputControlled = value !== void 0;
3733
- const inputValue = isInputControlled ? value : uncontrolledInput;
3900
+ const [activeInternal, setActiveInternal] = useControlledState(controlledActive, defaultActive, setActive);
3901
+ const { registerItem, unregisterItem, getItems, isItemDisabled } = useItemRegistry();
3734
3902
  const setInputValue = useCallback(
3735
3903
  (next) => {
3736
- if (!isInputControlled) {
3737
- setUncontrolledInput(next);
3738
- }
3739
- onChange?.(next);
3740
- setOpen(true);
3904
+ setInputValueInternal(next);
3905
+ setOpenInternal(true);
3741
3906
  },
3742
- [isInputControlled, onChange, setOpen]
3907
+ [setInputValueInternal, setOpenInternal]
3743
3908
  );
3744
- const toggleValueSelection = useCallback(
3745
- (value2) => {
3746
- let newSelection = [];
3747
- if (selectionMode === "multiple") {
3748
- if (selectedItems.includes(value2)) {
3749
- newSelection = selectedItems.filter((item) => item !== value2);
3750
- } else {
3751
- newSelection = [...selectedItems, value2];
3752
- }
3753
- } else {
3754
- newSelection = [value2];
3755
- }
3756
- onSelectionChangeInner(newSelection);
3909
+ const setOpen = useCallback(
3910
+ (next) => {
3911
+ setOpenInternal(next);
3757
3912
  },
3758
- [selectionMode, selectedItems, onSelectionChangeInner]
3913
+ [setOpenInternal]
3759
3914
  );
3760
- const [active, setActive] = useState(void 0);
3761
- const innerRef = useRef(null);
3762
- const getItems = useCallback(() => {
3763
- const container = innerRef.current;
3764
- if (!container) {
3765
- return [];
3915
+ const { handleKeyDown: handleNavKeyDown } = useKeyboardNavigation({
3916
+ getItems,
3917
+ isItemDisabled,
3918
+ active: activeInternal,
3919
+ setActive: setActiveInternal,
3920
+ loop: false,
3921
+ orientation: "vertical",
3922
+ onSelect: (id) => {
3923
+ const newSelection = selectionMode === "multiple" ? selectionSet.has(id) ? Array.from(selectionSet).filter((item) => item !== id) : [...Array.from(selectionSet), id] : [id];
3924
+ onSelectionChangeInner(newSelection);
3766
3925
  }
3767
- const optionNodes = container.querySelectorAll('[role="option"][data-value]');
3768
- return Array.from(optionNodes).map((node) => node.dataset.value).filter((v) => v !== void 0);
3769
- }, []);
3770
- const moveActive = useCallback(
3771
- (delta) => {
3772
- const items = getItems();
3773
- if (!items.length) {
3774
- return;
3775
- }
3776
- const currentIndex = active ? items.indexOf(active) : -1;
3777
- const newIndex = Math.max(0, Math.min(items.length - 1, currentIndex + delta));
3778
- setActive(items[newIndex]);
3779
- },
3780
- [active, setActive, getItems]
3781
- );
3926
+ });
3782
3927
  const keyHandler = useCallback(
3783
3928
  (e) => {
3784
3929
  if (disabled) {
3785
3930
  return;
3786
3931
  }
3787
- const items = getItems();
3788
- if (e.key === "ArrowDown") {
3789
- e.preventDefault();
3790
- setOpen(true);
3791
- moveActive(1);
3792
- } else if (e.key === "ArrowUp") {
3793
- setOpen(true);
3794
- moveActive(-1);
3795
- } else if (e.key === "Home") {
3796
- e.preventDefault();
3797
- setActive(items[0]);
3798
- } else if (e.key === "End") {
3799
- e.preventDefault();
3800
- setActive(items[items.length - 1]);
3801
- } else if (e.key === "Enter") {
3802
- if (open) {
3932
+ if (e.key === "ArrowDown" || e.key === "ArrowUp") {
3933
+ if (!open) {
3803
3934
  e.preventDefault();
3804
- if (active) {
3805
- toggleValueSelection(active);
3806
- }
3807
- setOpen(false);
3808
- setActive(items[0]);
3935
+ setOpenInternal(true);
3809
3936
  }
3810
- } else if (e.key === "Escape") {
3811
- setOpen(false);
3812
- setActive(items[0]);
3813
3937
  }
3938
+ if (e.key === "Escape") {
3939
+ e.preventDefault();
3940
+ setOpenInternal(false);
3941
+ return;
3942
+ }
3943
+ handleNavKeyDown(e);
3814
3944
  },
3815
- [disabled, moveActive, setOpen, toggleValueSelection, open]
3945
+ [disabled, open, setOpenInternal, handleNavKeyDown]
3816
3946
  );
3817
3947
  useEffect(() => {
3818
- if (open && active === void 0) {
3948
+ if (open && activeInternal === void 0) {
3819
3949
  const items = getItems();
3820
3950
  if (items.length > 0) {
3821
- setActive(items[0]);
3951
+ setActiveInternal(items[0]);
3822
3952
  }
3823
3953
  }
3824
- }, [open, active, getItems]);
3954
+ }, [open, activeInternal, getItems, setActiveInternal]);
3825
3955
  const context = useMemo(
3826
3956
  () => ({
3827
3957
  open,
@@ -3829,36 +3959,52 @@ const ComboboxRoot = ({
3829
3959
  inputValue,
3830
3960
  setInputValue,
3831
3961
  baseId,
3832
- active,
3962
+ active: activeInternal,
3833
3963
  disabled,
3834
3964
  error,
3835
3965
  closeOnBlur,
3836
3966
  keyHandler,
3837
- selection: selectedItems
3967
+ selection: selectionSet
3838
3968
  }),
3839
- [open, setOpen, closeOnBlur, keyHandler, inputValue, setInputValue, active, baseId, disabled, error, selectedItems]
3969
+ [
3970
+ open,
3971
+ setOpen,
3972
+ closeOnBlur,
3973
+ keyHandler,
3974
+ inputValue,
3975
+ setInputValue,
3976
+ activeInternal,
3977
+ baseId,
3978
+ disabled,
3979
+ error,
3980
+ selectionSet
3981
+ ]
3840
3982
  );
3841
- return /* @__PURE__ */ u("div", { ref: innerRef, children: /* @__PURE__ */ u(ComboboxProvider, { value: context, children: /* @__PURE__ */ u(
3983
+ return /* @__PURE__ */ u(ComboboxProvider, { value: context, children: /* @__PURE__ */ u(
3842
3984
  Listbox.Root,
3843
3985
  {
3844
3986
  selectionMode,
3845
- selection: selectedItems,
3987
+ selection: Array.from(selectionSet),
3846
3988
  onSelectionChange: onSelectionChangeInner,
3847
3989
  disabled,
3848
- active,
3990
+ active: activeInternal,
3849
3991
  focusable: false,
3850
3992
  baseId,
3851
- setActive,
3993
+ setActive: setActiveInternal,
3852
3994
  keyHandler,
3995
+ registerItem,
3996
+ unregisterItem,
3997
+ getItems,
3998
+ isItemDisabled,
3853
3999
  children
3854
4000
  }
3855
- ) }) });
4001
+ ) });
3856
4002
  };
3857
4003
  ComboboxRoot.displayName = "Combobox.Root";
3858
4004
  const ComboboxContent = forwardRef(
3859
4005
  ({ className, children }, ref) => {
3860
- const { setOpen, baseId, closeOnBlur } = useCombobox();
3861
4006
  const innerRef = useRef(null);
4007
+ const { setOpen, baseId, closeOnBlur } = useCombobox();
3862
4008
  const handleFocusOut = closeOnBlur ? useCallback(
3863
4009
  (e) => {
3864
4010
  const nextTarget = e.relatedTarget;
@@ -3875,6 +4021,7 @@ const ComboboxContent = forwardRef(
3875
4021
  );
3876
4022
  }
3877
4023
  );
4024
+ ComboboxContent.displayName = "Combobox.Content";
3878
4025
  const comboboxControlVariants = cva(
3879
4026
  [
3880
4027
  "flex items-center",
@@ -3925,8 +4072,8 @@ const ComboboxControl = ({ children, className }) => {
3925
4072
  ComboboxControl.displayName = "Combobox.Control";
3926
4073
  const ComboboxInput = forwardRef(
3927
4074
  ({ className, placeholder, ...props }, ref) => {
3928
- const { inputValue, setInputValue, open, keyHandler, selection, baseId, active, disabled } = useCombobox();
3929
4075
  const innerRef = useRef(null);
4076
+ const { inputValue, setInputValue, open, keyHandler, selection, baseId, active, disabled, error } = useCombobox();
3930
4077
  useEffect(() => {
3931
4078
  if (open && !disabled) {
3932
4079
  innerRef.current?.focus();
@@ -3944,6 +4091,7 @@ const ComboboxInput = forwardRef(
3944
4091
  placeholder,
3945
4092
  disabled,
3946
4093
  "aria-disabled": disabled,
4094
+ "aria-invalid": error ?? void 0,
3947
4095
  role: "combobox",
3948
4096
  "aria-autocomplete": "list",
3949
4097
  "aria-expanded": open,
@@ -4009,37 +4157,13 @@ const Combobox = Object.assign(ComboboxRoot, {
4009
4157
  Toggle: ComboboxToggle,
4010
4158
  Popup: ComboboxPopup
4011
4159
  });
4012
- const useScrollLock = (lock, element) => {
4013
- useEffect(() => {
4014
- if (!lock) {
4015
- return;
4016
- }
4017
- const target = element ?? document.body;
4018
- const originalOverflow = target.style.overflow;
4019
- target.style.overflow = "hidden";
4020
- return () => {
4021
- target.style.overflow = originalOverflow;
4022
- };
4023
- }, [lock, element]);
4024
- };
4025
4160
  const DialogRoot = ({
4026
4161
  open: controlledOpen,
4027
4162
  defaultOpen = false,
4028
4163
  onOpenChange,
4029
4164
  children
4030
4165
  }) => {
4031
- const isControlled = controlledOpen !== void 0;
4032
- const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
4033
- const open = isControlled ? controlledOpen : uncontrolledOpen;
4034
- const setOpen = useCallback(
4035
- (next) => {
4036
- if (!isControlled) {
4037
- setUncontrolledOpen(next);
4038
- }
4039
- onOpenChange?.(next);
4040
- },
4041
- [isControlled, onOpenChange]
4042
- );
4166
+ const [open, setOpen] = useControlledState(controlledOpen, defaultOpen, onOpenChange);
4043
4167
  const value = useMemo(() => ({ open, setOpen }), [open, setOpen]);
4044
4168
  return /* @__PURE__ */ u(DialogProvider, { value, children });
4045
4169
  };
@@ -4179,7 +4303,7 @@ const DialogContent = forwardRef(
4179
4303
  preventScroll: false,
4180
4304
  onActivate: () => {
4181
4305
  requestAnimationFrame(() => {
4182
- const event = new Event("openautofocus");
4306
+ const event = new Event("openautofocus", { bubbles: true, cancelable: true });
4183
4307
  onOpenAutoFocus?.(event);
4184
4308
  if (!event.defaultPrevented && contentRef.current) {
4185
4309
  contentRef.current.focus();
@@ -4188,7 +4312,7 @@ const DialogContent = forwardRef(
4188
4312
  },
4189
4313
  onDeactivate: () => {
4190
4314
  requestAnimationFrame(() => {
4191
- const event = new Event("closeautofocus");
4315
+ const event = new Event("closeautofocus", { bubbles: true, cancelable: true });
4192
4316
  onCloseAutoFocus?.(event);
4193
4317
  });
4194
4318
  }
@@ -4207,6 +4331,7 @@ const DialogContent = forwardRef(
4207
4331
  "relative rounded-lg shadow-xl bg-surface-neutral",
4208
4332
  "flex flex-col max-w-lg w-full max-h-[90vh] gap-10 p-10",
4209
4333
  "border border-bdr-subtle outline-none overflow-hidden",
4334
+ "focus:outline-none focus:ring-0",
4210
4335
  "data-[state=open]:animate-in data-[state=closed]:animate-out",
4211
4336
  "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
4212
4337
  "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
@@ -4379,7 +4504,7 @@ const Input = forwardRef(
4379
4504
  const state = error ? "error" : "default";
4380
4505
  const hasError = state === "error";
4381
4506
  return /* @__PURE__ */ u("div", { className: cn("w-full", disabled && "opacity-30", className), children: [
4382
- /* @__PURE__ */ u("div", { className: "mb-2", children: [
4507
+ (!!label || !!description) && /* @__PURE__ */ u("div", { className: "mb-2", children: [
4383
4508
  label && /* @__PURE__ */ u(
4384
4509
  "label",
4385
4510
  {
@@ -4549,7 +4674,11 @@ const ListboxRoot = ({
4549
4674
  focusable = true,
4550
4675
  disabled,
4551
4676
  children,
4552
- keyHandler
4677
+ keyHandler,
4678
+ registerItem: externalRegisterItem,
4679
+ unregisterItem: externalUnregisterItem,
4680
+ getItems: externalGetItems,
4681
+ isItemDisabled: externalIsItemDisabled
4553
4682
  }) => {
4554
4683
  const listboxBaseId = baseId ?? usePrefixedId();
4555
4684
  const [uncontrolledSelection, setUncontrolledSelection] = useState(() => new Set(defaultSelection));
@@ -4558,18 +4687,12 @@ const ListboxRoot = ({
4558
4687
  () => isSelectionControlled ? new Set(controlledSelection) : uncontrolledSelection,
4559
4688
  [isSelectionControlled, controlledSelection, uncontrolledSelection]
4560
4689
  );
4561
- const [uncontrolledActive, setUncontrolledActive] = useState(defaultActive);
4562
- const isActiveControlled = controlledActive !== void 0;
4563
- const active = isActiveControlled ? controlledActive : uncontrolledActive;
4564
- const updateActive = useCallback(
4565
- (id) => {
4566
- if (!isActiveControlled) {
4567
- setUncontrolledActive(id);
4568
- }
4569
- setActive?.(id);
4570
- },
4571
- [isActiveControlled, setActive]
4572
- );
4690
+ const [active, updateActive] = useControlledState(controlledActive, defaultActive, setActive);
4691
+ const internalRegistry = useItemRegistry();
4692
+ const registerItem = externalRegisterItem ?? internalRegistry.registerItem;
4693
+ const unregisterItem = externalUnregisterItem ?? internalRegistry.unregisterItem;
4694
+ const getItems = externalGetItems ?? internalRegistry.getItems;
4695
+ const isItemDisabled = externalIsItemDisabled ?? internalRegistry.isItemDisabled;
4573
4696
  const toggleValue = useCallback(
4574
4697
  (value) => {
4575
4698
  const isSelected = selectionSet.has(value);
@@ -4604,15 +4727,34 @@ const ListboxRoot = ({
4604
4727
  setActive: updateActive,
4605
4728
  toggleValue,
4606
4729
  baseId: listboxBaseId,
4607
- keyHandler
4730
+ keyHandler,
4731
+ registerItem,
4732
+ unregisterItem,
4733
+ getItems,
4734
+ isItemDisabled
4608
4735
  }),
4609
- [active, selectionSet, selectionMode, focusable, disabled, updateActive, toggleValue, listboxBaseId, keyHandler]
4736
+ [
4737
+ active,
4738
+ selectionSet,
4739
+ selectionMode,
4740
+ focusable,
4741
+ disabled,
4742
+ updateActive,
4743
+ toggleValue,
4744
+ listboxBaseId,
4745
+ keyHandler,
4746
+ registerItem,
4747
+ unregisterItem,
4748
+ getItems,
4749
+ isItemDisabled
4750
+ ]
4610
4751
  );
4611
4752
  return /* @__PURE__ */ u(ListboxProvider, { value: contextValue, children });
4612
4753
  };
4613
4754
  ListboxRoot.displayName = "ListboxRoot";
4614
4755
  const ListboxContent = forwardRef(
4615
4756
  ({ className, label, children, ...props }, ref) => {
4757
+ const innerRef = useRef(null);
4616
4758
  const {
4617
4759
  active,
4618
4760
  disabled,
@@ -4621,59 +4763,22 @@ const ListboxContent = forwardRef(
4621
4763
  baseId,
4622
4764
  selectionMode,
4623
4765
  keyHandler,
4624
- focusable = true
4766
+ focusable = true,
4767
+ getItems,
4768
+ isItemDisabled
4625
4769
  } = useListbox();
4626
- const innerRef = useRef(null);
4627
- const getItems = useCallback(() => {
4628
- const container = innerRef.current;
4629
- if (!container) {
4630
- return [];
4770
+ const { handleKeyDown: handleNavKeyDown } = useKeyboardNavigation({
4771
+ getItems,
4772
+ isItemDisabled,
4773
+ active,
4774
+ setActive,
4775
+ loop: false,
4776
+ orientation: "vertical",
4777
+ onSelect: (id) => {
4778
+ toggleValue(id);
4631
4779
  }
4632
- const optionNodes = container.querySelectorAll('[role="option"][data-value]');
4633
- return Array.from(optionNodes).map((node) => node.dataset.value).filter((v) => v !== void 0);
4634
- }, []);
4635
- const moveActive = useCallback(
4636
- (delta) => {
4637
- const items = getItems();
4638
- if (!items.length) {
4639
- return;
4640
- }
4641
- const currentIndex = active ? items.indexOf(active) : -1;
4642
- const newIndex = Math.max(0, Math.min(items.length - 1, currentIndex + delta));
4643
- setActive(items[newIndex]);
4644
- },
4645
- [active, setActive, getItems]
4646
- );
4647
- const handleKeyDown = keyHandler ?? useCallback(
4648
- (e) => {
4649
- if (disabled) {
4650
- return;
4651
- }
4652
- const items = getItems();
4653
- if (!items.length) {
4654
- return;
4655
- }
4656
- if (e.key === "ArrowDown") {
4657
- e.preventDefault();
4658
- moveActive(1);
4659
- } else if (e.key === "ArrowUp") {
4660
- e.preventDefault();
4661
- moveActive(-1);
4662
- } else if (e.key === "Home") {
4663
- e.preventDefault();
4664
- setActive(items[0]);
4665
- } else if (e.key === "End") {
4666
- e.preventDefault();
4667
- setActive(items[items.length - 1]);
4668
- } else if (e.key === " " || e.key === "Enter") {
4669
- e.preventDefault();
4670
- if (active) {
4671
- toggleValue(active);
4672
- }
4673
- }
4674
- },
4675
- [disabled, getItems, active, moveActive, toggleValue, setActive]
4676
- );
4780
+ });
4781
+ const handleKeyDown = keyHandler ?? handleNavKeyDown;
4677
4782
  useEffect(() => {
4678
4783
  if (!active || !innerRef.current) {
4679
4784
  return;
@@ -4721,6 +4826,10 @@ const listboxItemVariants = cva("flex w-full items-center px-4.5 py-1 gap-x-2.5
4721
4826
  active: {
4722
4827
  true: "bg-surface-primary-hover",
4723
4828
  false: ""
4829
+ },
4830
+ disabled: {
4831
+ true: "opacity-30 cursor-not-allowed pointer-events-none",
4832
+ false: ""
4724
4833
  }
4725
4834
  },
4726
4835
  compoundVariants: [
@@ -4732,19 +4841,25 @@ const listboxItemVariants = cva("flex w-full items-center px-4.5 py-1 gap-x-2.5
4732
4841
  ],
4733
4842
  defaultVariants: {
4734
4843
  selected: false,
4735
- active: false
4844
+ active: false,
4845
+ disabled: false
4736
4846
  }
4737
4847
  });
4738
- const ListboxItem = ({ value, children, className, ...props }) => {
4848
+ const ListboxItem = ({ value, disabled = false, children, className, ...props }) => {
4739
4849
  const ctx = useListbox();
4740
- const { disabled, toggleValue, baseId } = ctx;
4850
+ const { disabled: listboxDisabled, toggleValue, baseId, registerItem, unregisterItem } = ctx;
4741
4851
  const isSelected = ctx.selection.has(value);
4742
4852
  const isActive = ctx.active === value;
4853
+ const isDisabled = disabled || listboxDisabled;
4854
+ useEffect(() => {
4855
+ registerItem(value, isDisabled);
4856
+ return () => unregisterItem(value);
4857
+ }, [value, isDisabled, registerItem, unregisterItem]);
4743
4858
  const handleClick = useCallback(() => {
4744
- if (!disabled) {
4859
+ if (!isDisabled) {
4745
4860
  toggleValue(value);
4746
4861
  }
4747
- }, [disabled, toggleValue, value]);
4862
+ }, [isDisabled, toggleValue, value]);
4748
4863
  return (
4749
4864
  // ARIA listbox pattern: options are not individually focusable
4750
4865
  // Parent listbox handles all keyboard interactions via aria-activedescendant
@@ -4753,9 +4868,10 @@ const ListboxItem = ({ value, children, className, ...props }) => {
4753
4868
  "div",
4754
4869
  {
4755
4870
  id: `${baseId}-listbox-option-${value}`,
4756
- className: cn(listboxItemVariants({ selected: isSelected, active: isActive }), className),
4871
+ className: cn(listboxItemVariants({ selected: isSelected, active: isActive, disabled: isDisabled }), className),
4757
4872
  role: "option",
4758
4873
  "aria-selected": isSelected,
4874
+ "aria-disabled": isDisabled ?? void 0,
4759
4875
  "data-value": value,
4760
4876
  "data-active": isActive || void 0,
4761
4877
  onClick: handleClick,
@@ -4777,35 +4893,12 @@ const MenuRoot = ({
4777
4893
  onOpenChange,
4778
4894
  children
4779
4895
  }) => {
4780
- const isControlled = controlledOpen !== void 0;
4781
- const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
4782
- const open = isControlled ? controlledOpen : uncontrolledOpen;
4783
- const setOpen = useCallback(
4784
- (next) => {
4785
- if (!isControlled) {
4786
- setUncontrolledOpen(next);
4787
- }
4788
- onOpenChange?.(next);
4789
- },
4790
- [isControlled, onOpenChange]
4791
- );
4792
- const [active, setActive] = useState(void 0);
4793
- const itemsRef = useRef(/* @__PURE__ */ new Map());
4794
4896
  const triggerRef = useRef(null);
4795
4897
  const triggerId = usePrefixedId(void 0, "menu-trigger");
4796
4898
  const menuId = usePrefixedId(void 0, "menu");
4797
- const registerItem = useCallback((id, disabled = false) => {
4798
- itemsRef.current.set(id, { disabled });
4799
- }, []);
4800
- const unregisterItem = useCallback((id) => {
4801
- itemsRef.current.delete(id);
4802
- }, []);
4803
- const getItems = useCallback(() => {
4804
- return Array.from(itemsRef.current.keys());
4805
- }, []);
4806
- const isItemDisabled = useCallback((id) => {
4807
- return itemsRef.current.get(id)?.disabled ?? false;
4808
- }, []);
4899
+ const [open, setOpen] = useControlledState(controlledOpen, defaultOpen, onOpenChange);
4900
+ const [active, setActive] = useState(void 0);
4901
+ const { registerItem, unregisterItem, getItems, isItemDisabled } = useItemRegistry();
4809
4902
  const value = useMemo(
4810
4903
  () => ({
4811
4904
  open,
@@ -4892,9 +4985,7 @@ const MenuContent = forwardRef(
4892
4985
  const { open, setOpen, active, setActive, getItems, isItemDisabled, menuId, triggerRef } = useMenu();
4893
4986
  const contentRef = useRef(null);
4894
4987
  const composedRefs = useComposedRefs(ref, contentRef);
4895
- const [position, setPosition] = useState(
4896
- null
4897
- );
4988
+ const [position, setPosition] = useState(void 0);
4898
4989
  useEffect(() => {
4899
4990
  if (!open || !triggerRef?.current || !contentRef.current) {
4900
4991
  return;
@@ -4950,101 +5041,42 @@ const MenuContent = forwardRef(
4950
5041
  }
4951
5042
  }
4952
5043
  }, [open, getItems, isItemDisabled, setActive]);
4953
- const moveActive = useCallback(
4954
- (delta) => {
4955
- const items = getItems();
4956
- if (!items.length) {
4957
- return;
4958
- }
4959
- const currentIndex = active ? items.indexOf(active) : -1;
4960
- let newIndex;
4961
- let attempts = 0;
4962
- const maxAttempts = items.length;
4963
- if (currentIndex === -1) {
4964
- newIndex = delta > 0 ? 0 : items.length - 1;
4965
- } else {
4966
- newIndex = currentIndex + delta;
4967
- }
4968
- while (attempts < maxAttempts) {
4969
- if (loop) {
4970
- if (newIndex < 0) {
4971
- newIndex = items.length - 1;
4972
- } else if (newIndex >= items.length) {
4973
- newIndex = 0;
4974
- }
4975
- } else {
4976
- if (newIndex < 0 || newIndex >= items.length) {
4977
- return;
4978
- }
4979
- }
4980
- if (!isItemDisabled(items[newIndex])) {
4981
- setActive(items[newIndex]);
4982
- return;
4983
- }
4984
- newIndex += delta;
4985
- attempts++;
5044
+ const { handleKeyDown: handleNavKeyDown } = useKeyboardNavigation({
5045
+ getItems,
5046
+ isItemDisabled,
5047
+ active,
5048
+ setActive,
5049
+ loop,
5050
+ orientation: "vertical",
5051
+ onSelect: (id) => {
5052
+ const itemElement = document.getElementById(id);
5053
+ if (itemElement) {
5054
+ itemElement.click();
4986
5055
  }
4987
- },
4988
- [getItems, active, setActive, loop, isItemDisabled]
4989
- );
5056
+ }
5057
+ });
4990
5058
  const handleKeyDown = useCallback(
4991
5059
  (e) => {
4992
- const items = getItems();
4993
- if (!items.length) {
5060
+ if (e.key === "Tab") {
5061
+ setOpen(false);
4994
5062
  return;
4995
5063
  }
4996
- switch (e.key) {
4997
- case "ArrowDown":
4998
- e.preventDefault();
4999
- moveActive(1);
5000
- break;
5001
- case "ArrowUp":
5002
- e.preventDefault();
5003
- moveActive(-1);
5004
- break;
5005
- case "Home":
5006
- e.preventDefault();
5007
- {
5008
- const firstEnabled = items.find((id) => !isItemDisabled(id));
5009
- if (firstEnabled) {
5010
- setActive(firstEnabled);
5011
- }
5012
- }
5013
- break;
5014
- case "End":
5015
- e.preventDefault();
5016
- {
5017
- const lastEnabled = [...items].reverse().find((id) => !isItemDisabled(id));
5018
- if (lastEnabled) {
5019
- setActive(lastEnabled);
5020
- }
5021
- }
5022
- break;
5023
- case "Enter":
5024
- case " ":
5025
- e.preventDefault();
5026
- if (active && !isItemDisabled(active)) {
5027
- const itemElement = document.getElementById(active);
5028
- if (itemElement) {
5029
- itemElement.click();
5030
- }
5031
- }
5032
- break;
5033
- case "Escape":
5034
- e.preventDefault();
5035
- onEscapeKeyDown?.(e);
5036
- setOpen(false);
5037
- break;
5038
- case "Tab":
5039
- setOpen(false);
5040
- break;
5064
+ if (e.key === "Escape") {
5065
+ e.preventDefault();
5066
+ onEscapeKeyDown?.(e);
5067
+ setOpen(false);
5068
+ return;
5041
5069
  }
5070
+ handleNavKeyDown(e);
5042
5071
  },
5043
- [getItems, active, moveActive, setActive, isItemDisabled, onEscapeKeyDown, setOpen]
5072
+ [handleNavKeyDown, setOpen, onEscapeKeyDown]
5044
5073
  );
5045
5074
  const handlePointerDownOutside = useCallback(
5046
5075
  (e) => {
5047
5076
  const target = e.target;
5077
+ if (!(target instanceof Node)) {
5078
+ return;
5079
+ }
5048
5080
  if (triggerRef?.current?.contains(target)) {
5049
5081
  return;
5050
5082
  }
@@ -5227,25 +5259,16 @@ const SearchInput = forwardRef(
5227
5259
  ...props
5228
5260
  }, ref) => {
5229
5261
  const inputId = usePrefixedId(unwrap(id));
5230
- const [uncontrolledValue, setUncontrolledValue] = useState$1(defaultValue);
5231
- const isControlled = value !== void 0;
5232
- const inputValue = isControlled ? value : uncontrolledValue;
5262
+ const inputRef = useRef(null);
5263
+ const [inputValue, setInputValue] = useControlledState(value, defaultValue, onChange);
5233
5264
  const isValueSet = inputValue.length > 0;
5234
5265
  const canClear = showClearButton && isValueSet && !disabled && !readOnly;
5235
5266
  const handleChange = (e) => {
5236
5267
  const newValue = e.currentTarget.value;
5237
- if (!isControlled) {
5238
- setUncontrolledValue(newValue);
5239
- }
5240
- onChange?.(newValue);
5268
+ setInputValue(newValue);
5241
5269
  };
5242
- const inputRef = useRef(null);
5243
5270
  const handleClear = () => {
5244
- const newValue = "";
5245
- if (!isControlled) {
5246
- setUncontrolledValue(newValue);
5247
- }
5248
- onChange?.(newValue);
5271
+ setInputValue("");
5249
5272
  inputRef.current?.focus();
5250
5273
  };
5251
5274
  return /* @__PURE__ */ u(
@@ -5277,7 +5300,7 @@ const SearchInput = forwardRef(
5277
5300
  "w-full text-base border-0",
5278
5301
  "text-main bg-surface-neutral",
5279
5302
  "placeholder:text-subtle",
5280
- "focus:outline-none",
5303
+ "focus:outline-none focus:ring-0",
5281
5304
  "read-only:bg-surface-primary",
5282
5305
  !showSearchIcon && "pl-4.5",
5283
5306
  "pr-4"
@@ -5606,12 +5629,19 @@ export {
5606
5629
  Separator,
5607
5630
  Tooltip,
5608
5631
  cn,
5632
+ generateAriaId,
5633
+ generateAriaIds,
5634
+ generateItemId,
5635
+ getActiveDescendantId,
5609
5636
  setRef,
5610
5637
  unwrap,
5611
5638
  useAvatar,
5612
5639
  useCombobox,
5613
5640
  useComposedRefs,
5641
+ useControlledState,
5614
5642
  useDialog,
5643
+ useItemRegistry,
5644
+ useKeyboardNavigation,
5615
5645
  useListbox,
5616
5646
  useMenu,
5617
5647
  usePrefixedId,