@godxjp/ui 21.0.0 → 23.0.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 (154) hide show
  1. package/README.md +4 -4
  2. package/dist/app/app-provider.js +2 -1
  3. package/dist/components/data-display/badge.d.ts +28 -1
  4. package/dist/components/data-display/badge.js +2 -0
  5. package/dist/components/data-display/data-table.d.ts +18 -2
  6. package/dist/components/data-display/data-table.js +4 -0
  7. package/dist/components/data-display/index.d.ts +1 -1
  8. package/dist/components/data-display/popover.d.ts +19 -1
  9. package/dist/components/data-display/popover.js +9 -1
  10. package/dist/components/data-display/progress.d.ts +37 -0
  11. package/dist/components/data-display/progress.js +57 -5
  12. package/dist/components/data-display/scroll-area.d.ts +24 -4
  13. package/dist/components/data-display/scroll-area.js +34 -72
  14. package/dist/components/data-entry/cascader.js +8 -6
  15. package/dist/components/data-entry/checkbox.js +2 -0
  16. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  17. package/dist/components/data-entry/choice-hit-target.js +12 -0
  18. package/dist/components/data-entry/control-surface.d.ts +8 -0
  19. package/dist/components/data-entry/control-surface.js +10 -1
  20. package/dist/components/data-entry/date-picker.d.ts +10 -3
  21. package/dist/components/data-entry/date-picker.js +503 -238
  22. package/dist/components/data-entry/form-field.d.ts +1 -1
  23. package/dist/components/data-entry/form-field.js +7 -3
  24. package/dist/components/data-entry/form.js +10 -1
  25. package/dist/components/data-entry/index.d.ts +0 -6
  26. package/dist/components/data-entry/index.js +0 -6
  27. package/dist/components/data-entry/label.d.ts +14 -1
  28. package/dist/components/data-entry/label.js +6 -1
  29. package/dist/components/data-entry/radio.d.ts +27 -6
  30. package/dist/components/data-entry/radio.js +89 -43
  31. package/dist/components/data-entry/search-select.js +226 -89
  32. package/dist/components/data-entry/select.d.ts +76 -14
  33. package/dist/components/data-entry/select.js +497 -163
  34. package/dist/components/data-entry/slider.d.ts +3 -11
  35. package/dist/components/data-entry/slider.js +582 -93
  36. package/dist/components/data-entry/switch.d.ts +6 -3
  37. package/dist/components/data-entry/switch.js +36 -11
  38. package/dist/components/layout/mobile-shell.d.ts +1 -1
  39. package/dist/components/layout/mobile-shell.js +2 -0
  40. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  41. package/dist/components/navigation/dropdown-menu.js +190 -15
  42. package/dist/components/navigation/index.d.ts +0 -3
  43. package/dist/components/navigation/index.js +0 -78
  44. package/dist/components/navigation/steps.d.ts +1 -1
  45. package/dist/components/navigation/steps.js +3 -1
  46. package/dist/components/navigation/tabs-scroll.d.ts +27 -0
  47. package/dist/components/navigation/tabs-scroll.js +52 -1
  48. package/dist/components/navigation/tabs.d.ts +2 -2
  49. package/dist/components/navigation/tabs.js +88 -23
  50. package/dist/components/ui/index.d.ts +0 -4
  51. package/dist/components/ui/index.js +0 -4
  52. package/dist/components/ui/input-otp.d.ts +1 -1
  53. package/dist/components/ui/segmented.d.ts +15 -1
  54. package/dist/components/ui/segmented.js +15 -6
  55. package/dist/components/ui/tag-input.js +3 -5
  56. package/dist/i18n/messages/en.json +3 -2
  57. package/dist/i18n/messages/ja.json +3 -2
  58. package/dist/i18n/messages/vi.json +3 -2
  59. package/dist/lib/datetime/picker-format.d.ts +18 -0
  60. package/dist/lib/datetime/picker-format.js +35 -1
  61. package/dist/lib/field-a11y.d.ts +12 -0
  62. package/dist/lib/field-a11y.js +14 -1
  63. package/dist/lib/select-options.d.ts +21 -0
  64. package/dist/lib/select-options.js +46 -0
  65. package/dist/props/components/data-display.prop.d.ts +16 -6
  66. package/dist/props/components/data-entry.prop.d.ts +503 -129
  67. package/dist/props/components/index.d.ts +2 -2
  68. package/dist/props/components/layout.prop.d.ts +12 -1
  69. package/dist/props/components/navigation.prop.d.ts +93 -1
  70. package/dist/props/registry.d.ts +79 -16
  71. package/dist/props/registry.js +85 -15
  72. package/dist/props/vocabulary/data.prop.d.ts +12 -0
  73. package/dist/props/vocabulary/index.d.ts +2 -2
  74. package/dist/props/vocabulary/layout.prop.d.ts +11 -0
  75. package/dist/styles/badge-layout.css +4 -0
  76. package/dist/styles/card-layout.css +6 -6
  77. package/dist/styles/control.css +220 -29
  78. package/dist/styles/data-display-layout.css +94 -23
  79. package/dist/styles/focus-ring.css +6 -4
  80. package/dist/styles/fonts.css +88 -4
  81. package/dist/styles/navigation-layout.css +36 -236
  82. package/dist/styles/shell-layout.css +149 -139
  83. package/dist/styles/table-layout.css +41 -3
  84. package/dist/tokens/components/control.css +4 -0
  85. package/dist/tokens/components/data-display.css +9 -5
  86. package/dist/tokens/components/navigation.css +9 -5
  87. package/dist/tokens/components/scroll-area.css +7 -0
  88. package/dist/tokens/components/segmented.css +0 -2
  89. package/dist/tokens/components/shell.css +2 -0
  90. package/dist/tokens/components/table.css +3 -0
  91. package/dist/tokens/foundation.css +7 -0
  92. package/docs/COMPONENTS.md +14 -14
  93. package/docs/CONSUMER-RULES.md +24 -1
  94. package/docs/CUSTOMER-THEMING.md +7 -1
  95. package/docs/FRAME-COVERAGE-REPORT.md +5 -11
  96. package/docs/TOKENS.md +65 -0
  97. package/docs/data-display/badge.tsx +37 -0
  98. package/docs/data-display/data-table/index.tsx +45 -1
  99. package/docs/data-display/popover.tsx +43 -5
  100. package/docs/data-display/progress.tsx +28 -0
  101. package/docs/data-display/scroll-area.tsx +51 -45
  102. package/docs/data-entry/calendar.tsx +2 -2
  103. package/docs/data-entry/date-picker.tsx +144 -5
  104. package/docs/data-entry/form/examples/invoice-form.tsx +2 -2
  105. package/docs/data-entry/form-field/examples/a11y-contract.tsx +3 -5
  106. package/docs/data-entry/form.tsx +4 -4
  107. package/docs/data-entry/input-otp.tsx +5 -2
  108. package/docs/data-entry/number-input.tsx +31 -0
  109. package/docs/data-entry/segmented.tsx +143 -2
  110. package/docs/data-entry/select-matrix.tsx +1 -2
  111. package/docs/data-entry/select.tsx +108 -3
  112. package/docs/data-entry/slider.tsx +125 -47
  113. package/docs/feedback/dialog.tsx +1 -2
  114. package/docs/feedback/sheet.tsx +1 -2
  115. package/docs/layout/app-shell-states.tsx +143 -0
  116. package/docs/layout/mobile-shell.tsx +5 -0
  117. package/docs/navigation/dropdown-menu.tsx +75 -4
  118. package/docs/navigation/tabs.tsx +149 -0
  119. package/docs/roadmap/antd-parity.md +35 -34
  120. package/docs/roadmap/parity-audit-data-entry.md +196 -172
  121. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  122. package/docs/roadmap/parity-backlog.md +8 -10
  123. package/docs/showcase/acme-portal.tsx +7 -0
  124. package/package.json +28 -64
  125. package/scripts/ui-audit.mjs +18 -5
  126. package/scripts/visual-audit-rules.mjs +0 -7
  127. package/scripts/visual-audit.mjs +6 -27
  128. package/dist/components/data-entry/date-range-picker.d.ts +0 -11
  129. package/dist/components/data-entry/date-range-picker.js +0 -349
  130. package/dist/components/data-entry/month-picker.d.ts +0 -10
  131. package/dist/components/data-entry/month-picker.js +0 -241
  132. package/dist/components/data-entry/month-range-picker.d.ts +0 -10
  133. package/dist/components/data-entry/month-range-picker.js +0 -301
  134. package/dist/components/navigation/context-menu.d.ts +0 -21
  135. package/dist/components/navigation/context-menu.js +0 -149
  136. package/dist/components/navigation/menubar.d.ts +0 -21
  137. package/dist/components/navigation/menubar.js +0 -136
  138. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  139. package/dist/components/navigation/navigation-menu.js +0 -91
  140. package/dist/components/ui/context-menu.d.ts +0 -1
  141. package/dist/components/ui/context-menu.js +0 -2
  142. package/dist/components/ui/date-range-picker.d.ts +0 -1
  143. package/dist/components/ui/date-range-picker.js +0 -2
  144. package/dist/components/ui/menubar.d.ts +0 -1
  145. package/dist/components/ui/menubar.js +0 -2
  146. package/dist/components/ui/navigation-menu.d.ts +0 -1
  147. package/dist/components/ui/navigation-menu.js +0 -2
  148. package/docs/FRAME-A11Y-CI.md +0 -349
  149. package/docs/data-entry/date-range-picker.tsx +0 -175
  150. package/docs/data-entry/month-picker.tsx +0 -101
  151. package/docs/data-entry/month-range-picker.tsx +0 -115
  152. package/docs/navigation/context-menu.tsx +0 -128
  153. package/docs/navigation/menubar.tsx +0 -141
  154. package/docs/navigation/navigation-menu.tsx +0 -158
