@parallelworks/ui 0.76.0 → 0.78.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (270) hide show
  1. package/README.md +30 -5
  2. package/dist/access/AccessDrawer.d.ts +28 -7
  3. package/dist/access/AccessDrawer.js +216 -35
  4. package/dist/access/AccessList.d.ts +19 -1
  5. package/dist/access/AccessList.js +220 -9
  6. package/dist/access/GrantView.d.ts +3 -1
  7. package/dist/access/GrantView.js +17 -4
  8. package/dist/access/PrincipalPicker.js +5 -5
  9. package/dist/access/focus.js +1 -1
  10. package/dist/access/index.d.ts +2 -2
  11. package/dist/access/index.js +2 -2
  12. package/dist/access/strings.d.ts +18 -0
  13. package/dist/access/strings.js +20 -2
  14. package/dist/access/types.d.ts +26 -0
  15. package/dist/ai/adapter/types.d.ts +1 -0
  16. package/dist/ai/components/AllocationSelector.js +1 -1
  17. package/dist/ai/components/AttachmentManager.js +5 -5
  18. package/dist/ai/components/AttachmentUpload.js +2 -2
  19. package/dist/ai/components/BranchNavigator.js +1 -1
  20. package/dist/ai/components/ChatActivity.d.ts +2 -0
  21. package/dist/ai/components/ChatActivity.js +31 -0
  22. package/dist/ai/components/ChatEmptyState.js +3 -3
  23. package/dist/ai/components/ChatInput.js +3 -5
  24. package/dist/ai/components/ChatLayout.js +1 -1
  25. package/dist/ai/components/ChatMessage.js +1 -1
  26. package/dist/ai/components/ChatMessageList.js +2 -2
  27. package/dist/ai/components/ChatSidebar.js +3 -3
  28. package/dist/ai/components/ChatThread.js +4 -4
  29. package/dist/ai/components/ComposerAddMenu.js +9 -20
  30. package/dist/ai/components/ModelSelector.js +3 -3
  31. package/dist/ai/components/PastedText.js +5 -5
  32. package/dist/ai/components/SessionView.js +5 -2
  33. package/dist/ai/components/ShareDialog.js +3 -3
  34. package/dist/ai/components/SlashMenu.d.ts +31 -4
  35. package/dist/ai/components/SlashMenu.js +36 -63
  36. package/dist/ai/components/agent/ActivityLine.js +1 -1
  37. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -1
  38. package/dist/ai/components/agent/AgentMessageParts.js +44 -18
  39. package/dist/ai/components/conversationActions.js +3 -3
  40. package/dist/ai/core/ChatProvider.js +11 -2
  41. package/dist/ai/core/config.d.ts +10 -1
  42. package/dist/ai/core/config.js +1 -1
  43. package/dist/ai/index.d.ts +1 -1
  44. package/dist/ai/strings.d.ts +2 -0
  45. package/dist/ai/strings.js +2 -1
  46. package/dist/ai/types.d.ts +3 -0
  47. package/dist/ai/ui/ConversationSidebar.js +3 -3
  48. package/dist/ai/ui/sidebar.js +3 -3
  49. package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
  50. package/dist/components/ActionMenu.d.ts +38 -9
  51. package/dist/components/ActionMenu.js +48 -22
  52. package/dist/components/Avatar.d.ts +66 -6
  53. package/dist/components/Avatar.js +130 -18
  54. package/dist/components/Breadcrumbs.d.ts +4 -0
  55. package/dist/components/Breadcrumbs.js +26 -10
  56. package/dist/components/Button.d.ts +3 -2
  57. package/dist/components/Button.js +11 -1
  58. package/dist/components/Callout.d.ts +3 -2
  59. package/dist/components/Callout.js +3 -2
  60. package/dist/components/Card.d.ts +33 -9
  61. package/dist/components/Card.js +24 -8
  62. package/dist/components/Checkbox.d.ts +3 -1
  63. package/dist/components/Checkbox.js +4 -2
  64. package/dist/components/ColorInput.js +1 -1
  65. package/dist/components/Combobox.d.ts +23 -0
  66. package/dist/components/Combobox.js +252 -86
  67. package/dist/components/ConfirmModal.d.ts +26 -2
  68. package/dist/components/ConfirmModal.js +81 -15
  69. package/dist/components/ContextMenu.d.ts +29 -2
  70. package/dist/components/ContextMenu.js +60 -8
  71. package/dist/components/CopyButton.d.ts +21 -8
  72. package/dist/components/CopyButton.js +29 -9
  73. package/dist/components/CreateModal.d.ts +16 -4
  74. package/dist/components/CreateModal.js +27 -16
  75. package/dist/components/DatePicker.d.ts +31 -1
  76. package/dist/components/DatePicker.js +178 -47
  77. package/dist/components/DescriptionList.d.ts +13 -3
  78. package/dist/components/DescriptionList.js +43 -11
  79. package/dist/components/Disclosure.d.ts +10 -2
  80. package/dist/components/Disclosure.js +18 -7
  81. package/dist/components/Drawer.js +5 -5
  82. package/dist/components/EmptyState.d.ts +12 -4
  83. package/dist/components/EmptyState.js +18 -10
  84. package/dist/components/ErrorPage.d.ts +40 -0
  85. package/dist/components/ErrorPage.js +101 -0
  86. package/dist/components/ExternalLink.d.ts +4 -1
  87. package/dist/components/ExternalLink.js +4 -5
  88. package/dist/components/Field.d.ts +21 -6
  89. package/dist/components/Field.js +32 -16
  90. package/dist/components/FileButton.d.ts +19 -0
  91. package/dist/components/FileButton.js +32 -0
  92. package/dist/components/HoverCard.d.ts +26 -6
  93. package/dist/components/HoverCard.js +11 -5
  94. package/dist/components/IconButton.js +4 -2
  95. package/dist/components/InfoTip.js +3 -3
  96. package/dist/components/Input.d.ts +33 -3
  97. package/dist/components/Input.js +149 -14
  98. package/dist/components/Kbd.d.ts +40 -0
  99. package/dist/components/Kbd.js +168 -0
  100. package/dist/components/Listbox.d.ts +103 -0
  101. package/dist/components/Listbox.js +190 -0
  102. package/dist/components/Loader.d.ts +8 -2
  103. package/dist/components/Loader.js +16 -1
  104. package/dist/components/Markdown.js +1 -1
  105. package/dist/components/MarkdownImpl.js +1 -1
  106. package/dist/components/Menu.d.ts +20 -2
  107. package/dist/components/Menu.js +48 -19
  108. package/dist/components/MenuEntries.d.ts +38 -2
  109. package/dist/components/MenuEntries.js +266 -39
  110. package/dist/components/Meter.d.ts +50 -11
  111. package/dist/components/Meter.js +71 -21
  112. package/dist/components/Modal.d.ts +9 -3
  113. package/dist/components/Modal.js +68 -29
  114. package/dist/components/OneTimeCodeInput.js +1 -1
  115. package/dist/components/PageHeader.js +111 -7
  116. package/dist/components/PageHeaderAction.d.ts +8 -3
  117. package/dist/components/PageHeaderAction.js +122 -34
  118. package/dist/components/Pagination.d.ts +13 -1
  119. package/dist/components/Pagination.js +90 -19
  120. package/dist/components/Popover.d.ts +13 -2
  121. package/dist/components/Popover.js +20 -21
  122. package/dist/components/Provider.d.ts +140 -3
  123. package/dist/components/Provider.js +142 -12
  124. package/dist/components/RefreshButton.d.ts +13 -6
  125. package/dist/components/RefreshButton.js +17 -9
  126. package/dist/components/RunStatusIcon.d.ts +16 -7
  127. package/dist/components/RunStatusIcon.js +21 -8
  128. package/dist/components/Section.d.ts +11 -4
  129. package/dist/components/Section.js +33 -22
  130. package/dist/components/SegmentedControl.d.ts +16 -3
  131. package/dist/components/SegmentedControl.js +7 -3
  132. package/dist/components/Select.d.ts +19 -3
  133. package/dist/components/Select.js +114 -68
  134. package/dist/components/SelectionCopy.d.ts +10 -0
  135. package/dist/components/SelectionCopy.js +105 -0
  136. package/dist/components/SettingModeOption.js +1 -1
  137. package/dist/components/SettingsCard.js +1 -1
  138. package/dist/components/Skeleton.js +1 -1
  139. package/dist/components/StatusBadge.d.ts +55 -10
  140. package/dist/components/StatusBadge.js +91 -15
  141. package/dist/components/StickyBar.js +1 -1
  142. package/dist/components/Table.d.ts +22 -4
  143. package/dist/components/Table.js +46 -24
  144. package/dist/components/Tabs.d.ts +9 -4
  145. package/dist/components/Tabs.js +9 -4
  146. package/dist/components/Tag.d.ts +49 -0
  147. package/dist/components/Tag.js +199 -0
  148. package/dist/components/ThemeEditor.js +1 -1
  149. package/dist/components/Toast.js +1 -1
  150. package/dist/components/ToggleGroup.d.ts +4 -1
  151. package/dist/components/ToggleGroup.js +30 -13
  152. package/dist/components/Tooltip.d.ts +9 -3
  153. package/dist/components/Tooltip.js +55 -39
  154. package/dist/components/buttonClasses.d.ts +5 -2
  155. package/dist/components/buttonClasses.js +11 -10
  156. package/dist/components/chart.d.ts +69 -0
  157. package/dist/components/chart.js +145 -0
  158. package/dist/components/comboboxItems.d.ts +7 -1
  159. package/dist/components/comboboxItems.js +6 -2
  160. package/dist/components/dismiss.d.ts +5 -1
  161. package/dist/components/dismiss.js +6 -2
  162. package/dist/components/floating.d.ts +4 -4
  163. package/dist/components/floating.js +5 -5
  164. package/dist/components/headerActionsFit.d.ts +37 -0
  165. package/dist/components/headerActionsFit.js +81 -0
  166. package/dist/components/pageHeaderStore.d.ts +3 -0
  167. package/dist/components/pageHeaderStore.js +3 -0
  168. package/dist/components/pathBreaks.js +2 -2
  169. package/dist/components/popupContainer.d.ts +17 -0
  170. package/dist/components/popupContainer.js +69 -0
  171. package/dist/components/relativeTime.d.ts +21 -10
  172. package/dist/components/relativeTime.js +50 -22
  173. package/dist/components/segmented.d.ts +18 -4
  174. package/dist/components/segmented.js +48 -6
  175. package/dist/components/typedDate.d.ts +5 -0
  176. package/dist/components/typedDate.js +50 -0
  177. package/dist/components/useClosingValue.d.ts +4 -0
  178. package/dist/components/useClosingValue.js +12 -0
  179. package/dist/components/useCopy.d.ts +6 -0
  180. package/dist/components/useCopy.js +3 -3
  181. package/dist/components/useNarrow.d.ts +2 -0
  182. package/dist/components/useNarrow.js +16 -0
  183. package/dist/editor/Editor.js +1 -1
  184. package/dist/editor/FormCodePanel.js +1 -1
  185. package/dist/editor/Monaco.js +1 -1
  186. package/dist/editor/workers.js +1 -1
  187. package/dist/file-explorer/FileExplorer.js +16 -15
  188. package/dist/file-explorer/FileExplorerContext.js +2 -2
  189. package/dist/file-explorer/FilePreview/Viewer.js +3 -3
  190. package/dist/file-explorer/FilePreview/index.js +2 -2
  191. package/dist/file-explorer/FileRowsWindow.js +3 -3
  192. package/dist/file-explorer/FileTable.js +1 -1
  193. package/dist/file-explorer/LoadMoreRow.js +1 -1
  194. package/dist/file-explorer/TreeView.js +5 -5
  195. package/dist/file-explorer/helpers.js +2 -2
  196. package/dist/file-explorer/toasts.js +2 -2
  197. package/dist/form/Fields/CheckboxGroupField.js +1 -1
  198. package/dist/form/Fields/DropdownField.js +1 -1
  199. package/dist/form/Fields/DurationField.js +2 -2
  200. package/dist/form/Fields/MultiDropdownField.js +1 -1
  201. package/dist/form/Fields/NumberField.js +1 -1
  202. package/dist/form/Fields/ObjectField.js +1 -1
  203. package/dist/form/Fields/RangeField.js +1 -1
  204. package/dist/form/Fields/StringField.js +1 -1
  205. package/dist/form/Form.js +3 -3
  206. package/dist/form/FormLayout.js +1 -1
  207. package/dist/form/Wizard/WizardContainer.js +1 -1
  208. package/dist/form/Wizard/WizardNavigation.js +1 -1
  209. package/dist/form/Wizard/WizardStepContent.js +1 -1
  210. package/dist/form/fieldRegistry.js +1 -1
  211. package/dist/form/formEditing.js +1 -1
  212. package/dist/format.d.ts +17 -7
  213. package/dist/format.js +39 -16
  214. package/dist/graph/AnnotationBanner.js +2 -2
  215. package/dist/graph/Connectors.js +5 -5
  216. package/dist/graph/DependencyGraph.js +10 -9
  217. package/dist/graph/GraphEditor.js +4 -4
  218. package/dist/graph/GraphEditorDialogs.js +13 -12
  219. package/dist/graph/GraphNode.js +1 -1
  220. package/dist/graph/InputDialog.js +5 -5
  221. package/dist/graph/InputsEditor.js +7 -7
  222. package/dist/graph/MatrixGroup.js +2 -2
  223. package/dist/graph/ProblemsButton.js +3 -3
  224. package/dist/graph/Reveal.js +1 -1
  225. package/dist/graph/RunWarningsList.js +1 -1
  226. package/dist/graph/ShortcutsButton.js +25 -8
  227. package/dist/graph/StepSummaries.js +1 -1
  228. package/dist/graph/TreeView.js +2 -2
  229. package/dist/graph/WorkflowSettingsDialog.js +3 -3
  230. package/dist/graph/editorFields.js +3 -3
  231. package/dist/graph/editorPrimitives.js +1 -1
  232. package/dist/graph/editorRows.js +5 -5
  233. package/dist/graph/fieldProblems.js +2 -2
  234. package/dist/graph/inputRefs.js +7 -6
  235. package/dist/graph/stepWith.js +2 -2
  236. package/dist/index.d.ts +30 -21
  237. package/dist/index.js +20 -12
  238. package/dist/list/ActionToolbar.d.ts +4 -0
  239. package/dist/list/ActionToolbar.js +35 -12
  240. package/dist/list/ListFilterBar.d.ts +42 -0
  241. package/dist/list/ListFilterBar.js +789 -0
  242. package/dist/list/ListPage.d.ts +42 -4
  243. package/dist/list/ListPage.js +56 -22
  244. package/dist/list/ListTable.d.ts +27 -3
  245. package/dist/list/ListTable.js +48 -23
  246. package/dist/list/ListToolbar.d.ts +31 -0
  247. package/dist/list/ListToolbar.js +43 -0
  248. package/dist/list/ListViewControls.d.ts +8 -4
  249. package/dist/list/ListViewControls.js +22 -14
  250. package/dist/list/UserHoverCard.d.ts +8 -2
  251. package/dist/list/UserHoverCard.js +26 -13
  252. package/dist/list/filterModel.d.ts +122 -0
  253. package/dist/list/filterModel.js +308 -0
  254. package/dist/list/index.d.ts +6 -2
  255. package/dist/list/index.js +6 -2
  256. package/dist/list/listSearch.d.ts +1 -1
  257. package/dist/list/listSearch.js +8 -4
  258. package/dist/list/listView.d.ts +24 -2
  259. package/dist/list/listView.js +47 -45
  260. package/dist/list/rowMenu.d.ts +10 -5
  261. package/dist/list/rowMenu.js +17 -7
  262. package/dist/list/useListFilters.d.ts +68 -0
  263. package/dist/list/useListFilters.js +252 -0
  264. package/dist/logviewer/LogViewer.js +4 -4
  265. package/dist/styles/theme.css +94 -0
  266. package/dist/styles.css +1 -1
  267. package/package.json +11 -11
  268. package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
  269. package/dist/components/fullscreen.d.ts +0 -4
  270. package/dist/components/fullscreen.js +0 -18
