@primitiv-ui/core 0.1.52 → 0.1.54

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,8 @@
1
+ /**
2
+ * Elements that can receive keyboard focus and take part in the natural tab
3
+ * order. Two consumers rely on it: moving initial focus into a surface when it
4
+ * opens (e.g. `Popover`), and locating the boundary elements of a focus trap
5
+ * (e.g. `Modal`). `[tabindex="-1"]` is excluded because such elements are only
6
+ * programmatically focusable, not tabbable.
7
+ */
8
+ export declare const FOCUSABLE_SELECTOR: string;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Elements that can receive keyboard focus and take part in the natural tab
3
+ * order. Two consumers rely on it: moving initial focus into a surface when it
4
+ * opens (e.g. `Popover`), and locating the boundary elements of a focus trap
5
+ * (e.g. `Modal`). `[tabindex="-1"]` is excluded because such elements are only
6
+ * programmatically focusable, not tabbable.
7
+ */
8
+ export const FOCUSABLE_SELECTOR = [
9
+ "a[href]",
10
+ "button:not([disabled])",
11
+ "input:not([disabled])",
12
+ "select:not([disabled])",
13
+ "textarea:not([disabled])",
14
+ '[tabindex]:not([tabindex="-1"])',
15
+ ].join(",");
package/dist/index.d.ts CHANGED
@@ -4,3 +4,5 @@ export { resolveRovingTarget, type ResolveRovingTargetOptions, } from "./keyboar
4
4
  export { tabsClick, tabsKeydown, tabsTabStop, type TabsActivationMode, type TabsKeyOutcome, type TabsOrientation, type TabsReadingDirection, type TabsState, } from "./tabs/policy.js";
5
5
  export { accordionKeydown, accordionToggle, type AccordionKeyOutcome, type AccordionOrientation, type AccordionReadingDirection, type AccordionState, } from "./accordion/policy.js";
6
6
  export { collapsibleKeydown, collapsibleToggle, type CollapsibleKeyOutcome, type CollapsibleState, } from "./collapsible/policy.js";
7
+ export { FOCUSABLE_SELECTOR } from "./focus/focusable.js";
8
+ export { modalPointerOutside, modalTabKeydown, type ModalBox, type ModalTabOutcome, type ModalTabState, } from "./modal/policy.js";
package/dist/index.js CHANGED
@@ -4,3 +4,5 @@ export { resolveRovingTarget, } from "./keyboard/resolveRovingTarget.js";
4
4
  export { tabsClick, tabsKeydown, tabsTabStop, } from "./tabs/policy.js";
5
5
  export { accordionKeydown, accordionToggle, } from "./accordion/policy.js";
6
6
  export { collapsibleKeydown, collapsibleToggle, } from "./collapsible/policy.js";
7
+ export { FOCUSABLE_SELECTOR } from "./focus/focusable.js";
8
+ export { modalPointerOutside, modalTabKeydown, } from "./modal/policy.js";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * What a key press inside an open modal dialog needs to know: the key, whether
3
+ * Shift was held, how many focusable elements the dialog holds, and which of
4
+ * them (by index, in tab order) has focus — `-1` when focus is on none of them.
5
+ */
6
+ export type ModalTabState = {
7
+ key: string;
8
+ shiftKey: boolean;
9
+ /** The index of the focused element among the focusables, or `-1`. */
10
+ active: number;
11
+ /** How many focusable elements the dialog holds. */
12
+ count: number;
13
+ };
14
+ /**
15
+ * What a Tab press inside the dialog asks for: prevent the browser's default
16
+ * and focus the focusable at `focus`, or, when `focus` is `null`, focus
17
+ * nothing (there is nothing inside to focus).
18
+ */
19
+ export type ModalTabOutcome = {
20
+ focus: number | null;
21
+ };
22
+ /**
23
+ * The modal dialog's focus trap (WAI-ARIA APG Modal Dialog). A native modal
24
+ * `<dialog>` makes the page behind it inert but does not wrap: Tab past the last
25
+ * element escapes to the browser chrome. So only the two boundary presses are
26
+ * taken over — Tab on the last focusable moves to the first, Shift+Tab on the
27
+ * first moves to the last — and a dialog with nothing focusable blocks Tab so
28
+ * focus cannot leak out. Every interior press is left to the browser, whose
29
+ * inert-aware order keeps radio groups and the like working.
30
+ *
31
+ * Returns `null` for anything to leave alone (no `preventDefault`).
32
+ */
33
+ export declare function modalTabKeydown({ key, shiftKey, active, count }: ModalTabState): ModalTabOutcome | null;
34
+ /** A box in viewport coordinates, as `getBoundingClientRect()` reports one. */
35
+ export type ModalBox = {
36
+ left: number;
37
+ top: number;
38
+ right: number;
39
+ bottom: number;
40
+ };
41
+ /**
42
+ * Whether a pointer at `point` landed outside the dialog's `box`. A modal
43
+ * `<dialog>` paints its own `::backdrop` in the top layer, so a press on the
44
+ * dimmed page targets the dialog itself; its coordinates are the only way to
45
+ * tell the backdrop from the dialog. The box's edges count as inside.
46
+ */
47
+ export declare function modalPointerOutside(box: ModalBox, point: {
48
+ x: number;
49
+ y: number;
50
+ }): boolean;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The modal dialog's focus trap (WAI-ARIA APG Modal Dialog). A native modal
3
+ * `<dialog>` makes the page behind it inert but does not wrap: Tab past the last
4
+ * element escapes to the browser chrome. So only the two boundary presses are
5
+ * taken over — Tab on the last focusable moves to the first, Shift+Tab on the
6
+ * first moves to the last — and a dialog with nothing focusable blocks Tab so
7
+ * focus cannot leak out. Every interior press is left to the browser, whose
8
+ * inert-aware order keeps radio groups and the like working.
9
+ *
10
+ * Returns `null` for anything to leave alone (no `preventDefault`).
11
+ */
12
+ export function modalTabKeydown({ key, shiftKey, active, count }) {
13
+ if (key !== "Tab")
14
+ return null;
15
+ if (count === 0)
16
+ return { focus: null };
17
+ const last = count - 1;
18
+ if (!shiftKey && active === last)
19
+ return { focus: 0 };
20
+ if (shiftKey && active === 0)
21
+ return { focus: last };
22
+ return null;
23
+ }
24
+ /**
25
+ * Whether a pointer at `point` landed outside the dialog's `box`. A modal
26
+ * `<dialog>` paints its own `::backdrop` in the top layer, so a press on the
27
+ * dimmed page targets the dialog itself; its coordinates are the only way to
28
+ * tell the backdrop from the dialog. The box's edges count as inside.
29
+ */
30
+ export function modalPointerOutside(box, point) {
31
+ return point.x < box.left || point.x > box.right || point.y < box.top || point.y > box.bottom;
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitiv-ui/core",
3
- "version": "0.1.52",
3
+ "version": "0.1.54",
4
4
  "description": "The framework-free behaviour core shared by every Primitiv surface — keyboard maps, roving-focus targets, id derivation and per-component policy, as pure functions with no DOM and no dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",