@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
@@ -1,5 +1,6 @@
1
1
  import { usePersistedState, useStrings } from "../components/Provider.js";
2
2
  import { actionsColumnWidth } from "./ListTable.js";
3
+ import { useListFilters } from "./useListFilters.js";
3
4
  import { useCallback, useMemo } from "react";
4
5
  //#region src/list/listView.tsx
5
6
  var PRIORITY_SHOW_CLASS = {
@@ -14,11 +15,16 @@ function columnResponsiveClass(col, i) {
14
15
  if (stacks(col, i)) return PRIORITY_SHOW_CLASS.medium;
15
16
  return col.priority ? PRIORITY_SHOW_CLASS[col.priority] : "";
16
17
  }
18
+ /** A column's classes for its header and every cell: when it shows, and how it aligns. */
19
+ function columnClass(col, i) {
20
+ const show = columnResponsiveClass(col, i);
21
+ return col.align === "end" ? show ? `${show} text-right` : "text-right" : show;
22
+ }
17
23
  var DEFAULT_ORDER = "__default";
18
24
  var FAVORITE_ORDER = "__favorite";
19
25
  /** Persisted grouping + column-visibility + field-filter state for a list page,
20
26
  * driving the shared Display/Filter menus. */
21
- function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], defaultGroupBy, orderBys = [], defaultOrderBy, facets = [], actions = [], actionCount, defaultShowColumnHeaders = true, defaultShowActions = true, isFavorite, isOn, createdAt }) {
27
+ function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], defaultGroupBy, orderBys = [], defaultOrderBy, facets = [], filterFields, defaultFilters, filterStore, rememberFilters = true, actions = [], actionCount, defaultShowColumnHeaders = true, defaultShowActions = true, isFavorite, isOn, createdAt }) {
22
28
  const t = useStrings().list;
23
29
  const tiebreak = orderBys[0]?.compare;
24
30
  const effectiveOrderBys = useMemo(() => {
@@ -59,23 +65,20 @@ function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], def
59
65
  const [alwaysShowActions, setAlwaysShowActions] = usePersistedState(`${storageKey}:alwaysShowActions`, false);
60
66
  const [orderBy, setOrderBy] = usePersistedState(`${storageKey}:orderBy`, defaultOrderByValue);
61
67
  const [orderDir, setOrderDir] = usePersistedState(`${storageKey}:orderDir`, "asc");
62
- const [localFilters, setLocalFilters] = usePersistedState(`${storageKey}:filters`, {});
63
- const [sharedFilters, setSharedFilters] = usePersistedState(`${sharedFiltersKey ?? storageKey}:sharedFilters`, {});
64
- const sharedKeys = useMemo(() => new Set(facets.filter((f) => f.shared).map((f) => f.key)), [facets]);
68
+ const fields = useMemo(() => [...facets.map(facetField), ...filterFields ?? []], [facets, filterFields]);
69
+ const filterState = useListFilters({
70
+ storageKey,
71
+ sharedFiltersKey,
72
+ fields,
73
+ defaultFilters,
74
+ store: filterStore,
75
+ remember: rememberFilters
76
+ });
65
77
  const filters = useMemo(() => {
66
- if (sharedKeys.size === 0) return localFilters;
67
- const merged = {};
68
- for (const [key, values] of Object.entries(localFilters)) if (!sharedKeys.has(key)) merged[key] = values;
69
- for (const key of sharedKeys) {
70
- const values = sharedFilters[key];
71
- if (values?.length) merged[key] = values;
72
- }
73
- return merged;
74
- }, [
75
- localFilters,
76
- sharedFilters,
77
- sharedKeys
78
- ]);
78
+ const out = {};
79
+ for (const f of filterState.active) if (f.op === "is") out[f.field] = [...f.values];
80
+ return out;
81
+ }, [filterState.active]);
79
82
  const pinnedCount = actionCount ?? pinnedKeys.filter((key) => actions.some((action) => action.key === key)).length;
80
83
  const effectiveShowActions = showActions || alwaysShowActions;
81
84
  const actionsWidth = effectiveShowActions ? actionsColumnWidth(pinnedCount) : 0;
@@ -84,35 +87,24 @@ function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], def
84
87
  return Boolean(col?.alwaysVisible) || !hiddenKeys.includes(key);
85
88
  }, [columns, hiddenKeys]);
