haze-ui 1.12.2 → 1.14.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.
Files changed (93) hide show
  1. package/README.md +64 -1
  2. package/dist/components/ColorPicker/ColorPickerCore.js +2 -0
  3. package/dist/components/Combobox/Combobox.js +45 -34
  4. package/dist/components/Command/Command.js +72 -37
  5. package/dist/components/ContextMenu/ContextMenu.js +19 -17
  6. package/dist/components/ContextMenu/ContextMenuContent.js +24 -12
  7. package/dist/components/ContextMenu/ContextMenuItem.js +2 -0
  8. package/dist/components/DateRangePicker/DateRangePickerCore.js +2 -0
  9. package/dist/components/Datepicker/Calendar.js +58 -44
  10. package/dist/components/Datepicker/DatepickerCore.js +37 -29
  11. package/dist/components/DropdownMenu/DropdownMenu.js +20 -20
  12. package/dist/components/DropdownMenu/DropdownMenuContent.js +38 -45
  13. package/dist/components/DropdownMenu/DropdownMenuTrigger.js +9 -7
  14. package/dist/components/InlineEdit/InlineEdit.js +1 -0
  15. package/dist/components/Menu/Menu.js +38 -26
  16. package/dist/components/Menu/MenuItem.js +1 -0
  17. package/dist/components/ModelPicker/ModelPicker.js +1 -0
  18. package/dist/components/OTPInput/OTPInputCore.js +1 -0
  19. package/dist/components/PasswordInput/PasswordInputCore.js +1 -0
  20. package/dist/components/Popover/Popover.js +32 -75
  21. package/dist/components/Progress/Progress.js +2 -0
  22. package/dist/components/Tooltip/Tooltip.js +50 -24
  23. package/dist/components/Tree/TreeItem.js +44 -41
  24. package/dist/components/Upload/Upload.js +1 -0
  25. package/dist/css/combobox.css +2 -1
  26. package/dist/css/command.css +1 -1
  27. package/dist/css/context-menu.css +1 -0
  28. package/dist/css/datepicker.css +3 -2
  29. package/dist/css/dropdown-menu.css +2 -1
  30. package/dist/css/menu.css +2 -1
  31. package/dist/css/popover.css +2 -1
  32. package/dist/css/tooltip.css +2 -1
  33. package/dist/form/FormItem.js +23 -15
  34. package/dist/haze-ui.css +15 -8
  35. package/dist/types/components/Alert/Alert.d.ts +2 -2
  36. package/dist/types/components/Banner/Banner.d.ts +2 -2
  37. package/dist/types/components/BottomSheet/BottomSheet.d.ts +2 -2
  38. package/dist/types/components/Carousel/Carousel.d.ts +2 -2
  39. package/dist/types/components/ChatInput/ChatInput.d.ts +2 -2
  40. package/dist/types/components/Checkbox/Checkbox.d.ts +2 -2
  41. package/dist/types/components/Collapsible/Collapsible.d.ts +2 -2
  42. package/dist/types/components/ColorPicker/ColorPicker.d.ts +2 -2
  43. package/dist/types/components/Combobox/Combobox.d.ts +3 -3
  44. package/dist/types/components/Command/Command.d.ts +2 -2
  45. package/dist/types/components/ConfirmDialog/ConfirmDialog.d.ts +2 -2
  46. package/dist/types/components/ContextMenu/ContextMenu.d.ts +2 -2
  47. package/dist/types/components/ContextMenu/ContextMenuContent.d.ts +1 -1
  48. package/dist/types/components/ContextMenu/ContextMenuContext.d.ts +5 -1
  49. package/dist/types/components/DateRangePicker/DateRangePicker.d.ts +3 -3
  50. package/dist/types/components/Datepicker/Datepicker.d.ts +3 -3
  51. package/dist/types/components/Dialog/Dialog.d.ts +2 -2
  52. package/dist/types/components/Disclosure/Disclosure.d.ts +2 -2
  53. package/dist/types/components/Drawer/Drawer.d.ts +2 -2
  54. package/dist/types/components/DropdownMenu/DropdownMenu.d.ts +2 -2
  55. package/dist/types/components/DropdownMenu/DropdownMenuContent.d.ts +1 -1
  56. package/dist/types/components/DropdownMenu/DropdownMenuContext.d.ts +3 -0
  57. package/dist/types/components/DropdownMenu/DropdownMenuTrigger.d.ts +1 -1
  58. package/dist/types/components/InlineEdit/InlineEdit.d.ts +2 -2
  59. package/dist/types/components/Input/Input.d.ts +2 -2
  60. package/dist/types/components/LogViewer/LogViewer.d.ts +2 -2
  61. package/dist/types/components/Menu/Menu.d.ts +2 -2
  62. package/dist/types/components/ModelPicker/ModelPicker.d.ts +2 -2
  63. package/dist/types/components/NumberInput/NumberInput.d.ts +2 -2
  64. package/dist/types/components/OTPInput/OTPInput.d.ts +2 -2
  65. package/dist/types/components/Pagination/Pagination.d.ts +2 -2
  66. package/dist/types/components/PasswordInput/PasswordInput.d.ts +2 -2
  67. package/dist/types/components/Popover/Popover.d.ts +2 -2
  68. package/dist/types/components/Radio/RadioGroup.d.ts +2 -2
  69. package/dist/types/components/Rating/Rating.d.ts +2 -2
  70. package/dist/types/components/Segmented/Segmented.d.ts +2 -2
  71. package/dist/types/components/Select/Select.d.ts +2 -2
  72. package/dist/types/components/Slider/Slider.d.ts +2 -2
  73. package/dist/types/components/Stepper/Stepper.d.ts +2 -2
  74. package/dist/types/components/Switch/Switch.d.ts +2 -2
  75. package/dist/types/components/Tabs/Tabs.d.ts +2 -2
  76. package/dist/types/components/TagInput/TagInput.d.ts +2 -2
  77. package/dist/types/components/Textarea/Textarea.d.ts +2 -2
  78. package/dist/types/components/TimePicker/TimePicker.d.ts +2 -2
  79. package/dist/types/components/Tooltip/Tooltip.d.ts +5 -1
  80. package/dist/types/components/Transfer/Transfer.d.ts +2 -2
  81. package/dist/types/components/Tree/types.d.ts +4 -4
  82. package/dist/types/form/FormItem.d.ts +63 -7
  83. package/dist/types/form/index.d.ts +1 -1
  84. package/dist/types/index.d.ts +2 -1
  85. package/dist/types/utils/floating.d.ts +99 -0
  86. package/dist/types/utils/menuKeyboard.d.ts +43 -0
  87. package/dist/utils/floating.js +181 -0
  88. package/dist/utils/menuKeyboard.js +88 -0
  89. package/package.json +34 -8
  90. package/dist/components/DropdownMenu/useMenuKeyboard.js +0 -52
  91. package/dist/types/components/DropdownMenu/useMenuKeyboard.d.ts +0 -19
  92. package/dist/types/hooks/index.d.ts +0 -1
  93. package/dist/types/utils/index.d.ts +0 -1