@@ -1,16 +1,17 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { AddIcon, CheckIcon, ChevronDownIcon, RemoveIcon } from "../icons.js";
3
3
  import { panelClassName, positionerClassName } from "./floating.js";
4
- import { useFullscreenContainer } from "./fullscreen.js";
4
+ import { usePopupContainer } from "./popupContainer.js";
5
5
  import { Loader } from "./Loader.js";
6
6
  import { Tooltip } from "./Tooltip.js";
7
- import { useStrings } from "./Provider.js";
7
+ import { useLocale, useStrings } from "./Provider.js";
8
8
  import { useQueryMatch } from "./search.js";
9
9
  import { fieldBox } from "./fieldBox.js";
10
- import { isComboboxGroup, optionNaming, pickerItemClassName } from "./comboboxItems.js";
10
+ import { isComboboxGroup, optionNaming, pickerEndClassName, pickerItemClassName } from "./comboboxItems.js";
11
11
  import { OptionDescription } from "./OptionDescription.js";
12
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
- import { useCallback, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
12
+ import { tagClasses } from "./Tag.js";
13
+ import { useCallback, useEffect, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
14
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
14
15
  import { dequal } from "dequal";
15
16
  import { Combobox } from "@base-ui/react/combobox";
16
17
  import { Autocomplete } from "@base-ui/react/autocomplete";
@@ -82,14 +83,20 @@ var triggerClassName = "flex shrink-0 items-center self-stretch px-2 text-(--the
82
83
  var popupInputClassName = fieldBox({ size: "sm" });
83
84
  /** A pick-from-the-list field: one value, or several with `multiple`. Typing filters the list. */
84
85
  function Combobox$1(props) {
85
- const { items, isItemEqualToValue = dequal, itemToStringLabel = String, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, trigger, triggerTooltip, size = "md", details, detailsSummary, onItemHighlighted, onQueryChange, loading = false, collisionBoundary } = props;
86
+ const { items, isItemEqualToValue = dequal, itemToStringLabel = String, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, trigger, triggerTooltip, size = "md", details, detailsSummary, onItemHighlighted, onQueryChange, loading = false, collisionBoundary, open, defaultOpen, onOpenChange, align = "start", onCreate } = props;
86
87
  const entries = useEntries(items);
87
88
  const filter = useEntryFilter();
88
89
  const t = useStrings().dropdown;
90
+ const locale = useLocale();
89
91
  const list = useRef(null);
90
92
  const uid = useId();
91
93
  const [inputValue, setInputValue] = useState(null);
92
94
  const [highlighted, setHighlighted] = useState(void 0);
95
+ const [startsOpen] = useState(open === void 0 && defaultOpen === true);
96
+ const [ownOpen, setOwnOpen] = useState(false);
97
+ useEffect(() => {
98
+ if (startsOpen) setOwnOpen(true);
99
+ }, [startsOpen]);
93
100
  const entryOf = (value) => entries.flat.find((entry) => isItemEqualToValue(entry.value, value)) ?? {
94
101
  value,
95
102
  label: itemToStringLabel(value)
@@ -97,19 +104,74 @@ function Combobox$1(props) {
97
104
  const toEntries = (values) => values?.map(entryOf);
98
105
  const toSingle = (value) => value === void 0 ? void 0 : value === null ? null : entryOf(value);
99
106
  const selected = props.multiple ? void 0 : toSingle(props.value);
100
- const [uncontrolledCount, setUncontrolledCount] = useState(props.multiple ? props.defaultValue?.length ?? 0 : 0);
101
- const chosen = props.multiple ? props.value ?? null : null;
102
- const full = props.multiple && props.maxItems !== void 0 && (chosen?.length ?? uncontrolledCount) >= props.maxItems;
107
+ const [ownValues, setOwnValues] = useState(props.multiple ? props.defaultValue ?? [] : []);
108
+ const chosen = props.multiple ? props.value ?? ownValues : null;
109
+ const full = props.multiple && props.maxItems !== void 0 && (chosen?.length ?? 0) >= props.maxItems;
103
110
  const isChosen = (entry) => chosen?.some((value) => isItemEqualToValue(entry.value, value)) ?? false;
111
+ const setValues = (next) => {
112
+ if (props.multiple) {
113
+ setOwnValues(next);
114
+ props.onValueChange?.(next);
115
+ }
116
+ };
117
+ const creatable = props.multiple && props.creatable;
104
118
  const typed = inputValue?.trim() ?? "";
105
- const lowered = typed.toLocaleLowerCase();
106
- const sameText = (value, label) => label?.toLocaleLowerCase() === lowered || typeof value === "string" && value.toLocaleLowerCase() === lowered;
107
- const created = props.multiple && props.creatable && typed !== "" && !full && !entries.flat.some((entry) => sameText(entry.value, entry.label)) && !chosen?.some((value) => sameText(value)) ? {
119
+ const sameText = (text) => (value, label) => {
120
+ const lowered = text.toLocaleLowerCase();
121
+ return label?.toLocaleLowerCase() === lowered || typeof value === "string" && value.toLocaleLowerCase() === lowered;
122
+ };
123
+ const isNew = (text, among = chosen) => !entries.flat.some((entry) => sameText(text)(entry.value, entry.label)) && !among?.some((value) => sameText(text)(value));
124
+ /** The value typed text adds, or undefined when it adds none. */
125
+ const toCreated = (text, among) => {
126
+ if (!props.multiple || !props.creatable) return;
127
+ const value = props.createValue ? props.createValue(text) : text.trim();
128
+ return value && isNew(value, among) && isNew(text.trim(), among) ? value : void 0;
129
+ };
130
+ const createdValue = creatable && !full && typed !== "" ? toCreated(typed) : void 0;
131
+ const created = onCreate ? typed !== "" && !full && isNew(typed) ? {
108
132
  value: typed,
109
- label: t.add(typed),
133
+ label: t.create(typed),
134
+ icon: /* @__PURE__ */ jsx(AddIcon, {}),
135
+ created: true,
136
+ action: true
137
+ } : void 0 : createdValue !== void 0 ? {
138
+ value: createdValue,
139
+ label: t.add(createdValue),
110
140
  icon: /* @__PURE__ */ jsx(AddIcon, {}),
111
141
  created: true
112
142
  } : void 0;
143
+ /** Adds what is typed, or each part of pasted text, as values; true when any was added. */
144
+ const addTyped = (parts) => {
145
+ if (!chosen || !props.multiple) return false;
146
+ const { maxItems } = props;
147
+ const next = [...chosen];
148
+ for (const part of parts) {
149
+ if (maxItems !== void 0 && next.length >= maxItems) break;
150
+ const value = part.trim() === "" ? void 0 : toCreated(part, next);
151
+ if (value !== void 0) next.push(value);
152
+ }
153
+ if (next.length === chosen.length) return false;
154
+ setValues(next);
155
+ setInputValue("");
156
+ return true;
157
+ };
158
+ const separators = props.multiple && props.creatable ? props.separators ?? [] : [];
159
+ const splitOnSeparators = (text) => separators.reduce((parts, separator) => parts.flatMap((p) => p.split(separator)), [text]);
160
+ const onInputKeyDown = (event) => {
161
+ if (separators.includes(event.key) && !event.nativeEvent.isComposing) {
162
+ event.preventDefault();
163
+ addTyped([typed]);
164
+ }
165
+ };
166
+ const onInputPaste = (event) => {
167
+ const text = event.clipboardData.getData("text");
168
+ const parts = splitOnSeparators(text);
169
+ if (parts.length > 1) {
170
+ event.preventDefault();
171
+ addTyped(parts);
172
+ }
173
+ };
174
+ const freeEntry = creatable && entries.flat.length === 0;
113
175
  const [first] = entries.items;
114
176
  const listItems = !created ? entries.items : first !== void 0 && "items" in first ? [{
115
177
  label: "",
@@ -121,6 +183,9 @@ function Combobox$1(props) {
121
183
  id: trigger ? void 0 : id,
122
184
  placeholder: full && props.maxItems !== void 0 ? t.limitReached(props.maxItems) : placeholder,
123
185
  maxLength: props.multiple ? props.maxLength : void 0,
186
+ onKeyDown: separators.length > 0 ? onInputKeyDown : void 0,
187
+ onPaste: separators.length > 0 ? onInputPaste : void 0,
188
+ onBlur: props.multiple && props.creatable && props.addOnBlur ? () => addTyped([typed]) : void 0,
124
189
  "aria-label": ariaLabel || void 0,
125
190
  "aria-labelledby": ariaLabelledBy,
126
191
  "aria-describedby": ariaDescribedBy,
@@ -144,15 +209,25 @@ function Combobox$1(props) {
144
209
  className: "mt-0.5 text-xs leading-snug text-(--theme-muted-text-color) @min-[30rem]/picker:hidden",
145
210
  children: detailsSummary(entry.value)
146
211
  })
147
- }), /* @__PURE__ */ jsx(Combobox.ItemIndicator, {
148
- className: "ml-auto flex shrink-0 items-center",
149
- children: /* @__PURE__ */ jsx(CheckIcon, {
150
- "aria-hidden": "true",
151
- className: "size-3.5"
152
- })
212
+ }), /* @__PURE__ */ jsxs("span", {
213
+ className: pickerEndClassName,
214
+ children: [entry.hint && /* @__PURE__ */ jsx("span", {
215
+ id: `${uid}${rowProps.props.index}hint`,
216
+ className: "text-xs tabular-nums text-(--theme-muted-text-color)",
217
+ children: entry.hint
218
+ }), /* @__PURE__ */ jsx("span", {
219
+ className: cx("flex items-center", entry.hint && "w-3.5"),
220
+ children: /* @__PURE__ */ jsx(Combobox.ItemIndicator, {
221
+ className: "flex items-center",
222
+ children: /* @__PURE__ */ jsx(CheckIcon, {
223
+ "aria-hidden": "true",
224
+ className: "size-3.5"
225
+ })
226
+ })
227
+ })]
153
228
  })]
154
229
  }, rowProps.key);
155
- const parts = trigger ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Tooltip, {
230
+ const parts = trigger ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Tooltip, {
156
231
  content: triggerTooltip,
157
232
  children: /* @__PURE__ */ jsx(Combobox.Trigger, {
158
233
  id,
@@ -172,53 +247,64 @@ function Combobox$1(props) {
172
247
  className: "border-b border-(--theme-border) p-1.5",
173
248
  children: input
174
249
  }),
175
- className: details ? "w-[min(36rem,var(--available-width))]" : "w-max min-w-(--anchor-width) max-w-[min(28rem,var(--available-width))]",
250
+ className: details ? "w-[min(36rem,var(--available-width))]" : "w-max min-w-[min(max(14rem,var(--anchor-width)),var(--available-width))] max-w-[min(28rem,var(--available-width))]",
251
+ sizer: details ? void 0 : /* @__PURE__ */ jsx(ListSizer, { entries: entries.flat }),
252
+ align,
176
253
  loading: loading ? t.searching : void 0,
177
254
  details: details?.(highlighted?.created ? void 0 : highlighted?.value),
178
255
  collisionBoundary,
179
256
  renderItem: itemRow
180
- })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Combobox.InputGroup, {
181
- "data-testid": "combobox",
182
- className: cx(fieldClassName(size), className),
183
- children: [props.multiple ? /* @__PURE__ */ jsxs(Combobox.Chips, {
184
- className: cx("flex min-w-0 flex-1 flex-wrap items-center gap-1 pl-1", size === "sm" ? "py-0.5" : "py-1"),
185
- children: [/* @__PURE__ */ jsx(Combobox.Value, { children: (values) => values.map((entry, index) => /* @__PURE__ */ jsxs(Combobox.Chip, {
186
- "aria-label": entry.label,
187
- className: "flex items-center gap-1 rounded bg-(--theme-muted-panel-bg) py-0.5 pr-0.5 pl-1.5 text-xs outline-none data-highlighted:bg-(--theme-hover)",
188
- children: [entry.label, /* @__PURE__ */ jsx(Combobox.ChipRemove, {
189
- "aria-label": t.removeValue(entry.label),
190
- className: "flex size-4 items-center justify-center rounded hover:bg-(--theme-hover)",
191
- children: /* @__PURE__ */ jsx(RemoveIcon, {
192
- "aria-hidden": "true",
193
- className: "size-3"
194
- })
195
- })]
196
- }, `${index}:${entry.label}`)) }), input]
197
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [selectedIcon && /* @__PURE__ */ jsx("span", {
198
- className: cx("flex shrink-0 items-center", size === "sm" ? "pl-2" : "pl-3"),
199
- children: selectedIcon
200
- }), input] }), /* @__PURE__ */ jsx(Combobox.Trigger, {
201
- "aria-label": t.showOptions,
202
- "data-testid": "combobox-button",
203
- className: triggerClassName,
204
- children: /* @__PURE__ */ jsx(ChevronDownIcon, {
205
- "aria-hidden": "true",
206
- className: "size-3"
207
- })
208
- })]
209
- }), /* @__PURE__ */ jsx(ListPopup, {
210
- listRef: list,
211
- empty: t.noOptionsFound,
212
- statusGroups: entries.statusGroups,
213
- query: inputValue ?? "",
214
- loading: loading ? t.searching : void 0,
215
- details: details?.(highlighted?.created ? void 0 : highlighted?.value),
216
- collisionBoundary,
217
- renderItem: itemRow
218
- })] });
257
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
258
+ /* @__PURE__ */ jsxs(Combobox.InputGroup, {
259
+ "data-testid": "combobox",
260
+ className: cx(fieldClassName(size), freeEntry && "pr-2", className),
261
+ children: [props.multiple ? /* @__PURE__ */ jsxs(Combobox.Chips, {
262
+ className: cx("flex min-w-0 flex-1 flex-wrap items-center gap-1 pl-1", size === "sm" ? "py-0.5" : "py-1"),
263
+ children: [/* @__PURE__ */ jsx(Combobox.Value, { children: (values) => values.map((entry, index) => /* @__PURE__ */ jsxs(Combobox.Chip, {
264
+ "aria-label": entry.label,
265
+ className: cx(tagClasses(), "pr-0.5 outline-none data-highlighted:bg-(--theme-hover)"),
266
+ children: [entry.label, /* @__PURE__ */ jsx(Combobox.ChipRemove, {
267
+ "aria-label": t.removeValue(entry.label),
268
+ className: "flex size-4 cursor-pointer items-center justify-center rounded-full text-(--theme-muted-text-color) hover:bg-(--theme-hover) hover:text-(--theme-app)",
269
+ children: /* @__PURE__ */ jsx(RemoveIcon, {
270
+ "aria-hidden": "true",
271
+ className: "size-2.5"
272
+ })
273
+ })]
274
+ }, `${index}:${entry.label}`)) }), input]
275
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [selectedIcon && /* @__PURE__ */ jsx("span", {
276
+ className: cx("flex shrink-0 items-center", size === "sm" ? "pl-2" : "pl-3"),
277
+ children: selectedIcon
278
+ }), input] }), !freeEntry && /* @__PURE__ */ jsx(Combobox.Trigger, {
279
+ "aria-label": t.showOptions,
280
+ "data-testid": "combobox-button",
281
+ className: triggerClassName,
282
+ children: /* @__PURE__ */ jsx(ChevronDownIcon, {
283
+ "aria-hidden": "true",
284
+ className: "size-3"
285
+ })
286
+ })]
287
+ }),
288
+ props.multiple && props.creatable && props.suggested && /* @__PURE__ */ jsx(Suggestions, {
289
+ suggested: props.suggested.filter((s) => toCreated(s) !== void 0),
290
+ disabled: disabled || full,
291
+ onAdd: (s) => addTyped([s])
292
+ }),
293
+ /* @__PURE__ */ jsx(ListPopup, {
294
+ listRef: list,
295
+ empty: freeEntry ? void 0 : t.noOptionsFound,
296
+ align,
297
+ statusGroups: entries.statusGroups,
298
+ query: inputValue ?? "",
299
+ loading: loading ? t.searching : void 0,
300
+ details: details?.(highlighted?.created ? void 0 : highlighted?.value),
301
+ collisionBoundary,
302
+ renderItem: itemRow
303
+ })
304
+ ] });
219
305
  const root = {
220
306
  items: listItems,
221
- isItemEqualToValue: (a, b) => isItemEqualToValue(a.value, b.value),
307
+ isItemEqualToValue: (a, b) => a.action || b.action ? a === b : isItemEqualToValue(a.value, b.value),
222
308
  itemToStringLabel: entryLabel,
223
309
  filter: onQueryChange ? null : filter,
224
310
  onInputValueChange: (next) => {
@@ -232,29 +318,49 @@ function Combobox$1(props) {
232
318
  },
233
319
  disabled,
234
320
  required,
321
+ locale,
235
322
  virtualized: true,
236
- autoHighlight: Boolean(props.multiple && props.creatable),
323
+ autoHighlight: trigger ? "always" : Boolean(creatable || onCreate),
324
+ ...open !== void 0 ? { open } : startsOpen ? { open: ownOpen } : {},
325
+ onOpenChange: (next) => {
326
+ setOwnOpen(next);
327
+ if (!next && (trigger || !props.multiple)) setInputValue(null);
328
+ onOpenChange?.(next);
329
+ },
237
330
  children: parts
238
331
  };
239
332
  return props.multiple ? /* @__PURE__ */ jsx(Combobox.Root, {
240
333
  ...root,
241
334
  multiple: true,
242
- value: toEntries(props.value),
243
- defaultValue: toEntries(props.defaultValue),
335
+ value: toEntries(chosen ?? []),
336
+ inputValue: inputValue ?? "",
244
337
  onValueChange: (next, details) => {
245
338
  if (details.reason === "escape-key") {
246
339
  details.cancel();
247
340
  details.allowPropagation();
248
341
  return;
249
342
  }
250
- setUncontrolledCount(next.length);
251
- props.onValueChange?.(next.map((entry) => entry.value));
343
+ const action = next.find((entry) => entry.action);
344
+ if (action) {
345
+ details.cancel();
346
+ setInputValue("");
347
+ onCreate?.(String(action.value));
348
+ return;
349
+ }
350
+ setValues(next.map((entry) => entry.value));
252
351
  }
253
352
  }) : /* @__PURE__ */ jsx(Combobox.Root, {
254
353
  ...root,
255
354
  value: selected,
256
355
  defaultValue: toSingle(props.defaultValue),
257
- onValueChange: (next) => props.onValueChange?.(next?.value ?? null)
356
+ onValueChange: (next, details) => {
357
+ if (next?.action) {
358
+ details.cancel();
359
+ onCreate?.(String(next.value));
360
+ return;
361
+ }
362
+ props.onValueChange?.(next?.value ?? null);
363
+ }
258
364
  });
259
365
  }
