@lumx/core 4.11.0-next.1 → 4.11.0-next.3

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,11 @@
1
+ /**
2
+ * Like `querySelectorAll`, but also tests the root node itself.
3
+ *
4
+ * Yields the root element first (if it matches), then all matching descendants
5
+ * in document order. Being a generator, callers that only need the first match
6
+ * can break early without collecting the full list.
7
+ *
8
+ * @param node The starting DOM node.
9
+ * @param selector CSS selector to match against.
10
+ */
11
+ export declare function querySelectorInclusive(node: Node, selector: string): Generator<HTMLElement>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Track whether the container currently has focus.
3
+ *
4
+ * We can't rely on `document.activeElement` inside MutationObserver callbacks because
5
+ * the browser moves focus to `<body>` before they fire. focusout with `relatedTarget === null`
6
+ * (element removed from DOM) keeps the flag true so the observer can move focus to a fallback.
7
+ */
8
+ export declare function trackContainerFocus(container: HTMLElement, signal: AbortSignal): {
9
+ readonly hasFocus: boolean;
10
+ reset(): void;
11
+ };
@@ -1,10 +1,15 @@
1
- import type { FocusNavigationCallbacks, FocusNavigationController, ListNavigationOptions } from './types';
1
+ import type { FocusNavigationCallbacks, ListFocusNavigationController, ListNavigationOptions } from './types';
2
2
  /**
3
3
  * Create a focus navigation controller for a 1D list.
4
4
  *
5
- * @param options List navigation options (container, itemSelector, direction, wrap).
5
+ * This controller is **stateless** it does not maintain an internal reference to
6
+ * the active item. Instead it reads the active item from the DOM each time via the
7
+ * `getActiveItem` callback provided in the options. This avoids any desync between
8
+ * the controller's internal state and the actual DOM.
9
+ *
10
+ * @param options List navigation options (container, itemSelector, direction, wrap, getActiveItem).
6
11
  * @param callbacks Callbacks for focus state changes.
7
12
  * @param signal AbortSignal for cleanup.
8
- * @returns FocusNavigationController instance.
13
+ * @returns ListFocusNavigationController instance.
9
14
  */
10
- export declare function createListFocusNavigation(options: ListNavigationOptions, callbacks: FocusNavigationCallbacks, signal: AbortSignal): FocusNavigationController;
15
+ export declare function createListFocusNavigation(options: ListNavigationOptions, callbacks: FocusNavigationCallbacks, signal: AbortSignal): ListFocusNavigationController;
@@ -1,4 +1,4 @@
1
- export type { FocusNavigationCallbacks, FocusNavigationController, GridNavigationOptions, ListNavigationOptions, NavigationOptions, } from './types';
1
+ export type { FocusNavigationCallbacks, FocusNavigationController, GridNavigationOptions, ListFocusNavigationController, ListNavigationOptions, NavigationOptions, } from './types';
2
2
  export type { RovingTabIndexOptions } from './setupRovingTabIndex';
3
3
  export { createListFocusNavigation } from './createListFocusNavigation';
4
4
  export { createGridFocusNavigation } from './createGridFocusNavigation';
@@ -1,42 +1,37 @@
1
1
  import type { FocusNavigationController } from './types';
2
- /**
3
- * Options for the roving tabindex setup.
4
- */
2
+ /** Options for {@link setupRovingTabIndex}. */
5
3
  export interface RovingTabIndexOptions {
6
- /** The container element holding the focusable items. */
4
+ /** Container element holding the focusable items. */
7
5
  container: HTMLElement;
8
- /** CSS selector to identify focusable items within the container. */
6
+ /** CSS selector identifying focusable items within the container. */
9
7
  itemSelector: string;
10
8
  /**
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.
9
+ * Navigation axis — determines which arrow keys navigate.
10
+ * - `'horizontal'` (default): Left/Right navigate.
11
+ * - `'vertical'`: Up/Down navigate.
14
12
  */
15
13
  direction?: 'horizontal' | 'vertical';
14
+ /** CSS selector matching disabled items (skipped during navigation). */
15
+ itemDisabledSelector?: string;
16
16
  /**
17
- * CSS selector matching disabled items within the container.
18
- * Disabled items are skipped during keyboard navigation.
17
+ * Attribute name indicating the selected item (e.g. `'aria-selected'`, `'aria-checked'`).
18
+ * When set, the roving tabindex will observe changes to this attribute and keep
19
+ * `tabindex="0"` in sync with the item whose attribute value is `"true"`.
20
+ * Default: `'aria-selected'`.
19
21
  */
20
- itemDisabledSelector?: string;
21
- /** Callback invoked when an item receives focus via keyboard navigation. */
22
+ itemSelectedAttr?: string;
23
+ /** Called when an item receives focus via keyboard navigation. */
22
24
  onItemFocused?: (item: HTMLElement) => void;
23
25
  }
