@ctrl-ui/react 0.8.7 → 0.8.9

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 (341) hide show
  1. package/dist/action-bar.d.ts +15 -7
  2. package/dist/action-bar.js +39 -19
  3. package/dist/activity.js +7 -15
  4. package/dist/audio-recorder.d.ts +14 -4
  5. package/dist/audio-recorder.js +82 -29
  6. package/dist/audio-visualizer.js +1 -1
  7. package/dist/blocks/coding-agent.js +39 -39
  8. package/dist/blocks/design-canvas.d.ts +5 -1
  9. package/dist/blocks/design-canvas.js +198 -92
  10. package/dist/blocks/discussion.d.ts +28 -0
  11. package/dist/blocks/discussion.js +67 -0
  12. package/dist/blocks/file-explorer-data.d.ts +1 -0
  13. package/dist/blocks/file-explorer-data.js +4 -1
  14. package/dist/blocks/file-explorer.d.ts +9 -1
  15. package/dist/blocks/file-explorer.js +461 -192
  16. package/dist/blocks/settings.d.ts +2 -1
  17. package/dist/blocks/settings.js +101 -79
  18. package/dist/blocks/team-chat.d.ts +12 -0
  19. package/dist/blocks/team-chat.js +26 -0
  20. package/dist/blocks/theme-toggle.js +23 -31
  21. package/dist/chat-composer-attachment.d.ts +4 -1
  22. package/dist/chat-composer-attachment.js +35 -10
  23. package/dist/chat-composer-editor/extensions/mention.d.ts +5 -1
  24. package/dist/chat-composer-editor/extensions/mention.js +59 -28
  25. package/dist/chat-composer-editor/ghost.js +2 -4
  26. package/dist/chat-composer-editor/types.d.ts +5 -0
  27. package/dist/chat-composer-editor.d.ts +1 -1
  28. package/dist/chat-composer-editor.js +57 -30
  29. package/dist/chat-composer.d.ts +13 -32
  30. package/dist/chat-composer.js +52 -12
  31. package/dist/chat-layout.d.ts +19 -0
  32. package/dist/chat-layout.js +91 -40
  33. package/dist/chat-message/social.d.ts +26 -0
  34. package/dist/chat-message/social.js +122 -0
  35. package/dist/chat-message.d.ts +28 -29
  36. package/dist/chat-message.js +106 -25
  37. package/dist/context.d.ts +2 -2
  38. package/dist/context.js +38 -23
  39. package/dist/dynamic-notification-canvas-loop.d.ts +24 -0
  40. package/dist/dynamic-notification-canvas-loop.js +113 -0
  41. package/dist/dynamic-notification-glass.js +23 -114
  42. package/dist/dynamic-notification-liquid.js +14 -118
  43. package/dist/dynamic-notification.d.ts +16 -8
  44. package/dist/dynamic-notification.js +85 -22
  45. package/dist/email/email-brand.d.ts +7 -4
  46. package/dist/email/email-brand.js +9 -6
  47. package/dist/email/email-code.d.ts +3 -2
  48. package/dist/email/email-code.js +11 -9
  49. package/dist/email/email-footer.d.ts +5 -3
  50. package/dist/email/email-footer.js +11 -5
  51. package/dist/email/email.d.ts +31 -9
  52. package/dist/email/email.js +50 -26
  53. package/dist/email/templates.d.ts +10 -9
  54. package/dist/email/templates.js +102 -38
  55. package/dist/email/theme.js +1 -1
  56. package/dist/environment-variables.d.ts +4 -2
  57. package/dist/environment-variables.js +52 -32
  58. package/dist/filter-bar/editor.js +20 -8
  59. package/dist/filter-bar/model.d.ts +1 -0
  60. package/dist/filter-bar/model.js +10 -1
  61. package/dist/filter-bar/motion.js +4 -4
  62. package/dist/filter-bar/parts.js +19 -8
  63. package/dist/filter-bar/types.d.ts +1 -0
  64. package/dist/filter-bar.js +19 -13
  65. package/dist/hooks/use-audio-recorder.js +12 -6
  66. package/dist/hooks/use-chat-composer.d.ts +19 -7
  67. package/dist/hooks/use-chat-composer.js +32 -5
  68. package/dist/hooks/use-chat-message.d.ts +28 -2
  69. package/dist/hooks/use-chat-message.js +41 -10
  70. package/dist/hooks/use-chat-thread-scroll.js +43 -7
  71. package/dist/hooks/use-color-area.d.ts +3 -3
  72. package/dist/hooks/use-color-area.js +13 -12
  73. package/dist/hooks/use-copy-to-clipboard.d.ts +2 -1
  74. package/dist/hooks/use-copy-to-clipboard.js +20 -20
  75. package/dist/hooks/use-dynamic-notification.d.ts +6 -2
  76. package/dist/hooks/use-dynamic-notification.js +26 -10
  77. package/dist/hooks/use-mobile.d.ts +3 -2
  78. package/dist/hooks/use-mobile.js +8 -4
  79. package/dist/hooks/use-textarea-trigger-menu.js +2 -1
  80. package/dist/hooks/use-trigger-menu.d.ts +54 -0
  81. package/dist/hooks/use-trigger-menu.js +102 -47
  82. package/dist/hooks/use-user-ask.d.ts +2 -0
  83. package/dist/hooks/use-user-ask.js +35 -41
  84. package/dist/inline-attachment.d.ts +6 -2
  85. package/dist/inline-attachment.js +36 -15
  86. package/dist/inline-citation.d.ts +12 -5
  87. package/dist/inline-citation.js +25 -16
  88. package/dist/knob-contracts/audio-recorder-knobs.d.ts +1 -1
  89. package/dist/knob-contracts/audio-recorder-knobs.js +2 -1
  90. package/dist/knob-contracts/audio-visualizer-knobs.d.ts +1 -1
  91. package/dist/knob-contracts/audio-visualizer-knobs.js +2 -1
  92. package/dist/knob-contracts/avatar-knobs.d.ts +1 -1
  93. package/dist/knob-contracts/avatar-knobs.js +6 -1
  94. package/dist/knob-contracts/badge-knobs.d.ts +1 -1
  95. package/dist/knob-contracts/badge-knobs.js +2 -1
  96. package/dist/knob-contracts/breadcrumb-knobs.d.ts +4 -0
  97. package/dist/knob-contracts/breadcrumb-knobs.js +8 -0
  98. package/dist/knob-contracts/button-knobs.d.ts +1 -1
  99. package/dist/knob-contracts/button-knobs.js +1 -0
  100. package/dist/knob-contracts/chat-message-knobs.d.ts +1 -1
  101. package/dist/knob-contracts/chat-message-knobs.js +8 -1
  102. package/dist/knob-contracts/code-diff-knobs.d.ts +1 -1
  103. package/dist/knob-contracts/code-diff-knobs.js +2 -0
  104. package/dist/knob-contracts/code-knobs.d.ts +1 -1
  105. package/dist/knob-contracts/code-knobs.js +2 -1
  106. package/dist/knob-contracts/dynamic-notification-knobs.d.ts +1 -1
  107. package/dist/knob-contracts/dynamic-notification-knobs.js +2 -0
  108. package/dist/knob-contracts/emoji-picker-knobs.d.ts +4 -0
  109. package/dist/knob-contracts/emoji-picker-knobs.js +10 -0
  110. package/dist/knob-contracts/field-knobs.d.ts +1 -1
  111. package/dist/knob-contracts/field-knobs.js +5 -1
  112. package/dist/knob-contracts/filter-bar-knobs.d.ts +1 -1
  113. package/dist/knob-contracts/filter-bar-knobs.js +1 -2
  114. package/dist/knob-contracts/markdown-knobs.d.ts +1 -1
  115. package/dist/knob-contracts/markdown-knobs.js +3 -1
  116. package/dist/knob-contracts/page-layout-knobs.d.ts +1 -1
  117. package/dist/knob-contracts/page-layout-knobs.js +2 -1
  118. package/dist/knob-contracts/popup-knobs.d.ts +1 -1
  119. package/dist/knob-contracts/popup-knobs.js +27 -0
  120. package/dist/knob-contracts/range-knobs.d.ts +1 -1
  121. package/dist/knob-contracts/range-knobs.js +1 -0
  122. package/dist/knob-contracts/rich-tooltip-knobs.d.ts +1 -1
  123. package/dist/knob-contracts/rich-tooltip-knobs.js +2 -1
  124. package/dist/knob-contracts/sidebar-knobs.d.ts +1 -1
  125. package/dist/knob-contracts/sidebar-knobs.js +27 -2
  126. package/dist/knob-contracts/spinner-knobs.d.ts +1 -1
  127. package/dist/knob-contracts/spinner-knobs.js +1 -1
  128. package/dist/knob-contracts/stepper-knobs.d.ts +1 -1
  129. package/dist/knob-contracts/stepper-knobs.js +4 -1
  130. package/dist/knob-contracts/switch-knobs.d.ts +1 -1
  131. package/dist/knob-contracts/switch-knobs.js +5 -0
  132. package/dist/knob-contracts/table-of-contents-knobs.d.ts +1 -1
  133. package/dist/knob-contracts/table-of-contents-knobs.js +2 -1
  134. package/dist/knob-contracts/tabs-knobs.d.ts +1 -1
  135. package/dist/knob-contracts/tabs-knobs.js +1 -0
  136. package/dist/knob-contracts/tree-knobs.d.ts +1 -1
  137. package/dist/knob-contracts/tree-knobs.js +2 -1
  138. package/dist/lib/cn.js +0 -4
  139. package/dist/lib/contrast.d.ts +1 -0
  140. package/dist/lib/contrast.js +17 -5
  141. package/dist/lib/dropzone-validation.js +41 -28
  142. package/dist/lib/env-file.d.ts +1 -1
  143. package/dist/lib/env-file.js +5 -5
  144. package/dist/lib/motion.d.ts +4 -0
  145. package/dist/lib/motion.js +15 -0
  146. package/dist/lib/trigger-detect.js +6 -8
  147. package/dist/markdown-block.js +2 -1
  148. package/dist/popup-parts.d.ts +8 -0
  149. package/dist/popup-parts.js +43 -0
  150. package/dist/source-badge.js +3 -1
  151. package/dist/styles/code.css +0 -2
  152. package/dist/styles/effects.css +6 -32
  153. package/dist/styles/index.css +10 -5
  154. package/dist/styles/recipes/accordion.css +9 -6
  155. package/dist/styles/recipes/action-bar.css +0 -15
  156. package/dist/styles/recipes/activity.css +5 -11
  157. package/dist/styles/recipes/alert.css +4 -4
  158. package/dist/styles/recipes/audio-recorder.css +15 -3
  159. package/dist/styles/recipes/audio-visualizer.css +13 -2
  160. package/dist/styles/recipes/avatar.css +78 -1
  161. package/dist/styles/recipes/badge.css +14 -4
  162. package/dist/styles/recipes/breadcrumb.css +77 -0
  163. package/dist/styles/recipes/button-group.css +5 -7
  164. package/dist/styles/recipes/button-states.css +66 -0
  165. package/dist/styles/recipes/button.css +24 -83
  166. package/dist/styles/recipes/calendar.css +17 -23
  167. package/dist/styles/recipes/card.css +2 -2
  168. package/dist/styles/recipes/chat-composer-attachment.css +9 -3
  169. package/dist/styles/recipes/chat-composer.css +7 -10
  170. package/dist/styles/recipes/chat-layout.css +35 -0
  171. package/dist/styles/recipes/chat-message-social.css +123 -0
  172. package/dist/styles/recipes/chat-message.css +157 -37
  173. package/dist/styles/recipes/choice.css +18 -21
  174. package/dist/styles/recipes/code-diff.css +46 -33
  175. package/dist/styles/recipes/code.css +36 -16
  176. package/dist/styles/recipes/collapsible.css +7 -2
  177. package/dist/styles/recipes/color-picker-details.css +3 -4
  178. package/dist/styles/recipes/color-picker-values.css +9 -8
  179. package/dist/styles/recipes/color-picker.css +11 -2
  180. package/dist/styles/recipes/context.css +3 -3
  181. package/dist/styles/recipes/dockable-panel.css +7 -4
  182. package/dist/styles/recipes/dropzone.css +7 -8
  183. package/dist/styles/recipes/dynamic-notification-input.css +8 -5
  184. package/dist/styles/recipes/dynamic-notification-motion.css +12 -21
  185. package/dist/styles/recipes/dynamic-notification.css +40 -26
  186. package/dist/styles/recipes/emoji-picker.css +132 -0
  187. package/dist/styles/recipes/empty.css +5 -4
  188. package/dist/styles/recipes/environment-variables.css +4 -15
  189. package/dist/styles/recipes/field-parts.css +30 -24
  190. package/dist/styles/recipes/field.css +61 -26
  191. package/dist/styles/recipes/filter-bar.css +18 -31
  192. package/dist/styles/recipes/gradient-editor.css +18 -7
  193. package/dist/styles/recipes/infinite-canvas.css +0 -1
  194. package/dist/styles/recipes/inline-attachment.css +3 -3
  195. package/dist/styles/recipes/inline-citation.css +10 -4
  196. package/dist/styles/recipes/item.css +23 -9
  197. package/dist/styles/recipes/markdown-block.css +5 -4
  198. package/dist/styles/recipes/markdown.css +68 -26
  199. package/dist/styles/recipes/morphing-panel.css +8 -8
  200. package/dist/styles/recipes/page-layout.css +20 -12
  201. package/dist/styles/recipes/pagination.css +4 -5
  202. package/dist/styles/recipes/phone-input.css +12 -8
  203. package/dist/styles/recipes/popup-controls.css +166 -33
  204. package/dist/styles/recipes/popup-drawer.css +3 -14
  205. package/dist/styles/recipes/popup-lists.css +128 -27
  206. package/dist/styles/recipes/popup-motion.css +4 -45
  207. package/dist/styles/recipes/popup-toast.css +55 -26
  208. package/dist/styles/recipes/popup.css +139 -141
  209. package/dist/styles/recipes/range.css +54 -18
  210. package/dist/styles/recipes/resizable.css +2 -7
  211. package/dist/styles/recipes/rich-tooltip.css +24 -34
  212. package/dist/styles/recipes/scroll-area.css +8 -1
  213. package/dist/styles/recipes/sidebar-menu.css +166 -34
  214. package/dist/styles/recipes/sidebar-parts.css +192 -0
  215. package/dist/styles/recipes/sidebar.css +188 -128
  216. package/dist/styles/recipes/source-badge.css +1 -1
  217. package/dist/styles/recipes/spinner.css +8 -1
  218. package/dist/styles/recipes/stepper.css +31 -13
  219. package/dist/styles/recipes/switch.css +68 -23
  220. package/dist/styles/recipes/table-of-contents.css +9 -1
  221. package/dist/styles/recipes/table.css +14 -10
  222. package/dist/styles/recipes/tabs.css +17 -12
  223. package/dist/styles/recipes/task-list.css +7 -14
  224. package/dist/styles/recipes/thread-rail.css +42 -19
  225. package/dist/styles/recipes/timeline.css +2 -0
  226. package/dist/styles/recipes/toolbar.css +27 -25
  227. package/dist/styles/recipes/track-highlight.css +10 -7
  228. package/dist/styles/recipes/tree.css +31 -4
  229. package/dist/styles/recipes/user-ask-details.css +0 -1
  230. package/dist/styles/recipes/user-ask.css +4 -4
  231. package/dist/styles/theme.css +37 -16
  232. package/dist/task-list.d.ts +4 -1
  233. package/dist/task-list.js +24 -8
  234. package/dist/thread-rail.d.ts +1 -1
  235. package/dist/thread-rail.js +61 -19
  236. package/dist/transcript-divider.js +2 -1
  237. package/dist/ui/alert.d.ts +3 -1
  238. package/dist/ui/alert.js +14 -8
  239. package/dist/ui/autocomplete.js +3 -1
  240. package/dist/ui/avatar.d.ts +8 -0
  241. package/dist/ui/avatar.js +23 -3
  242. package/dist/ui/badge.js +1 -1
  243. package/dist/ui/breadcrumb.d.ts +27 -0
  244. package/dist/ui/breadcrumb.js +88 -0
  245. package/dist/ui/button.d.ts +2 -1
  246. package/dist/ui/button.js +1 -1
  247. package/dist/ui/calendar.js +2 -2
  248. package/dist/ui/card.d.ts +3 -1
  249. package/dist/ui/card.js +12 -7
  250. package/dist/ui/checkbox.d.ts +2 -0
  251. package/dist/ui/code-diff.d.ts +1 -1
  252. package/dist/ui/code-diff.js +71 -27
  253. package/dist/ui/code.d.ts +5 -3
  254. package/dist/ui/code.js +26 -27
  255. package/dist/ui/color-picker.d.ts +5 -2
  256. package/dist/ui/color-picker.js +84 -36
  257. package/dist/ui/combobox-disabled-selection.d.ts +1 -2
  258. package/dist/ui/combobox-disabled-selection.js +7 -9
  259. package/dist/ui/combobox.js +13 -5
  260. package/dist/ui/command.d.ts +8 -2
  261. package/dist/ui/command.js +30 -14
  262. package/dist/ui/context-menu.js +3 -0
  263. package/dist/ui/dialog.d.ts +2 -1
  264. package/dist/ui/dialog.js +3 -22
  265. package/dist/ui/dockable-panel-geometry.d.ts +1 -0
  266. package/dist/ui/dockable-panel-geometry.js +7 -1
  267. package/dist/ui/dockable-panel.d.ts +9 -6
  268. package/dist/ui/dockable-panel.js +63 -23
  269. package/dist/ui/dropdown-menu.js +4 -3
  270. package/dist/ui/dropzone.d.ts +2 -2
  271. package/dist/ui/dropzone.js +75 -16
  272. package/dist/ui/emoji-picker.d.ts +18 -0
  273. package/dist/ui/emoji-picker.js +136 -0
  274. package/dist/ui/empty.d.ts +3 -1
  275. package/dist/ui/empty.js +12 -7
  276. package/dist/ui/gradient-editor.d.ts +1 -1
  277. package/dist/ui/gradient-editor.js +37 -21
  278. package/dist/ui/infinite-canvas.d.ts +3 -2
  279. package/dist/ui/infinite-canvas.js +63 -19
  280. package/dist/ui/input-group.d.ts +0 -3
  281. package/dist/ui/input-group.js +3 -13
  282. package/dist/ui/input-otp.d.ts +1 -1
  283. package/dist/ui/input-otp.js +32 -15
  284. package/dist/ui/input.js +4 -4
  285. package/dist/ui/item.d.ts +2 -1
  286. package/dist/ui/item.js +11 -7
  287. package/dist/ui/live-status.d.ts +7 -0
  288. package/dist/ui/live-status.js +17 -0
  289. package/dist/ui/markdown-elements.d.ts +6 -0
  290. package/dist/ui/markdown-elements.js +31 -1
  291. package/dist/ui/menubar.js +2 -2
  292. package/dist/ui/morphing-panel.js +2 -1
  293. package/dist/ui/native-select.d.ts +1 -1
  294. package/dist/ui/native-select.js +24 -11
  295. package/dist/ui/page-layout.d.ts +2 -2
  296. package/dist/ui/page-layout.js +2 -4
  297. package/dist/ui/pagination.d.ts +7 -5
  298. package/dist/ui/pagination.js +19 -13
  299. package/dist/ui/phone-input.d.ts +1 -0
  300. package/dist/ui/phone-input.js +56 -58
  301. package/dist/ui/radio-group.d.ts +2 -0
  302. package/dist/ui/resizable.d.ts +3 -2
  303. package/dist/ui/resizable.js +5 -3
  304. package/dist/ui/responsive-dialog.d.ts +1 -1
  305. package/dist/ui/responsive-dialog.js +4 -21
  306. package/dist/ui/rich-tooltip.d.ts +10 -4
  307. package/dist/ui/rich-tooltip.js +81 -33
  308. package/dist/ui/scroll-area.d.ts +1 -1
  309. package/dist/ui/scroll-area.js +43 -28
  310. package/dist/ui/sidebar-menu.d.ts +5 -1
  311. package/dist/ui/sidebar-menu.js +34 -34
  312. package/dist/ui/sidebar-provider.d.ts +10 -1
  313. package/dist/ui/sidebar-provider.js +38 -13
  314. package/dist/ui/sidebar-resize-rail.js +7 -3
  315. package/dist/ui/sidebar.d.ts +11 -6
  316. package/dist/ui/sidebar.js +48 -24
  317. package/dist/ui/slider.js +31 -22
  318. package/dist/ui/spinner.d.ts +3 -2
  319. package/dist/ui/spinner.js +3 -2
  320. package/dist/ui/stepper.d.ts +8 -1
  321. package/dist/ui/stepper.js +74 -25
  322. package/dist/ui/switch.d.ts +1 -0
  323. package/dist/ui/table-of-contents.d.ts +2 -2
  324. package/dist/ui/table-of-contents.js +20 -8
  325. package/dist/ui/table.js +4 -2
  326. package/dist/ui/textarea.d.ts +1 -1
  327. package/dist/ui/textarea.js +20 -9
  328. package/dist/ui/timeline.d.ts +3 -1
  329. package/dist/ui/timeline.js +17 -9
  330. package/dist/ui/toast.d.ts +11 -5
  331. package/dist/ui/toast.js +100 -54
  332. package/dist/ui/toolbar.d.ts +2 -1
  333. package/dist/ui/toolbar.js +3 -1
  334. package/dist/ui/tooltip.js +3 -20
  335. package/dist/ui/tree.js +120 -80
  336. package/dist/ui/trigger-menu.js +1 -0
  337. package/dist/user-ask.d.ts +10 -5
  338. package/dist/user-ask.js +31 -11
  339. package/package.json +5 -1
  340. package/dist/hooks/use-keyboard-navigation.d.ts +0 -3
  341. package/dist/hooks/use-keyboard-navigation.js +0 -22