260
366
  /** A free-text field that suggests values as you type. */
@@ -314,7 +420,7 @@ function Autocomplete$1({ items, value, defaultValue, onValueChange, className,
314
420
  });
315
421
  }
316
422
  function ItemContent({ entry, id, children }) {
317
- return /* @__PURE__ */ jsxs(Fragment, { children: [entry.icon && /* @__PURE__ */ jsx("span", {
423
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [entry.icon && /* @__PURE__ */ jsx("span", {
318
424
  className: "flex shrink-0 items-center",
319
425
  children: entry.icon
320
426
  }), /* @__PURE__ */ jsxs("span", {
@@ -340,8 +446,64 @@ function ItemContent({ entry, id, children }) {
340
446
  ]
341
447
  })] });
342
448
  }
449
+ var SIZER_ROWS = 3;
450
+ /** Unseen copies of the longest rows, which set the width of a popup whose own rows are
451
+ * positioned out of its flow by the virtual list. */
452
+ function ListSizer({ entries }) {
453
+ const longest = useMemo(() => [...entries].sort((a, b) => b.label.length + (b.hint?.length ?? 0) - (a.label.length + (a.hint?.length ?? 0))).slice(0, SIZER_ROWS), [entries]);
454
+ return /* @__PURE__ */ jsx("div", {
455
+ "aria-hidden": "true",
456
+ className: "invisible h-0 overflow-hidden px-1",
457
+ children: longest.map((entry, index) => /* @__PURE__ */ jsxs("div", {
458
+ className: cx(pickerItemClassName, "w-max"),
459
+ children: [
460
+ entry.icon && /* @__PURE__ */ jsx("span", {
461
+ className: "flex shrink-0 items-center",
462
+ children: entry.icon
463
+ }),
464
+ /* @__PURE__ */ jsxs("span", {
465
+ className: "flex items-center gap-2 whitespace-nowrap",
466
+ children: [entry.label, entry.badge]
467
+ }),
468
+ /* @__PURE__ */ jsxs("span", {
469
+ className: pickerEndClassName,
470
+ children: [entry.hint && /* @__PURE__ */ jsx("span", {
471
+ className: "text-xs tabular-nums text-(--theme-muted-text-color)",
472
+ children: entry.hint
473
+ }), /* @__PURE__ */ jsx("span", { className: "size-3.5" })]
474
+ })
475
+ ]
476
+ }, index))
477
+ });
478
+ }
479
+ /** Values offered under a tag field, each added with a press. */
480
+ function Suggestions({ suggested, disabled, onAdd }) {
481
+ const t = useStrings().dropdown;
482
+ const id = useId();
483
+ if (suggested.length === 0) return null;
484
+ return /* @__PURE__ */ jsxs("div", {
485
+ role: "group",
486
+ "aria-labelledby": id,
487
+ className: "mt-1.5 flex flex-wrap items-center gap-1",
488
+ children: [/* @__PURE__ */ jsx("span", {
489
+ id,
490
+ className: "mr-0.5 text-xs text-(--theme-muted-text-color)",
491
+ children: t.suggested
492
+ }), suggested.map((value) => /* @__PURE__ */ jsxs("button", {
493
+ type: "button",
494
+ disabled,
495
+ "aria-label": t.add(value),
496
+ onClick: () => onAdd(value),
497
+ className: cx(tagClasses(), "cursor-pointer gap-0.5 border-dashed border-(--theme-border-strong) pr-2 pl-1.5 text-(--theme-muted-text-color) transition-colors hover:border-solid hover:bg-(--theme-hover) hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-(--theme-focus) disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:border-dashed disabled:hover:bg-transparent"),
498
+ children: [/* @__PURE__ */ jsx(AddIcon, {
499
+ "aria-hidden": "true",
500
+ className: "size-2.5"
501
+ }), value]
502
+ }, value))]
503
+ });
504
+ }
343
505
  function GroupHeading({ group }) {
344
- return /* @__PURE__ */ jsxs(Fragment, { children: [
506
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
345
507
  group.icon && /* @__PURE__ */ jsx("span", {
346
508
  className: "flex shrink-0 items-center",
347
509
  children: group.icon
@@ -361,10 +523,10 @@ function GroupHeading({ group }) {
361
523
  ] });
362
524
  }
363
525
  /** Portal, positioner and popup shared by Combobox and Autocomplete; both use the same parts. */
364
- function ListPopup({ listRef, empty, statusGroups = [], query = "", header, className = "w-(--anchor-width) max-w-(--available-width)", loading, details, collisionBoundary, renderItem }) {
365
- const fullscreen = useFullscreenContainer();
526
+ function ListPopup({ listRef, empty, statusGroups = [], query = "", header, className = "w-(--anchor-width) max-w-(--available-width)", loading, details, collisionBoundary, renderItem, sizer, align = "start" }) {
527
+ const portal = usePopupContainer();
366
528
  const withDetails = details !== void 0;
367
- const list = /* @__PURE__ */ jsxs(Fragment, { children: [
529
+ const list = /* @__PURE__ */ jsxs(Fragment$1, { children: [
368
530
  loading && /* @__PURE__ */ jsxs("div", {
369
531
  role: "status",
370
532
  className: "flex items-center gap-2 px-3 py-2 text-(--theme-muted-text-color)",
@@ -390,26 +552,30 @@ function ListPopup({ listRef, empty, statusGroups = [], query = "", header, clas
390
552
  })
391
553
  ] });
392
554
  return /* @__PURE__ */ jsx(Combobox.Portal, {
393
- container: fullscreen,
555
+ ...portal,
394
556
  children: /* @__PURE__ */ jsx(Combobox.Positioner, {
395
- align: "start",
557
+ align,
396
558
  sideOffset: 4,
397
559
  className: positionerClassName,
398
560
  ...collisionBoundary && { collisionBoundary },
399
561
  children: /* @__PURE__ */ jsxs(Combobox.Popup, {
400
562
  className: cx("overflow-hidden", withDetails && "@container/picker flex h-[min(40rem,var(--available-height))] flex-col", className, panelClassName, !empty && !loading && statusGroups.length === 0 && !header && "data-empty:hidden"),
401
- children: [header, withDetails ? /* @__PURE__ */ jsxs("div", {
402
- className: "flex min-h-0 flex-1",
403
- children: [/* @__PURE__ */ jsx("div", {
404
- className: "flex min-w-0 flex-1 flex-col",
405
- children: list
406
- }), /* @__PURE__ */ jsxs("div", {
407
- "aria-hidden": "true",
408
- "data-testid": "combobox-details",
409
- className: "hidden w-1/2 shrink-0 overflow-y-auto overscroll-contain border-l border-(--theme-border) bg-(--theme-panel-bg) @min-[30rem]/picker:block",
410
- children: [details, /* @__PURE__ */ jsx("div", { className: "pointer-events-none sticky bottom-0 -mt-6 h-6 bg-linear-to-t from-(--theme-panel-bg) to-transparent" })]
411
- })]
412
- }) : list]
563
+ children: [
564
+ header,
565
+ withDetails ? /* @__PURE__ */ jsxs("div", {
566
+ className: "flex min-h-0 flex-1",
567
+ children: [/* @__PURE__ */ jsx("div", {
568
+ className: "flex min-w-0 flex-1 flex-col",
569
+ children: list
570
+ }), /* @__PURE__ */ jsxs("div", {
571
+ "aria-hidden": "true",
572
+ "data-testid": "combobox-details",
573
+ className: "hidden w-1/2 shrink-0 overflow-y-auto overscroll-contain border-l border-(--theme-border) bg-(--theme-panel-bg) @min-[30rem]/picker:block",
574
+ children: [details, /* @__PURE__ */ jsx("div", { className: "pointer-events-none sticky bottom-0 -mt-6 h-6 bg-linear-to-t from-(--theme-panel-bg) to-transparent" })]
575
+ })]
576
+ }) : list,
577
+ sizer
578
+ ]
413
579
  })
414
580
  })
415
581
  });
@@ -1,4 +1,5 @@
1
1
  import type { ReactNode } from 'react';
2
+ import { type ModalSize } from './Modal.js';
2
3
  export interface ConfirmModalProps {
3
4
  open: boolean;
4
5
  onClose: () => void;
@@ -12,7 +13,7 @@ export interface ConfirmModalProps {
12
13
  confirmLabel: string;
13
14
  /** An async handler keeps both buttons disabled until it settles. One that
14
15
  * rejects leaves the dialog open and rethrows, so report the error in the
15
- * handler, such as with a toast. */
16
+ * handler, or pass it back as `error` to show it in the dialog. */
16
17
  onConfirm: () => void | Promise<unknown>;
17
18
  confirmDisabled?: boolean | undefined;
18
19
  /** An action that can't be undone: the confirm button reads as dangerous, and
@@ -22,7 +23,30 @@ export interface ConfirmModalProps {
22
23
  cancelLabel?: string | undefined;
23
24
  /** When false, the dialog stays open after a successful confirm. */
24
25
  closeOnConfirm?: boolean | undefined;
26
+ /** Which button has focus as it opens. Cancel for a destructive dialog, so a stray Enter
27
+ * destroys nothing; the confirm button otherwise, so the keyboard answers at once. */
28
+ initialFocus?: 'cancel' | 'confirm' | undefined;
29
+ /** Work the host runs for it, such as a mutation already under way: the buttons wait, and
30
+ * the dialog can't be dismissed, as while its own handler runs. */
31
+ pending?: boolean | undefined;
32
+ /** Why the last confirm failed, under the body, so the reader can try again. */
33
+ error?: ReactNode;
34
+ size?: ModalSize | undefined;
25
35
  }
26
36
  /** Asks before an action: its title the question, its description what follows,
27
37
  * and Cancel beside a confirm button named for the action. */
28
- export declare function ConfirmModal({ open, onClose, title, description, children, confirmLabel, onConfirm, confirmDisabled, destructive, align, cancelLabel, closeOnConfirm, }: ConfirmModalProps): import("react").JSX.Element;
38
+ export declare function ConfirmModal({ open, onClose, title, description, children, confirmLabel, onConfirm, confirmDisabled, destructive, align, cancelLabel, closeOnConfirm, initialFocus, pending: pendingProp, error, size, }: ConfirmModalProps): import("react").JSX.Element;
39
+ /** What `confirm` asks: a ConfirmModal's props without the ones the hook manages. */
40
+ export interface ConfirmRequest extends Omit<ConfirmModalProps, 'open' | 'onClose' | 'error' | 'pending'> {
41
+ /** Turns a rejected `onConfirm` into the line the dialog shows, and the dialog stays open
42
+ * for another try. Without it, a rejection still keeps the dialog open, silently. */
43
+ errorMessage?: ((error: unknown) => ReactNode) | undefined;
44
+ }
45
+ /** Asks from anywhere, such as a menu row, without keeping a dialog's state by hand:
46
+ * `const { confirm, dialog } = useConfirm()`, render `dialog` once, then
47
+ * `confirm({ title, confirmLabel, destructive, onConfirm })`. It resolves true once
48
+ * confirmed and `onConfirm` has settled, and false when the reader cancels. */
49
+ export declare function useConfirm(): {
50
+ confirm: (request: ConfirmRequest) => Promise<boolean>;
51
+ dialog: ReactNode;
52
+ };
@@ -1,43 +1,59 @@
1
1
  import { useStrings } from "./Provider.js";
2
2
  import { Button } from "./Button.js";
3
+ import { ErrorText } from "./Field.js";
3
4
  import { Modal } from "./Modal.js";
4
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { useRef, useState } from "react";
5
+ import { useCallback, useEffect, useRef, useState } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region src/components/ConfirmModal.tsx
7
8
  /** Asks before an action: its title the question, its description what follows,
8
9
  * and Cancel beside a confirm button named for the action. */
9
- function ConfirmModal({ open, onClose, title, description, children, confirmLabel, onConfirm, confirmDisabled = false, destructive = false, align = "top", cancelLabel, closeOnConfirm = true }) {
10
+ function ConfirmModal({ open, onClose, title, description, children, confirmLabel, onConfirm, confirmDisabled = false, destructive = false, align = "top", cancelLabel, closeOnConfirm = true, initialFocus = destructive ? "cancel" : "confirm", pending: pendingProp = false, error, size = "md" }) {
10
11
  const { common } = useStrings();
11
- const [pending, setPending] = useState(false);
12
- const running = useRef(false);
12
+ const [running, setRunning] = useState(false);
13
+ const fired = useRef(false);
14
+ const cancelButton = useRef(null);
15
+ const confirmButton = useRef(null);
16
+ useEffect(() => {
17
+ if (open) fired.current = false;
18
+ }, [open]);
19
+ const pending = running || pendingProp;
13
20
  const runConfirm = async () => {
14
- if (running.current) return;
15
- running.current = true;
16
- setPending(true);
21
+ if (fired.current || pendingProp) return;
22
+ fired.current = true;
23
+ setRunning(true);
24
+ let closing = false;
17
25
  try {
18
26
  await onConfirm();
19
- if (closeOnConfirm) onClose();
27
+ closing = closeOnConfirm;
28
+ if (closing) onClose();
20
29
  } finally {
21
- running.current = false;
22
- setPending(false);
30
+ fired.current = closing;
31
+ setRunning(false);
23
32
  }
24
33
  };
34
+ const body = children !== void 0 && children !== null && children !== false ? error ? /* @__PURE__ */ jsxs("div", {
35
+ className: "flex flex-col gap-3",
36
+ children: [children, /* @__PURE__ */ jsx(ErrorText, { children: error })]
37
+ }) : children : error ? /* @__PURE__ */ jsx(ErrorText, { children: error }) : void 0;
25
38
  return /* @__PURE__ */ jsx(Modal, {
26
39
  alert: destructive,
27
40
  open,
28
41
  onClose,
29
42
  align,
30
43
  dismissible: !pending,
31
- size: "md",
44
+ size,
32
45
  title,
33
46
  description,
34
- footer: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
47
+ initialFocus: initialFocus === "cancel" ? cancelButton : confirmButton,
48
+ footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
49
+ ref: cancelButton,
35
50
  variant: "ghost",
36
51
  size: "sm",
37
52
  onClick: onClose,
38
53
  disabled: pending,
39
54
  children: cancelLabel ?? common.cancel
40
55
  }), /* @__PURE__ */ jsx(Button, {
56
+ ref: confirmButton,
41
57
  variant: destructive ? "danger" : "primary",
42
58
  size: "sm",
43
59
  onClick: runConfirm,
@@ -45,8 +61,58 @@ function ConfirmModal({ open, onClose, title, description, children, confirmLabe
45
61
  loading: pending,
46
62
  children: confirmLabel
47
63
  })] }),
48
- children
64
+ children: body
49
65
  });
50
66
  }
67
+ /** Asks from anywhere, such as a menu row, without keeping a dialog's state by hand:
68
+ * `const { confirm, dialog } = useConfirm()`, render `dialog` once, then
69
+ * `confirm({ title, confirmLabel, destructive, onConfirm })`. It resolves true once
70
+ * confirmed and `onConfirm` has settled, and false when the reader cancels. */
71
+ function useConfirm() {
72
+ const [request, setRequest] = useState(null);
73
+ const [open, setOpen] = useState(false);
74
+ const [error, setError] = useState(void 0);
75
+ const settle = useRef(null);
76
+ const finish = useCallback((confirmed) => {
77
+ const resolve = settle.current;
78
+ settle.current = null;
79
+ resolve?.(confirmed);
80
+ }, []);
81
+ const confirm = useCallback((next) => {
82
+ finish(false);
83
+ setRequest(next);
84
+ setError(void 0);
85
+ setOpen(true);
86
+ return new Promise((resolve) => {
87
+ settle.current = resolve;
88
+ });
89
+ }, [finish]);
90
+ const close = useCallback(() => {
91
+ setOpen(false);
92
+ finish(false);
93
+ }, [finish]);
94
+ return {
95
+ confirm,
96
+ dialog: request && /* @__PURE__ */ jsx(ConfirmModal, {
97
+ ...request,
98
+ open,
99
+ onClose: close,
100
+ error,
101
+ closeOnConfirm: false,
102
+ onConfirm: async () => {
103
+ if (!settle.current) return;
104
+ setError(void 0);
105
+ try {
106
+ await request.onConfirm();
107
+ } catch (failure) {
108
+ setError(request.errorMessage?.(failure));
109
+ return;
110
+ }
111
+ finish(true);
112
+ if (request.closeOnConfirm !== false) setOpen(false);
113
+ }
114
+ })
115
+ };
116
+ }
51
117
  //#endregion
52
- export { ConfirmModal };
118
+ export { ConfirmModal, useConfirm };