@enonic/ui 1.5.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +18 -6
  2. package/dist/enonic-ui.cjs +1 -1
  3. package/dist/enonic-ui.es.js +135 -131
  4. package/dist/style.cjs +1 -1
  5. package/dist/style.es.js +1 -1
  6. package/dist/styles/style.css +2 -2
  7. package/dist/types/components/avatar/avatar.d.ts +18 -13
  8. package/dist/types/components/button/button.d.ts +6 -3
  9. package/dist/types/components/checkbox/checkbox.d.ts +25 -3
  10. package/dist/types/components/combobox/combobox.d.ts +23 -14
  11. package/dist/types/components/context-menu/context-menu.d.ts +78 -37
  12. package/dist/types/components/date-picker/date-picker.d.ts +36 -23
  13. package/dist/types/components/dialog/dialog-actions.d.ts +16 -1
  14. package/dist/types/components/dialog/dialog.d.ts +81 -44
  15. package/dist/types/components/grid-list/grid-list.d.ts +102 -12
  16. package/dist/types/components/icon-button/icon-button.d.ts +6 -5
  17. package/dist/types/components/input/input.d.ts +28 -6
  18. package/dist/types/components/link/link.d.ts +7 -3
  19. package/dist/types/components/listbox/listbox.d.ts +5 -3
  20. package/dist/types/components/menu/menu.d.ts +52 -25
  21. package/dist/types/components/menubar/menubar.d.ts +94 -33
  22. package/dist/types/components/radio-group/radio-group.d.ts +30 -12
  23. package/dist/types/components/search-field/search-field.d.ts +5 -5
  24. package/dist/types/components/selectable-list-item/selectable-list-item.d.ts +1 -1
  25. package/dist/types/components/selector/selector.d.ts +72 -36
  26. package/dist/types/components/stepper/index.d.ts +1 -1
  27. package/dist/types/components/stepper/stepper.d.ts +66 -18
  28. package/dist/types/components/tab/tab.d.ts +97 -21
  29. package/dist/types/components/tab/tab.phrases.d.ts +4 -0
  30. package/dist/types/components/tag/tag.d.ts +29 -24
  31. package/dist/types/components/textarea/textarea.d.ts +34 -3
  32. package/dist/types/components/time-picker/time-picker.d.ts +26 -19
  33. package/dist/types/components/toast/toast.d.ts +29 -25
  34. package/dist/types/components/toggle/toggle.d.ts +5 -3
  35. package/dist/types/components/toggle-group/toggle-group.d.ts +6 -9
  36. package/dist/types/components/toolbar/toolbar-toggle-group.d.ts +10 -12
  37. package/dist/types/components/toolbar/toolbar.d.ts +29 -21
  38. package/dist/types/components/tooltip/tooltip.d.ts +13 -1
  39. package/dist/types/components/tree-list/tree-list.d.ts +255 -62
  40. package/dist/types/components/virtualized-tree-list/virtualized-tree-list.d.ts +90 -43
  41. package/dist/types/hooks/use-active-item-focus.d.ts +1 -1
  42. package/dist/types/hooks/use-active-on-open.d.ts +1 -1
  43. package/dist/types/hooks/use-click-outside.d.ts +2 -2
  44. package/dist/types/hooks/use-floating-position.d.ts +2 -2
  45. package/dist/types/hooks/use-focus-outside.d.ts +9 -0
  46. package/dist/types/hooks/use-pointer-position.d.ts +1 -1
  47. package/dist/types/hooks/use-portal-focus-container.d.ts +6 -2
  48. package/dist/types/hooks/use-radio-navigation.d.ts +1 -1
  49. package/dist/types/hooks/use-scroll-active-into-view.d.ts +1 -1
  50. package/dist/types/hooks/use-step-navigation.d.ts +1 -1
  51. package/dist/types/hooks/use-submenu-safe-area.d.ts +1 -1
  52. package/dist/types/hooks/use-sync-value.d.ts +1 -1
  53. package/dist/types/i18n/phrases.d.ts +3 -0
  54. package/dist/types/primitives/menu-primitive/menu-primitive.d.ts +41 -20
  55. package/dist/types/providers/combobox-provider.d.ts +5 -5
  56. package/dist/types/providers/context-menu-content-provider.d.ts +1 -0
  57. package/dist/types/providers/context-menu-provider.d.ts +4 -5
  58. package/dist/types/providers/context-menu-sub-provider.d.ts +3 -3
  59. package/dist/types/providers/date-picker-provider.d.ts +2 -2
  60. package/dist/types/providers/dialog-provider.d.ts +6 -5
  61. package/dist/types/providers/focus-container-provider.d.ts +4 -2
  62. package/dist/types/providers/menu-provider.d.ts +2 -1
  63. package/dist/types/providers/menubar-menu-provider.d.ts +4 -4
  64. package/dist/types/providers/menubar-provider.d.ts +1 -1
  65. package/dist/types/providers/search-field-provider.d.ts +1 -1
  66. package/dist/types/providers/selector-provider.d.ts +1 -1
  67. package/dist/types/providers/stepper-provider.d.ts +2 -0
  68. package/dist/types/providers/time-picker-provider.d.ts +3 -3
  69. package/dist/types/providers/toolbar-provider.d.ts +1 -1
  70. package/dist/types/types/lucide.d.ts +9 -5
  71. package/dist/types/utils/dom.d.ts +23 -1
  72. package/dist/types/utils/unwrap.d.ts +1 -1
  73. package/package.json +32 -37