86
89
  const visibleColumns = useMemo(() => columns.filter((c) => !c.inline && (c.alwaysVisible || !hiddenKeys.includes(c.key))), [columns, hiddenKeys]);
87
- const columnClasses = useMemo(() => visibleColumns.map(columnResponsiveClass), [visibleColumns]);
90
+ const columnClasses = useMemo(() => visibleColumns.map(columnClass), [visibleColumns]);
88
91
  const columnStacks = useMemo(() => visibleColumns.map(stacks), [visibleColumns]);
89
92
  const toggleColumn = useCallback((key) => setHiddenKeys((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]), [setHiddenKeys]);
93
+ const { add, update, remove } = filterState;
90
94
  const setFacetValues = useCallback((key, values) => {
91
- const update = (prev) => {
92
- const next = { ...prev };
93
- if (values.length) next[key] = values;
94
- else delete next[key];
95
- return next;
96
- };
97
- if (sharedKeys.has(key)) setSharedFilters(update);
98
- else setLocalFilters(update);
99
- }, [
100
- setLocalFilters,
101
- setSharedFilters,
102
- sharedKeys
103
- ]);
104
- const clearFilters = useCallback(() => {
105
- setLocalFilters({});
106
- if (sharedKeys.size > 0) setSharedFilters((prev) => {
107
- const next = { ...prev };
108
- for (const key of sharedKeys) delete next[key];
109
- return next;
95
+ if (values.length === 0) remove(key);
96
+ else if (filterState.active.some((f) => f.field === key)) update(key, {
97
+ op: "is",
98
+ values
110
99
  });
100
+ else add(key, "is", values);
111
101
  }, [
112
- setLocalFilters,
113
- setSharedFilters,
114
- sharedKeys
102
+ add,
103
+ update,
104
+ remove,
105
+ filterState.active
115
106
  ]);
107
+ const clearFilters = filterState.clear;
116
108
  const isPinned = useCallback((key) => pinnedKeys.includes(key), [pinnedKeys]);
117
109
  const togglePinned = useCallback((key) => setPinnedKeys((prev) => prev.includes(key) ? prev.filter((k) => k !== key) : [...prev, key]), [setPinnedKeys]);
118
110
  const toggleColumnHeaders = useCallback(() => setShowColumnHeaders((prev) => !prev), [setShowColumnHeaders]);
@@ -206,14 +198,12 @@ function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], def
206
198
  sortBy,
207
199
  applyOrder,
208
200
  facets,
201
+ filterState,
209
202
  filters,
210
203
  setFacetValues,
211
204
  clearFilters,
212
- activeFilterCount: useMemo(() => Object.values(filters).filter((v) => v && v.length > 0).length, [filters]),
213
- applyFilters: useCallback((rows) => rows.filter((row) => facets.every((f) => {
214
- const selected = filters[f.key];
215
- return !selected || selected.length === 0 ? true : f.matches(row, selected);
216
- })), [facets, filters]),
205
+ activeFilterCount: filterState.count,
206
+ applyFilters: useCallback((rows) => filterState.apply(rows), [filterState.apply]),
217
207
  actions,
218
208
  isPinned,
219
209
  togglePinned,
@@ -227,5 +217,17 @@ function useListView({ storageKey, sharedFiltersKey, columns, groupBys = [], def
227
217
  resetDisplay
228
218
  };
229
219
  }
220
+ /** A facet as the field it is shorthand for. */
221
+ function facetField(facet) {
222
+ return {
223
+ key: facet.key,
224
+ label: facet.label,
225
+ kind: "enum",
226
+ options: facet.options,
227
+ searchable: facet.searchable,
228
+ shared: facet.shared,
229
+ matches: (row, filter) => filter.op === "isNot" ? !facet.matches(row, [...filter.values]) : facet.matches(row, [...filter.values])
230
+ };
231
+ }
230
232
  //#endregion
231
233
  export { DEFAULT_ORDER, FAVORITE_ORDER, useListView };
@@ -1,5 +1,5 @@
1
1
  import type { MouseEvent as ReactMouseEvent, ReactNode } from 'react';
2
- import type { OpenMenu } from '../components/ContextMenu.js';
2
+ import { type OpenMenu } from '../components/ContextMenu.js';
3
3
  import type { MenuAction, MenuEntry, MenuSubmenu } from '../components/MenuEntries.js';
4
4
  /** An action on a row, which its menu lists and its pinned buttons show, named by `key`. */
5
5
  export type RowAction = MenuAction & {
@@ -29,8 +29,8 @@ export declare function useRowMenuActive(openMenu: OpenMenu): {
29
29
  active: boolean;
30
30
  openMenu: OpenMenu;
31
31
  };
32
- /** Opens the row's menu on a right-click. Pass a function to build a large
33
- * list's menus only when one opens, not on every render. */
32
+ /** Opens the row's menu on a right-click; Shift+right-click keeps the browser's own. Pass a
33
+ * function to build a large list's menus only when one opens, not on every render. */
34
34
  export declare function rowContextMenuProps(openMenu: OpenMenu, entries: MenuEntry[] | (() => MenuEntry[])): {
35
35
  onContextMenu: (e: ReactMouseEvent) => void;
36
36
  };
@@ -56,8 +56,13 @@ export declare function useCopySubmenu(): CopySubmenu;
56
56
  * stays while its menu is `open`, though the pointer has left the row. A touch
57
57
  * screen has no hover, so there it always shows: the row's menu is otherwise
58
58
  * out of reach. */
59
- export declare function MoreButton({ onOpen, open, visible, label, className, }: {
60
- onOpen: (button: HTMLElement) => void;
59
+ export declare function MoreButton({ onOpen, items, open: openProp, visible: visibleProp, label, className, }: {
60
+ /** Opens a menu of the host's own under the button. */
61
+ onOpen?: ((button: HTMLElement) => void) | undefined;
62
+ /** The menu's rows, opened in the shared menu, which a second press closes. A function
63
+ * builds them only when it opens. Use this or `onOpen`. */
64
+ items?: MenuEntry[] | (() => MenuEntry[]) | undefined;
65
+ /** Whether its menu is open; with `items` the shared menu tells it. */
61
66
  open?: boolean;
62
67
  /** Shows at rest too. */
63
68
  visible?: boolean;
@@ -1,9 +1,10 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { CopyIcon, IdCardIcon, KeyIcon, LinkIcon, MailIcon, MoreIcon, UserIcon } from "../icons.js";
3
+ import { useMenuOpenedBy, useOpenMenu } from "../components/ContextMenu.js";
3
4
  import { useStrings } from "../components/Provider.js";
4
5
  import { IconButton } from "../components/IconButton.js";
6
+ import { useCallback, useRef, useState } from "react";
5
7
  import { jsx } from "react/jsx-runtime";
6
- import { useCallback, useState } from "react";
7
8
  //#region src/list/rowMenu.tsx
8
9
  /** Assembles a row's context menu in the shared order: leading link(s) → middle
9
10
  * actions → extra items → Copy submenu → trailing (destructive) actions last. */
@@ -31,21 +32,23 @@ function useRowMenuActive(openMenu) {
31
32
  return {
32
33
  active,
33
34
  openMenu: useCallback((at, items, options) => {
34
- setActive(true);
35
- openMenu(at, items, {
35
+ const opened = openMenu(at, items, {
36
36
  ...options,
37
37
  onClose: () => {
38
38
  setActive(false);
39
39
  options?.onClose?.();
40
40
  }
41
41
  });
42
+ if (opened !== false) setActive(true);
43
+ return opened;
42
44
  }, [openMenu])
43
45
  };
44
46
  }
45
- /** Opens the row's menu on a right-click. Pass a function to build a large
46
- * list's menus only when one opens, not on every render. */
47
+ /** Opens the row's menu on a right-click; Shift+right-click keeps the browser's own. Pass a
48
+ * function to build a large list's menus only when one opens, not on every render. */
47
49
  function rowContextMenuProps(openMenu, entries) {
48
50
  return { onContextMenu: (e) => {
51
+ if (e.shiftKey) return;
49
52
  const items = typeof entries === "function" ? entries() : entries;
50
53
  e.preventDefault();
51
54
  if (items.length > 0) e.stopPropagation();
@@ -111,9 +114,15 @@ function useCopySubmenu() {
111
114
  * stays while its menu is `open`, though the pointer has left the row. A touch
112
115
  * screen has no hover, so there it always shows: the row's menu is otherwise
113
116
  * out of reach. */
114
- function MoreButton({ onOpen, open = false, visible = open, label, className }) {
117
+ function MoreButton({ onOpen, items, open: openProp, visible: visibleProp, label, className }) {
115
118
  const { list: t } = useStrings();
119
+ const openMenu = useOpenMenu();
120
+ const openedBy = useMenuOpenedBy();
121
+ const button = useRef(null);
122
+ const open = openProp ?? (items !== void 0 && openedBy !== null && openedBy === button.current);
123
+ const visible = visibleProp ?? open;
116
124
  return /* @__PURE__ */ jsx(IconButton, {
125
+ ref: button,
117
126
  label: label ?? t.moreActions,
118
127
  icon: /* @__PURE__ */ jsx(MoreIcon, {}),
119
128
  "aria-haspopup": "menu",
@@ -121,7 +130,8 @@ function MoreButton({ onOpen, open = false, visible = open, label, className })
121
130
  "data-popup-open": open || void 0,
122
131
  onClick: (e) => {
123
132
  e.stopPropagation();
124
- onOpen(e.currentTarget);
133
+ if (items !== void 0) openMenu(e.currentTarget, typeof items === "function" ? items() : items);
134
+ else onOpen?.(e.currentTarget);
125
135
  },
126
136
  className: cx("focus-visible:opacity-100", visible ? "opacity-100" : "opacity-0 group-hover:opacity-100 pointer-coarse:opacity-100", className)
127
137
  });
@@ -0,0 +1,68 @@
1
+ import { type ActiveFilter, type FilterField, type FilterOperator } from './filterModel.js';
2
+ /** Where filters live besides what the list remembers, such as the page's URL. */
3
+ export interface FilterStore {
4
+ /** The filters it holds, or `null` when it holds none, such as a URL with no filter params,
5
+ * so the remembered filters apply. */
6
+ value: readonly ActiveFilter[] | null;
7
+ onChange: (next: ActiveFilter[]) => void;
8
+ }
9
+ export interface ListFiltersConfig<T> {
10
+ /** Remembers the filters under `${storageKey}:filters`, through the host's persisted state
11
+ * (the browser's storage unless the host binds its own, such as an account preference). */
12
+ storageKey: string;
13
+ /** Fields marked `shared` keep one value under `${sharedFiltersKey}:sharedFilters`. */
14
+ sharedFiltersKey?: string | undefined;
15
+ fields: readonly FilterField<T>[];
16
+ /** What the page shows before the reader changes anything, and what Reset returns to. */
17
+ defaultFilters?: readonly ActiveFilter[] | undefined;
18
+ /** Filters held outside the list, such as the URL. Its filters win while it has any, and
19
+ * every change goes to it and to what the list remembers. */
20
+ store?: FilterStore | undefined;
21
+ /** False leaves remembering to the store alone. */
22
+ remember?: boolean | undefined;
23
+ }
24
+ /** A request for a chip to open one of its pickers, which the chip clears once it has. */
25
+ export interface FilterEditRequest {
26
+ field: string;
27
+ part: 'value' | 'field' | 'operator';
28
+ id: number;
29
+ }
30
+ /** A list's filters, the state behind the chip row and the Filter menu. */
31
+ export interface ListFilters {
32
+ fields: readonly FilterField<any>[];
33
+ /** The filters in effect, in the order they were added. */
34
+ active: readonly ActiveFilter[];
35
+ /** The chips to show: the filters in effect, then one being added that has no value yet. */
36
+ chips: readonly ActiveFilter[];
37
+ /** The field of a chip just added and still waiting for its value. */
38
+ draft: string | null;
39
+ /** Starts a filter: a chip for the field appears and its value picker opens. A field
40
+ * already in use opens its chip's picker instead. */
41
+ begin: (field: string) => void;
42
+ add: (field: string, op?: FilterOperator, values?: readonly string[]) => void;
43
+ update: (field: string, patch: Partial<Omit<ActiveFilter, 'field'>>) => void;
44
+ /** Swaps a chip's field for another, keeping its values when the kinds match. */
45
+ replaceField: (field: string, next: string) => void;
46
+ remove: (field: string) => void;
47
+ /** Drops the chip being added, if it never got a value. */
48
+ discardDraft: () => void;
49
+ /** Empties every filter but the required ones. */
50
+ clear: () => void;
51
+ /** Returns to the page's default filters. */
52
+ reset: () => void;
53
+ hasDefaults: boolean;
54
+ isDefault: boolean;
55
+ /** How many filters narrow the list. */
56
+ count: number;
57
+ apply: <R>(rows: readonly R[]) => R[];
58
+ editRequest: FilterEditRequest | null;
59
+ requestEdit: (field: string, part?: FilterEditRequest['part']) => void;
60
+ clearEditRequest: (id: number) => void;
61
+ /** Set by a mounted chip row, so the Filter menu knows a new chip has somewhere to show. */
62
+ barMounted: boolean;
63
+ registerBar: () => () => void;
64
+ }
65
+ /** A list's active filters: which, in what order, where they are remembered and how rows
66
+ * match them. `useListView` builds one from its facets and fields; a page not on it, or
67
+ * filtering on the server, can call this alone. */
68
+ export declare function useListFilters<T>({ storageKey, sharedFiltersKey, fields, defaultFilters, store, remember, }: ListFiltersConfig<T>): ListFilters;
@@ -0,0 +1,252 @@
1
+ import { usePersistedState } from "../components/Provider.js";
2
+ import { applyFilterList, fieldOperators, fromStored, isComplete, sameFilters, toStored } from "./filterModel.js";
3
+ import { useCallback, useMemo, useRef, useState } from "react";
4
+ //#region src/list/useListFilters.ts
5
+ var NONE = [];
6
+ /** A list's active filters: which, in what order, where they are remembered and how rows
7
+ * match them. `useListView` builds one from its facets and fields; a page not on it, or
8
+ * filtering on the server, can call this alone. */
9
+ function useListFilters({ storageKey, sharedFiltersKey, fields, defaultFilters = NONE, store, remember = true }) {
10
+ const sharedKeys = useMemo(() => new Set(fields.filter((f) => f.shared).map((f) => f.key)), [fields]);
11
+ const defaults = useMemo(() => defaultFilters.filter((f) => !sharedKeys.has(f.field)), [defaultFilters, sharedKeys]);
12
+ const sharedDefaults = useMemo(() => defaultFilters.filter((f) => sharedKeys.has(f.field)), [defaultFilters, sharedKeys]);
13
+ const [localStored, setLocalStored] = usePersistedState(`${storageKey}:filters`, toStored(defaults));
14
+ const [sharedStored, setSharedStored] = usePersistedState(`${sharedFiltersKey ?? storageKey}:sharedFilters`, toStored(sharedDefaults));
15
+ const known = useMemo(() => new Set(fields.map((f) => f.key)), [fields]);
16
+ const remembered = useMemo(() => {
17
+ const local = fromStored(localStored).filter((f) => !sharedKeys.has(f.field));
18
+ const shared = fromStored(sharedStored).filter((f) => sharedKeys.has(f.field));
19
+ return [...local, ...shared];
20
+ }, [
21
+ localStored,
22
+ sharedStored,
23
+ sharedKeys
24
+ ]);
25
+ const source = store?.value ?? (remember ? remembered : defaultFilters);
26
+ const active = useMemo(() => source.filter((f) => known.has(f.field) && isComplete(f)), [source, known]);
27
+ const latest = useRef(active);
28
+ latest.current = active;
29
+ const write = useCallback((next) => {
30
+ latest.current = next;
31
+ if (remember) {
32
+ setLocalStored(toStored(next.filter((f) => !sharedKeys.has(f.field))));
33
+ if (sharedKeys.size > 0) setSharedStored((prev) => {
34
+ const kept = {};
35
+ for (const [key, value] of Object.entries(prev ?? {})) if (!sharedKeys.has(key)) kept[key] = value;
36
+ return {
37
+ ...kept,
38
+ ...toStored(next.filter((f) => sharedKeys.has(f.field)))
39
+ };
40
+ });
41
+ }
42
+ store?.onChange(next);
43
+ }, [
44
+ remember,
45
+ setLocalStored,
46
+ setSharedStored,
47
+ sharedKeys,
48
+ store
49
+ ]);
50
+ const [draftState, setDraftStateRaw] = useState(null);
51
+ const draftRef = useRef(draftState);
52
+ const setDraft = useCallback((filter, index) => {
53
+ const next = filter ? {
54
+ filter,
55
+ index: index ?? Number.POSITIVE_INFINITY
56
+ } : null;
57
+ draftRef.current = next;
58
+ setDraftStateRaw(next);
59
+ }, []);
60
+ const draft = draftState && !active.some((f) => f.field === draftState.filter.field) ? draftState : null;
61
+ const [editRequest, setEditRequest] = useState(null);
62
+ const nextId = useRef(0);
63
+ const requestEdit = useCallback((field, part = "value") => {
64
+ nextId.current += 1;
65
+ setEditRequest({
66
+ field,
67
+ part,
68
+ id: nextId.current
69
+ });
70
+ }, []);
71
+ const clearEditRequest = useCallback((id) => setEditRequest((prev) => prev?.id === id ? null : prev), []);
72
+ const fieldOf = useCallback((key) => fields.find((f) => f.key === key), [fields]);
73
+ const add = useCallback((key, op, values = []) => {
74
+ const field = fieldOf(key);
75
+ if (!field) return;
76
+ const filter = {
77
+ field: key,
78
+ op: op ?? fieldOperators(field)[0] ?? "is",
79
+ values: field.kind === "boolean" && values.length === 0 ? ["true"] : values
80
+ };
81
+ const rest = latest.current.filter((f) => f.field !== key);
82
+ if (!isComplete(filter)) {
83
+ if (rest.length !== latest.current.length) write(rest);
84
+ setDraft(filter);
85
+ return;
86
+ }
87
+ if (draftRef.current?.filter.field === key) setDraft(null);
88
+ write([...rest, filter]);
89
+ }, [
90
+ fieldOf,
91
+ write,
92
+ setDraft
93
+ ]);
94
+ const update = useCallback((key, patch) => {
95
+ const index = latest.current.findIndex((f) => f.field === key);
96
+ const current = latest.current[index];
97
+ if (!current) {
98
+ const held = draftRef.current;
99
+ if (held?.filter.field !== key) {
100
+ const field = fieldOf(key);
101
+ const next = {
102
+ field: key,
103
+ op: patch.op ?? (field ? fieldOperators(field)[0] : void 0) ?? "is",
104
+ values: patch.values ?? []
105
+ };
106
+ if (field && isComplete(next)) write([...latest.current, next]);
107
+ return;
108
+ }
109
+ const next = {
110
+ ...held.filter,
111
+ ...patch
112
+ };
113
+ if (isComplete(next)) {
114
+ setDraft(null);
115
+ const at = Math.min(held.index, latest.current.length);
116
+ write([
117
+ ...latest.current.slice(0, at),
118
+ next,
119
+ ...latest.current.slice(at)
120
+ ]);
121
+ } else setDraft(next, held.index);
122
+ return;
123
+ }
124
+ const next = {
125
+ ...current,
126
+ ...patch
127
+ };
128
+ if (isComplete(next)) {
129
+ write(latest.current.map((f) => f.field === key ? next : f));
130
+ return;
131
+ }
132
+ const field = fieldOf(key);
133
+ if (!field?.required && !field?.pinned) setDraft(next, index);
134
+ write(latest.current.filter((f) => f.field !== key));
135
+ }, [
136
+ fieldOf,
137
+ write,
138
+ setDraft
139
+ ]);
140
+ const remove = useCallback((key) => {
141
+ if (draftRef.current?.filter.field === key) setDraft(null);
142
+ if (latest.current.some((f) => f.field === key)) write(latest.current.filter((f) => f.field !== key));
143
+ }, [write, setDraft]);
144
+ const replaceField = useCallback((key, nextKey) => {
145
+ const from = fieldOf(key);
146
+ const to = fieldOf(nextKey);
147
+ const index = latest.current.findIndex((f) => f.field === key);
148
+ const held = draftRef.current?.filter.field === key ? draftRef.current : void 0;
149
+ const current = latest.current[index] ?? held?.filter;
150
+ if (!from || !to || !current || key === nextKey) return;
151
+ const ops = fieldOperators(to);
152
+ const op = ops.includes(current.op) ? current.op : ops[0] ?? "is";
153
+ const listed = from.kind === "enum" || from.kind === "people";
154
+ const carries = from.kind === to.kind && (!listed || current.values.every((v) => to.options?.some((o) => o.value === v) ?? false));
155
+ const next = {
156
+ field: nextKey,
157
+ op,
158
+ values: to.kind === "boolean" ? ["true"] : carries ? current.values : []
159
+ };
160
+ const rest = latest.current.filter((f) => f.field !== nextKey);
161
+ const at = index >= 0 ? rest.findIndex((f) => f.field === key) : held?.index ?? rest.length;
162
+ const without = rest.filter((f) => f.field !== key);
163
+ if (isComplete(next)) {
164
+ setDraft(null);
165
+ const pos = Math.min(Math.max(at, 0), without.length);
166
+ write([
167
+ ...without.slice(0, pos),
168
+ next,
169
+ ...without.slice(pos)
170
+ ]);
171
+ } else {
172
+ if (without.length !== latest.current.length) write(without);
173
+ setDraft(next, at);
174
+ requestEdit(nextKey);
175
+ }
176
+ }, [
177
+ fieldOf,
178
+ write,
179
+ setDraft,
180
+ requestEdit
181
+ ]);
182
+ const begin = useCallback((key) => {
183
+ const field = fieldOf(key);
184
+ if (!field) return;
185
+ if (latest.current.some((f) => f.field === key) || field.required || field.pinned) {
186
+ requestEdit(key);
187
+ return;
188
+ }
189
+ add(key);
190
+ if (field.kind !== "boolean") requestEdit(key);
191
+ }, [
192
+ fieldOf,
193
+ add,
194
+ requestEdit
195
+ ]);
196
+ const discardDraft = useCallback(() => setDraft(null), [setDraft]);
197
+ const clear = useCallback(() => {
198
+ setDraft(null);
199
+ write(latest.current.filter((f) => fieldOf(f.field)?.required));
200
+ }, [
201
+ fieldOf,
202
+ write,
203
+ setDraft
204
+ ]);
205
+ const reset = useCallback(() => {
206
+ setDraft(null);
207
+ write([...defaultFilters]);
208
+ }, [
209
+ defaultFilters,
210
+ write,
211
+ setDraft
212
+ ]);
213
+ const [bars, setBars] = useState(0);
214
+ const registerBar = useCallback(() => {
215
+ setBars((n) => n + 1);
216
+ return () => setBars((n) => n - 1);
217
+ }, []);
218
+ const apply = useCallback((rows) => applyFilterList(rows, active, fields), [active, fields]);
219
+ return {
220
+ fields,
221
+ active,
222
+ chips: useMemo(() => {
223
+ if (!draft) return active;
224
+ const at = Math.min(draft.index, active.length);
225
+ return [
226
+ ...active.slice(0, at),
227
+ draft.filter,
228
+ ...active.slice(at)
229
+ ];
230
+ }, [active, draft]),
231
+ draft: draft?.filter.field ?? null,
232
+ begin,
233
+ add,
234
+ update,
235
+ replaceField,
236
+ remove,
237
+ discardDraft,
238
+ clear,
239
+ reset,
240
+ hasDefaults: defaultFilters.length > 0,
241
+ isDefault: sameFilters(active, defaultFilters),
242
+ count: active.length,
243
+ apply,
244
+ editRequest,
245
+ requestEdit,
246
+ clearEditRequest,
247
+ barMounted: bars > 0,
248
+ registerBar
249
+ };
250
+ }
251
+ //#endregion
252
+ export { useListFilters };
@@ -11,8 +11,8 @@ import { InfoTip } from "../components/InfoTip.js";
11
11
  import { DatePicker } from "../components/DatePicker.js";
12
12
  import { SearchInput } from "../components/SearchInput.js";
13
13
  import { highlightSegments, parseAnsiSegments, plainSegments } from "./segments.js";
14
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
- import { Fragment as Fragment$1, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
14
+ import { Fragment, memo, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
16
  import { useVirtualizer } from "@tanstack/react-virtual";
17
17
  import { DateTime } from "luxon";
18
18
  //#region src/logviewer/LogViewer.tsx
@@ -24,7 +24,7 @@ function LogSegments({ segments }) {
24
24
  }, i) : segment.className ? /* @__PURE__ */ jsx("span", {
25
25
  className: segment.className,
26
26
  children: segment.content
27
- }, i) : /* @__PURE__ */ jsx(Fragment$1, { children: segment.content }, i));
27
+ }, i) : /* @__PURE__ */ jsx(Fragment, { children: segment.content }, i));
28
28
  }
29
29
  var ANNOTATION_TONES = {
30
30
  error: {
@@ -419,7 +419,7 @@ function LogViewer({ log, height = "unset", width, downloadName = "log.txt", rou
419
419
  /* @__PURE__ */ jsxs("div", {
420
420
  className: "flex flex-auto flex-wrap gap-2",
421
421
  children: [
422
- !inline && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Button, {
422
+ !inline && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
423
423
  variant: "secondary",
424
424
  size: "sm",
425
425
  icon: /* @__PURE__ */ jsx(ArrowUpIcon, {}),