@parallelworks/ui 0.77.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 (262) 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/components/AllocationSelector.js +1 -1
  16. package/dist/ai/components/AttachmentManager.js +5 -5
  17. package/dist/ai/components/AttachmentUpload.js +2 -2
  18. package/dist/ai/components/BranchNavigator.js +1 -1
  19. package/dist/ai/components/ChatActivity.js +1 -1
  20. package/dist/ai/components/ChatEmptyState.js +3 -3
  21. package/dist/ai/components/ChatInput.js +3 -5
  22. package/dist/ai/components/ChatLayout.js +1 -1
  23. package/dist/ai/components/ChatMessage.js +1 -1
  24. package/dist/ai/components/ChatMessageList.js +2 -2
  25. package/dist/ai/components/ChatSidebar.js +3 -3
  26. package/dist/ai/components/ChatThread.js +4 -4
  27. package/dist/ai/components/ComposerAddMenu.js +9 -20
  28. package/dist/ai/components/ModelSelector.js +3 -3
  29. package/dist/ai/components/PastedText.js +5 -5
  30. package/dist/ai/components/SessionView.js +1 -1
  31. package/dist/ai/components/ShareDialog.js +3 -3
  32. package/dist/ai/components/SlashMenu.d.ts +31 -4
  33. package/dist/ai/components/SlashMenu.js +36 -63
  34. package/dist/ai/components/agent/ActivityLine.js +1 -1
  35. package/dist/ai/components/agent/AgentMessageParts.js +13 -13
  36. package/dist/ai/components/conversationActions.js +3 -3
  37. package/dist/ai/core/ChatProvider.js +1 -1
  38. package/dist/ai/core/config.js +1 -1
  39. package/dist/ai/ui/ConversationSidebar.js +3 -3
  40. package/dist/ai/ui/sidebar.js +3 -3
  41. package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
  42. package/dist/components/ActionMenu.d.ts +38 -9
  43. package/dist/components/ActionMenu.js +48 -22
  44. package/dist/components/Avatar.d.ts +66 -6
  45. package/dist/components/Avatar.js +130 -18
  46. package/dist/components/Breadcrumbs.d.ts +4 -0
  47. package/dist/components/Breadcrumbs.js +26 -10
  48. package/dist/components/Button.d.ts +3 -2
  49. package/dist/components/Button.js +11 -1
  50. package/dist/components/Callout.d.ts +3 -2
  51. package/dist/components/Callout.js +3 -2
  52. package/dist/components/Card.d.ts +33 -9
  53. package/dist/components/Card.js +24 -8
  54. package/dist/components/Checkbox.d.ts +3 -1
  55. package/dist/components/Checkbox.js +4 -2
  56. package/dist/components/ColorInput.js +1 -1
  57. package/dist/components/Combobox.d.ts +23 -0
  58. package/dist/components/Combobox.js +252 -86
  59. package/dist/components/ConfirmModal.d.ts +26 -2
  60. package/dist/components/ConfirmModal.js +81 -15
  61. package/dist/components/ContextMenu.d.ts +29 -2
  62. package/dist/components/ContextMenu.js +60 -8
  63. package/dist/components/CopyButton.d.ts +21 -8
  64. package/dist/components/CopyButton.js +29 -9
  65. package/dist/components/CreateModal.d.ts +16 -4
  66. package/dist/components/CreateModal.js +27 -16
  67. package/dist/components/DatePicker.d.ts +31 -1
  68. package/dist/components/DatePicker.js +178 -47
  69. package/dist/components/DescriptionList.d.ts +13 -3
  70. package/dist/components/DescriptionList.js +43 -11
  71. package/dist/components/Disclosure.d.ts +10 -2
  72. package/dist/components/Disclosure.js +18 -7
  73. package/dist/components/Drawer.js +5 -5
  74. package/dist/components/EmptyState.d.ts +12 -4
  75. package/dist/components/EmptyState.js +18 -10
  76. package/dist/components/ErrorPage.d.ts +40 -0
  77. package/dist/components/ErrorPage.js +101 -0
  78. package/dist/components/ExternalLink.d.ts +4 -1
  79. package/dist/components/ExternalLink.js +4 -5
  80. package/dist/components/Field.d.ts +21 -6
  81. package/dist/components/Field.js +32 -16
  82. package/dist/components/FileButton.d.ts +19 -0
  83. package/dist/components/FileButton.js +32 -0
  84. package/dist/components/HoverCard.d.ts +26 -6
  85. package/dist/components/HoverCard.js +11 -5
  86. package/dist/components/IconButton.js +4 -2
  87. package/dist/components/InfoTip.js +3 -3
  88. package/dist/components/Input.d.ts +33 -3
  89. package/dist/components/Input.js +149 -14
  90. package/dist/components/Kbd.d.ts +40 -0
  91. package/dist/components/Kbd.js +168 -0
  92. package/dist/components/Listbox.d.ts +103 -0
  93. package/dist/components/Listbox.js +190 -0
  94. package/dist/components/Loader.d.ts +8 -2
  95. package/dist/components/Loader.js +16 -1
  96. package/dist/components/Markdown.js +1 -1
  97. package/dist/components/MarkdownImpl.js +1 -1
  98. package/dist/components/Menu.d.ts +20 -2
  99. package/dist/components/Menu.js +48 -19
  100. package/dist/components/MenuEntries.d.ts +38 -2
  101. package/dist/components/MenuEntries.js +266 -39
  102. package/dist/components/Meter.d.ts +50 -11
  103. package/dist/components/Meter.js +71 -21
  104. package/dist/components/Modal.d.ts +9 -3
  105. package/dist/components/Modal.js +68 -29
  106. package/dist/components/OneTimeCodeInput.js +1 -1
  107. package/dist/components/PageHeader.js +111 -7
  108. package/dist/components/PageHeaderAction.d.ts +8 -3
  109. package/dist/components/PageHeaderAction.js +122 -34
  110. package/dist/components/Pagination.d.ts +13 -1
  111. package/dist/components/Pagination.js +90 -19
  112. package/dist/components/Popover.d.ts +13 -2
  113. package/dist/components/Popover.js +20 -21
  114. package/dist/components/Provider.d.ts +140 -3
  115. package/dist/components/Provider.js +142 -12
  116. package/dist/components/RefreshButton.d.ts +13 -6
  117. package/dist/components/RefreshButton.js +17 -9
  118. package/dist/components/RunStatusIcon.d.ts +16 -7
  119. package/dist/components/RunStatusIcon.js +21 -8
  120. package/dist/components/Section.d.ts +11 -4
  121. package/dist/components/Section.js +33 -22
  122. package/dist/components/SegmentedControl.d.ts +16 -3
  123. package/dist/components/SegmentedControl.js +7 -3
  124. package/dist/components/Select.d.ts +19 -3
  125. package/dist/components/Select.js +114 -68
  126. package/dist/components/SelectionCopy.d.ts +10 -0
  127. package/dist/components/SelectionCopy.js +105 -0
  128. package/dist/components/SettingModeOption.js +1 -1
  129. package/dist/components/SettingsCard.js +1 -1
  130. package/dist/components/Skeleton.js +1 -1
  131. package/dist/components/StatusBadge.d.ts +55 -10
  132. package/dist/components/StatusBadge.js +91 -15
  133. package/dist/components/StickyBar.js +1 -1
  134. package/dist/components/Table.d.ts +22 -4
  135. package/dist/components/Table.js +46 -24
  136. package/dist/components/Tabs.d.ts +9 -4
  137. package/dist/components/Tabs.js +9 -4
  138. package/dist/components/Tag.d.ts +49 -0
  139. package/dist/components/Tag.js +199 -0
  140. package/dist/components/ThemeEditor.js +1 -1
  141. package/dist/components/Toast.js +1 -1
  142. package/dist/components/ToggleGroup.d.ts +4 -1
  143. package/dist/components/ToggleGroup.js +30 -13
  144. package/dist/components/Tooltip.d.ts +9 -3
  145. package/dist/components/Tooltip.js +55 -39
  146. package/dist/components/buttonClasses.d.ts +5 -2
  147. package/dist/components/buttonClasses.js +11 -10
  148. package/dist/components/chart.d.ts +69 -0
  149. package/dist/components/chart.js +145 -0
  150. package/dist/components/comboboxItems.d.ts +7 -1
  151. package/dist/components/comboboxItems.js +6 -2
  152. package/dist/components/dismiss.d.ts +5 -1
  153. package/dist/components/dismiss.js +6 -2
  154. package/dist/components/floating.d.ts +4 -4
  155. package/dist/components/floating.js +5 -5
  156. package/dist/components/headerActionsFit.d.ts +37 -0
  157. package/dist/components/headerActionsFit.js +81 -0
  158. package/dist/components/pageHeaderStore.d.ts +3 -0
  159. package/dist/components/pageHeaderStore.js +3 -0
  160. package/dist/components/pathBreaks.js +2 -2
  161. package/dist/components/popupContainer.d.ts +17 -0
  162. package/dist/components/popupContainer.js +69 -0
  163. package/dist/components/relativeTime.d.ts +21 -10
  164. package/dist/components/relativeTime.js +50 -22
  165. package/dist/components/segmented.d.ts +18 -4
  166. package/dist/components/segmented.js +48 -6
  167. package/dist/components/typedDate.d.ts +5 -0
  168. package/dist/components/typedDate.js +50 -0
  169. package/dist/components/useClosingValue.d.ts +4 -0
  170. package/dist/components/useClosingValue.js +12 -0
  171. package/dist/components/useCopy.d.ts +6 -0
  172. package/dist/components/useCopy.js +3 -3
  173. package/dist/components/useNarrow.d.ts +2 -0
  174. package/dist/components/useNarrow.js +16 -0
  175. package/dist/editor/Editor.js +1 -1
  176. package/dist/editor/FormCodePanel.js +1 -1
  177. package/dist/editor/Monaco.js +1 -1
  178. package/dist/editor/workers.js +1 -1
  179. package/dist/file-explorer/FileExplorer.js +16 -15
  180. package/dist/file-explorer/FileExplorerContext.js +2 -2
  181. package/dist/file-explorer/FilePreview/Viewer.js +3 -3
  182. package/dist/file-explorer/FilePreview/index.js +2 -2
  183. package/dist/file-explorer/FileRowsWindow.js +3 -3
  184. package/dist/file-explorer/FileTable.js +1 -1
  185. package/dist/file-explorer/LoadMoreRow.js +1 -1
  186. package/dist/file-explorer/TreeView.js +5 -5
  187. package/dist/file-explorer/helpers.js +2 -2
  188. package/dist/file-explorer/toasts.js +2 -2
  189. package/dist/form/Fields/CheckboxGroupField.js +1 -1
  190. package/dist/form/Fields/DropdownField.js +1 -1
  191. package/dist/form/Fields/DurationField.js +2 -2
  192. package/dist/form/Fields/MultiDropdownField.js +1 -1
  193. package/dist/form/Fields/NumberField.js +1 -1
  194. package/dist/form/Fields/ObjectField.js +1 -1
  195. package/dist/form/Fields/RangeField.js +1 -1
  196. package/dist/form/Fields/StringField.js +1 -1
  197. package/dist/form/Form.js +3 -3
  198. package/dist/form/FormLayout.js +1 -1
  199. package/dist/form/Wizard/WizardContainer.js +1 -1
  200. package/dist/form/Wizard/WizardNavigation.js +1 -1
  201. package/dist/form/Wizard/WizardStepContent.js +1 -1
  202. package/dist/form/fieldRegistry.js +1 -1
  203. package/dist/form/formEditing.js +1 -1
  204. package/dist/format.d.ts +17 -7
  205. package/dist/format.js +39 -16
  206. package/dist/graph/AnnotationBanner.js +2 -2
  207. package/dist/graph/Connectors.js +5 -5
  208. package/dist/graph/DependencyGraph.js +10 -9
  209. package/dist/graph/GraphEditor.js +4 -4
  210. package/dist/graph/GraphEditorDialogs.js +13 -12
  211. package/dist/graph/GraphNode.js +1 -1
  212. package/dist/graph/InputDialog.js +5 -5
  213. package/dist/graph/InputsEditor.js +7 -7
  214. package/dist/graph/MatrixGroup.js +2 -2
  215. package/dist/graph/ProblemsButton.js +3 -3
  216. package/dist/graph/Reveal.js +1 -1
  217. package/dist/graph/RunWarningsList.js +1 -1
  218. package/dist/graph/ShortcutsButton.js +25 -8
  219. package/dist/graph/StepSummaries.js +1 -1
  220. package/dist/graph/TreeView.js +2 -2
  221. package/dist/graph/WorkflowSettingsDialog.js +3 -3
  222. package/dist/graph/editorFields.js +3 -3
  223. package/dist/graph/editorPrimitives.js +1 -1
  224. package/dist/graph/editorRows.js +5 -5
  225. package/dist/graph/fieldProblems.js +2 -2
  226. package/dist/graph/inputRefs.js +7 -6
  227. package/dist/graph/stepWith.js +2 -2
  228. package/dist/index.d.ts +30 -21
  229. package/dist/index.js +20 -12
  230. package/dist/list/ActionToolbar.d.ts +4 -0
  231. package/dist/list/ActionToolbar.js +35 -12
  232. package/dist/list/ListFilterBar.d.ts +42 -0
  233. package/dist/list/ListFilterBar.js +789 -0
  234. package/dist/list/ListPage.d.ts +42 -4
  235. package/dist/list/ListPage.js +56 -22
  236. package/dist/list/ListTable.d.ts +27 -3
  237. package/dist/list/ListTable.js +48 -23
  238. package/dist/list/ListToolbar.d.ts +31 -0
  239. package/dist/list/ListToolbar.js +43 -0
  240. package/dist/list/ListViewControls.d.ts +8 -4
  241. package/dist/list/ListViewControls.js +22 -14
  242. package/dist/list/UserHoverCard.d.ts +8 -2
  243. package/dist/list/UserHoverCard.js +26 -13
  244. package/dist/list/filterModel.d.ts +122 -0
  245. package/dist/list/filterModel.js +308 -0
  246. package/dist/list/index.d.ts +6 -2
  247. package/dist/list/index.js +6 -2
  248. package/dist/list/listSearch.d.ts +1 -1
  249. package/dist/list/listSearch.js +8 -4
  250. package/dist/list/listView.d.ts +24 -2
  251. package/dist/list/listView.js +47 -45
  252. package/dist/list/rowMenu.d.ts +10 -5
  253. package/dist/list/rowMenu.js +17 -7
  254. package/dist/list/useListFilters.d.ts +68 -0
  255. package/dist/list/useListFilters.js +252 -0
  256. package/dist/logviewer/LogViewer.js +4 -4
  257. package/dist/styles/theme.css +94 -0
  258. package/dist/styles.css +1 -1
  259. package/package.json +11 -11
  260. package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
  261. package/dist/components/fullscreen.d.ts +0 -4
  262. package/dist/components/fullscreen.js +0 -18