24
26
  /**
25
27
  * Set up the roving tabindex pattern on a container element.
26
28
  *
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.
29
+ * - Keyboard navigation (Arrow keys, Home, End)
30
+ * - tabindex management (`0` on active, `-1` on others)
31
+ * - Mount normalization (single-tabstop invariant)
32
+ * - DOM mutation handling via MutationObserver:
33
+ * removal recovery, insertion normalization, disabled-state changes
37
34
  *
38
- * @param options Roving tabindex configuration.
39
- * @param signal AbortSignal for teardown.
40
- * @returns The underlying {@link FocusNavigationController} for programmatic access.
35
+ * Torn down when `signal` is aborted.
41
36
  */
42
37
  export declare function setupRovingTabIndex(options: RovingTabIndexOptions, signal: AbortSignal): FocusNavigationController;
@@ -33,11 +33,18 @@ export interface ListNavigationOptions {
33
33
  */
34
34
  itemDisabledSelector?: string;
35
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).
36
+ * Callback returning the currently active item from the DOM.
37
+ *
38
+ * The list navigation controller does **not** maintain an internal active-item
39
+ * reference; instead it delegates to this callback every time it needs the current
40
+ * active item (e.g. before navigating by offset).
41
+ *
42
+ * The callback is expected to read from the DOM (e.g. query `[tabindex="0"]` for
43
+ * roving tabindex, or read `aria-activedescendant` for combobox patterns).
44
+ *
45
+ * Default: `() => null` (no active item).
39
46
  */
40
- itemActiveSelector?: string;
47
+ getActiveItem?: () => HTMLElement | null;
41
48
  }
42
49
  /** Options for 2D grid navigation. */
43
50
  export interface GridNavigationOptions {
@@ -100,3 +107,20 @@ export interface FocusNavigationController {
100
107
  /** Navigate right (next item in horizontal list, next cell in grid). */
101
108
  goRight(): boolean;
102
109
  }
110
+ /**
111
+ * Extended controller for 1D list navigation.
112
+ * Adds list-specific methods that don't apply to grid navigation.
113
+ */
114
+ export interface ListFocusNavigationController extends FocusNavigationController {
115
+ /** Combined CSS selector matching enabled (non-disabled) items. */
116
+ readonly enabledItemSelector: string;
117
+ /**
118
+ * Find the nearest enabled item to the given anchor node.
119
+ * The anchor does not need to be an item or even an HTMLElement — it's used
120
+ * as a positional reference to find the closest enabled item in DOM order.
121
+ * Prefers the next item; falls back to the previous item.
122
+ *
123
+ * @returns The nearest enabled item, or null if no enabled items exist.
124
+ */
125
+ findNearestEnabled(anchor: Node): HTMLElement | null;
126
+ }
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "dependencies": {
9
9
  "@floating-ui/dom": "^1.7.5",
10
- "@lumx/icons": "^4.11.0-next.1",
10
+ "@lumx/icons": "^4.11.0-next.3",
11
11
  "classnames": "^2.3.2",
12
12
  "focus-visible": "^5.0.2",
13
13
  "lodash": "4.18.1",
@@ -69,7 +69,7 @@
69
69
  "update-version-changelog": "yarn version-changelog ../../CHANGELOG.md"
70
70
  },
71
71
  "sideEffects": false,
72
- "version": "4.11.0-next.1",
72
+ "version": "4.11.0-next.3",
73
73
  "devDependencies": {
74
74
  "@rollup/plugin-typescript": "^12.3.0",
75
75
  "@testing-library/dom": "^10.4.1",