@@ -0,0 +1,99 @@
1
+ import type { ComponentProps, CSSProperties, Dispatch, ReactNode, RefObject, SetStateAction } from 'react';
2
+ /** Lazy feature detection so tests can flip between native and fallback. */
3
+ export declare function supportsNativePopover(): boolean;
4
+ /**
5
+ * Anchor positioning requires the popover API first: jsdom's
6
+ * `CSS.supports` returns true for arbitrary declarations, so it must never
7
+ * be trusted on its own.
8
+ */
9
+ export declare function supportsAnchorPositioning(): boolean;
10
+ declare const anchoredPlacements: {
11
+ readonly bottom: import("@linaria/core").LinariaClassName;
12
+ readonly 'bottom-span': import("@linaria/core").LinariaClassName;
13
+ readonly 'bottom-center': import("@linaria/core").LinariaClassName;
14
+ readonly 'bottom-end': import("@linaria/core").LinariaClassName;
15
+ readonly top: import("@linaria/core").LinariaClassName;
16
+ readonly left: import("@linaria/core").LinariaClassName;
17
+ readonly right: import("@linaria/core").LinariaClassName;
18
+ };
19
+ type AnchorablePlacement = keyof typeof anchoredPlacements;
20
+ /**
21
+ * Panel placement relative to the trigger. `point` leaves positioning
22
+ * entirely to the consumer (e.g. ContextMenu at the pointer coordinates).
23
+ */
24
+ export type FloatingPlacement = AnchorablePlacement | 'point';
25
+ type UseFloatingOptions = {
26
+ open: boolean;
27
+ setOpen: Dispatch<SetStateAction<boolean>>;
28
+ /** Element the panel is anchored to; also the outside-close boundary. */
29
+ triggerRef: RefObject<HTMLElement | null>;
30
+ /** The floating panel element. */
31
+ panelRef: RefObject<HTMLElement | null>;
32
+ };
33
+ export type FloatingBehavior = {
34
+ open: boolean;
35
+ native: boolean;
36
+ anchored: boolean;
37
+ /**
38
+ * Whether the panel element is actually shown right now (native path:
39
+ * mirrored from `toggle` events; fallback path: identical to `open`).
40
+ * Differs from `open` across the commit→effect gap: `open` flips when
41
+ * React commits, while the popover only becomes focusable/paintable
42
+ * after the effect calls showPopover(). Consumers that move focus into
43
+ * the panel on open must gate on `shown`, or the focus call lands on a
44
+ * display:none element and is silently dropped (Chromium).
45
+ */
46
+ shown: boolean;
47
+ /** Unique CSS anchor name for this instance (anchored path). */
48
+ anchorName: string;
49
+ /** Inline style for the trigger: declares `anchor-name`. */
50
+ triggerStyle: CSSProperties | undefined;
51
+ /** Spread on the trigger: records pointerdown for race attribution. */
52
+ onTriggerPointerDown: () => void;
53
+ /** Spread as the trigger's click handler: suppression-aware toggle. */
54
+ onTriggerClick: () => void;
55
+ /** Spread on the panel: popover attribute + anchor custom property. */
56
+ panelAttrs: {
57
+ popover: 'auto' | undefined;
58
+ style: CSSProperties | undefined;
59
+ };
60
+ /** Positioning-skeleton entries for the panel's x-class array. */
61
+ panelClasses: (string | false)[];
62
+ triggerRef: RefObject<HTMLElement | null>;
63
+ panelRef: RefObject<HTMLElement | null>;
64
+ };
65
+ export declare function useFloating({ open, setOpen, triggerRef, panelRef, }: UseFloatingOptions): FloatingBehavior;
66
+ /** x-class entries placing the panel relative to the trigger, per tier. */
67
+ export declare function floatingPlacementClasses(behavior: Pick<FloatingBehavior, 'native' | 'anchored'>, placement: FloatingPlacement): (string | false)[];
68
+ /**
69
+ * Tier 2 positioning: place the panel under/over/beside the trigger and
70
+ * re-place on scroll/resize while open. No-op on the anchored and
71
+ * fallback tiers.
72
+ */
73
+ export declare function useFloatingPosition({ behavior, placement, }: {
74
+ behavior: FloatingBehavior;
75
+ placement: FloatingPlacement;
76
+ }): void;
77
+ type FloatingPanelProps = {
78
+ /** Behavior record produced by `useFloating` at the floating root. */
79
+ behavior: FloatingBehavior;
80
+ /**
81
+ * Placement relative to the trigger. Omit (or pass 'point') when the
82
+ * consumer positions the panel itself, e.g. ContextMenu at the pointer.
83
+ */
84
+ placement?: FloatingPlacement;
85
+ /** Consumer visual skin: border, background, shadow, padding. */
86
+ visualClass?: string;
87
+ className?: string;
88
+ children?: ReactNode;
89
+ /** Merged with the anchor custom property the behavior may set. */
90
+ style?: CSSProperties;
91
+ } & Omit<ComponentProps<'div'>, 'className' | 'children' | 'style'>;
92
+ /**
93
+ * The panel element of a floating pair: applies the popover attribute,
94
+ * the anchor custom property and the tier/placement classes, and runs the
95
+ * tier-2 position effect. Everything else (id, role, handlers, ref) is
96
+ * passed through to the underlying div.
97
+ */
98
+ export declare function FloatingPanel({ behavior, placement, visualClass, className, style, children, ...rest }: FloatingPanelProps): import("react").JSX.Element;
99
+ export {};
@@ -0,0 +1,43 @@
1
+ import type { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
2
+ /** Enabled items inside a container, in DOM order. */
3
+ export declare function getEnabledMenuItems(container: HTMLElement | null, selector?: string): HTMLElement[];
4
+ type UseMenuKeyboardOptions = {
5
+ /** Element containing the item children (`[role=menuitem]` by default). */
6
+ menuRef: RefObject<HTMLElement | null>;
7
+ /**
8
+ * Called when the menu should close via keyboard; the caller returns
9
+ * focus to the trigger.
10
+ */
11
+ onClose: () => void;
12
+ /**
13
+ * Item selector for containers that are not a `role="menu"` — e.g. the
14
+ * `[role=option]` list of a Command palette.
15
+ */
16
+ selector?: string;
17
+ };
18
+ /**
19
+ * Keyboard behavior for a roving-tabindex item container (WAI-ARIA menu
20
+ * button / listbox pattern): ↑/↓ move focus (wrapping, skipping disabled
21
+ * items), Home/End jump to the ends, Escape closes (returning focus to
22
+ * the trigger), Tab closes, and printable characters run typeahead.
23
+ */
24
+ export declare function useMenuKeyboard({ menuRef, onClose, selector, }: UseMenuKeyboardOptions): (event: ReactKeyboardEvent) => void;
25
+ type UseRovingTabindexOptions = {
26
+ menuRef: RefObject<HTMLElement | null>;
27
+ /**
28
+ * While false the effect is inert — panels that stay mounted when
29
+ * closed (Menu, Combobox-style listboxes) manage the tab stops only
30
+ * while open.
31
+ */
32
+ active: boolean;
33
+ selector?: string;
34
+ };
35
+ /**
36
+ * Roving tabindex for a menu/listbox container: exactly one item is a tab
37
+ * stop at any time. On activation the current stop is kept (first item
38
+ * initially); a `focusin` listener moves the stop with focus, and a
39
+ * MutationObserver re-syncs when the item set changes (e.g. a filtered
40
+ * Command list).
41
+ */
42
+ export declare function useRovingTabindex({ menuRef, active, selector, }: UseRovingTabindexOptions): void;
43
+ export {};
@@ -0,0 +1,181 @@
1
+
2
+ import { classnames as e } from "./classnames.js";
3
+ /* empty css */
4
+ import { jsx as t } from "react/jsx-runtime";
5
+ import { useCallback as n, useEffect as r, useId as i, useRef as a, useState as o } from "react";
6
+ //#region src/lib/utils/floating.tsx
7
+ var s = 500;
8
+ function c() {
9
+ return typeof HTMLElement < "u" && "popover" in HTMLElement.prototype;
10
+ }
11
+ function l() {
12
+ return c() && typeof CSS < "u" && typeof CSS.supports == "function" && CSS.supports("anchor-name: --haze-floating-probe");
13
+ }
14
+ var u = "haze-floating__floatingNative", d = "haze-floating__floatingAnchored", f = "haze-floating__floatingFixed", p = "haze-floating__floatingHidden", m = "haze-floating__anchoredBottom", h = "haze-floating__anchoredBottomSpan", g = "haze-floating__anchoredBottomCenter", _ = "haze-floating__anchoredBottomEnd", v = "haze-floating__anchoredTop", y = "haze-floating__anchoredLeft", b = "haze-floating__anchoredRight", x = "haze-floating__fallbackBottom", S = x, C = "haze-floating__fallbackBottomCenter", w = "haze-floating__fallbackBottomEnd", T = "haze-floating__fallbackTop", E = "haze-floating__fallbackLeft", D = "haze-floating__fallbackRight", O = "haze-floating__gapBelow", k = "haze-floating__gapAbove", A = "haze-floating__gapBefore", j = "haze-floating__gapAfter", M = {
15
+ bottom: m,
16
+ "bottom-span": h,
17
+ "bottom-center": g,
18
+ "bottom-end": _,
19
+ top: v,
20
+ left: y,
21
+ right: b
22
+ }, N = {
23
+ bottom: x,
24
+ "bottom-span": S,
25
+ "bottom-center": C,
26
+ "bottom-end": w,
27
+ top: T,
28
+ left: E,
29
+ right: D
30
+ }, P = {
31
+ bottom: O,
32
+ "bottom-span": O,
33
+ "bottom-center": O,
34
+ "bottom-end": O,
35
+ top: k,
36
+ left: A,
37
+ right: j
38
+ };
39
+ function F({ open: e, setOpen: t, triggerRef: m, panelRef: h }) {
40
+ let g = `--haze-floating-${i().replace(/[^a-zA-Z0-9]/g, "")}`, _ = c(), v = l(), y = a(!1), [b, x] = o(!1), S = a(null), C = a(-Infinity), w = a(-Infinity);
41
+ r(() => {
42
+ let n = h.current;
43
+ if (!n || !_) return;
44
+ n !== S.current && (S.current = n, y.current = !1, x(!1));
45
+ let r = (n) => {
46
+ let r = n.newState;
47
+ y.current = r === "open", x(y.current), r === "closed" && e ? (performance.now() - C.current < s && (w.current = performance.now()), C.current = -Infinity, t(!1)) : r === "open" && !e && t(!0);
48
+ };
49
+ return n.addEventListener("toggle", r), e && !y.current ? n.showPopover() : !e && y.current && n.hidePopover(), () => n.removeEventListener("toggle", r);
50
+ }, [
51
+ _,
52
+ e,
53
+ t,
54
+ h
55
+ ]), r(() => {
56
+ if (_ || !e) return;
57
+ let n = (e) => e instanceof Node && !!(m.current?.contains(e) || h.current?.contains(e)), r = (e) => {
58
+ n(e.target) || t(!1);
59
+ }, i = (e) => {
60
+ e.key === "Escape" && t(!1);
61
+ };
62
+ return document.addEventListener("pointerdown", r), document.addEventListener("keydown", i), () => {
63
+ document.removeEventListener("pointerdown", r), document.removeEventListener("keydown", i);
64
+ };
65
+ }, [
66
+ _,
67
+ e,
68
+ t,
69
+ m,
70
+ h
71
+ ]);
72
+ let T = n(() => {
73
+ e && (C.current = performance.now());
74
+ }, [e]), E = n(() => {
75
+ if (performance.now() - w.current < s) {
76
+ w.current = -Infinity;
77
+ return;
78
+ }
79
+ t((e) => !e);
80
+ }, [t]);
81
+ return {
82
+ open: e,
83
+ native: _,
84
+ anchored: v,
85
+ shown: _ ? b : e,
86
+ anchorName: g,
87
+ triggerStyle: v ? { anchorName: g } : void 0,
88
+ onTriggerPointerDown: T,
89
+ onTriggerClick: E,
90
+ panelAttrs: {
91
+ popover: _ ? "auto" : void 0,
92
+ style: v ? { "--haze-floating-anchor": g } : void 0
93
+ },
94
+ panelClasses: [
95
+ _ && u,
96
+ _ && v && d,
97
+ _ && !v && f,
98
+ !_ && !e && p
99
+ ],
100
+ triggerRef: m,
101
+ panelRef: h
102
+ };
103
+ }
104
+ function I(e, t) {
105
+ return t === "point" ? [] : [
106
+ e.native && e.anchored && M[t],
107
+ e.native && !e.anchored && P[t],
108
+ !e.native && N[t]
109
+ ];
110
+ }
111
+ function L(e, t) {
112
+ return parseFloat(getComputedStyle(e)[t]) || 0;
113
+ }
114
+ function R(e, t, n) {
115
+ if (n === "point") return;
116
+ let r = t.getBoundingClientRect(), i = e.getBoundingClientRect(), a = r.left + r.width / 2 - i.width / 2, o = r.top + r.height / 2 - i.height / 2;
117
+ switch (n) {
118
+ case "bottom":
119
+ case "bottom-span":
120
+ e.style.top = `${r.bottom + L(e, "marginTop")}px`, e.style.left = `${r.left}px`;
121
+ return;
122
+ case "bottom-center":
123
+ e.style.top = `${r.bottom + L(e, "marginTop")}px`, e.style.left = `${a}px`;
124
+ return;
125
+ case "bottom-end":
126
+ e.style.top = `${r.bottom + L(e, "marginTop")}px`, e.style.left = `${r.right - i.width}px`;
127
+ return;
128
+ case "top":
129
+ e.style.top = `${r.top - i.height - L(e, "marginBottom")}px`, e.style.left = `${a}px`;
130
+ return;
131
+ case "left":
132
+ e.style.top = `${o}px`, e.style.left = `${r.left - i.width - L(e, "marginRight")}px`;
133
+ return;
134
+ case "right":
135
+ e.style.top = `${o}px`, e.style.left = `${r.right + L(e, "marginLeft")}px`;
136
+ return;
137
+ }
138
+ }
139
+ function z({ behavior: e, placement: t }) {
140
+ let { native: n, anchored: i, open: a, triggerRef: o, panelRef: s } = e;
141
+ r(() => {
142
+ if (!n || i || t === "point" || !a) return;
143
+ let e = s.current, r = o.current;
144
+ if (!e || !r) return;
145
+ let c = () => R(e, r, t);
146
+ return c(), window.addEventListener("scroll", c, !0), window.addEventListener("resize", c), () => {
147
+ window.removeEventListener("scroll", c, !0), window.removeEventListener("resize", c);
148
+ };
149
+ }, [
150
+ n,
151
+ i,
152
+ a,
153
+ t,
154
+ o,
155
+ s
156
+ ]);
157
+ }
158
+ function B({ behavior: n, placement: r, visualClass: i, className: a, style: o, children: s, ...c }) {
159
+ z({
160
+ behavior: n,
161
+ placement: r ?? "point"
162
+ });
163
+ let l = n.panelAttrs.style || o ? {
164
+ ...n.panelAttrs.style,
165
+ ...o
166
+ } : void 0;
167
+ return /* @__PURE__ */ t("div", {
168
+ ...c,
169
+ ...n.panelAttrs,
170
+ style: l,
171
+ className: e([
172
+ i,
173
+ ...n.panelClasses,
174
+ ...r ? I(n, r) : [],
175
+ a
176
+ ]),
177
+ children: s
178
+ });
179
+ }
180
+ //#endregion
181
+ export { B as FloatingPanel, I as floatingPlacementClasses, l as supportsAnchorPositioning, c as supportsNativePopover, F as useFloating, z as useFloatingPosition };
@@ -0,0 +1,88 @@
1
+ import { useCallback as e, useEffect as t, useRef as n } from "react";
2
+ //#region src/lib/utils/menuKeyboard.ts
3
+ var r = 500, i = "[role=\"menuitem\"]:not([disabled])";
4
+ function a(e, t = i) {
5
+ return e ? Array.from(e.querySelectorAll(t)) : [];
6
+ }
7
+ function o({ menuRef: i, onClose: o, selector: s }) {
8
+ let c = n(""), l = n(0);
9
+ return t(() => () => window.clearTimeout(l.current), []), e((e) => {
10
+ let t = a(i.current, s);
11
+ if (t.length === 0) return;
12
+ let n = t.indexOf(document.activeElement), u = (e) => t[e]?.focus();
13
+ switch (e.key) {
14
+ case "ArrowDown":
15
+ e.preventDefault(), u((n + 1) % t.length);
16
+ return;
17
+ case "ArrowUp":
18
+ e.preventDefault(), u((n - 1 + t.length) % t.length);
19
+ return;
20
+ case "Home":
21
+ e.preventDefault(), u(0);
22
+ return;
23
+ case "End":
24
+ e.preventDefault(), u(t.length - 1);
25
+ return;
26
+ case "Escape":
27
+ e.preventDefault(), o();
28
+ return;
29
+ case "Tab":
30
+ e.preventDefault(), o();
31
+ return;
32
+ default: {
33
+ if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) return;
34
+ e.preventDefault();
35
+ let i = (c.current + e.key).toLowerCase();
36
+ c.current = i, window.clearTimeout(l.current), l.current = window.setTimeout(() => {
37
+ c.current = "";
38
+ }, r);
39
+ let a = n + 1;
40
+ for (let e = 0; e < t.length; e++) {
41
+ let n = (a + e) % t.length;
42
+ if (t[n].textContent.toLowerCase().startsWith(i)) {
43
+ u(n);
44
+ return;
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }, [
50
+ i,
51
+ o,
52
+ s
53
+ ]);
54
+ }
55
+ function s({ menuRef: e, active: n, selector: r = i }) {
56
+ t(() => {
57
+ let t = e.current;
58
+ if (!n || !t) return;
59
+ let i = () => {
60
+ let e = a(t, r);
61
+ if (e.length === 0) return;
62
+ let n = e.findIndex((e) => e.tabIndex === 0), i = n >= 0 ? n : 0;
63
+ e.forEach((e, t) => {
64
+ e.tabIndex = t === i ? 0 : -1;
65
+ });
66
+ };
67
+ i();
68
+ let o = new MutationObserver(i);
69
+ o.observe(t, {
70
+ childList: !0,
71
+ subtree: !0
72
+ });
73
+ let s = (e) => {
74
+ a(t, r).forEach((t) => {
75
+ t.tabIndex = t === e.target ? 0 : -1;
76
+ });
77
+ };
78
+ return t.addEventListener("focusin", s), () => {
79
+ o.disconnect(), t.removeEventListener("focusin", s);
80
+ };
81
+ }, [
82
+ e,
83
+ n,
84
+ r
85
+ ]);
86
+ }
87
+ //#endregion
88
+ export { a as getEnabledMenuItems, o as useMenuKeyboard, s as useRovingTabindex };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haze-ui",
3
- "version": "1.12.2",
3
+ "version": "1.14.0",
4
4
  "type": "module",
5
5
  "description": "A React UI component library powered by react-use-control and Linaria",
6
6
  "main": "./dist/index.js",
@@ -9,7 +9,8 @@
9
9
  "exports": {
10
10
  ".": {
11
11
  "types": "./dist/types/index.d.ts",
12
- "import": "./dist/index.js"
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.js"
13
14
  },
14
15
  "./styles.css": "./dist/haze-ui.css",
15
16
  "./css/*": "./dist/css/*"
@@ -29,9 +30,11 @@
29
30
  "lint": "eslint --fix src *.mjs",
30
31
  "deploy": "npm run build:demo && gh-pages -d dist",
31
32
  "test": "vitest run --passWithNoTests",
33
+ "test:e2e": "playwright test --config e2e/playwright.config.ts",
32
34
  "test:watch": "vitest",
33
35
  "prepublishOnly": "npm run build",
34
- "serve": "vite preview"
36
+ "serve": "vite preview",
37
+ "size": "size-limit"
35
38
  },
36
39
  "repository": {
37
40
  "type": "git",
@@ -49,11 +52,10 @@
49
52
  "peerDependencies": {
50
53
  "@linaria/core": "^7.0.0 || ^8.0.0",
51
54
  "react": "^19.0.0",
52
- "react-f0rm": ">=0.7.0"
55
+ "react-f0rm": ">=0.7.0 <0.9.0"
53
56
  },
54
57
  "dependencies": {
55
- "react-toolroom": "^0.18.4",
56
- "react-use-control": "^1.3.2"
58
+ "react-use-control": "^1.5.0"
57
59
  },
58
60
  "devDependencies": {
59
61
  "@babel/core": "^8.0.1",
@@ -64,6 +66,8 @@
64
66
  "@linaria/core": "^8.2.0",
65
67
  "@native-router/core": "^1.10.0",
66
68
  "@native-router/react": "^1.9.0",
69
+ "@playwright/test": "^1.62.1",
70
+ "@size-limit/file": "^13.0.3",
67
71
  "@testing-library/jest-dom": "^7.0.1",
68
72
  "@testing-library/react": "^16.3.3",
69
73
  "@testing-library/user-event": "^14.6.6",
@@ -95,15 +99,37 @@
95
99
  "ramda": "^0.32.0",
96
100
  "react": "^19.2.8",
97
101
  "react-dom": "^19.2.8",
98
- "react-f0rm": "^0.7.0",
102
+ "react-f0rm": "^0.8.0",
99
103
  "react-icons": "^5.7.0",
104
+ "react-toolroom": "^0.18.4",
100
105
  "rollup-plugin-type-as-json-schema": "^0.2.6",
101
106
  "semantic-release": "^25.0.9",
107
+ "size-limit": "^13.0.3",
102
108
  "terser": "^5.51.2",
103
109
  "tools-config": "^0.3.1",
104
110
  "tsc-alias": "^1.9.2",
105
111
  "typescript": "^6.0.3",
106
112
  "vite": "^8.2.2",
107
113
  "vitest": "^4.1.11"
108
- }
114
+ },
115
+ "size-limit": [
116
+ {
117
+ "path": "dist/index.js",
118
+ "limit": "16 KB"
119
+ },
120
+ {
121
+ "path": "dist/index.js",
122
+ "limit": "4 KB",
123
+ "gzip": true
124
+ },
125
+ {
126
+ "path": "dist/haze-ui.css",
127
+ "limit": "136 KB"
128
+ },
129
+ {
130
+ "path": "dist/haze-ui.css",
131
+ "limit": "19 KB",
132
+ "gzip": true
133
+ }
134
+ ]
109
135
  }
@@ -1,52 +0,0 @@
1
- import { useCallback as e, useEffect as t, useRef as n } from "react";
2
- //#region src/lib/components/DropdownMenu/useMenuKeyboard.ts
3
- var r = 500;
4
- function i(e) {
5
- return e ? Array.from(e.querySelectorAll("[role=\"menuitem\"]:not([disabled])")) : [];
6
- }
7
- function a({ menuRef: a, onClose: o }) {
8
- let s = n(""), c = n(0);
9
- return t(() => () => window.clearTimeout(c.current), []), e((e) => {
10
- let t = i(a.current);
11
- if (t.length === 0) return;
12
- let n = t.indexOf(document.activeElement), l = (e) => t[e]?.focus();
13
- switch (e.key) {
14
- case "ArrowDown":
15
- e.preventDefault(), l((n + 1) % t.length);
16
- return;
17
- case "ArrowUp":
18
- e.preventDefault(), l((n - 1 + t.length) % t.length);
19
- return;
20
- case "Home":
21
- e.preventDefault(), l(0);
22
- return;
23
- case "End":
24
- e.preventDefault(), l(t.length - 1);
25
- return;
26
- case "Escape":
27
- e.preventDefault(), o();
28
- return;
29
- case "Tab":
30
- e.preventDefault(), o();
31
- return;
32
- default: {
33
- if (e.key.length !== 1 || e.ctrlKey || e.metaKey || e.altKey) return;
34
- e.preventDefault();
35
- let i = (s.current + e.key).toLowerCase();
36
- s.current = i, window.clearTimeout(c.current), c.current = window.setTimeout(() => {
37
- s.current = "";
38
- }, r);
39
- let a = n + 1;
40
- for (let e = 0; e < t.length; e++) {
41
- let n = (a + e) % t.length;
42
- if (t[n].textContent.toLowerCase().startsWith(i)) {
43
- l(n);
44
- return;
45
- }
46
- }
47
- }
48
- }
49
- }, [a, o]);
50
- }
51
- //#endregion
52
- export { i as getEnabledMenuItems, a as useMenuKeyboard };
@@ -1,19 +0,0 @@
1
- import type { KeyboardEvent as ReactKeyboardEvent, RefObject } from 'react';
2
- /** Enabled menu items inside a menu container, in DOM order. */
3
- export declare function getEnabledMenuItems(container: HTMLElement | null): HTMLElement[];
4
- type UseMenuKeyboardOptions = {
5
- /** Element containing the `[role=menuitem]` children. */
6
- menuRef: RefObject<HTMLElement | null>;
7
- /** Called when the menu should close via keyboard; the caller returns
8
- * focus to the trigger. */
9
- onClose: () => void;
10
- };
11
- /**
12
- * Keyboard behavior for a `role="menu"` container with roving-tabindex
13
- * items: ↑/↓ move focus (wrapping, skipping disabled items), Home/End
14
- * jump to the ends, Escape closes (returning focus to the trigger), Tab
15
- * closes, and printable characters run typeahead — matching the WAI-ARIA
16
- * Menu Button pattern.
17
- */
18
- export declare function useMenuKeyboard({ menuRef, onClose }: UseMenuKeyboardOptions): (event: ReactKeyboardEvent) => void;
19
- export {};
@@ -1 +0,0 @@
1
- export {};
@@ -1 +0,0 @@
1
- export {};