package/dist/ui/tree.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { useSkin } from "../skin-provider.js";
4
4
  import { TrackHighlight } from "../extensions/track-highlight.js";
5
- import { Children, createContext, isValidElement, useContext, useRef, useState } from "react";
5
+ import { Children, createContext, isValidElement, useContext, useEffect, useId, useRef, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  import { ChevronRightIcon } from "lucide-react";
8
8
  import { useRender } from "@base-ui/react/use-render";
@@ -34,8 +34,18 @@ function compareTreeItems(a, b) {
34
34
  if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
35
35
  return 0;
36
36
  }
37
- function visibleItems(items) {
38
- return [...items.values()].filter((item) => !item.disabled && item.node.checkVisibility()).sort(compareTreeItems);
37
+ function isUnderExpandedAncestors(item, items, expanded) {
38
+ for (let parentValue = item.parentValue; parentValue !== void 0; parentValue = items.get(parentValue)?.parentValue) if (!expanded.has(parentValue)) return false;
39
+ return true;
40
+ }
41
+ function reachableItems(items, expanded) {
42
+ return [...items.values()].filter((item) => isUnderExpandedAncestors(item, items, expanded)).sort(compareTreeItems);
43
+ }
44
+ function resolveTabStop(items, expanded, selected, focusedValue) {
45
+ const reachable = reachableItems(items, expanded);
46
+ if (reachable.some((item) => item.value === focusedValue)) return focusedValue;
47
+ const enabled = reachable.filter((item) => !item.disabled);
48
+ return (enabled.find((item) => selected.has(item.value)) ?? enabled[0])?.value ?? null;
39
49
  }
40
50
  function focusItem(item, ctx) {
41
51
  if (!item) return;
@@ -55,40 +65,56 @@ function handleTypeahead(event, items, index, ctx, typeahead) {
55
65
  const now = Date.now();
56
66
  typeahead.current.query = now - typeahead.current.at > TYPEAHEAD_TIMEOUT ? event.key : typeahead.current.query + event.key;
57
67
  typeahead.current.at = now;
58
- const query = typeahead.current.query.toLowerCase();
59
- const match = [...items.slice(index + 1), ...items.slice(0, index + 1)].find((item) => labelOf(item, ctx).toLowerCase().startsWith(query));
68
+ const typed = typeahead.current.query.toLowerCase();
69
+ const query = [...typed].every((character) => character === typed[0]) ? typed.charAt(0) : typed;
70
+ const searchStart = query.length === 1 ? index + 1 : index;
71
+ const match = [...items.slice(searchStart), ...items.slice(0, searchStart)].find((item) => labelOf(item, ctx).toLowerCase().startsWith(query));
60
72
  if (match) focusItem(match, ctx);
61
73
  }
62
- function registeredItemFromTarget(target, root, itemsByNode) {
63
- let node = target;
64
- while (node && root.contains(node)) {
65
- const item = itemsByNode.get(node);
66
- if (item) return item;
67
- if (node === root) return;
68
- node = node.parentElement;
69
- }
70
- }
71
74
  function getTreeKeyboardState(event, ctx) {
72
75
  if (event.defaultPrevented || !(event.target instanceof HTMLElement)) return null;
73
- const current = ctx.getItemFromTarget(event.target, event.currentTarget);
74
- if (!current) return null;
75
- const items = ctx.getVisibleItems();
76
- const index = items.indexOf(current);
76
+ const item = ctx.getItemFromTarget(event.target, event.currentTarget);
77
+ if (!item) return null;
78
+ const items = ctx.getNavigableItems();
79
+ const index = items.indexOf(item);
77
80
  if (index === -1) return null;
78
81
  return {
79
- value: current.value,
82
+ item,
80
83
  items,
81
84
  index,
82
- isBranch: current.expandable,
83
- isExpanded: ctx.expanded.has(current.value),
84
- level: current.level
85
+ isExpanded: ctx.expanded.has(item.value)
85
86
  };
86
87
  }
88
+ const RTL_ARROW_KEYS = {
89
+ ArrowLeft: "ArrowRight",
90
+ ArrowRight: "ArrowLeft"
91
+ };
92
+ function handleInlineArrow(key, { item, items, index, isExpanded }, ctx) {
93
+ const isBranch = item.expandable && !item.disabled;
94
+ if (key === "ArrowRight") {
95
+ if (!isBranch) return;
96
+ if (isExpanded) focusItem(items[index + 1], ctx);
97
+ else ctx.toggleExpanded(item.value, true, "keyboard");
98
+ return;
99
+ }
100
+ if (isBranch && isExpanded) ctx.toggleExpanded(item.value, false, "keyboard");
101
+ else focusParentItem(items, index, item.level, ctx);
102
+ }
103
+ function activateItem(key, item, ctx) {
104
+ if (item.disabled) return;
105
+ if (key === " ") {
106
+ ctx.select(item.value, "keyboard", ctx.selectionMode === "multiple");
107
+ return;
108
+ }
109
+ ctx.select(item.value, "keyboard", false);
110
+ if (item.expandable) ctx.toggleExpanded(item.value, void 0, "keyboard");
111
+ }
87
112
  function handleTreeKeyDown(event, ctx, typeahead) {
88
113
  const state = getTreeKeyboardState(event, ctx);
89
114
  if (!state) return;
90
- const { value, items, index, isBranch, isExpanded, level } = state;
91
- switch (event.key) {
115
+ const { item, items, index } = state;
116
+ const key = getComputedStyle(event.currentTarget).direction === "rtl" && RTL_ARROW_KEYS[event.key] || event.key;
117
+ switch (key) {
92
118
  case "ArrowDown":
93
119
  event.preventDefault();
94
120
  focusItem(items[index + 1], ctx);
@@ -98,14 +124,9 @@ function handleTreeKeyDown(event, ctx, typeahead) {
98
124
  focusItem(items[index - 1], ctx);
99
125
  break;
100
126
  case "ArrowRight":
101
- event.preventDefault();
102
- if (isBranch && !isExpanded) ctx.toggleExpanded(value, true, "keyboard");
103
- else if (isBranch && isExpanded) focusItem(items[index + 1], ctx);
104
- break;
105
127
  case "ArrowLeft":
106
128
  event.preventDefault();
107
- if (isBranch && isExpanded) ctx.toggleExpanded(value, false, "keyboard");
108
- else focusParentItem(items, index, level, ctx);
129
+ handleInlineArrow(key, state, ctx);
109
130
  break;
110
131
  case "Home":
111
132
  event.preventDefault();
@@ -116,13 +137,9 @@ function handleTreeKeyDown(event, ctx, typeahead) {
116
137
  focusItem(items[items.length - 1], ctx);
117
138
  break;
118
139
  case "Enter":
119
- event.preventDefault();
120
- ctx.select(value, "keyboard", false);
121
- if (isBranch) ctx.toggleExpanded(value, void 0, "keyboard");
122
- break;
123
140
  case " ":
124
141
  event.preventDefault();
125
- ctx.select(value, "keyboard", ctx.selectionMode === "multiple");
142
+ activateItem(key, item, ctx);
126
143
  break;
127
144
  default: handleTypeahead(event, items, index, ctx, typeahead);
128
145
  }
@@ -132,31 +149,23 @@ function Tree({ selectionMode = "single", indicator, value, defaultValue, onValu
132
149
  const [selectedState, setSelectedState] = useState(() => new Set(defaultValue));
133
150
  const [expandedState, setExpandedState] = useState(() => new Set(defaultExpandedValue));
134
151
  const [focusedValue, setFocusedValue] = useState(null);
152
+ const [items, setItems] = useState(() => /* @__PURE__ */ new Map());
153
+ const [registerItem] = useState(() => (item) => {
154
+ setItems((current) => new Map(current).set(item.value, item));
155
+ return () => setItems((current) => {
156
+ if (current.get(item.value) !== item) return current;
157
+ const next = new Map(current);
158
+ next.delete(item.value);
159
+ return next;
160
+ });
161
+ });
135
162
  const selected = value ? new Set(value) : selectedState;
136
163
  const expanded = expandedValue ? new Set(expandedValue) : expandedState;
137
164
  const typeahead = useRef({
138
165
  query: "",
139
166
  at: 0
140
167
  });
141
- const itemsRef = useRef(/* @__PURE__ */ new Map());
142
- const itemsByNodeRef = useRef(/* @__PURE__ */ new WeakMap());
143
168
  const labelsRef = useRef(/* @__PURE__ */ new Map());
144
- const seededFocusRef = useRef(false);
145
- const registerItem = (item) => {
146
- const previous = itemsRef.current.get(item.value);
147
- if (previous) itemsByNodeRef.current.delete(previous.node);
148
- itemsRef.current.set(item.value, item);
149
- itemsByNodeRef.current.set(item.node, item);
150
- if (!seededFocusRef.current && !item.disabled) {
151
- seededFocusRef.current = true;
152
- setFocusedValue(item.value);
153
- }
154
- return () => {
155
- if (itemsRef.current.get(item.value) !== item) return;
156
- itemsRef.current.delete(item.value);
157
- itemsByNodeRef.current.delete(item.node);
158
- };
159
- };
160
169
  const registerLabel = (itemValue, node) => {
161
170
  if (!node) return;
162
171
  labelsRef.current.set(itemValue, node);
@@ -164,10 +173,15 @@ function Tree({ selectionMode = "single", indicator, value, defaultValue, onValu
164
173
  if (labelsRef.current.get(itemValue) === node) labelsRef.current.delete(itemValue);
165
174
  };
166
175
  };
167
- const getVisibleItems = () => visibleItems(itemsRef.current);
168
- const getItemFromTarget = (target, root) => registeredItemFromTarget(target, root, itemsByNodeRef.current);
176
+ const getNavigableItems = () => reachableItems(items, expanded).filter((item) => item.node.checkVisibility());
177
+ const getItemFromTarget = (target, root) => {
178
+ const node = target.closest("[data-control-family=\"tree\"][data-slot=\"item\"]");
179
+ if (!(node instanceof HTMLLIElement) || !root.contains(node)) return;
180
+ const item = items.get(node.dataset.value ?? "");
181
+ return item?.node === node ? item : void 0;
182
+ };
169
183
  const getLabel = (itemValue) => labelsRef.current.get(itemValue)?.textContent ?? "";
170
- const getItem = (itemValue) => itemsRef.current.get(itemValue);
184
+ const getItem = (itemValue) => items.get(itemValue);
171
185
  const select = (itemValue, reason, toggle) => {
172
186
  if (selectionMode === "none") return;
173
187
  let next;
@@ -201,10 +215,10 @@ function Tree({ selectionMode = "single", indicator, value, defaultValue, onValu
201
215
  indicator: resolvedIndicator,
202
216
  selected,
203
217
  expanded,
204
- focusedValue,
218
+ tabStopValue: resolveTabStop(items, expanded, selected, focusedValue),
205
219
  registerItem,
206
220
  registerLabel,
207
- getVisibleItems,
221
+ getNavigableItems,
208
222
  getItemFromTarget,
209
223
  getLabel,
210
224
  getItem,
@@ -252,33 +266,48 @@ function Tree({ selectionMode = "single", indicator, value, defaultValue, onValu
252
266
  }
253
267
  function TreeItem({ value, disabled = false, label, className, style, children, ref, ...props }) {
254
268
  const tree = useTree();
255
- const level = (useContext(TreeItemContext)?.level ?? 0) + 1;
269
+ const parent = useContext(TreeItemContext);
270
+ const parentValue = parent?.value;
271
+ const level = (parent?.level ?? 0) + 1;
256
272
  const expandable = Children.toArray(children).some((child) => isValidElement(child) && child.type === TreeItemContent);
273
+ const labelId = useId();
274
+ const [labelled, setLabelled] = useState(false);
275
+ const nodeRef = useRef(null);
276
+ const { registerItem } = tree;
277
+ useEffect(() => {
278
+ const node = nodeRef.current;
279
+ if (!node) return;
280
+ return registerItem({
281
+ value,
282
+ parentValue,
283
+ node,
284
+ level,
285
+ disabled,
286
+ expandable,
287
+ label
288
+ });
289
+ }, [
290
+ registerItem,
291
+ value,
292
+ parentValue,
293
+ level,
294
+ disabled,
295
+ expandable,
296
+ label
297
+ ]);
257
298
  const expanded = expandable && tree.expanded.has(value);
258
299
  const selected = tree.selected.has(value);
259
300
  const itemContext = {
260
301
  value,
261
302
  level,
262
303
  disabled,
263
- expandable
304
+ expandable,
305
+ labelId,
306
+ setLabelled
264
307
  };
265
308
  const itemRef = (node) => {
266
- const itemNode = node instanceof HTMLLIElement ? node : null;
267
- const refCleanup = setRef(ref, itemNode);
268
- if (!itemNode) return;
269
- const unregister = tree.registerItem({
270
- value,
271
- node: itemNode,
272
- level,
273
- disabled,
274
- expandable,
275
- label
276
- });
277
- return () => {
278
- unregister();
279
- if (typeof refCleanup === "function") refCleanup();
280
- else setRef(ref, null);
281
- };
309
+ nodeRef.current = node instanceof HTMLLIElement ? node : null;
310
+ return setRef(ref, nodeRef.current);
282
311
  };
283
312
  const baseClass = cn("list-none", className);
284
313
  const itemStyle = {
@@ -286,6 +315,7 @@ function TreeItem({ value, disabled = false, label, className, style, children,
286
315
  ...style
287
316
  };
288
317
  const shared = {
318
+ "aria-labelledby": labelled ? labelId : void 0,
289
319
  ...props,
290
320
  "data-control-ui": "tree",
291
321
  "data-control-family": "tree",
@@ -298,7 +328,7 @@ function TreeItem({ value, disabled = false, label, className, style, children,
298
328
  "aria-level": level,
299
329
  "aria-selected": tree.selectionMode === "none" ? void 0 : selected,
300
330
  "aria-disabled": disabled || void 0,
301
- tabIndex: tree.focusedValue === value ? 0 : -1,
331
+ tabIndex: tree.tabStopValue === value ? 0 : -1,
302
332
  style: itemStyle
303
333
  };
304
334
  if (!expandable) return /* @__PURE__ */ jsx(TreeItemContext.Provider, {
@@ -321,7 +351,7 @@ function TreeItem({ value, disabled = false, label, className, style, children,
321
351
  ...shared,
322
352
  role: "treeitem",
323
353
  "aria-expanded": expanded,
324
- tabIndex: tree.focusedValue === value ? 0 : -1,
354
+ tabIndex: shared.tabIndex,
325
355
  "data-state": expanded ? "open" : "closed",
326
356
  className: cn(renderProps.className, baseClass)
327
357
  }),
@@ -353,7 +383,7 @@ function TreeItemTrigger({ className, children, onClick, render, ...props }) {
353
383
  if (item.expandable) tree.toggleExpanded(item.value);
354
384
  focusItem(tree.getItem(item.value), tree);
355
385
  },
356
- className: cn("flex cursor-pointer select-none items-center", "pl-[calc(var(--\\_tree-level,1)*1.25rem)]", "aria-disabled:pointer-events-none", className),
386
+ className: cn("flex cursor-pointer select-none items-center", "aria-disabled:pointer-events-none", className),
357
387
  children
358
388
  }
359
389
  });
@@ -378,12 +408,20 @@ function TreeItemIndicator({ className, children, ...props }) {
378
408
  "aria-hidden": true,
379
409
  className: cn("inline-flex shrink-0 items-center justify-center", className),
380
410
  ...props,
381
- children: children ?? /* @__PURE__ */ jsx(ChevronRightIcon, { className: "size-4" })
411
+ children: children ?? /* @__PURE__ */ jsx(ChevronRightIcon, {
412
+ "data-icon-dir": "inline",
413
+ className: "size-4"
414
+ })
382
415
  });
383
416
  }
384
417
  function TreeItemLabel({ className, children, render, ref, ...props }) {
385
418
  const tree = useTree();
386
419
  const item = useTreeItem();
420
+ const { setLabelled } = item;
421
+ useEffect(() => {
422
+ setLabelled(true);
423
+ return () => setLabelled(false);
424
+ }, [setLabelled]);
387
425
  const labelRef = (node) => {
388
426
  const unregister = tree.registerLabel(item.value, node);
389
427
  const cleanup = setRef(ref, node);
@@ -398,8 +436,10 @@ function TreeItemLabel({ className, children, render, ref, ...props }) {
398
436
  defaultTagName: "span",
399
437
  render,
400
438
  props: {
439
+ title: typeof children === "string" ? children : void 0,
401
440
  ...props,
402
441
  ref: labelRef,
442
+ id: item.labelId,
403
443
  "data-control-ui": "tree",
404
444
  "data-control-family": "tree",
405
445
  "data-slot": "item-label",
@@ -103,6 +103,7 @@ function TriggerMenuGroup({ className, ...props }) {
103
103
  "data-control-family": "popup",
104
104
  "data-popup-kind": "trigger-menu",
105
105
  "data-slot": "group",
106
+ role: "group",
106
107
  className: cn("flex flex-col", className),
107
108
  ...props
108
109
  });
@@ -6,7 +6,7 @@ import { CSSProperties, ComponentProps } from "react";
6
6
  type UserAskStyleProps<Props, Style> = Omit<Props, "style"> & {
7
7
  style?: CSSProperties & Style;
8
8
  };
9
- export declare function UserAsk({ onComplete, onDismiss, autoFocus, className, children, ...props }: UserAskProps): import("react").JSX.Element;
9
+ export declare function UserAsk({ onComplete, onDismiss, autoFocus, questionStatusLabel, className, children, ...props }: UserAskProps): import("react").JSX.Element;
10
10
  export type UserAskHeaderProps = ComponentProps<"header"> & {
11
11
  style?: CSSProperties & UserAskKnobStyle;
12
12
  };
@@ -44,8 +44,13 @@ export type UserAskFooterProps = ComponentProps<"footer"> & {
44
44
  style?: CSSProperties & UserAskKnobStyle;
45
45
  };
46
46
  export declare function UserAskFooter({ className, ...props }: UserAskFooterProps): import("react").JSX.Element;
47
- export type UserAskDismissProps = ComponentProps<typeof Button>;
48
- export declare function UserAskDismiss({ className, children, onClick, ...props }: UserAskDismissProps): import("react").JSX.Element;
49
- export type UserAskSubmitProps = ComponentProps<typeof Button>;
50
- export declare function UserAskSubmit({ className, children, disabled, onClick, ...props }: UserAskSubmitProps): import("react").JSX.Element;
47
+ export type UserAskDismissProps = ComponentProps<typeof Button> & {
48
+ label?: string;
49
+ };
50
+ export declare function UserAskDismiss({ label, className, children, onClick, ...props }: UserAskDismissProps): import("react").JSX.Element;
51
+ export type UserAskSubmitProps = ComponentProps<typeof Button> & {
52
+ continueLabel?: string;
53
+ submitLabel?: string;
54
+ };
55
+ export declare function UserAskSubmit({ continueLabel, submitLabel, className, children, disabled, onClick, ...props }: UserAskSubmitProps): import("react").JSX.Element;
51
56
  //#endregion
package/dist/user-ask.js CHANGED
@@ -3,7 +3,8 @@ import { cn } from "./lib/cn.js";
3
3
  import { useUserAsk } from "./hooks/use-user-ask.js";
4
4
  import { Button } from "./ui/button.js";
5
5
  import { Kbd } from "./ui/kbd.js";
6
- import { createContext, useContext, useEffect, useId, useRef } from "react";
6
+ import { LiveStatus } from "./ui/live-status.js";
7
+ import { createContext, useContext, useEffect, useId, useRef, useState } from "react";
7
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
8
9
  import { Check, ChevronLeft, ChevronRight, PencilLine } from "lucide-react";
9
10
  //#region src/user-ask.tsx
@@ -19,13 +20,18 @@ function useUserAskQuestionContext() {
19
20
  if (!context) throw new Error("UserAsk options must be rendered inside <UserAskQuestion>.");
20
21
  return context;
21
22
  }
22
- function UserAsk({ onComplete, onDismiss, autoFocus = false, className, children, ...props }) {
23
+ function defaultQuestionStatusLabel(current, total, title) {
24
+ return `Question ${current} of ${total}: ${title}`;
25
+ }
26
+ function UserAsk({ onComplete, onDismiss, autoFocus = false, questionStatusLabel = defaultQuestionStatusLabel, className, children, ...props }) {
23
27
  const ask = useUserAsk({
24
28
  onComplete,
25
29
  onDismiss
26
30
  });
27
31
  const titleId = useId();
28
32
  const panelRef = useRef(null);
33
+ const [hasNavigated, setHasNavigated] = useState(false);
34
+ const questionStatus = hasNavigated && ask.activeQuestion ? questionStatusLabel(ask.activeIndex + 1, ask.questions.length, ask.activeQuestion.title) : "";
29
35
  useEffect(() => {
30
36
  if (autoFocus) panelRef.current?.focus();
31
37
  }, [autoFocus]);
@@ -33,6 +39,7 @@ function UserAsk({ onComplete, onDismiss, autoFocus = false, className, children
33
39
  useEffect(() => {
34
40
  if (lastActiveIndex.current === ask.activeIndex) return;
35
41
  lastActiveIndex.current = ask.activeIndex;
42
+ setHasNavigated(true);
36
43
  const panel = panelRef.current;
37
44
  const active = document.activeElement;
38
45
  const activeIsUsable = active instanceof HTMLElement && panel?.contains(active) && active.checkVisibility();
@@ -43,7 +50,7 @@ function UserAsk({ onComplete, onDismiss, autoFocus = false, className, children
43
50
  ...ask,
44
51
  titleId
45
52
  },
46
- children: /* @__PURE__ */ jsx("section", {
53
+ children: /* @__PURE__ */ jsxs("section", {
47
54
  ref: panelRef,
48
55
  "data-control-ui": "user-ask",
49
56
  "data-control-family": "user-ask",
@@ -54,7 +61,7 @@ function UserAsk({ onComplete, onDismiss, autoFocus = false, className, children
54
61
  onKeyDown: ask.handleKeyDown,
55
62
  className: cn("relative w-full", className),
56
63
  ...props,
57
- children
64
+ children: [children, /* @__PURE__ */ jsx(LiveStatus, { message: questionStatus })]
58
65
  })
59
66
  });
60
67
  }
@@ -212,6 +219,7 @@ function UserAskOption({ value, recommended = false, disabled = false, className
212
219
  role: question.multiple ? "checkbox" : "radio",
213
220
  "aria-checked": selected,
214
221
  disabled,
222
+ tabIndex: ask.isTabStop(question.id, key) ? 0 : -1,
215
223
  "data-control-ui": "user-ask",
216
224
  "data-control-family": "user-ask",
217
225
  "data-slot": "option",
@@ -297,6 +305,7 @@ function UserAskOptionInput({ label = "Other", placeholder = "Type your own answ
297
305
  role: question.multiple ? "checkbox" : "radio",
298
306
  "aria-checked": false,
299
307
  disabled,
308
+ tabIndex: ask.isTabStop(question.id, key) ? 0 : -1,
300
309
  "data-control-ui": "user-ask",
301
310
  "data-control-family": "user-ask",
302
311
  "data-slot": "option",
@@ -334,7 +343,7 @@ function UserAskOptionInput({ label = "Other", placeholder = "Type your own answ
334
343
  type: "button",
335
344
  role: "checkbox",
336
345
  "aria-checked": true,
337
- "aria-label": `Clear ${label}`,
346
+ "aria-label": label,
338
347
  "data-control-ui": "user-ask",
339
348
  "data-control-family": "user-ask",
340
349
  "data-slot": "option-indicator",
@@ -343,12 +352,17 @@ function UserAskOptionInput({ label = "Other", placeholder = "Type your own answ
343
352
  onClick: () => ask.select(question.id, key),
344
353
  className: "inline-flex shrink-0 items-center justify-center",
345
354
  children: /* @__PURE__ */ jsx(Check, { className: "size-3" })
346
- }) : /* @__PURE__ */ jsx("span", {
347
- "aria-hidden": "true",
355
+ }) : /* @__PURE__ */ jsx("button", {
356
+ type: "button",
357
+ role: "radio",
358
+ "aria-checked": true,
359
+ "aria-label": label,
360
+ tabIndex: -1,
348
361
  "data-control-ui": "user-ask",
349
362
  "data-control-family": "user-ask",
350
363
  "data-slot": "option-indicator",
351
364
  "data-selected": "",
365
+ onClick: () => inputRef.current?.focus(),
352
366
  className: "inline-flex shrink-0 items-center justify-center",
353
367
  children: /* @__PURE__ */ jsx(PencilLine, { className: "size-3" })
354
368
  }), /* @__PURE__ */ jsx("input", {
@@ -376,7 +390,7 @@ function UserAskFooter({ className, ...props }) {
376
390
  ...props
377
391
  });
378
392
  }
379
- function UserAskDismiss({ className, children, onClick, ...props }) {
393
+ function UserAskDismiss({ label = "Dismiss", className, children, onClick, ...props }) {
380
394
  const ask = useUserAskContext();
381
395
  function handleClick(event) {
382
396
  onClick?.(event);
@@ -389,13 +403,17 @@ function UserAskDismiss({ className, children, onClick, ...props }) {
389
403
  type: "button",
390
404
  variant: "quiet",
391
405
  size: "xs",
406
+ "aria-keyshortcuts": "Escape",
392
407
  onClick: handleClick,
393
408
  className: cn(className),
394
409
  ...props,
395
- children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: ["Dismiss", /* @__PURE__ */ jsx(Kbd, { children: "esc" })] })
410
+ children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [label, /* @__PURE__ */ jsx(Kbd, {
411
+ "aria-hidden": "true",
412
+ children: "Esc"
413
+ })] })
396
414
  });
397
415
  }
398
- function UserAskSubmit({ className, children, disabled, onClick, ...props }) {
416
+ function UserAskSubmit({ continueLabel = "Continue", submitLabel = "Submit answers", className, children, disabled, onClick, ...props }) {
399
417
  const ask = useUserAskContext();
400
418
  function handleClick(event) {
401
419
  onClick?.(event);
@@ -410,10 +428,12 @@ function UserAskSubmit({ className, children, disabled, onClick, ...props }) {
410
428
  tone: "primary",
411
429
  size: "xs",
412
430
  disabled: disabled ?? !ask.canContinue,
431
+ "aria-keyshortcuts": "Enter",
413
432
  onClick: handleClick,
414
433
  className,
415
434
  ...props,
416
- children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: ["Continue", /* @__PURE__ */ jsx(Kbd, {
435
+ children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [ask.isLastQuestion ? submitLabel : continueLabel, /* @__PURE__ */ jsx(Kbd, {
436
+ "aria-hidden": "true",
417
437
  "data-user-ask-submit-kbd": "true",
418
438
  children: "⏎"
419
439
  })] })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl-ui/react",
3
- "version": "0.8.7",
3
+ "version": "0.8.9",
4
4
  "description": "Control UI components, blocks, and primitives with neutral defaults and consumer-owned themes.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,6 +40,7 @@
40
40
  "clsx": "^2.1.1",
41
41
  "cmdk": "^1.1.1",
42
42
  "diff": "^9.0.0",
43
+ "frimousse": "^0.4.0",
43
44
  "libphonenumber-js": "^1.13.13",
44
45
  "lucide-react": "^1.47.0",
45
46
  "prosemirror-commands": "^1.7.2",
@@ -69,6 +70,9 @@
69
70
  "diff": {
70
71
  "optional": true
71
72
  },
73
+ "frimousse": {
74
+ "optional": true
75
+ },
72
76
  "libphonenumber-js": {
73
77
  "optional": true
74
78
  },
@@ -1,3 +0,0 @@
1
- //#region src/hooks/use-keyboard-navigation.d.ts
2
- export declare function useKeyboardNavigation(): boolean;
3
- //#endregion
@@ -1,22 +0,0 @@
1
- import { useEffect, useState } from "react";
2
- //#region src/hooks/use-keyboard-navigation.ts
3
- function useKeyboardNavigation() {
4
- const [keyboardNavigation, setKeyboardNavigation] = useState(false);
5
- useEffect(() => {
6
- function markKeyboardNavigation(event) {
7
- if (event.key === "Tab") setKeyboardNavigation(true);
8
- }
9
- function clearKeyboardNavigation() {
10
- setKeyboardNavigation(false);
11
- }
12
- document.addEventListener("keydown", markKeyboardNavigation, true);
13
- document.addEventListener("pointerdown", clearKeyboardNavigation, true);
14
- return () => {
15
- document.removeEventListener("keydown", markKeyboardNavigation, true);
16
- document.removeEventListener("pointerdown", clearKeyboardNavigation, true);
17
- };
18
- }, []);
19
- return keyboardNavigation;
20
- }
21
- //#endregion
22
- export { useKeyboardNavigation };