@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
@@ -3,9 +3,9 @@ import { cn } from "./lib/cn.js";
3
3
  import { Button } from "./ui/button.js";
4
4
  import { DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE, collectEnvironmentVariables, rowsToEnvFileText } from "./lib/env-file.js";
5
5
  import { useEnvironmentVariables } from "./hooks/use-environment-variables.js";
6
- import { InputGroup, InputGroupAddon, InputGroupInput } from "./ui/input-group.js";
6
+ import { InputGroup, InputGroupAddon } from "./ui/input-group.js";
7
7
  import { Input } from "./ui/input.js";
8
- import { createContext, use, useId } from "react";
8
+ import { createContext, use, useId, useRef, useState } from "react";
9
9
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
10
  import { AlertTriangleIcon, EyeIcon, EyeOffIcon, FileUpIcon, PlusIcon, RotateCcwIcon, SaveIcon, Trash2Icon } from "lucide-react";
11
11
  //#region src/environment-variables.tsx
@@ -15,18 +15,28 @@ function useEnvironmentVariablesContext(componentName) {
15
15
  if (!context) throw new Error(`${componentName} must be used inside <EnvironmentVariables.Root>.`);
16
16
  return context;
17
17
  }
18
- function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false, rowErrors, keyLabel = "Key", valueLabel = "Value", keyPlaceholder = "OPENAI_API_KEY", valuePlaceholder = "sk-...", duplicateKeyMessage = DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE, onSubmit, className, children, ...props }) {
19
- function handleSubmit(event) {
18
+ function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false, rowErrors, keyLabel = "Key", valueLabel = "Value", keyPlaceholder = "OPENAI_API_KEY", valuePlaceholder = "sk-…", duplicateKeyMessage = DUPLICATE_ENVIRONMENT_VARIABLE_MESSAGE, revealValueLabel = (key) => `Show value for ${key}`, onSubmit, className, children, ...props }) {
19
+ const pendingRef = useRef(false);
20
+ const [pending, setPending] = useState(false);
21
+ async function handleSubmit(event) {
20
22
  if (event.defaultPrevented || !onSubmit) return;
21
23
  event.preventDefault();
22
- if (editor.hasDuplicateKeys) return;
24
+ if (pendingRef.current || editor.hasDuplicateKeys) return;
23
25
  const rows = editor.getRowsForSubmit();
24
- onSubmit({
26
+ const payload = {
25
27
  rows,
26
28
  variables: collectEnvironmentVariables(rows),
27
29
  envFileText: rowsToEnvFileText(rows),
28
30
  reset: () => editor.resetRows(rows)
29
- });
31
+ };
32
+ pendingRef.current = true;
33
+ setPending(true);
34
+ try {
35
+ await onSubmit(payload);
36
+ } finally {
37
+ pendingRef.current = false;
38
+ setPending(false);
39
+ }
30
40
  }
31
41
  return /* @__PURE__ */ jsx(EnvironmentVariablesContext.Provider, {
32
42
  value: {
@@ -38,7 +48,9 @@ function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false,
38
48
  valueLabel,
39
49
  keyPlaceholder,
40
50
  valuePlaceholder,
41
- duplicateKeyMessage
51
+ duplicateKeyMessage,
52
+ revealValueLabel,
53
+ pending
42
54
  },
43
55
  children: /* @__PURE__ */ jsx("form", {
44
56
  "data-control-ui": "environment-variables",
@@ -47,6 +59,7 @@ function EnvironmentVariablesRoot({ editor, disabled = false, readOnly = false,
47
59
  "data-surface": "panel",
48
60
  "data-disabled": disabled ? "true" : void 0,
49
61
  "data-readonly": readOnly ? "true" : void 0,
62
+ "aria-busy": pending || void 0,
50
63
  className: cn("flex min-w-0 flex-col", className),
51
64
  onSubmit: handleSubmit,
52
65
  ...props,
@@ -157,6 +170,7 @@ function EnvironmentVariablesUploadButton({ inputLabel = "Import .env file", chi
157
170
  type: "file",
158
171
  accept: ".env,text/plain",
159
172
  className: "sr-only",
173
+ tabIndex: -1,
160
174
  disabled: isDisabled,
161
175
  "aria-label": inputLabel,
162
176
  onChange: handleChange
@@ -203,7 +217,7 @@ function EnvironmentVariablesRows({ rowErrors, className, children, ...props })
203
217
  });
204
218
  }
205
219
  function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props }) {
206
- const { editor, disabled, readOnly, rowErrors: contextRowErrors, keyLabel, valueLabel, keyPlaceholder, valuePlaceholder, duplicateKeyMessage } = useEnvironmentVariablesContext("EnvironmentVariables.Row");
220
+ const { editor, disabled, readOnly, rowErrors: contextRowErrors, keyLabel, valueLabel, keyPlaceholder, valuePlaceholder, duplicateKeyMessage, revealValueLabel } = useEnvironmentVariablesContext("EnvironmentVariables.Row");
207
221
  const resolvedRowErrors = rowErrors ?? contextRowErrors;
208
222
  const keyError = resolvedRowErrors?.[index]?.key ?? (editor.rowHasDuplicateKey(index) ? duplicateKeyMessage : null);
209
223
  const valueError = resolvedRowErrors?.[index]?.value;
@@ -211,8 +225,11 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
211
225
  const rowId = editor.getRowId(index);
212
226
  const keyInputId = `${rowId}-key`;
213
227
  const valueInputId = `${rowId}-value`;
214
- const revealValueLabel = editor.isValueRevealed(index) ? "Hide value" : "Show value";
215
- const removeRowLabel = `Remove environment variable ${row.key.trim() || index + 1}`;
228
+ const keyErrorId = `${keyInputId}-error`;
229
+ const valueErrorId = `${valueInputId}-error`;
230
+ const rowName = row.key.trim() || `row ${index + 1}`;
231
+ const valueRevealed = editor.isValueRevealed(index);
232
+ const removeRowLabel = `Remove environment variable ${rowName}`;
216
233
  function handlePaste(event) {
217
234
  if (editor.handlePaste(index, event.clipboardData.getData("text"))) event.preventDefault();
218
235
  }
@@ -235,19 +252,18 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
235
252
  children: keyLabel
236
253
  }),
237
254
  /* @__PURE__ */ jsx(Input, {
238
- "data-control-ui": "environment-variables",
239
- "data-control-family": "environment-variables",
240
- "data-slot": "key-input",
241
255
  id: keyInputId,
242
256
  size: "sm",
243
257
  value: row.key,
244
258
  placeholder: keyPlaceholder,
245
259
  disabled: isDisabled,
246
260
  "aria-invalid": Boolean(keyError),
261
+ "aria-describedby": keyError ? keyErrorId : void 0,
247
262
  onChange: (event) => editor.updateRow(index, { key: event.target.value }),
248
263
  onPaste: handlePaste
249
264
  }),
250
265
  keyError ? /* @__PURE__ */ jsx("p", {
266
+ id: keyErrorId,
251
267
  "data-control-ui": "environment-variables",
252
268
  "data-control-family": "environment-variables",
253
269
  "data-slot": "field-error",
@@ -267,35 +283,30 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
267
283
  children: valueLabel
268
284
  }),
269
285
  /* @__PURE__ */ jsxs(InputGroup, {
270
- "data-control-ui": "environment-variables",
271
- "data-control-family": "environment-variables",
272
- "data-slot": "value-group",
273
- "data-invalid": valueError ? "true" : void 0,
274
286
  size: "sm",
275
- children: [/* @__PURE__ */ jsx(InputGroupInput, {
276
- "data-control-ui": "environment-variables",
277
- "data-control-family": "environment-variables",
278
- "data-slot": "value-input",
287
+ children: [/* @__PURE__ */ jsx(Input, {
279
288
  id: valueInputId,
280
289
  value: row.value,
281
290
  placeholder: valuePlaceholder,
282
291
  disabled: isDisabled,
283
292
  "aria-invalid": Boolean(valueError),
284
- type: editor.isValueRevealed(index) ? "text" : "password",
293
+ "aria-describedby": valueError ? valueErrorId : void 0,
294
+ type: valueRevealed ? "text" : "password",
285
295
  onChange: (event) => editor.updateRow(index, { value: event.target.value }),
286
296
  onPaste: handlePaste
287
297
  }), /* @__PURE__ */ jsx(InputGroupAddon, {
288
- className: "pr-1",
298
+ className: "pe-1",
289
299
  children: /* @__PURE__ */ jsx(Button, {
290
300
  type: "button",
291
301
  variant: "quiet",
292
302
  size: "sm",
293
303
  iconOnly: true,
294
304
  disabled: isDisabled,
295
- "aria-label": revealValueLabel,
296
- title: revealValueLabel,
305
+ "aria-label": revealValueLabel(rowName),
306
+ "aria-pressed": valueRevealed,
307
+ title: revealValueLabel(rowName),
297
308
  onClick: () => editor.toggleValueVisibility(index),
298
- children: editor.isValueRevealed(index) ? /* @__PURE__ */ jsx(EyeOffIcon, {
309
+ children: valueRevealed ? /* @__PURE__ */ jsx(EyeOffIcon, {
299
310
  "aria-hidden": "true",
300
311
  className: "size-3.5"
301
312
  }) : /* @__PURE__ */ jsx(EyeIcon, {
@@ -306,6 +317,7 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
306
317
  })]
307
318
  }),
308
319
  valueError ? /* @__PURE__ */ jsx("p", {
320
+ id: valueErrorId,
309
321
  "data-control-ui": "environment-variables",
310
322
  "data-control-family": "environment-variables",
311
323
  "data-slot": "field-error",
@@ -322,7 +334,7 @@ function EnvironmentVariablesRow({ row, index, rowErrors, className, ...props })
322
334
  disabled,
323
335
  "aria-label": removeRowLabel,
324
336
  title: "Remove variable",
325
- className: "absolute top-2 right-1 sm:static sm:justify-self-end sm:self-start",
337
+ className: "absolute top-2 end-1 sm:static sm:justify-self-end sm:self-start",
326
338
  onClick: () => editor.removeRow(index),
327
339
  children: /* @__PURE__ */ jsx(Trash2Icon, {
328
340
  "aria-hidden": "true",
@@ -371,14 +383,14 @@ function EnvironmentVariablesResetButton({ children = "Reset", variant = "quiet"
371
383
  });
372
384
  }
373
385
  function EnvironmentVariablesSubmitButton({ children = "Save", variant = "solid", tone = "primary", size = "sm", disabled, ...props }) {
374
- const { editor, disabled: contextDisabled, readOnly } = useEnvironmentVariablesContext("EnvironmentVariables.SubmitButton");
386
+ const { editor, disabled: contextDisabled, readOnly, pending } = useEnvironmentVariablesContext("EnvironmentVariables.SubmitButton");
375
387
  if (readOnly) return null;
376
388
  return /* @__PURE__ */ jsxs(Button, {
377
389
  type: "submit",
378
390
  variant,
379
391
  tone,
380
392
  size,
381
- disabled: disabled ?? (contextDisabled || editor.hasDuplicateKeys),
393
+ disabled: disabled ?? (contextDisabled || pending || editor.hasDuplicateKeys),
382
394
  ...props,
383
395
  children: [/* @__PURE__ */ jsx(SaveIcon, {
384
396
  "aria-hidden": "true",
@@ -449,7 +461,7 @@ function EnvironmentVariablesReadOnlyList({ rows, emptyMessage = "No environment
449
461
  }))
450
462
  });
451
463
  }
452
- function EnvironmentVariablesReadOnlyItem({ name, value = "********", revealed = false, className, ...props }) {
464
+ function EnvironmentVariablesReadOnlyItem({ name, value, revealed = false, hiddenValueLabel = "Hidden value", className, ...props }) {
453
465
  return /* @__PURE__ */ jsxs("div", {
454
466
  "data-control-ui": "environment-variables",
455
467
  "data-control-family": "environment-variables",
@@ -461,13 +473,21 @@ function EnvironmentVariablesReadOnlyItem({ name, value = "********", revealed =
461
473
  "data-control-family": "environment-variables",
462
474
  "data-slot": "readonly-key",
463
475
  className: "min-w-0 truncate",
476
+ title: typeof name === "string" ? name : void 0,
464
477
  children: name
465
478
  }), /* @__PURE__ */ jsx("span", {
466
479
  "data-control-ui": "environment-variables",
467
480
  "data-control-family": "environment-variables",
468
481
  "data-slot": "readonly-value",
469
482
  className: "min-w-0 truncate",
470
- children: revealed ? value : "********"
483
+ title: revealed && typeof value === "string" ? value : void 0,
484
+ children: revealed ? value : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
485
+ "aria-hidden": "true",
486
+ children: "••••••••"
487
+ }), /* @__PURE__ */ jsx("span", {
488
+ className: "sr-only",
489
+ children: hiddenValueLabel
490
+ })] })
471
491
  })]
472
492
  });
473
493
  }
@@ -36,7 +36,8 @@ function editorChoices(context, draft) {
36
36
  if (!field) return [];
37
37
  if (draft.stage === "operator") return fieldOperators(field, context.operators).map((operator) => ({
38
38
  id: operator.id,
39
- label: operator.label
39
+ label: operator.label,
40
+ ariaLabel: operator.ariaLabel
40
41
  }));
41
42
  return valueChoices(field, draft.query);
42
43
  }
@@ -45,6 +46,9 @@ function searchLabel(stage, field) {
45
46
  if (stage === "operator") return "Filter operator";
46
47
  return `${field?.label ?? "Filter"} value`;
47
48
  }
49
+ function statusId(owner) {
50
+ return `${owner}-filter-status`;
51
+ }
48
52
  function handleSelection(context, draft, choice) {
49
53
  if (!choice || !draft) return;
50
54
  const field = context.fields.find((candidate) => candidate.id === draft.fieldId);
@@ -70,19 +74,21 @@ function handleEditorKeyDown(context, draft, event) {
70
74
  context.commit(draft.values);
71
75
  return true;
72
76
  }
73
- function EditorOptions({ field, invalidNumber }) {
77
+ function EditorOptions({ field, invalidNumber, owner }) {
74
78
  if (field?.loading || field?.error) return /* @__PURE__ */ jsx("div", {
79
+ id: statusId(owner),
75
80
  className: "p-3 text-caption",
76
- role: field.error ? "alert" : "status",
77
81
  children: field.error ?? "Loading values…"
78
82
  });
79
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: invalidNumber ? "Enter a finite number." : "No matching options." }), /* @__PURE__ */ jsx(ComboboxList, { children: (choice) => /* @__PURE__ */ jsx(ComboboxItem, {
83
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComboboxEmpty, { children: invalidNumber ? "Enter a number, like 42 or 3.5." : "No matching options." }), /* @__PURE__ */ jsx(ComboboxList, { children: (choice) => /* @__PURE__ */ jsx(ComboboxItem, {
80
84
  value: choice,
81
85
  disabled: choice.disabled,
86
+ "aria-label": choice.ariaLabel,
82
87
  children: /* @__PURE__ */ jsxs("span", {
83
88
  className: "flex min-w-0 items-center gap-2",
84
89
  children: [choice.icon, /* @__PURE__ */ jsx("span", {
85
90
  className: "truncate",
91
+ title: choice.label,
86
92
  children: choice.label
87
93
  })]
88
94
  })
@@ -113,14 +119,15 @@ function editorView(context, draft) {
113
119
  invalidNumber: stage === "value" && Boolean(customNumber) && query.trim() !== "" && field !== void 0 && parseCustomValue(field, query) === void 0,
114
120
  remote: stage === "value" && field?.optionsMode === "remote",
115
121
  unavailable: stage === "value" && Boolean(field?.loading || field?.error),
116
- label: searchLabel(stage, field)
122
+ label: searchLabel(stage, field),
123
+ described: stage === "value" && Boolean(field?.error)
117
124
  };
118
125
  }
119
126
  function FilterBarEditor({ owner, mode, anchorRef, children }) {
120
127
  const context = useFilterBarContext();
121
128
  const searchRef = useRef(null);
122
129
  const draft = context.draft?.owner === owner ? context.draft : null;
123
- const { field, stage, many, query, choices, selected, invalidNumber, remote, unavailable, label } = editorView(context, draft);
130
+ const { field, stage, many, query, choices, selected, invalidNumber, remote, unavailable, label, described } = editorView(context, draft);
124
131
  const canCommitMany = selected.length > 0 && !unavailable && !context.disabled && !context.readOnly;
125
132
  return /* @__PURE__ */ jsxs(Combobox, {
126
133
  items: choices,
@@ -158,6 +165,7 @@ function FilterBarEditor({ owner, mode, anchorRef, children }) {
158
165
  ref: searchRef,
159
166
  size: "sm",
160
167
  "aria-label": label,
168
+ "aria-describedby": described ? statusId(owner) : void 0,
161
169
  placeholder: label,
162
170
  inputMode: field?.type === "number" && stage === "value" ? "decimal" : void 0,
163
171
  onKeyDown: (event) => handleEditorKeyDown(context, draft, event)
@@ -165,14 +173,15 @@ function FilterBarEditor({ owner, mode, anchorRef, children }) {
165
173
  }),
166
174
  /* @__PURE__ */ jsx(EditorOptions, {
167
175
  field: stage === "value" ? field : void 0,
168
- invalidNumber
176
+ invalidNumber,
177
+ owner
169
178
  }),
170
179
  many && /* @__PURE__ */ jsx("div", {
171
180
  className: "flex justify-end p-2",
172
181
  children: /* @__PURE__ */ jsx(Button, {
173
182
  disabled: !canCommitMany,
174
183
  onClick: () => context.commit(draft?.values ?? []),
175
- children: "Done"
184
+ children: "Apply"
176
185
  })
177
186
  })
178
187
  ]
@@ -182,6 +191,7 @@ function FilterBarEditor({ owner, mode, anchorRef, children }) {
182
191
  function FilterBarInlineInput({ owner, inputRef, className, ...props }) {
183
192
  const context = useFilterBarContext();
184
193
  const draft = context.draft?.owner === owner ? context.draft : null;
194
+ const { label, described } = editorView(context, draft);
185
195
  function handleKeyDown(event) {
186
196
  props.onKeyDown?.(event);
187
197
  if (event.defaultPrevented || event.nativeEvent.isComposing) return;
@@ -197,6 +207,8 @@ function FilterBarInlineInput({ owner, inputRef, className, ...props }) {
197
207
  size: "sm",
198
208
  className,
199
209
  ...props,
210
+ "aria-label": draft ? label : props["aria-label"],
211
+ "aria-describedby": described ? statusId(owner) : props["aria-describedby"],
200
212
  onKeyDown: handleKeyDown
201
213
  });
202
214
  }
@@ -5,5 +5,6 @@ export declare function fieldOptions(field: FilterBarField): readonly FilterBarO
5
5
  export declare function parseCustomValue(field: FilterBarField, query: string): FilterBarScalar | undefined;
6
6
  export declare function allowsCustomValue(field: FilterBarField): boolean;
7
7
  export declare function formatFilterValue(item: FilterBarItem, field: FilterBarField | undefined): string;
8
+ export declare function describeFilter(item: FilterBarItem, fields: readonly FilterBarField[], operators: readonly FilterBarOperator[]): string;
8
9
  export declare function filterNavigationKeys(items: readonly FilterBarItem[]): string[];
9
10
  //#endregion
@@ -36,6 +36,15 @@ function formatFilterValue(item, field) {
36
36
  function fieldOptionsForLabel(field, value) {
37
37
  return (field ? fieldOptions(field).find((option) => option.value === value)?.label : void 0) ?? String(value);
38
38
  }
39
+ function describeFilter(item, fields, operators) {
40
+ const field = fields.find((candidate) => candidate.id === item.fieldId);
41
+ const operator = operators.find((candidate) => candidate.id === item.operatorId);
42
+ return [
43
+ field?.label ?? item.fieldId,
44
+ operator?.ariaLabel ?? operator?.label,
45
+ formatFilterValue(item, field)
46
+ ].filter(Boolean).join(" ");
47
+ }
39
48
  function filterNavigationKeys(items) {
40
49
  return items.flatMap((item) => [
41
50
  "field",
@@ -45,4 +54,4 @@ function filterNavigationKeys(items) {
45
54
  ].map((segment) => `${item.id}:${segment}`));
46
55
  }
47
56
  //#endregion
48
- export { allowsCustomValue, fieldOperators, fieldOptions, filterNavigationKeys, formatFilterValue, parseCustomValue };
57
+ export { allowsCustomValue, describeFilter, fieldOperators, fieldOptions, filterNavigationKeys, formatFilterValue, parseCustomValue };
@@ -1,8 +1,9 @@
1
1
  "use client";
2
+ import { prefersReducedMotion, readDurationMs } from "../lib/motion.js";
2
3
  import { useLayoutEffect, useRef, useState } from "react";
3
4
  //#region src/filter-bar/motion.ts
4
5
  function filterMotionEnabled(element) {
5
- return !window.matchMedia("(prefers-reduced-motion: reduce)").matches && Number.parseFloat(getComputedStyle(element).getPropertyValue("--duration-fast")) > 0;
6
+ return !prefersReducedMotion(element) && readDurationMs(element, "--duration-fast") > 0;
6
7
  }
7
8
  function animateMovement(element, before, after, duration, easing) {
8
9
  const x = before.left - after.left;
@@ -56,9 +57,8 @@ function useFilterBarMotion() {
56
57
  captured = null;
57
58
  cancel();
58
59
  const next = measure();
59
- const computed = getComputedStyle(root);
60
- const duration = Number.parseFloat(computed.getPropertyValue("--duration-fast"));
61
- const easing = computed.getPropertyValue("--ease-standard").trim();
60
+ const duration = readDurationMs(root, "--duration-fast");
61
+ const easing = getComputedStyle(root).getPropertyValue("--ease-standard").trim();
62
62
  animateLayout(elements, origin, next, animations, duration, easing);
63
63
  }
64
64
  };
@@ -2,7 +2,7 @@
2
2
  import { cn } from "../lib/cn.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { useFilterBarContext } from "./context.js";
5
- import { formatFilterValue } from "./model.js";
5
+ import { describeFilter, formatFilterValue } from "./model.js";
6
6
  import { FilterBarEditor, FilterBarInlineInput } from "./editor.js";
7
7
  import { useEffect, useId, useRef } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -17,7 +17,8 @@ function chipSegments(item, field, operator, draft) {
17
17
  }];
18
18
  if (operator || draft) segments.push({
19
19
  stage: "operator",
20
- label: operator?.label ?? "Operator…"
20
+ label: operator?.label ?? "Operator…",
21
+ spokenLabel: operator?.ariaLabel
21
22
  });
22
23
  if (item.value !== null || draft && operator) segments.push({
23
24
  stage: "value",
@@ -54,7 +55,7 @@ function FilterBarChipContent({ item, readOnly = false, className, ref, ...props
54
55
  "data-control-ui": "filter-bar",
55
56
  "data-control-family": "filter-bar",
56
57
  "data-slot": "remove",
57
- "aria-label": `Remove ${field?.label ?? item.fieldId} filter`,
58
+ "aria-label": `Remove filter: ${describeFilter(item, context.fields, context.operators)}`,
58
59
  disabled: context.disabled,
59
60
  onClick: () => context.remove(item),
60
61
  onKeyDown: (event) => context.navigate(event, item.id, "remove"),
@@ -75,10 +76,19 @@ function FilterBarSegment({ item, segment, locked, draft }) {
75
76
  className: "inline-flex min-w-0 items-center",
76
77
  children: /* @__PURE__ */ jsxs("span", {
77
78
  className: "flex min-w-0 items-center gap-1.5",
78
- children: [segment.icon, /* @__PURE__ */ jsx("span", {
79
- className: "truncate",
80
- children: segment.label
81
- })]
79
+ children: [
80
+ segment.icon,
81
+ /* @__PURE__ */ jsx("span", {
82
+ className: "truncate",
83
+ title: segment.label,
84
+ "aria-hidden": segment.spokenLabel ? true : void 0,
85
+ children: segment.label
86
+ }),
87
+ segment.spokenLabel ? /* @__PURE__ */ jsx("span", {
88
+ className: "sr-only",
89
+ children: segment.spokenLabel
90
+ }) : null
91
+ ]
82
92
  })
83
93
  });
84
94
  return /* @__PURE__ */ jsxs(Button, {
@@ -89,7 +99,7 @@ function FilterBarSegment({ item, segment, locked, draft }) {
89
99
  "data-control-family": "filter-bar",
90
100
  "data-slot": "segment",
91
101
  className: "shrink",
92
- "aria-label": `Edit ${segment.stage}: ${segment.label}`,
102
+ "aria-label": `Edit ${segment.stage}: ${segment.spokenLabel ?? segment.label}`,
93
103
  "aria-haspopup": "listbox",
94
104
  "aria-expanded": context.draft?.owner === item.id && context.draft.stage === segment.stage,
95
105
  disabled: context.disabled || draft,
@@ -97,6 +107,7 @@ function FilterBarSegment({ item, segment, locked, draft }) {
97
107
  onKeyDown: (event) => context.navigate(event, item.id, segment.stage),
98
108
  children: [segment.icon, /* @__PURE__ */ jsx("span", {
99
109
  className: "truncate",
110
+ title: segment.label,
100
111
  children: segment.label
101
112
  })]
102
113
  });
@@ -5,6 +5,7 @@ export type FilterBarValue = FilterBarScalar | FilterBarScalar[] | null;
5
5
  export type FilterBarOperator = {
6
6
  id: string;
7
7
  label: string;
8
+ ariaLabel?: string;
8
9
  arity?: "none" | "one" | "many";
9
10
  };
10
11
  export type FilterBarOption = {
@@ -1,13 +1,18 @@
1
1
  "use client";
2
2
  import { cn } from "./lib/cn.js";
3
+ import { LiveStatus } from "./ui/live-status.js";
3
4
  import { FilterBarContext } from "./filter-bar/context.js";
4
- import { fieldOperators, filterNavigationKeys } from "./filter-bar/model.js";
5
+ import { describeFilter, fieldOperators, filterNavigationKeys } from "./filter-bar/model.js";
5
6
  import { filterMotionEnabled, useFilterBarMotion } from "./filter-bar/motion.js";
6
7
  import { FilterBarAddButton, FilterBarChip, FilterBarChips, FilterBarClear, FilterBarInput } from "./filter-bar/parts.js";
7
8
  import { useState } from "react";
8
9
  import { jsx, jsxs } from "react/jsx-runtime";
9
10
  import { useRender } from "@base-ui/react/use-render";
10
11
  //#region src/filter-bar.tsx
12
+ function valueStatusMessage(field) {
13
+ if (field?.error) return typeof field.error === "string" ? field.error : "Couldn’t load values.";
14
+ if (field?.loading) return "Loading values…";
15
+ }
11
16
  function FilterBarRoot({ fields, operators, value, onValueChange, createItemId = () => crypto.randomUUID(), disabled = false, readOnly = false, className, children, ref, "aria-label": ariaLabel = "Filters", ...props }) {
12
17
  const [draft, setDraft] = useState(null);
13
18
  const [exits, setExits] = useState([]);
@@ -92,7 +97,7 @@ function FilterBarRoot({ fields, operators, value, onValueChange, createItemId =
92
97
  };
93
98
  motion.capture();
94
99
  onValueChange(next.editing ? value.map((current) => current.id === item.id ? item : current) : [...value, item]);
95
- setAnnouncement(next.editing ? "Filter updated." : "Filter added.");
100
+ setAnnouncement(`${next.editing ? "Updated" : "Added"} filter: ${describeFilter(item, fields, operators)}`);
96
101
  if (next.anchor.isConnected) next.anchor.focus();
97
102
  setDraft(null);
98
103
  }
@@ -160,7 +165,7 @@ function FilterBarRoot({ fields, operators, value, onValueChange, createItemId =
160
165
  else focusEntry();
161
166
  if (draft?.id === item.id) setDraft(null);
162
167
  onValueChange(value.filter((candidate) => candidate.id !== item.id));
163
- setAnnouncement("Filter removed.");
168
+ setAnnouncement(`Removed filter: ${describeFilter(item, fields, operators)}`);
164
169
  }
165
170
  function clear() {
166
171
  if (disabled || readOnly) return;
@@ -169,7 +174,7 @@ function FilterBarRoot({ fields, operators, value, onValueChange, createItemId =
169
174
  setDraft(null);
170
175
  onValueChange([]);
171
176
  focusEntry();
172
- setAnnouncement("Filters cleared.");
177
+ setAnnouncement("Cleared all filters");
173
178
  }
174
179
  function deleteFocusedFilter(event, itemId) {
175
180
  const item = value.find((candidate) => candidate.id === itemId);
@@ -220,6 +225,7 @@ function FilterBarRoot({ fields, operators, value, onValueChange, createItemId =
220
225
  releaseExit: (id) => setExits((current) => current.filter((exit) => exit.item.id !== id)),
221
226
  navigate
222
227
  };
228
+ const liveMessage = valueStatusMessage(draft?.stage === "value" ? fields.find((field) => field.id === draft.fieldId) : void 0) ?? announcement;
223
229
  const rootElement = useRender({
224
230
  ref: [rootRef, ref ?? null],
225
231
  render: /* @__PURE__ */ jsxs("fieldset", {
@@ -231,11 +237,7 @@ function FilterBarRoot({ fields, operators, value, onValueChange, createItemId =
231
237
  "data-readonly": readOnly || void 0,
232
238
  className: cn("relative flex min-w-0 flex-wrap items-center", className),
233
239
  ...props,
234
- children: [children, /* @__PURE__ */ jsx("span", {
235
- className: "sr-only",
236
- "aria-live": "polite",
237
- children: announcement
238
- })]
240
+ children: [children, /* @__PURE__ */ jsx(LiveStatus, { message: liveMessage })]
239
241
  })
240
242
  });
241
243
  return /* @__PURE__ */ jsx(FilterBarContext.Provider, {
@@ -270,19 +272,23 @@ const DEFAULT_FILTER_OPERATORS = [
270
272
  },
271
273
  {
272
274
  id: "gt",
273
- label: ">"
275
+ label: ">",
276
+ ariaLabel: "greater than"
274
277
  },
275
278
  {
276
279
  id: "gte",
277
- label: "≥"
280
+ label: "≥",
281
+ ariaLabel: "at least"
278
282
  },
279
283
  {
280
284
  id: "lt",
281
- label: "<"
285
+ label: "<",
286
+ ariaLabel: "less than"
282
287
  },
283
288
  {
284
289
  id: "lte",
285
- label: "≤"
290
+ label: "≤",
291
+ ariaLabel: "at most"
286
292
  },
287
293
  {
288
294
  id: "in",
@@ -20,7 +20,7 @@ function formatAudioInputDeviceLabel(device, fallback = "Microphone") {
20
20
  return `${label.replace(deviceIdRegex, "").trim() || fallback} (${id})`;
21
21
  }
22
22
  async function enumerateAudioInputDevices() {
23
- if (typeof navigator === "undefined" || !navigator.mediaDevices?.enumerateDevices) throw new Error("Microphone device selection is not available in this browser.");
23
+ if (typeof navigator === "undefined" || !navigator.mediaDevices?.enumerateDevices) throw unsupportedRecordingError();
24
24
  return (await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === "audioinput");
25
25
  }
26
26
  function useAudioInputDevices() {
@@ -43,7 +43,7 @@ function useAudioInputDevices() {
43
43
  }
44
44
  async function requestPermission() {
45
45
  if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
46
- setError(/* @__PURE__ */ new Error("Microphone device selection is not available in this browser."));
46
+ setError(unsupportedRecordingError());
47
47
  return;
48
48
  }
49
49
  setLoading(true);
@@ -96,10 +96,16 @@ function browserEnvironment() {
96
96
  function asError(error) {
97
97
  return error instanceof Error ? error : new Error(String(error));
98
98
  }
99
+ function namedError(name, message) {
100
+ return Object.assign(new Error(message), { name });
101
+ }
102
+ function unsupportedRecordingError() {
103
+ return namedError("NotSupportedError", "This browser can't record audio. Try a current version of Chrome, Safari or Firefox.");
104
+ }
99
105
  function microphoneError(error) {
100
106
  const nextError = asError(error);
101
- if (nextError.name === "NotAllowedError" || nextError.name === "SecurityError") return /* @__PURE__ */ new Error("Microphone permission was denied.");
102
- if (nextError.name === "NotFoundError" || nextError.name === "OverconstrainedError") return /* @__PURE__ */ new Error("Selected microphone is not available.");
107
+ if (nextError.name === "NotAllowedError" || nextError.name === "SecurityError") return namedError("NotAllowedError", "Microphone access is blocked. Allow it in your browser's site settings, then try again.");
108
+ if (nextError.name === "NotFoundError" || nextError.name === "OverconstrainedError") return namedError("NotFoundError", "Selected microphone isn't connected. Plug it in or choose another microphone.");
103
109
  return nextError;
104
110
  }
105
111
  function clampLevel(value) {
@@ -351,7 +357,7 @@ function createControllerForEnvironment(initialOptions, environment) {
351
357
  }
352
358
  function beginRecording(nextStream) {
353
359
  const MediaRecorderImpl = environment.MediaRecorder;
354
- if (!MediaRecorderImpl) throw new Error("Microphone recording is not available in this browser.");
360
+ if (!MediaRecorderImpl) throw unsupportedRecordingError();
355
361
  selectedMimeType = preferredAudioMimeType(MediaRecorderImpl) ?? "";
356
362
  const nextRecorder = new MediaRecorderImpl(nextStream, selectedMimeType ? { mimeType: selectedMimeType } : void 0);
357
363
  chunks = [];
@@ -388,7 +394,7 @@ function createControllerForEnvironment(initialOptions, environment) {
388
394
  if (!environment.mediaDevices?.getUserMedia || !environment.MediaRecorder) {
389
395
  setSnapshot({
390
396
  state: "error",
391
- error: /* @__PURE__ */ new Error("Microphone recording is not available in this browser.")
397
+ error: unsupportedRecordingError()
392
398
  });
393
399
  return false;
394
400
  }