@@ -6,6 +6,7 @@ import { RefObject } from 'react';
6
6
  *
7
7
  * @param elementRef - Ref to the popup element to register
8
8
  * @param isEnabled - Whether registration is active (typically: open && isPortalMode)
9
+ * @returns A stable callback for cancellable focus after the parent trap acknowledges the container.
9
10
  *
10
11
  * @example
11
12
  * ```tsx
@@ -18,7 +19,10 @@ import { RefObject } from 'react';
18
19
  * setIsPortalMode(contentRef.current.parentElement === portalContainer);
19
20
  * }, [open, portalContainer]);
20
21
  *
21
- * usePortalFocusContainer(contentRef, isPortalMode);
22
+ * const requestFocus = usePortalFocusContainer(contentRef, open && isPortalMode);
23
+ * useLayoutEffect(() => {
24
+ * if (open) return requestFocus(() => contentRef.current?.focus());
25
+ * }, [open, requestFocus]);
22
26
  * ```
23
27
  */
24
- export declare const usePortalFocusContainer: (elementRef: RefObject<HTMLElement | null>, isEnabled: boolean) => void;
28
+ export declare const usePortalFocusContainer: (elementRef: RefObject<HTMLElement | null>, isEnabled: boolean) => ((focus: () => void) => () => void);
@@ -3,7 +3,7 @@ export type RadioNavigationConfig = {
3
3
  baseId: string;
4
4
  getItems: () => string[];
5
5
  getItemId?: (baseId: string, itemId: string) => string;
6
- rootRef?: RefObject<HTMLElement>;
6
+ rootRef?: RefObject<HTMLElement | null>;
7
7
  value: string | undefined;
8
8
  onValueChange: (value: string) => void;
9
9
  isItemDisabled: (id: string) => boolean;
@@ -1,7 +1,7 @@
1
1
  import { RefObject } from 'react';
2
2
  export type UseScrollActiveIntoViewConfig = {
3
3
  /** Reference to the container element */
4
- containerRef: RefObject<HTMLElement> | null;
4
+ containerRef: RefObject<HTMLElement | null> | null;
5
5
  /** ID of the currently active item (undefined if no active item) */
6
6
  activeId: string | undefined;
7
7
  /**
@@ -3,7 +3,7 @@ export type StepNavigationConfig = {
3
3
  baseId: string;
4
4
  getItems: () => string[];
5
5
  getNextFocusable?: (baseId: string, itemId: string) => string;
6
- rootRef?: RefObject<HTMLElement>;
6
+ rootRef?: RefObject<HTMLElement | null>;
7
7
  value: string | undefined;
8
8
  onValueChange: (value: string) => void;
9
9
  isItemDisabled: (id: string) => boolean;
@@ -4,7 +4,7 @@ export type UseSubmenuSafeAreaConfig = {
4
4
  /** Whether the submenu is open (tracking is active) */
5
5
  enabled: boolean;
6
6
  /** Reference to the submenu content element — used to compute its near edge */
7
- submenuRef: RefObject<HTMLElement>;
7
+ submenuRef: RefObject<HTMLElement | null>;
8
8
  /**
9
9
  * Which side the submenu sits on relative to its trigger.
10
10
  * - 'right': near edge is the submenu's LEFT edge; pointer travels right toward it
@@ -1,4 +1,4 @@
1
- import { Signalish } from 'preact';
1
+ import { Signalish } from '../utils';
2
2
  /**
3
3
  * Syncs a value related to a setter function with a value from arguments
4
4
  * @param value - The value to sync with
@@ -23,6 +23,9 @@ export declare const uiPhrases: Readonly<{
23
23
  readonly 'enonic.ui.stepper.goToStep': "Go to step {0}";
24
24
  } & {
25
25
  readonly 'enonic.ui.dialog.close': "Close";
26
+ } & {
27
+ readonly 'enonic.ui.tab.scrollLeft': "Scroll tabs left";
28
+ readonly 'enonic.ui.tab.scrollRight': "Scroll tabs right";
26
29
  } & {
27
30
  readonly 'enonic.ui.timePicker.hour': "Hour";
28
31
  readonly 'enonic.ui.timePicker.minute': "Minute";
@@ -20,25 +20,31 @@ export type MenuItemOwnProps = {
20
20
  onSelect?: (event: Event) => void;
21
21
  className?: string;
22
22
  children: ReactNode;
23
- } & ComponentPropsWithoutRef<'div'>;
23
+ } & Omit<ComponentPropsWithoutRef<'div'>, 'onSelect'>;
24
24
  /** Internal primitive props (own props + context operations for dependency injection) */
25
25
  export type MenuPrimitiveItemProps = MenuItemOwnProps & MenuContextOperations;
26
- export declare const MenuPrimitiveItem: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveItemProps> & {
27
- ref?: import('preact').Ref<HTMLDivElement> | undefined;
28
- }>;
26
+ export declare const MenuPrimitiveItem: import('react').ForwardRefExoticComponent<{
27
+ id?: string;
28
+ asChild?: boolean;
29
+ disabled?: boolean;
30
+ onSelect?: (event: Event) => void;
31
+ className?: string;
32
+ children: ReactNode;
33
+ } & Omit<Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onSelect"> & MenuContextOperations & import('react').RefAttributes<HTMLDivElement>>;
29
34
  export type MenuPrimitiveLabelProps = {
30
35
  className?: string;
31
36
  children: ReactNode;
32
37
  } & ComponentPropsWithoutRef<'div'>;
33
- export declare const MenuPrimitiveLabel: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveLabelProps> & {
34
- ref?: import('preact').Ref<HTMLDivElement> | undefined;
35
- }>;
38
+ export declare const MenuPrimitiveLabel: import('react').ForwardRefExoticComponent<{
39
+ className?: string;
40
+ children: ReactNode;
41
+ } & Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
36
42
  export type MenuPrimitiveSeparatorProps = {
37
43
  className?: string;
38
44
  } & ComponentPropsWithoutRef<'div'>;
39
- export declare const MenuPrimitiveSeparator: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveSeparatorProps> & {
40
- ref?: import('preact').Ref<HTMLDivElement> | undefined;
41
- }>;
45
+ export declare const MenuPrimitiveSeparator: import('react').ForwardRefExoticComponent<{
46
+ className?: string;
47
+ } & Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
42
48
  type RadioGroupContextValue = {
43
49
  value: string | undefined;
44
50
  setValue: (value: string) => void;
@@ -59,9 +65,16 @@ export type MenuRadioGroupOwnProps = {
59
65
  export type MenuPrimitiveRadioGroupProps = MenuRadioGroupOwnProps & {
60
66
  setOpen: (open: boolean) => void;
61
67
  };
62
- export declare const MenuPrimitiveRadioGroup: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveRadioGroupProps> & {
63
- ref?: import('preact').Ref<HTMLDivElement> | undefined;
64
- }>;
68
+ export declare const MenuPrimitiveRadioGroup: import('react').ForwardRefExoticComponent<{
69
+ value?: string;
70
+ defaultValue?: string;
71
+ onValueChange?: (value: string) => void;
72
+ closeOnSelect?: boolean;
73
+ className?: string;
74
+ children?: ReactNode;
75
+ } & Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & {
76
+ setOpen: (open: boolean) => void;
77
+ } & import('react').RefAttributes<HTMLDivElement>>;
65
78
  export declare const menuRadioItemVariants: (props?: ({
66
79
  active?: boolean | null | undefined;
67
80
  disabled?: boolean | null | undefined;
@@ -76,18 +89,26 @@ export type MenuRadioItemOwnProps = {
76
89
  onSelect?: (event: Event) => void;
77
90
  className?: string;
78
91
  children: ReactNode;
79
- } & ComponentPropsWithoutRef<'div'>;
92
+ } & Omit<ComponentPropsWithoutRef<'div'>, 'onSelect'>;
80
93
  /** Internal primitive props (own props + context operations except setOpen, which comes from RadioGroup) */
81
94
  export type MenuPrimitiveRadioItemProps = MenuRadioItemOwnProps & Omit<MenuContextOperations, 'setOpen'>;
82
- export declare const MenuPrimitiveRadioItem: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveRadioItemProps> & {
83
- ref?: import('preact').Ref<HTMLDivElement> | undefined;
84
- }>;
95
+ export declare const MenuPrimitiveRadioItem: import('react').ForwardRefExoticComponent<{
96
+ id?: string;
97
+ value: string;
98
+ asChild?: boolean;
99
+ disabled?: boolean;
100
+ onSelect?: (event: Event) => void;
101
+ className?: string;
102
+ children: ReactNode;
103
+ } & Omit<Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref">, "onSelect"> & Omit<MenuContextOperations, "setOpen"> & import('react').RefAttributes<HTMLDivElement>>;
85
104
  export type MenuPrimitiveItemIndicatorProps = {
86
105
  forceMount?: boolean;
87
106
  className?: string;
88
107
  children?: ReactNode;
89
108
  } & ComponentPropsWithoutRef<'span'>;
90
- export declare const MenuPrimitiveItemIndicator: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveItemIndicatorProps> & {
91
- ref?: import('preact').Ref<HTMLSpanElement> | undefined;
92
- }>;
109
+ export declare const MenuPrimitiveItemIndicator: import('react').ForwardRefExoticComponent<{
110
+ forceMount?: boolean;
111
+ className?: string;
112
+ children?: ReactNode;
113
+ } & Omit<import('react').DetailedHTMLProps<import('react').HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "ref"> & import('react').RefAttributes<HTMLSpanElement>>;
93
114
  export {};
@@ -8,11 +8,11 @@ export type ComboboxOpenOptions = {
8
8
  export type ComboboxApplyOptions = Pick<ComboboxOpenOptions, 'restoreFocus'>;
9
9
  export type ComboboxContextValue = {
10
10
  baseId: string;
11
- contentRef: RefObject<HTMLDivElement>;
12
- controlRef: RefObject<HTMLDivElement> | null;
13
- inputRef: RefObject<HTMLInputElement>;
14
- applyRef: RefObject<HTMLButtonElement>;
15
- popupRef: RefObject<HTMLDivElement>;
11
+ contentRef: RefObject<HTMLDivElement | null>;
12
+ controlRef: RefObject<HTMLDivElement | null> | null;
13
+ inputRef: RefObject<HTMLInputElement | null>;
14
+ applyRef: RefObject<HTMLButtonElement | null>;
15
+ popupRef: RefObject<HTMLDivElement | null>;
16
16
  restoreFocusOnCloseRef: RefObject<boolean>;
17
17
  inputFocusIntentRef: RefObject<boolean>;
18
18
  ignoreFocusExitCloseRef: RefObject<boolean>;
@@ -14,6 +14,7 @@ export type ContextMenuContentContextValue = {
14
14
  getItems: () => string[];
15
15
  isItemDisabled: (id: string) => boolean;
16
16
  getItemElement: (id: string) => HTMLElement | null;
17
+ registerFocusBranch: (element: HTMLElement) => () => void;
17
18
  /**
18
19
  * Safe-triangle check registered by a currently-open child `Sub`. When non-null,
19
20
  * the Content's pointer-move capture handler consults it to decide whether to
@@ -10,7 +10,7 @@ export type ContextMenuContextValue = {
10
10
  baseId: string;
11
11
  open: boolean;
12
12
  setOpen: (open: boolean) => void;
13
- /** Pointer coordinates where the menu was opened (set by Trigger onContextMenu) */
13
+ /** Coordinates where the menu was opened, from the pointer or keyboard target. */
14
14
  position: {
15
15
  x: number;
16
16
  y: number;
@@ -22,10 +22,9 @@ export type ContextMenuContextValue = {
22
22
  /**
23
23
  * Tracks whether the user has started keyboard navigation.
24
24
  *
25
- * Context menus are always opened via pointer (right-click), so focus-visible
26
- * rings are hidden initially. Once the user presses a navigation key (Arrow keys,
27
- * Home, End), this becomes `true` and focus rings appear. The state resets to
28
- * `false` when the menu closes.
25
+ * Keyboard openings show focus-visible rings immediately. Pointer openings
26
+ * show them once the user presses a navigation key (Arrow keys, Home, End).
27
+ * The state resets to `false` when the menu closes.
29
28
  */
30
29
  isUsingKeyboard: boolean;
31
30
  setIsUsingKeyboard: (value: boolean) => void;
@@ -1,4 +1,4 @@
1
- import { RefObject } from 'react';
1
+ import { Context, RefObject } from 'react';
2
2
  import { ContextMenuContentContextValue } from './context-menu-content-provider';
3
3
  /**
4
4
  * Context value for a single `ContextMenu.Sub` wrapper.
@@ -16,13 +16,13 @@ export type ContextMenuSubContextValue = {
16
16
  /** Open/close the submenu */
17
17
  setOpen: (open: boolean) => void;
18
18
  /** Ref to the SubTrigger DOM node (for ArrowLeft refocus and collision anchoring) */
19
- triggerRef: RefObject<HTMLDivElement> | null;
19
+ triggerRef: RefObject<HTMLDivElement | null> | null;
20
20
  /**
21
21
  * Snapshot of the parent content context captured at mount time.
22
22
  * Used by SubContent's ArrowLeft handler to set parent `active` back to the trigger.
23
23
  */
24
24
  parentContent: ContextMenuContentContextValue;
25
25
  };
26
- export declare const ContextMenuSubProvider: import('preact').Provider<ContextMenuSubContextValue | undefined>;
26
+ export declare const ContextMenuSubProvider: Context<ContextMenuSubContextValue | undefined>['Provider'];
27
27
  export declare const useContextMenuSub: () => ContextMenuSubContextValue;
28
28
  export declare const useContextMenuSubOptional: () => ContextMenuSubContextValue | undefined;
@@ -3,8 +3,8 @@ export type DatePickerContextValue = {
3
3
  baseId: string;
4
4
  open: boolean;
5
5
  setOpen: (open: boolean) => void;
6
- triggerRef: RefObject<HTMLButtonElement>;
7
- focusOnCloseRef?: RefObject<HTMLElement>;
6
+ triggerRef: RefObject<HTMLButtonElement | null>;
7
+ focusOnCloseRef?: RefObject<HTMLElement | null>;
8
8
  monthSelectId: string;
9
9
  yearSelectId: string;
10
10
  prevButtonId: string;
@@ -1,11 +1,12 @@
1
- import { ReactElement, ReactNode } from 'react';
1
+ import { Dispatch, ReactElement, ReactNode, SetStateAction } from 'react';
2
2
  export type DialogContextValue = {
3
3
  open: boolean;
4
4
  setOpen: (next: boolean) => void;
5
- titleId: string;
6
- descriptionId: string;
7
- setTitleId: (id: string) => void;
8
- setDescriptionId: (id: string) => void;
5
+ titleId: string | undefined;
6
+ descriptionId: string | undefined;
7
+ setTitleId: Dispatch<SetStateAction<string | undefined>>;
8
+ setDescriptionId: Dispatch<SetStateAction<string | undefined>>;
9
+ isUsingStepper?: boolean;
9
10
  };
10
11
  export type DialogProviderProps = {
11
12
  value: DialogContextValue;
@@ -4,13 +4,15 @@
4
4
  * Used by Dialog.Content to extend focus-trap-react's containerElements
5
5
  * to include portaled popups (e.g., Combobox.Popup in portal mode).
6
6
  *
7
- * @see .cursor/rules/patterns.mdc — "Focus Trap with Portaled Content"
7
+ * @see docs/patterns.md — "Focus Trap with Portaled Content"
8
8
  */
9
9
  export type FocusContainerRegistry = {
10
10
  register: (element: HTMLElement) => void;
11
11
  unregister: (element: HTMLElement) => void;
12
+ /** Focus once the trap has acknowledged this container. Returns a cancellation callback. */
13
+ requestFocus?: (container: HTMLElement, focus: () => void) => () => void;
12
14
  };
13
- export declare const FocusContainerContext: import('preact').Context<FocusContainerRegistry | null>;
15
+ export declare const FocusContainerContext: import('react').Context<FocusContainerRegistry | null>;
14
16
  /**
15
17
  * Hook for portaled components to register with a parent focus trap.
16
18
  * Returns null when not inside a focus container provider (e.g., Dialog.Content).
@@ -10,7 +10,8 @@ export type MenuContextValue = {
10
10
  getItems: () => string[];
11
11
  isItemDisabled: (id: string) => boolean;
12
12
  getItemElement: (id: string) => HTMLElement | null;
13
- triggerRef: RefObject<HTMLButtonElement> | null;
13
+ triggerRef: RefObject<HTMLButtonElement | null> | null;
14
+ contentRef: RefObject<HTMLDivElement | null>;
14
15
  };
15
16
  export type MenuProviderProps = {
16
17
  value: MenuContextValue;
@@ -1,4 +1,4 @@
1
- import { RefObject } from 'react';
1
+ import { Context, RefObject } from 'react';
2
2
  import { MenubarContextValue } from './menubar-provider';
3
3
  /**
4
4
  * Context value for individual menu within a menubar.
@@ -17,10 +17,10 @@ export type MenubarMenuContextValue = {
17
17
  /** Reference to the parent menubar context */
18
18
  menubarContext: MenubarContextValue;
19
19
  /** Reference to the trigger element */
20
- triggerRef: RefObject<HTMLButtonElement> | null;
20
+ triggerRef: RefObject<HTMLButtonElement | null> | null;
21
21
  };
22
- export declare const MenubarMenuContext: import('preact').Context<MenubarMenuContextValue | undefined>;
23
- export declare const MenubarMenuProvider: import('preact').Provider<MenubarMenuContextValue | undefined>;
22
+ export declare const MenubarMenuContext: Context<MenubarMenuContextValue | undefined>;
23
+ export declare const MenubarMenuProvider: Context<MenubarMenuContextValue | undefined>['Provider'];
24
24
  /**
25
25
  * Access the menubar menu context.
26
26
  *
@@ -35,7 +35,7 @@ export type MenubarContextValue = {
35
35
  /**
36
36
  * Ref to the menubar container element for focus management.
37
37
  */
38
- menubarRef: RefObject<HTMLDivElement> | null;
38
+ menubarRef: RefObject<HTMLDivElement | null> | null;
39
39
  };
40
40
  export type MenubarProviderProps = {
41
41
  value: MenubarContextValue;
@@ -8,7 +8,7 @@ export type SearchFieldContextValue = {
8
8
  placeholder?: string;
9
9
  clearLabel: string;
10
10
  setValue: (v: string) => void;
11
- inputRef: RefObject<HTMLInputElement>;
11
+ inputRef: RefObject<HTMLInputElement | null>;
12
12
  };
13
13
  export type SearchFieldProviderProps = {
14
14
  value: SearchFieldContextValue;
@@ -20,7 +20,7 @@ export type SelectorContextValue = {
20
20
  unregisterItemText: (id: string) => void;
21
21
  getItemText: (id: string) => string | undefined;
22
22
  keyHandler: (e: React.KeyboardEvent<HTMLElement>) => void;
23
- triggerRef: RefObject<HTMLButtonElement>;
23
+ triggerRef: RefObject<HTMLButtonElement | null>;
24
24
  };
25
25
  export type SelectorProviderProps = {
26
26
  value: SelectorContextValue;
@@ -19,4 +19,6 @@ export declare const StepperProvider: {
19
19
  ({ value, children }: StepperProviderProps): ReactElement;
20
20
  displayName: string;
21
21
  };
22
+ /** The enclosing stepper, if any — for parts that adapt to steps without requiring them. */
23
+ export declare const useStepperOptional: () => StepperContextValue | undefined;
22
24
  export declare const useStepper: () => StepperContextValue;
@@ -3,9 +3,9 @@ export type TimePickerContextValue = {
3
3
  baseId: string;
4
4
  open: boolean;
5
5
  setOpen: (open: boolean) => void;
6
- triggerRef: RefObject<HTMLButtonElement>;
7
- hourTriggerRef: RefObject<HTMLButtonElement>;
8
- minuteTriggerRef: RefObject<HTMLButtonElement>;
6
+ triggerRef: RefObject<HTMLButtonElement | null>;
7
+ hourTriggerRef: RefObject<HTMLButtonElement | null>;
8
+ minuteTriggerRef: RefObject<HTMLButtonElement | null>;
9
9
  hourSelectId: string;
10
10
  minuteSelectId: string;
11
11
  selectorActive: string | undefined;
@@ -1,7 +1,7 @@
1
1
  import { ReactElement, ReactNode } from 'react';
2
2
  export type ToolbarContextValue = {
3
3
  toolbarId: string;
4
- toolbarRef: React.RefObject<HTMLDivElement>;
4
+ toolbarRef: React.RefObject<HTMLDivElement | null>;
5
5
  active: string | undefined;
6
6
  setActive: (id: string | undefined) => void;
7
7
  registerItem: (id: string, disabled?: boolean, element?: HTMLElement | null) => void;
@@ -1,7 +1,11 @@
1
- import { LucideIcon as LucideIconPreact } from 'lucide-preact';
2
- import { LucideIcon as LucideIconReact } from 'lucide-react';
1
+ import { ComponentType } from 'react';
3
2
  /**
4
- * Unified LucideIcon type that accepts icons from both lucide-react and lucide-preact.
5
- * This allows the UI library to work seamlessly with both React and Preact projects.
3
+ * An icon component as the library renders it. Structural rather than `lucide-react`'s own type, so icons from
4
+ * both `lucide-react` and `lucide-preact` fit.
6
5
  */
7
- export type LucideIcon = LucideIconPreact | LucideIconReact;
6
+ export type LucideIcon = ComponentType<{
7
+ size?: string | number;
8
+ strokeWidth?: string | number;
9
+ className?: string;
10
+ 'aria-hidden'?: boolean | 'true' | 'false';
11
+ }>;
@@ -13,10 +13,32 @@ export declare const getRoot: (node: Node | null | undefined) => Document | Shad
13
13
  /**
14
14
  * The deeply focused element. From outside a shadow root `document.activeElement` reports the host
15
15
  * rather than the focused element inside it. Stops at a closed root, whose `shadowRoot` is hidden.
16
+ *
17
+ * Reads from `root` when given instead of the global document: a held reference to a closed root
18
+ * still sees inside it, and another frame's document has its own focus.
16
19
  */
17
- export declare const getActiveElement: () => Element | null;
20
+ export declare const getActiveElement: (root?: Document | ShadowRoot | null) => Element | null;
18
21
  /**
19
22
  * Whether a container holds a node, crossing shadow boundaries. `Node.contains` stops at them, so a
20
23
  * node focused inside a nested root reads as outside the host that holds it.
21
24
  */
22
25
  export declare const containsDeep: (container: Node | null | undefined, node: Node | null | undefined) => boolean;
26
+ type BubbledEvent = {
27
+ currentTarget: EventTarget | null;
28
+ target: EventTarget | null;
29
+ };
30
+ /**
31
+ * Whether an event reached its handler from a portal rendered inside it rather than from the
32
+ * handler's own DOM. React bubbles events along the component tree, through portals, while Preact
33
+ * bubbles along the DOM. Container handlers return early on these so both runtimes behave alike.
34
+ */
35
+ export declare const isPortaledEvent: ({ currentTarget, target }: BubbledEvent) => boolean;
36
+ /**
37
+ * The element a container asks to receive focus: the first `[data-autofocus]` that is not under a
38
+ * `[hidden]` ancestor. The consumer marks the target; the caller decides when focus moves.
39
+ *
40
+ * `data-autofocus="false"` is skipped, since Preact writes a `false` boolean to a `data-*`
41
+ * attribute as a string — toggle the marker with `data-autofocus={condition || undefined}`.
42
+ */
43
+ export declare const getAutofocusTarget: (container: Element | null | undefined) => HTMLElement | null;
44
+ export {};
@@ -1,4 +1,4 @@
1
- type Signalish<T> = T | SignalLike<T>;
1
+ export type Signalish<T> = T | SignalLike<T>;
2
2
  type SignalLike<T> = {
3
3
  value: T;
4
4
  peek(): T;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enonic/ui",
3
- "version": "1.5.0",
3
+ "version": "1.7.0",
4
4
  "description": "Enonic UI Component Library",
5
5
  "author": "Enonic",
6
6
  "license": "Apache-2.0",
@@ -59,66 +59,61 @@
59
59
  "class-variance-authority": "~0.7.1",
60
60
  "clsx": "~2.1.1",
61
61
  "focus-trap-react": "^12.0.3",
62
- "lucide-react": "^1.16.0",
62
+ "lucide-react": "^1.72.0",
63
63
  "tailwind-merge": "~3.7.0"
64
64
  },
65
65
  "peerDependencies": {
66
66
  "@radix-ui/react-slot": "^1.2.0",
67
- "preact": ">=10.0.0",
68
67
  "react": "^19.0.0",
69
68
  "react-dom": "^19.0.0",
70
- "react-virtuoso": "^4.0.0",
71
69
  "tw-animate-css": "^1.4.0"
72
70
  },
73
71
  "peerDependenciesMeta": {
74
- "preact": {
75
- "optional": true
76
- },
77
72
  "react": {
78
73
  "optional": true
79
74
  },
80
75
  "react-dom": {
81
76
  "optional": true
82
77
  },
83
- "react-virtuoso": {
84
- "optional": true
85
- },
86
78
  "tw-animate-css": {
87
79
  "optional": true
88
80
  }
89
81
  },
90
82
  "devDependencies": {
91
83
  "@preact/preset-vite": "~2.10.6",
92
- "@radix-ui/react-slot": "^1.3.3",
93
- "@size-limit/preset-small-lib": "~14.1.0",
94
- "@storybook/addon-a11y": "~10.6.0",
95
- "@storybook/addon-docs": "~10.6.0",
96
- "@storybook/addon-themes": "~10.6.0",
97
- "@storybook/preact-vite": "~10.6.0",
98
- "@storybook/test-runner": "^0.24.4",
99
- "@tailwindcss/vite": "~4.3.3",
100
- "@types/node": "~26.4.1",
84
+ "@radix-ui/react-slot": "^1.5.0",
85
+ "@size-limit/preset-small-lib": "~14.3.0",
86
+ "@storybook/addon-a11y": "~10.6.7",
87
+ "@storybook/addon-docs": "~10.6.7",
88
+ "@storybook/addon-themes": "~10.6.7",
89
+ "@storybook/preact-vite": "~10.6.7",
90
+ "@storybook/test-runner": "^0.26.0",
91
+ "@tailwindcss/vite": "~4.3.5",
92
+ "@types/node": "~26.6.6",
101
93
  "@types/react": "~19.3.0",
102
94
  "@types/react-dom": "~19.3.0",
103
95
  "@typescript/typescript6": "~6.0.2",
104
96
  "http-server": "^14.1.1",
105
- "lucide-preact": "~1.48.0",
106
- "playwright": "^1.62.1",
107
- "publint": "^0.3.24",
108
- "react-doctor": "0.9.14",
109
- "react-virtuoso": "^4.18.11",
110
- "rollup-plugin-visualizer": "~7.1.1",
111
- "size-limit": "~14.1.0",
112
- "storybook": "~10.6.0",
113
- "tailwindcss": "~4.3.3",
97
+ "jsdom": "~31.0.0",
98
+ "playwright": "^1.65.0",
99
+ "preact": "~10.29.8",
100
+ "publint": "^0.3.25",
101
+ "react": "~19.3.1",
102
+ "react-doctor": "0.9.22",
103
+ "react-dom": "~19.3.1",
104
+ "react-virtuoso": "^4.18.21",
105
+ "rollup-plugin-visualizer": "~7.1.3",
106
+ "size-limit": "~14.3.0",
107
+ "storybook": "~10.6.7",
108
+ "tailwindcss": "~4.3.5",
114
109
  "terser": "~5.51.2",
115
110
  "tw-animate-css": "^1.4.0",
116
111
  "typescript": "~7.0.2",
117
- "vite": "npm:@voidzero-dev/vite-plus-core@^1.0.0",
118
- "vite-plugin-dts": "~5.1.0",
119
- "vite-plugin-environment": "~1.1.3",
120
- "vite-plus": "^1.0.0",
121
- "wait-on": "^9.1.0"
112
+ "vite": "npm:@voidzero-dev/vite-plus-core@^1.6.0",
113
+ "vite-plugin-dts": "~5.1.2",
114
+ "vite-plugin-environment": "~1.1.4",
115
+ "vite-plus": "^1.6.0",
116
+ "wait-on": "^9.5.1"
122
117
  },
123
118
  "engines": {
124
119
  "node": ">=24.16.0",
@@ -130,14 +125,14 @@
130
125
  "ignore": [
131
126
  "focus-trap-react"
132
127
  ],
133
- "limit": "60 KB"
128
+ "limit": "65 KB"
134
129
  },
135
130
  {
136
131
  "path": "dist/enonic-ui.cjs",
137
132
  "ignore": [
138
133
  "focus-trap-react"
139
134
  ],
140
- "limit": "60 KB"
135
+ "limit": "65 KB"
141
136
  },
142
137
  {
143
138
  "path": "dist/styles/style.css",
@@ -172,9 +167,9 @@
172
167
  "format": "vp fmt",
173
168
  "format:check": "vp fmt --check",
174
169
  "check:react": "react-doctor --no-telemetry",
175
- "test": "exit 0",
170
+ "test": "vp test",
176
171
  "check:package": "node scripts/package-smoke.mjs",
177
- "test:ci": "exit 0",
172
+ "test:ci": "vp test",
178
173
  "test:storybook": "test-storybook --includeTags interactions-smoke",
179
174
  "release:dry": "pnpm publish --dry-run --no-git-checks",
180
175
  "analyze": "pnpm build && open dist/stats.html",