@evinvest/uikit 0.30.0 → 0.31.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 (204) hide show
  1. package/README.md +266 -72
  2. package/dist/{chunk-3IPB4QQJ.js → chunk-35SPIU4U.js} +10 -6
  3. package/dist/chunk-35SPIU4U.js.map +1 -0
  4. package/dist/components/account-list.js +128 -0
  5. package/dist/components/account-list.js.map +1 -0
  6. package/dist/components/account-menu-profile.js +114 -0
  7. package/dist/components/account-menu-profile.js.map +1 -0
  8. package/dist/components/account-menu.js +50 -23
  9. package/dist/components/account-menu.js.map +1 -1
  10. package/dist/components/account-settings-dialog.js +84 -0
  11. package/dist/components/account-settings-dialog.js.map +1 -0
  12. package/dist/components/alert-dialog.js +3 -2
  13. package/dist/components/alert-dialog.js.map +1 -1
  14. package/dist/components/animated-number-columns.js +105 -0
  15. package/dist/components/animated-number-columns.js.map +1 -0
  16. package/dist/components/animated-number.js +157 -0
  17. package/dist/components/animated-number.js.map +1 -0
  18. package/dist/components/app-shell.js +9 -0
  19. package/dist/components/app-shell.js.map +1 -1
  20. package/dist/components/aside.js +140 -0
  21. package/dist/components/aside.js.map +1 -0
  22. package/dist/components/badge.js +1 -1
  23. package/dist/components/badge.js.map +1 -1
  24. package/dist/components/button.js +3 -11
  25. package/dist/components/button.js.map +1 -1
  26. package/dist/components/chat-composer.js +141 -0
  27. package/dist/components/chat-composer.js.map +1 -0
  28. package/dist/components/chat-message.js +195 -0
  29. package/dist/components/chat-message.js.map +1 -0
  30. package/dist/components/chat-panel.js +137 -0
  31. package/dist/components/chat-panel.js.map +1 -0
  32. package/dist/components/chat-stream-text.js +69 -0
  33. package/dist/components/chat-stream-text.js.map +1 -0
  34. package/dist/components/chat-typing.js +26 -0
  35. package/dist/components/chat-typing.js.map +1 -0
  36. package/dist/components/command-palette.js +119 -0
  37. package/dist/components/command-palette.js.map +1 -0
  38. package/dist/components/command.js +3 -1
  39. package/dist/components/command.js.map +1 -1
  40. package/dist/components/confirm-dialog.js +123 -0
  41. package/dist/components/confirm-dialog.js.map +1 -0
  42. package/dist/components/context-menu.js +53 -5
  43. package/dist/components/context-menu.js.map +1 -1
  44. package/dist/components/dialog.js +3 -2
  45. package/dist/components/dialog.js.map +1 -1
  46. package/dist/components/drawer.js +3 -1
  47. package/dist/components/drawer.js.map +1 -1
  48. package/dist/components/dropdown-menu.js +64 -5
  49. package/dist/components/dropdown-menu.js.map +1 -1
  50. package/dist/components/form-row.js +51 -0
  51. package/dist/components/form-row.js.map +1 -0
  52. package/dist/components/header-controls.js +149 -0
  53. package/dist/components/header-controls.js.map +1 -0
  54. package/dist/components/icon-button.js +35 -0
  55. package/dist/components/icon-button.js.map +1 -0
  56. package/dist/components/key-value.js +68 -0
  57. package/dist/components/key-value.js.map +1 -0
  58. package/dist/components/menubar.js +39 -5
  59. package/dist/components/menubar.js.map +1 -1
  60. package/dist/components/native-select.js +3 -4
  61. package/dist/components/native-select.js.map +1 -1
  62. package/dist/components/nav-collapse.js +142 -0
  63. package/dist/components/nav-collapse.js.map +1 -0
  64. package/dist/components/nav-model.js.map +1 -1
  65. package/dist/components/navigation-menu.js +4 -5
  66. package/dist/components/navigation-menu.js.map +1 -1
  67. package/dist/components/page-frame.js +2 -5
  68. package/dist/components/page-frame.js.map +1 -1
  69. package/dist/components/popover-form.js +60 -0
  70. package/dist/components/popover-form.js.map +1 -0
  71. package/dist/components/resource-error.js +1 -1
  72. package/dist/components/resource-error.js.map +1 -1
  73. package/dist/components/section-nav.js +73 -20
  74. package/dist/components/section-nav.js.map +1 -1
  75. package/dist/components/select.js +16 -6
  76. package/dist/components/select.js.map +1 -1
  77. package/dist/components/settings.js +94 -0
  78. package/dist/components/settings.js.map +1 -0
  79. package/dist/components/sheet.js +3 -2
  80. package/dist/components/sheet.js.map +1 -1
  81. package/dist/components/shell-nav.js +80 -38
  82. package/dist/components/shell-nav.js.map +1 -1
  83. package/dist/components/smart-header.js +53 -0
  84. package/dist/components/smart-header.js.map +1 -0
  85. package/dist/components/status-item.js +42 -0
  86. package/dist/components/status-item.js.map +1 -0
  87. package/dist/components/system-banner.js +22 -12
  88. package/dist/components/system-banner.js.map +1 -1
  89. package/dist/components/top-bar.js +4 -3
  90. package/dist/components/top-bar.js.map +1 -1
  91. package/dist/components/ui-icons.js +77 -0
  92. package/dist/components/ui-icons.js.map +1 -0
  93. package/dist/components/use-count-tween.js +42 -0
  94. package/dist/components/use-count-tween.js.map +1 -0
  95. package/dist/components/workspace-tab.js +140 -0
  96. package/dist/components/workspace-tab.js.map +1 -0
  97. package/dist/generated/accent.js +2 -2
  98. package/dist/generated/accent.js.map +1 -1
  99. package/dist/generated/account-list.js +35 -0
  100. package/dist/generated/account-list.js.map +1 -0
  101. package/dist/generated/account-settings-dialog.js +27 -0
  102. package/dist/generated/account-settings-dialog.js.map +1 -0
  103. package/dist/generated/alert-dialog.js +1 -1
  104. package/dist/generated/alert-dialog.js.map +1 -1
  105. package/dist/generated/alert.js +2 -2
  106. package/dist/generated/alert.js.map +1 -1
  107. package/dist/generated/aside.js +36 -0
  108. package/dist/generated/aside.js.map +1 -0
  109. package/dist/generated/badge.js +7 -6
  110. package/dist/generated/badge.js.map +1 -1
  111. package/dist/generated/band.js +1 -1
  112. package/dist/generated/band.js.map +1 -1
  113. package/dist/generated/breadcrumb.js +2 -2
  114. package/dist/generated/breadcrumb.js.map +1 -1
  115. package/dist/generated/button-group.js +2 -2
  116. package/dist/generated/button-group.js.map +1 -1
  117. package/dist/generated/button.js +4 -4
  118. package/dist/generated/button.js.map +1 -1
  119. package/dist/generated/card.js +6 -6
  120. package/dist/generated/card.js.map +1 -1
  121. package/dist/generated/chat-composer.js +21 -0
  122. package/dist/generated/chat-composer.js.map +1 -0
  123. package/dist/generated/chat-message.js +86 -0
  124. package/dist/generated/chat-message.js.map +1 -0
  125. package/dist/generated/chat-typing.js +9 -0
  126. package/dist/generated/chat-typing.js.map +1 -0
  127. package/dist/generated/command-palette.js +17 -0
  128. package/dist/generated/command-palette.js.map +1 -0
  129. package/dist/generated/command.js +2 -2
  130. package/dist/generated/command.js.map +1 -1
  131. package/dist/generated/confirm-dialog.js +28 -0
  132. package/dist/generated/confirm-dialog.js.map +1 -0
  133. package/dist/generated/context-menu.js +1 -1
  134. package/dist/generated/context-menu.js.map +1 -1
  135. package/dist/generated/date-time-picker.js +1 -1
  136. package/dist/generated/date-time-picker.js.map +1 -1
  137. package/dist/generated/dialog.js +1 -1
  138. package/dist/generated/dialog.js.map +1 -1
  139. package/dist/generated/drawer.js +1 -1
  140. package/dist/generated/drawer.js.map +1 -1
  141. package/dist/generated/dropdown-menu.js +1 -1
  142. package/dist/generated/dropdown-menu.js.map +1 -1
  143. package/dist/generated/form-row.js +17 -0
  144. package/dist/generated/form-row.js.map +1 -0
  145. package/dist/generated/header-controls.js +56 -0
  146. package/dist/generated/header-controls.js.map +1 -0
  147. package/dist/generated/icon-button.js +13 -0
  148. package/dist/generated/icon-button.js.map +1 -0
  149. package/dist/generated/key-value.js +15 -0
  150. package/dist/generated/key-value.js.map +1 -0
  151. package/dist/generated/menubar.js +1 -1
  152. package/dist/generated/menubar.js.map +1 -1
  153. package/dist/generated/navigation-menu.js +1 -1
  154. package/dist/generated/navigation-menu.js.map +1 -1
  155. package/dist/generated/popover-form.js +13 -0
  156. package/dist/generated/popover-form.js.map +1 -0
  157. package/dist/generated/section-label.js +10 -0
  158. package/dist/generated/section-label.js.map +1 -0
  159. package/dist/generated/select.js +15 -3
  160. package/dist/generated/select.js.map +1 -1
  161. package/dist/generated/settings.js +36 -0
  162. package/dist/generated/settings.js.map +1 -0
  163. package/dist/generated/sheet.js +1 -1
  164. package/dist/generated/sheet.js.map +1 -1
  165. package/dist/generated/slider.js +1 -1
  166. package/dist/generated/slider.js.map +1 -1
  167. package/dist/generated/smart-header.js +11 -0
  168. package/dist/generated/smart-header.js.map +1 -0
  169. package/dist/generated/sonner.js +4 -4
  170. package/dist/generated/sonner.js.map +1 -1
  171. package/dist/generated/status-item.js +17 -0
  172. package/dist/generated/status-item.js.map +1 -0
  173. package/dist/generated/system-banner.js +33 -0
  174. package/dist/generated/system-banner.js.map +1 -0
  175. package/dist/generated/top-bar.js +9 -0
  176. package/dist/generated/top-bar.js.map +1 -0
  177. package/dist/generated/workspace-tab.js +17 -0
  178. package/dist/generated/workspace-tab.js.map +1 -0
  179. package/dist/index.d.ts +852 -156
  180. package/dist/index.js +21 -1
  181. package/dist/lib/cn.js +8 -1
  182. package/dist/lib/cn.js.map +1 -1
  183. package/dist/lib/number-parts.js +90 -0
  184. package/dist/lib/number-parts.js.map +1 -0
  185. package/dist/palette-cli.js +1 -1
  186. package/dist/palette.d.ts +6 -4
  187. package/dist/palette.js +3 -1
  188. package/dist/primitives/dismissable-layer.js +76 -4
  189. package/dist/primitives/dismissable-layer.js.map +1 -1
  190. package/dist/primitives/focus-scope.js +20 -5
  191. package/dist/primitives/focus-scope.js.map +1 -1
  192. package/dist/primitives/use-floating.js +16 -1
  193. package/dist/primitives/use-floating.js.map +1 -1
  194. package/dist/primitives/use-reduced-motion.js +20 -0
  195. package/dist/primitives/use-reduced-motion.js.map +1 -0
  196. package/package.json +1 -1
  197. package/styles/ev.css +281 -49
  198. package/styles/theme.css +335 -39
  199. package/styles/tokens.css +643 -103
  200. package/dist/chunk-3IPB4QQJ.js.map +0 -1
  201. package/dist/components/terminal.js +0 -206
  202. package/dist/components/terminal.js.map +0 -1
  203. package/dist/generated/terminal.js +0 -70
  204. package/dist/generated/terminal.js.map +0 -1
