@planningcenter/tapestry 4.4.1-rc.3 → 4.4.1-rc.4
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/dist/components/DataTable/ViewSettingsToggle.d.ts.map +1 -1
- package/dist/components/DataTable/ViewSettingsToggle.js +4 -1
- package/dist/components/DataTable/ViewSettingsToggle.js.map +1 -1
- package/dist/components/internal/filterable-list-box/FilterableListBox.d.ts +20 -9
- package/dist/components/internal/filterable-list-box/FilterableListBox.d.ts.map +1 -1
- package/dist/components/internal/filterable-list-box/FilterableListBox.js +58 -10
- package/dist/components/internal/filterable-list-box/FilterableListBox.js.map +1 -1
- package/dist/reactRender.css +1385 -1376
- package/dist/reactRender.css.map +1 -1
- package/dist/reactRenderLegacy.css +1385 -1376
- package/dist/reactRenderLegacy.css.map +1 -1
- package/dist/unstable.css +9 -0
- package/dist/unstable.css.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewSettingsToggle.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/ViewSettingsToggle.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACtC,iEAAiE;IACjE,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,wBAAwB,EAAE,CAAC,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAChE,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAC7B;
|
|
1
|
+
{"version":3,"file":"ViewSettingsToggle.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/ViewSettingsToggle.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAA;AAGzB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD,MAAM,WAAW,uBAAuB;IACtC,iEAAiE;IACjE,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,wBAAwB,EAAE,CAAC,kBAAkB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAChE,kBAAkB,EAAE,MAAM,EAAE,CAAA;CAC7B;AAUD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,wBAAwB,EACxB,kBAAkB,GACnB,EAAE,uBAAuB,qBAsCzB;yBA1Ce,kBAAkB"}
|
|
@@ -11,6 +11,9 @@ import Icon from '../../utilities/Icon.js';
|
|
|
11
11
|
import React__default from 'react';
|
|
12
12
|
import { Tooltip } from '../internal/tooltip/Tooltip.js';
|
|
13
13
|
|
|
14
|
+
function renderColumnItem(column) {
|
|
15
|
+
return (React__default.createElement(FilterableListBoxItem, { id: column.name }, column.headerLabel));
|
|
16
|
+
}
|
|
14
17
|
/**
|
|
15
18
|
* A trigger + popover for table view settings. Today it only holds column
|
|
16
19
|
* visibility (via `FilterableListBox`), but the panel is meant to grow to
|
|
@@ -23,7 +26,7 @@ function ViewSettingsToggle({ columns, onColumnVisibilityChange, visibleColumnNa
|
|
|
23
26
|
React__default.createElement(Tooltip, { content: "View settings", placement: "bottom end" },
|
|
24
27
|
React__default.createElement(PopoverTrigger, null,
|
|
25
28
|
React__default.createElement(IconButton, { "aria-label": "View settings", className: "tds-data-table-popover-trigger", icon: React__default.createElement(Icon, { "aria-hidden": true, symbol: "general#faders" }) }))),
|
|
26
|
-
React__default.createElement(PopoverContent, { className: "tds-data-table-popover", modal: false, placement: "bottom end" }, columns.length > 0 && (React__default.createElement(FilterableListBox, { hideLabel: true, items: columns, label: "Column visibility", onSelectionChange: (keys) => onColumnVisibilityChange(keys), placeholder: "Search columns", selectedKeys: visibleColumnNames, selectionMode: "multiple"
|
|
29
|
+
React__default.createElement(PopoverContent, { className: "tds-data-table-popover", modal: false, placement: "bottom end" }, columns.length > 0 && (React__default.createElement(FilterableListBox, { hideLabel: true, items: columns, label: "Column visibility", onSelectionChange: (keys) => onColumnVisibilityChange(keys), placeholder: "Search columns", selectedKeys: visibleColumnNames, selectionMode: "multiple", showBulkActions: true }, renderColumnItem))))));
|
|
27
30
|
}
|
|
28
31
|
ViewSettingsToggle.displayName = "ViewSettingsToggle";
|
|
29
32
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ViewSettingsToggle.js","sources":["../../../src/components/DataTable/ViewSettingsToggle.tsx"],"sourcesContent":["import { IconButton } from \"@components/button\"\nimport {\n FilterableListBox,\n FilterableListBoxItem,\n} from \"@components/internal/filterable-list-box\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@components/popover\"\nimport Icon from \"@utilities/Icon\"\nimport React from \"react\"\n\nimport { Tooltip } from \"../internal/tooltip/Tooltip\"\nimport type { DataTableColumn } from \"./DataTableColumn\"\n\nexport interface ViewSettingsToggleProps {\n /** The columns opted into visibility toggling via `hideable`. */\n columns: DataTableColumn[]\n onColumnVisibilityChange: (visibleColumnNames: string[]) => void\n visibleColumnNames: string[]\n}\n\n/**\n * A trigger + popover for table view settings. Today it only holds column\n * visibility (via `FilterableListBox`), but the panel is meant to grow to\n * include other view-related settings (e.g. a Table/Card display toggle, a\n * Compact View switch) as siblings inside the same dialog.\n */\nexport function ViewSettingsToggle({\n columns,\n onColumnVisibilityChange,\n visibleColumnNames,\n}: ViewSettingsToggleProps) {\n return (\n <div className=\"tds-data-table-view-settings-toggle\">\n <Popover>\n <Tooltip content=\"View settings\" placement=\"bottom end\">\n <PopoverTrigger>\n <IconButton\n aria-label=\"View settings\"\n className=\"tds-data-table-popover-trigger\"\n icon={<Icon aria-hidden symbol=\"general#faders\" />}\n />\n </PopoverTrigger>\n </Tooltip>\n <PopoverContent\n className=\"tds-data-table-popover\"\n modal={false}\n placement=\"bottom end\"\n >\n {columns.length > 0 && (\n <FilterableListBox\n hideLabel\n items={columns}\n label=\"Column visibility\"\n onSelectionChange={(keys) =>\n onColumnVisibilityChange(keys as string[])\n }\n placeholder=\"Search columns\"\n selectedKeys={visibleColumnNames}\n selectionMode=\"multiple\"\n
|
|
1
|
+
{"version":3,"file":"ViewSettingsToggle.js","sources":["../../../src/components/DataTable/ViewSettingsToggle.tsx"],"sourcesContent":["import { IconButton } from \"@components/button\"\nimport {\n FilterableListBox,\n FilterableListBoxItem,\n} from \"@components/internal/filterable-list-box\"\nimport { Popover, PopoverContent, PopoverTrigger } from \"@components/popover\"\nimport Icon from \"@utilities/Icon\"\nimport React from \"react\"\n\nimport { Tooltip } from \"../internal/tooltip/Tooltip\"\nimport type { DataTableColumn } from \"./DataTableColumn\"\n\nexport interface ViewSettingsToggleProps {\n /** The columns opted into visibility toggling via `hideable`. */\n columns: DataTableColumn[]\n onColumnVisibilityChange: (visibleColumnNames: string[]) => void\n visibleColumnNames: string[]\n}\n\nfunction renderColumnItem(column: DataTableColumn) {\n return (\n <FilterableListBoxItem id={column.name}>\n {column.headerLabel}\n </FilterableListBoxItem>\n )\n}\n\n/**\n * A trigger + popover for table view settings. Today it only holds column\n * visibility (via `FilterableListBox`), but the panel is meant to grow to\n * include other view-related settings (e.g. a Table/Card display toggle, a\n * Compact View switch) as siblings inside the same dialog.\n */\nexport function ViewSettingsToggle({\n columns,\n onColumnVisibilityChange,\n visibleColumnNames,\n}: ViewSettingsToggleProps) {\n return (\n <div className=\"tds-data-table-view-settings-toggle\">\n <Popover>\n <Tooltip content=\"View settings\" placement=\"bottom end\">\n <PopoverTrigger>\n <IconButton\n aria-label=\"View settings\"\n className=\"tds-data-table-popover-trigger\"\n icon={<Icon aria-hidden symbol=\"general#faders\" />}\n />\n </PopoverTrigger>\n </Tooltip>\n <PopoverContent\n className=\"tds-data-table-popover\"\n modal={false}\n placement=\"bottom end\"\n >\n {columns.length > 0 && (\n <FilterableListBox\n hideLabel\n items={columns}\n label=\"Column visibility\"\n onSelectionChange={(keys) =>\n onColumnVisibilityChange(keys as string[])\n }\n placeholder=\"Search columns\"\n selectedKeys={visibleColumnNames}\n selectionMode=\"multiple\"\n showBulkActions\n >\n {renderColumnItem}\n </FilterableListBox>\n )}\n </PopoverContent>\n </Popover>\n </div>\n )\n}\n\nViewSettingsToggle.displayName = \"ViewSettingsToggle\"\n"],"names":["React"],"mappings":";;;;;;;;;;;;;AAmBA,SAAS,gBAAgB,CAAC,MAAuB,EAAA;AAC/C,IAAA,QACEA,cAAA,CAAA,aAAA,CAAC,qBAAqB,EAAA,EAAC,EAAE,EAAE,MAAM,CAAC,IAAI,IACnC,MAAM,CAAC,WAAW,CACG;AAE5B;AAEA;;;;;AAKG;AACG,SAAU,kBAAkB,CAAC,EACjC,OAAO,EACP,wBAAwB,EACxB,kBAAkB,GACM,EAAA;AACxB,IAAA,QACEA,cAAA,CAAA,aAAA,CAAA,KAAA,EAAA,EAAK,SAAS,EAAC,qCAAqC,EAAA;AAClD,QAAAA,cAAA,CAAA,aAAA,CAAC,OAAO,EAAA,IAAA;YACNA,cAAA,CAAA,aAAA,CAAC,OAAO,IAAC,OAAO,EAAC,eAAe,EAAC,SAAS,EAAC,YAAY,EAAA;AACrD,gBAAAA,cAAA,CAAA,aAAA,CAAC,cAAc,EAAA,IAAA;AACb,oBAAAA,cAAA,CAAA,aAAA,CAAC,UAAU,EAAA,EAAA,YAAA,EACE,eAAe,EAC1B,SAAS,EAAC,gCAAgC,EAC1C,IAAI,EAAEA,cAAA,CAAA,aAAA,CAAC,IAAI,yBAAa,MAAM,EAAC,gBAAgB,EAAA,CAAG,EAAA,CAClD,CACa,CACT;AACV,YAAAA,cAAA,CAAA,aAAA,CAAC,cAAc,EAAA,EACb,SAAS,EAAC,wBAAwB,EAClC,KAAK,EAAE,KAAK,EACZ,SAAS,EAAC,YAAY,EAAA,EAErB,OAAO,CAAC,MAAM,GAAG,CAAC,KACjBA,6BAAC,iBAAiB,EAAA,EAChB,SAAS,EAAA,IAAA,EACT,KAAK,EAAE,OAAO,EACd,KAAK,EAAC,mBAAmB,EACzB,iBAAiB,EAAE,CAAC,IAAI,KACtB,wBAAwB,CAAC,IAAgB,CAAC,EAE5C,WAAW,EAAC,gBAAgB,EAC5B,YAAY,EAAE,kBAAkB,EAChC,aAAa,EAAC,UAAU,EACxB,eAAe,EAAA,IAAA,EAAA,EAEd,gBAAgB,CACC,CACrB,CACc,CACT,CACN;AAEV;AAEA,kBAAkB,CAAC,WAAW,GAAG,oBAAoB;;;;"}
|
|
@@ -4,11 +4,19 @@ import React, { type HTMLAttributes, type ReactElement, type ReactNode } from "r
|
|
|
4
4
|
import { type Key, type ListBoxItemProps } from "react-aria-components/ListBox";
|
|
5
5
|
export interface FilterableListBoxProps<T extends object = object> {
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
* with `items` (dynamic collection) — matches `ComboBox`'s own
|
|
7
|
+
* Render function paired with `items` — matches `ComboBox`'s own
|
|
9
8
|
* dynamic-collection convention.
|
|
10
9
|
*/
|
|
11
|
-
children:
|
|
10
|
+
children: (item: T) => ReactElement;
|
|
11
|
+
/**
|
|
12
|
+
* Optional action rendered next to the built-in "Select all"/"Deselect
|
|
13
|
+
* all" toggle (e.g. "Select my teams"). Requires `selectionMode="multiple"`
|
|
14
|
+
* and `showBulkActions`, and hides while filtering along with the toggle.
|
|
15
|
+
* Fully caller-owned: bring your own `onClick`/selection logic, and pass
|
|
16
|
+
* `disabled` to the action element yourself if it should follow the
|
|
17
|
+
* list's `disabled` prop — `FilterableListBox` doesn't forward it.
|
|
18
|
+
*/
|
|
19
|
+
customAction?: ReactNode;
|
|
12
20
|
/** Initial uncontrolled search query. */
|
|
13
21
|
defaultInputValue?: string;
|
|
14
22
|
/** Initial selection for the uncontrolled convenience mode. */
|
|
@@ -19,8 +27,8 @@ export interface FilterableListBoxProps<T extends object = object> {
|
|
|
19
27
|
disabledKeys?: Key[];
|
|
20
28
|
/** If `true`, visually hides `label` (it remains in the DOM). */
|
|
21
29
|
hideLabel?: boolean;
|
|
22
|
-
/**
|
|
23
|
-
items
|
|
30
|
+
/** Collection data. Pair with a function `children`. */
|
|
31
|
+
items: Iterable<T>;
|
|
24
32
|
/**
|
|
25
33
|
* Label for the search input. Also supplies the accessible name for the
|
|
26
34
|
* list.
|
|
@@ -40,6 +48,12 @@ export interface FilterableListBoxProps<T extends object = object> {
|
|
|
40
48
|
selectedKeys?: Key[];
|
|
41
49
|
/** Default `"multiple"`. The checkbox visual only renders in `"multiple"` mode. */
|
|
42
50
|
selectionMode?: "multiple" | "single";
|
|
51
|
+
/**
|
|
52
|
+
* Shows the built-in "Select all"/"Deselect all" toggle (and the
|
|
53
|
+
* `customAction` slot next to it). Meaningless in `selectionMode="single"`.
|
|
54
|
+
* Default `true`.
|
|
55
|
+
*/
|
|
56
|
+
showBulkActions?: boolean;
|
|
43
57
|
}
|
|
44
58
|
export type FilterableListBoxElementProps<T extends object = object> = Omit<HTMLAttributes<HTMLDivElement>, keyof FilterableListBoxProps<T> | "aria-labelledby"> & FilterableListBoxProps<T>;
|
|
45
59
|
/**
|
|
@@ -48,12 +62,9 @@ export type FilterableListBoxElementProps<T extends object = object> = Omit<HTML
|
|
|
48
62
|
* Unlike `ComboBox`, the input is a pure filter: it never holds the value,
|
|
49
63
|
* and the list never collapses.
|
|
50
64
|
*
|
|
51
|
-
* Collections can be either static (`FilterableListBoxItem` children) or
|
|
52
|
-
* dynamic (`items` paired with a function child).
|
|
53
|
-
*
|
|
54
65
|
* @component
|
|
55
66
|
*/
|
|
56
|
-
export declare function FilterableListBox<T extends object = object>({ children, className, defaultInputValue, defaultSelectedKeys, disabled, disabledKeys, hideLabel, id, items, label, onSelectionChange, placeholder, renderEmptyState, selectedKeys, selectionMode, ...restProps }: FilterableListBoxElementProps<T>): React.JSX.Element;
|
|
67
|
+
export declare function FilterableListBox<T extends object = object>({ children, className, customAction, defaultInputValue, defaultSelectedKeys, disabled, disabledKeys, hideLabel, id, items, label, onSelectionChange, placeholder, renderEmptyState, selectedKeys, selectionMode, showBulkActions, ...restProps }: FilterableListBoxElementProps<T>): React.JSX.Element;
|
|
57
68
|
export declare namespace FilterableListBox {
|
|
58
69
|
var displayName: string;
|
|
59
70
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterableListBox.d.ts","sourceRoot":"","sources":["../../../../src/components/internal/filterable-list-box/FilterableListBox.tsx"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAA;AAIpB,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,2BAA2B,CAAA;AAGhF,OAAO,KAAK,EAAE,EAEZ,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,SAAS,
|
|
1
|
+
{"version":3,"file":"FilterableListBox.d.ts","sourceRoot":"","sources":["../../../../src/components/internal/filterable-list-box/FilterableListBox.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '../../button/ActionsDropdownIconButton.js';
|
|
2
|
-
import '../../button/Button.js';
|
|
2
|
+
import { Button } from '../../button/Button.js';
|
|
3
3
|
import '../../button/DropdownButton.js';
|
|
4
4
|
import '../../button/DropdownIconButton.js';
|
|
5
5
|
import { IconButton } from '../../button/IconButton.js';
|
|
@@ -8,7 +8,7 @@ import '../../button/PageHeaderActionsDropdownButton.js';
|
|
|
8
8
|
import Icon from '../../../utilities/Icon.js';
|
|
9
9
|
import { useId } from '../../../utilities/useId.js';
|
|
10
10
|
import classNames from 'classnames';
|
|
11
|
-
import React__default, { createContext, useContext } from 'react';
|
|
11
|
+
import React__default, { createContext, useState, useMemo, useContext } from 'react';
|
|
12
12
|
import { useFilter } from 'react-aria/useFilter';
|
|
13
13
|
import { Autocomplete } from 'react-aria-components/Autocomplete';
|
|
14
14
|
import { ButtonContext } from 'react-aria-components/Button';
|
|
@@ -16,11 +16,37 @@ import { Input } from 'react-aria-components/Input';
|
|
|
16
16
|
import { Label } from 'react-aria-components/Label';
|
|
17
17
|
import { ListBox, ListBoxItem } from 'react-aria-components/ListBox';
|
|
18
18
|
import { SearchField } from 'react-aria-components/SearchField';
|
|
19
|
+
import { useControlledState } from 'react-stately/useControlledState';
|
|
19
20
|
|
|
20
21
|
const FilterableListBoxDisabledContext = createContext(false);
|
|
21
22
|
function resolveEmptyState(value) {
|
|
22
23
|
return typeof value === "function" ? value() : value;
|
|
23
24
|
}
|
|
25
|
+
function getItemKey(element) {
|
|
26
|
+
const props = element.props;
|
|
27
|
+
return props.id ?? (typeof element.key === "string" ? element.key : undefined);
|
|
28
|
+
}
|
|
29
|
+
function getItemDisabled(element) {
|
|
30
|
+
return !!element.props.disabled;
|
|
31
|
+
}
|
|
32
|
+
// Computed from `children`/`items`/`disabledKeys` directly, without reading
|
|
33
|
+
// anything out of `ListBox`'s own internal filtered state — this is what the
|
|
34
|
+
// "Select all" toggle acts on, and it's always the whole collection, not just
|
|
35
|
+
// what the search currently shows.
|
|
36
|
+
function getSelectableKeys(children, items, disabledKeys) {
|
|
37
|
+
const disabledKeySet = new Set(disabledKeys);
|
|
38
|
+
const keys = [];
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
const element = children(item);
|
|
41
|
+
const key = getItemKey(element);
|
|
42
|
+
if (key !== undefined &&
|
|
43
|
+
!getItemDisabled(element) &&
|
|
44
|
+
!disabledKeySet.has(key)) {
|
|
45
|
+
keys.push(key);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return keys;
|
|
49
|
+
}
|
|
24
50
|
// `SearchField` delivers its clear-button props via RAC's `ButtonContext`,
|
|
25
51
|
// which only RAC's own `<Button>` reads automatically — this bridges it onto
|
|
26
52
|
// `IconButton`. Must render inside `<SearchField>` for the context to apply.
|
|
@@ -34,33 +60,55 @@ function ClearButton() {
|
|
|
34
60
|
* Unlike `ComboBox`, the input is a pure filter: it never holds the value,
|
|
35
61
|
* and the list never collapses.
|
|
36
62
|
*
|
|
37
|
-
* Collections can be either static (`FilterableListBoxItem` children) or
|
|
38
|
-
* dynamic (`items` paired with a function child).
|
|
39
|
-
*
|
|
40
63
|
* @component
|
|
41
64
|
*/
|
|
42
|
-
function FilterableListBox({ children, className, defaultInputValue, defaultSelectedKeys, disabled, disabledKeys, hideLabel, id, items, label, onSelectionChange, placeholder = "Search", renderEmptyState = "No results", selectedKeys, selectionMode = "multiple", ...restProps }) {
|
|
65
|
+
function FilterableListBox({ children, className, customAction, defaultInputValue, defaultSelectedKeys, disabled, disabledKeys, hideLabel, id, items, label, onSelectionChange, placeholder = "Search", renderEmptyState = "No results", selectedKeys, selectionMode = "multiple", showBulkActions = true, ...restProps }) {
|
|
43
66
|
const stableId = useId();
|
|
44
67
|
const panelId = id || `tds-filterable-list-box-${stableId}`;
|
|
45
68
|
const labelId = `${panelId}-label`;
|
|
46
69
|
const { contains } = useFilter({ sensitivity: "base" });
|
|
70
|
+
const [selection, setSelection] = useControlledState(selectedKeys, defaultSelectedKeys ?? [], onSelectionChange);
|
|
71
|
+
const [query, setQuery] = useState(defaultInputValue ?? "");
|
|
47
72
|
const handleSelectionChange = (keys) => {
|
|
48
73
|
if (keys === "all")
|
|
49
74
|
return;
|
|
50
|
-
|
|
75
|
+
setSelection([...keys]);
|
|
76
|
+
};
|
|
77
|
+
const showActions = selectionMode === "multiple" && showBulkActions && query === "";
|
|
78
|
+
const selectableKeys = useMemo(() => getSelectableKeys(children, items, disabledKeys), [children, items, disabledKeys]);
|
|
79
|
+
const selectableKeySet = new Set(selectableKeys);
|
|
80
|
+
const selectionSet = new Set(selection);
|
|
81
|
+
const allSelectableSelected = selectableKeys.length > 0 &&
|
|
82
|
+
selectableKeys.every((key) => selectionSet.has(key));
|
|
83
|
+
const handleToggleSelectAll = () => {
|
|
84
|
+
if (allSelectableSelected) {
|
|
85
|
+
setSelection(selection.filter((key) => !selectableKeySet.has(key)));
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
setSelection([...new Set([...selection, ...selectableKeys])]);
|
|
89
|
+
}
|
|
51
90
|
};
|
|
91
|
+
const toggleSelectAllLabel = allSelectableSelected
|
|
92
|
+
? "Deselect all"
|
|
93
|
+
: "Select all";
|
|
94
|
+
const toggleSelectAllAriaLabel = selectableKeys.length <= 1
|
|
95
|
+
? toggleSelectAllLabel
|
|
96
|
+
: `${toggleSelectAllLabel} ${selectableKeys.length} ${label}`;
|
|
52
97
|
return (React__default.createElement("div", { ...restProps, "aria-labelledby": labelId, className: classNames("tds-filterable-list-box", className), id: panelId, role: "group" },
|
|
53
|
-
React__default.createElement(Autocomplete, { defaultInputValue: defaultInputValue, filter: contains },
|
|
98
|
+
React__default.createElement(Autocomplete, { defaultInputValue: defaultInputValue, filter: contains, onInputChange: setQuery },
|
|
54
99
|
React__default.createElement(SearchField, { className: "tds-filterable-list-box-search-field", isDisabled: disabled },
|
|
55
100
|
React__default.createElement("div", { className: "tds-filterable-list-box-header" },
|
|
56
101
|
React__default.createElement(Label, { className: classNames("tds-filterable-list-box-label", {
|
|
57
102
|
"tds-filterable-list-box-label--hidden": hideLabel,
|
|
58
|
-
}), id: labelId }, label)
|
|
103
|
+
}), id: labelId }, label),
|
|
104
|
+
showActions && (React__default.createElement("div", { className: "tds-filterable-list-box-actions" },
|
|
105
|
+
customAction,
|
|
106
|
+
React__default.createElement(Button, { "aria-label": toggleSelectAllAriaLabel, disabled: disabled || selectableKeys.length === 0, kind: "ghost-interaction", label: toggleSelectAllLabel, onClick: handleToggleSelectAll, size: "sm" })))),
|
|
59
107
|
React__default.createElement("div", { className: "tds-filterable-list-box-search-control" },
|
|
60
108
|
React__default.createElement(Input, { className: "tds-filterable-list-box-search-input", placeholder: placeholder }),
|
|
61
109
|
React__default.createElement(ClearButton, null))),
|
|
62
110
|
React__default.createElement(FilterableListBoxDisabledContext.Provider, { value: !!disabled },
|
|
63
|
-
React__default.createElement(ListBox, { "aria-labelledby": labelId, className: "tds-filterable-list-box-list",
|
|
111
|
+
React__default.createElement(ListBox, { "aria-labelledby": labelId, className: "tds-filterable-list-box-list", disabledKeys: disabledKeys, escapeKeyBehavior: "none", items: items, onSelectionChange: handleSelectionChange, renderEmptyState: () => (React__default.createElement("div", { className: "tds-filterable-list-box-empty-state" }, resolveEmptyState(renderEmptyState))), selectedKeys: selection, selectionMode: selectionMode }, children)))));
|
|
64
112
|
}
|
|
65
113
|
FilterableListBox.displayName = "FilterableListBox";
|
|
66
114
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterableListBox.js","sources":["../../../../src/components/internal/filterable-list-box/FilterableListBox.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { IconButton } from \"@components/button\"\nimport Icon from \"@utilities/Icon\"\nimport type { CombineAriaPropsWithCustomProps } from \"@utilities/reactAriaProps\"\nimport { useId } from \"@utilities/useId\"\nimport classNames from \"classnames\"\nimport React, {\n createContext,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n useContext,\n} from \"react\"\nimport { useFilter } from \"react-aria/useFilter\"\nimport { Autocomplete } from \"react-aria-components/Autocomplete\"\nimport {\n ButtonContext,\n type ButtonProps as AriaButtonProps,\n type PressEvent,\n} from \"react-aria-components/Button\"\nimport { Input } from \"react-aria-components/Input\"\nimport { Label } from \"react-aria-components/Label\"\nimport {\n type Key,\n ListBox,\n ListBoxItem,\n type ListBoxItemProps,\n} from \"react-aria-components/ListBox\"\nimport { SearchField } from \"react-aria-components/SearchField\"\n\nconst FilterableListBoxDisabledContext = createContext(false)\n\nfunction resolveEmptyState(value: ReactNode | (() => ReactNode)): ReactNode {\n return typeof value === \"function\" ? value() : value\n}\n\n// `SearchField` delivers its clear-button props via RAC's `ButtonContext`,\n// which only RAC's own `<Button>` reads automatically — this bridges it onto\n// `IconButton`. Must render inside `<SearchField>` for the context to apply.\nfunction ClearButton() {\n const { onPress } = (useContext(ButtonContext) ?? {}) as AriaButtonProps\n\n return (\n <IconButton\n aria-label=\"Clear search\"\n className=\"tds-filterable-list-box-search-clear-button\"\n icon={<Icon aria-hidden symbol=\"general#x\" />}\n kind=\"neutral-inline\"\n onClick={() => onPress?.({} as PressEvent)}\n tabIndex={-1}\n />\n )\n}\n\nexport interface FilterableListBoxProps<T extends object = object> {\n /**\n * `FilterableListBoxItem` elements (static), or a render function paired\n * with `items` (dynamic collection) — matches `ComboBox`'s own\n * dynamic-collection convention.\n */\n children: ReactNode | ((item: T) => ReactElement)\n /** Initial uncontrolled search query. */\n defaultInputValue?: string\n /** Initial selection for the uncontrolled convenience mode. */\n defaultSelectedKeys?: Key[]\n /** Disables the search input, the clear button, and item selection. */\n disabled?: boolean\n /** Items that cannot be selected, deselected, or focused. */\n disabledKeys?: Key[]\n /** If `true`, visually hides `label` (it remains in the DOM). */\n hideLabel?: boolean\n /** Dynamic collection data. Pair with a function `children`. */\n items?: Iterable<T>\n /**\n * Label for the search input. Also supplies the accessible name for the\n * list.\n */\n label: string\n /**\n * Fired immediately on every selection change. Always a plain,\n * collection-ordered array — never a `Set`, never React Aria's `\"all\"`\n * sentinel.\n */\n onSelectionChange?: (keys: Key[]) => void\n /** Placeholder text for the search input. */\n placeholder?: string\n /** Content to display when there are no items in the list. Defaults to \"No results\". */\n renderEmptyState?: ReactNode | (() => ReactNode)\n /** Controlled selection. Presence of this prop makes the component controlled. */\n selectedKeys?: Key[]\n /** Default `\"multiple\"`. The checkbox visual only renders in `\"multiple\"` mode. */\n selectionMode?: \"multiple\" | \"single\"\n}\n\nexport type FilterableListBoxElementProps<T extends object = object> = Omit<\n HTMLAttributes<HTMLDivElement>,\n keyof FilterableListBoxProps<T> | \"aria-labelledby\"\n> &\n FilterableListBoxProps<T>\n\n/**\n * A search input paired with a persistently visible, selectable list of\n * caller-supplied options — single- or multi-select (`selectionMode`).\n * Unlike `ComboBox`, the input is a pure filter: it never holds the value,\n * and the list never collapses.\n *\n * Collections can be either static (`FilterableListBoxItem` children) or\n * dynamic (`items` paired with a function child).\n *\n * @component\n */\nexport function FilterableListBox<T extends object = object>({\n children,\n className,\n defaultInputValue,\n defaultSelectedKeys,\n disabled,\n disabledKeys,\n hideLabel,\n id,\n items,\n label,\n onSelectionChange,\n placeholder = \"Search\",\n renderEmptyState = \"No results\",\n selectedKeys,\n selectionMode = \"multiple\",\n ...restProps\n}: FilterableListBoxElementProps<T>) {\n const stableId = useId()\n const panelId = id || `tds-filterable-list-box-${stableId}`\n const labelId = `${panelId}-label`\n\n const { contains } = useFilter({ sensitivity: \"base\" })\n\n const handleSelectionChange = (keys: \"all\" | Set<Key>) => {\n if (keys === \"all\") return\n onSelectionChange?.([...keys])\n }\n\n return (\n <div\n {...restProps}\n aria-labelledby={labelId}\n className={classNames(\"tds-filterable-list-box\", className)}\n id={panelId}\n role=\"group\"\n >\n <Autocomplete defaultInputValue={defaultInputValue} filter={contains}>\n <SearchField\n className=\"tds-filterable-list-box-search-field\"\n isDisabled={disabled}\n >\n <div className=\"tds-filterable-list-box-header\">\n <Label\n className={classNames(\"tds-filterable-list-box-label\", {\n \"tds-filterable-list-box-label--hidden\": hideLabel,\n })}\n id={labelId}\n >\n {label}\n </Label>\n </div>\n <div className=\"tds-filterable-list-box-search-control\">\n <Input\n className=\"tds-filterable-list-box-search-input\"\n placeholder={placeholder}\n />\n <ClearButton />\n </div>\n </SearchField>\n\n <FilterableListBoxDisabledContext.Provider value={!!disabled}>\n <ListBox\n aria-labelledby={labelId}\n className=\"tds-filterable-list-box-list\"\n defaultSelectedKeys={defaultSelectedKeys}\n disabledKeys={disabledKeys}\n escapeKeyBehavior=\"none\"\n items={items}\n onSelectionChange={handleSelectionChange}\n renderEmptyState={() => (\n <div className=\"tds-filterable-list-box-empty-state\">\n {resolveEmptyState(renderEmptyState)}\n </div>\n )}\n selectedKeys={selectedKeys}\n selectionMode={selectionMode}\n >\n {children}\n </ListBox>\n </FilterableListBoxDisabledContext.Provider>\n </Autocomplete>\n </div>\n )\n}\n\nFilterableListBox.displayName = \"FilterableListBox\"\n\ninterface FilterableListBoxItemPropsWithTextChildren {\n /** Plain-text item content. Also supplies `textValue` automatically. */\n children: string\n /** Disables the item. */\n disabled?: boolean\n textValue?: never\n}\n\n// A plain string is structurally `Iterable<string>`, so `Iterable<ReactNode>`\n// must be excluded too, not just `string`.\ntype FilterableListBoxItemNodeChildren = Exclude<\n ReactNode,\n Iterable<ReactNode> | string\n>\n\ninterface FilterableListBoxItemPropsWithNodeChildren {\n /** Item content. */\n children: FilterableListBoxItemNodeChildren\n /** Disables the item. */\n disabled?: boolean\n /** Required — `children` isn't plain text. */\n textValue: string\n}\n\nexport type FilterableListBoxItemProps =\n | FilterableListBoxItemPropsWithNodeChildren\n | FilterableListBoxItemPropsWithTextChildren\n\ntype AriaListBoxItemPropsToInclude = \"onAction\" | \"value\"\n\nexport type FilterableListBoxItemElementProps<T extends object = object> =\n CombineAriaPropsWithCustomProps<\n ListBoxItemProps<T>,\n FilterableListBoxItemProps,\n never,\n AriaListBoxItemPropsToInclude\n >\n\n/**\n * A selectable option within a {@link FilterableListBox}. Renders as a\n * listbox item with a decorative checkbox visual (shown only in\n * `selectionMode=\"multiple\"`).\n *\n * Provide an `id` (or `key` when rendering from a collection) to identify\n * the option, and `textValue` when the visible children are not plain text\n * so filtering and screen readers have a stable label to use.\n *\n * @component\n */\nexport function FilterableListBoxItem<T extends object>({\n children,\n className,\n disabled,\n textValue,\n ...restProps\n}: FilterableListBoxItemElementProps<T>) {\n const isGroupDisabled = useContext(FilterableListBoxDisabledContext)\n\n return (\n <ListBoxItem\n {...restProps}\n className={classNames(\"tds-filterable-list-box-item\", className)}\n isDisabled={disabled || isGroupDisabled}\n textValue={\n textValue ?? (typeof children === \"string\" ? children : undefined)\n }\n >\n <span aria-hidden className=\"tds-filterable-list-box-item-checkbox\" />\n <span className=\"tds-filterable-list-box-item-label\">{children}</span>\n </ListBoxItem>\n )\n}\n\nFilterableListBoxItem.displayName = \"FilterableListBoxItem\"\n"],"names":["React"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FilterableListBox.js","sources":["../../../../src/components/internal/filterable-list-box/FilterableListBox.tsx"],"sourcesContent":["import \"./index.css\"\n\nimport { Button, IconButton } from \"@components/button\"\nimport Icon from \"@utilities/Icon\"\nimport type { CombineAriaPropsWithCustomProps } from \"@utilities/reactAriaProps\"\nimport { useId } from \"@utilities/useId\"\nimport classNames from \"classnames\"\nimport React, {\n createContext,\n type HTMLAttributes,\n type ReactElement,\n type ReactNode,\n useContext,\n useMemo,\n useState,\n} from \"react\"\nimport { useFilter } from \"react-aria/useFilter\"\nimport { Autocomplete } from \"react-aria-components/Autocomplete\"\nimport {\n ButtonContext,\n type ButtonProps as AriaButtonProps,\n type PressEvent,\n} from \"react-aria-components/Button\"\nimport { Input } from \"react-aria-components/Input\"\nimport { Label } from \"react-aria-components/Label\"\nimport {\n type Key,\n ListBox,\n ListBoxItem,\n type ListBoxItemProps,\n} from \"react-aria-components/ListBox\"\nimport { SearchField } from \"react-aria-components/SearchField\"\nimport { useControlledState } from \"react-stately/useControlledState\"\n\nconst FilterableListBoxDisabledContext = createContext(false)\n\nfunction resolveEmptyState(value: ReactNode | (() => ReactNode)): ReactNode {\n return typeof value === \"function\" ? value() : value\n}\n\nfunction getItemKey(element: ReactElement): Key | undefined {\n const props = element.props as { id?: Key }\n return props.id ?? (typeof element.key === \"string\" ? element.key : undefined)\n}\n\nfunction getItemDisabled(element: ReactElement): boolean {\n return !!(element.props as { disabled?: boolean }).disabled\n}\n\n// Computed from `children`/`items`/`disabledKeys` directly, without reading\n// anything out of `ListBox`'s own internal filtered state — this is what the\n// \"Select all\" toggle acts on, and it's always the whole collection, not just\n// what the search currently shows.\nfunction getSelectableKeys<T extends object>(\n children: (item: T) => ReactElement,\n items: Iterable<T>,\n disabledKeys: Key[] | undefined\n): Key[] {\n const disabledKeySet = new Set(disabledKeys)\n const keys: Key[] = []\n\n for (const item of items) {\n const element = children(item)\n const key = getItemKey(element)\n if (\n key !== undefined &&\n !getItemDisabled(element) &&\n !disabledKeySet.has(key)\n ) {\n keys.push(key)\n }\n }\n\n return keys\n}\n\n// `SearchField` delivers its clear-button props via RAC's `ButtonContext`,\n// which only RAC's own `<Button>` reads automatically — this bridges it onto\n// `IconButton`. Must render inside `<SearchField>` for the context to apply.\nfunction ClearButton() {\n const { onPress } = (useContext(ButtonContext) ?? {}) as AriaButtonProps\n\n return (\n <IconButton\n aria-label=\"Clear search\"\n className=\"tds-filterable-list-box-search-clear-button\"\n icon={<Icon aria-hidden symbol=\"general#x\" />}\n kind=\"neutral-inline\"\n onClick={() => onPress?.({} as PressEvent)}\n tabIndex={-1}\n />\n )\n}\n\nexport interface FilterableListBoxProps<T extends object = object> {\n /**\n * Render function paired with `items` — matches `ComboBox`'s own\n * dynamic-collection convention.\n */\n children: (item: T) => ReactElement\n /**\n * Optional action rendered next to the built-in \"Select all\"/\"Deselect\n * all\" toggle (e.g. \"Select my teams\"). Requires `selectionMode=\"multiple\"`\n * and `showBulkActions`, and hides while filtering along with the toggle.\n * Fully caller-owned: bring your own `onClick`/selection logic, and pass\n * `disabled` to the action element yourself if it should follow the\n * list's `disabled` prop — `FilterableListBox` doesn't forward it.\n */\n customAction?: ReactNode\n /** Initial uncontrolled search query. */\n defaultInputValue?: string\n /** Initial selection for the uncontrolled convenience mode. */\n defaultSelectedKeys?: Key[]\n /** Disables the search input, the clear button, and item selection. */\n disabled?: boolean\n /** Items that cannot be selected, deselected, or focused. */\n disabledKeys?: Key[]\n /** If `true`, visually hides `label` (it remains in the DOM). */\n hideLabel?: boolean\n /** Collection data. Pair with a function `children`. */\n items: Iterable<T>\n /**\n * Label for the search input. Also supplies the accessible name for the\n * list.\n */\n label: string\n /**\n * Fired immediately on every selection change. Always a plain,\n * collection-ordered array — never a `Set`, never React Aria's `\"all\"`\n * sentinel.\n */\n onSelectionChange?: (keys: Key[]) => void\n /** Placeholder text for the search input. */\n placeholder?: string\n /** Content to display when there are no items in the list. Defaults to \"No results\". */\n renderEmptyState?: ReactNode | (() => ReactNode)\n /** Controlled selection. Presence of this prop makes the component controlled. */\n selectedKeys?: Key[]\n /** Default `\"multiple\"`. The checkbox visual only renders in `\"multiple\"` mode. */\n selectionMode?: \"multiple\" | \"single\"\n /**\n * Shows the built-in \"Select all\"/\"Deselect all\" toggle (and the\n * `customAction` slot next to it). Meaningless in `selectionMode=\"single\"`.\n * Default `true`.\n */\n showBulkActions?: boolean\n}\n\nexport type FilterableListBoxElementProps<T extends object = object> = Omit<\n HTMLAttributes<HTMLDivElement>,\n keyof FilterableListBoxProps<T> | \"aria-labelledby\"\n> &\n FilterableListBoxProps<T>\n\n/**\n * A search input paired with a persistently visible, selectable list of\n * caller-supplied options — single- or multi-select (`selectionMode`).\n * Unlike `ComboBox`, the input is a pure filter: it never holds the value,\n * and the list never collapses.\n *\n * @component\n */\nexport function FilterableListBox<T extends object = object>({\n children,\n className,\n customAction,\n defaultInputValue,\n defaultSelectedKeys,\n disabled,\n disabledKeys,\n hideLabel,\n id,\n items,\n label,\n onSelectionChange,\n placeholder = \"Search\",\n renderEmptyState = \"No results\",\n selectedKeys,\n selectionMode = \"multiple\",\n showBulkActions = true,\n ...restProps\n}: FilterableListBoxElementProps<T>) {\n const stableId = useId()\n const panelId = id || `tds-filterable-list-box-${stableId}`\n const labelId = `${panelId}-label`\n\n const { contains } = useFilter({ sensitivity: \"base\" })\n\n const [selection, setSelection] = useControlledState<Key[]>(\n selectedKeys,\n defaultSelectedKeys ?? [],\n onSelectionChange\n )\n\n const [query, setQuery] = useState(defaultInputValue ?? \"\")\n\n const handleSelectionChange = (keys: \"all\" | Set<Key>) => {\n if (keys === \"all\") return\n setSelection([...keys])\n }\n\n const showActions =\n selectionMode === \"multiple\" && showBulkActions && query === \"\"\n\n const selectableKeys = useMemo(\n () => getSelectableKeys(children, items, disabledKeys),\n [children, items, disabledKeys]\n )\n const selectableKeySet = new Set(selectableKeys)\n const selectionSet = new Set(selection)\n const allSelectableSelected =\n selectableKeys.length > 0 &&\n selectableKeys.every((key) => selectionSet.has(key))\n\n const handleToggleSelectAll = () => {\n if (allSelectableSelected) {\n setSelection(selection.filter((key) => !selectableKeySet.has(key)))\n } else {\n setSelection([...new Set([...selection, ...selectableKeys])])\n }\n }\n\n const toggleSelectAllLabel = allSelectableSelected\n ? \"Deselect all\"\n : \"Select all\"\n\n const toggleSelectAllAriaLabel =\n selectableKeys.length <= 1\n ? toggleSelectAllLabel\n : `${toggleSelectAllLabel} ${selectableKeys.length} ${label}`\n\n return (\n <div\n {...restProps}\n aria-labelledby={labelId}\n className={classNames(\"tds-filterable-list-box\", className)}\n id={panelId}\n role=\"group\"\n >\n <Autocomplete\n defaultInputValue={defaultInputValue}\n filter={contains}\n onInputChange={setQuery}\n >\n <SearchField\n className=\"tds-filterable-list-box-search-field\"\n isDisabled={disabled}\n >\n <div className=\"tds-filterable-list-box-header\">\n <Label\n className={classNames(\"tds-filterable-list-box-label\", {\n \"tds-filterable-list-box-label--hidden\": hideLabel,\n })}\n id={labelId}\n >\n {label}\n </Label>\n {showActions && (\n <div className=\"tds-filterable-list-box-actions\">\n {customAction}\n <Button\n aria-label={toggleSelectAllAriaLabel}\n disabled={disabled || selectableKeys.length === 0}\n kind=\"ghost-interaction\"\n label={toggleSelectAllLabel}\n onClick={handleToggleSelectAll}\n size=\"sm\"\n />\n </div>\n )}\n </div>\n <div className=\"tds-filterable-list-box-search-control\">\n <Input\n className=\"tds-filterable-list-box-search-input\"\n placeholder={placeholder}\n />\n <ClearButton />\n </div>\n </SearchField>\n\n <FilterableListBoxDisabledContext.Provider value={!!disabled}>\n <ListBox\n aria-labelledby={labelId}\n className=\"tds-filterable-list-box-list\"\n disabledKeys={disabledKeys}\n escapeKeyBehavior=\"none\"\n items={items}\n onSelectionChange={handleSelectionChange}\n renderEmptyState={() => (\n <div className=\"tds-filterable-list-box-empty-state\">\n {resolveEmptyState(renderEmptyState)}\n </div>\n )}\n selectedKeys={selection}\n selectionMode={selectionMode}\n >\n {children}\n </ListBox>\n </FilterableListBoxDisabledContext.Provider>\n </Autocomplete>\n </div>\n )\n}\n\nFilterableListBox.displayName = \"FilterableListBox\"\n\ninterface FilterableListBoxItemPropsWithTextChildren {\n /** Plain-text item content. Also supplies `textValue` automatically. */\n children: string\n /** Disables the item. */\n disabled?: boolean\n textValue?: never\n}\n\n// A plain string is structurally `Iterable<string>`, so `Iterable<ReactNode>`\n// must be excluded too, not just `string`.\ntype FilterableListBoxItemNodeChildren = Exclude<\n ReactNode,\n Iterable<ReactNode> | string\n>\n\ninterface FilterableListBoxItemPropsWithNodeChildren {\n /** Item content. */\n children: FilterableListBoxItemNodeChildren\n /** Disables the item. */\n disabled?: boolean\n /** Required — `children` isn't plain text. */\n textValue: string\n}\n\nexport type FilterableListBoxItemProps =\n | FilterableListBoxItemPropsWithNodeChildren\n | FilterableListBoxItemPropsWithTextChildren\n\ntype AriaListBoxItemPropsToInclude = \"onAction\" | \"value\"\n\nexport type FilterableListBoxItemElementProps<T extends object = object> =\n CombineAriaPropsWithCustomProps<\n ListBoxItemProps<T>,\n FilterableListBoxItemProps,\n never,\n AriaListBoxItemPropsToInclude\n >\n\n/**\n * A selectable option within a {@link FilterableListBox}. Renders as a\n * listbox item with a decorative checkbox visual (shown only in\n * `selectionMode=\"multiple\"`).\n *\n * Provide an `id` (or `key` when rendering from a collection) to identify\n * the option, and `textValue` when the visible children are not plain text\n * so filtering and screen readers have a stable label to use.\n *\n * @component\n */\nexport function FilterableListBoxItem<T extends object>({\n children,\n className,\n disabled,\n textValue,\n ...restProps\n}: FilterableListBoxItemElementProps<T>) {\n const isGroupDisabled = useContext(FilterableListBoxDisabledContext)\n\n return (\n <ListBoxItem\n {...restProps}\n className={classNames(\"tds-filterable-list-box-item\", className)}\n isDisabled={disabled || isGroupDisabled}\n textValue={\n textValue ?? (typeof children === \"string\" ? children : undefined)\n }\n >\n <span aria-hidden className=\"tds-filterable-list-box-item-checkbox\" />\n <span className=\"tds-filterable-list-box-item-label\">{children}</span>\n </ListBoxItem>\n )\n}\n\nFilterableListBoxItem.displayName = \"FilterableListBoxItem\"\n"],"names":["React"],"mappings":"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"}
|