@enonic/ui 0.23.1 → 0.25.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.
@@ -0,0 +1,131 @@
1
+ import { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';
2
+ export type SelectorRootProps = {
3
+ children?: ReactNode;
4
+ value?: string;
5
+ defaultValue?: string;
6
+ onValueChange?: (value: string) => void;
7
+ open?: boolean;
8
+ defaultOpen?: boolean;
9
+ onOpenChange?: (open: boolean) => void;
10
+ disabled?: boolean;
11
+ error?: boolean;
12
+ required?: boolean;
13
+ name?: string;
14
+ form?: string;
15
+ };
16
+ export type SelectorTriggerProps = {
17
+ className?: string;
18
+ children?: ReactNode;
19
+ } & Omit<ComponentPropsWithoutRef<'button'>, 'children'>;
20
+ export type SelectorValueProps = {
21
+ className?: string;
22
+ placeholder?: string;
23
+ /**
24
+ * Content to display for the selected value.
25
+ * Can be a render function `(value: string) => ReactNode` for dynamic display text,
26
+ * or a static ReactNode. If not provided, falls back to raw value.
27
+ *
28
+ * **Recommended**: Use a render function to display proper labels instead of raw values.
29
+ * This ensures the correct text is shown even before the dropdown is opened.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * // Render function (recommended)
34
+ * <Selector.Value placeholder="Select...">
35
+ * {(value) => items.find(i => i.value === value)?.label}
36
+ * </Selector.Value>
37
+ *
38
+ * // Falls back to raw value when no children provided
39
+ * <Selector.Value placeholder="Select..." />
40
+ * ```
41
+ */
42
+ children?: ReactNode | ((value: string) => ReactNode);
43
+ } & Omit<ComponentPropsWithoutRef<'span'>, 'children'>;
44
+ export type SelectorIconProps = {
45
+ className?: string;
46
+ children?: ReactNode;
47
+ } & ComponentPropsWithoutRef<'span'>;
48
+ export type SelectorContentProps = {
49
+ className?: string;
50
+ children?: ReactNode;
51
+ align?: 'start' | 'end';
52
+ onEscapeKeyDown?: (event: KeyboardEvent) => void;
53
+ onPointerDownOutside?: (event: PointerEvent) => void;
54
+ onInteractOutside?: (event: Event) => void;
55
+ } & ComponentPropsWithoutRef<'div'>;
56
+ export type SelectorViewportProps = {
57
+ className?: string;
58
+ children?: ReactNode;
59
+ } & ComponentPropsWithoutRef<'div'>;
60
+ export type SelectorItemProps = {
61
+ value: string;
62
+ disabled?: boolean;
63
+ textValue?: string;
64
+ className?: string;
65
+ children: ReactNode;
66
+ } & Omit<ComponentPropsWithoutRef<'div'>, 'children'>;
67
+ export type SelectorItemTextProps = {
68
+ className?: string;
69
+ children?: ReactNode;
70
+ } & ComponentPropsWithoutRef<'span'>;
71
+ export type SelectorItemIndicatorProps = {
72
+ className?: string;
73
+ children?: ReactNode;
74
+ } & ComponentPropsWithoutRef<'span'>;
75
+ export type SelectorGroupProps = {
76
+ className?: string;
77
+ children?: ReactNode;
78
+ } & ComponentPropsWithoutRef<'div'>;
79
+ export type SelectorLabelProps = {
80
+ className?: string;
81
+ children?: ReactNode;
82
+ } & ComponentPropsWithoutRef<'div'>;
83
+ export type SelectorSeparatorProps = {
84
+ className?: string;
85
+ } & ComponentPropsWithoutRef<'div'>;
86
+ export type SelectorHiddenSelectProps = ComponentPropsWithoutRef<'select'>;
87
+ export declare const Selector: {
88
+ ({ children, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled, error, required, name, form, }: SelectorRootProps): ReactElement;
89
+ displayName: string;
90
+ } & {
91
+ Root: {
92
+ ({ children, value: controlledValue, defaultValue, onValueChange, open: controlledOpen, defaultOpen, onOpenChange, disabled, error, required, name, form, }: SelectorRootProps): ReactElement;
93
+ displayName: string;
94
+ };
95
+ Trigger: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorTriggerProps> & {
96
+ ref?: import('preact').Ref<HTMLButtonElement> | undefined;
97
+ }>;
98
+ Value: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorValueProps> & {
99
+ ref?: import('preact').Ref<HTMLSpanElement> | undefined;
100
+ }>;
101
+ Icon: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorIconProps> & {
102
+ ref?: import('preact').Ref<HTMLSpanElement> | undefined;
103
+ }>;
104
+ Content: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorContentProps> & {
105
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
106
+ }>;
107
+ Viewport: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorViewportProps> & {
108
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
109
+ }>;
110
+ Item: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorItemProps> & {
111
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
112
+ }>;
113
+ ItemText: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorItemTextProps> & {
114
+ ref?: import('preact').Ref<HTMLSpanElement> | undefined;
115
+ }>;
116
+ ItemIndicator: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorItemIndicatorProps> & {
117
+ ref?: import('preact').Ref<HTMLSpanElement> | undefined;
118
+ }>;
119
+ Group: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorGroupProps> & {
120
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
121
+ }>;
122
+ Label: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorLabelProps> & {
123
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
124
+ }>;
125
+ Separator: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorSeparatorProps> & {
126
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
127
+ }>;
128
+ HiddenSelect: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<SelectorHiddenSelectProps> & {
129
+ ref?: import('preact').Ref<HTMLSelectElement> | undefined;
130
+ }>;
131
+ };
@@ -1 +1 @@
1
- export { isLoadingPlaceholder, TreeList, type TreeListProps, type TreeNode } from './tree-list';
1
+ export { type DefaultTreeListLoadingRowViewProps, type FlatTreeNode, LOADING_SUFFIX, ROOT_PARENT_ID, type TreeData, type TreeItems, TreeList, type TreeListContainerProps, type TreeListContentProps, type TreeListErrorRowProps, type TreeListLoadingRowProps, type TreeListProps, type TreeListRowContentProps, type TreeListRowExpandControlProps, type TreeListRowLeftProps, type TreeListRowLevelSpacerProps, type TreeListRowProps, type TreeListRowRightProps, type TreeListRowSelectionControlProps, } from './tree-list';
@@ -1,14 +1,29 @@
1
1
  import { ComponentPropsWithoutRef, ReactElement, ReactNode } from 'react';
2
2
  import { IconButtonProps } from '../icon-button';
3
3
  import { LucideIcon } from '../../types';
4
- export type TreeListProps<T extends TreeNode = TreeNode> = {
4
+ export declare const LOADING_SUFFIX = "__loading__";
5
+ export declare const ROOT_PARENT_ID = "__root__";
6
+ export type TreeData = {
7
+ id: string;
8
+ hasChildren: boolean;
9
+ };
10
+ export type TreeItems<TreeData> = {
11
+ nodes: Record<string, TreeData | undefined>;
12
+ children: Record<string, string[] | undefined>;
13
+ hasMore: Record<string, boolean | undefined>;
14
+ };
15
+ export type FlatTreeNode<TData extends TreeData> = {
16
+ id: string;
17
+ data: TData;
18
+ level: number;
19
+ parentId: string | null;
20
+ nodeType: 'node' | 'loading' | 'error';
21
+ };
22
+ export type TreeListProps<TData extends TreeData = TreeData> = {
5
23
  className?: string;
6
- items?: T[];
7
- setItems?: (items: T[]) => void;
8
- isItemSelectable?: (item: T) => boolean;
9
- fetchChildren: (parentNode: T | undefined, offset: number) => Promise<{
10
- items: T[];
11
- total: number;
24
+ fetchChildren: (parentId: string | undefined, offset: number) => Promise<{
25
+ items: TData[];
26
+ hasMore: boolean;
12
27
  }>;
13
28
  expanded?: ReadonlySet<string>;
14
29
  onExpandedChange?: (expanded: ReadonlySet<string>) => void;
@@ -18,75 +33,85 @@ export type TreeListProps<T extends TreeNode = TreeNode> = {
18
33
  active?: string | null;
19
34
  defaultActive?: string;
20
35
  setActive?: (active: string | null | undefined) => void;
36
+ isItemSelectable?: (item: TData) => boolean;
37
+ items?: TreeItems<TData>;
38
+ onItemsChange?: (items: TreeItems<TData>) => void;
21
39
  children?: ReactNode;
22
40
  } & ComponentPropsWithoutRef<'div'>;
23
- export type TreeListContentProps = {
41
+ export type TreeListContainerProps = {
24
42
  children?: ReactNode;
25
- load?: boolean;
43
+ className?: string;
44
+ } & ComponentPropsWithoutRef<'div'>;
45
+ export type DefaultTreeListLoadingRowViewProps<TData extends TreeData> = {
46
+ item: FlatTreeNode<TData>;
26
47
  } & ComponentPropsWithoutRef<'div'>;
27
- type TreeListContainerProps = {
48
+ export type TreeListLoadingRowProps<TData extends TreeData> = {
49
+ item: FlatTreeNode<TData>;
50
+ renderLoading?: (item: FlatTreeNode<TData>) => ReactNode;
28
51
  children?: ReactNode;
52
+ intersectionProps?: IntersectionObserverInit;
53
+ } & ComponentPropsWithoutRef<'div'>;
54
+ export type TreeListErrorRowProps<TData extends TreeData> = {
55
+ item: FlatTreeNode<TData>;
56
+ renderError?: (item: FlatTreeNode<TData>) => ReactNode;
29
57
  className?: string;
58
+ onRetry?: () => void;
30
59
  } & ComponentPropsWithoutRef<'div'>;
31
- type TreeListRowLeftProps = {
60
+ export type TreeListContentProps<TData extends TreeData> = {
61
+ renderNode: (item: FlatTreeNode<TData>) => ReactNode;
62
+ renderLoading?: (item: FlatTreeNode<TData>) => ReactNode;
63
+ renderError?: (item: FlatTreeNode<TData>) => ReactNode;
64
+ };
65
+ export type TreeListRowLeftProps = {
32
66
  children?: ReactNode;
33
67
  className?: string;
34
68
  } & ComponentPropsWithoutRef<'div'>;
35
- type TreeListRowRightProps = {
69
+ export type TreeListRowRightProps = {
36
70
  children?: ReactNode;
37
71
  className?: string;
38
72
  } & ComponentPropsWithoutRef<'div'>;
39
- type TreeListRowLevelSpacerProps = {
73
+ export type TreeListRowLevelSpacerProps = {
40
74
  level?: number;
41
75
  className?: string;
42
76
  } & ComponentPropsWithoutRef<'div'>;
43
- type TreeListRowExpandControlProps = {
44
- data: TreeNode;
77
+ export type TreeListRowExpandControlProps<TData extends TreeData> = {
78
+ data: FlatTreeNode<TData>;
45
79
  icon?: LucideIcon;
46
80
  } & Omit<IconButtonProps, 'icon'>;
47
- type TreeListRowContentProps = {
81
+ export type TreeListRowContentProps = {
48
82
  children?: ReactNode;
49
83
  className?: string;
50
84
  } & ComponentPropsWithoutRef<'div'>;
51
- type TreeListRowSelectionControlProps = {
52
- data: TreeNode;
85
+ export type TreeListRowSelectionControlProps<TData extends TreeData> = {
86
+ data: FlatTreeNode<TData>;
53
87
  className?: string;
54
88
  } & ComponentPropsWithoutRef<'div'>;
55
- type TreeListRowProps<T extends TreeNode> = {
56
- item: T;
89
+ export type TreeListRowProps<TData extends TreeData> = {
90
+ item: FlatTreeNode<TData>;
57
91
  children: ReactNode;
58
92
  className?: string;
59
93
  } & ComponentPropsWithoutRef<'div'>;
60
- export type TreeListLoadingRowProps = {
61
- item: TreeNode;
62
- children?: ReactNode;
63
- intersectionProps?: IntersectionObserverInit;
64
- } & ComponentPropsWithoutRef<'div'>;
65
- export declare const TreeListRoot: <T extends TreeNode = TreeNode>({ className, items: controlledItems, setItems: setItemsControlled, fetchChildren, selection: controlledSelection, expanded: controlledExpanded, onSelectionChange, onExpandedChange, active: controlledActive, defaultActive, setActive, selectionMode, isItemSelectable, children, ...props }: TreeListProps<T>) => ReactElement;
66
- export type TreeNode = {
67
- id: string;
68
- hasChildren?: boolean;
69
- children?: TreeNode[];
70
- hasMoreChildren?: boolean;
71
- path: string[];
72
- };
73
- export declare function isLoadingPlaceholder(node: TreeNode): boolean;
74
- export declare const TreeList: (<T extends TreeNode = TreeNode>({ className, items: controlledItems, setItems: setItemsControlled, fetchChildren, selection: controlledSelection, expanded: controlledExpanded, onSelectionChange, onExpandedChange, active: controlledActive, defaultActive, setActive, selectionMode, isItemSelectable, children, ...props }: TreeListProps<T>) => ReactElement) & {
75
- Root: <T extends TreeNode = TreeNode>({ className, items: controlledItems, setItems: setItemsControlled, fetchChildren, selection: controlledSelection, expanded: controlledExpanded, onSelectionChange, onExpandedChange, active: controlledActive, defaultActive, setActive, selectionMode, isItemSelectable, children, ...props }: TreeListProps<T>) => ReactElement;
94
+ export declare const TreeList: (<TData extends TreeData = TreeData>({ className, fetchChildren, selection: controlledSelection, expanded: controlledExpanded, onSelectionChange, onExpandedChange, active: controlledActive, defaultActive, setActive, selectionMode, isItemSelectable, items: controlledItems, onItemsChange, children, ...props }: TreeListProps<TData>) => ReactElement) & {
95
+ Root: <TData extends TreeData = TreeData>({ className, fetchChildren, selection: controlledSelection, expanded: controlledExpanded, onSelectionChange, onExpandedChange, active: controlledActive, defaultActive, setActive, selectionMode, isItemSelectable, items: controlledItems, onItemsChange, children, ...props }: TreeListProps<TData>) => ReactElement;
76
96
  Container: {
77
97
  ({ children, className, ...props }: TreeListContainerProps): ReactElement<TreeListContainerProps>;
78
98
  displayName: string;
79
99
  };
80
100
  Content: {
81
- ({ children, load }: TreeListContentProps): ReactElement;
101
+ <TData extends TreeData>({ renderNode, renderLoading, renderError, }: TreeListContentProps<TData>): ReactElement;
82
102
  displayName: string;
83
103
  };
84
104
  Row: {
85
- <T extends TreeNode>({ item, children, className, ...props }: TreeListRowProps<T>): ReactElement;
105
+ <TData extends TreeData>({ item, children, className, ...props }: TreeListRowProps<TData>): ReactElement;
86
106
  displayName: string;
87
107
  };
88
108
  LoadingRow: {
89
- ({ item, children, intersectionProps, ...props }: TreeListLoadingRowProps): ReactElement;
109
+ <TData extends TreeData>({ item, renderLoading, className, children, intersectionProps, ...props }: TreeListLoadingRowProps<TData>): ReactElement;
110
+ displayName: string;
111
+ };
112
+ DefaultLoadingRowView: <TData extends TreeData>({ item, className, children, ...props }: DefaultTreeListLoadingRowViewProps<TData>) => ReactElement;
113
+ ErrorRow: {
114
+ <TData extends TreeData>({ item, onRetry, renderError, className, ...props }: TreeListErrorRowProps<TData>): ReactElement;
90
115
  displayName: string;
91
116
  };
92
117
  RowLeft: {
@@ -94,15 +119,15 @@ export declare const TreeList: (<T extends TreeNode = TreeNode>({ className, ite
94
119
  displayName: string;
95
120
  };
96
121
  RowRight: {
97
- ({ children, className, ...props }: TreeListRowRightProps): ReactElement<TreeListRowLeftProps>;
122
+ ({ children, className, ...props }: TreeListRowRightProps): ReactElement<TreeListRowRightProps>;
98
123
  displayName: string;
99
124
  };
100
125
  RowLevelSpacer: {
101
- ({ level, className, ...props }: TreeListRowLevelSpacerProps): ReactElement<TreeListRowLeftProps> | undefined;
126
+ ({ level, className, ...props }: TreeListRowLevelSpacerProps): ReactElement<TreeListRowLevelSpacerProps> | null;
102
127
  displayName: string;
103
128
  };
104
129
  RowExpandControl: {
105
- ({ data, icon, className, ...props }: TreeListRowExpandControlProps): ReactElement<TreeListRowExpandControlProps>;
130
+ <TData extends TreeData>({ data, icon, className, ...props }: TreeListRowExpandControlProps<TData>): ReactElement<TreeListRowExpandControlProps<TData>>;
106
131
  displayName: string;
107
132
  };
108
133
  RowContent: {
@@ -110,8 +135,7 @@ export declare const TreeList: (<T extends TreeNode = TreeNode>({ className, ite
110
135
  displayName: string;
111
136
  };
112
137
  RowSelectionControl: {
113
- ({ data, className, ...props }: TreeListRowSelectionControlProps): ReactElement;
138
+ <TData extends TreeData>({ data, className, ...props }: TreeListRowSelectionControlProps<TData>): ReactElement;
114
139
  displayName: string;
115
140
  };
116
141
  };
117
- export {};
@@ -4,8 +4,12 @@ export { useControlledState } from './use-controlled-state';
4
4
  export { useControlledStateWithNull } from './use-controlled-state-with-null';
5
5
  export { type FloatingPosition, type UseFloatingPositionConfig, useFloatingPosition } from './use-floating-position';
6
6
  export { type ItemMetadata, type UseItemRegistryReturn, useItemRegistry } from './use-item-registry';
7
+ export { type UseItemTextRegistryReturn, useItemTextRegistry } from './use-item-text-registry';
7
8
  export { type KeyboardNavigationConfig, type UseKeyboardNavigationReturn, useKeyboardNavigation, } from './use-keyboard-navigation';
9
+ export { type PointerPosition, type UsePointerPositionConfig, usePointerPosition } from './use-pointer-position';
8
10
  export { type UseRovingTabIndexConfig, type UseRovingTabIndexReturn, useRovingTabIndex } from './use-roving-tabindex';
9
11
  export { type UseScrollActiveIntoViewConfig, useScrollActiveIntoView } from './use-scroll-active-into-view';
10
12
  export { useScrollLock } from './use-scroll-lock';
13
+ export { type UseSelectorKeyboardConfig, type UseSelectorKeyboardReturn, useSelectorKeyboard, } from './use-selector-keyboard';
11
14
  export { useSyncValue } from './use-sync-value';
15
+ export { type UseTypeAheadConfig, type UseTypeAheadReturn, useTypeAhead } from './use-type-ahead';
@@ -0,0 +1,29 @@
1
+ export type UseItemTextRegistryReturn = {
2
+ /**
3
+ * Register text content for an item (for type-ahead matching)
4
+ */
5
+ registerItemText: (id: string, text: string) => void;
6
+ /**
7
+ * Unregister text content for an item
8
+ */
9
+ unregisterItemText: (id: string) => void;
10
+ /**
11
+ * Get the text content for an item
12
+ */
13
+ getItemText: (id: string) => string | undefined;
14
+ };
15
+ /**
16
+ * Hook for managing item text content registry.
17
+ * Used for type-ahead search functionality.
18
+ *
19
+ * @example
20
+ * ```tsx
21
+ * const { registerItemText, getItemText } = useItemTextRegistry();
22
+ *
23
+ * // In item component:
24
+ * useEffect(() => {
25
+ * registerItemText(id, textContent);
26
+ * }, [id, textContent]);
27
+ * ```
28
+ */
29
+ export declare function useItemTextRegistry(): UseItemTextRegistryReturn;
@@ -0,0 +1,57 @@
1
+ import { RefObject } from 'react';
2
+ export type PointerPosition = {
3
+ top: number;
4
+ left: number;
5
+ };
6
+ export type UsePointerPositionConfig = {
7
+ /** Whether positioning is enabled/active */
8
+ enabled: boolean;
9
+ /** Mouse/pointer position where the menu was triggered */
10
+ mousePosition: {
11
+ x: number;
12
+ y: number;
13
+ } | null;
14
+ /** Reference to the floating content element */
15
+ contentRef: RefObject<HTMLElement> | null;
16
+ };
17
+ /**
18
+ * Calculates optimal position for floating content relative to pointer coordinates,
19
+ * with automatic viewport collision detection and flip behavior.
20
+ *
21
+ * This hook handles:
22
+ * - Positioning at pointer cursor location
23
+ * - Horizontal flipping when overflowing right edge
24
+ * - Vertical flipping when overflowing bottom edge
25
+ * - Automatic repositioning on window resize
26
+ *
27
+ * @param config - Configuration object for positioning behavior
28
+ * @returns Position object with top and left coordinates, or null if not yet calculated
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * function ContextMenu() {
33
+ * const contentRef = useRef<HTMLDivElement>(null);
34
+ * const [mousePosition, setMousePosition] = useState<{ x: number; y: number } | null>(null);
35
+ *
36
+ * const position = usePointerPosition({
37
+ * enabled: open,
38
+ * mousePosition,
39
+ * contentRef,
40
+ * });
41
+ *
42
+ * return (
43
+ * <div
44
+ * ref={contentRef}
45
+ * style={{
46
+ * position: 'fixed',
47
+ * top: position ? `${position.top}px` : '0',
48
+ * left: position ? `${position.left}px` : '0',
49
+ * }}
50
+ * >
51
+ * Context menu content
52
+ * </div>
53
+ * );
54
+ * }
55
+ * ```
56
+ */
57
+ export declare function usePointerPosition({ enabled, mousePosition, contentRef, }: UsePointerPositionConfig): PointerPosition | null;
@@ -0,0 +1,68 @@
1
+ export type UseSelectorKeyboardConfig = {
2
+ /**
3
+ * Get all item IDs in order
4
+ */
5
+ getItems: () => string[];
6
+ /**
7
+ * Check if an item is disabled
8
+ */
9
+ isItemDisabled: (id: string) => boolean;
10
+ /**
11
+ * Get the text content for an item (for type-ahead matching)
12
+ */
13
+ getItemText: (id: string) => string | undefined;
14
+ /**
15
+ * Currently active item ID
16
+ */
17
+ active: string | undefined;
18
+ /**
19
+ * Set the active item ID
20
+ */
21
+ setActive: (id: string | undefined) => void;
22
+ /**
23
+ * Currently selected value
24
+ */
25
+ value: string | undefined;
26
+ /**
27
+ * Whether the dropdown is open
28
+ */
29
+ open: boolean;
30
+ /**
31
+ * Set open state (internal, doesn't return focus)
32
+ */
33
+ setOpenInternal: (open: boolean) => void;
34
+ /**
35
+ * Set open state (with focus return handling)
36
+ */
37
+ setOpen: (open: boolean) => void;
38
+ /**
39
+ * Whether the selector is disabled
40
+ */
41
+ disabled: boolean;
42
+ /**
43
+ * Called when an item is selected
44
+ */
45
+ onSelect: (id: string) => void;
46
+ };
47
+ export type UseSelectorKeyboardReturn = {
48
+ /**
49
+ * Combined keyboard event handler
50
+ */
51
+ keyHandler: (e: React.KeyboardEvent<HTMLElement>) => void;
52
+ };
53
+ /**
54
+ * Hook for Selector keyboard handling.
55
+ *
56
+ * Combines keyboard navigation, type-ahead, and selector-specific behaviors:
57
+ * - Arrow keys, Home/End for navigation
58
+ * - Enter/Space for selection
59
+ * - Escape to close
60
+ * - Tab to select and close
61
+ * - PageUp/PageDown for large jumps
62
+ * - Type-ahead search (single char cycling, multi-char search)
63
+ * - Opens dropdown on navigation keys when closed
64
+ *
65
+ * @param config - Configuration object
66
+ * @returns Object with keyHandler
67
+ */
68
+ export declare function useSelectorKeyboard(config: UseSelectorKeyboardConfig): UseSelectorKeyboardReturn;
@@ -0,0 +1,76 @@
1
+ export type UseTypeAheadConfig = {
2
+ /**
3
+ * Get all item IDs in order
4
+ */
5
+ getItems: () => string[];
6
+ /**
7
+ * Check if an item is disabled
8
+ */
9
+ isItemDisabled: (id: string) => boolean;
10
+ /**
11
+ * Get the text content for an item (for matching)
12
+ */
13
+ getItemText: (id: string) => string | undefined;
14
+ /**
15
+ * Currently active item ID
16
+ */
17
+ active: string | undefined;
18
+ /**
19
+ * Set the active item ID
20
+ */
21
+ setActive: (id: string | undefined) => void;
22
+ /**
23
+ * Called when a match is found
24
+ */
25
+ onMatch?: (id: string) => void;
26
+ /**
27
+ * Timeout in ms before search string resets
28
+ * @default 500
29
+ */
30
+ timeout?: number;
31
+ };
32
+ export type UseTypeAheadReturn = {
33
+ /**
34
+ * Handle a typed character for type-ahead search
35
+ */
36
+ handleTypeAhead: (char: string) => void;
37
+ /**
38
+ * Reset the search string manually
39
+ */
40
+ resetSearch: () => void;
41
+ };
42
+ /**
43
+ * Hook for type-ahead search in a list of items.
44
+ *
45
+ * Behavior:
46
+ * - Single character: cycles through items starting with that character
47
+ * - Multiple characters typed quickly: finds first match for full string
48
+ * - Case-insensitive matching
49
+ * - Skips disabled items
50
+ * - Resets after timeout (default 500ms)
51
+ *
52
+ * **Important**: The `getItemText` function must return the searchable text for each item.
53
+ * When using complex children (e.g., `<ItemText>{label}</ItemText>`), you must either:
54
+ * 1. Provide a `textValue` prop to register searchable text explicitly
55
+ * 2. Extract text from DOM after mount using `element.textContent`
56
+ *
57
+ * Plain string children work automatically, but wrapped content requires explicit registration.
58
+ *
59
+ * @example
60
+ * ```tsx
61
+ * const { handleTypeAhead } = useTypeAhead({
62
+ * getItems,
63
+ * isItemDisabled,
64
+ * getItemText,
65
+ * active,
66
+ * setActive,
67
+ * onMatch: (id) => console.log('Found:', id),
68
+ * });
69
+ *
70
+ * // In keyboard handler:
71
+ * if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
72
+ * handleTypeAhead(e.key);
73
+ * }
74
+ * ```
75
+ */
76
+ export declare function useTypeAhead(config: UseTypeAheadConfig): UseTypeAheadReturn;
@@ -0,0 +1 @@
1
+ export { type MenuItemOwnProps, MenuPrimitiveItem, MenuPrimitiveItemIndicator, type MenuPrimitiveItemIndicatorProps, type MenuPrimitiveItemProps, MenuPrimitiveLabel, type MenuPrimitiveLabelProps, MenuPrimitiveRadioGroup, type MenuPrimitiveRadioGroupProps, MenuPrimitiveRadioItem, type MenuPrimitiveRadioItemProps, MenuPrimitiveSeparator, type MenuPrimitiveSeparatorProps, type MenuRadioGroupOwnProps, type MenuRadioItemOwnProps, menuItemVariants, menuRadioItemVariants, useRadioGroup, } from './menu-primitive';
@@ -0,0 +1,93 @@
1
+ import { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ export type MenuContextOperations = {
3
+ active: string | undefined;
4
+ setActive: (id: string | undefined) => void;
5
+ setOpen: (open: boolean) => void;
6
+ registerItem: (id: string, disabled?: boolean) => void;
7
+ unregisterItem: (id: string) => void;
8
+ getItems: () => string[];
9
+ isItemDisabled: (id: string) => boolean;
10
+ };
11
+ export declare const menuItemVariants: (props?: ({
12
+ active?: boolean | null | undefined;
13
+ disabled?: boolean | null | undefined;
14
+ } & import('class-variance-authority/types').ClassProp) | undefined) => string;
15
+ /** Public API props for menu items (without context operations) */
16
+ export type MenuItemOwnProps = {
17
+ id?: string;
18
+ asChild?: boolean;
19
+ disabled?: boolean;
20
+ onSelect?: (event: Event) => void;
21
+ className?: string;
22
+ children: ReactNode;
23
+ } & ComponentPropsWithoutRef<'div'>;
24
+ /** Internal primitive props (own props + context operations for dependency injection) */
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
+ }>;
29
+ export type MenuPrimitiveLabelProps = {
30
+ className?: string;
31
+ children: ReactNode;
32
+ } & ComponentPropsWithoutRef<'div'>;
33
+ export declare const MenuPrimitiveLabel: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveLabelProps> & {
34
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
35
+ }>;
36
+ export type MenuPrimitiveSeparatorProps = {
37
+ className?: string;
38
+ } & ComponentPropsWithoutRef<'div'>;
39
+ export declare const MenuPrimitiveSeparator: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveSeparatorProps> & {
40
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
41
+ }>;
42
+ type RadioGroupContextValue = {
43
+ value: string | undefined;
44
+ setValue: (value: string) => void;
45
+ closeOnSelect: boolean;
46
+ setOpen: (open: boolean) => void;
47
+ };
48
+ export declare const useRadioGroup: () => RadioGroupContextValue;
49
+ /** Public API props for radio groups (without setOpen) */
50
+ export type MenuRadioGroupOwnProps = {
51
+ value?: string;
52
+ defaultValue?: string;
53
+ onValueChange?: (value: string) => void;
54
+ closeOnSelect?: boolean;
55
+ className?: string;
56
+ children?: ReactNode;
57
+ } & ComponentPropsWithoutRef<'div'>;
58
+ /** Internal primitive props (own props + setOpen for dependency injection) */
59
+ export type MenuPrimitiveRadioGroupProps = MenuRadioGroupOwnProps & {
60
+ setOpen: (open: boolean) => void;
61
+ };
62
+ export declare const MenuPrimitiveRadioGroup: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveRadioGroupProps> & {
63
+ ref?: import('preact').Ref<HTMLDivElement> | undefined;
64
+ }>;
65
+ export declare const menuRadioItemVariants: (props?: ({
66
+ active?: boolean | null | undefined;
67
+ disabled?: boolean | null | undefined;
68
+ checked?: boolean | null | undefined;
69
+ } & import('class-variance-authority/types').ClassProp) | undefined) => string;
70
+ /** Public API props for radio items (without context operations) */
71
+ export type MenuRadioItemOwnProps = {
72
+ id?: string;
73
+ value: string;
74
+ asChild?: boolean;
75
+ disabled?: boolean;
76
+ onSelect?: (event: Event) => void;
77
+ className?: string;
78
+ children: ReactNode;
79
+ } & ComponentPropsWithoutRef<'div'>;
80
+ /** Internal primitive props (own props + context operations except setOpen, which comes from RadioGroup) */
81
+ 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
+ }>;
85
+ export type MenuPrimitiveItemIndicatorProps = {
86
+ forceMount?: boolean;
87
+ className?: string;
88
+ children?: ReactNode;
89
+ } & ComponentPropsWithoutRef<'span'>;
90
+ export declare const MenuPrimitiveItemIndicator: import('preact').FunctionalComponent<import('preact/compat').PropsWithoutRef<MenuPrimitiveItemIndicatorProps> & {
91
+ ref?: import('preact').Ref<HTMLSpanElement> | undefined;
92
+ }>;
93
+ export {};