@primitiv-ui/core 0.1.55 → 0.1.57

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/dist/index.d.ts CHANGED
@@ -5,5 +5,8 @@ export { tabsClick, tabsKeydown, tabsTabStop, type TabsActivationMode, type Tabs
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
7
  export { FOCUSABLE_SELECTOR } from "./focus/focusable.js";
8
+ export { MENU_ITEM_SELECTOR, MENU_TYPEAHEAD_RESET_MS, menuKeydown, menuTypeahead, type MenuKeyOutcome, type MenuKeyState, type MenuTypeaheadOutcome, type MenuTypeaheadState, } from "./menu/policy.js";
8
9
  export { modalPointerOutside, modalTabKeydown, type ModalBox, type ModalTabOutcome, type ModalTabState, } from "./modal/policy.js";
10
+ export { panelPlacement, type PanelPlacement, type PlacementBox } from "./placement/placement.js";
9
11
  export { popoverKeydown, popoverPressDismisses, type PopoverKeyOutcome, type PopoverPress, } from "./popover/policy.js";
12
+ export { TOOLTIP_GRACE_MS, tooltipOpenDelay, type TooltipDelayState } from "./tooltip/policy.js";
package/dist/index.js CHANGED
@@ -5,5 +5,8 @@ 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
7
  export { FOCUSABLE_SELECTOR } from "./focus/focusable.js";
8
+ export { MENU_ITEM_SELECTOR, MENU_TYPEAHEAD_RESET_MS, menuKeydown, menuTypeahead, } from "./menu/policy.js";
8
9
  export { modalPointerOutside, modalTabKeydown, } from "./modal/policy.js";
10
+ export { panelPlacement } from "./placement/placement.js";
9
11
  export { popoverKeydown, popoverPressDismisses, } from "./popover/policy.js";
12
+ export { TOOLTIP_GRACE_MS, tooltipOpenDelay } from "./tooltip/policy.js";
@@ -0,0 +1,61 @@
1
+ /** A menu's enabled items, as the selector a host finds them with: every item role, bar the disabled. */
2
+ export declare const MENU_ITEM_SELECTOR: string;
3
+ /** A key press inside an open menu, with where focus is among its enabled items. */
4
+ export type MenuKeyState = {
5
+ /** The key pressed. */
6
+ key: string;
7
+ /** The index of the focused item, or `-1` when focus is on none of them. */
8
+ active: number;
9
+ /** How many enabled items the menu holds. */
10
+ count: number;
11
+ };
12
+ /** What a key press inside a menu asks for. */
13
+ export type MenuKeyOutcome =
14
+ /** Move focus to the item at this index. */
15
+ {
16
+ focus: number;
17
+ }
18
+ /** Activate (click) the item at this index. */
19
+ | {
20
+ activate: number;
21
+ }
22
+ /** Close the menu (and the caller hands focus back to the trigger). */
23
+ | {
24
+ open: false;
25
+ };
26
+ /**
27
+ * The menu's keyboard contract (WAI-ARIA APG Menu Button): the arrows move
28
+ * through the items and wrap, Home and End jump to the ends, Enter and Space
29
+ * activate the focused item, and Escape closes. Returns `null` for any other
30
+ * key, and for every key in a menu with no enabled items, which tells the
31
+ * caller to leave the event alone. Typeahead is {@link menuTypeahead}'s.
32
+ */
33
+ export declare function menuKeydown({ key, active, count }: MenuKeyState): MenuKeyOutcome | null;
34
+ /** How long, in milliseconds, a typeahead query lasts after the last key before it starts afresh. */
35
+ export declare const MENU_TYPEAHEAD_RESET_MS: number;
36
+ /** A key press to search a menu's items by, with the query typed so far. */
37
+ export type MenuTypeaheadState = {
38
+ /** The query typed so far, lower-cased; empty once it has lapsed. */
39
+ query: string;
40
+ /** The key pressed. */
41
+ key: string;
42
+ /** Each enabled item's text, in order. */
43
+ labels: readonly string[];
44
+ /** The index of the focused item, or `-1` when focus is on none of them. */
45
+ active: number;
46
+ };
47
+ /** The query after a typeahead key, and the item to focus (`null` when none matches). */
48
+ export type MenuTypeaheadOutcome = {
49
+ query: string;
50
+ focus: number | null;
51
+ };
52
+ /**
53
+ * Typeahead: a printable key extends the query and focuses the first item
54
+ * whose text starts with it. A single letter, or one letter repeated, starts
55
+ * after the focused item so repeating it cycles through the matches; a longer
56
+ * query may match the focused item itself. Returns `null` for a key that is
57
+ * not a printable character (Space activates), which tells the caller to leave
58
+ * the event alone. The caller lapses the query after
59
+ * {@link MENU_TYPEAHEAD_RESET_MS}.
60
+ */
61
+ export declare function menuTypeahead({ query, key, labels, active }: MenuTypeaheadState): MenuTypeaheadOutcome | null;
@@ -0,0 +1,55 @@
1
+ /** A menu's enabled items, as the selector a host finds them with: every item role, bar the disabled. */
2
+ export const MENU_ITEM_SELECTOR = '[role="menuitem"]:not([aria-disabled="true"]), [role="menuitemcheckbox"]:not([aria-disabled="true"]), [role="menuitemradio"]:not([aria-disabled="true"])';
3
+ /**
4
+ * The menu's keyboard contract (WAI-ARIA APG Menu Button): the arrows move
5
+ * through the items and wrap, Home and End jump to the ends, Enter and Space
6
+ * activate the focused item, and Escape closes. Returns `null` for any other
7
+ * key, and for every key in a menu with no enabled items, which tells the
8
+ * caller to leave the event alone. Typeahead is {@link menuTypeahead}'s.
9
+ */
10
+ export function menuKeydown({ key, active, count }) {
11
+ if (count === 0)
12
+ return null;
13
+ switch (key) {
14
+ case "ArrowDown":
15
+ return { focus: (active + 1) % count };
16
+ case "ArrowUp":
17
+ return { focus: active <= 0 ? count - 1 : active - 1 };
18
+ case "Home":
19
+ return { focus: 0 };
20
+ case "End":
21
+ return { focus: count - 1 };
22
+ case "Enter":
23
+ case " ":
24
+ return active < 0 ? null : { activate: active };
25
+ case "Escape":
26
+ return { open: false };
27
+ default:
28
+ return null;
29
+ }
30
+ }
31
+ /** How long, in milliseconds, a typeahead query lasts after the last key before it starts afresh. */
32
+ export const MENU_TYPEAHEAD_RESET_MS = 500;
33
+ /**
34
+ * Typeahead: a printable key extends the query and focuses the first item
35
+ * whose text starts with it. A single letter, or one letter repeated, starts
36
+ * after the focused item so repeating it cycles through the matches; a longer
37
+ * query may match the focused item itself. Returns `null` for a key that is
38
+ * not a printable character (Space activates), which tells the caller to leave
39
+ * the event alone. The caller lapses the query after
40
+ * {@link MENU_TYPEAHEAD_RESET_MS}.
41
+ */
42
+ export function menuTypeahead({ query, key, labels, active }) {
43
+ if (key.length !== 1 || key === " ")
44
+ return null;
45
+ const next = (query + key).toLowerCase();
46
+ const repeated = [...next].every((char) => char === next[0]);
47
+ const search = repeated ? next[0] : next;
48
+ const start = Math.max(active, 0) + (search.length === 1 ? 1 : 0);
49
+ for (let i = 0; i < labels.length; i++) {
50
+ const index = (start + i) % labels.length;
51
+ if (labels[index].trim().toLowerCase().startsWith(search))
52
+ return { query: next, focus: index };
53
+ }
54
+ return { query: next, focus: null };
55
+ }
@@ -0,0 +1,25 @@
1
+ /** A box's edges in viewport coordinates, as `getBoundingClientRect()` gives them. */
2
+ export type PlacementBox = {
3
+ left: number;
4
+ top: number;
5
+ right: number;
6
+ bottom: number;
7
+ };
8
+ /** Where an anchored panel landed. */
9
+ export type PanelPlacement = {
10
+ /** The side of the anchor the panel is on. */
11
+ side: "top" | "right" | "bottom" | "left";
12
+ /**
13
+ * The third of the panel's facing edge that the anchor's centre is in:
14
+ * `left` / `center` / `right` on a top or bottom panel, `top` / `center` /
15
+ * `bottom` on a left or right one. An arrow placed there points at the anchor.
16
+ */
17
+ align: "left" | "center" | "right" | "top" | "bottom";
18
+ };
19
+ /**
20
+ * Where a panel positioned against an anchor actually landed — after the
21
+ * browser's `position-try-fallbacks` have had their say, so a host can point
22
+ * an arrow at the anchor whichever way the panel flipped. The side is the one
23
+ * the panel stands furthest out on.
24
+ */
25
+ export declare function panelPlacement(panel: PlacementBox, anchor: PlacementBox): PanelPlacement;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Where a panel positioned against an anchor actually landed — after the
3
+ * browser's `position-try-fallbacks` have had their say, so a host can point
4
+ * an arrow at the anchor whichever way the panel flipped. The side is the one
5
+ * the panel stands furthest out on.
6
+ */
7
+ export function panelPlacement(panel, anchor) {
8
+ const out = {
9
+ top: anchor.top - panel.bottom,
10
+ bottom: panel.top - anchor.bottom,
11
+ left: anchor.left - panel.right,
12
+ right: panel.left - anchor.right,
13
+ };
14
+ const side = Object.keys(out).reduce((best, key) => (out[key] > out[best] ? key : best));
15
+ const vertical = side === "top" || side === "bottom";
16
+ const [start, end, centre] = vertical
17
+ ? [panel.left, panel.right, (anchor.left + anchor.right) / 2]
18
+ : [panel.top, panel.bottom, (anchor.top + anchor.bottom) / 2];
19
+ const third = (centre - start) / (end - start);
20
+ const align = third < 1 / 3 ? (vertical ? "left" : "top") : third > 2 / 3 ? (vertical ? "right" : "bottom") : "center";
21
+ return { side, align };
22
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * How long the pointer has, after leaving a tooltip's trigger, to cross onto
3
+ * the tooltip itself before it closes — so hoverable content can be reached.
4
+ */
5
+ export declare const TOOLTIP_GRACE_MS: number;
6
+ /** What the open delay depends on. */
7
+ export type TooltipDelayState = {
8
+ /** Whether a tooltip in the same group closed within the group's skip-delay window. */
9
+ groupOpen: boolean;
10
+ /** The delay before opening on hover, in milliseconds. */
11
+ delay: number;
12
+ };
13
+ /**
14
+ * The delay before a hovered tooltip opens: its own delay, or none while the
15
+ * group is still in its skip-delay window — so moving along a row of triggers
16
+ * opens each next tooltip at once rather than waiting every time.
17
+ */
18
+ export declare function tooltipOpenDelay({ groupOpen, delay }: TooltipDelayState): number;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * How long the pointer has, after leaving a tooltip's trigger, to cross onto
3
+ * the tooltip itself before it closes — so hoverable content can be reached.
4
+ */
5
+ export const TOOLTIP_GRACE_MS = 100;
6
+ /**
7
+ * The delay before a hovered tooltip opens: its own delay, or none while the
8
+ * group is still in its skip-delay window — so moving along a row of triggers
9
+ * opens each next tooltip at once rather than waiting every time.
10
+ */
11
+ export function tooltipOpenDelay({ groupOpen, delay }) {
12
+ return groupOpen ? 0 : delay;
13
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primitiv-ui/core",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
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",