@@ -0,0 +1,26 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import * as React from "react";
3
+ import { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from "../generated/chat-typing.js";
4
+ import { cn } from "../lib/cn.js";
5
+ function ChatTyping({ label = "Assistant is thinking", className, ...props }) {
6
+ return /* @__PURE__ */ jsxs(
7
+ "span",
8
+ {
9
+ role: "status",
10
+ "aria-label": label,
11
+ "data-slot": "chat-typing",
12
+ className: cn(CHAT_TYPING_BASE, className),
13
+ ...props,
14
+ children: [
15
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-dot": true, className: CHAT_TYPING_DOT }),
16
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-dot": true, className: CHAT_TYPING_DOT }),
17
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-dot": true, className: CHAT_TYPING_DOT }),
18
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, "data-still": true, className: CHAT_TYPING_STILL, children: "\u2026" })
19
+ ]
20
+ }
21
+ );
22
+ }
23
+ export {
24
+ ChatTyping
25
+ };
26
+ //# sourceMappingURL=chat-typing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/chat-typing.tsx"],"sourcesContent":["import * as React from \"react\";\nimport { CHAT_TYPING_BASE, CHAT_TYPING_DOT, CHAT_TYPING_STILL } from \"../generated/chat-typing\";\nimport { cn } from \"../lib/cn\";\n\nexport interface ChatTypingProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n /** Names the state for assistive tech. Default `\"Assistant is thinking\"`. */\n label?: string;\n}\n\n/**\n * The wait before the first token: three dots whose opacity ripples. Under\n * reduced motion CSS swaps them for a still \"…\". It is a status region, so a\n * screen reader hears the label once when it appears.\n */\nexport function ChatTyping({ label = \"Assistant is thinking\", className, ...props }: ChatTypingProps) {\n return (\n <span\n role=\"status\"\n aria-label={label}\n data-slot=\"chat-typing\"\n className={cn(CHAT_TYPING_BASE, className)}\n {...props}\n >\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-dot className={CHAT_TYPING_DOT} />\n <span aria-hidden data-still className={CHAT_TYPING_STILL}>\n …\n </span>\n </span>\n );\n}\n"],"mappings":"AAgBI,SAOE,KAPF;AAhBJ,YAAY,WAAW;AACvB,SAAS,kBAAkB,iBAAiB,yBAAyB;AACrE,SAAS,UAAU;AAYZ,SAAS,WAAW,EAAE,QAAQ,yBAAyB,WAAW,GAAG,MAAM,GAAoB;AACpG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,aAAU;AAAA,MACV,WAAW,GAAG,kBAAkB,SAAS;AAAA,MACxC,GAAG;AAAA,MAEJ;AAAA,4BAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,YAAQ,MAAC,WAAW,iBAAiB;AAAA,QACvD,oBAAC,UAAK,eAAW,MAAC,cAAU,MAAC,WAAW,mBAAmB,oBAE3D;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,119 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import {
5
+ COMMAND_PALETTE_COMMAND,
6
+ COMMAND_PALETTE_CONTENT,
7
+ COMMAND_PALETTE_HINT,
8
+ COMMAND_PALETTE_HINTS,
9
+ COMMAND_PALETTE_LABEL,
10
+ COMMAND_PALETTE_LIST,
11
+ COMMAND_PALETTE_TITLE
12
+ } from "../generated/command-palette.js";
13
+ import { cn } from "../lib/cn.js";
14
+ import { useControllableState } from "../primitives/use-controllable-state.js";
15
+ import {
16
+ Command,
17
+ CommandEmpty,
18
+ CommandGroup,
19
+ CommandInput,
20
+ CommandItem,
21
+ CommandList,
22
+ CommandSeparator,
23
+ CommandShortcut
24
+ } from "./command.js";
25
+ import { Dialog, DialogContent, DialogTitle } from "./dialog.js";
26
+ import { Kbd } from "./kbd.js";
27
+ function CommandPalette({
28
+ open,
29
+ defaultOpen = false,
30
+ onOpenChange,
31
+ groups,
32
+ labels = {},
33
+ className
34
+ }) {
35
+ const [isOpen, setOpen] = useControllableState({
36
+ ...open !== void 0 ? { value: open } : {},
37
+ defaultValue: defaultOpen,
38
+ ...onOpenChange ? { onChange: onOpenChange } : {}
39
+ });
40
+ return /* @__PURE__ */ jsx(Dialog, { open: isOpen, onOpenChange: setOpen, children: /* @__PURE__ */ jsxs(
41
+ DialogContent,
42
+ {
43
+ showCloseButton: false,
44
+ className: cn(COMMAND_PALETTE_CONTENT, className),
45
+ children: [
46
+ /* @__PURE__ */ jsx(DialogTitle, { className: COMMAND_PALETTE_TITLE, children: labels.title ?? "Command palette" }),
47
+ /* @__PURE__ */ jsxs(Command, { className: COMMAND_PALETTE_COMMAND, children: [
48
+ /* @__PURE__ */ jsx(CommandInput, { placeholder: labels.placeholder ?? "Type a command or search\u2026" }),
49
+ /* @__PURE__ */ jsxs(CommandList, { className: COMMAND_PALETTE_LIST, children: [
50
+ /* @__PURE__ */ jsx(CommandEmpty, { children: labels.empty ?? "No results." }),
51
+ groups.map((group, i) => /* @__PURE__ */ jsxs(React.Fragment, { children: [
52
+ i > 0 && /* @__PURE__ */ jsx(CommandSeparator, {}),
53
+ /* @__PURE__ */ jsx(CommandGroup, { ...group.heading !== void 0 ? { heading: group.heading } : {}, children: group.items.map((item) => /* @__PURE__ */ jsxs(
54
+ CommandItem,
55
+ {
56
+ value: [item.label, ...item.keywords ?? []].join(" "),
57
+ disabled: item.disabled ?? false,
58
+ onSelect: () => {
59
+ setOpen(false);
60
+ item.onSelect();
61
+ },
62
+ children: [
63
+ item.icon,
64
+ /* @__PURE__ */ jsx("span", { className: COMMAND_PALETTE_LABEL, children: item.label }),
65
+ item.shortcut !== void 0 && /* @__PURE__ */ jsx(CommandShortcut, { children: item.shortcut })
66
+ ]
67
+ },
68
+ item.id
69
+ )) })
70
+ ] }, group.id))
71
+ ] })
72
+ ] }),
73
+ /* @__PURE__ */ jsxs(
74
+ "div",
75
+ {
76
+ "data-slot": "command-palette-hints",
77
+ className: COMMAND_PALETTE_HINTS,
78
+ children: [
79
+ /* @__PURE__ */ jsxs("span", { className: COMMAND_PALETTE_HINT, children: [
80
+ /* @__PURE__ */ jsx(Kbd, { children: "\u2191\u2193" }),
81
+ labels.navigate ?? "navigate"
82
+ ] }),
83
+ /* @__PURE__ */ jsxs("span", { className: COMMAND_PALETTE_HINT, children: [
84
+ /* @__PURE__ */ jsx(Kbd, { children: "\u21B5" }),
85
+ labels.select ?? "select"
86
+ ] }),
87
+ /* @__PURE__ */ jsxs("span", { className: COMMAND_PALETTE_HINT, children: [
88
+ /* @__PURE__ */ jsx(Kbd, { children: "esc" }),
89
+ labels.close ?? "close"
90
+ ] })
91
+ ]
92
+ }
93
+ )
94
+ ]
95
+ }
96
+ ) });
97
+ }
98
+ function useCommandPaletteShortcut(onTrigger, enabled = true) {
99
+ const ref = React.useRef(onTrigger);
100
+ React.useEffect(() => {
101
+ ref.current = onTrigger;
102
+ });
103
+ React.useEffect(() => {
104
+ if (!enabled) return;
105
+ const onKeyDown = (e) => {
106
+ if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.code === "KeyK") {
107
+ e.preventDefault();
108
+ if (!e.repeat) ref.current();
109
+ }
110
+ };
111
+ document.addEventListener("keydown", onKeyDown);
112
+ return () => document.removeEventListener("keydown", onKeyDown);
113
+ }, [enabled]);
114
+ }
115
+ export {
116
+ CommandPalette,
117
+ useCommandPaletteShortcut
118
+ };
119
+ //# sourceMappingURL=command-palette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/command-palette.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport {\n COMMAND_PALETTE_COMMAND,\n COMMAND_PALETTE_CONTENT,\n COMMAND_PALETTE_HINT,\n COMMAND_PALETTE_HINTS,\n COMMAND_PALETTE_LABEL,\n COMMAND_PALETTE_LIST,\n COMMAND_PALETTE_TITLE,\n} from \"../generated/command-palette\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n CommandSeparator,\n CommandShortcut,\n} from \"./command\";\nimport { Dialog, DialogContent, DialogTitle } from \"./dialog\";\nimport { Kbd } from \"./kbd\";\n\nexport interface CommandPaletteItem {\n id: string;\n label: string;\n icon?: React.ReactNode;\n /** Shown at the row end, e.g. \"⌘N\". */\n shortcut?: string;\n /** Extra words the search matches besides the label. */\n keywords?: readonly string[];\n disabled?: boolean;\n onSelect: () => void;\n}\n\nexport interface CommandPaletteGroup {\n id: string;\n heading?: string;\n items: readonly CommandPaletteItem[];\n}\n\nexport interface CommandPaletteLabels {\n /** Names the dialog for assistive tech. Default `\"Command palette\"`. */\n title?: string;\n placeholder?: string;\n empty?: string;\n navigate?: string;\n select?: string;\n close?: string;\n}\n\nexport interface CommandPaletteProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n groups: readonly CommandPaletteGroup[];\n labels?: CommandPaletteLabels;\n className?: string;\n}\n\n// Built on Dialog + Command rather than the kit's CommandDialog: that one has no accessible\n// name and no footer, and both need Dialog's title and focus wiring.\n/**\n * Search-and-run over a list of actions. Choosing a row runs it and closes the\n * palette. Pair it with `useCommandPaletteShortcut` for ⌘K / Ctrl+K.\n */\nexport function CommandPalette({\n open,\n defaultOpen = false,\n onOpenChange,\n groups,\n labels = {},\n className,\n}: CommandPaletteProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n return (\n <Dialog open={isOpen} onOpenChange={setOpen}>\n <DialogContent\n showCloseButton={false}\n className={cn(COMMAND_PALETTE_CONTENT, className)}\n >\n <DialogTitle className={COMMAND_PALETTE_TITLE}>{labels.title ?? \"Command palette\"}</DialogTitle>\n <Command className={COMMAND_PALETTE_COMMAND}>\n <CommandInput placeholder={labels.placeholder ?? \"Type a command or search…\"} />\n <CommandList className={COMMAND_PALETTE_LIST}>\n <CommandEmpty>{labels.empty ?? \"No results.\"}</CommandEmpty>\n {groups.map((group, i) => (\n <React.Fragment key={group.id}>\n {i > 0 && <CommandSeparator />}\n <CommandGroup {...(group.heading !== undefined ? { heading: group.heading } : {})}>\n {group.items.map((item) => (\n <CommandItem\n key={item.id}\n value={[item.label, ...(item.keywords ?? [])].join(\" \")}\n disabled={item.disabled ?? false}\n onSelect={() => {\n setOpen(false);\n item.onSelect();\n }}\n >\n {item.icon}\n <span className={COMMAND_PALETTE_LABEL}>{item.label}</span>\n {item.shortcut !== undefined && <CommandShortcut>{item.shortcut}</CommandShortcut>}\n </CommandItem>\n ))}\n </CommandGroup>\n </React.Fragment>\n ))}\n </CommandList>\n </Command>\n <div\n data-slot=\"command-palette-hints\"\n className={COMMAND_PALETTE_HINTS}\n >\n <span className={COMMAND_PALETTE_HINT}>\n <Kbd>↑↓</Kbd>\n {labels.navigate ?? \"navigate\"}\n </span>\n <span className={COMMAND_PALETTE_HINT}>\n <Kbd>↵</Kbd>\n {labels.select ?? \"select\"}\n </span>\n <span className={COMMAND_PALETTE_HINT}>\n <Kbd>esc</Kbd>\n {labels.close ?? \"close\"}\n </span>\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n\n/**\n * Calls `onTrigger` on ⌘K (Mac) or Ctrl+K, and keeps the browser from taking\n * the key (Chrome focuses the address bar on Ctrl+K in some layouts). Matches the\n * physical key (`code`), so it also works on a Russian or other non-Latin layout.\n */\nexport function useCommandPaletteShortcut(onTrigger: () => void, enabled = true): void {\n // Latest callback without re-binding the listener on every render.\n const ref = React.useRef(onTrigger);\n React.useEffect(() => {\n ref.current = onTrigger;\n });\n React.useEffect(() => {\n if (!enabled) return;\n const onKeyDown = (e: KeyboardEvent) => {\n if ((e.metaKey || e.ctrlKey) && !e.altKey && !e.shiftKey && e.code === \"KeyK\") {\n e.preventDefault();\n // A held key would toggle the palette open and shut.\n if (!e.repeat) ref.current();\n }\n };\n document.addEventListener(\"keydown\", onKeyDown);\n return () => document.removeEventListener(\"keydown\", onKeyDown);\n }, [enabled]);\n}\n"],"mappings":"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","names":[]}
@@ -3,7 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { cn } from "../lib/cn.js";
5
5
  import { useControllableState } from "../primitives/use-controllable-state.js";
