@planningcenter/tapestry 4.12.0 → 4.13.0-rc.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/dist/componentRegistrars.d.ts +1 -0
- package/dist/componentRegistrars.d.ts.map +1 -1
- package/dist/componentRegistrars.js +6 -2
- package/dist/componentRegistrars.js.map +1 -1
- package/dist/components/DataTable/ActiveFilters.d.ts +23 -1
- package/dist/components/DataTable/ActiveFilters.d.ts.map +1 -1
- package/dist/components/DataTable/ActiveFilters.js +49 -4
- package/dist/components/DataTable/ActiveFilters.js.map +1 -1
- package/dist/components/DataTable/CollectionEditorsLazy.d.ts.map +1 -1
- package/dist/components/DataTable/CollectionEditorsLazy.js +20 -18
- package/dist/components/DataTable/CollectionEditorsLazy.js.map +1 -1
- package/dist/components/DataTable/DataTable.d.ts +66 -50
- package/dist/components/DataTable/DataTable.d.ts.map +1 -1
- package/dist/components/DataTable/DataTable.js +78 -17
- package/dist/components/DataTable/DataTable.js.map +1 -1
- package/dist/components/DataTable/ManageSavedViewsModal.d.ts +18 -7
- package/dist/components/DataTable/ManageSavedViewsModal.d.ts.map +1 -1
- package/dist/components/DataTable/ManageSavedViewsModal.js +120 -104
- package/dist/components/DataTable/ManageSavedViewsModal.js.map +1 -1
- package/dist/components/DataTable/ManageSavedViewsModal.module.css.js +1 -1
- package/dist/components/DataTable/NewSavedViewButton.d.ts.map +1 -1
- package/dist/components/DataTable/NewSavedViewButton.js +1 -1
- package/dist/components/DataTable/NewSavedViewButton.js.map +1 -1
- package/dist/components/DataTable/SavedViewActions.d.ts +19 -0
- package/dist/components/DataTable/SavedViewActions.d.ts.map +1 -0
- package/dist/components/DataTable/SavedViewActions.js +34 -0
- package/dist/components/DataTable/SavedViewActions.js.map +1 -0
- package/dist/components/DataTable/SavedViewMenu.d.ts +18 -0
- package/dist/components/DataTable/SavedViewMenu.d.ts.map +1 -0
- package/dist/components/DataTable/SavedViewMenu.js +25 -0
- package/dist/components/DataTable/SavedViewMenu.js.map +1 -0
- package/dist/components/DataTable/SavedViewsBar.d.ts +25 -13
- package/dist/components/DataTable/SavedViewsBar.d.ts.map +1 -1
- package/dist/components/DataTable/SavedViewsBar.js +43 -83
- package/dist/components/DataTable/SavedViewsBar.js.map +1 -1
- package/dist/components/DataTable/TableActions.d.ts +4 -2
- package/dist/components/DataTable/TableActions.d.ts.map +1 -1
- package/dist/components/DataTable/TableActions.js +11 -14
- package/dist/components/DataTable/TableActions.js.map +1 -1
- package/dist/components/DataTable/index.d.ts +2 -0
- package/dist/components/DataTable/index.d.ts.map +1 -1
- package/dist/components/DataTable/saveErrorMessage.d.ts +3 -0
- package/dist/components/DataTable/saveErrorMessage.d.ts.map +1 -0
- package/dist/components/DataTable/saveErrorMessage.js +9 -0
- package/dist/components/DataTable/saveErrorMessage.js.map +1 -0
- package/dist/components/DataTable/savedViews.d.ts +15 -30
- package/dist/components/DataTable/savedViews.d.ts.map +1 -1
- package/dist/components/DataTable/savedViews.js +51 -98
- package/dist/components/DataTable/savedViews.js.map +1 -1
- package/dist/components/DataTable/useActiveSavedView.d.ts +1 -1
- package/dist/components/DataTable/useActiveSavedView.d.ts.map +1 -1
- package/dist/components/DataTable/useActiveSavedView.js +5 -4
- package/dist/components/DataTable/useActiveSavedView.js.map +1 -1
- package/dist/components/DataTable/useColumnVisibility.d.ts +7 -7
- package/dist/components/DataTable/useColumnVisibility.d.ts.map +1 -1
- package/dist/components/DataTable/useColumnVisibility.js +14 -14
- package/dist/components/DataTable/useColumnVisibility.js.map +1 -1
- package/dist/components/DataTable/useResultsAnnouncement.d.ts +26 -0
- package/dist/components/DataTable/useResultsAnnouncement.d.ts.map +1 -0
- package/dist/components/DataTable/{useFilterAnnouncement.js → useResultsAnnouncement.js} +16 -12
- package/dist/components/DataTable/useResultsAnnouncement.js.map +1 -0
- package/dist/components/DataTable/useSaveStatus.d.ts +32 -0
- package/dist/components/DataTable/useSaveStatus.d.ts.map +1 -0
- package/dist/components/DataTable/useSaveStatus.js +80 -0
- package/dist/components/DataTable/useSaveStatus.js.map +1 -0
- package/dist/components/DataTable/useSavedViewNameModal.d.ts +14 -0
- package/dist/components/DataTable/useSavedViewNameModal.d.ts.map +1 -0
- package/dist/components/DataTable/useSavedViewNameModal.js +43 -0
- package/dist/components/DataTable/useSavedViewNameModal.js.map +1 -0
- package/dist/components/DataTable/useSavedViewsClient.d.ts +58 -0
- package/dist/components/DataTable/useSavedViewsClient.d.ts.map +1 -0
- package/dist/components/DataTable/useSavedViewsClient.js +87 -0
- package/dist/components/DataTable/useSavedViewsClient.js.map +1 -0
- package/dist/components/DataTable/useSavedViewsState.d.ts +35 -0
- package/dist/components/DataTable/useSavedViewsState.d.ts.map +1 -0
- package/dist/components/DataTable/useSavedViewsState.js +192 -0
- package/dist/components/DataTable/useSavedViewsState.js.map +1 -0
- package/dist/components/DataTable/useUpdateSavedViewName.d.ts +2 -3
- package/dist/components/DataTable/useUpdateSavedViewName.d.ts.map +1 -1
- package/dist/components/DataTable/useUpdateSavedViewName.js +1 -2
- package/dist/components/DataTable/useUpdateSavedViewName.js.map +1 -1
- package/dist/components/button/BaseButton.d.ts +2 -0
- package/dist/components/button/BaseButton.d.ts.map +1 -1
- package/dist/components/button/BaseButton.js +8 -17
- package/dist/components/button/BaseButton.js.map +1 -1
- package/dist/components/internal/LoadingAnnouncement.d.ts +6 -0
- package/dist/components/internal/LoadingAnnouncement.d.ts.map +1 -1
- package/dist/components/internal/LoadingAnnouncement.js +12 -3
- package/dist/components/internal/LoadingAnnouncement.js.map +1 -1
- package/dist/components/internal/index.d.ts +1 -1
- package/dist/components/internal/index.d.ts.map +1 -1
- package/dist/components/internal/tooltip/useTooltipTriggerProps.d.ts +13 -0
- package/dist/components/internal/tooltip/useTooltipTriggerProps.d.ts.map +1 -0
- package/dist/components/internal/tooltip/useTooltipTriggerProps.js +24 -0
- package/dist/components/internal/tooltip/useTooltipTriggerProps.js.map +1 -0
- package/dist/components/link/BaseLink.d.ts.map +1 -1
- package/dist/components/link/BaseLink.js +3 -13
- package/dist/components/link/BaseLink.js.map +1 -1
- package/dist/components/segmented-control/IconSegment.d.ts +30 -8
- package/dist/components/segmented-control/IconSegment.d.ts.map +1 -1
- package/dist/components/segmented-control/IconSegment.js +10 -3
- package/dist/components/segmented-control/IconSegment.js.map +1 -1
- package/dist/components/segmented-control/SegmentBase.d.ts.map +1 -1
- package/dist/components/segmented-control/SegmentBase.js +4 -2
- package/dist/components/segmented-control/SegmentBase.js.map +1 -1
- package/dist/components/segmented-control/SegmentedControl.d.ts.map +1 -1
- package/dist/components/segmented-control/SegmentedControl.js +10 -3
- package/dist/components/segmented-control/SegmentedControl.js.map +1 -1
- package/dist/index.css +20 -5
- package/dist/index.css.map +1 -1
- package/dist/reactRender.css +1409 -1319
- package/dist/reactRender.css.map +1 -1
- package/dist/reactRender.js +1 -1
- package/dist/reactRenderLegacy.css +1409 -1319
- package/dist/reactRenderLegacy.css.map +1 -1
- package/dist/reactRenderLegacy.js +1 -1
- package/dist/tapestryMount.css +1409 -1319
- package/dist/tapestryMount.css.map +1 -1
- package/dist/tapestryMount.js +1 -1
- package/dist/tapestryMountLegacy.css +1409 -1319
- package/dist/tapestryMountLegacy.css.map +1 -1
- package/dist/tapestryMountLegacy.js +1 -1
- package/dist/tokens-dark.css +2 -2
- package/dist/tokens-dark.css.map +1 -1
- package/dist/tokens.css +3 -3
- package/dist/tokens.css.map +1 -1
- package/dist/unstable.css +385 -295
- package/dist/unstable.css.map +1 -1
- package/dist/unstable.js +1 -0
- package/dist/unstable.js.map +1 -1
- package/package.json +6 -7
- package/dist/components/DataTable/useFilterAnnouncement.d.ts +0 -24
- package/dist/components/DataTable/useFilterAnnouncement.d.ts.map +0 -1
- package/dist/components/DataTable/useFilterAnnouncement.js.map +0 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Sort } from "./DataTable";
|
|
2
2
|
import type { ActiveFilter } from "./FilterTypes";
|
|
3
3
|
export interface SavedView {
|
|
4
|
+
/** A search (`q`) filter is ignored; applying a view keeps the search. */
|
|
4
5
|
activeFilters: ActiveFilter[];
|
|
5
6
|
/** Omit to leave the current column visibility unchanged when applied. */
|
|
6
7
|
hiddenColumnNames?: string[];
|
|
@@ -9,11 +10,6 @@ export interface SavedView {
|
|
|
9
10
|
name: string;
|
|
10
11
|
/** Omit to leave the current sort unchanged when applied. */
|
|
11
12
|
sort?: Sort;
|
|
12
|
-
/**
|
|
13
|
-
* The view's position, as a fractional index. Views without one are listed
|
|
14
|
-
* after those with one.
|
|
15
|
-
*/
|
|
16
|
-
sortKey?: string;
|
|
17
13
|
}
|
|
18
14
|
/** An app-provided view. Read-only. */
|
|
19
15
|
export type StaticView = SavedView & {
|
|
@@ -25,36 +21,25 @@ export type StaticView = SavedView & {
|
|
|
25
21
|
*/
|
|
26
22
|
export declare const initialView: StaticView;
|
|
27
23
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* A view from the app, without any search. Search isn't part of a view, so
|
|
25
|
+
* views enter the table without it. A view without one is returned as is.
|
|
30
26
|
*/
|
|
31
|
-
export declare function
|
|
27
|
+
export declare function withoutSearch<View extends SavedView>(view: View): View;
|
|
32
28
|
/**
|
|
33
|
-
* A
|
|
34
|
-
*
|
|
35
|
-
* without one stays at the end.
|
|
29
|
+
* A duplicate of a view under a new name. It's a new view, so it doesn't keep
|
|
30
|
+
* the original's id.
|
|
36
31
|
*/
|
|
37
|
-
export declare function
|
|
32
|
+
export declare function savedViewDuplicate({ id: _id, ...view }: SavedView, name: string): Omit<SavedView, "id">;
|
|
38
33
|
/**
|
|
39
|
-
*
|
|
40
|
-
* view
|
|
41
|
-
* view, with a key between its new neighbors'. Dropping among views without
|
|
42
|
-
* a key also gives a key to those before it, since they come after every
|
|
43
|
-
* view with one.
|
|
34
|
+
* Whether a view can be renamed, deleted, or saved over. Static views can't,
|
|
35
|
+
* and neither can a view that isn't saved yet, since updates are matched by id.
|
|
44
36
|
*/
|
|
45
|
-
export declare function
|
|
46
|
-
interface SavedViewState {
|
|
47
|
-
activeFilters: ActiveFilter[];
|
|
48
|
-
hiddenColumnNames: string[];
|
|
49
|
-
sort: Sort | undefined;
|
|
50
|
-
}
|
|
37
|
+
export declare function isSavedViewEditable(view: SavedView, savedViews: SavedView[]): boolean;
|
|
51
38
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* more specific. Among equally specific matches, the first wins.
|
|
39
|
+
* Whether the table's filters, sort, or hidden columns differ from a view.
|
|
40
|
+
* Filters match by name, operator, scope, and value, in any order; labels are
|
|
41
|
+
* display-only. Sort and hidden columns count only when both the view and the
|
|
42
|
+
* table have them.
|
|
57
43
|
*/
|
|
58
|
-
export declare function
|
|
59
|
-
export {};
|
|
44
|
+
export declare function isSavedViewModified(view: SavedView, { activeFilters, hiddenColumnNames, sort }: Omit<SavedView, "id" | "name">): boolean;
|
|
60
45
|
//# sourceMappingURL=savedViews.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savedViews.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/savedViews.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"savedViews.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/savedViews.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAGjD,MAAM,WAAW,SAAS;IACxB,0EAA0E;IAC1E,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,uDAAuD;IACvD,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,6DAA6D;IAC7D,IAAI,CAAC,EAAE,IAAI,CAAA;CACZ;AAED,uCAAuC;AACvC,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG;IAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AAE5D;;;GAGG;AACH,eAAO,MAAM,WAAW,EAAE,UAIzB,CAAA;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAS,SAAS,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAQtE;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,SAAS,EAC/B,IAAI,EAAE,MAAM,GACX,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAEvB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,WAE3E;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,SAAS,EACf,EAAE,aAAa,EAAE,iBAAiB,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC,WAoB3E"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isSearchFilter } from './useSearchFilter.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Always listed, so there's a view to fall back to. Added before the static
|
|
@@ -10,121 +10,74 @@ const initialView = {
|
|
|
10
10
|
name: "All",
|
|
11
11
|
};
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* A view from the app, without any search. Search isn't part of a view, so
|
|
14
|
+
* views enter the table without it. A view without one is returned as is.
|
|
15
15
|
*/
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
return -1;
|
|
24
|
-
return a.sortKey < b.sortKey ? -1 : 1;
|
|
25
|
-
});
|
|
16
|
+
function withoutSearch(view) {
|
|
17
|
+
if (!view.activeFilters.some(isSearchFilter))
|
|
18
|
+
return view;
|
|
19
|
+
return {
|
|
20
|
+
...view,
|
|
21
|
+
activeFilters: view.activeFilters.filter((filter) => !isSearchFilter(filter)),
|
|
22
|
+
};
|
|
26
23
|
}
|
|
27
24
|
/**
|
|
28
|
-
* A
|
|
29
|
-
*
|
|
30
|
-
* without one stays at the end.
|
|
25
|
+
* A duplicate of a view under a new name. It's a new view, so it doesn't keep
|
|
26
|
+
* the original's id.
|
|
31
27
|
*/
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
for (const { sortKey } of savedViews) {
|
|
35
|
-
if (sortKey === undefined)
|
|
36
|
-
return undefined;
|
|
37
|
-
if (lastSortKey === null || sortKey > lastSortKey)
|
|
38
|
-
lastSortKey = sortKey;
|
|
39
|
-
}
|
|
40
|
-
return generateKeyBetween(lastSortKey, null);
|
|
28
|
+
function savedViewDuplicate({ id: _id, ...view }, name) {
|
|
29
|
+
return { ...view, name };
|
|
41
30
|
}
|
|
42
31
|
/**
|
|
43
|
-
*
|
|
44
|
-
* view
|
|
45
|
-
* view, with a key between its new neighbors'. Dropping among views without
|
|
46
|
-
* a key also gives a key to those before it, since they come after every
|
|
47
|
-
* view with one.
|
|
32
|
+
* Whether a view can be renamed, deleted, or saved over. Static views can't,
|
|
33
|
+
* and neither can a view that isn't saved yet, since updates are matched by id.
|
|
48
34
|
*/
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
// Taking the view out shifts the views after it up one.
|
|
52
|
-
if (from < to)
|
|
53
|
-
to--;
|
|
54
|
-
if (from === to)
|
|
55
|
-
return [];
|
|
56
|
-
const newOrder = [...savedViews];
|
|
57
|
-
const [view] = newOrder.splice(from, 1);
|
|
58
|
-
newOrder.splice(to, 0, view);
|
|
59
|
-
let start = to;
|
|
60
|
-
while (start > 0 && newOrder[start - 1].sortKey === undefined)
|
|
61
|
-
start--;
|
|
62
|
-
const keys = generateNKeysBetween(newOrder[start - 1]?.sortKey ?? null, newOrder[to + 1]?.sortKey ?? null, to - start + 1);
|
|
63
|
-
return newOrder
|
|
64
|
-
.slice(start, to + 1)
|
|
65
|
-
.map((view, index) => ({ ...view, sortKey: keys[index] }));
|
|
35
|
+
function isSavedViewEditable(view, savedViews) {
|
|
36
|
+
return view.id !== undefined && savedViews.includes(view);
|
|
66
37
|
}
|
|
67
38
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* more specific. Among equally specific matches, the first wins.
|
|
39
|
+
* Whether the table's filters, sort, or hidden columns differ from a view.
|
|
40
|
+
* Filters match by name, operator, scope, and value, in any order; labels are
|
|
41
|
+
* display-only. Sort and hidden columns count only when both the view and the
|
|
42
|
+
* table have them.
|
|
73
43
|
*/
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
specificity(view) > specificity(mostSpecificMatch)) {
|
|
81
|
-
mostSpecificMatch = view;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return mostSpecificMatch;
|
|
85
|
-
}
|
|
86
|
-
function specificity(view) {
|
|
87
|
-
return Number(Boolean(view.sort)) + Number(Boolean(view.hiddenColumnNames));
|
|
88
|
-
}
|
|
89
|
-
function savedViewMatches(view, state) {
|
|
90
|
-
if (!filtersMatch(view.activeFilters, state.activeFilters))
|
|
91
|
-
return false;
|
|
92
|
-
if (view.sort && !sortMatches(view.sort, state.sort))
|
|
93
|
-
return false;
|
|
94
|
-
return (!view.hiddenColumnNames ||
|
|
95
|
-
namesMatch(view.hiddenColumnNames, state.hiddenColumnNames));
|
|
96
|
-
}
|
|
97
|
-
function filtersMatch(a, b) {
|
|
98
|
-
if (a.length !== b.length)
|
|
99
|
-
return false;
|
|
100
|
-
const unmatched = [...b];
|
|
101
|
-
return a.every((filter) => {
|
|
102
|
-
const index = unmatched.findIndex((other) => filterMatches(filter, other));
|
|
103
|
-
if (index === -1)
|
|
104
|
-
return false;
|
|
105
|
-
unmatched.splice(index, 1);
|
|
44
|
+
function isSavedViewModified(view, { activeFilters, hiddenColumnNames, sort }) {
|
|
45
|
+
if (view.sort && sort && !isSameSort(view.sort, sort))
|
|
46
|
+
return true;
|
|
47
|
+
if (view.hiddenColumnNames &&
|
|
48
|
+
hiddenColumnNames &&
|
|
49
|
+
!isSameNames(view.hiddenColumnNames, hiddenColumnNames))
|
|
106
50
|
return true;
|
|
107
|
-
|
|
51
|
+
// Each saved filter matches one current filter, so repeats count.
|
|
52
|
+
const unmatched = [...view.activeFilters];
|
|
53
|
+
return (activeFilters.length !== unmatched.length ||
|
|
54
|
+
!activeFilters.every((filter) => {
|
|
55
|
+
const index = unmatched.findIndex((saved) => isSameFilter(saved, filter));
|
|
56
|
+
if (index === -1)
|
|
57
|
+
return false;
|
|
58
|
+
unmatched.splice(index, 1);
|
|
59
|
+
return true;
|
|
60
|
+
}));
|
|
108
61
|
}
|
|
109
|
-
function
|
|
62
|
+
function isSameFilter(a, b) {
|
|
110
63
|
return (a.name === b.name &&
|
|
111
64
|
a.operator === b.operator &&
|
|
112
65
|
a.scope === b.scope &&
|
|
113
|
-
|
|
66
|
+
isSameValue(a.value, b.value));
|
|
114
67
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
68
|
+
// Filter values are primitives or flat arrays, such as ranges. Arrays match
|
|
69
|
+
// only in the same order.
|
|
70
|
+
function isSameValue(a, b) {
|
|
71
|
+
if (!Array.isArray(a) || !Array.isArray(b))
|
|
72
|
+
return a === b;
|
|
73
|
+
return a.length === b.length && a.every((item, index) => item === b[index]);
|
|
120
74
|
}
|
|
121
|
-
function
|
|
122
|
-
return a.column === b
|
|
75
|
+
function isSameSort(a, b) {
|
|
76
|
+
return a.column === b.column && a.direction === b.direction;
|
|
123
77
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
return new Set(a).size === names.size && a.every((name) => names.has(name));
|
|
78
|
+
function isSameNames(a, b) {
|
|
79
|
+
return a.length === b.length && a.every((name) => b.includes(name));
|
|
127
80
|
}
|
|
128
81
|
|
|
129
|
-
export {
|
|
82
|
+
export { initialView, isSavedViewEditable, isSavedViewModified, savedViewDuplicate, withoutSearch };
|
|
130
83
|
//# sourceMappingURL=savedViews.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"savedViews.js","sources":["../../../src/components/DataTable/savedViews.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"savedViews.js","sources":["../../../src/components/DataTable/savedViews.ts"],"sourcesContent":["import type { Sort } from \"./DataTable\"\nimport type { ActiveFilter } from \"./FilterTypes\"\nimport { isSearchFilter } from \"./useSearchFilter\"\n\nexport interface SavedView {\n /** A search (`q`) filter is ignored; applying a view keeps the search. */\n activeFilters: ActiveFilter[]\n /** Omit to leave the current column visibility unchanged when applied. */\n hiddenColumnNames?: string[]\n /** Assigned by the consumer once the view is saved. */\n id?: number | string\n name: string\n /** Omit to leave the current sort unchanged when applied. */\n sort?: Sort\n}\n\n/** An app-provided view. Read-only. */\nexport type StaticView = SavedView & { id: number | string }\n\n/**\n * Always listed, so there's a view to fall back to. Added before the static\n * views unless one of them uses its id, which replaces it.\n */\nexport const initialView: StaticView = {\n activeFilters: [],\n id: \"initial\",\n name: \"All\",\n}\n\n/**\n * A view from the app, without any search. Search isn't part of a view, so\n * views enter the table without it. A view without one is returned as is.\n */\nexport function withoutSearch<View extends SavedView>(view: View): View {\n if (!view.activeFilters.some(isSearchFilter)) return view\n return {\n ...view,\n activeFilters: view.activeFilters.filter(\n (filter) => !isSearchFilter(filter)\n ),\n }\n}\n\n/**\n * A duplicate of a view under a new name. It's a new view, so it doesn't keep\n * the original's id.\n */\nexport function savedViewDuplicate(\n { id: _id, ...view }: SavedView,\n name: string\n): Omit<SavedView, \"id\"> {\n return { ...view, name }\n}\n\n/**\n * Whether a view can be renamed, deleted, or saved over. Static views can't,\n * and neither can a view that isn't saved yet, since updates are matched by id.\n */\nexport function isSavedViewEditable(view: SavedView, savedViews: SavedView[]) {\n return view.id !== undefined && savedViews.includes(view)\n}\n\n/**\n * Whether the table's filters, sort, or hidden columns differ from a view.\n * Filters match by name, operator, scope, and value, in any order; labels are\n * display-only. Sort and hidden columns count only when both the view and the\n * table have them.\n */\nexport function isSavedViewModified(\n view: SavedView,\n { activeFilters, hiddenColumnNames, sort }: Omit<SavedView, \"id\" | \"name\">\n) {\n if (view.sort && sort && !isSameSort(view.sort, sort)) return true\n if (\n view.hiddenColumnNames &&\n hiddenColumnNames &&\n !isSameNames(view.hiddenColumnNames, hiddenColumnNames)\n )\n return true\n // Each saved filter matches one current filter, so repeats count.\n const unmatched = [...view.activeFilters]\n return (\n activeFilters.length !== unmatched.length ||\n !activeFilters.every((filter) => {\n const index = unmatched.findIndex((saved) => isSameFilter(saved, filter))\n if (index === -1) return false\n unmatched.splice(index, 1)\n return true\n })\n )\n}\n\nfunction isSameFilter(a: ActiveFilter, b: ActiveFilter) {\n return (\n a.name === b.name &&\n a.operator === b.operator &&\n a.scope === b.scope &&\n isSameValue(a.value, b.value)\n )\n}\n\n// Filter values are primitives or flat arrays, such as ranges. Arrays match\n// only in the same order.\nfunction isSameValue(a: unknown, b: unknown) {\n if (!Array.isArray(a) || !Array.isArray(b)) return a === b\n return a.length === b.length && a.every((item, index) => item === b[index])\n}\n\nfunction isSameSort(a: Sort, b: Sort) {\n return a.column === b.column && a.direction === b.direction\n}\n\nfunction isSameNames(a: string[], b: string[]) {\n return a.length === b.length && a.every((name) => b.includes(name))\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,6 @@ import type { SavedView } from "./savedViews";
|
|
|
2
2
|
export declare function useActiveSavedView(savedViews: SavedView[], getInitialView: () => SavedView | undefined): {
|
|
3
3
|
activeSavedView: SavedView | undefined;
|
|
4
4
|
select: (view: SavedView) => void;
|
|
5
|
-
selectWhenSaved: (savedView: Promise<SavedView> | SavedView) => Promise<
|
|
5
|
+
selectWhenSaved: (savedView: Promise<SavedView | undefined> | SavedView) => Promise<SavedView | undefined>;
|
|
6
6
|
};
|
|
7
7
|
//# sourceMappingURL=useActiveSavedView.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActiveSavedView.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useActiveSavedView.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,SAAS,EAAE,EACvB,cAAc,EAAE,MAAM,SAAS,GAAG,SAAS;;mBAY1B,SAAS;
|
|
1
|
+
{"version":3,"file":"useActiveSavedView.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useActiveSavedView.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,SAAS,EAAE,EACvB,cAAc,EAAE,MAAM,SAAS,GAAG,SAAS;;mBAY1B,SAAS;iCAMX,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS;EAW1D"}
|
|
@@ -13,14 +13,15 @@ function useActiveSavedView(savedViews, getInitialView) {
|
|
|
13
13
|
selectionCount.current++;
|
|
14
14
|
setActiveId(view.id);
|
|
15
15
|
},
|
|
16
|
+
// Resolves to the view it selected, if any.
|
|
16
17
|
selectWhenSaved: async (savedView) => {
|
|
17
18
|
const selection = ++selectionCount.current;
|
|
18
19
|
// Tolerates a consumer that returns nothing, leaving the selection as is.
|
|
19
|
-
const
|
|
20
|
-
if (id === undefined)
|
|
20
|
+
const saved = await savedView;
|
|
21
|
+
if (saved?.id === undefined || selection !== selectionCount.current)
|
|
21
22
|
return;
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
setActiveId(saved.id);
|
|
24
|
+
return saved;
|
|
24
25
|
},
|
|
25
26
|
};
|
|
26
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useActiveSavedView.js","sources":["../../../src/components/DataTable/useActiveSavedView.ts"],"sourcesContent":["import { useRef, useState } from \"react\"\n\nimport type { SavedView } from \"./savedViews\"\n\nexport function useActiveSavedView(\n savedViews: SavedView[],\n getInitialView: () => SavedView | undefined\n) {\n const [activeId, setActiveId] = useState(() => getInitialView()?.id)\n // Lets a slow save skip selecting its view if the user picked another.\n const selectionCount = useRef(0)\n\n return {\n // A view without an id isn't saved yet, so it can't be the active one.\n activeSavedView:\n activeId === undefined\n ? undefined\n : savedViews.find(({ id }) => id === activeId),\n select: (view: SavedView) => {\n selectionCount.current++\n setActiveId(view.id)\n },\n selectWhenSaved: async (savedView: Promise<SavedView> | SavedView) => {\n const selection = ++selectionCount.current\n // Tolerates a consumer that returns nothing, leaving the selection as is.\n const
|
|
1
|
+
{"version":3,"file":"useActiveSavedView.js","sources":["../../../src/components/DataTable/useActiveSavedView.ts"],"sourcesContent":["import { useRef, useState } from \"react\"\n\nimport type { SavedView } from \"./savedViews\"\n\nexport function useActiveSavedView(\n savedViews: SavedView[],\n getInitialView: () => SavedView | undefined\n) {\n const [activeId, setActiveId] = useState(() => getInitialView()?.id)\n // Lets a slow save skip selecting its view if the user picked another.\n const selectionCount = useRef(0)\n\n return {\n // A view without an id isn't saved yet, so it can't be the active one.\n activeSavedView:\n activeId === undefined\n ? undefined\n : savedViews.find(({ id }) => id === activeId),\n select: (view: SavedView) => {\n selectionCount.current++\n setActiveId(view.id)\n },\n // Resolves to the view it selected, if any.\n selectWhenSaved: async (\n savedView: Promise<SavedView | undefined> | SavedView\n ) => {\n const selection = ++selectionCount.current\n // Tolerates a consumer that returns nothing, leaving the selection as is.\n const saved = await savedView\n if (saved?.id === undefined || selection !== selectionCount.current)\n return\n setActiveId(saved.id)\n return saved\n },\n }\n}\n"],"names":[],"mappings":";;AAIM,SAAU,kBAAkB,CAChC,UAAuB,EACvB,cAA2C,EAAA;AAE3C,IAAA,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,MAAM,cAAc,EAAE,EAAE,EAAE,CAAC;;AAEpE,IAAA,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC;IAEhC,OAAO;;QAEL,eAAe,EACb,QAAQ,KAAK;AACX,cAAE;AACF,cAAE,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,QAAQ,CAAC;AAClD,QAAA,MAAM,EAAE,CAAC,IAAe,KAAI;YAC1B,cAAc,CAAC,OAAO,EAAE;AACxB,YAAA,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,CAAC;;AAED,QAAA,eAAe,EAAE,OACf,SAAqD,KACnD;AACF,YAAA,MAAM,SAAS,GAAG,EAAE,cAAc,CAAC,OAAO;;AAE1C,YAAA,MAAM,KAAK,GAAG,MAAM,SAAS;YAC7B,IAAI,KAAK,EAAE,EAAE,KAAK,SAAS,IAAI,SAAS,KAAK,cAAc,CAAC,OAAO;gBACjE;AACF,YAAA,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;AACrB,YAAA,OAAO,KAAK;QACd,CAAC;KACF;AACH;;;;"}
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
import type { DataTableColumn } from "./DataTableColumn";
|
|
2
2
|
/**
|
|
3
|
-
* Resolves which columns render for a given `
|
|
3
|
+
* Resolves which columns render for a given `hiddenColumnNames` value. Only
|
|
4
4
|
* columns that opted in with `hideable` can be hidden; every other column is
|
|
5
|
-
* always visible. Omitting `
|
|
5
|
+
* always visible. Omitting `hiddenColumnNames` leaves all columns visible,
|
|
6
6
|
* and unknown names are ignored.
|
|
7
7
|
*
|
|
8
8
|
* Visibility is controlled: `onColumnVisibilityChange` reports the next value
|
|
9
9
|
* but nothing is stored here, so the table renders whatever
|
|
10
|
-
* `
|
|
10
|
+
* `hiddenColumnNames` says on the next render.
|
|
11
11
|
*/
|
|
12
|
-
export declare function useColumnVisibility({ columns,
|
|
12
|
+
export declare function useColumnVisibility({ columns, hiddenColumnNames, onHiddenColumnNamesChange, }: {
|
|
13
13
|
columns: DataTableColumn[];
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
hiddenColumnNames: string[] | undefined;
|
|
15
|
+
onHiddenColumnNamesChange: ((hiddenColumnNames: string[]) => void) | undefined;
|
|
16
16
|
}): {
|
|
17
17
|
hideableColumns: DataTableColumn[];
|
|
18
18
|
isColumnVisible: ({ hideable, name }: DataTableColumn) => boolean;
|
|
19
|
-
onColumnVisibilityChange: (
|
|
19
|
+
onColumnVisibilityChange: (visibleNames: string[]) => void;
|
|
20
20
|
visibleHideableColumns: string[];
|
|
21
21
|
};
|
|
22
22
|
//# sourceMappingURL=useColumnVisibility.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useColumnVisibility.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useColumnVisibility.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,
|
|
1
|
+
{"version":3,"file":"useColumnVisibility.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useColumnVisibility.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAExD;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,iBAAiB,EACjB,yBAAyB,GAC1B,EAAE;IACD,OAAO,EAAE,eAAe,EAAE,CAAA;IAC1B,iBAAiB,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;IACvC,yBAAyB,EAAE,CAAC,CAAC,iBAAiB,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;CAC/E;;0CAiBwB,eAAe;6CAWrB,MAAM,EAAE;;EAuB1B"}
|
|
@@ -1,35 +1,35 @@
|
|
|
1
1
|
import { useMemo, useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Resolves which columns render for a given `
|
|
4
|
+
* Resolves which columns render for a given `hiddenColumnNames` value. Only
|
|
5
5
|
* columns that opted in with `hideable` can be hidden; every other column is
|
|
6
|
-
* always visible. Omitting `
|
|
6
|
+
* always visible. Omitting `hiddenColumnNames` leaves all columns visible,
|
|
7
7
|
* and unknown names are ignored.
|
|
8
8
|
*
|
|
9
9
|
* Visibility is controlled: `onColumnVisibilityChange` reports the next value
|
|
10
10
|
* but nothing is stored here, so the table renders whatever
|
|
11
|
-
* `
|
|
11
|
+
* `hiddenColumnNames` says on the next render.
|
|
12
12
|
*/
|
|
13
|
-
function useColumnVisibility({ columns,
|
|
13
|
+
function useColumnVisibility({ columns, hiddenColumnNames, onHiddenColumnNamesChange, }) {
|
|
14
14
|
// Everything below keys off the props themselves. `DataTable` documents
|
|
15
15
|
// `columns` as a stable reference, so a consumer that honors that contract
|
|
16
16
|
// gets the memoization; one that passes a fresh literal each render only
|
|
17
17
|
// repeats a filter and a `Set` build, which is cheaper than the indirection
|
|
18
18
|
// needed to avoid it.
|
|
19
19
|
const hideableColumns = useMemo(() => columns.filter(({ hideable }) => hideable), [columns]);
|
|
20
|
-
const
|
|
21
|
-
const isColumnVisible = useCallback(({ hideable, name }) => !hideable || !
|
|
20
|
+
const hiddenNames = useMemo(() => new Set(hiddenColumnNames), [hiddenColumnNames]);
|
|
21
|
+
const isColumnVisible = useCallback(({ hideable, name }) => !hideable || !hiddenNames.has(name), [hiddenNames]);
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
23
|
+
* Reports the hideable columns left out of `visibleNames`, in `columns`
|
|
24
|
+
* order, so the reported value can be passed straight back as
|
|
25
|
+
* `hiddenColumnNames`.
|
|
26
26
|
*/
|
|
27
|
-
const onColumnVisibilityChange = useCallback((
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
.filter(({
|
|
27
|
+
const onColumnVisibilityChange = useCallback((visibleNames) => {
|
|
28
|
+
const visibleNameSet = new Set(visibleNames);
|
|
29
|
+
onHiddenColumnNamesChange?.(hideableColumns
|
|
30
|
+
.filter(({ name }) => !visibleNameSet.has(name))
|
|
31
31
|
.map(({ name }) => name));
|
|
32
|
-
}, [
|
|
32
|
+
}, [hideableColumns, onHiddenColumnNamesChange]);
|
|
33
33
|
const visibleHideableColumns = useMemo(() => hideableColumns.filter(isColumnVisible).map(({ name }) => name), [hideableColumns, isColumnVisible]);
|
|
34
34
|
return {
|
|
35
35
|
hideableColumns,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useColumnVisibility.js","sources":["../../../src/components/DataTable/useColumnVisibility.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\"\n\nimport type { DataTableColumn } from \"./DataTableColumn\"\n\n/**\n * Resolves which columns render for a given `
|
|
1
|
+
{"version":3,"file":"useColumnVisibility.js","sources":["../../../src/components/DataTable/useColumnVisibility.ts"],"sourcesContent":["import { useCallback, useMemo } from \"react\"\n\nimport type { DataTableColumn } from \"./DataTableColumn\"\n\n/**\n * Resolves which columns render for a given `hiddenColumnNames` value. Only\n * columns that opted in with `hideable` can be hidden; every other column is\n * always visible. Omitting `hiddenColumnNames` leaves all columns visible,\n * and unknown names are ignored.\n *\n * Visibility is controlled: `onColumnVisibilityChange` reports the next value\n * but nothing is stored here, so the table renders whatever\n * `hiddenColumnNames` says on the next render.\n */\nexport function useColumnVisibility({\n columns,\n hiddenColumnNames,\n onHiddenColumnNamesChange,\n}: {\n columns: DataTableColumn[]\n hiddenColumnNames: string[] | undefined\n onHiddenColumnNamesChange: ((hiddenColumnNames: string[]) => void) | undefined\n}) {\n // Everything below keys off the props themselves. `DataTable` documents\n // `columns` as a stable reference, so a consumer that honors that contract\n // gets the memoization; one that passes a fresh literal each render only\n // repeats a filter and a `Set` build, which is cheaper than the indirection\n // needed to avoid it.\n const hideableColumns = useMemo(\n () => columns.filter(({ hideable }) => hideable),\n [columns]\n )\n\n const hiddenNames = useMemo(\n () => new Set(hiddenColumnNames),\n [hiddenColumnNames]\n )\n\n const isColumnVisible = useCallback(\n ({ hideable, name }: DataTableColumn) =>\n !hideable || !hiddenNames.has(name),\n [hiddenNames]\n )\n\n /**\n * Reports the hideable columns left out of `visibleNames`, in `columns`\n * order, so the reported value can be passed straight back as\n * `hiddenColumnNames`.\n */\n const onColumnVisibilityChange = useCallback(\n (visibleNames: string[]) => {\n const visibleNameSet = new Set(visibleNames)\n\n onHiddenColumnNamesChange?.(\n hideableColumns\n .filter(({ name }) => !visibleNameSet.has(name))\n .map(({ name }) => name)\n )\n },\n [hideableColumns, onHiddenColumnNamesChange]\n )\n\n const visibleHideableColumns = useMemo(\n () => hideableColumns.filter(isColumnVisible).map(({ name }) => name),\n [hideableColumns, isColumnVisible]\n )\n\n return {\n hideableColumns,\n isColumnVisible,\n onColumnVisibilityChange,\n visibleHideableColumns,\n }\n}\n"],"names":[],"mappings":";;AAIA;;;;;;;;;AASG;AACG,SAAU,mBAAmB,CAAC,EAClC,OAAO,EACP,iBAAiB,EACjB,yBAAyB,GAK1B,EAAA;;;;;;IAMC,MAAM,eAAe,GAAG,OAAO,CAC7B,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,QAAQ,CAAC,EAChD,CAAC,OAAO,CAAC,CACV;AAED,IAAA,MAAM,WAAW,GAAG,OAAO,CACzB,MAAM,IAAI,GAAG,CAAC,iBAAiB,CAAC,EAChC,CAAC,iBAAiB,CAAC,CACpB;AAED,IAAA,MAAM,eAAe,GAAG,WAAW,CACjC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAmB,KAClC,CAAC,QAAQ,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EACrC,CAAC,WAAW,CAAC,CACd;AAED;;;;AAIG;AACH,IAAA,MAAM,wBAAwB,GAAG,WAAW,CAC1C,CAAC,YAAsB,KAAI;AACzB,QAAA,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC;QAE5C,yBAAyB,GACvB;AACG,aAAA,MAAM,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC;aAC9C,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,CAC3B;AACH,IAAA,CAAC,EACD,CAAC,eAAe,EAAE,yBAAyB,CAAC,CAC7C;AAED,IAAA,MAAM,sBAAsB,GAAG,OAAO,CACpC,MAAM,eAAe,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC,EACrE,CAAC,eAAe,EAAE,eAAe,CAAC,CACnC;IAED,OAAO;QACL,eAAe;QACf,eAAe;QACf,wBAAwB;QACxB,sBAAsB;KACvB;AACH;;;;"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DataTableLoadingState } from "./DataTable";
|
|
2
|
+
import type { ActiveFilter } from "./FilterTypes";
|
|
3
|
+
import { type ResultNoun } from "./ResultsCount";
|
|
4
|
+
export declare const RESULTS_ANNOUNCEMENT_DELAY_MS = 500;
|
|
5
|
+
/**
|
|
6
|
+
* Announces the results count to screen reader users after each filter
|
|
7
|
+
* change and each load, once the results settle: `loadingState` is
|
|
8
|
+
* `"idle"`, and neither it nor the count has changed for
|
|
9
|
+
* {@link RESULTS_ANNOUNCEMENT_DELAY_MS}. A load covers the first load and
|
|
10
|
+
* page changes too. A change to `"error"` cancels the message, because the
|
|
11
|
+
* error is announced on its own. An `"error"` left over from an earlier
|
|
12
|
+
* request only delays it.
|
|
13
|
+
*
|
|
14
|
+
* Returns an `onActiveFiltersChange` to use in place of the caller's, so
|
|
15
|
+
* filter changes the user makes are announced without a load. Filter changes
|
|
16
|
+
* from outside are announced only when they load.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useResultsAnnouncement({ isPageCount, loadingState, onActiveFiltersChange, resultNoun, totalCount, }: {
|
|
19
|
+
/** Whether `totalCount` covers only the current page, not the whole set. */
|
|
20
|
+
isPageCount: boolean;
|
|
21
|
+
loadingState: DataTableLoadingState;
|
|
22
|
+
onActiveFiltersChange: (activeFilters: ActiveFilter[]) => void;
|
|
23
|
+
resultNoun: ResultNoun | undefined;
|
|
24
|
+
totalCount: number;
|
|
25
|
+
}): (activeFilters: ActiveFilter[]) => void;
|
|
26
|
+
//# sourceMappingURL=useResultsAnnouncement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResultsAnnouncement.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useResultsAnnouncement.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAwB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEtE,eAAO,MAAM,6BAA6B,MAAM,CAAA;AAEhD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,UAAU,EACV,UAAU,GACX,EAAE;IACD,4EAA4E;IAC5E,WAAW,EAAE,OAAO,CAAA;IACpB,YAAY,EAAE,qBAAqB,CAAA;IACnC,qBAAqB,EAAE,CAAC,aAAa,EAAE,YAAY,EAAE,KAAK,IAAI,CAAA;IAC9D,UAAU,EAAE,UAAU,GAAG,SAAS,CAAA;IAClC,UAAU,EAAE,MAAM,CAAA;CACnB,GAAG,CAAC,aAAa,EAAE,YAAY,EAAE,KAAK,IAAI,CA+B1C"}
|
|
@@ -2,19 +2,21 @@ import { useAnnounce } from '../../utilities/useAnnounce.js';
|
|
|
2
2
|
import { useRef, useState, useEffect } from 'react';
|
|
3
3
|
import { getResultsCountLabel } from './ResultsCount.js';
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const RESULTS_ANNOUNCEMENT_DELAY_MS = 500;
|
|
6
6
|
/**
|
|
7
7
|
* Announces the results count to screen reader users after each filter
|
|
8
|
-
* change, once the results settle: `loadingState` is
|
|
9
|
-
* neither it nor the count has changed for
|
|
10
|
-
* {@link
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* change and each load, once the results settle: `loadingState` is
|
|
9
|
+
* `"idle"`, and neither it nor the count has changed for
|
|
10
|
+
* {@link RESULTS_ANNOUNCEMENT_DELAY_MS}. A load covers the first load and
|
|
11
|
+
* page changes too. A change to `"error"` cancels the message, because the
|
|
12
|
+
* error is announced on its own. An `"error"` left over from an earlier
|
|
13
|
+
* request only delays it.
|
|
13
14
|
*
|
|
14
|
-
* Returns an `onActiveFiltersChange` to use in place of the caller's, so
|
|
15
|
-
* changes the user makes are announced.
|
|
15
|
+
* Returns an `onActiveFiltersChange` to use in place of the caller's, so
|
|
16
|
+
* filter changes the user makes are announced without a load. Filter changes
|
|
17
|
+
* from outside are announced only when they load.
|
|
16
18
|
*/
|
|
17
|
-
function
|
|
19
|
+
function useResultsAnnouncement({ isPageCount, loadingState, onActiveFiltersChange, resultNoun, totalCount, }) {
|
|
18
20
|
const announce = useAnnounce();
|
|
19
21
|
const isDue = useRef(false);
|
|
20
22
|
const previousLoadingState = useRef(loadingState);
|
|
@@ -24,6 +26,8 @@ function useFilterAnnouncement({ isPageCount, loadingState, onActiveFiltersChang
|
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
const failed = loadingState === "error" && previousLoadingState.current !== "error";
|
|
26
28
|
previousLoadingState.current = loadingState;
|
|
29
|
+
if (loadingState === "loading")
|
|
30
|
+
isDue.current = true;
|
|
27
31
|
if (!isDue.current)
|
|
28
32
|
return;
|
|
29
33
|
if (failed) {
|
|
@@ -35,7 +39,7 @@ function useFilterAnnouncement({ isPageCount, loadingState, onActiveFiltersChang
|
|
|
35
39
|
const timeout = setTimeout(() => {
|
|
36
40
|
isDue.current = false;
|
|
37
41
|
announce(message);
|
|
38
|
-
},
|
|
42
|
+
}, RESULTS_ANNOUNCEMENT_DELAY_MS);
|
|
39
43
|
return () => clearTimeout(timeout);
|
|
40
44
|
}, [announce, changeCount, loadingState, message]);
|
|
41
45
|
return (activeFilters) => {
|
|
@@ -45,5 +49,5 @@ function useFilterAnnouncement({ isPageCount, loadingState, onActiveFiltersChang
|
|
|
45
49
|
};
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
export {
|
|
49
|
-
//# sourceMappingURL=
|
|
52
|
+
export { RESULTS_ANNOUNCEMENT_DELAY_MS, useResultsAnnouncement };
|
|
53
|
+
//# sourceMappingURL=useResultsAnnouncement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useResultsAnnouncement.js","sources":["../../../src/components/DataTable/useResultsAnnouncement.ts"],"sourcesContent":["import { useAnnounce } from \"@utilities/useAnnounce\"\nimport { useEffect, useRef, useState } from \"react\"\n\nimport type { DataTableLoadingState } from \"./DataTable\"\nimport type { ActiveFilter } from \"./FilterTypes\"\nimport { getResultsCountLabel, type ResultNoun } from \"./ResultsCount\"\n\nexport const RESULTS_ANNOUNCEMENT_DELAY_MS = 500\n\n/**\n * Announces the results count to screen reader users after each filter\n * change and each load, once the results settle: `loadingState` is\n * `\"idle\"`, and neither it nor the count has changed for\n * {@link RESULTS_ANNOUNCEMENT_DELAY_MS}. A load covers the first load and\n * page changes too. A change to `\"error\"` cancels the message, because the\n * error is announced on its own. An `\"error\"` left over from an earlier\n * request only delays it.\n *\n * Returns an `onActiveFiltersChange` to use in place of the caller's, so\n * filter changes the user makes are announced without a load. Filter changes\n * from outside are announced only when they load.\n */\nexport function useResultsAnnouncement({\n isPageCount,\n loadingState,\n onActiveFiltersChange,\n resultNoun,\n totalCount,\n}: {\n /** Whether `totalCount` covers only the current page, not the whole set. */\n isPageCount: boolean\n loadingState: DataTableLoadingState\n onActiveFiltersChange: (activeFilters: ActiveFilter[]) => void\n resultNoun: ResultNoun | undefined\n totalCount: number\n}): (activeFilters: ActiveFilter[]) => void {\n const announce = useAnnounce()\n const isDue = useRef(false)\n const previousLoadingState = useRef(loadingState)\n const [changeCount, setChangeCount] = useState(0)\n const countLabel = getResultsCountLabel(totalCount, resultNoun || true)\n const message = isPageCount ? `${countLabel} on this page` : countLabel\n\n useEffect(() => {\n const failed =\n loadingState === \"error\" && previousLoadingState.current !== \"error\"\n previousLoadingState.current = loadingState\n if (loadingState === \"loading\") isDue.current = true\n if (!isDue.current) return\n if (failed) {\n isDue.current = false\n return\n }\n if (loadingState !== \"idle\") return\n const timeout = setTimeout(() => {\n isDue.current = false\n announce(message)\n }, RESULTS_ANNOUNCEMENT_DELAY_MS)\n return () => clearTimeout(timeout)\n }, [announce, changeCount, loadingState, message])\n\n return (activeFilters) => {\n isDue.current = true\n setChangeCount((count) => count + 1)\n onActiveFiltersChange(activeFilters)\n }\n}\n"],"names":[],"mappings":";;;;AAOO,MAAM,6BAA6B,GAAG;AAE7C;;;;;;;;;;;;AAYG;AACG,SAAU,sBAAsB,CAAC,EACrC,WAAW,EACX,YAAY,EACZ,qBAAqB,EACrB,UAAU,EACV,UAAU,GAQX,EAAA;AACC,IAAA,MAAM,QAAQ,GAAG,WAAW,EAAE;AAC9B,IAAA,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;AAC3B,IAAA,MAAM,oBAAoB,GAAG,MAAM,CAAC,YAAY,CAAC;IACjD,MAAM,CAAC,WAAW,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IACjD,MAAM,UAAU,GAAG,oBAAoB,CAAC,UAAU,EAAE,UAAU,IAAI,IAAI,CAAC;AACvE,IAAA,MAAM,OAAO,GAAG,WAAW,GAAG,CAAA,EAAG,UAAU,CAAA,aAAA,CAAe,GAAG,UAAU;IAEvE,SAAS,CAAC,MAAK;QACb,MAAM,MAAM,GACV,YAAY,KAAK,OAAO,IAAI,oBAAoB,CAAC,OAAO,KAAK,OAAO;AACtE,QAAA,oBAAoB,CAAC,OAAO,GAAG,YAAY;QAC3C,IAAI,YAAY,KAAK,SAAS;AAAE,YAAA,KAAK,CAAC,OAAO,GAAG,IAAI;QACpD,IAAI,CAAC,KAAK,CAAC,OAAO;YAAE;QACpB,IAAI,MAAM,EAAE;AACV,YAAA,KAAK,CAAC,OAAO,GAAG,KAAK;YACrB;QACF;QACA,IAAI,YAAY,KAAK,MAAM;YAAE;AAC7B,QAAA,MAAM,OAAO,GAAG,UAAU,CAAC,MAAK;AAC9B,YAAA,KAAK,CAAC,OAAO,GAAG,KAAK;YACrB,QAAQ,CAAC,OAAO,CAAC;QACnB,CAAC,EAAE,6BAA6B,CAAC;AACjC,QAAA,OAAO,MAAM,YAAY,CAAC,OAAO,CAAC;IACpC,CAAC,EAAE,CAAC,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAElD,OAAO,CAAC,aAAa,KAAI;AACvB,QAAA,KAAK,CAAC,OAAO,GAAG,IAAI;QACpB,cAAc,CAAC,CAAC,KAAK,KAAK,KAAK,GAAG,CAAC,CAAC;QACpC,qBAAqB,CAAC,aAAa,CAAC;AACtC,IAAA,CAAC;AACH;;;;"}
|