@@ -0,0 +1,789 @@
1
+ import { cx } from "../cx.js";
2
+ import { buttonClasses } from "../components/buttonClasses.js";
3
+ import { AddIcon, CheckIcon, CloseIcon } from "../icons.js";
4
+ import { MenuCheckboxItem, MenuGroupLabel, MenuItem, MenuPopup, MenuRadioItem, MenuSeparator } from "../components/Menu.js";
5
+ import { useLocale, useStrings } from "../components/Provider.js";
6
+ import { useQueryMatch } from "../components/search.js";
7
+ import { Popover } from "../components/Popover.js";
8
+ import { Input } from "../components/Input.js";
9
+ import { Combobox } from "../components/Combobox.js";
10
+ import { Calendar } from "../components/DatePicker.js";
11
+ import { Drawer } from "../components/Drawer.js";
12
+ import { DATE_PRESETS, fieldOperators, isDatePreset, isMultiple, parseDay, toDay } from "./filterModel.js";
13
+ import { Menu } from "@base-ui/react/menu";
14
+ import { useEffect, useLayoutEffect, useRef, useState } from "react";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
+ //#region src/list/ListFilterBar.tsx
17
+ /** Below this width the row stops at two lines and the rest open in a sheet. */
18
+ var NARROW_PX = 640;
19
+ var partClassName = "relative flex min-w-0 shrink-0 cursor-pointer items-center gap-1.5 px-2 outline-none transition-colors before:absolute before:inset-x-0 before:-inset-y-0.5 before:content-[''] hover:bg-(--theme-hover) focus-visible:z-[1] focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-(--theme-focus) data-popup-open:bg-(--theme-hover)";
20
+ function Divider() {
21
+ return /* @__PURE__ */ jsx("span", {
22
+ "aria-hidden": "true",
23
+ className: "w-px shrink-0 self-stretch bg-(--theme-border)"
24
+ });
25
+ }
26
+ /** The labels a filter's values show, and their icons. */
27
+ function valueParts(field, filter, t, labels, locale) {
28
+ const labelOf = (v) => labels.get(v)?.label ?? field.options?.find((o) => o.value === v)?.label ?? field.valueLabel?.(v) ?? v;
29
+ const iconOf = (v) => labels.get(v)?.icon ?? field.options?.find((o) => o.value === v)?.icon;
30
+ const dayText = (v) => {
31
+ if (!v) return "";
32
+ if (isDatePreset(v)) return t.datePreset(v);
33
+ const day = parseDay(v);
34
+ return day ? t.formatDate(day, locale) : v;
35
+ };
36
+ switch (field.kind) {
37
+ case "date": return {
38
+ all: [filter.op === "between" ? t.between(dayText(filter.values[0]), dayText(filter.values[1])) : dayText(filter.values[0])],
39
+ icons: []
40
+ };
41
+ case "boolean": return {
42
+ all: [filter.values[0] === "false" ? t.no : t.yes],
43
+ icons: []
44
+ };
45
+ case "text": return {
46
+ all: [filter.values[0] ?? ""],
47
+ icons: []
48
+ };
49
+ default: return {
50
+ all: filter.values.map(labelOf),
51
+ icons: filter.values.slice(0, field.kind === "people" ? 3 : 1).map(iconOf).filter(Boolean)
52
+ };
53
+ }
54
+ }
55
+ /** One filter as a sentence: its field, its condition and its values, each opening its own
56
+ * picker, and a button that removes it. */
57
+ function FilterChip({ filters, filter, field, stretch = false, answers, onRemoved }) {
58
+ const t = useStrings().filters;
59
+ const [seen] = useState(() => /* @__PURE__ */ new Map());
60
+ const [open, setOpen] = useState(null);
61
+ const requested = useRef(false);
62
+ const valueButton = useRef(null);
63
+ const request = answers && filters.editRequest?.field === filter.field ? filters.editRequest : null;
64
+ const { clearEditRequest } = filters;
65
+ useEffect(() => {
66
+ if (!request) return;
67
+ const frame = requestAnimationFrame(() => {
68
+ requested.current = true;
69
+ setOpen(request.part);
70
+ clearEditRequest(request.id);
71
+ });
72
+ return () => cancelAnimationFrame(frame);
73
+ }, [request, clearEditRequest]);
74
+ const unset = filter.values.length === 0 && filter.op !== "isSet" && filter.op !== "isNotSet";
75
+ const fixed = Boolean(field.required || field.pinned);
76
+ const showsAny = unset && fixed;
77
+ const isDraft = filters.draft === filter.field;
78
+ const removable = !field.required && !(field.pinned && unset);
79
+ const operators = fieldOperators(field);
80
+ const { all, icons } = valueParts(field, filter, t, seen, useLocale());
81
+ const shown = all.slice(0, 2);
82
+ const hidden = all.length - shown.length;
83
+ const valueText = unset ? showsAny ? t.any : t.pending : t.list(shown);
84
+ const operatorText = t.operator(filter.op, filter.values.length);
85
+ const noValuePart = filter.op === "isSet" || filter.op === "isNotSet" || field.kind === "boolean" && filter.values[0] !== "false";
86
+ const noOperatorPart = showsAny || field.kind === "boolean" && filter.values[0] !== "false";
87
+ const readAll = unset ? valueText : t.list(all);
88
+ const remove = () => {
89
+ if (field.pinned && !field.required) filters.update(filter.field, { values: [] });
90
+ else filters.remove(filter.field);
91
+ };
92
+ const onKeyDown = (e) => {
93
+ if ((e.key === "Backspace" || e.key === "Delete") && removable && open === null) {
94
+ e.preventDefault();
95
+ remove();
96
+ onRemoved?.();
97
+ }
98
+ };
99
+ const setPart = (part) => (next) => setOpen((current) => next ? part : current === part ? null : current);
100
+ const onValueClose = () => {
101
+ const wasRequested = requested.current;
102
+ requested.current = false;
103
+ if (isDraft && filter.values.length === 0) {
104
+ filters.discardDraft();
105
+ return;
106
+ }
107
+ if (wasRequested) setTimeout(() => valueButton.current?.focus(), 0);
108
+ };
109
+ return /* @__PURE__ */ jsxs("div", {
110
+ role: "group",
111
+ "aria-label": t.chip(field.label, noOperatorPart ? "" : operatorText, noValuePart ? "" : readAll),
112
+ "data-filter-chip": filter.field,
113
+ onKeyDown,
114
+ className: cx("flex h-7 max-w-full items-stretch overflow-hidden rounded-md border border-(--theme-border) bg-(--theme-panel-bg) text-[12px] text-(--theme-app)", stretch ? "w-full" : "w-fit"),
115
+ children: [
116
+ /* @__PURE__ */ jsx(FieldMenu, {
117
+ filters,
118
+ field,
119
+ open: open === "field",
120
+ onOpenChange: setPart("field")
121
+ }),
122
+ !noOperatorPart && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Divider, {}), operators.length > 1 ? /* @__PURE__ */ jsx(OperatorMenu, {
123
+ filters,
124
+ filter,
125
+ operators,
126
+ open: open === "operator",
127
+ onOpenChange: setPart("operator")
128
+ }) : /* @__PURE__ */ jsx("span", {
129
+ className: "flex shrink-0 items-center px-2 text-(--theme-muted-text-color)",
130
+ children: operatorText
131
+ })] }),
132
+ !noValuePart && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Divider, {}), /* @__PURE__ */ jsx(ValuePicker, {
133
+ filters,
134
+ filter,
135
+ field,
136
+ seen,
137
+ open: open === "value",
138
+ onOpenChange: (next) => {
139
+ setPart("value")(next);
140
+ if (!next) onValueClose();
141
+ },
142
+ trigger: /* @__PURE__ */ jsxs("button", {
143
+ type: "button",
144
+ ref: valueButton,
145
+ "aria-label": t.changeValue(readAll || field.label),
146
+ className: cx(partClassName, "shrink", stretch && "flex-1"),
147
+ children: [
148
+ icons.length > 0 && /* @__PURE__ */ jsx("span", {
149
+ className: "flex shrink-0 items-center -space-x-1 [&_svg]:size-3",
150
+ children: icons.map((icon, i) => /* @__PURE__ */ jsx("span", {
151
+ className: "flex",
152
+ children: icon
153
+ }, i))
154
+ }),
155
+ /* @__PURE__ */ jsx("span", {
156
+ className: cx("min-w-0 truncate", !stretch && "max-w-40", unset && "text-(--theme-muted-text-color)"),
157
+ children: valueText
158
+ }),
159
+ hidden > 0 && /* @__PURE__ */ jsx("span", {
160
+ className: "shrink-0 text-(--theme-muted-text-color)",
161
+ children: t.more(hidden)
162
+ })
163
+ ]
164
+ })
165
+ })] }),
166
+ removable && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Divider, {}), /* @__PURE__ */ jsx("button", {
167
+ type: "button",
168
+ "aria-label": t.remove(field.label),
169
+ onClick: remove,
170
+ className: cx(partClassName, "px-1.5 text-(--theme-muted-text-color) hover:text-(--theme-app)", stretch && "ml-auto"),
171
+ children: /* @__PURE__ */ jsx(CloseIcon, {
172
+ "aria-hidden": "true",
173
+ className: "size-3"
174
+ })
175
+ })] })
176
+ ]
177
+ });
178
+ }
179
+ function FieldMenu({ filters, field, open, onOpenChange }) {
180
+ const t = useStrings().filters;
181
+ const used = new Set(filters.chips.map((f) => f.field));
182
+ const others = filters.fields.filter((f) => f.key === field.key || !used.has(f.key) && !f.required && !f.pinned);
183
+ const fixed = field.required || field.pinned;
184
+ const label = /* @__PURE__ */ jsxs(Fragment$1, { children: [field.icon && /* @__PURE__ */ jsx("span", {
185
+ className: "flex shrink-0 text-(--theme-muted-text-color) [&_svg]:size-3.5",
186
+ children: field.icon
187
+ }), /* @__PURE__ */ jsx("span", {
188
+ className: "whitespace-nowrap",
189
+ children: field.label
190
+ })] });
191
+ if (fixed || others.length < 2) return /* @__PURE__ */ jsx("span", {
192
+ className: "flex shrink-0 items-center gap-1.5 px-2",
193
+ children: label
194
+ });
195
+ return /* @__PURE__ */ jsxs(Menu.Root, {
196
+ open,
197
+ onOpenChange,
198
+ children: [/* @__PURE__ */ jsx(Menu.Trigger, {
199
+ render: /* @__PURE__ */ jsx("button", {
200
+ type: "button",
201
+ "aria-label": t.changeField(field.label),
202
+ className: partClassName
203
+ }),
204
+ children: label
205
+ }), /* @__PURE__ */ jsx(MenuPopup, {
206
+ className: "max-w-[min(20rem,var(--available-width))]",
207
+ children: /* @__PURE__ */ jsx(Menu.RadioGroup, {
208
+ value: field.key,
209
+ onValueChange: (next) => filters.replaceField(field.key, next),
210
+ children: others.map((f) => /* @__PURE__ */ jsxs(MenuRadioItem, {
211
+ value: f.key,
212
+ children: [f.icon && /* @__PURE__ */ jsx("span", {
213
+ className: "flex [&_svg]:size-3.5",
214
+ children: f.icon
215
+ }), f.label]
216
+ }, f.key))
217
+ })
218
+ })]
219
+ });
220
+ }
221
+ function OperatorMenu({ filters, filter, operators, open, onOpenChange }) {
222
+ const t = useStrings().filters;
223
+ const count = filter.values.length;
224
+ const text = t.operator(filter.op, count);
225
+ return /* @__PURE__ */ jsxs(Menu.Root, {
226
+ open,
227
+ onOpenChange,
228
+ children: [/* @__PURE__ */ jsx(Menu.Trigger, {
229
+ render: /* @__PURE__ */ jsx("button", {
230
+ type: "button",
231
+ "aria-label": t.changeOperator(text),
232
+ className: cx(partClassName, "text-(--theme-muted-text-color)")
233
+ }),
234
+ children: /* @__PURE__ */ jsx("span", {
235
+ className: "whitespace-nowrap",
236
+ children: text
237
+ })
238
+ }), /* @__PURE__ */ jsx(MenuPopup, { children: /* @__PURE__ */ jsx(Menu.RadioGroup, {
239
+ value: filter.op,
240
+ onValueChange: (op) => {
241
+ const values = op === "between" || filter.op === "between" || op === "isSet" || op === "isNotSet" ? op === "isSet" || op === "isNotSet" ? [] : filter.values.filter((v) => !isDatePreset(v)).slice(0, op === "between" ? 2 : 1) : filter.op === "is" && (op === "after" || op === "before") ? filter.values.filter((v) => !isDatePreset(v)) : filter.values;
242
+ filters.update(filter.field, {
243
+ op,
244
+ values
245
+ });
246
+ },
247
+ children: operators.map((op) => /* @__PURE__ */ jsx(MenuRadioItem, {
248
+ value: op,
249
+ children: t.operator(op, Math.max(count, 1))
250
+ }, op))
251
+ }) })]
252
+ });
253
+ }
254
+ function ValuePicker(props) {
255
+ switch (props.field.kind) {
256
+ case "date": return /* @__PURE__ */ jsx(DateValuePicker, { ...props });
257
+ case "text": return /* @__PURE__ */ jsx(TextValuePicker, { ...props });
258
+ case "boolean": return /* @__PURE__ */ jsx(BooleanValuePicker, { ...props });
259
+ default: return /* @__PURE__ */ jsx(OptionValuePicker, { ...props });
260
+ }
261
+ }
262
+ /** Options from the server for what is typed, while the picker is open. */
263
+ function useLoadedOptions(field, query, open) {
264
+ const [state, setState] = useState({
265
+ options: field.options ?? [],
266
+ loading: false
267
+ });
268
+ const { loadOptions } = field;
269
+ useEffect(() => {
270
+ if (!loadOptions || !open) return;
271
+ const controller = new AbortController();
272
+ setState((s) => ({
273
+ ...s,
274
+ loading: true
275
+ }));
276
+ loadOptions(query, controller.signal).then((options) => {
277
+ if (!controller.signal.aborted) setState({
278
+ options,
279
+ loading: false
280
+ });
281
+ }, () => {
282
+ if (!controller.signal.aborted) setState((s) => ({
283
+ ...s,
284
+ loading: false
285
+ }));
286
+ });
287
+ return () => controller.abort();
288
+ }, [
289
+ loadOptions,
290
+ query,
291
+ open
292
+ ]);
293
+ return loadOptions ? state : {
294
+ options: field.options ?? [],
295
+ loading: false
296
+ };
297
+ }
298
+ function OptionValuePicker({ filters, filter, field, seen, open, onOpenChange, trigger }) {
299
+ const strings = useStrings();
300
+ const matches = useQueryMatch();
301
+ const [query, setQuery] = useState("");
302
+ const multiple = isMultiple(field);
303
+ const loaded = useLoadedOptions(field, query.trim(), open);
304
+ for (const option of loaded.options) seen.set(option.value, option);
305
+ const searchable = field.searchable ?? (Boolean(field.loadOptions) || (field.options?.length ?? 0) > 8);
306
+ const needle = query.trim();
307
+ const selected = filter.values;
308
+ const all = [...selected.filter((v) => !loaded.options.some((o) => o.value === v)).map((v) => seen.get(v) ?? {
309
+ value: v,
310
+ label: field.valueLabel?.(v) ?? v
311
+ }), ...loaded.options];
312
+ const options = needle && !field.loadOptions ? all.filter((o) => selected.includes(o.value) || matches(needle, o.label, o.description, o.group)) : all;
313
+ const set = (values) => filters.update(filter.field, { values });
314
+ const content = (option) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
315
+ option.icon && /* @__PURE__ */ jsx("span", {
316
+ className: "flex shrink-0 [&_svg]:size-3.5",
317
+ children: option.icon
318
+ }),
319
+ /* @__PURE__ */ jsxs("span", {
320
+ className: "flex min-w-0 flex-1 flex-col",
321
+ children: [/* @__PURE__ */ jsx("span", {
322
+ className: "truncate",
323
+ children: option.label
324
+ }), option.description && /* @__PURE__ */ jsx("span", {
325
+ className: "truncate text-[11px] text-(--theme-muted-text-color)",
326
+ children: option.description
327
+ })]
328
+ }),
329
+ option.count !== void 0 && /* @__PURE__ */ jsx("span", {
330
+ className: "shrink-0 text-[11px] tabular-nums text-(--theme-muted-text-color)",
331
+ children: COUNT.format(option.count)
332
+ })
333
+ ] });
334
+ const row = (option) => {
335
+ const checked = selected.includes(option.value);
336
+ return multiple ? /* @__PURE__ */ jsx(MenuCheckboxItem, {
337
+ checked,
338
+ closeOnClick: false,
339
+ onCheckedChange: () => set(checked ? selected.filter((v) => v !== option.value) : [...selected, option.value]),
340
+ children: content(option)
341
+ }, option.value) : /* @__PURE__ */ jsx(MenuRadioItem, {
342
+ value: option.value,
343
+ children: content(option)
344
+ }, option.value);
345
+ };
346
+ const groups = [];
347
+ for (const option of options) {
348
+ const last = groups.find((g) => g.label === option.group);
349
+ if (last) last.options.push(option);
350
+ else groups.push({
351
+ label: option.group,
352
+ options: [option]
353
+ });
354
+ }
355
+ const rows = groups.map((g) => g.label ? /* @__PURE__ */ jsxs(Menu.Group, { children: [/* @__PURE__ */ jsx(MenuGroupLabel, { children: g.label }), g.options.map(row)] }, g.label) : g.options.map(row));
356
+ const list = multiple ? rows : /* @__PURE__ */ jsx(Menu.RadioGroup, {
357
+ value: selected[0] ?? null,
358
+ onValueChange: (v) => set([v]),
359
+ children: rows
360
+ });
361
+ if (searchable) return /* @__PURE__ */ jsx(SearchablePicker, {
362
+ options,
363
+ selected,
364
+ multiple,
365
+ loading: loaded.loading,
366
+ open,
367
+ onOpenChange: (next) => {
368
+ onOpenChange(next);
369
+ if (!next) setQuery("");
370
+ },
371
+ onQueryChange: setQuery,
372
+ onValuesChange: set,
373
+ trigger
374
+ });
375
+ return /* @__PURE__ */ jsxs(Menu.Root, {
376
+ open,
377
+ onOpenChange,
378
+ children: [/* @__PURE__ */ jsx(Menu.Trigger, { render: trigger }), /* @__PURE__ */ jsxs(MenuPopup, {
379
+ className: "w-max min-w-56 max-w-[min(28rem,var(--available-width))]",
380
+ children: [list, !loaded.loading && options.length === 0 && /* @__PURE__ */ jsx("div", {
381
+ className: "px-3 py-1.5 text-[13px] text-(--theme-muted-text-color)",
382
+ children: strings.dropdown.noOptionsFound
383
+ })]
384
+ })]
385
+ });
386
+ }
387
+ /** A long list's picker, such as of people: typing narrows it and Enter takes the first match,
388
+ * and only the rows in view are drawn. */
389
+ function SearchablePicker({ options, selected, multiple, loading, open, onOpenChange, onQueryChange, onValuesChange, trigger }) {
390
+ const strings = useStrings();
391
+ const items = [];
392
+ const groups = /* @__PURE__ */ new Map();
393
+ for (const option of options) {
394
+ const item = {
395
+ value: option.value,
396
+ label: option.label,
397
+ description: option.description,
398
+ icon: option.icon && /* @__PURE__ */ jsx("span", {
399
+ className: "flex [&_svg]:size-3.5",
400
+ children: option.icon
401
+ }),
402
+ hint: option.count === void 0 ? void 0 : COUNT.format(option.count)
403
+ };
404
+ if (!option.group) {
405
+ items.push(item);
406
+ continue;
407
+ }
408
+ let group = groups.get(option.group);
409
+ if (!group) {
410
+ group = [];
411
+ groups.set(option.group, group);
412
+ items.push({
413
+ label: option.group,
414
+ items: group
415
+ });
416
+ }
417
+ group.push(item);
418
+ }
419
+ const shared = {
420
+ items,
421
+ placeholder: strings.list.searchOptions,
422
+ open,
423
+ onOpenChange,
424
+ onQueryChange,
425
+ loading: loading && options.length === 0,
426
+ trigger
427
+ };
428
+ return multiple ? /* @__PURE__ */ jsx(Combobox, {
429
+ ...shared,
430
+ multiple: true,
431
+ value: selected,
432
+ onValueChange: onValuesChange
433
+ }) : /* @__PURE__ */ jsx(Combobox, {
434
+ ...shared,
435
+ value: selected[0] ?? null,
436
+ onValueChange: (value) => {
437
+ if (value !== null) onValuesChange([value]);
438
+ }
439
+ });
440
+ }
441
+ var COUNT = new Intl.NumberFormat();
442
+ function BooleanValuePicker({ filters, filter, open, onOpenChange, trigger }) {
443
+ const t = useStrings().filters;
444
+ return /* @__PURE__ */ jsxs(Menu.Root, {
445
+ open,
446
+ onOpenChange,
447
+ children: [/* @__PURE__ */ jsx(Menu.Trigger, { render: trigger }), /* @__PURE__ */ jsx(MenuPopup, { children: /* @__PURE__ */ jsxs(Menu.RadioGroup, {
448
+ value: filter.values[0] ?? "true",
449
+ onValueChange: (v) => filters.update(filter.field, { values: [v] }),
450
+ children: [/* @__PURE__ */ jsx(MenuRadioItem, {
451
+ value: "true",
452
+ children: t.yes
453
+ }), /* @__PURE__ */ jsx(MenuRadioItem, {
454
+ value: "false",
455
+ children: t.no
456
+ })]
457
+ }) })]
458
+ });
459
+ }
460
+ function DateValuePicker({ filters, filter, open, onOpenChange, trigger }) {
461
+ const t = useStrings().filters;
462
+ const [custom, setCustom] = useState(false);
463
+ const [first, second] = filter.values;
464
+ const choosePreset = (preset) => {
465
+ filters.update(filter.field, {
466
+ op: "is",
467
+ values: [preset]
468
+ });
469
+ onOpenChange(false);
470
+ };
471
+ const showCalendar = custom || filter.op !== "is" || first !== void 0 && !isDatePreset(first);
472
+ const range = filter.op === "between" || custom && filter.op === "is";
473
+ return /* @__PURE__ */ jsxs(Popover, {
474
+ "aria-label": t.changeValue(""),
475
+ trigger,
476
+ open,
477
+ onOpenChange: (next) => {
478
+ onOpenChange(next);
479
+ if (!next) setCustom(false);
480
+ },
481
+ align: "start",
482
+ initialFocus: "first-control",
483
+ className: "p-0",
484
+ children: [filter.op === "is" && /* @__PURE__ */ jsxs("div", {
485
+ className: cx("flex flex-col p-1", showCalendar && "border-b border-(--theme-border)"),
486
+ children: [DATE_PRESETS.map((preset) => /* @__PURE__ */ jsxs("button", {
487
+ type: "button",
488
+ "aria-pressed": first === preset,
489
+ onClick: () => choosePreset(preset),
490
+ className: "flex h-7 cursor-pointer items-center justify-between gap-4 rounded px-2 text-left text-[13px] outline-none hover:bg-(--theme-hover) focus-visible:bg-(--theme-hover)",
491
+ children: [t.datePreset(preset), first === preset && /* @__PURE__ */ jsx(CheckIcon, {
492
+ "aria-hidden": "true",
493
+ className: "size-3"
494
+ })]
495
+ }, preset)), !showCalendar && /* @__PURE__ */ jsx("button", {
496
+ type: "button",
497
+ onClick: () => setCustom(true),
498
+ className: "flex h-7 cursor-pointer items-center rounded px-2 text-left text-[13px] outline-none hover:bg-(--theme-hover) focus-visible:bg-(--theme-hover)",
499
+ children: t.customDate
500
+ })]
501
+ }), showCalendar && (range ? /* @__PURE__ */ jsx(Calendar, {
502
+ mode: "range",
503
+ autoFocus: custom,
504
+ selected: {
505
+ from: parseDay(first),
506
+ to: parseDay(second)
507
+ },
508
+ onSelect: (next) => {
509
+ if (!next?.from) return;
510
+ const from = toDay(next.from);
511
+ const to = next.to ? toDay(next.to) : "";
512
+ filters.update(filter.field, {
513
+ op: "between",
514
+ values: to ? [from, to] : [from]
515
+ });
516
+ if (to && from !== to) onOpenChange(false);
517
+ }
518
+ }) : /* @__PURE__ */ jsx(Calendar, {
519
+ mode: "single",
520
+ autoFocus: true,
521
+ selected: parseDay(first),
522
+ onSelect: (day) => {
523
+ if (day) {
524
+ filters.update(filter.field, { values: [toDay(day)] });
525
+ onOpenChange(false);
526
+ }
527
+ }
528
+ }))]
529
+ });
530
+ }
531
+ function TextValuePicker({ filters, filter, field, open, onOpenChange, trigger }) {
532
+ const t = useStrings().filters;
533
+ const [text, setText] = useState(filter.values[0] ?? "");
534
+ useEffect(() => {
535
+ if (open) setText(filter.values[0] ?? "");
536
+ }, [open, filter.values]);
537
+ return /* @__PURE__ */ jsx(Popover, {
538
+ "aria-label": field.label,
539
+ trigger,
540
+ open,
541
+ onOpenChange,
542
+ align: "start",
543
+ className: "w-64 p-2",
544
+ children: /* @__PURE__ */ jsx(Input, {
545
+ size: "sm",
546
+ "aria-label": field.label,
547
+ placeholder: t.textPlaceholder,
548
+ value: text,
549
+ onChange: (e) => setText(e.target.value),
550
+ onKeyDown: (e) => {
551
+ if (e.key === "Enter") {
552
+ e.preventDefault();
553
+ const value = text.trim();
554
+ filters.update(filter.field, { values: value ? [value] : [] });
555
+ onOpenChange(false);
556
+ }
557
+ }
558
+ })
559
+ });
560
+ }
561
+ /** The fields a filter can be added on. Picking one adds its chip and opens its value picker
562
+ * there; a field already in use opens its chip's picker. */
563
+ function AddFilterMenu({ filters, trigger, align = "start" }) {
564
+ const strings = useStrings();
565
+ const t = strings.filters;
566
+ const matches = useQueryMatch();
567
+ const [query, setQuery] = useState("");
568
+ const picked = useRef(null);
569
+ const used = new Set(filters.active.map((f) => f.field));
570
+ const searchable = filters.fields.length > 8;
571
+ const needle = query.trim();
572
+ const fields = needle ? filters.fields.filter((f) => matches(needle, f.label)) : filters.fields;
573
+ const removable = filters.active.some((f) => !filters.fields.find((x) => x.key === f.field)?.required);
574
+ const resetting = filters.hasDefaults;
575
+ const showClear = resetting ? !filters.isDefault : removable;
576
+ const rows = fields.map((f) => /* @__PURE__ */ jsxs(MenuItem, {
577
+ onClick: () => {
578
+ picked.current = f.key;
579
+ },
580
+ children: [
581
+ f.icon && /* @__PURE__ */ jsx("span", {
582
+ className: "flex shrink-0 text-(--theme-muted-text-color) [&_svg]:size-3.5",
583
+ children: f.icon
584
+ }),
585
+ /* @__PURE__ */ jsx("span", {
586
+ className: "min-w-0 flex-1 truncate",
587
+ children: f.label
588
+ }),
589
+ used.has(f.key) && /* @__PURE__ */ jsx(CheckIcon, {
590
+ "aria-hidden": "true",
591
+ className: "size-3.5 shrink-0 opacity-80"
592
+ })
593
+ ]
594
+ }, f.key));
595
+ const menu = /* @__PURE__ */ jsxs(Menu.Root, {
596
+ onOpenChange: (next) => {
597
+ if (!next) setQuery("");
598
+ },
599
+ onOpenChangeComplete: (next) => {
600
+ const key = picked.current;
601
+ picked.current = null;
602
+ if (!next && key) filters.begin(key);
603
+ },
604
+ children: [/* @__PURE__ */ jsx(Menu.Trigger, { render: trigger }), /* @__PURE__ */ jsxs(MenuPopup, {
605
+ align,
606
+ className: "w-56",
607
+ children: [
608
+ /* @__PURE__ */ jsx("div", {
609
+ className: "px-2 py-1 text-[11px] font-medium uppercase tracking-wide text-(--theme-muted-text-color)",
610
+ children: t.addFilter
611
+ }),
612
+ searchable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
613
+ className: "sticky top-0 z-[1] -mx-1 mb-1 border-y border-(--theme-border) bg-(--theme-app-bg) px-3 py-1.5",
614
+ children: /* @__PURE__ */ jsx(Menu.Input, {
615
+ placeholder: t.searchFields,
616
+ "aria-label": t.searchFields,
617
+ className: "w-full bg-transparent text-[13px] text-(--theme-app) outline-none placeholder:text-(--theme-placeholder)"
618
+ })
619
+ }), /* @__PURE__ */ jsx(Menu.List, { children: rows })] }) : rows,
620
+ fields.length === 0 && /* @__PURE__ */ jsx("div", {
621
+ className: "px-3 py-1.5 text-[13px] text-(--theme-muted-text-color)",
622
+ children: strings.dropdown.noOptionsFound
623
+ }),
624
+ showClear && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(MenuSeparator, {}), /* @__PURE__ */ jsx(MenuItem, {
625
+ onClick: resetting ? filters.reset : filters.clear,
626
+ children: resetting ? strings.common.reset : strings.common.clear
627
+ })] })
628
+ ]
629
+ })]
630
+ });
631
+ return searchable ? /* @__PURE__ */ jsx(Menu.FilterProvider, {
632
+ filter: null,
633
+ value: query,
634
+ onValueChange: setQuery,
635
+ children: menu
636
+ }) : menu;
637
+ }
638
+ /** The chips that say what narrows a list, under its toolbar: one per filter, each editable in
639
+ * place, then "+ Filter" and Clear (or Reset, where the page has default filters). It shows
640
+ * only while a filter is on or a field is required. At phone width it stops at two lines and
641
+ * a "+n filters" chip opens the rest in a sheet. */
642
+ function ListFilterBar({ filters, trailing, label, className }) {
643
+ const strings = useStrings();
644
+ const t = strings.filters;
645
+ const registerBar = filters?.registerBar;
646
+ useEffect(() => registerBar?.(), [registerBar]);
647
+ const row = useRef(null);
648
+ const [narrow, setNarrow] = useState(false);
649
+ const [width, setWidth] = useState(0);
650
+ const [limit, setLimit] = useState(null);
651
+ const [sheet, setSheet] = useState(false);
652
+ const [focusAt, setFocusAt] = useState(null);
653
+ const chips = filters ? orderedChips(filters) : [];
654
+ const fieldOf = (key) => filters?.fields.find((f) => f.key === key);
655
+ const empty = chips.length === 0;
656
+ const chipKeys = chips.map((c) => c.filter.field).join("\0");
657
+ useLayoutEffect(() => {
658
+ const el = row.current;
659
+ if (!el) return;
660
+ const measure = () => {
661
+ const w = el.getBoundingClientRect().width;
662
+ setWidth(w);
663
+ setNarrow(w > 0 && w < NARROW_PX);
664
+ };
665
+ measure();
666
+ const observer = new ResizeObserver(measure);
667
+ observer.observe(el);
668
+ return () => observer.disconnect();
669
+ }, [empty]);
670
+ useLayoutEffect(() => setLimit(null), [
671
+ width,
672
+ chipKeys,
673
+ narrow
674
+ ]);
675
+ useLayoutEffect(() => {
676
+ const el = row.current;
677
+ if (!el || !narrow) return;
678
+ const tops = /* @__PURE__ */ new Set();
679
+ for (const child of el.children) tops.add(Math.round(child.offsetTop));
680
+ const shown = limit ?? chips.length;
681
+ if (tops.size > 2 && shown > 1) setLimit(shown - 1);
682
+ });
683
+ useEffect(() => {
684
+ if (focusAt === null || !row.current) return;
685
+ (row.current.querySelectorAll("[data-filter-chip]")[focusAt]?.querySelector("button") ?? row.current.querySelector("[data-filter-add]"))?.focus();
686
+ setFocusAt(null);
687
+ }, [focusAt]);
688
+ const visible = narrow && limit !== null ? limit : chips.length;
689
+ const shownChips = chips.filter((c, i) => i < visible || c.filter.field === filters?.draft);
690
+ const overflow = chips.length - shownChips.length;
691
+ const request = filters?.editRequest;
692
+ const hiddenRequest = request && !shownChips.some((c) => c.filter.field === request.field) && chips.some((c) => c.filter.field === request.field);
693
+ useEffect(() => {
694
+ if (hiddenRequest) setSheet(true);
695
+ }, [hiddenRequest]);
696
+ if (!filters || empty) return null;
697
+ const removable = filters.active.some((f) => !fieldOf(f.field)?.required);
698
+ const resetting = filters.hasDefaults;
699
+ const clearButton = (resetting ? !filters.isDefault : removable) && /* @__PURE__ */ jsx("button", {
700
+ type: "button",
701
+ onClick: resetting ? filters.reset : filters.clear,
702
+ className: buttonClasses({
703
+ variant: "ghost",
704
+ size: "sm"
705
+ }),
706
+ children: resetting ? strings.common.reset : strings.common.clear
707
+ });
708
+ const addButton = /* @__PURE__ */ jsx(AddFilterMenu, {
709
+ filters,
710
+ trigger: /* @__PURE__ */ jsxs("button", {
711
+ type: "button",
712
+ "data-filter-add": "",
713
+ className: buttonClasses({
714
+ variant: "ghost",
715
+ size: "sm"
716
+ }),
717
+ children: [/* @__PURE__ */ jsx(AddIcon, { "aria-hidden": "true" }), t.addFilter]
718
+ })
719
+ });
720
+ return /* @__PURE__ */ jsxs("div", {
721
+ ref: row,
722
+ role: "group",
723
+ "aria-label": label ?? t.activeFilters,
724
+ className: cx("flex flex-wrap items-center gap-1.5 py-1.5", className),
725
+ children: [
726
+ shownChips.map(({ filter, field }, i) => /* @__PURE__ */ jsx(FilterChip, {
727
+ filters,
728
+ filter,
729
+ field,
730
+ answers: !sheet,
731
+ onRemoved: () => setFocusAt(i)
732
+ }, filter.field)),
733
+ overflow > 0 && /* @__PURE__ */ jsx("button", {
734
+ type: "button",
735
+ onClick: () => setSheet(true),
736
+ className: "flex h-7 cursor-pointer items-center rounded-md border border-dashed border-(--theme-border) px-2 text-[12px] text-(--theme-app) hover:bg-(--theme-hover) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus)",
737
+ children: t.overflow(overflow)
738
+ }),
739
+ addButton,
740
+ clearButton,
741
+ trailing && /* @__PURE__ */ jsx("div", {
742
+ className: "ml-auto flex items-center gap-1.5",
743
+ children: trailing
744
+ }),
745
+ /* @__PURE__ */ jsx(Drawer, {
746
+ open: sheet,
747
+ onClose: () => setSheet(false),
748
+ title: t.sheetTitle,
749
+ width: 400,
750
+ footer: /* @__PURE__ */ jsxs("div", {
751
+ className: "flex w-full items-center gap-1.5",
752
+ children: [addButton, clearButton]
753
+ }),
754
+ children: /* @__PURE__ */ jsx("div", {
755
+ className: "flex flex-col gap-2",
756
+ children: chips.map(({ filter, field }) => /* @__PURE__ */ jsx(FilterChip, {
757
+ filters,
758
+ filter,
759
+ field,
760
+ stretch: true,
761
+ answers: sheet
762
+ }, filter.field))
763
+ })
764
+ })
765
+ ]
766
+ });
767
+ }
768
+ /** Required and pinned fields first, unset ones reading "Any", then filters in the order added. */
769
+ function orderedChips(filters) {
770
+ const out = [];
771
+ for (const field of filters.fields) if (field.required || field.pinned) out.push({
772
+ field,
773
+ filter: filters.chips.find((c) => c.field === field.key) ?? {
774
+ field: field.key,
775
+ op: fieldOperators(field)[0] ?? "is",
776
+ values: []
777
+ }
778
+ });
779
+ for (const filter of filters.chips) {
780
+ const field = filters.fields.find((f) => f.key === filter.field);
781
+ if (field && !field.required && !field.pinned) out.push({
782
+ filter,
783
+ field
784
+ });
785
+ }
786
+ return out;
787
+ }
788
+ //#endregion
789
+ export { AddFilterMenu, FilterChip, ListFilterBar };