@@ -1,9 +1,12 @@
1
1
  import * as React from "react";
2
- import * as SwitchPrimitive from "@radix-ui/react-switch";
3
2
  export type { SwitchProp, SwitchProp as SwitchProps } from "../../props/components/data-entry.prop.js";
4
- export declare const Switch: React.ForwardRefExoticComponent<Omit<SwitchPrimitive.SwitchProps & React.RefAttributes<HTMLButtonElement>, "ref"> & {
3
+ export declare const Switch: React.ForwardRefExoticComponent<Omit<Omit<React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref">, "checked" | "defaultChecked" | "onChange" | "value"> & {
4
+ checked?: boolean;
5
+ defaultChecked?: boolean;
6
+ onCheckedChange?: (checked: boolean) => void;
7
+ required?: boolean;
5
8
  size?: "sm" | "md";
6
9
  loading?: boolean;
7
10
  checkedChildren?: React.ReactNode;
8
11
  unCheckedChildren?: React.ReactNode;
9
- } & React.RefAttributes<HTMLButtonElement>>;
12
+ } & React.RefAttributes<HTMLLabelElement>>;
@@ -1,10 +1,11 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import * as SwitchPrimitive from "@radix-ui/react-switch";
4
+ import { Switch as AriaSwitch } from "react-aria-components";
5
5
  import { Loader2 } from "lucide-react";
