@parallelworks/ui 0.90.0 → 0.92.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.
- package/README.md +2 -1
- package/dist/components/Table.d.ts +7 -0
- package/dist/components/Table.js +10 -3
- package/dist/list/ListFilterBar.d.ts +6 -1
- package/dist/list/ListFilterBar.js +109 -3
- package/dist/list/ListPage.d.ts +23 -2
- package/dist/list/ListPage.js +95 -30
- package/dist/list/ListToolbar.d.ts +13 -7
- package/dist/list/ListToolbar.js +28 -17
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +3 -3
- package/dist/list/listSearch.d.ts +18 -2
- package/dist/list/listSearch.js +24 -8
- package/dist/list/listView.d.ts +10 -0
- package/dist/list/listView.js +2 -0
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
88
88
|
| Nothing to show yet, or nothing matches | `EmptyState` |
|
|
89
89
|
| A page, card or section that couldn't show what it was asked for, such as a 404 | `ErrorPage`, its words chosen by the status |
|
|
90
90
|
| A list page's toolbar and its loading, error, empty and no-match states | `ListToolbar` in `ListPage` with `total`, `matches`, `empty` and `noMatches` (from `./list`) |
|
|
91
|
+
| How many rows a list has, or how many of them match ("2 of 7 tokens") | `count` on `ListPage`, with its `total` and `matches`: it shows under the last row, only while there are rows. A `ListToolbar`'s `count` moves there too, and the toolbar then sits at the end of the filter chips' first line (`ListFilterBar`'s `end`), or on a line alone while no chip shows. `ListCount` placed by hand is deprecated |
|
|
91
92
|
| A text field | `Input`, `Textarea`, or a control framed by `Field`, whose `actions` hold buttons that act on it |
|
|
92
93
|
| A number, a date, a color | `NumberInput`, `DatePicker`, `ColorInput` |
|
|
93
94
|
| A one-time code, such as a sign-in check | `OneTimeCodeInput` |
|
|
@@ -119,7 +120,7 @@ Each need has one component. Reach for it before styling markup by hand.
|
|
|
119
120
|
| A message set into the page: guidance, a warning, an error | `Callout`; `variant="bar"` across a surface under its header |
|
|
120
121
|
| A passing notice, or the progress of work in the background | `toast` |
|
|
121
122
|
| How full something is, how far a task has come | `Meter`, `ProgressBar` |
|
|
122
|
-
| Pages of a long list | `Pagination` |
|
|
123
|
+
| Pages of a long list | `Pagination`; in a `ListPage`, as its `pager`, on the count's line under the last row, where a "Load more" button goes too |
|
|
123
124
|
| A grouped page of settings | `SettingsCard` with `SettingRow`s |
|
|
124
125
|
| A person or an agent | `Avatar`, colored by name without a picture, or by kind; `AvatarGroup` for an overlapping stack with a "+N" chip |
|
|
125
126
|
| A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
|
|
@@ -12,10 +12,17 @@ export interface CellPlacement {
|
|
|
12
12
|
className?: string | undefined;
|
|
13
13
|
after?: React.ReactNode;
|
|
14
14
|
stacked?: boolean | undefined;
|
|
15
|
+
/** A stacked value joined to the one before it on its line, after a dot. */
|
|
16
|
+
joined?: boolean | undefined;
|
|
15
17
|
}
|
|
16
18
|
/** A stacked column's value on the line under a row's name: it flows inline with the others and
|
|
17
19
|
* wraps between them, never inside a value, which truncates if it is wider than the line. */
|
|
18
20
|
export declare const STACKED_ITEM = "flex min-w-0 max-w-full items-center whitespace-nowrap [&>*]:min-w-0 [&>*]:truncate";
|
|
21
|
+
/** A joined stacked value's dot, part of the value so a value that renders nothing leaves none:
|
|
22
|
+
* 6px either side of it, where the line keeps 12px between values. */
|
|
23
|
+
export declare const STACKED_JOINED = "-ml-1.5 before:pr-1.5 before:content-['\u00B7'] before:text-(--theme-muted-text-color)";
|
|
24
|
+
/** Whether a cell's content shows anything: an empty string, like a missing value, doesn't. */
|
|
25
|
+
export declare function hasContent(children: React.ReactNode): boolean;
|
|
19
26
|
/** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
|
|
20
27
|
export declare const CellPlacementContext: React.Context<CellPlacement | null>;
|
|
21
28
|
interface ItemProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
|
package/dist/components/Table.js
CHANGED
|
@@ -26,13 +26,20 @@ var cellX = "px-3";
|
|
|
26
26
|
/** A stacked column's value on the line under a row's name: it flows inline with the others and
|
|
27
27
|
* wraps between them, never inside a value, which truncates if it is wider than the line. */
|
|
28
28
|
var STACKED_ITEM = "flex min-w-0 max-w-full items-center whitespace-nowrap [&>*]:min-w-0 [&>*]:truncate";
|
|
29
|
+
/** A joined stacked value's dot, part of the value so a value that renders nothing leaves none:
|
|
30
|
+
* 6px either side of it, where the line keeps 12px between values. */
|
|
31
|
+
var STACKED_JOINED = "-ml-1.5 before:pr-1.5 before:content-['·'] before:text-(--theme-muted-text-color)";
|
|
32
|
+
/** Whether a cell's content shows anything: an empty string, like a missing value, doesn't. */
|
|
33
|
+
function hasContent(children) {
|
|
34
|
+
return React.Children.toArray(children).some((child) => child !== "");
|
|
35
|
+
}
|
|
29
36
|
/** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
|
|
30
37
|
var CellPlacementContext = createContext(null);
|
|
31
38
|
function Item({ wrap = false, children, className, ...tdProps }) {
|
|
32
39
|
const density = useContext(DensityContext);
|
|
33
40
|
const placed = useContext(CellPlacementContext);
|
|
34
|
-
if (placed?.stacked) return
|
|
35
|
-
className: STACKED_ITEM,
|
|
41
|
+
if (placed?.stacked) return hasContent(children) ? /* @__PURE__ */ jsx("div", {
|
|
42
|
+
className: cx(STACKED_ITEM, placed.joined && "-ml-1.5 before:pr-1.5 before:content-['·'] before:text-(--theme-muted-text-color)"),
|
|
36
43
|
"data-stacked": true,
|
|
37
44
|
children
|
|
38
45
|
}) : null;
|
|
@@ -121,4 +128,4 @@ function Table({ density = "comfortable", columns, caps = true, flush = false, c
|
|
|
121
128
|
Table.Header = Header;
|
|
122
129
|
Table.Item = Item;
|
|
123
130
|
//#endregion
|
|
124
|
-
export { CellPlacementContext, STACKED_ITEM, Table };
|
|
131
|
+
export { CellPlacementContext, STACKED_ITEM, STACKED_JOINED, Table, hasContent };
|
|
@@ -30,6 +30,11 @@ export interface ListFilterBarProps {
|
|
|
30
30
|
filters: ListFilters | undefined;
|
|
31
31
|
/** At the row's end, such as Save view or Update view. */
|
|
32
32
|
trailing?: ReactNode;
|
|
33
|
+
/** The list's own controls, such as its toolbar's menus and search, held at the end of the
|
|
34
|
+
* first line while the chips wrap beside them, so they never take a line of their own. With
|
|
35
|
+
* no chip to show, they take the row alone. `ListPage` puts its toolbar here once the count
|
|
36
|
+
* has moved under the rows. */
|
|
37
|
+
end?: ReactNode;
|
|
33
38
|
/** Names the row; defaults to the provider's `filters.activeFilters`. */
|
|
34
39
|
label?: string | undefined;
|
|
35
40
|
className?: string | undefined;
|
|
@@ -42,5 +47,5 @@ export interface ListFilterBarProps {
|
|
|
42
47
|
* The list's quick filters show above the chips as toggles, always, setting the same filters
|
|
43
48
|
* the chips edit. At phone width each keeps to one line, and "+n more" opens the rest in the
|
|
44
49
|
* same sheet. */
|
|
45
|
-
export declare function ListFilterBar({
|
|
50
|
+
export declare function ListFilterBar({ end, trailing, ...props }: ListFilterBarProps): import("react").JSX.Element;
|
|
46
51
|
export {};
|
|
@@ -11,6 +11,7 @@ import { Calendar } from "../components/DatePicker.js";
|
|
|
11
11
|
import { Drawer } from "../components/Drawer.js";
|
|
12
12
|
import { ToggleGroup } from "../components/ToggleGroup.js";
|
|
13
13
|
import { DATE_PRESETS, fieldOperators, isDatePreset, isMultiple, operatorCount, parseDay, toDay } from "./filterModel.js";
|
|
14
|
+
import { ListToolbarInlineContext } from "./listSearch.js";
|
|
14
15
|
import { Menu } from "@base-ui/react/menu";
|
|
15
16
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
16
17
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -155,6 +156,7 @@ function FilterChip({ filters, filter, field, stretch = false, answers, onRemove
|
|
|
155
156
|
}, i))
|
|
156
157
|
}),
|
|
157
158
|
/* @__PURE__ */ jsx("span", {
|
|
159
|
+
"data-filter-value": "",
|
|
158
160
|
className: cx("min-w-0 truncate", !stretch && "max-w-40", unset && "text-(--theme-muted-text-color)"),
|
|
159
161
|
children: valueText
|
|
160
162
|
}),
|
|
@@ -802,7 +804,95 @@ function QuickRule() {
|
|
|
802
804
|
* The list's quick filters show above the chips as toggles, always, setting the same filters
|
|
803
805
|
* the chips edit. At phone width each keeps to one line, and "+n more" opens the rest in the
|
|
804
806
|
* same sheet. */
|
|
805
|
-
function ListFilterBar({
|
|
807
|
+
function ListFilterBar({ end, trailing, ...props }) {
|
|
808
|
+
if (end === void 0 || end === null || end === false) return /* @__PURE__ */ jsx(FilterRow, {
|
|
809
|
+
trailing,
|
|
810
|
+
...props
|
|
811
|
+
});
|
|
812
|
+
return /* @__PURE__ */ jsx(SharedRow, {
|
|
813
|
+
end,
|
|
814
|
+
trailing,
|
|
815
|
+
...props
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
var VALUE_MIN_PX = 88;
|
|
819
|
+
/** The chips with the list's controls at their first line's end. */
|
|
820
|
+
function SharedRow({ end, trailing, ...props }) {
|
|
821
|
+
const box = useRef(null);
|
|
822
|
+
const controls = useRef(null);
|
|
823
|
+
const endBox = useRef(null);
|
|
824
|
+
const [width, setWidth] = useState(0);
|
|
825
|
+
const [trailingLast, setTrailingLast] = useState(false);
|
|
826
|
+
const [reach, setReach] = useState(0);
|
|
827
|
+
const [noReach, setNoReach] = useState(false);
|
|
828
|
+
const chipKeys = props.filters?.chips.map((c) => c.field).join("\0") ?? "";
|
|
829
|
+
const shows = props.filters !== void 0 && rowShows(props.filters);
|
|
830
|
+
useLayoutEffect(() => {
|
|
831
|
+
const el = box.current;
|
|
832
|
+
if (!el) return;
|
|
833
|
+
const measure = () => setWidth(el.getBoundingClientRect().width);
|
|
834
|
+
measure();
|
|
835
|
+
const observer = new ResizeObserver(measure);
|
|
836
|
+
observer.observe(el);
|
|
837
|
+
return () => observer.disconnect();
|
|
838
|
+
}, []);
|
|
839
|
+
useLayoutEffect(() => {
|
|
840
|
+
setTrailingLast(false);
|
|
841
|
+
setNoReach(false);
|
|
842
|
+
}, [width, chipKeys]);
|
|
843
|
+
useLayoutEffect(() => {
|
|
844
|
+
const el = box.current;
|
|
845
|
+
if (!el || !trailing || !shows) return;
|
|
846
|
+
const chips = Array.from(el.querySelectorAll("[data-filter-chip]"));
|
|
847
|
+
const firstTop = chips[0]?.offsetTop;
|
|
848
|
+
if (trailingLast) {
|
|
849
|
+
const moved = el.querySelector("[data-filter-trailing]");
|
|
850
|
+
if (!noReach && moved && moved.offsetTop === firstTop) {
|
|
851
|
+
setNoReach(true);
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const next = noReach ? 0 : (controls.current?.getBoundingClientRect().width ?? 0) + GAP_PX;
|
|
855
|
+
if (Math.abs(next - reach) > .5) setReach(next);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
if (chips.some((chip) => {
|
|
859
|
+
const value = chip.querySelector("[data-filter-value]");
|
|
860
|
+
return chip.offsetTop === firstTop && value !== null && value.clientWidth < Math.min(value.scrollWidth, VALUE_MIN_PX);
|
|
861
|
+
})) {
|
|
862
|
+
setReach((endBox.current?.getBoundingClientRect().width ?? 0) + GAP_PX);
|
|
863
|
+
setTrailingLast(true);
|
|
864
|
+
}
|
|
865
|
+
});
|
|
866
|
+
return /* @__PURE__ */ jsx(ListToolbarInlineContext, {
|
|
867
|
+
value: true,
|
|
868
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
869
|
+
ref: box,
|
|
870
|
+
className: "flex min-h-10 items-start gap-3",
|
|
871
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
872
|
+
className: "min-w-0 flex-1 empty:hidden",
|
|
873
|
+
children: /* @__PURE__ */ jsx(FilterRow, {
|
|
874
|
+
...props,
|
|
875
|
+
trailing: trailingLast ? trailing : void 0,
|
|
876
|
+
trailingReach: trailingLast ? reach : 0
|
|
877
|
+
})
|
|
878
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
879
|
+
ref: controls,
|
|
880
|
+
className: "ml-auto flex min-h-10 shrink-0 items-center gap-1.5",
|
|
881
|
+
children: [shows && !trailingLast && trailing, /* @__PURE__ */ jsx("div", {
|
|
882
|
+
ref: endBox,
|
|
883
|
+
className: "flex items-center",
|
|
884
|
+
children: end
|
|
885
|
+
})]
|
|
886
|
+
})]
|
|
887
|
+
})
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
var GAP_PX = 12;
|
|
891
|
+
/** Whether the row has anything to show: a chip, or a quick filter's toggles. */
|
|
892
|
+
function rowShows(filters) {
|
|
893
|
+
return orderedChips(filters).length > 0 || (filters.quick ?? []).some((q) => filters.fields.some((f) => f.key === q.field));
|
|
894
|
+
}
|
|
895
|
+
function FilterRow({ filters, trailing, trailingReach = 0, label, className }) {
|
|
806
896
|
const strings = useStrings();
|
|
807
897
|
const t = strings.filters;
|
|
808
898
|
const registerBar = filters?.registerBar;
|
|
@@ -813,6 +903,7 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
813
903
|
const [width, setWidth] = useState(0);
|
|
814
904
|
const [limit, setLimit] = useState(null);
|
|
815
905
|
const [clearFolded, setClearFolded] = useState(false);
|
|
906
|
+
const [addFolded, setAddFolded] = useState(false);
|
|
816
907
|
const [sheet, setSheet] = useState(false);
|
|
817
908
|
const [focusAt, setFocusAt] = useState(null);
|
|
818
909
|
const chips = filters ? orderedChips(filters) : [];
|
|
@@ -844,6 +935,7 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
844
935
|
useLayoutEffect(() => {
|
|
845
936
|
setLimit(null);
|
|
846
937
|
setClearFolded(false);
|
|
938
|
+
setAddFolded(false);
|
|
847
939
|
}, [
|
|
848
940
|
width,
|
|
849
941
|
chipKeys,
|
|
@@ -860,6 +952,18 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
860
952
|
return;
|
|
861
953
|
}
|
|
862
954
|
const add = el.querySelector("[data-filter-add]");
|
|
955
|
+
const addItem = add ? Array.from(el.children).find((child) => child.contains(add)) : void 0;
|
|
956
|
+
const before = addItem?.previousElementSibling;
|
|
957
|
+
if (addItem instanceof HTMLElement && before instanceof HTMLElement && Math.round(addItem.offsetTop) > Math.round(before.offsetTop)) {
|
|
958
|
+
if (chips.length > shown) {
|
|
959
|
+
setAddFolded(true);
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
if (shown > fixedChips) {
|
|
963
|
+
setLimit(shown - 1);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
863
967
|
const clear = el.querySelector("[data-filter-clear]");
|
|
864
968
|
if (add && clear && Math.round(clear.offsetTop) > Math.round(add.offsetTop)) setClearFolded(true);
|
|
865
969
|
});
|
|
@@ -950,10 +1054,12 @@ function ListFilterBar({ filters, trailing, label, className }) {
|
|
|
950
1054
|
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)",
|
|
951
1055
|
children: t.overflow(overflow)
|
|
952
1056
|
}),
|
|
953
|
-
addButton(narrow),
|
|
954
|
-
!(narrow && clearFolded) && clearButton,
|
|
1057
|
+
!(narrow && addFolded) && addButton(narrow),
|
|
1058
|
+
!(narrow && (clearFolded || addFolded)) && clearButton,
|
|
955
1059
|
trailing && /* @__PURE__ */ jsx("div", {
|
|
1060
|
+
"data-filter-trailing": "",
|
|
956
1061
|
className: "ml-auto flex items-center gap-1.5",
|
|
1062
|
+
style: trailingReach ? { marginRight: -trailingReach } : void 0,
|
|
957
1063
|
children: trailing
|
|
958
1064
|
}),
|
|
959
1065
|
!hasQuick && drawer
|
package/dist/list/ListPage.d.ts
CHANGED
|
@@ -14,7 +14,10 @@ export interface ListPageProps {
|
|
|
14
14
|
bleed?: boolean | undefined;
|
|
15
15
|
/** The list's search, filters and view controls, above the rows, such as a
|
|
16
16
|
* `ListToolbar`. The page's trail and actions, "+ Add" among them, go in
|
|
17
|
-
* its `PageHeader`. With `total`, it hides while the list is empty.
|
|
17
|
+
* its `PageHeader`. With `total`, it hides while the list is empty. Once
|
|
18
|
+
* the page shows the count under its rows, the toolbar sits at the end of
|
|
19
|
+
* the filter chips' first line, or takes the line alone while no chip
|
|
20
|
+
* shows. */
|
|
18
21
|
toolbar?: ReactNode;
|
|
19
22
|
/** Under the toolbar: the chips of the list's active filters. By default the view's own
|
|
20
23
|
* `ListFilterBar`, shown while a filter is on; pass one to add a trailing slot, such as Save
|
|
@@ -43,8 +46,26 @@ export interface ListPageProps {
|
|
|
43
46
|
/** Keeps the toolbar while the list is empty, for one whose controls still
|
|
44
47
|
* matter then, such as a filter set by default that hides everything. */
|
|
45
48
|
toolbarWhenEmpty?: boolean | undefined;
|
|
49
|
+
/** Names what the list counts. With `total` (and `matches`), the page
|
|
50
|
+
* shows the count under its last row: "7 tokens", or "2 of 7 tokens"
|
|
51
|
+
* while a search or filter narrows the list. It shows only while there
|
|
52
|
+
* are rows: the empty and no-matches states say enough. A `ListToolbar`'s
|
|
53
|
+
* own `count` shows there too. */
|
|
54
|
+
count?: ListPageCount | undefined;
|
|
55
|
+
/** Under the last row, on the count's line: a `Pagination`, or a "Load
|
|
56
|
+
* more" button. */
|
|
57
|
+
pager?: ReactNode;
|
|
46
58
|
children: ReactNode;
|
|
47
59
|
}
|
|
60
|
+
/** What a list's count calls its rows. */
|
|
61
|
+
export interface ListPageCount {
|
|
62
|
+
noun: string;
|
|
63
|
+
nounPlural: string;
|
|
64
|
+
/** Whether a search or filter narrows the list, so the count reads "2 of
|
|
65
|
+
* 7". By default, whether the view's filters are on or `matches` differs
|
|
66
|
+
* from `total`; pass it for a search the page holds. */
|
|
67
|
+
filterActive?: boolean | undefined;
|
|
68
|
+
}
|
|
48
69
|
/** A list's empty state: what is missing, and what to do about it. */
|
|
49
70
|
export interface ListPageEmpty {
|
|
50
71
|
/** A glyph for what the list holds, drawn faded above the title. */
|
|
@@ -58,7 +79,7 @@ export interface ListPageEmpty {
|
|
|
58
79
|
* Given `total`, it also shows the list's states in one shared look: a
|
|
59
80
|
* sketch while loading, the error when loading failed, the empty state with
|
|
60
81
|
* the toolbar hidden, the no-matches state, or the rows. */
|
|
61
|
-
export declare function ListPage({ bleed, toolbar, filterBar, view, loading, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty, children, }: ListPageProps): import("react").JSX.Element;
|
|
82
|
+
export declare function ListPage({ bleed, toolbar, filterBar, view, loading, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty, count, pager, children, }: ListPageProps): import("react").JSX.Element;
|
|
62
83
|
export declare function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children, }: {
|
|
63
84
|
/** Where the row goes. Its `NameCell` with the same href becomes a link
|
|
64
85
|
* covering the row; a click the link doesn't catch still goes there. */
|
package/dist/list/ListPage.js
CHANGED
|
@@ -4,9 +4,11 @@ import { Tooltip } from "../components/Tooltip.js";
|
|
|
4
4
|
import { useNavigation } from "../components/Provider.js";
|
|
5
5
|
import { AppLink, isPlainClick } from "../components/AppLink.js";
|
|
6
6
|
import { isMenuDivider, isMenuGroup, isMenuSubmenu, useRunAction, warnIfInert } from "../components/MenuEntries.js";
|
|
7
|
-
import { CellPlacementContext, STACKED_ITEM, Table } from "../components/Table.js";
|
|
7
|
+
import { CellPlacementContext, STACKED_ITEM, Table, hasContent } from "../components/Table.js";
|
|
8
|
+
import { ListCountText, ListFooterCountContext } from "./listSearch.js";
|
|
8
9
|
import { ListFilterBar } from "./ListFilterBar.js";
|
|
9
10
|
import { ListActionsHeader, ListError, ListSkeleton, StretchRowLinkContext } from "./ListTable.js";
|
|
11
|
+
import { ListToolbar, toolbarCount } from "./ListToolbar.js";
|
|
10
12
|
import { MoreButton, rowContextMenuProps, useRowMenuActive } from "./rowMenu.js";
|
|
11
13
|
import { Children, cloneElement, createContext, isValidElement, useContext, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
12
14
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -17,20 +19,24 @@ var RowActionsContext = createContext({
|
|
|
17
19
|
always: false,
|
|
18
20
|
columnClasses: [],
|
|
19
21
|
columnStacks: [],
|
|
22
|
+
columnStackLines: [],
|
|
20
23
|
reservesLane: false
|
|
21
24
|
});
|
|
25
|
+
var NO_STACK_LINES = [];
|
|
22
26
|
function useRowActions(view) {
|
|
23
27
|
return useMemo(() => ({
|
|
24
28
|
show: view?.showActions ?? true,
|
|
25
29
|
always: view?.alwaysShowActions ?? false,
|
|
26
30
|
columnClasses: view?.columnClasses ?? [],
|
|
27
31
|
columnStacks: view?.columnStacks ?? [],
|
|
32
|
+
columnStackLines: view?.columnStackLines ?? NO_STACK_LINES,
|
|
28
33
|
reservesLane: view?.reservesActionLane ?? view?.lastColumnLocked ?? false
|
|
29
34
|
}), [
|
|
30
35
|
view?.showActions,
|
|
31
36
|
view?.alwaysShowActions,
|
|
32
37
|
view?.columnClasses,
|
|
33
38
|
view?.columnStacks,
|
|
39
|
+
view?.columnStackLines,
|
|
34
40
|
view?.lastColumnLocked,
|
|
35
41
|
view?.reservesActionLane
|
|
36
42
|
]);
|
|
@@ -47,13 +53,25 @@ var STAYS_BESIDE_STACK = "@max-[36rem]:align-top @max-[36rem]:pt-2.5";
|
|
|
47
53
|
* Given `total`, it also shows the list's states in one shared look: a
|
|
48
54
|
* sketch while loading, the error when loading failed, the empty state with
|
|
49
55
|
* the toolbar hidden, the no-matches state, or the rows. */
|
|
50
|
-
function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty = false, children }) {
|
|
56
|
+
function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, error, errorNoun, total, matches, empty, noMatches, toolbarWhenEmpty = false, count, pager, children }) {
|
|
51
57
|
const failed = !loading && error !== void 0 && error !== null && !total;
|
|
52
58
|
const known = total !== void 0 && !loading && !failed;
|
|
53
59
|
const isEmpty = known && total === 0;
|
|
54
60
|
const noneMatch = known && !isEmpty && (matches ?? total) === 0;
|
|
55
61
|
const controls = total === void 0 || toolbarWhenEmpty || known && total > 0;
|
|
56
|
-
const
|
|
62
|
+
const givenBar = filterBar === void 0 ? view?.filterState && view.filterState.fields.length > 0 ? /* @__PURE__ */ jsx(ListFilterBar, { filters: view.filterState }) : null : filterBar;
|
|
63
|
+
const fromToolbar = isValidElement(toolbar) && toolbar.type === ListToolbar ? toolbarCount(toolbar.props) : void 0;
|
|
64
|
+
const counted = count && total !== void 0 ? {
|
|
65
|
+
total,
|
|
66
|
+
matches: matches ?? total,
|
|
67
|
+
filterActive: count.filterActive ?? ((view?.filterState?.count ?? 0) > 0 || (matches ?? total) !== total),
|
|
68
|
+
noun: count.noun,
|
|
69
|
+
nounPlural: count.nounPlural
|
|
70
|
+
} : fromToolbar;
|
|
71
|
+
const showsRows = !loading && !failed && !isEmpty && !noneMatch && (!counted || counted.matches > 0);
|
|
72
|
+
const footerCount = showsRows ? counted : void 0;
|
|
73
|
+
const shareLine = counted !== void 0 && !!toolbar && isValidElement(givenBar) && givenBar.type === ListFilterBar;
|
|
74
|
+
const bar = shareLine ? cloneElement(givenBar, { end: toolbar }) : givenBar;
|
|
57
75
|
let body = children;
|
|
58
76
|
if (loading) body = /* @__PURE__ */ jsx(ListSkeleton, {});
|
|
59
77
|
else if (failed) body = /* @__PURE__ */ jsx(ListError, {
|
|
@@ -70,52 +88,96 @@ function ListPage({ bleed = false, toolbar, filterBar, view, loading = false, er
|
|
|
70
88
|
});
|
|
71
89
|
return /* @__PURE__ */ jsxs("div", {
|
|
72
90
|
className: cx("flex h-full min-h-0 flex-col", bleed && "-m-4"),
|
|
73
|
-
children: [
|
|
74
|
-
|
|
91
|
+
children: [/* @__PURE__ */ jsxs(ListFooterCountContext, {
|
|
92
|
+
value: counted !== void 0,
|
|
93
|
+
children: [!!toolbar && !shareLine && controls && /* @__PURE__ */ jsx("div", {
|
|
75
94
|
className: "shrink-0 px-4",
|
|
76
95
|
children: toolbar
|
|
77
|
-
}),
|
|
78
|
-
!!bar && controls && /* @__PURE__ */ jsx("div", {
|
|
96
|
+
}), !!bar && controls && /* @__PURE__ */ jsx("div", {
|
|
79
97
|
className: "shrink-0 px-4",
|
|
80
98
|
children: bar
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
})]
|
|
100
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
101
|
+
className: "relative @container min-h-0 flex-1 overflow-auto",
|
|
102
|
+
children: [/* @__PURE__ */ jsx(ListRowActionsProvider, {
|
|
103
|
+
view,
|
|
104
|
+
children: body
|
|
105
|
+
}), (footerCount || showsRows && !!pager) && /* @__PURE__ */ jsx(ListFooter, {
|
|
106
|
+
count: footerCount,
|
|
107
|
+
pager
|
|
108
|
+
})]
|
|
109
|
+
})]
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
/** The line under a list's last row: its count at the rows' start, its pager at their end. */
|
|
113
|
+
function ListFooter({ count, pager }) {
|
|
114
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
115
|
+
className: "flex min-h-10 flex-wrap items-center gap-x-3 px-4 empty:hidden [&>:not([role=status])]:ml-auto [&>nav]:flex-1 [&>nav]:px-0 [&>nav>div:first-child]:justify-end",
|
|
116
|
+
children: [count && /* @__PURE__ */ jsx(ListCountText, { ...count }), pager]
|
|
90
117
|
});
|
|
91
118
|
}
|
|
92
119
|
var STACKED = { stacked: true };
|
|
93
|
-
var
|
|
120
|
+
var STACKED_JOINED_PLACEMENT = {
|
|
121
|
+
stacked: true,
|
|
122
|
+
joined: true
|
|
123
|
+
};
|
|
124
|
+
var STACKED_LINE = "mt-1 flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1 text-xs text-(--theme-muted-text-color) empty:hidden @[36rem]:hidden";
|
|
125
|
+
var STACKED_LINES = "mt-1 flex min-w-0 flex-col gap-y-1 text-xs text-(--theme-muted-text-color) empty:hidden @[36rem]:hidden";
|
|
126
|
+
var STACKED_LINES_LINE = "flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1";
|
|
94
127
|
var isCellElement = (child) => child.type === Table.Item || child.type === "td";
|
|
95
128
|
function ListRow({ href, onActivate, items, goTo, openMenu, pinnedActions, selected, selectionActive, onRowSelect, leading, className, children }) {
|
|
96
129
|
const { active: menuActive, openMenu: openRowMenu } = useRowMenuActive(openMenu);
|
|
97
|
-
const { show: showActions, always: alwaysShowActions, columnClasses, columnStacks, reservesLane } = useContext(RowActionsContext);
|
|
130
|
+
const { show: showActions, always: alwaysShowActions, columnClasses, columnStacks, columnStackLines, reservesLane } = useContext(RowActionsContext);
|
|
98
131
|
const cells = Children.toArray(children);
|
|
99
132
|
const stacked = cells.flatMap((child, i) => {
|
|
100
133
|
if (i === 0 || !columnStacks[i] || !isValidElement(child)) return [];
|
|
134
|
+
const line = columnStackLines[i];
|
|
101
135
|
if (isCellElement(child)) {
|
|
102
136
|
const content = child.props.children;
|
|
103
|
-
return
|
|
137
|
+
return hasContent(content) ? [{
|
|
138
|
+
child,
|
|
139
|
+
line
|
|
140
|
+
}] : [];
|
|
104
141
|
}
|
|
105
|
-
return [
|
|
142
|
+
return [{
|
|
143
|
+
child,
|
|
144
|
+
line
|
|
145
|
+
}];
|
|
106
146
|
});
|
|
107
|
-
const
|
|
147
|
+
const item = (child, j, joined = false) => isValidElement(child) && child.type === "td" ? /* @__PURE__ */ jsx("div", {
|
|
148
|
+
className: cx(STACKED_ITEM, joined && "-ml-1.5 before:pr-1.5 before:content-['·'] before:text-(--theme-muted-text-color)"),
|
|
149
|
+
"data-stacked": true,
|
|
150
|
+
children: child.props.children
|
|
151
|
+
}, j) : /* @__PURE__ */ jsx(CellPlacementContext, {
|
|
152
|
+
value: joined ? STACKED_JOINED_PLACEMENT : STACKED,
|
|
153
|
+
children: child
|
|
154
|
+
}, j);
|
|
155
|
+
let under;
|
|
156
|
+
if (stacked.length > 0 && stacked.every(({ line }) => line === void 0)) under = /* @__PURE__ */ jsx("div", {
|
|
108
157
|
className: STACKED_LINE,
|
|
109
158
|
"data-stacked-line": true,
|
|
110
|
-
children: stacked.map((child, j) =>
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}, j)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
159
|
+
children: stacked.map(({ child }, j) => item(child, j))
|
|
160
|
+
});
|
|
161
|
+
else if (stacked.length > 0) {
|
|
162
|
+
const lines = [];
|
|
163
|
+
stacked.forEach(({ child, line }, j) => {
|
|
164
|
+
const current = lines.at(-1);
|
|
165
|
+
if (!current || line === "own") {
|
|
166
|
+
lines.push([item(child, j)]);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
current.push(item(child, j, line === "join"));
|
|
170
|
+
});
|
|
171
|
+
under = /* @__PURE__ */ jsx("div", {
|
|
172
|
+
className: STACKED_LINES,
|
|
173
|
+
"data-stacked-lines": true,
|
|
174
|
+
children: lines.map((values, k) => /* @__PURE__ */ jsx("div", {
|
|
175
|
+
className: STACKED_LINES_LINE,
|
|
176
|
+
"data-stacked-line": true,
|
|
177
|
+
children: values
|
|
178
|
+
}, k))
|
|
179
|
+
});
|
|
180
|
+
}
|
|
119
181
|
const styledCells = cells.map((child, i) => {
|
|
120
182
|
const cls = cx(columnClasses[i], i > 0 && stacked.length > 0 && !columnStacks[i] && STAYS_BESIDE_STACK);
|
|
121
183
|
const after = i === 0 ? under : void 0;
|
|
@@ -333,6 +395,9 @@ function shareWidths(columns, stacks) {
|
|
|
333
395
|
const total = shares.reduce((sum, share) => sum + (share ?? 0), 0);
|
|
334
396
|
const scale = (tier) => {
|
|
335
397
|
const kept = columns.reduce((sum, col, i) => sum + (shows(col, i, tier) ? shares[i] ?? 0 : 0), 0);
|
|
398
|
+
const drops = columns.some((col, i) => !shows(col, i, tier));
|
|
399
|
+
const unshared = columns.some((col, i) => shows(col, i, tier) && shares[i] === void 0);
|
|
400
|
+
if (drops && !unshared && kept > 0) return 100 / kept;
|
|
336
401
|
const left = 100 - (total - kept);
|
|
337
402
|
return left > 0 ? 100 / left : 1;
|
|
338
403
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
-
import { type ListSearch } from './listSearch.js';
|
|
2
|
+
import { type ListCountProps, type ListSearch } from './listSearch.js';
|
|
3
3
|
import type { ListView } from './listView.js';
|
|
4
4
|
export interface ListToolbarProps<T> {
|
|
5
5
|
/** What the count says: how many there are and, while a search or filter
|
|
6
6
|
* is on, how many match. `filterActive` defaults to whether the search or
|
|
7
|
-
* the view's filters narrow the list.
|
|
8
|
-
|
|
7
|
+
* the view's filters narrow the list. As a `ListPage`'s `toolbar`, the
|
|
8
|
+
* page shows it under the last row, as its own `count` would; anywhere
|
|
9
|
+
* else, it leads the toolbar. */
|
|
10
|
+
count?: {
|
|
9
11
|
total: number;
|
|
10
12
|
matches: number;
|
|
11
13
|
filterActive?: boolean | undefined;
|
|
@@ -25,7 +27,11 @@ export interface ListToolbarProps<T> {
|
|
|
25
27
|
/** More controls, before the filter and display menus. */
|
|
26
28
|
children?: ReactNode;
|
|
27
29
|
}
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
/** The count a `ListToolbar` was given, as a `ListPage` shows it under its rows. */
|
|
31
|
+
export declare function toolbarCount<T>({ count, view, search, }: ListToolbarProps<T>): ListCountProps | undefined;
|
|
32
|
+
/** A list page's toolbar in the shared order, at its end: the page's own
|
|
33
|
+
* controls, the filter menu, the display options, the search and an action.
|
|
34
|
+
* Give it to `ListPage` as its `toolbar`; the list's count goes under its
|
|
35
|
+
* rows (see `ListPage`'s `count`), and while filter chips show, the toolbar
|
|
36
|
+
* sits at the end of their first line. */
|
|
37
|
+
export declare function ListToolbar<T>(props: ListToolbarProps<T>): import("react").JSX.Element;
|
package/dist/list/ListToolbar.js
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
import { useStrings } from "../components/Provider.js";
|
|
2
|
+
import { ListCountText, ListFooterCountContext, ListSearchControl, ListToolbarInlineContext } from "./listSearch.js";
|
|
2
3
|
import { ListDisplayMenu, ListFilterMenu } from "./ListViewControls.js";
|
|
3
|
-
import {
|
|
4
|
+
import { useContext } from "react";
|
|
4
5
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
//#region src/list/ListToolbar.tsx
|
|
6
|
-
/**
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
function ListToolbar({ count, view, search, searchLabel, searchPlaceholder, action, children }) {
|
|
10
|
-
const { common } = useStrings();
|
|
7
|
+
/** The count a `ListToolbar` was given, as a `ListPage` shows it under its rows. */
|
|
8
|
+
function toolbarCount({ count, view, search }) {
|
|
9
|
+
if (!count) return;
|
|
11
10
|
const filterActive = count.filterActive ?? (search !== void 0 && (search.filter.trim() !== "" || search.terms.length > 0) || (view?.activeFilterCount ?? 0) > 0);
|
|
11
|
+
return {
|
|
12
|
+
total: count.total,
|
|
13
|
+
matches: count.matches,
|
|
14
|
+
filterActive,
|
|
15
|
+
noun: count.noun,
|
|
16
|
+
nounPlural: count.nounPlural
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** A list page's toolbar in the shared order, at its end: the page's own
|
|
20
|
+
* controls, the filter menu, the display options, the search and an action.
|
|
21
|
+
* Give it to `ListPage` as its `toolbar`; the list's count goes under its
|
|
22
|
+
* rows (see `ListPage`'s `count`), and while filter chips show, the toolbar
|
|
23
|
+
* sits at the end of their first line. */
|
|
24
|
+
function ListToolbar(props) {
|
|
25
|
+
const { view, search, searchLabel, searchPlaceholder, action, children } = props;
|
|
26
|
+
const { common } = useStrings();
|
|
27
|
+
const count = useContext(ListFooterCountContext) ? void 0 : toolbarCount(props);
|
|
28
|
+
const inline = useContext(ListToolbarInlineContext);
|
|
12
29
|
return /* @__PURE__ */ jsxs("div", {
|
|
13
|
-
className: "flex min-h-10 items-center justify-
|
|
14
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
15
|
-
className: "shrink-0 whitespace-nowrap",
|
|
16
|
-
children: /* @__PURE__ */ jsx(
|
|
17
|
-
total: count.total,
|
|
18
|
-
matches: count.matches,
|
|
19
|
-
filterActive,
|
|
20
|
-
noun: count.noun,
|
|
21
|
-
nounPlural: count.nounPlural
|
|
22
|
-
})
|
|
30
|
+
className: inline ? "flex items-center justify-end gap-3" : "flex min-h-10 items-center justify-end gap-3 py-1",
|
|
31
|
+
children: [count && /* @__PURE__ */ jsx("div", {
|
|
32
|
+
className: "mr-auto flex h-8 shrink-0 items-center whitespace-nowrap",
|
|
33
|
+
children: /* @__PURE__ */ jsx(ListCountText, { ...count })
|
|
23
34
|
}), /* @__PURE__ */ jsxs("div", {
|
|
24
35
|
className: "flex min-w-0 items-center gap-1",
|
|
25
36
|
children: [
|
|
@@ -40,4 +51,4 @@ function ListToolbar({ count, view, search, searchLabel, searchPlaceholder, acti
|
|
|
40
51
|
});
|
|
41
52
|
}
|
|
42
53
|
//#endregion
|
|
43
|
-
export { ListToolbar };
|
|
54
|
+
export { ListToolbar, toolbarCount };
|
package/dist/list/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
export { ActionToolbar, type ToolbarAction, ToolbarButton, type ToolbarItem, } from './ActionToolbar.js';
|
|
2
2
|
export { type ActiveFilter, applyFilterList, DATE_PRESETS, type DatePreset, dateFilterRange, decodeFilters, encodeFilters, type FilterField, type FilterKind, type FilterOperator, type FilterOption, type FilterRowValue, filterParamKeys, matchFilter, type QuickFilter, sameFilters, withFilterParams, } from './filterModel.js';
|
|
3
3
|
export { AddFilterMenu, type AddFilterMenuProps, FilterChip, ListFilterBar, type ListFilterBarProps, } from './ListFilterBar.js';
|
|
4
|
-
export { GroupRow, ListColumns, ListPage, type ListPageEmpty, type ListPageProps, ListRow, ListRowActionsProvider, useListNavigate, } from './ListPage.js';
|
|
4
|
+
export { GroupRow, ListColumns, ListPage, type ListPageCount, type ListPageEmpty, type ListPageProps, ListRow, ListRowActionsProvider, useListNavigate, } from './ListPage.js';
|
|
5
5
|
export { actionsColumnWidth, ListActionsHeader, ListCellText, ListError, ListSelectAllHeader, ListSkeleton, listTableFixedProps, listTableProps, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, } from './ListTable.js';
|
|
6
6
|
export { ListToolbar, type ListToolbarProps } from './ListToolbar.js';
|
|
7
7
|
export { ListDisplayMenu, ListFilterMenu } from './ListViewControls.js';
|
|
8
8
|
export { ListCount, type ListSearch, ListSearchControl, useListSearch, } from './listSearch.js';
|
|
9
|
-
export { type ActionDef, type ColumnDef, DEFAULT_ORDER, FAVORITE_ORDER, type FilterFacet, type ListView, type ListViewChrome, type ListViewConfig, type OrderByOption, useListView, } from './listView.js';
|
|
9
|
+
export { type ActionDef, type ColumnDef, DEFAULT_ORDER, FAVORITE_ORDER, type FilterFacet, type ListView, type ListViewChrome, type ListViewConfig, type OrderByOption, type StackLine, useListView, } from './listView.js';
|
|
10
10
|
export { assembleRowMenu, type CopyFields, type CopySubmenu, MoreButton, type RowAction, rowContextMenuProps, useCopySubmenu, useRowMenuActive, } from './rowMenu.js';
|
|
11
11
|
export { HoverCardRow, UserHoverCard, type UserHoverCardProps, } from './UserHoverCard.js';
|
|
12
12
|
export { type FilterStore, type ListFilters, type ListFiltersConfig, useListFilters, } from './useListFilters.js';
|
package/dist/list/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { ActionToolbar, ToolbarButton } from "./ActionToolbar.js";
|
|
2
2
|
import { DATE_PRESETS, applyFilterList, dateFilterRange, decodeFilters, encodeFilters, filterParamKeys, matchFilter, sameFilters, withFilterParams } from "./filterModel.js";
|
|
3
|
+
import { ListCount, ListSearchControl, useListSearch } from "./listSearch.js";
|
|
3
4
|
import { AddFilterMenu, FilterChip, ListFilterBar } from "./ListFilterBar.js";
|
|
4
5
|
import { ListActionsHeader, ListCellText, ListError, ListSelectAllHeader, ListSkeleton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, actionsColumnWidth, listTableFixedProps, listTableProps } from "./ListTable.js";
|
|
5
|
-
import { MoreButton, assembleRowMenu, rowContextMenuProps, useCopySubmenu, useRowMenuActive } from "./rowMenu.js";
|
|
6
|
-
import { GroupRow, ListColumns, ListPage, ListRow, ListRowActionsProvider, useListNavigate } from "./ListPage.js";
|
|
7
6
|
import { useListFilters } from "./useListFilters.js";
|
|
8
7
|
import { DEFAULT_ORDER, FAVORITE_ORDER, useListView } from "./listView.js";
|
|
9
8
|
import { ListDisplayMenu, ListFilterMenu } from "./ListViewControls.js";
|
|
10
|
-
import { ListCount, ListSearchControl, useListSearch } from "./listSearch.js";
|
|
11
9
|
import { ListToolbar } from "./ListToolbar.js";
|
|
10
|
+
import { MoreButton, assembleRowMenu, rowContextMenuProps, useCopySubmenu, useRowMenuActive } from "./rowMenu.js";
|
|
11
|
+
import { GroupRow, ListColumns, ListPage, ListRow, ListRowActionsProvider, useListNavigate } from "./ListPage.js";
|
|
12
12
|
import { HoverCardRow, UserHoverCard } from "./UserHoverCard.js";
|
|
13
13
|
export { ActionToolbar, AddFilterMenu, DATE_PRESETS, DEFAULT_ORDER, FAVORITE_ORDER, FilterChip, GroupRow, HoverCardRow, ListActionsHeader, ListCellText, ListColumns, ListCount, ListDisplayMenu, ListError, ListFilterBar, ListFilterMenu, ListPage, ListRow, ListRowActionsProvider, ListSearchControl, ListSelectAllHeader, ListSkeleton, ListToolbar, MoreButton, NAME_MIN_WIDTH_PX, NameCell, RowSelectCheckbox, STRETCHED_LINK, ToolbarButton, UserHoverCard, actionsColumnWidth, applyFilterList, assembleRowMenu, dateFilterRange, decodeFilters, encodeFilters, filterParamKeys, listTableFixedProps, listTableProps, matchFilter, rowContextMenuProps, sameFilters, useCopySubmenu, useListFilters, useListNavigate, useListSearch, useListView, useRowMenuActive, withFilterParams };
|