@lumx/core 4.8.1 → 4.9.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components-and-utils.css +36 -4
- package/js/components/Combobox/ComboboxButton.d.ts +54 -0
- package/js/components/Combobox/ComboboxInput.d.ts +49 -0
- package/js/components/Combobox/ComboboxList.d.ts +47 -0
- package/js/components/Combobox/ComboboxOption.d.ts +72 -0
- package/js/components/Combobox/ComboboxOptionAction.d.ts +35 -0
- package/js/components/Combobox/ComboboxOptionMoreInfo.d.ts +52 -0
- package/js/components/Combobox/ComboboxOptionSkeleton.d.ts +41 -0
- package/js/components/Combobox/ComboboxPopover.d.ts +50 -0
- package/js/components/Combobox/ComboboxSection.d.ts +58 -0
- package/js/components/Combobox/ComboboxState.d.ts +84 -0
- package/js/components/Combobox/index.d.ts +23 -0
- package/js/components/Combobox/setupCombobox.d.ts +24 -0
- package/js/components/Combobox/setupComboboxButton.d.ts +16 -0
- package/js/components/Combobox/setupComboboxInput.d.ts +29 -0
- package/js/components/Combobox/setupListbox.d.ts +21 -0
- package/js/components/Combobox/types.d.ts +124 -0
- package/js/components/Combobox/utils.d.ts +27 -0
- package/js/components/List/ListItem.d.ts +58 -0
- package/js/components/List/ListItemAction.d.ts +24 -0
- package/js/components/List/index.d.ts +39 -0
- package/js/components/Tabs/Tests.d.ts +11 -0
- package/js/components/Tabs/constants.d.ts +4 -0
- package/js/components/Text/index.d.ts +1 -1
- package/js/components/UserBlock/Tests.d.ts +13 -0
- package/js/types/jsx/PropsToOverride.d.ts +1 -1
- package/js/utils/browser/createSelectorTreeWalker.d.ts +13 -0
- package/js/utils/focusNavigation/createActiveItemState.d.ts +26 -0
- package/js/utils/focusNavigation/createGridFocusNavigation.d.ts +13 -0
- package/js/utils/focusNavigation/createListFocusNavigation.d.ts +10 -0
- package/js/utils/focusNavigation/index.d.ts +5 -0
- package/js/utils/focusNavigation/setupRovingTabIndex.d.ts +42 -0
- package/js/utils/focusNavigation/types.d.ts +102 -0
- package/js/utils/typeahead/index.d.ts +29 -0
- package/lumx.css +36 -4
- package/package.json +4 -3
- package/scss/_components_classes.scss +2 -1
- package/scss/components/combobox/_index.scss +44 -0
- package/scss/components/list/_mixins.scss +44 -32
- package/stories/types.d.ts +2 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ComboboxCallbacks, ComboboxHandle } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Set up a combobox with a button trigger (select-only pattern).
|
|
4
|
+
*
|
|
5
|
+
* Creates a full combobox handle with the button-mode controller automatically
|
|
6
|
+
* wired in and the trigger registered. The consumer only needs to call
|
|
7
|
+
* `handle.registerListbox(listbox)`.
|
|
8
|
+
*
|
|
9
|
+
* Handles: Space (select/open), Home/End (listbox navigation),
|
|
10
|
+
* printable characters (typeahead), and click (toggle).
|
|
11
|
+
*
|
|
12
|
+
* @param button The button element to use as the combobox trigger.
|
|
13
|
+
* @param callbacks Callbacks for select and open/close events.
|
|
14
|
+
* @returns A ComboboxHandle for interacting with the combobox.
|
|
15
|
+
*/
|
|
16
|
+
export declare function setupComboboxButton(button: HTMLButtonElement, callbacks: ComboboxCallbacks): ComboboxHandle;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ComboboxCallbacks, ComboboxHandle } from './types';
|
|
2
|
+
/** Options for configuring the input-mode combobox controller. */
|
|
3
|
+
export interface SetupComboboxInputOptions {
|
|
4
|
+
/**
|
|
5
|
+
* When true (default), the combobox automatically filters options as the user types.
|
|
6
|
+
* Each registered `Combobox.Option` receives filter state updates and hides itself
|
|
7
|
+
* when it does not match the current input value.
|
|
8
|
+
*
|
|
9
|
+
* Set to false when you want to handle filtering yourself (e.g. async search,
|
|
10
|
+
* consumer-side pre-filtering). Options will not be registered for auto-filtering.
|
|
11
|
+
*/
|
|
12
|
+
autoFilter?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Set up a combobox with an input trigger (autocomplete/filter pattern).
|
|
16
|
+
*
|
|
17
|
+
* Creates a full combobox handle with the input-mode controller automatically
|
|
18
|
+
* wired in and the trigger registered. The consumer only needs to call
|
|
19
|
+
* `handle.registerListbox(listbox)`.
|
|
20
|
+
*
|
|
21
|
+
* Handles: Home/End (text cursor), ArrowLeft/Right (clear active descendant),
|
|
22
|
+
* filtering (on input and on open), and focus behavior.
|
|
23
|
+
*
|
|
24
|
+
* @param input The input element to use as the combobox trigger.
|
|
25
|
+
* @param callbacks Callbacks for select and open/close events.
|
|
26
|
+
* @param options Options for configuring the input-mode controller.
|
|
27
|
+
* @returns A ComboboxHandle for interacting with the combobox.
|
|
28
|
+
*/
|
|
29
|
+
export declare function setupComboboxInput(input: HTMLInputElement, callbacks: ComboboxCallbacks, options?: SetupComboboxInputOptions): ComboboxHandle;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type FocusNavigationController } from '../../utils/focusNavigation';
|
|
2
|
+
import type { ComboboxEventMap, ComboboxHandle } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Set up focus navigation and delegated event listeners on a listbox element.
|
|
5
|
+
*
|
|
6
|
+
* This merges two concerns:
|
|
7
|
+
* 1. Creating the appropriate {@link FocusNavigationController} (grid or list mode)
|
|
8
|
+
* with callbacks that manage `aria-activedescendant` and visual focus indicators.
|
|
9
|
+
* 2. Attaching delegated `click` and `mousedown` listeners to the listbox for
|
|
10
|
+
* option selection and blur prevention.
|
|
11
|
+
*
|
|
12
|
+
* The caller is responsible for guarding against duplicate calls (i.e., checking
|
|
13
|
+
* that a focus navigation controller does not already exist before calling)
|
|
14
|
+
* and ensuring `handle.trigger` and `handle.listbox` are non-null.
|
|
15
|
+
*
|
|
16
|
+
* @param handle The combobox handle (provides trigger, listbox, select, etc.).
|
|
17
|
+
* @param signal Abort signal used to clean up all attached listeners.
|
|
18
|
+
* @param notify Notify subscribers of combobox events.
|
|
19
|
+
* @returns The created focus navigation controller.
|
|
20
|
+
*/
|
|
21
|
+
export declare function setupListbox(handle: ComboboxHandle, signal: AbortSignal, notify: <K extends keyof ComboboxEventMap>(event: K, value: ComboboxEventMap[K]) => void): FocusNavigationController;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { FocusNavigationController } from '../../utils/focusNavigation';
|
|
2
|
+
/** Section visibility state tracked per registration. */
|
|
3
|
+
export interface SectionState {
|
|
4
|
+
hidden: boolean;
|
|
5
|
+
'aria-hidden': boolean;
|
|
6
|
+
}
|
|
7
|
+
/** Registration entry for a section element. */
|
|
8
|
+
export interface SectionRegistration {
|
|
9
|
+
callback: (state: SectionState) => void;
|
|
10
|
+
last: SectionState;
|
|
11
|
+
}
|
|
12
|
+
/** Registration entry for an option element. */
|
|
13
|
+
export interface OptionRegistration {
|
|
14
|
+
callback: (isFiltered: boolean) => void;
|
|
15
|
+
lastFiltered: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Map of combobox event names to their payload types. */
|
|
18
|
+
export interface ComboboxEventMap {
|
|
19
|
+
/** Fired when the combobox open state changes. Payload: whether the combobox is open. */
|
|
20
|
+
open: boolean;
|
|
21
|
+
/** Fired when the active descendant changes (visual focus). Payload: the option id or null. */
|
|
22
|
+
activeDescendantChange: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* Fired when the visible option count transitions between empty and non-empty.
|
|
25
|
+
* Payload: whether the list is empty plus the current input value.
|
|
26
|
+
*/
|
|
27
|
+
emptyChange: {
|
|
28
|
+
isEmpty?: boolean;
|
|
29
|
+
inputValue?: string;
|
|
30
|
+
} | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Fired immediately when the aggregate loading state changes (skeleton count transitions
|
|
33
|
+
* between 0 and >0). Used for empty suppression in ComboboxState and for aria-busy on the listbox.
|
|
34
|
+
*/
|
|
35
|
+
loadingChange: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Fired after a 500ms debounce when loading persists, or immediately when loading ends.
|
|
38
|
+
* Used to control the loading message text in the live region (ComboboxState).
|
|
39
|
+
*/
|
|
40
|
+
loadingAnnouncement: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** Callbacks provided by the consumer (React/Vue) to react to combobox state changes. */
|
|
43
|
+
export interface ComboboxCallbacks {
|
|
44
|
+
/** Called when an option is selected (click or keyboard). */
|
|
45
|
+
onSelect(option: {
|
|
46
|
+
value: string;
|
|
47
|
+
}): void;
|
|
48
|
+
}
|
|
49
|
+
/** Handle returned by `setupCombobox`. Used by framework wrappers and mode controllers. */
|
|
50
|
+
export interface ComboboxHandle {
|
|
51
|
+
/** Register the trigger element. Returns a cleanup function. */
|
|
52
|
+
registerTrigger(trigger: HTMLInputElement | HTMLButtonElement): () => void;
|
|
53
|
+
/** Register the listbox/grid element. Returns a cleanup function. */
|
|
54
|
+
registerListbox(listbox: HTMLElement): () => void;
|
|
55
|
+
/** Tear down all listeners and state. */
|
|
56
|
+
destroy(): void;
|
|
57
|
+
/** Subscribe to a combobox event. Returns an unsubscribe function. */
|
|
58
|
+
subscribe<K extends keyof ComboboxEventMap>(event: K, callback: (value: ComboboxEventMap[K]) => void): () => void;
|
|
59
|
+
/** The current trigger element (may be null before registration). */
|
|
60
|
+
readonly trigger: HTMLInputElement | HTMLButtonElement | null;
|
|
61
|
+
/** The current listbox/grid element (may be null before registration). */
|
|
62
|
+
readonly listbox: HTMLElement | null;
|
|
63
|
+
/** The focus navigation controller. */
|
|
64
|
+
readonly focusNav: FocusNavigationController | null;
|
|
65
|
+
/** Whether the popup is open. */
|
|
66
|
+
readonly isOpen: boolean;
|
|
67
|
+
/** Whether multi-select mode. */
|
|
68
|
+
readonly isMultiSelect: boolean;
|
|
69
|
+
/** Whether any skeleton placeholders are currently registered (loading). */
|
|
70
|
+
readonly isLoading: boolean;
|
|
71
|
+
/** Set the open state, update ARIA, fire callback. */
|
|
72
|
+
setIsOpen(isOpen: boolean): void;
|
|
73
|
+
/** Select an option (or null to clear), fire callback. */
|
|
74
|
+
select(option: HTMLElement | null): void;
|
|
75
|
+
/**
|
|
76
|
+
* Register an option DOM element for filter notifications.
|
|
77
|
+
* The element's textContent is used as the searchable text.
|
|
78
|
+
* The callback is invoked immediately with the current filter state,
|
|
79
|
+
* and again whenever the filter changes.
|
|
80
|
+
* Returns a cleanup function that unregisters the option.
|
|
81
|
+
*/
|
|
82
|
+
registerOption(element: HTMLElement, onFilterChange: (isFiltered: boolean) => void): () => void;
|
|
83
|
+
/**
|
|
84
|
+
* Set the current filter value and notify all registered options of their match state.
|
|
85
|
+
* Options whose text does not start with the filter value are notified with isFiltered=true.
|
|
86
|
+
* An empty filter value clears filtering (all options become visible).
|
|
87
|
+
*/
|
|
88
|
+
setFilter(filterValue: string): void;
|
|
89
|
+
/**
|
|
90
|
+
* Register a section DOM element for state notifications.
|
|
91
|
+
* The callback is invoked immediately with the current state, and again whenever
|
|
92
|
+
* the state changes after a filter update or option un/registration.
|
|
93
|
+
*
|
|
94
|
+
* - `hidden`: true when all registered options are filtered out (keeps children mounted
|
|
95
|
+
* but invisible to the user and screen readers).
|
|
96
|
+
* - `aria-hidden`: true when the section has no registered options at all (skeleton-only).
|
|
97
|
+
* The section stays visually rendered but is hidden from assistive technology —
|
|
98
|
+
* the live region (`ComboboxState`) handles the loading announcement instead.
|
|
99
|
+
*
|
|
100
|
+
* At most one of `hidden` / `aria-hidden` is true at a time.
|
|
101
|
+
*
|
|
102
|
+
* Returns a cleanup function that unregisters the section.
|
|
103
|
+
*/
|
|
104
|
+
registerSection(element: HTMLElement, onChange: (state: {
|
|
105
|
+
hidden: boolean;
|
|
106
|
+
'aria-hidden': boolean;
|
|
107
|
+
}) => void): () => void;
|
|
108
|
+
/**
|
|
109
|
+
* Register a skeleton placeholder. Increments the internal skeleton counter.
|
|
110
|
+
* When the counter transitions from 0 to >0, fires `loadingChange` immediately
|
|
111
|
+
* and schedules `loadingAnnouncement` after 500ms. Returns a cleanup function
|
|
112
|
+
* that decrements the counter (and fires the reverse transitions when reaching 0).
|
|
113
|
+
*/
|
|
114
|
+
registerSkeleton(): () => void;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Callback invoked when the trigger is attached and the abort controller is ready.
|
|
118
|
+
*
|
|
119
|
+
* The callback can optionally return a mode-specific keydown hook. When returned,
|
|
120
|
+
* it is called before the shared keydown handler; return `true` to indicate the
|
|
121
|
+
* event was handled (the caller will call `stopPropagation`/`preventDefault`)
|
|
122
|
+
* and skip the shared logic.
|
|
123
|
+
*/
|
|
124
|
+
export type OnTriggerAttach = (handle: ComboboxHandle, signal: AbortSignal) => ((event: KeyboardEvent) => boolean) | void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FocusNavigationController } from '../../utils/focusNavigation';
|
|
2
|
+
import type { OptionRegistration, SectionRegistration } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Get the value for a combobox option element.
|
|
5
|
+
* Uses `data-value` when set; falls back to the element's trimmed `textContent`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getOptionValue(option: HTMLElement): string;
|
|
8
|
+
/** Returns true when an option carries aria-disabled="true". */
|
|
9
|
+
export declare function isOptionDisabled(option: HTMLElement): boolean;
|
|
10
|
+
/** Returns true when the cell is NOT the first gridcell in its row (i.e., it's an action cell). */
|
|
11
|
+
export declare function isActionCell(cell: HTMLElement): boolean;
|
|
12
|
+
/** Predicate matching an option element that carries `aria-selected="true"`. */
|
|
13
|
+
export declare const isSelected: (el: Element) => boolean;
|
|
14
|
+
/** Navigate to the selected option, or to the first option if none is selected. */
|
|
15
|
+
export declare function goToSelectedOrFirst(nav: FocusNavigationController): void;
|
|
16
|
+
/** Navigate to the selected option, or to the last option if none is selected. */
|
|
17
|
+
export declare function goToSelectedOrLast(nav: FocusNavigationController): void;
|
|
18
|
+
/**
|
|
19
|
+
* Compute the current state of a section and notify when it changed.
|
|
20
|
+
*
|
|
21
|
+
* Section state:
|
|
22
|
+
* - `hidden`: true when the section has registered options but all are filtered out.
|
|
23
|
+
* - `aria-hidden`: true when the section has no registered options at all (skeleton-only).
|
|
24
|
+
*
|
|
25
|
+
* At most one of `hidden` / `aria-hidden` is true at a time.
|
|
26
|
+
*/
|
|
27
|
+
export declare function notifySection(sectionElement: HTMLElement, sectionRegistrations: Map<HTMLElement, SectionRegistration>, optionRegistrations: Map<HTMLElement, OptionRegistration>, force?: boolean): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Size } from '../../constants';
|
|
2
|
+
import type { CommonRef, GenericProps, HasAriaDisabled, HasClassName, JSXElement, LumxClassName } from '../../types';
|
|
3
|
+
/** ListItem size variants. */
|
|
4
|
+
export type ListItemSize = Extract<Size, 'tiny' | 'regular' | 'big' | 'huge'>;
|
|
5
|
+
/**
|
|
6
|
+
* Defines the props of the component.
|
|
7
|
+
*/
|
|
8
|
+
export interface ListItemProps extends HasClassName, HasAriaDisabled {
|
|
9
|
+
/** A component to be rendered after the content. */
|
|
10
|
+
after?: JSXElement;
|
|
11
|
+
/** A component to be rendered before the content. */
|
|
12
|
+
before?: JSXElement;
|
|
13
|
+
/** Content. */
|
|
14
|
+
children?: JSXElement;
|
|
15
|
+
/** Whether the list item should be highlighted or not. */
|
|
16
|
+
isHighlighted?: boolean;
|
|
17
|
+
/** Whether the component is selected or not. */
|
|
18
|
+
isSelected?: boolean;
|
|
19
|
+
/** Whether link/button is disabled or not. */
|
|
20
|
+
isDisabled?: boolean;
|
|
21
|
+
/** Custom component for the link (can be used to inject router Link). */
|
|
22
|
+
linkAs?: 'a' | any;
|
|
23
|
+
/** Props that will be passed on to the Link. */
|
|
24
|
+
linkProps?: GenericProps;
|
|
25
|
+
/** Reference to the link element. */
|
|
26
|
+
linkRef?: CommonRef;
|
|
27
|
+
/** Size variant. */
|
|
28
|
+
size?: ListItemSize;
|
|
29
|
+
/** ref to the root <li> element */
|
|
30
|
+
ref?: CommonRef;
|
|
31
|
+
/** On click callback. */
|
|
32
|
+
handleClick?: (event: any) => void;
|
|
33
|
+
}
|
|
34
|
+
export type ListItemPropsToOverride = 'after' | 'before' | 'children' | 'handleClick';
|
|
35
|
+
/**
|
|
36
|
+
* Component display name.
|
|
37
|
+
*/
|
|
38
|
+
export declare const COMPONENT_NAME = "ListItem";
|
|
39
|
+
/**
|
|
40
|
+
* Component default class name and class prefix.
|
|
41
|
+
*/
|
|
42
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
43
|
+
/**
|
|
44
|
+
* Component default props.
|
|
45
|
+
*/
|
|
46
|
+
export declare const DEFAULT_PROPS: Partial<ListItemProps>;
|
|
47
|
+
/**
|
|
48
|
+
* ListItem component.
|
|
49
|
+
*
|
|
50
|
+
* @param props Component props.
|
|
51
|
+
* @return JSX element.
|
|
52
|
+
*/
|
|
53
|
+
export declare const ListItem: {
|
|
54
|
+
(props: ListItemProps): import("react").JSX.Element;
|
|
55
|
+
displayName: string;
|
|
56
|
+
className: "lumx-list-item";
|
|
57
|
+
defaultProps: Partial<ListItemProps>;
|
|
58
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HasClassName } from '../../types';
|
|
2
|
+
import { ClickableElement, RawClickableProps } from '../RawClickable';
|
|
3
|
+
/**
|
|
4
|
+
* ListItemAction props.
|
|
5
|
+
*/
|
|
6
|
+
export type ListItemActionProps<E extends ClickableElement = 'button'> = RawClickableProps<E> & HasClassName;
|
|
7
|
+
/**
|
|
8
|
+
* Component display name.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COMPONENT_NAME = "ListItemAction";
|
|
11
|
+
/**
|
|
12
|
+
* Component classname (used by action area CSS pattern).
|
|
13
|
+
*/
|
|
14
|
+
export declare const CLASSNAME = "lumx-action-area__action";
|
|
15
|
+
export declare const DEFAULT_PROPS: Partial<ListItemActionProps>;
|
|
16
|
+
/**
|
|
17
|
+
* ListItemAction component.
|
|
18
|
+
*
|
|
19
|
+
* Renders a button or link with action area classes.
|
|
20
|
+
* When placed as a child of ListItem, it activates the action area pattern:
|
|
21
|
+
* the entire list item becomes visually clickable, while other interactive
|
|
22
|
+
* elements (in `before`/`after` slots) remain independently clickable.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ListItemAction: <E extends ClickableElement = "button">(props: ListItemActionProps<E>) => import("react").JSX.Element;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Size } from '../../constants';
|
|
2
|
+
import type { CommonRef, HasClassName, JSXElement, LumxClassName } from '../../types';
|
|
3
|
+
/** List item padding size. */
|
|
4
|
+
export type ListItemPadding = Extract<Size, 'big' | 'huge'>;
|
|
5
|
+
/**
|
|
6
|
+
* Defines the props of the component.
|
|
7
|
+
*/
|
|
8
|
+
export interface ListProps extends HasClassName {
|
|
9
|
+
/** List content (should be ListItem, ListDivider, etc.). */
|
|
10
|
+
children?: JSXElement;
|
|
11
|
+
/** Item padding size. */
|
|
12
|
+
itemPadding?: ListItemPadding;
|
|
13
|
+
/** ref to the root element */
|
|
14
|
+
ref?: CommonRef;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Component display name.
|
|
18
|
+
*/
|
|
19
|
+
export declare const COMPONENT_NAME = "List";
|
|
20
|
+
/**
|
|
21
|
+
* Component default class name and class prefix.
|
|
22
|
+
*/
|
|
23
|
+
export declare const CLASSNAME: LumxClassName<typeof COMPONENT_NAME>;
|
|
24
|
+
/**
|
|
25
|
+
* Component default props.
|
|
26
|
+
*/
|
|
27
|
+
export declare const DEFAULT_PROPS: Partial<ListProps>;
|
|
28
|
+
/**
|
|
29
|
+
* List component.
|
|
30
|
+
*
|
|
31
|
+
* @param props Component props.
|
|
32
|
+
* @return JSX element.
|
|
33
|
+
*/
|
|
34
|
+
export declare const List: {
|
|
35
|
+
(props: ListProps): import("react").JSX.Element;
|
|
36
|
+
displayName: string;
|
|
37
|
+
className: "lumx-list";
|
|
38
|
+
defaultProps: Partial<ListProps>;
|
|
39
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SetupOptions } from '../../../testing';
|
|
2
|
+
/**
|
|
3
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
4
|
+
*/
|
|
5
|
+
export declare const setup: (propsOverride: any | undefined, { render, ...options }: SetupOptions<any>) => {
|
|
6
|
+
props: any;
|
|
7
|
+
tab: HTMLElement;
|
|
8
|
+
wrapper: Partial<import("../../../testing").SetupResult>;
|
|
9
|
+
};
|
|
10
|
+
declare const _default: (renderOptions: SetupOptions<any>) => void;
|
|
11
|
+
export default _default;
|
|
@@ -69,7 +69,7 @@ export declare const DEFAULT_PROPS: {};
|
|
|
69
69
|
* @param props Component props.
|
|
70
70
|
* @return Common Props
|
|
71
71
|
*/
|
|
72
|
-
export declare const getTextProps: (props: TextProps) => {
|
|
72
|
+
export declare const getTextProps: (props: Omit<TextProps, "as">) => {
|
|
73
73
|
className: string;
|
|
74
74
|
style: {
|
|
75
75
|
accentColor?: import("csstype").Property.AccentColor | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SetupOptions } from '../../../testing';
|
|
2
|
+
/**
|
|
3
|
+
* Mounts the component and returns common DOM elements / data needed in multiple tests further down.
|
|
4
|
+
*/
|
|
5
|
+
export declare const setup: (propsOverride: any | undefined, { render, ...options }: SetupOptions<any>) => {
|
|
6
|
+
props: any;
|
|
7
|
+
userBlock: HTMLElement;
|
|
8
|
+
fields: HTMLElement | null;
|
|
9
|
+
div: HTMLElement;
|
|
10
|
+
wrapper: Partial<import("../../../testing").SetupResult>;
|
|
11
|
+
};
|
|
12
|
+
declare const _default: (renderOptions: SetupOptions<any>) => void;
|
|
13
|
+
export default _default;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** list of generic props defined on JSX that need to be redefined for each framework */
|
|
2
|
-
export type PropsToOverride = 'ref' | 'handleClick' | 'handleChange' | 'handleKeyPress' | 'handleClose' | 'handleBeforeClick' | 'handleAfterClick' | 'handleKeyDown';
|
|
2
|
+
export type PropsToOverride = 'ref' | 'handleClick' | 'handleChange' | 'handleKeyPress' | 'handleClose' | 'handleFocus' | 'handleBeforeClick' | 'handleAfterClick' | 'handleMouseEnter' | 'handleMouseLeave' | 'handleKeyDown';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a TreeWalker that iterates over elements matching a CSS selector
|
|
3
|
+
* within a container.
|
|
4
|
+
*
|
|
5
|
+
* Uses `NodeFilter.SHOW_ELEMENT` and accepts nodes that match the given
|
|
6
|
+
* selector, skipping everything else. The returned walker can be used with
|
|
7
|
+
* `nextNode()` / `previousNode()` for lazy, sequential DOM traversal.
|
|
8
|
+
*
|
|
9
|
+
* @param container The root element to walk within.
|
|
10
|
+
* @param selector CSS selector that items must match.
|
|
11
|
+
* @returns A TreeWalker scoped to the container and filtered by the selector.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createSelectorTreeWalker(container: HTMLElement, selector: string): TreeWalker;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { FocusNavigationCallbacks } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Internal state for tracking the active (focused) item.
|
|
4
|
+
* Shared by both list and grid navigation implementations.
|
|
5
|
+
*/
|
|
6
|
+
export interface ActiveItemState {
|
|
7
|
+
/** Get the currently active item. */
|
|
8
|
+
readonly active: HTMLElement | null;
|
|
9
|
+
/** Set the active item (handles deactivate/activate callbacks). */
|
|
10
|
+
setActive(item: HTMLElement | null): void;
|
|
11
|
+
/** Clear the active item (deactivate + clear callbacks). */
|
|
12
|
+
clear(): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Create shared active item state with cleanup on abort.
|
|
16
|
+
*
|
|
17
|
+
* Callback invocation:
|
|
18
|
+
* - `setActive(item)`: calls `onDeactivate(previous)` then `onActivate(item)`.
|
|
19
|
+
* - `clear()`: calls `onDeactivate(current)` then `onClear()`.
|
|
20
|
+
* - On `signal.abort()`: same as `clear()`.
|
|
21
|
+
*
|
|
22
|
+
* @param callbacks Focus state change callbacks.
|
|
23
|
+
* @param signal AbortSignal for cleanup.
|
|
24
|
+
* @param initialItem Optional item to silently pre-select on creation (no callbacks fired).
|
|
25
|
+
*/
|
|
26
|
+
export declare function createActiveItemState(callbacks: FocusNavigationCallbacks, signal: AbortSignal, initialItem?: HTMLElement | null): ActiveItemState;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FocusNavigationCallbacks, FocusNavigationController, GridNavigationOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a focus navigation controller for a 2D grid.
|
|
4
|
+
*
|
|
5
|
+
* Supports Up/Down between rows (with column memory) and Left/Right between cells
|
|
6
|
+
* (with wrapping across rows).
|
|
7
|
+
*
|
|
8
|
+
* @param options Grid navigation options (container, rowSelector, cellSelector, isRowVisible, wrap).
|
|
9
|
+
* @param callbacks Callbacks for focus state changes.
|
|
10
|
+
* @param signal AbortSignal for cleanup.
|
|
11
|
+
* @returns FocusNavigationController instance.
|
|
12
|
+
*/
|
|
13
|
+
export declare function createGridFocusNavigation(options: GridNavigationOptions, callbacks: FocusNavigationCallbacks, signal: AbortSignal): FocusNavigationController;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FocusNavigationCallbacks, FocusNavigationController, ListNavigationOptions } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a focus navigation controller for a 1D list.
|
|
4
|
+
*
|
|
5
|
+
* @param options List navigation options (container, itemSelector, direction, wrap).
|
|
6
|
+
* @param callbacks Callbacks for focus state changes.
|
|
7
|
+
* @param signal AbortSignal for cleanup.
|
|
8
|
+
* @returns FocusNavigationController instance.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createListFocusNavigation(options: ListNavigationOptions, callbacks: FocusNavigationCallbacks, signal: AbortSignal): FocusNavigationController;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { FocusNavigationCallbacks, FocusNavigationController, GridNavigationOptions, ListNavigationOptions, NavigationOptions, } from './types';
|
|
2
|
+
export type { RovingTabIndexOptions } from './setupRovingTabIndex';
|
|
3
|
+
export { createListFocusNavigation } from './createListFocusNavigation';
|
|
4
|
+
export { createGridFocusNavigation } from './createGridFocusNavigation';
|
|
5
|
+
export { setupRovingTabIndex } from './setupRovingTabIndex';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { FocusNavigationController } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Options for the roving tabindex setup.
|
|
4
|
+
*/
|
|
5
|
+
export interface RovingTabIndexOptions {
|
|
6
|
+
/** The container element holding the focusable items. */
|
|
7
|
+
container: HTMLElement;
|
|
8
|
+
/** CSS selector to identify focusable items within the container. */
|
|
9
|
+
itemSelector: string;
|
|
10
|
+
/**
|
|
11
|
+
* Primary navigation axis — determines which arrow keys navigate.
|
|
12
|
+
* - `'horizontal'` (default): Left/Right navigate, Up/Down are no-ops.
|
|
13
|
+
* - `'vertical'`: Up/Down navigate, Left/Right are no-ops.
|
|
14
|
+
*/
|
|
15
|
+
direction?: 'horizontal' | 'vertical';
|
|
16
|
+
/**
|
|
17
|
+
* CSS selector matching disabled items within the container.
|
|
18
|
+
* Disabled items are skipped during keyboard navigation.
|
|
19
|
+
*/
|
|
20
|
+
itemDisabledSelector?: string;
|
|
21
|
+
/** Callback invoked when an item receives focus via keyboard navigation. */
|
|
22
|
+
onItemFocused?: (item: HTMLElement) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Set up the roving tabindex pattern on a container element.
|
|
26
|
+
*
|
|
27
|
+
* Handles:
|
|
28
|
+
* - Keyboard navigation (Arrow keys, Home, End) via a keydown listener on the container
|
|
29
|
+
* - tabindex management on focus changes (`0` on active, `-1` on inactive)
|
|
30
|
+
* - Calling `.focus()` on the newly active item
|
|
31
|
+
*
|
|
32
|
+
* The consumer is responsible for setting the initial tabindex values on items
|
|
33
|
+
* (`tabindex="0"` on the active item, `tabindex="-1"` on the rest). On setup, the item
|
|
34
|
+
* with `tabindex="0"` is silently adopted as the initial active item.
|
|
35
|
+
*
|
|
36
|
+
* The setup is torn down when the provided `signal` is aborted.
|
|
37
|
+
*
|
|
38
|
+
* @param options Roving tabindex configuration.
|
|
39
|
+
* @param signal AbortSignal for teardown.
|
|
40
|
+
* @returns The underlying {@link FocusNavigationController} for programmatic access.
|
|
41
|
+
*/
|
|
42
|
+
export declare function setupRovingTabIndex(options: RovingTabIndexOptions, signal: AbortSignal): FocusNavigationController;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Callbacks for focus state changes.
|
|
3
|
+
* The consumer decides how to manifest focus (roving tabindex, aria-activedescendant, etc.).
|
|
4
|
+
*/
|
|
5
|
+
export interface FocusNavigationCallbacks {
|
|
6
|
+
/** Called when an item becomes the active (focused) item. */
|
|
7
|
+
onActivate(item: HTMLElement): void;
|
|
8
|
+
/** Called when an item is no longer the active item (being replaced or cleared). */
|
|
9
|
+
onDeactivate(item: HTMLElement): void;
|
|
10
|
+
/** Called when focus is completely cleared (no active item). */
|
|
11
|
+
onClear?(): void;
|
|
12
|
+
}
|
|
13
|
+
/** Options for 1D list navigation. */
|
|
14
|
+
export interface ListNavigationOptions {
|
|
15
|
+
type: 'list';
|
|
16
|
+
/** The container element to scan for items. */
|
|
17
|
+
container: HTMLElement;
|
|
18
|
+
/** CSS selector to identify navigable items within the container. */
|
|
19
|
+
itemSelector: string;
|
|
20
|
+
/**
|
|
21
|
+
* Primary navigation axis — determines which arrow keys navigate the list.
|
|
22
|
+
* - `'vertical'` (default): Up/Down navigate, Left/Right are no-ops.
|
|
23
|
+
* - `'horizontal'`: Left/Right navigate, Up/Down are no-ops.
|
|
24
|
+
*/
|
|
25
|
+
direction?: 'vertical' | 'horizontal';
|
|
26
|
+
/** Whether navigation wraps at boundaries (last→first, first→last). Default: false. */
|
|
27
|
+
wrap?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* CSS selector matching disabled items within the container.
|
|
30
|
+
* Disabled items are skipped during navigation (goUp/goDown/goLeft/goRight/goToFirst/goToLast)
|
|
31
|
+
* but can still be navigated to directly via `goToItem`.
|
|
32
|
+
* Default: no items are disabled.
|
|
33
|
+
*/
|
|
34
|
+
itemDisabledSelector?: string;
|
|
35
|
+
/**
|
|
36
|
+
* CSS selector identifying the initially active item within the container.
|
|
37
|
+
* If an item matching this selector is found on setup, it becomes the active item
|
|
38
|
+
* without triggering focus or activation callbacks (silent init from DOM state).
|
|
39
|
+
*/
|
|
40
|
+
itemActiveSelector?: string;
|
|
41
|
+
}
|
|
42
|
+
/** Options for 2D grid navigation. */
|
|
43
|
+
export interface GridNavigationOptions {
|
|
44
|
+
type: 'grid';
|
|
45
|
+
/** The container element (grid root) to scan for rows and cells. */
|
|
46
|
+
container: HTMLElement;
|
|
47
|
+
/** CSS selector to identify row elements within the container. */
|
|
48
|
+
rowSelector: string;
|
|
49
|
+
/** CSS selector to identify cell elements within each row. */
|
|
50
|
+
cellSelector: string;
|
|
51
|
+
/**
|
|
52
|
+
* Predicate to determine if a row should be included in navigation.
|
|
53
|
+
* Rows for which this returns `false` are skipped.
|
|
54
|
+
* Default: all rows are visible.
|
|
55
|
+
*/
|
|
56
|
+
isRowVisible?: (row: HTMLElement) => boolean;
|
|
57
|
+
/** Whether navigation wraps at boundaries. Default: false. */
|
|
58
|
+
wrap?: boolean;
|
|
59
|
+
}
|
|
60
|
+
/** Union of all navigation option types. */
|
|
61
|
+
export type NavigationOptions = ListNavigationOptions | GridNavigationOptions;
|
|
62
|
+
/** Focus navigation controller interface — works for both 1D lists and 2D grids. */
|
|
63
|
+
export interface FocusNavigationController {
|
|
64
|
+
/** The navigation structure type. */
|
|
65
|
+
readonly type: 'list' | 'grid';
|
|
66
|
+
/** The currently active item, or null if no item is active. */
|
|
67
|
+
readonly activeItem: HTMLElement | null;
|
|
68
|
+
/** Whether an item is currently active. */
|
|
69
|
+
readonly hasActiveItem: boolean;
|
|
70
|
+
/** Whether there are any navigable items in the container. */
|
|
71
|
+
readonly hasNavigableItems: boolean;
|
|
72
|
+
/** Navigate to the first navigable item. */
|
|
73
|
+
goToFirst(): boolean;
|
|
74
|
+
/** Navigate to the last navigable item. */
|
|
75
|
+
goToLast(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Navigate to a specific item element.
|
|
78
|
+
* @returns true if navigation occurred (item is navigable).
|
|
79
|
+
*/
|
|
80
|
+
goToItem(item: HTMLElement): boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Navigate by offset from the current item.
|
|
83
|
+
* In list mode, moves by items. In grid mode, moves by rows.
|
|
84
|
+
* Positive offsets move forward/down, negative move backward/up.
|
|
85
|
+
*/
|
|
86
|
+
goToOffset(offset: number): boolean;
|
|
87
|
+
/**
|
|
88
|
+
* Navigate to the first item matching a predicate.
|
|
89
|
+
* @returns true if a matching item was found and focused.
|
|
90
|
+
*/
|
|
91
|
+
goToItemMatching(predicate: (item: HTMLElement) => boolean): boolean;
|
|
92
|
+
/** Clear the active item — no item is active after this call. */
|
|
93
|
+
clear(): void;
|
|
94
|
+
/** Navigate up (previous item in vertical list, previous row in grid). */
|
|
95
|
+
goUp(): boolean;
|
|
96
|
+
/** Navigate down (next item in vertical list, next row in grid). */
|
|
97
|
+
goDown(): boolean;
|
|
98
|
+
/** Navigate left (previous item in horizontal list, previous cell in grid). */
|
|
99
|
+
goLeft(): boolean;
|
|
100
|
+
/** Navigate right (next item in horizontal list, next cell in grid). */
|
|
101
|
+
goRight(): boolean;
|
|
102
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/** Typeahead interface. */
|
|
2
|
+
export interface Typeahead {
|
|
3
|
+
/**
|
|
4
|
+
* Handle a printable character keypress.
|
|
5
|
+
* @param key The character typed.
|
|
6
|
+
* @param currentItem The currently active item.
|
|
7
|
+
* @returns The matched item, or null.
|
|
8
|
+
*/
|
|
9
|
+
handle(key: string, currentItem: HTMLElement | null): HTMLElement | null;
|
|
10
|
+
/** Reset the accumulated search string. */
|
|
11
|
+
reset(): void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a typeahead controller for keyboard navigation in list-like widgets
|
|
15
|
+
* (combobox, menu, listbox, tree, etc.).
|
|
16
|
+
*
|
|
17
|
+
* Accumulates typed characters and matches them against item labels.
|
|
18
|
+
* Supports single-char cycling (e.g. pressing "a" repeatedly cycles through
|
|
19
|
+
* items starting with "a") and multi-char prefix matching.
|
|
20
|
+
*
|
|
21
|
+
* Uses a {@link TreeWalker} for lazy DOM traversal — items are visited one
|
|
22
|
+
* at a time without materializing the full list.
|
|
23
|
+
*
|
|
24
|
+
* @param getWalker Callback returning a fresh TreeWalker over navigable items, or null if unavailable.
|
|
25
|
+
* @param getItemValue Callback extracting the text label from an item element.
|
|
26
|
+
* @param signal AbortSignal for cleanup.
|
|
27
|
+
* @returns Typeahead instance.
|
|
28
|
+
*/
|
|
29
|
+
export declare function createTypeahead(getWalker: () => TreeWalker | null, getItemValue: (item: HTMLElement) => string, signal: AbortSignal): Typeahead;
|