6
- import { DismissableLayerScope, useDismissableLayer } from "../primitives/dismissable-layer.js";
6
+ import { DismissableLayerScope, useDismissableLayer, useModalScrimStyle } from "../primitives/dismissable-layer.js";
7
7
  import { useFocusScope } from "../primitives/focus-scope.js";
8
8
  import { mergeRefs } from "../primitives/merge-refs.js";
9
9
  import { Portal } from "../primitives/portal.js";
@@ -237,6 +237,7 @@ function CommandDialog({
237
237
  pointerOutside: false,
238
238
  modal: true
239
239
  });
240
+ const scrimStyle = useModalScrimStyle(dismissRef);
240
241
  if (!isOpen) return null;
241
242
  return /* @__PURE__ */ jsxs(Portal, { children: [
242
243
  /* @__PURE__ */ jsx(
@@ -244,6 +245,7 @@ function CommandDialog({
244
245
  {
245
246
  "data-slot": "command-overlay",
246
247
  className: COMMAND_DIALOG_OVERLAY,
248
+ style: scrimStyle,
247
249
  onClick: () => setOpen(false)
248
250
  }
249
251
  ),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/command.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer } from \"../primitives/dismissable-layer\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport {\n COMMAND_DIALOG_COMMAND,\n COMMAND_DIALOG_CONTENT,\n COMMAND_DIALOG_OVERLAY,\n COMMAND_EMPTY,\n COMMAND_GROUP,\n COMMAND_INPUT,\n COMMAND_INPUT_WRAPPER,\n COMMAND_ITEM,\n COMMAND_LIST,\n COMMAND_ROOT,\n COMMAND_SEPARATOR,\n COMMAND_SHORTCUT,\n} from \"../generated/command\";\n\ninterface CommandContextValue {\n search: string;\n setSearch: (next: string) => void;\n /// Registers a `CommandItem`'s value so `CommandEmpty` can tell \"nothing\n /// matched\" from \"nothing is here\", and its select handler so Enter can fire\n /// it. Items register even while filtered out.\n registerItem: (id: string, value: string, select: () => void) => void;\n unregisterItem: (id: string) => void;\n /// The active query: trimmed, so blank input is not a search, and lowercased.\n /// Shared by the item filter and the empty-state gate so the two can never\n /// disagree.\n query: string;\n /// `null` filters the item out; a number is its CSS `order`, `0` while\n /// nothing is being ranked.\n order: (value: string) => number | null;\n hasMatches: boolean;\n listId: string;\n /// DOM id of the highlighted item. Focus stays in the input and points here\n /// through `aria-activedescendant` (the ARIA combobox pattern), so items\n /// never need to be tabbable.\n activeId: string | null;\n activate: (id: string) => void;\n}\n\nconst CommandContext = React.createContext<CommandContextValue | null>(null);\n\nfunction useCommand(): CommandContextValue {\n const ctx = React.useContext(CommandContext);\n if (!ctx) throw new Error(\"Command parts must be used within <Command>\");\n return ctx;\n}\n\nexport interface CommandProps extends React.ComponentProps<\"div\"> {\n search?: string;\n defaultSearch?: string;\n onSearchChange?: (search: string) => void;\n /// `false` hands filtering to the caller (e.g. rows that are a server\n /// response to the query): every mounted item renders, in the caller's\n /// order, and `CommandEmpty` counts mounted items. Defaults to `true`.\n shouldFilter?: boolean;\n}\n\nconst ITEM_SELECTOR = '[data-slot=\"command-item\"]:not([aria-disabled=\"true\"])';\n\n/// fzf v1: per space-separated term, the shortest window ending at the first\n/// full subsequence match, scored for word starts and runs, charged for gaps.\n/// `null` when a term does not match.\nfunction fuzzyScore(value: string, query: string): number | null {\n const hay = [...value.toLowerCase()];\n let total = 0;\n for (const term of query.split(/\\s+/)) {\n const needle = [...term];\n let k = 0;\n let end = -1;\n for (let i = 0; i < hay.length; i++) {\n if (hay[i] === needle[k] && ++k === needle.length) {\n end = i;\n break;\n }\n }\n if (end < 0) return null;\n let start = end;\n for (let i = end, j = needle.length - 1; j >= 0; i--) {\n if (hay[i] === needle[j]) {\n start = i;\n j--;\n }\n }\n let prev = -1;\n for (let i = start, j = 0; j < needle.length; i++) {\n if (hay[i] !== needle[j]) continue;\n total += 16;\n if (i === 0 || !/[\\p{L}\\p{N}]/u.test(hay[i - 1]!)) total += 8;\n if (j > 0) total += i === prev + 1 ? 4 : -(3 + (i - prev - 2));\n prev = i;\n j++;\n }\n }\n return total;\n}\n\n/// Keyboard highlight over the items currently in the DOM, in the order on\n/// screen — each container's rows by their CSS `order`, then DOM order — so it\n/// follows whatever the filter (or the caller) rendered rather than the order\n/// items happened to register in.\nfunction useCommandNavigation(\n query: string,\n handlers: React.RefObject<Map<string, () => void>>,\n) {\n const rootRef = React.useRef<HTMLDivElement>(null);\n const [activeId, setActiveId] = React.useState<string | null>(null);\n // Until the user moves the highlight it tracks the first row, so results\n // that arrive after the keystroke (or reorder under it) put Enter on the top\n // hit. Once moved, it sticks for as long as that row stays.\n const userMoved = React.useRef(false);\n\n const enabledItems = React.useCallback((): HTMLElement[] => {\n const root = rootRef.current;\n if (!root) return [];\n const byParent = new Map<Element | null, HTMLElement[]>();\n for (const el of root.querySelectorAll<HTMLElement>(ITEM_SELECTOR)) {\n if (el.closest('[data-slot=\"command\"]') !== root) continue;\n const rows = byParent.get(el.parentElement) ?? [];\n rows.push(el);\n byParent.set(el.parentElement, rows);\n }\n return [...byParent.values()].flatMap((rows) =>\n rows.sort((a, b) => Number(a.style.order) - Number(b.style.order)),\n );\n }, []);\n\n React.useEffect(() => {\n userMoved.current = false;\n }, [query]);\n\n // No deps on purpose: the caller can change the rendered rows on any render;\n // setting an unchanged id bails out without re-rendering.\n React.useEffect(() => {\n const els = enabledItems();\n const moved = userMoved.current;\n setActiveId((prev) =>\n moved && prev !== null && els.some((el) => el.id === prev)\n ? prev\n : (els[0]?.id ?? null),\n );\n });\n\n const activate = React.useCallback((id: string) => {\n userMoved.current = true;\n setActiveId(id);\n }, []);\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.nativeEvent.isComposing) return;\n // Only the search field drives the list: Enter or an arrow on a button\n // inside Command (an action in `CommandEmpty`, a footer) stays its own.\n const field = event.target as HTMLElement;\n if (field.dataset[\"slot\"] !== \"command-input\") return;\n const els = enabledItems();\n const current = els.findIndex((el) => el.id === activeId);\n const last = els.length - 1;\n let next: number;\n switch (event.key) {\n case \"ArrowDown\":\n next = current < 0 ? 0 : Math.min(current + 1, last);\n break;\n case \"ArrowUp\":\n next = current < 0 ? last : Math.max(current - 1, 0);\n break;\n // Home / End are left to the caret: the field is editable (ARIA APG\n // editable combobox).\n case \"Enter\": {\n const select =\n activeId !== null && current >= 0\n ? handlers.current.get(activeId)\n : undefined;\n if (select) {\n event.preventDefault();\n select();\n }\n return;\n }\n default:\n return;\n }\n const target = els[next];\n if (!target) return;\n event.preventDefault();\n activate(target.id);\n // Optional call: jsdom implements no `scrollIntoView`.\n target.scrollIntoView?.({ block: \"nearest\" });\n };\n\n return { rootRef, activeId, activate, onKeyDown };\n}\n\nexport function Command({\n className,\n search,\n defaultSearch = \"\",\n onSearchChange,\n shouldFilter = true,\n children,\n ref,\n onKeyDown,\n ...props\n}: CommandProps) {\n const [currentSearch, setSearch] = useControllableState<string>({\n ...(search !== undefined ? { value: search } : {}),\n defaultValue: defaultSearch,\n ...(onSearchChange ? { onChange: onSearchChange } : {}),\n });\n\n const [items, setItems] = React.useState<ReadonlyMap<string, string>>(\n () => new Map(),\n );\n const handlers = React.useRef(new Map<string, () => void>());\n const registerItem = React.useCallback(\n (id: string, value: string, select: () => void) => {\n handlers.current.set(id, select);\n setItems((prev) => {\n if (prev.get(id) === value) return prev;\n const next = new Map(prev);\n next.set(id, value);\n return next;\n });\n },\n [],\n );\n const unregisterItem = React.useCallback((id: string) => {\n handlers.current.delete(id);\n setItems((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n }, []);\n\n const query = currentSearch.trim().toLowerCase();\n const order = React.useCallback(\n (value: string) => {\n if (!shouldFilter || query === \"\") return 0;\n const score = fuzzyScore(value, query);\n return score === null ? null : -score;\n },\n [query, shouldFilter],\n );\n const hasMatches = React.useMemo(\n () => [...items.values()].some((value) => order(value) !== null),\n [items, order],\n );\n\n const listId = React.useId();\n const nav = useCommandNavigation(query, handlers);\n\n return (\n <CommandContext.Provider\n value={{\n search: currentSearch,\n setSearch,\n registerItem,\n unregisterItem,\n query,\n order,\n hasMatches,\n listId,\n activeId: nav.activeId,\n activate: nav.activate,\n }}\n >\n <div\n data-slot=\"command\"\n ref={mergeRefs(nav.rootRef, ref)}\n className={cn(COMMAND_ROOT, className)}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (!e.defaultPrevented) nav.onKeyDown(e);\n }}\n {...props}\n >\n {children}\n </div>\n </CommandContext.Provider>\n );\n}\n\nexport interface CommandDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /// See `CommandProps.shouldFilter`.\n shouldFilter?: boolean;\n className?: string;\n children?: React.ReactNode;\n}\n\nexport function CommandDialog({\n open,\n defaultOpen = false,\n onOpenChange,\n shouldFilter = true,\n className,\n children,\n}: CommandDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const scopeRef = useFocusScope(isOpen);\n // Outside clicks stay with the overlay, as in Drawer: it is what \"outside\" means.\n const dismissRef = useDismissableLayer({\n enabled: isOpen,\n onDismiss: () => setOpen(false),\n pointerOutside: false,\n modal: true,\n });\n if (!isOpen) return null;\n return (\n <Portal>\n <div\n data-slot=\"command-overlay\"\n className={COMMAND_DIALOG_OVERLAY}\n onClick={() => setOpen(false)}\n />\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n data-slot=\"command-dialog\"\n ref={mergeRefs(scopeRef, dismissRef)}\n className={cn(COMMAND_DIALOG_CONTENT, className)}\n >\n <Command className={COMMAND_DIALOG_COMMAND} shouldFilter={shouldFilter}>\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </Command>\n </div>\n </Portal>\n );\n}\n\nexport function CommandInput({\n className,\n ...props\n}: React.ComponentProps<\"input\">) {\n const { search, setSearch, listId, activeId } = useCommand();\n return (\n <div className={COMMAND_INPUT_WRAPPER} data-slot=\"command-input-wrapper\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-4 shrink-0 opacity-50\"\n aria-hidden=\"true\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n <input\n type=\"text\"\n role=\"combobox\"\n data-slot=\"command-input\"\n aria-autocomplete=\"list\"\n aria-expanded={true}\n aria-controls={listId}\n aria-activedescendant={activeId ?? undefined}\n autoComplete=\"off\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className={cn(COMMAND_INPUT, className)}\n {...props}\n />\n </div>\n );\n}\n\nexport function CommandList({ className, ...props }: React.ComponentProps<\"div\">) {\n const { listId } = useCommand();\n return (\n <div\n role=\"listbox\"\n id={listId}\n data-slot=\"command-list\"\n className={cn(COMMAND_LIST, className)}\n {...props}\n />\n );\n}\n\n/// Renders only when a search is under way and no item matched it — never next\n/// to results, and never before the user has typed. With `shouldFilter={false}`\n/// \"matched\" means \"is mounted\": the caller's rows are the results.\nexport function CommandEmpty({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { query, hasMatches } = useCommand();\n if (query === \"\" || hasMatches) return null;\n return (\n <div\n data-slot=\"command-empty\"\n className={cn(COMMAND_EMPTY, className)}\n {...props}\n >\n {children}\n </div>\n );\n}\n\nexport interface CommandGroupProps extends React.ComponentProps<\"div\"> {\n heading?: string;\n}\n\nexport function CommandGroup({ className, heading, children, ...props }: CommandGroupProps) {\n return (\n <div\n role=\"group\"\n data-slot=\"command-group\"\n className={cn(COMMAND_GROUP, className)}\n {...props}\n >\n {heading ? <div data-slot=\"command-group-heading\">{heading}</div> : null}\n {children}\n </div>\n );\n}\n\nexport interface CommandItemProps\n extends Omit<React.ComponentProps<\"div\">, \"onSelect\"> {\n value: string;\n disabled?: boolean;\n onSelect?: (value: string) => void;\n}\n\nexport function CommandItem({\n className,\n value,\n disabled = false,\n onSelect,\n id: idProp,\n onPointerMove,\n children,\n ...props\n}: CommandItemProps) {\n const { registerItem, unregisterItem, order, activeId, activate } = useCommand();\n const reactId = React.useId();\n // The DOM id is what `aria-activedescendant` points at, so a caller's own id\n // has to be the one registered.\n const id = idProp ?? reactId;\n // Read at select time, so an inline `onSelect` doesn't re-register every render.\n const onSelectRef = React.useRef(onSelect);\n React.useEffect(() => {\n onSelectRef.current = onSelect;\n });\n // Registered whether or not this item survives the filter below, so\n // `CommandEmpty` gates on the search, not on who happens to be mounted.\n React.useEffect(() => {\n registerItem(id, value, () => onSelectRef.current?.(value));\n return () => unregisterItem(id);\n }, [id, value, registerItem, unregisterItem]);\n\n const rank = order(value);\n if (rank === null) return null;\n const selected = activeId === id;\n return (\n <div\n role=\"option\"\n id={id}\n data-slot=\"command-item\"\n data-disabled={disabled}\n data-selected={selected}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n tabIndex={-1}\n onClick={() => {\n if (!disabled) onSelect?.(value);\n }}\n onPointerMove={(e) => {\n onPointerMove?.(e);\n if (!disabled && !selected) activate(id);\n }}\n className={cn(COMMAND_ITEM, className)}\n {...props}\n style={{ ...props.style, order: rank }}\n >\n {children}\n </div>\n );\n}\n\nexport function CommandSeparator({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"command-separator\"\n className={cn(COMMAND_SEPARATOR, className)}\n {...props}\n />\n );\n}\n\nexport function CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"command-shortcut\"\n className={cn(COMMAND_SHORTCUT, className)}\n {...props}\n />\n );\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/components/command.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport { DismissableLayerScope, useDismissableLayer, useModalScrimStyle } from \"../primitives/dismissable-layer\";\nimport { useFocusScope } from \"../primitives/focus-scope\";\nimport { mergeRefs } from \"../primitives/merge-refs\";\nimport { Portal } from \"../primitives/portal\";\nimport {\n COMMAND_DIALOG_COMMAND,\n COMMAND_DIALOG_CONTENT,\n COMMAND_DIALOG_OVERLAY,\n COMMAND_EMPTY,\n COMMAND_GROUP,\n COMMAND_INPUT,\n COMMAND_INPUT_WRAPPER,\n COMMAND_ITEM,\n COMMAND_LIST,\n COMMAND_ROOT,\n COMMAND_SEPARATOR,\n COMMAND_SHORTCUT,\n} from \"../generated/command\";\n\ninterface CommandContextValue {\n search: string;\n setSearch: (next: string) => void;\n /// Registers a `CommandItem`'s value so `CommandEmpty` can tell \"nothing\n /// matched\" from \"nothing is here\", and its select handler so Enter can fire\n /// it. Items register even while filtered out.\n registerItem: (id: string, value: string, select: () => void) => void;\n unregisterItem: (id: string) => void;\n /// The active query: trimmed, so blank input is not a search, and lowercased.\n /// Shared by the item filter and the empty-state gate so the two can never\n /// disagree.\n query: string;\n /// `null` filters the item out; a number is its CSS `order`, `0` while\n /// nothing is being ranked.\n order: (value: string) => number | null;\n hasMatches: boolean;\n listId: string;\n /// DOM id of the highlighted item. Focus stays in the input and points here\n /// through `aria-activedescendant` (the ARIA combobox pattern), so items\n /// never need to be tabbable.\n activeId: string | null;\n activate: (id: string) => void;\n}\n\nconst CommandContext = React.createContext<CommandContextValue | null>(null);\n\nfunction useCommand(): CommandContextValue {\n const ctx = React.useContext(CommandContext);\n if (!ctx) throw new Error(\"Command parts must be used within <Command>\");\n return ctx;\n}\n\nexport interface CommandProps extends React.ComponentProps<\"div\"> {\n search?: string;\n defaultSearch?: string;\n onSearchChange?: (search: string) => void;\n /// `false` hands filtering to the caller (e.g. rows that are a server\n /// response to the query): every mounted item renders, in the caller's\n /// order, and `CommandEmpty` counts mounted items. Defaults to `true`.\n shouldFilter?: boolean;\n}\n\nconst ITEM_SELECTOR = '[data-slot=\"command-item\"]:not([aria-disabled=\"true\"])';\n\n/// fzf v1: per space-separated term, the shortest window ending at the first\n/// full subsequence match, scored for word starts and runs, charged for gaps.\n/// `null` when a term does not match.\nfunction fuzzyScore(value: string, query: string): number | null {\n const hay = [...value.toLowerCase()];\n let total = 0;\n for (const term of query.split(/\\s+/)) {\n const needle = [...term];\n let k = 0;\n let end = -1;\n for (let i = 0; i < hay.length; i++) {\n if (hay[i] === needle[k] && ++k === needle.length) {\n end = i;\n break;\n }\n }\n if (end < 0) return null;\n let start = end;\n for (let i = end, j = needle.length - 1; j >= 0; i--) {\n if (hay[i] === needle[j]) {\n start = i;\n j--;\n }\n }\n let prev = -1;\n for (let i = start, j = 0; j < needle.length; i++) {\n if (hay[i] !== needle[j]) continue;\n total += 16;\n if (i === 0 || !/[\\p{L}\\p{N}]/u.test(hay[i - 1]!)) total += 8;\n if (j > 0) total += i === prev + 1 ? 4 : -(3 + (i - prev - 2));\n prev = i;\n j++;\n }\n }\n return total;\n}\n\n/// Keyboard highlight over the items currently in the DOM, in the order on\n/// screen — each container's rows by their CSS `order`, then DOM order — so it\n/// follows whatever the filter (or the caller) rendered rather than the order\n/// items happened to register in.\nfunction useCommandNavigation(\n query: string,\n handlers: React.RefObject<Map<string, () => void>>,\n) {\n const rootRef = React.useRef<HTMLDivElement>(null);\n const [activeId, setActiveId] = React.useState<string | null>(null);\n // Until the user moves the highlight it tracks the first row, so results\n // that arrive after the keystroke (or reorder under it) put Enter on the top\n // hit. Once moved, it sticks for as long as that row stays.\n const userMoved = React.useRef(false);\n\n const enabledItems = React.useCallback((): HTMLElement[] => {\n const root = rootRef.current;\n if (!root) return [];\n const byParent = new Map<Element | null, HTMLElement[]>();\n for (const el of root.querySelectorAll<HTMLElement>(ITEM_SELECTOR)) {\n if (el.closest('[data-slot=\"command\"]') !== root) continue;\n const rows = byParent.get(el.parentElement) ?? [];\n rows.push(el);\n byParent.set(el.parentElement, rows);\n }\n return [...byParent.values()].flatMap((rows) =>\n rows.sort((a, b) => Number(a.style.order) - Number(b.style.order)),\n );\n }, []);\n\n React.useEffect(() => {\n userMoved.current = false;\n }, [query]);\n\n // No deps on purpose: the caller can change the rendered rows on any render;\n // setting an unchanged id bails out without re-rendering.\n React.useEffect(() => {\n const els = enabledItems();\n const moved = userMoved.current;\n setActiveId((prev) =>\n moved && prev !== null && els.some((el) => el.id === prev)\n ? prev\n : (els[0]?.id ?? null),\n );\n });\n\n const activate = React.useCallback((id: string) => {\n userMoved.current = true;\n setActiveId(id);\n }, []);\n\n const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.nativeEvent.isComposing) return;\n // Only the search field drives the list: Enter or an arrow on a button\n // inside Command (an action in `CommandEmpty`, a footer) stays its own.\n const field = event.target as HTMLElement;\n if (field.dataset[\"slot\"] !== \"command-input\") return;\n const els = enabledItems();\n const current = els.findIndex((el) => el.id === activeId);\n const last = els.length - 1;\n let next: number;\n switch (event.key) {\n case \"ArrowDown\":\n next = current < 0 ? 0 : Math.min(current + 1, last);\n break;\n case \"ArrowUp\":\n next = current < 0 ? last : Math.max(current - 1, 0);\n break;\n // Home / End are left to the caret: the field is editable (ARIA APG\n // editable combobox).\n case \"Enter\": {\n const select =\n activeId !== null && current >= 0\n ? handlers.current.get(activeId)\n : undefined;\n if (select) {\n event.preventDefault();\n select();\n }\n return;\n }\n default:\n return;\n }\n const target = els[next];\n if (!target) return;\n event.preventDefault();\n activate(target.id);\n // Optional call: jsdom implements no `scrollIntoView`.\n target.scrollIntoView?.({ block: \"nearest\" });\n };\n\n return { rootRef, activeId, activate, onKeyDown };\n}\n\nexport function Command({\n className,\n search,\n defaultSearch = \"\",\n onSearchChange,\n shouldFilter = true,\n children,\n ref,\n onKeyDown,\n ...props\n}: CommandProps) {\n const [currentSearch, setSearch] = useControllableState<string>({\n ...(search !== undefined ? { value: search } : {}),\n defaultValue: defaultSearch,\n ...(onSearchChange ? { onChange: onSearchChange } : {}),\n });\n\n const [items, setItems] = React.useState<ReadonlyMap<string, string>>(\n () => new Map(),\n );\n const handlers = React.useRef(new Map<string, () => void>());\n const registerItem = React.useCallback(\n (id: string, value: string, select: () => void) => {\n handlers.current.set(id, select);\n setItems((prev) => {\n if (prev.get(id) === value) return prev;\n const next = new Map(prev);\n next.set(id, value);\n return next;\n });\n },\n [],\n );\n const unregisterItem = React.useCallback((id: string) => {\n handlers.current.delete(id);\n setItems((prev) => {\n if (!prev.has(id)) return prev;\n const next = new Map(prev);\n next.delete(id);\n return next;\n });\n }, []);\n\n const query = currentSearch.trim().toLowerCase();\n const order = React.useCallback(\n (value: string) => {\n if (!shouldFilter || query === \"\") return 0;\n const score = fuzzyScore(value, query);\n return score === null ? null : -score;\n },\n [query, shouldFilter],\n );\n const hasMatches = React.useMemo(\n () => [...items.values()].some((value) => order(value) !== null),\n [items, order],\n );\n\n const listId = React.useId();\n const nav = useCommandNavigation(query, handlers);\n\n return (\n <CommandContext.Provider\n value={{\n search: currentSearch,\n setSearch,\n registerItem,\n unregisterItem,\n query,\n order,\n hasMatches,\n listId,\n activeId: nav.activeId,\n activate: nav.activate,\n }}\n >\n <div\n data-slot=\"command\"\n ref={mergeRefs(nav.rootRef, ref)}\n className={cn(COMMAND_ROOT, className)}\n onKeyDown={(e) => {\n onKeyDown?.(e);\n if (!e.defaultPrevented) nav.onKeyDown(e);\n }}\n {...props}\n >\n {children}\n </div>\n </CommandContext.Provider>\n );\n}\n\nexport interface CommandDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /// See `CommandProps.shouldFilter`.\n shouldFilter?: boolean;\n className?: string;\n children?: React.ReactNode;\n}\n\nexport function CommandDialog({\n open,\n defaultOpen = false,\n onOpenChange,\n shouldFilter = true,\n className,\n children,\n}: CommandDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const scopeRef = useFocusScope(isOpen);\n // Outside clicks stay with the overlay, as in Drawer: it is what \"outside\" means.\n const dismissRef = useDismissableLayer({\n enabled: isOpen,\n onDismiss: () => setOpen(false),\n pointerOutside: false,\n modal: true,\n });\n const scrimStyle = useModalScrimStyle(dismissRef);\n if (!isOpen) return null;\n return (\n <Portal>\n <div\n data-slot=\"command-overlay\"\n className={COMMAND_DIALOG_OVERLAY}\n style={scrimStyle}\n onClick={() => setOpen(false)}\n />\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n data-slot=\"command-dialog\"\n ref={mergeRefs(scopeRef, dismissRef)}\n className={cn(COMMAND_DIALOG_CONTENT, className)}\n >\n <Command className={COMMAND_DIALOG_COMMAND} shouldFilter={shouldFilter}>\n <DismissableLayerScope layer={dismissRef}>{children}</DismissableLayerScope>\n </Command>\n </div>\n </Portal>\n );\n}\n\nexport function CommandInput({\n className,\n ...props\n}: React.ComponentProps<\"input\">) {\n const { search, setSearch, listId, activeId } = useCommand();\n return (\n <div className={COMMAND_INPUT_WRAPPER} data-slot=\"command-input-wrapper\">\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"size-4 shrink-0 opacity-50\"\n aria-hidden=\"true\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.3-4.3\" />\n </svg>\n <input\n type=\"text\"\n role=\"combobox\"\n data-slot=\"command-input\"\n aria-autocomplete=\"list\"\n aria-expanded={true}\n aria-controls={listId}\n aria-activedescendant={activeId ?? undefined}\n autoComplete=\"off\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className={cn(COMMAND_INPUT, className)}\n {...props}\n />\n </div>\n );\n}\n\nexport function CommandList({ className, ...props }: React.ComponentProps<\"div\">) {\n const { listId } = useCommand();\n return (\n <div\n role=\"listbox\"\n id={listId}\n data-slot=\"command-list\"\n className={cn(COMMAND_LIST, className)}\n {...props}\n />\n );\n}\n\n/// Renders only when a search is under way and no item matched it — never next\n/// to results, and never before the user has typed. With `shouldFilter={false}`\n/// \"matched\" means \"is mounted\": the caller's rows are the results.\nexport function CommandEmpty({ className, children, ...props }: React.ComponentProps<\"div\">) {\n const { query, hasMatches } = useCommand();\n if (query === \"\" || hasMatches) return null;\n return (\n <div\n data-slot=\"command-empty\"\n className={cn(COMMAND_EMPTY, className)}\n {...props}\n >\n {children}\n </div>\n );\n}\n\nexport interface CommandGroupProps extends React.ComponentProps<\"div\"> {\n heading?: string;\n}\n\nexport function CommandGroup({ className, heading, children, ...props }: CommandGroupProps) {\n return (\n <div\n role=\"group\"\n data-slot=\"command-group\"\n className={cn(COMMAND_GROUP, className)}\n {...props}\n >\n {heading ? <div data-slot=\"command-group-heading\">{heading}</div> : null}\n {children}\n </div>\n );\n}\n\nexport interface CommandItemProps\n extends Omit<React.ComponentProps<\"div\">, \"onSelect\"> {\n value: string;\n disabled?: boolean;\n onSelect?: (value: string) => void;\n}\n\nexport function CommandItem({\n className,\n value,\n disabled = false,\n onSelect,\n id: idProp,\n onPointerMove,\n children,\n ...props\n}: CommandItemProps) {\n const { registerItem, unregisterItem, order, activeId, activate } = useCommand();\n const reactId = React.useId();\n // The DOM id is what `aria-activedescendant` points at, so a caller's own id\n // has to be the one registered.\n const id = idProp ?? reactId;\n // Read at select time, so an inline `onSelect` doesn't re-register every render.\n const onSelectRef = React.useRef(onSelect);\n React.useEffect(() => {\n onSelectRef.current = onSelect;\n });\n // Registered whether or not this item survives the filter below, so\n // `CommandEmpty` gates on the search, not on who happens to be mounted.\n React.useEffect(() => {\n registerItem(id, value, () => onSelectRef.current?.(value));\n return () => unregisterItem(id);\n }, [id, value, registerItem, unregisterItem]);\n\n const rank = order(value);\n if (rank === null) return null;\n const selected = activeId === id;\n return (\n <div\n role=\"option\"\n id={id}\n data-slot=\"command-item\"\n data-disabled={disabled}\n data-selected={selected}\n aria-selected={selected}\n aria-disabled={disabled || undefined}\n tabIndex={-1}\n onClick={() => {\n if (!disabled) onSelect?.(value);\n }}\n onPointerMove={(e) => {\n onPointerMove?.(e);\n if (!disabled && !selected) activate(id);\n }}\n className={cn(COMMAND_ITEM, className)}\n {...props}\n style={{ ...props.style, order: rank }}\n >\n {children}\n </div>\n );\n}\n\nexport function CommandSeparator({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n data-slot=\"command-separator\"\n className={cn(COMMAND_SEPARATOR, className)}\n {...props}\n />\n );\n}\n\nexport function CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n data-slot=\"command-shortcut\"\n className={cn(COMMAND_SHORTCUT, className)}\n {...props}\n />\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { cn } from "../lib/cn.js";
5
+ import {
6
+ CONFIRM_DIALOG_CONTENT,
7
+ CONFIRM_DIALOG_DESCRIPTION,
8
+ CONFIRM_DIALOG_ERROR,
9
+ CONFIRM_DIALOG_FOOTER,
10
+ CONFIRM_DIALOG_HEADER,
11
+ CONFIRM_DIALOG_ICON,
12
+ CONFIRM_DIALOG_PENDING,
13
+ CONFIRM_DIALOG_SPINNER,
14
+ CONFIRM_DIALOG_TEXT,
15
+ CONFIRM_DIALOG_TITLE,
16
+ confirmDialogIconToneClasses
17
+ } from "../generated/confirm-dialog.js";
18
+ import { useControllableState } from "../primitives/use-controllable-state.js";
19
+ import {
20
+ AlertDialog,
21
+ AlertDialogContent,
22
+ AlertDialogDescription,
23
+ AlertDialogFooter,
24
+ AlertDialogHeader,
25
+ AlertDialogTitle,
26
+ AlertDialogTrigger
27
+ } from "./alert-dialog.js";
28
+ import { buttonVariants } from "./button.js";
29
+ import { Spinner } from "./spinner.js";
30
+ import { UiIcon } from "./ui-icons.js";
31
+ function ConfirmDialog({
32
+ open,
33
+ defaultOpen = false,
34
+ onOpenChange,
35
+ trigger,
36
+ title,
37
+ description,
38
+ tone = "default",
39
+ confirmLabel,
40
+ onConfirm,
41
+ icon,
42
+ labels = {},
43
+ className
44
+ }) {
45
+ const [isOpen, setOpen] = useControllableState({
46
+ ...open !== void 0 ? { value: open } : {},
47
+ defaultValue: defaultOpen,
48
+ ...onOpenChange ? { onChange: onOpenChange } : {}
49
+ });
50
+ const [pending, setPending] = React.useState(false);
51
+ const [error, setError] = React.useState(null);
52
+ const danger = tone === "danger";
53
+ React.useEffect(() => {
54
+ if (isOpen) setError(null);
55
+ }, [isOpen]);
56
+ const change = (next) => {
57
+ if (pending) return;
58
+ if (next) setError(null);
59
+ setOpen(next);
60
+ };
61
+ const confirm = async () => {
62
+ if (pending) return;
63
+ setError(null);
64
+ setPending(true);
65
+ try {
66
+ await onConfirm();
67
+ setPending(false);
68
+ setOpen(false);
69
+ } catch (e) {
70
+ setPending(false);
71
+ setError(e instanceof Error && e.message ? e.message : labels.error ?? "Something went wrong");
72
+ }
73
+ };
74
+ return /* @__PURE__ */ jsxs(AlertDialog, { open: isOpen, onOpenChange: change, children: [
75
+ trigger != null && /* @__PURE__ */ jsx(AlertDialogTrigger, { asChild: true, children: trigger }),
76
+ /* @__PURE__ */ jsxs(AlertDialogContent, { className: cn(CONFIRM_DIALOG_CONTENT, className), children: [
77
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { className: CONFIRM_DIALOG_HEADER, children: [
78
+ /* @__PURE__ */ jsx(
79
+ "span",
80
+ {
81
+ className: cn(CONFIRM_DIALOG_ICON, confirmDialogIconToneClasses[tone]),
82
+ children: icon ?? /* @__PURE__ */ jsx(UiIcon, { name: "alert" })
83
+ }
84
+ ),
85
+ /* @__PURE__ */ jsxs("div", { className: CONFIRM_DIALOG_TEXT, children: [
86
+ /* @__PURE__ */ jsx(AlertDialogTitle, { className: CONFIRM_DIALOG_TITLE, children: title }),
87
+ description != null && /* @__PURE__ */ jsx(AlertDialogDescription, { className: CONFIRM_DIALOG_DESCRIPTION, children: description }),
88
+ error !== null && /* @__PURE__ */ jsx("p", { role: "alert", className: CONFIRM_DIALOG_ERROR, children: error })
89
+ ] })
90
+ ] }),
91
+ /* @__PURE__ */ jsxs(AlertDialogFooter, { className: CONFIRM_DIALOG_FOOTER, children: [
92
+ /* @__PURE__ */ jsx(
93
+ "button",
94
+ {
95
+ type: "button",
96
+ "aria-disabled": pending,
97
+ onClick: () => change(false),
98
+ className: cn(buttonVariants({ variant: "ghost", size: "xs" }), CONFIRM_DIALOG_PENDING),
99
+ children: labels.cancel ?? "Cancel"
100
+ }
101
+ ),
102
+ /* @__PURE__ */ jsxs(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ "aria-disabled": pending,
107
+ "aria-busy": pending,
108
+ onClick: confirm,
109
+ className: cn(buttonVariants({ variant: danger ? "destructive" : "primary", size: "xs" }), CONFIRM_DIALOG_PENDING),
110
+ children: [
111
+ pending && /* @__PURE__ */ jsx(Spinner, { className: CONFIRM_DIALOG_SPINNER }),
112
+ confirmLabel
113
+ ]
114
+ }
115
+ )
116
+ ] })
117
+ ] })
118
+ ] });
119
+ }
120
+ export {
121
+ ConfirmDialog
122
+ };
123
+ //# sourceMappingURL=confirm-dialog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/confirm-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"../lib/cn\";\nimport {\n CONFIRM_DIALOG_CONTENT,\n CONFIRM_DIALOG_DESCRIPTION,\n CONFIRM_DIALOG_ERROR,\n CONFIRM_DIALOG_FOOTER,\n CONFIRM_DIALOG_HEADER,\n CONFIRM_DIALOG_ICON,\n CONFIRM_DIALOG_PENDING,\n CONFIRM_DIALOG_SPINNER,\n CONFIRM_DIALOG_TEXT,\n CONFIRM_DIALOG_TITLE,\n confirmDialogIconToneClasses,\n type ConfirmDialogTone,\n} from \"../generated/confirm-dialog\";\nimport { useControllableState } from \"../primitives/use-controllable-state\";\nimport {\n AlertDialog,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n AlertDialogTrigger,\n} from \"./alert-dialog\";\nimport { buttonVariants } from \"./button\";\nimport { Spinner } from \"./spinner\";\nimport { UiIcon } from \"./ui-icons\";\n\nexport type { ConfirmDialogTone };\n\nexport interface ConfirmDialogLabels {\n cancel?: string;\n /** Shown when `onConfirm` rejects with something that has no message. */\n error?: string;\n}\n\nexport interface ConfirmDialogProps {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Opens the dialog; wrapped with `asChild`, so pass one button. */\n trigger?: React.ReactNode;\n title: React.ReactNode;\n description?: React.ReactNode;\n tone?: ConfirmDialogTone;\n confirmLabel: React.ReactNode;\n /**\n * Resolve to close the dialog. While a returned promise is pending both\n * buttons are disabled and Escape and outside clicks do nothing; a rejection\n * keeps the dialog open and shows its message under the text.\n */\n onConfirm: () => void | Promise<void>;\n /** Replaces the tone's glyph. */\n icon?: React.ReactNode;\n labels?: ConfirmDialogLabels;\n className?: string;\n}\n\nexport function ConfirmDialog({\n open,\n defaultOpen = false,\n onOpenChange,\n trigger,\n title,\n description,\n tone = \"default\",\n confirmLabel,\n onConfirm,\n icon,\n labels = {},\n className,\n}: ConfirmDialogProps) {\n const [isOpen, setOpen] = useControllableState<boolean>({\n ...(open !== undefined ? { value: open } : {}),\n defaultValue: defaultOpen,\n ...(onOpenChange ? { onChange: onOpenChange } : {}),\n });\n const [pending, setPending] = React.useState(false);\n const [error, setError] = React.useState<string | null>(null);\n const danger = tone === \"danger\";\n // A controlled parent can reopen the dialog without going through `change`.\n React.useEffect(() => {\n if (isOpen) setError(null);\n }, [isOpen]);\n\n const change = (next: boolean) => {\n if (pending) return;\n if (next) setError(null);\n setOpen(next);\n };\n\n const confirm = async () => {\n if (pending) return;\n setError(null);\n setPending(true);\n try {\n await onConfirm();\n setPending(false);\n setOpen(false);\n } catch (e) {\n setPending(false);\n setError(e instanceof Error && e.message ? e.message : (labels.error ?? \"Something went wrong\"));\n }\n };\n\n return (\n <AlertDialog open={isOpen} onOpenChange={change}>\n {trigger != null && <AlertDialogTrigger asChild>{trigger}</AlertDialogTrigger>}\n <AlertDialogContent className={cn(CONFIRM_DIALOG_CONTENT, className)}>\n <AlertDialogHeader className={CONFIRM_DIALOG_HEADER}>\n <span\n className={cn(CONFIRM_DIALOG_ICON, confirmDialogIconToneClasses[tone])}\n >\n {icon ?? <UiIcon name=\"alert\" />}\n </span>\n <div className={CONFIRM_DIALOG_TEXT}>\n <AlertDialogTitle className={CONFIRM_DIALOG_TITLE}>{title}</AlertDialogTitle>\n {description != null && (\n <AlertDialogDescription className={CONFIRM_DIALOG_DESCRIPTION}>{description}</AlertDialogDescription>\n )}\n {error !== null && (\n <p role=\"alert\" className={CONFIRM_DIALOG_ERROR}>\n {error}\n </p>\n )}\n </div>\n </AlertDialogHeader>\n <AlertDialogFooter className={CONFIRM_DIALOG_FOOTER}>\n {/* aria-disabled, not disabled: a disabled button that holds focus drops it to <body> and Tab leaves the dialog. */}\n <button\n type=\"button\"\n aria-disabled={pending}\n onClick={() => change(false)}\n className={cn(buttonVariants({ variant: \"ghost\", size: \"xs\" }), CONFIRM_DIALOG_PENDING)}\n >\n {labels.cancel ?? \"Cancel\"}\n </button>\n <button\n type=\"button\"\n aria-disabled={pending}\n aria-busy={pending}\n onClick={confirm}\n className={cn(buttonVariants({ variant: danger ? \"destructive\" : \"primary\", size: \"xs\" }), CONFIRM_DIALOG_PENDING)}\n >\n {pending && <Spinner className={CONFIRM_DIALOG_SPINNER} />}\n {confirmLabel}\n </button>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n"],"mappings":"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","names":[]}
@@ -6,6 +6,7 @@ import { useControllableState } from "../primitives/use-controllable-state.js";
6
6
  import { DismissableLayerScope, useDismissableLayer } from "../primitives/dismissable-layer.js";
7
7
  import { usePresence } from "../primitives/presence.js";
8
8
  import { useRovingFocus } from "../primitives/use-roving-focus.js";
9
+ import { useHoverIntent } from "../primitives/use-hover-intent.js";
9
10
  import { mergeRefs } from "../primitives/merge-refs.js";
10
11
  import { Portal } from "../primitives/portal.js";
11
12
  import { Slot } from "../primitives/slot.js";
@@ -149,9 +150,9 @@ function ContextMenuCheckboxItem({
149
150
  checked,
150
151
  disabled,
151
152
  onClick,
153
+ onKeyDown,
152
154
  ...props
153
155
  }) {
154
- const close = React.useContext(ContextMenuCloseContext);
155
156
  return /* @__PURE__ */ jsxs(
156
157
  "div",
157
158
  {
@@ -168,7 +169,13 @@ function ContextMenuCheckboxItem({
168
169
  onClick: (e) => {
169
170
  if (disabled) return;
170
171
  onClick?.(e);
171
- close();
172
+ },
173
+ onKeyDown: (e) => {
174
+ onKeyDown?.(e);
175
+ if (e.key === "Enter" || e.key === " ") {
176
+ e.preventDefault();
177
+ e.currentTarget.click();
178
+ }
172
179
  },
173
180
  ...props,
174
181
  children: [
@@ -269,13 +276,29 @@ function ContextMenuSub({ open, defaultOpen = false, onOpenChange, children }) {
269
276
  defaultValue: defaultOpen,
270
277
  ...onOpenChange ? { onChange: onOpenChange } : {}
271
278
  });
272
- return /* @__PURE__ */ jsx(ContextMenuSubContext.Provider, { value: { open: isOpen, setOpen }, children: /* @__PURE__ */ jsx("div", { "data-slot": "context-menu-sub", children }) });
279
+ const triggerRef = React.useRef(null);
280
+ const focusFirst = React.useRef(false);
281
+ const hover = useHoverIntent({
282
+ onOpen: () => setOpen(true),
283
+ onClose: () => setOpen(false),
284
+ openDelay: 100,
285
+ closeDelay: 150
286
+ });
287
+ return /* @__PURE__ */ jsx(ContextMenuSubContext.Provider, { value: { open: isOpen, setOpen, triggerRef, focusFirst }, children: /* @__PURE__ */ jsx("div", { "data-slot": "context-menu-sub", ...hover, children }) });
273
288
  }
274
- function ContextMenuSubTrigger({ className, inset, children, onClick, ...props }) {
289
+ function ContextMenuSubTrigger({
290
+ className,
291
+ inset,
292
+ children,
293
+ onClick,
294
+ onKeyDown,
295
+ ...props
296
+ }) {
275
297
  const ctx = React.useContext(ContextMenuSubContext);
276
298
  return /* @__PURE__ */ jsxs(
277
299
  "div",
278
300
  {
301
+ ref: ctx?.triggerRef,
279
302
  "data-slot": "context-menu-sub-trigger",
280
303
  "data-inset": inset ? "" : void 0,
281
304
  "data-state": ctx?.open ? "open" : "closed",
@@ -291,6 +314,14 @@ function ContextMenuSubTrigger({ className, inset, children, onClick, ...props }
291
314
  onClick?.(e);
292
315
  ctx?.setOpen(!ctx.open);
293
316
  },
317
+ onKeyDown: (e) => {
318
+ onKeyDown?.(e);
319
+ if (!ctx || e.key !== "ArrowRight" && e.key !== "Enter" && e.key !== " ") return;
320
+ e.preventDefault();
321
+ e.stopPropagation();
322
+ ctx.focusFirst.current = true;
323
+ ctx.setOpen(true);
324
+ },
294
325
  ...props,
295
326
  children: [
296
327
  children,
@@ -299,12 +330,29 @@ function ContextMenuSubTrigger({ className, inset, children, onClick, ...props }
299
330
  }
300
331
  );
301
332
  }
302
- function ContextMenuSubContent({ className, children, ...props }) {
333
+ function ContextMenuSubContent({ className, children, onKeyDown, ...props }) {
303
334
  const ctx = React.useContext(ContextMenuSubContext);
335
+ const ref = React.useRef(null);
336
+ const open = ctx?.open ?? false;
337
+ React.useEffect(() => {
338
+ if (!open || !ctx?.focusFirst.current) return;
339
+ ctx.focusFirst.current = false;
340
+ ref.current?.querySelector("[role^='menuitem']")?.focus();
341
+ }, [open, ctx]);
304
342
  if (!ctx?.open) return null;
305
343
  return /* @__PURE__ */ jsx(
306
344
  "div",
307
345
  {
346
+ ref,
347
+ onKeyDown: (e) => {
348
+ onKeyDown?.(e);
349
+ if (e.key === "ArrowLeft" || e.key === "Escape") {
350
+ e.preventDefault();
351
+ e.stopPropagation();
352
+ ctx.setOpen(false);
353
+ ctx.triggerRef.current?.focus();
354
+ }
355
+ },
308
356
  "data-slot": "context-menu-sub-content",
309
357
  "data-state": "open",
310
358
  role: "menu",