6
6
  import { cn } from "../../lib/utils.js";
7
- import { useFieldIdentity } from "../../lib/field-a11y.js";
7
+ import { useFieldIdentity, useMirroredInputAttributes } from "../../lib/field-a11y.js";
8
+ import { withOwnHitTarget } from "./choice-hit-target.js";
8
9
  const Switch = React.forwardRef(
9
10
  ({
10
11
  className,
@@ -16,6 +17,8 @@ const Switch = React.forwardRef(
16
17
  loading = false,
17
18
  checkedChildren,
18
19
  unCheckedChildren,
20
+ disabled,
21
+ required,
19
22
  ...props
20
23
  }, ref) => {
21
24
  const identity = useFieldIdentity({
@@ -34,19 +37,43 @@ const Switch = React.forwardRef(
34
37
  }
35
38
  onCheckedChange?.(next);
36
39
  };
40
+ const ownField = props["data-field"];
41
+ const fieldKey = ownField ?? identity["data-field"];
42
+ const invalid = props["aria-invalid"];
43
+ const isInvalid = invalid !== void 0 && invalid !== false && invalid !== "false";
44
+ const inputRef = React.useRef(null);
45
+ useMirroredInputAttributes(inputRef, {
46
+ "aria-invalid": isInvalid ? "true" : void 0,
47
+ "aria-busy": loading ? "true" : void 0,
48
+ "aria-disabled": loading ? "true" : void 0,
49
+ // react-aria's `Switch` Omits `isRequired`, and a switch is never the target of native
50
+ // constraint validation in this library — the form layer owns that. The prop is accepted
51
+ // for shape and announced through `aria-required`.
52
+ "aria-required": required ? "true" : void 0,
53
+ "data-field": fieldKey
54
+ });
55
+ const state = isChecked ? "checked" : "unchecked";
37
56
  return /* @__PURE__ */ jsxs(Fragment, { children: [
38
57
  resolvedName ? /* @__PURE__ */ jsx("input", { type: "hidden", name: resolvedName, value: isChecked ? "1" : "0", readOnly: true }) : null,
39
58
  /* @__PURE__ */ jsxs(
40
- SwitchPrimitive.Root,
59
+ AriaSwitch,
41
60
  {
61
+ ...props,
42
62
  ref,
63
+ inputRef,
43
64
  "data-slot": "switch",
44
65
  "data-size": size,
66
+ "data-state": state,
45
67
  "data-loading": loading ? "true" : void 0,
46
- "aria-busy": loading || void 0,
47
- "aria-disabled": loading || void 0,
48
- checked: isChecked,
49
- onCheckedChange: handleCheckedChange,
68
+ isSelected: isChecked,
69
+ isDisabled: disabled,
70
+ onChange: handleCheckedChange,
71
+ onKeyDown: (event) => {
72
+ if (event.key !== "Enter") return;
73
+ event.preventDefault();
74
+ handleCheckedChange(!isChecked);
75
+ },
76
+ render: (domProps) => /* @__PURE__ */ jsx("label", { ...domProps, children: withOwnHitTarget(domProps.children) }),
50
77
  className: cn(
51
78
  // `.ui-switch:disabled, .ui-switch[data-disabled]` in styles/control.css already
52
79
  // declares both and reads --disabled-opacity. The utility was layered after
@@ -56,11 +83,9 @@ const Switch = React.forwardRef(
56
83
  "peer ui-switch shadow-xs transition-all outline-none",
57
84
  className
58
85
  ),
59
- ...props,
60
- "data-field": identity["data-field"] ?? props["data-field"],
61
86
  children: [
62
87
  checkedChildren != null || unCheckedChildren != null ? /* @__PURE__ */ jsx("span", { "data-slot": "switch-content", className: "ui-switch-content", "aria-hidden": "true", children: isChecked ? checkedChildren : unCheckedChildren }) : null,
63
- /* @__PURE__ */ jsx(SwitchPrimitive.Thumb, { "data-slot": "switch-thumb", className: "ui-switch-thumb", children: loading ? /* @__PURE__ */ jsx(
88
+ /* @__PURE__ */ jsx("span", { "data-slot": "switch-thumb", "data-state": state, className: "ui-switch-thumb", children: loading ? /* @__PURE__ */ jsx(
64
89
  Loader2,
65
90
  {
66
91
  "data-slot": "switch-spinner",
@@ -74,7 +99,7 @@ const Switch = React.forwardRef(
74
99
  ] });
75
100
  }
76
101
  );
77
- Switch.displayName = SwitchPrimitive.Root.displayName;
102
+ Switch.displayName = "Switch";
78
103
  export {
79
104
  Switch
80
105
  };
@@ -18,4 +18,4 @@ export type { MobileShellProp, MobileShellProp as MobileShellProps, } from "../.
18
18
  * IS the app's primary navigation). They are the same strings in all three locales, and a fourth
19
19
  * identical copy under `layout.mobileShell.*` would be three files of duplication to keep in sync.
20
20
  */
21
- export declare function MobileShell({ statusBar, header, children, actions, tabBar, height, className, }: MobileShellProp): import("react").JSX.Element;
21
+ export declare function MobileShell({ statusBar, header, children, actions, tabBar, height, width, className, }: MobileShellProp): import("react").JSX.Element;
@@ -9,6 +9,7 @@ function MobileShell({
9
9
  actions,
10
10
  tabBar,
11
11
  height = "viewport",
12
+ width = "fill",
12
13
  className
13
14
  }) {
14
15
  const { t } = useTranslation();
@@ -17,6 +18,7 @@ function MobileShell({
17
18
  {
18
19
  "data-slot": "mobile-shell",
19
20
  "data-height": height === "viewport" ? void 0 : height,
21
+ "data-width": width === "fill" ? void 0 : width,
20
22
  className: cn("ui-mobile-shell", className),
21
23
  children: [
22
24
  (statusBar !== void 0 || header !== void 0) && /* @__PURE__ */ jsxs("header", { className: "ui-mobile-shell-chrome", "aria-label": t("layout.appShell.headerLabel"), children: [
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
  import { type MenuItemRenderProps, type PopoverProps, type PopoverRenderProps } from "react-aria-components";
3
- import type { DropdownMenuPlacementProp } from "../../props/components/navigation.prop.js";
4
- export type { DropdownMenuPlacementProp } from "../../props/components/navigation.prop.js";
3
+ import type { DropdownMenuPlacementProp, DropdownMenuTriggerActionProp } from "../../props/components/navigation.prop.js";
4
+ export type { DropdownMenuPlacementProp, DropdownMenuTriggerActionProp, } from "../../props/components/navigation.prop.js";
5
5
  /**
6
6
  * Dịch trạng thái item của RAC sang bộ `data-*` mà Radix phát ra và CSS của kho đang bám.
7
7
  *
@@ -47,9 +47,17 @@ interface DropdownMenuPropsOwn {
47
47
  * `useLocale`), nên prop này còn trong API vì hợp đồng, nhưng không tự đảo hướng.
48
48
  */
49
49
  dir?: "ltr" | "rtl";
50
+ /** Ant Design `trigger`. @see DropdownMenuTriggerActionProp. Default `['click']`. */
51
+ trigger?: readonly DropdownMenuTriggerActionProp[];
52
+ /** Ant Design `disabled` — no gesture opens the menu (antd empties the trigger list). */
53
+ disabled?: boolean;
54
+ /** Ant Design `mouseEnterDelay`, in SECONDS, for `trigger={['hover']}`. Default 0.15. */
55
+ mouseEnterDelay?: number;
56
+ /** Ant Design `mouseLeaveDelay`, in SECONDS. Default 0.1 — long enough to cross the gap. */
57
+ mouseLeaveDelay?: number;
50
58
  }
51
59
  type DropdownMenuProps = React.PropsWithChildren<DropdownMenuPropsOwn>;
52
- export declare function DropdownMenu({ children, open, defaultOpen, onOpenChange, modal, dir, }: DropdownMenuProps): React.JSX.Element;
60
+ export declare function DropdownMenu({ children, open, defaultOpen, onOpenChange, modal, dir, trigger, disabled, mouseEnterDelay, mouseLeaveDelay, }: DropdownMenuProps): React.JSX.Element;
53
61
  interface DropdownMenuTriggerProps extends React.ComponentPropsWithoutRef<"button"> {
54
62
  /** Giao hành vi mở cho phần tử con thay vì `<button>` mặc định — như Radix. */
55
63
  asChild?: boolean;
@@ -12,9 +12,11 @@ import {
12
12
  OverlayTriggerStateContext,
13
13
  Popover,
14
14
  Pressable,
15
+ RootMenuTriggerStateContext,
15
16
  Separator,
16
17
  OverlayArrow,
17
- SubmenuTrigger
18
+ SubmenuTrigger,
19
+ useLocale
18
20
  } from "react-aria-components";
19
21
  import { Check, ChevronRight } from "lucide-react";
20
22
  import { cn } from "../../lib/utils.js";
@@ -58,39 +60,201 @@ function selectEvent(name) {
58
60
  return typeof CustomEvent === "function" ? new CustomEvent(name, { bubbles: false, cancelable: true }) : { defaultPrevented: false, preventDefault() {
59
61
  } };
60
62
  }
61
- const DropdownMenuModalContext = React.createContext({ modal: true });
63
+ const DROPDOWN_MENU_DEFAULT_TRIGGER = ["click"];
64
+ const DROPDOWN_MENU_MOUSE_ENTER_DELAY = 0.15;
65
+ const DROPDOWN_MENU_MOUSE_LEAVE_DELAY = 0.1;
66
+ const SECOND_IN_MS = 1e3;
67
+ const DropdownMenuModalContext = React.createContext({
68
+ modal: true,
69
+ contextMenuGesture: false,
70
+ disabled: false,
71
+ hover: null,
72
+ racOwnsContextMenu: false,
73
+ openedByHover: { current: false }
74
+ });
75
+ function clearContextMenuPoint(state) {
76
+ state?.setPoint(null);
77
+ }
78
+ function chainGestureProps(own, incoming) {
79
+ const merged = { ...own };
80
+ for (const [key, handler] of Object.entries(incoming)) {
81
+ const existing = own[key];
82
+ merged[key] = typeof existing === "function" && typeof handler === "function" ? (event) => {
83
+ existing(event);
84
+ if (!event.defaultPrevented) handler(event);
85
+ } : handler;
86
+ }
87
+ return merged;
88
+ }
89
+ function isContextMenuKey(event) {
90
+ return event.key === "ContextMenu" || event.key === "F10" && event.shiftKey;
91
+ }
62
92
  function DropdownMenu({
63
93
  children,
64
94
  open,
65
95
  defaultOpen,
66
96
  onOpenChange,
67
97
  modal = true,
68
- dir
98
+ dir,
99
+ trigger = DROPDOWN_MENU_DEFAULT_TRIGGER,
100
+ disabled = false,
101
+ mouseEnterDelay = DROPDOWN_MENU_MOUSE_ENTER_DELAY,
102
+ mouseLeaveDelay = DROPDOWN_MENU_MOUSE_LEAVE_DELAY
69
103
  }) {
70
104
  void dir;
71
- const options = React.useMemo(() => ({ modal }), [modal]);
72
- return /* @__PURE__ */ jsx(DropdownMenuModalContext.Provider, { value: options, children: /* @__PURE__ */ jsx(MenuTrigger, { isOpen: open, defaultOpen, onOpenChange, children }) });
105
+ const wantsHover = !disabled && trigger.includes("hover");
106
+ const wantsClick = !disabled && trigger.includes("click");
107
+ const wantsContextMenu = !disabled && trigger.includes("contextMenu");
108
+ const racOwnsContextMenu = wantsContextMenu && !wantsClick && !wantsHover;
109
+ const [uncontrolled, setUncontrolled] = React.useState(defaultOpen ?? false);
110
+ const isOpen = open ?? uncontrolled;
111
+ const isOpenRef = React.useRef(isOpen);
112
+ isOpenRef.current = isOpen;
113
+ const openedByHover = React.useRef(false);
114
+ const setOpen = React.useCallback(
115
+ (next) => {
116
+ if (next === isOpenRef.current) return;
117
+ if (next && disabled) return;
118
+ if (!next) openedByHover.current = false;
119
+ if (open === void 0) setUncontrolled(next);
120
+ onOpenChange?.(next);
121
+ },
122
+ [disabled, open, onOpenChange]
123
+ );
124
+ const enterTimer = React.useRef(null);
125
+ const leaveTimer = React.useRef(null);
126
+ React.useEffect(
127
+ () => () => {
128
+ if (enterTimer.current) clearTimeout(enterTimer.current);
129
+ if (leaveTimer.current) clearTimeout(leaveTimer.current);
130
+ },
131
+ []
132
+ );
133
+ const hover = React.useMemo(() => {
134
+ if (!wantsHover) return null;
135
+ return {
136
+ enter: () => {
137
+ if (leaveTimer.current) clearTimeout(leaveTimer.current);
138
+ if (isOpenRef.current) return;
139
+ enterTimer.current = setTimeout(() => {
140
+ openedByHover.current = true;
141
+ setOpen(true);
142
+ }, mouseEnterDelay * SECOND_IN_MS);
143
+ },
144
+ leave: () => {
145
+ if (enterTimer.current) clearTimeout(enterTimer.current);
146
+ leaveTimer.current = setTimeout(() => setOpen(false), mouseLeaveDelay * SECOND_IN_MS);
147
+ }
148
+ };
149
+ }, [wantsHover, mouseEnterDelay, mouseLeaveDelay, setOpen]);
150
+ const options = React.useMemo(
151
+ () => ({
152
+ // A HOVER MENU CANNOT BE MODAL. A modal RAC popover paints a full-bleed underlay the instant
153
+ // it opens; that underlay lands under the pointer, the trigger gets `pointerleave`, and the
154
+ // menu closes before the reader can move onto it. antd's hover dropdown is non-modal for the
155
+ // same reason. Every other mode keeps the caller's `modal`.
156
+ modal: wantsHover ? false : modal,
157
+ contextMenuGesture: wantsContextMenu,
158
+ disabled,
159
+ hover,
160
+ racOwnsContextMenu,
161
+ openedByHover
162
+ }),
163
+ [modal, wantsContextMenu, disabled, hover, racOwnsContextMenu, wantsHover]
164
+ );
165
+ return /* @__PURE__ */ jsx(DropdownMenuModalContext.Provider, { value: options, children: /* @__PURE__ */ jsx(
166
+ MenuTrigger,
167
+ {
168
+ isOpen,
169
+ onOpenChange: setOpen,
170
+ trigger: racOwnsContextMenu ? "contextMenu" : "press",
171
+ children
172
+ }
173
+ ) });
73
174
  }
74
175
  function DropdownMenuTrigger({ asChild, children, ...props }) {
75
176
  const state = React.useContext(OverlayTriggerStateContext);
177
+ const menuState = React.useContext(
178
+ RootMenuTriggerStateContext
179
+ );
180
+ const { contextMenuGesture, disabled, hover, racOwnsContextMenu } = React.useContext(DropdownMenuModalContext);
76
181
  const dataState = state?.isOpen ? "open" : "closed";
182
+ const wantsContextMenu = contextMenuGesture;
183
+ const isDisabled = disabled || props.disabled;
184
+ const { direction } = useLocale();
185
+ const openAtTriggerBox = (element) => {
186
+ const rect = element.getBoundingClientRect();
187
+ menuState?.setPoint({ x: direction === "rtl" ? rect.right : rect.left, y: rect.bottom });
188
+ menuState?.open("first");
189
+ };
190
+ const gestureProps = {};
191
+ if (wantsContextMenu && !isDisabled) {
192
+ gestureProps.onKeyDown = (event) => {
193
+ if (!isContextMenuKey(event)) return;
194
+ event.preventDefault();
195
+ openAtTriggerBox(event.currentTarget);
196
+ };
197
+ if (!racOwnsContextMenu) {
198
+ gestureProps.onContextMenu = (event) => {
199
+ event.preventDefault();
200
+ menuState?.setPoint({ x: event.clientX, y: event.clientY });
201
+ menuState?.open(null);
202
+ };
203
+ }
204
+ }
205
+ if (!racOwnsContextMenu) {
206
+ gestureProps.onPointerDownCapture = () => clearContextMenuPoint(menuState);
207
+ gestureProps.onKeyDownCapture = (event) => {
208
+ if (!isContextMenuKey(event)) clearContextMenuPoint(menuState);
209
+ };
210
+ }
211
+ if (hover && !isDisabled) {
212
+ gestureProps.onPointerEnter = (event) => {
213
+ if (event.pointerType === "mouse") hover.enter();
214
+ };
215
+ gestureProps.onPointerLeave = (event) => {
216
+ if (event.pointerType === "mouse") hover.leave();
217
+ };
218
+ }
77
219
  if (asChild && React.isValidElement(children)) {
78
- return /* @__PURE__ */ jsx(Pressable, { children: React.cloneElement(children, {
220
+ return /* @__PURE__ */ jsx(Pressable, { isDisabled, children: React.cloneElement(children, {
79
221
  "data-slot": "dropdown-menu-trigger",
80
- "data-state": dataState
222
+ "data-state": dataState,
223
+ // A right click target is usually a region rather than a button, and Shift+F10 needs it
224
+ // focusable — so it is given a tab stop unless the consumer already placed one.
225
+ ...wantsContextMenu && children.props.tabIndex === void 0 ? { tabIndex: 0 } : {},
226
+ ...chainGestureProps(
227
+ children.props,
228
+ gestureProps
229
+ )
81
230
  }) });
82
231
  }
83
232
  return /* @__PURE__ */ jsx(
84
233
  AriaButton,
85
234
  {
86
- ...props,
87
- isDisabled: props.disabled,
235
+ ...chainGestureProps(
236
+ props,
237
+ gestureProps
238
+ ),
239
+ isDisabled,
88
240
  "data-slot": "dropdown-menu-trigger",
89
241
  "data-state": dataState,
90
242
  children
91
243
  }
92
244
  );
93
245
  }
246
+ function useHoverSurfaceProps() {
247
+ const { hover } = React.useContext(DropdownMenuModalContext);
248
+ if (!hover) return {};
249
+ return {
250
+ onPointerEnter: (event) => {
251
+ if (event.pointerType === "mouse") hover.enter();
252
+ },
253
+ onPointerLeave: (event) => {
254
+ if (event.pointerType === "mouse") hover.leave();
255
+ }
256
+ };
257
+ }
94
258
  function DropdownMenuPortal({ children }) {
95
259
  return /* @__PURE__ */ jsx(Fragment, { children });
96
260
  }
@@ -129,7 +293,7 @@ function DropdownMenuContent({
129
293
  className,
130
294
  side,
131
295
  align,
132
- sideOffset = 4,
296
+ sideOffset,
133
297
  placement,
134
298
  arrow,
135
299
  width,
@@ -144,17 +308,25 @@ function DropdownMenuContent({
144
308
  void hideWhenDetached;
145
309
  void sticky;
146
310
  void forceMount;
147
- const { modal } = React.useContext(DropdownMenuModalContext);
311
+ const { modal, openedByHover } = React.useContext(DropdownMenuModalContext);
312
+ const hoverSurfaceProps = useHoverSurfaceProps();
313
+ const menuState = React.useContext(
314
+ RootMenuTriggerStateContext
315
+ );
148
316
  const anchor = placement ? DROPDOWN_MENU_PLACEMENT[placement] : void 0;
149
317
  const overlayPortalContainer = useOverlayPortalContainer();
318
+ const pointAnchored = menuState?.point != null;
319
+ const hasExplicitAnchor = placement !== void 0 || side !== void 0 || align !== void 0;
320
+ const resolvedPlacement = hasExplicitAnchor || !pointAnchored ? toPlacement(side ?? anchor?.side, align ?? anchor?.align) : "bottom start";
321
+ const resolvedOffset = sideOffset ?? (pointAnchored && !hasExplicitAnchor ? 0 : 4);
150
322
  return /* @__PURE__ */ jsx(DropdownMenuPortal, { children: /* @__PURE__ */ jsxs(
151
323
  Popover,
152
324
  {
153
325
  UNSTABLE_portalContainer: overlayPortalContainer,
154
326
  "data-slot": "dropdown-menu-content",
155
327
  isNonModal: !modal,
156
- placement: toPlacement(side ?? anchor?.side, align ?? anchor?.align),
157
- offset: sideOffset,
328
+ placement: resolvedPlacement,
329
+ offset: resolvedOffset,
158
330
  crossOffset: alignOffset,
159
331
  shouldFlip: avoidCollisions,
160
332
  containerPadding: collisionPadding,
@@ -168,15 +340,16 @@ function DropdownMenuContent({
168
340
  "div",
169
341
  {
170
342
  ...props,
343
+ ...hoverSurfaceProps,
171
344
  role: void 0,
172
345
  ...radixSurfaceState(state),
173
- "data-align": align ?? anchor?.align ?? "center",
346
+ "data-align": align ?? anchor?.align ?? (pointAnchored && !hasExplicitAnchor ? "start" : "center"),
174
347
  "data-width": width
175
348
  }
176
349
  )
177
350
  ),
178
351
  children: [
179
- /* @__PURE__ */ jsx(Menu, { shouldFocusWrap: loop, children }),
352
+ /* @__PURE__ */ jsx(Menu, { shouldFocusWrap: loop, autoFocus: openedByHover.current ? false : void 0, children }),
180
353
  arrow ? /* @__PURE__ */ jsx(OverlayArrow, { children: /* @__PURE__ */ jsx(
181
354
  "svg",
182
355
  {
@@ -388,6 +561,7 @@ function DropdownMenuSubContent({
388
561
  void hideWhenDetached;
389
562
  void sticky;
390
563
  const overlayPortalContainer = useOverlayPortalContainer();
564
+ const hoverSurfaceProps = useHoverSurfaceProps();
391
565
  return /* @__PURE__ */ jsx(
392
566
  Popover,
393
567
  {
@@ -408,6 +582,7 @@ function DropdownMenuSubContent({
408
582
  "div",
409
583
  {
410
584
  ...props,
585
+ ...hoverSurfaceProps,
411
586
  role: void 0,
412
587
  ...radixSurfaceState(state),
413
588
  "data-align": align ?? "center"
@@ -1,8 +1,5 @@
1
1
  export { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs.js";
2
- export { ContextMenu, ContextMenuCheckboxItem, ContextMenuContent, ContextMenuItem, ContextMenuLabel, ContextMenuPortal, ContextMenuRadioGroup, ContextMenuRadioItem, ContextMenuSeparator, ContextMenuShortcut, ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, ContextMenuTrigger, } from "./context-menu.js";
3
2
  export { Pagination, PaginationContent, PaginationItem, PaginationLink, PaginationEllipsis, PaginationPrevious, PaginationNext, } from "./pagination.js";
4
- export { Menubar, MenubarCheckboxItem, MenubarContent, MenubarItem, MenubarLabel, MenubarMenu, MenubarRadioGroup, MenubarRadioItem, MenubarSeparator, MenubarShortcut, MenubarSub, MenubarSubContent, MenubarSubTrigger, MenubarTrigger, } from "./menubar.js";
5
- export { NavigationMenu, NavigationMenuContent, NavigationMenuIndicator, NavigationMenuItem, NavigationMenuLink, NavigationMenuList, NavigationMenuTrigger, NavigationMenuViewport, } from "./navigation-menu.js";
6
3
  export { Steps } from "./steps.js";
7
4
  export { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./filter-bar.js";
8
5
  export type { FilterBarChipProp, FilterBarChipProps, FilterBarFilterProp, FilterBarFilterProps, FilterBarGroupProp, FilterBarGroupProps, FilterBarOverflowProp, FilterBarProp, FilterBarProps, FilterBarSearchProp, FilterBarSearchProps, ToolbarGroupProps, ToolbarProps, } from "./filter-bar.js";
@@ -1,20 +1,4 @@
1
1
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs.js";
2
- import {
3
- ContextMenu,
4
- ContextMenuCheckboxItem,
5
- ContextMenuContent,
6
- ContextMenuItem,
7
- ContextMenuLabel,
8
- ContextMenuPortal,
9
- ContextMenuRadioGroup,
10
- ContextMenuRadioItem,
11
- ContextMenuSeparator,
12
- ContextMenuShortcut,
13
- ContextMenuSub,
14
- ContextMenuSubContent,
15
- ContextMenuSubTrigger,
16
- ContextMenuTrigger
17
- } from "./context-menu.js";
18
2
  import {
19
3
  Pagination,
20
4
  PaginationContent,
@@ -24,32 +8,6 @@ import {
24
8
  PaginationPrevious,
25
9
  PaginationNext
26
10
  } from "./pagination.js";
27
- import {
28
- Menubar,
29
- MenubarCheckboxItem,
30
- MenubarContent,
31
- MenubarItem,
32
- MenubarLabel,
33
- MenubarMenu,
34
- MenubarRadioGroup,
35
- MenubarRadioItem,
36
- MenubarSeparator,
37
- MenubarShortcut,
38
- MenubarSub,
39
- MenubarSubContent,
40
- MenubarSubTrigger,
41
- MenubarTrigger
42
- } from "./menubar.js";
43
- import {
44
- NavigationMenu,
45
- NavigationMenuContent,
46
- NavigationMenuIndicator,
47
- NavigationMenuItem,
48
- NavigationMenuLink,
49
- NavigationMenuList,
50
- NavigationMenuTrigger,
51
- NavigationMenuViewport
52
- } from "./navigation-menu.js";
53
11
  import { Steps } from "./steps.js";
54
12
  import { FilterBar, FilterBarGroup, Toolbar, ToolbarGroup } from "./filter-bar.js";
55
13
  import { AppSettingPicker } from "./app-setting-picker.js";
@@ -74,20 +32,6 @@ import {
74
32
  export {
75
33
  AppSettingPicker,
76
34
  AppSettingToggle,
77
- ContextMenu,
78
- ContextMenuCheckboxItem,
79
- ContextMenuContent,
80
- ContextMenuItem,
81
- ContextMenuLabel,
82
- ContextMenuPortal,
83
- ContextMenuRadioGroup,
84
- ContextMenuRadioItem,
85
- ContextMenuSeparator,
86
- ContextMenuShortcut,
87
- ContextMenuSub,
88
- ContextMenuSubContent,
89
- ContextMenuSubTrigger,
90
- ContextMenuTrigger,
91
35
  DropdownMenu,
92
36
  DropdownMenuCheckboxItem,
93
37
  DropdownMenuContent,
@@ -105,28 +49,6 @@ export {
105
49
  DropdownMenuTrigger,
106
50
  FilterBar,
107
51
  FilterBarGroup,
108
- Menubar,
109
- MenubarCheckboxItem,
110
- MenubarContent,
111
- MenubarItem,
112
- MenubarLabel,
113
- MenubarMenu,
114
- MenubarRadioGroup,
115
- MenubarRadioItem,
116
- MenubarSeparator,
117
- MenubarShortcut,
118
- MenubarSub,
119
- MenubarSubContent,
120
- MenubarSubTrigger,
121
- MenubarTrigger,
122
- NavigationMenu,
123
- NavigationMenuContent,
124
- NavigationMenuIndicator,
125
- NavigationMenuItem,
126
- NavigationMenuLink,
127
- NavigationMenuList,
128
- NavigationMenuTrigger,
129
- NavigationMenuViewport,
130
52
  Pagination,
131
53
  PaginationContent,
132
54
  PaginationEllipsis,
@@ -1,4 +1,4 @@
1
1
  import * as React from "react";
2
2
  import type { StepsProp } from "../../props/components/navigation.prop.js";
3
3
  export type { StepsProp, StepsProp as StepsProps, StepItemProp, StepStatusProp, StepsSeparatorProp, StepsTypeProp, } from "../../props/components/navigation.prop.js";
4
- export declare function Steps({ items, value: current, defaultValue, status: currentStatus, orientation, type, size, titlePlacement, separator, percent, onValueChange, className, }: StepsProp): React.JSX.Element;
4
+ export declare function Steps({ items, value: current, defaultValue, status: currentStatus, orientation, type, size, titlePlacement, separator, percent, onValueChange, className, ...props }: StepsProp): React.JSX.Element;
@@ -50,7 +50,8 @@ function Steps({
50
50
  separator = "chevron",
51
51
  percent,
52
52
  onValueChange,
53
- className
53
+ className,
54
+ ...props
54
55
  }) {
55
56
  const { t } = useTranslation();
56
57
  const base = defaultValue;
@@ -69,6 +70,7 @@ function Steps({
69
70
  "data-type": type,
70
71
  className: cn("flex w-full", inline ? "ui-steps-inline" : "ui-steps-list", className),
71
72
  "aria-label": t("navigation.steps.ariaLabel"),
73
+ ...props,
72
74
  children: items.map((item, index) => {
73
75
  const absoluteIndex = base + index;
74
76
  const stepStatus = resolveStepStatus(absoluteIndex, current, item.status, currentStatus);
@@ -34,3 +34,30 @@ export declare function syncActiveTabIntoView(list: HTMLElement | null, behavior
34
34
  * pre-fix behaviour instead of throwing.
35
35
  */
36
36
  export declare function useKeepActiveTabVisible(listRef: React.RefObject<HTMLElement | null>): void;
37
+ /**
38
+ * The values of the triggers that are NOT fully inside the strip's scrollport, in document order.
39
+ *
40
+ * Pure apart from the two rect reads — `values` is the `items` array's own order, and the triggers
41
+ * are matched to it by INDEX rather than by DOM id: `TabsTrigger` hands its `value` to React Aria
42
+ * as the collection KEY, and the rendered `id` attribute is RAC-generated, so the element carries
43
+ * no readable handle back to the item it came from.
44
+ *
45
+ * This is the measurement behind `overflow="menu"`. It is separated from the hook below so it can
46
+ * be exercised on plain numbers — jsdom lays nothing out, so the only honest unit assertions about
47
+ * it are the ones that hand it real geometry.
48
+ */
49
+ export declare function resolveHiddenTabValues(list: HTMLElement, values: readonly string[]): string[];
50
+ /**
51
+ * Keeps `onChange` fed with the values currently out of the scrollport, recomputing on the two
52
+ * things that can move them: the strip resizing (a viewport change, a font swap, a tab added or
53
+ * removed) and the strip scrolling (the user swiping, or `useKeepActiveTabVisible` re-pinning a
54
+ * trigger).
55
+ *
56
+ * `values` is joined into the effect key rather than passed as a dependency, so a consumer that
57
+ * rebuilds its `items` array on every render does not re-arm the observers on every render.
58
+ *
59
+ * The first measurement ALWAYS reports, even when nothing is hidden. Suppressing it looks harmless
60
+ * and is not: on a remount the local `last` resets while the caller's state does not, so a strip
61
+ * that had overflowed and then stopped would keep a stale menu forever.
62
+ */
63
+ export declare function useTabsOverflowValues(listRef: React.RefObject<HTMLElement | null>, values: readonly string[] | undefined, onChange: (hidden: string[]) => void): void;