@planningcenter/tapestry 4.12.0 → 4.13.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 +81 -93
- 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/DataTable/useVisibleSavedViews.d.ts +31 -0
- package/dist/components/DataTable/useVisibleSavedViews.d.ts.map +1 -0
- package/dist/components/DataTable/useVisibleSavedViews.js +178 -0
- package/dist/components/DataTable/useVisibleSavedViews.js.map +1 -0
- 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/reorderable-list-box/ReorderableListBox.d.ts +6 -0
- package/dist/components/internal/reorderable-list-box/ReorderableListBox.d.ts.map +1 -1
- package/dist/components/internal/reorderable-list-box/ReorderableListBox.js +9 -2
- package/dist/components/internal/reorderable-list-box/ReorderableListBox.js.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 +28 -12
- package/dist/index.css.map +1 -1
- package/dist/packages/tapestry-render/dist/{buildComponent-C5PfOgH9.js → buildComponent-DzY2PDMT.js} +23 -23
- package/dist/packages/tapestry-render/dist/buildComponent-DzY2PDMT.js.map +1 -0
- package/dist/packages/tapestry-render/dist/index.js +2 -2
- package/dist/packages/tapestry-render/dist/index.js.map +1 -1
- package/dist/packages/tapestry-render/dist/legacy.js +2 -2
- package/dist/packages/tapestry-render/dist/legacy.js.map +1 -1
- package/dist/product-tokens/giving.css +1 -0
- package/dist/reactRender.css +773 -658
- package/dist/reactRender.css.map +1 -1
- package/dist/reactRender.js +1 -1
- package/dist/reactRenderLegacy.css +773 -658
- package/dist/reactRenderLegacy.css.map +1 -1
- package/dist/reactRenderLegacy.js +1 -1
- package/dist/tapestryMount.css +773 -658
- package/dist/tapestryMount.css.map +1 -1
- package/dist/tapestryMount.js +1 -1
- package/dist/tapestryMountLegacy.css +773 -658
- 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 +11 -10
- package/dist/tokens.css.map +1 -1
- package/dist/unstable.css +418 -303
- package/dist/unstable.css.map +1 -1
- package/dist/unstable.js +1 -0
- package/dist/unstable.js.map +1 -1
- package/package.json +8 -9
- 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
- package/dist/packages/tapestry-render/dist/buildComponent-C5PfOgH9.js.map +0 -1
|
@@ -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;;;;"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { SavedView } from "./savedViews";
|
|
3
|
+
/** Tracks saves of views not in the list yet, which have no id to go by. */
|
|
4
|
+
export declare const NEW_VIEW: unique symbol;
|
|
5
|
+
type SaveStatusKey = SavedView["id"] | typeof NEW_VIEW;
|
|
6
|
+
type SaveStatus = {
|
|
7
|
+
message: string;
|
|
8
|
+
status: "failed";
|
|
9
|
+
} | {
|
|
10
|
+
status: "saving";
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Tracks saved view saves, by view. A save shows a spinner while it's
|
|
14
|
+
* pending, which fades in after 500ms so quick saves show nothing. A failed
|
|
15
|
+
* one shows a "not saved" icon for 3s and is announced to screen readers by
|
|
16
|
+
* the view's `name`. A newer save of the same view replaces the older one's.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useSaveStatus(): {
|
|
19
|
+
statusFor: (id: SaveStatusKey) => SaveStatus | undefined;
|
|
20
|
+
track: (id: SaveStatusKey, saving: unknown, name: string) => Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Hidden from screen readers, which hear failures announced instead, so it
|
|
24
|
+
* stays out of the view's name. With a `label`, a failure shows it next to
|
|
25
|
+
* the icon.
|
|
26
|
+
*/
|
|
27
|
+
export declare function SaveStatusIcon({ label, status, }: {
|
|
28
|
+
label?: string;
|
|
29
|
+
status?: SaveStatus;
|
|
30
|
+
}): React.JSX.Element | null;
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=useSaveStatus.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSaveStatus.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useSaveStatus.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA2B,MAAM,OAAO,CAAA;AAE/C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAK7C,4EAA4E;AAC5E,eAAO,MAAM,QAAQ,eAAqB,CAAA;AAE1C,KAAK,aAAa,GAAG,SAAS,CAAC,IAAI,CAAC,GAAG,OAAO,QAAQ,CAAA;AAEtD,KAAK,UAAU,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,QAAQ,CAAA;CAAE,CAAA;AAI9E;;;;;GAKG;AACH,wBAAgB,aAAa;oBAyCF,aAAa;gBA1Bb,aAAa,UAAU,OAAO,QAAQ,MAAM;EA2BtE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,MAAM,GACP,EAAE;IACD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,UAAU,CAAA;CACpB,4BAgCA"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import LoadingSpinner from '../internal/LoadingSpinner.js';
|
|
2
|
+
import '../internal/LoadingAnnouncement.js';
|
|
3
|
+
import '../internal/tooltip/Tooltip.js';
|
|
4
|
+
import Icon from '../../utilities/Icon.js';
|
|
5
|
+
import { useAnnounce } from '../../utilities/useAnnounce.js';
|
|
6
|
+
import React__default, { useState, useRef } from 'react';
|
|
7
|
+
import { saveErrorMessage } from './saveErrorMessage.js';
|
|
8
|
+
|
|
9
|
+
const FAILED_DURATION = 3000;
|
|
10
|
+
/** Tracks saves of views not in the list yet, which have no id to go by. */
|
|
11
|
+
const NEW_VIEW = Symbol("new view");
|
|
12
|
+
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
/**
|
|
14
|
+
* Tracks saved view saves, by view. A save shows a spinner while it's
|
|
15
|
+
* pending, which fades in after 500ms so quick saves show nothing. A failed
|
|
16
|
+
* one shows a "not saved" icon for 3s and is announced to screen readers by
|
|
17
|
+
* the view's `name`. A newer save of the same view replaces the older one's.
|
|
18
|
+
*/
|
|
19
|
+
function useSaveStatus() {
|
|
20
|
+
const announce = useAnnounce();
|
|
21
|
+
const [statuses, setStatuses] = useState(() => new Map());
|
|
22
|
+
const latestSave = useRef(new Map());
|
|
23
|
+
const setStatus = (id, status) => setStatuses((current) => {
|
|
24
|
+
const next = new Map(current);
|
|
25
|
+
if (status)
|
|
26
|
+
next.set(id, status);
|
|
27
|
+
else
|
|
28
|
+
next.delete(id);
|
|
29
|
+
return next;
|
|
30
|
+
});
|
|
31
|
+
const track = async (id, saving, name) => {
|
|
32
|
+
const save = (latestSave.current.get(id) ?? 0) + 1;
|
|
33
|
+
latestSave.current.set(id, save);
|
|
34
|
+
const isLatest = () => latestSave.current.get(id) === save;
|
|
35
|
+
setStatus(id, { status: "saving" });
|
|
36
|
+
let failure;
|
|
37
|
+
try {
|
|
38
|
+
await saving;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
failure = { error };
|
|
42
|
+
}
|
|
43
|
+
if (!isLatest())
|
|
44
|
+
return;
|
|
45
|
+
if (!failure) {
|
|
46
|
+
setStatus(id);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const message = saveErrorMessage(failure.error);
|
|
50
|
+
setStatus(id, { message, status: "failed" });
|
|
51
|
+
announce(`${name} not saved. ${message}`);
|
|
52
|
+
await wait(FAILED_DURATION);
|
|
53
|
+
if (isLatest())
|
|
54
|
+
setStatus(id);
|
|
55
|
+
};
|
|
56
|
+
return { statusFor: (id) => statuses.get(id), track };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Hidden from screen readers, which hear failures announced instead, so it
|
|
60
|
+
* stays out of the view's name. With a `label`, a failure shows it next to
|
|
61
|
+
* the icon.
|
|
62
|
+
*/
|
|
63
|
+
function SaveStatusIcon({ label, status, }) {
|
|
64
|
+
if (!status)
|
|
65
|
+
return null;
|
|
66
|
+
if (status.status === "saving") {
|
|
67
|
+
return (React__default.createElement("span", { "aria-hidden": true, className: "tds-data-table-saved-view-save-status tds-data-table-saved-view-save-status--saving" },
|
|
68
|
+
React__default.createElement(LoadingSpinner, null)));
|
|
69
|
+
}
|
|
70
|
+
const icon = (React__default.createElement("span", { "aria-hidden": true, className: "tds-data-table-saved-view-save-status tds-data-table-saved-view-save-status--failed", title: `Not saved. ${status.message}` },
|
|
71
|
+
React__default.createElement(Icon, { "aria-hidden": true, symbol: "general#exclamation-triangle" })));
|
|
72
|
+
if (!label)
|
|
73
|
+
return icon;
|
|
74
|
+
return (React__default.createElement("span", { "aria-hidden": true, className: "tds-data-table-saved-view-save-error", title: status.message },
|
|
75
|
+
icon,
|
|
76
|
+
label));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export { NEW_VIEW, SaveStatusIcon, useSaveStatus };
|
|
80
|
+
//# sourceMappingURL=useSaveStatus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSaveStatus.js","sources":["../../../src/components/DataTable/useSaveStatus.tsx"],"sourcesContent":["import { LoadingSpinner } from \"@components/internal\"\nimport Icon from \"@utilities/Icon\"\nimport { useAnnounce } from \"@utilities/useAnnounce\"\nimport React, { useRef, useState } from \"react\"\n\nimport type { SavedView } from \"./savedViews\"\nimport { saveErrorMessage } from \"./saveErrorMessage\"\n\nconst FAILED_DURATION = 3000\n\n/** Tracks saves of views not in the list yet, which have no id to go by. */\nexport const NEW_VIEW = Symbol(\"new view\")\n\ntype SaveStatusKey = SavedView[\"id\"] | typeof NEW_VIEW\n\ntype SaveStatus = { message: string; status: \"failed\" } | { status: \"saving\" }\n\nconst wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\n/**\n * Tracks saved view saves, by view. A save shows a spinner while it's\n * pending, which fades in after 500ms so quick saves show nothing. A failed\n * one shows a \"not saved\" icon for 3s and is announced to screen readers by\n * the view's `name`. A newer save of the same view replaces the older one's.\n */\nexport function useSaveStatus() {\n const announce = useAnnounce()\n const [statuses, setStatuses] = useState(\n () => new Map<SaveStatusKey, SaveStatus>()\n )\n const latestSave = useRef(new Map<SaveStatusKey, number>())\n\n const setStatus = (id: SaveStatusKey, status?: SaveStatus) =>\n setStatuses((current) => {\n const next = new Map(current)\n if (status) next.set(id, status)\n else next.delete(id)\n return next\n })\n\n const track = async (id: SaveStatusKey, saving: unknown, name: string) => {\n const save = (latestSave.current.get(id) ?? 0) + 1\n latestSave.current.set(id, save)\n const isLatest = () => latestSave.current.get(id) === save\n\n setStatus(id, { status: \"saving\" })\n\n let failure: { error: unknown } | undefined\n try {\n await saving\n } catch (error) {\n failure = { error }\n }\n if (!isLatest()) return\n if (!failure) {\n setStatus(id)\n return\n }\n\n const message = saveErrorMessage(failure.error)\n setStatus(id, { message, status: \"failed\" })\n announce(`${name} not saved. ${message}`)\n await wait(FAILED_DURATION)\n if (isLatest()) setStatus(id)\n }\n\n return { statusFor: (id: SaveStatusKey) => statuses.get(id), track }\n}\n\n/**\n * Hidden from screen readers, which hear failures announced instead, so it\n * stays out of the view's name. With a `label`, a failure shows it next to\n * the icon.\n */\nexport function SaveStatusIcon({\n label,\n status,\n}: {\n label?: string\n status?: SaveStatus\n}) {\n if (!status) return null\n if (status.status === \"saving\") {\n return (\n <span\n aria-hidden\n className=\"tds-data-table-saved-view-save-status tds-data-table-saved-view-save-status--saving\"\n >\n <LoadingSpinner />\n </span>\n )\n }\n const icon = (\n <span\n aria-hidden\n className=\"tds-data-table-saved-view-save-status tds-data-table-saved-view-save-status--failed\"\n title={`Not saved. ${status.message}`}\n >\n <Icon aria-hidden symbol=\"general#exclamation-triangle\" />\n </span>\n )\n if (!label) return icon\n return (\n <span\n aria-hidden\n className=\"tds-data-table-saved-view-save-error\"\n title={status.message}\n >\n {icon}\n {label}\n </span>\n )\n}\n"],"names":["React"],"mappings":";;;;;;;;AAQA,MAAM,eAAe,GAAG,IAAI;AAE5B;MACa,QAAQ,GAAG,MAAM,CAAC,UAAU;AAMzC,MAAM,IAAI,GAAG,CAAC,EAAU,KAAK,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAE9E;;;;;AAKG;SACa,aAAa,GAAA;AAC3B,IAAA,MAAM,QAAQ,GAAG,WAAW,EAAE;AAC9B,IAAA,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CACtC,MAAM,IAAI,GAAG,EAA6B,CAC3C;IACD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,GAAG,EAAyB,CAAC;AAE3D,IAAA,MAAM,SAAS,GAAG,CAAC,EAAiB,EAAE,MAAmB,KACvD,WAAW,CAAC,CAAC,OAAO,KAAI;AACtB,QAAA,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC;AAC7B,QAAA,IAAI,MAAM;AAAE,YAAA,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC;;AAC3B,YAAA,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;AACpB,QAAA,OAAO,IAAI;AACb,IAAA,CAAC,CAAC;IAEJ,MAAM,KAAK,GAAG,OAAO,EAAiB,EAAE,MAAe,EAAE,IAAY,KAAI;AACvE,QAAA,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAClD,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC;AAChC,QAAA,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI;QAE1D,SAAS,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAEnC,QAAA,IAAI,OAAuC;AAC3C,QAAA,IAAI;AACF,YAAA,MAAM,MAAM;QACd;QAAE,OAAO,KAAK,EAAE;AACd,YAAA,OAAO,GAAG,EAAE,KAAK,EAAE;QACrB;QACA,IAAI,CAAC,QAAQ,EAAE;YAAE;QACjB,IAAI,CAAC,OAAO,EAAE;YACZ,SAAS,CAAC,EAAE,CAAC;YACb;QACF;QAEA,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,KAAK,CAAC;QAC/C,SAAS,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC5C,QAAA,QAAQ,CAAC,CAAA,EAAG,IAAI,eAAe,OAAO,CAAA,CAAE,CAAC;AACzC,QAAA,MAAM,IAAI,CAAC,eAAe,CAAC;AAC3B,QAAA,IAAI,QAAQ,EAAE;YAAE,SAAS,CAAC,EAAE,CAAC;AAC/B,IAAA,CAAC;AAED,IAAA,OAAO,EAAE,SAAS,EAAE,CAAC,EAAiB,KAAK,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE;AACtE;AAEA;;;;AAIG;SACa,cAAc,CAAC,EAC7B,KAAK,EACL,MAAM,GAIP,EAAA;AACC,IAAA,IAAI,CAAC,MAAM;AAAE,QAAA,OAAO,IAAI;AACxB,IAAA,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE;AAC9B,QAAA,QACEA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAA,aAAA,EAAA,IAAA,EAEE,SAAS,EAAC,qFAAqF,EAAA;AAE/F,YAAAA,cAAA,CAAA,aAAA,CAAC,cAAc,EAAA,IAAA,CAAG,CACb;IAEX;AACA,IAAA,MAAM,IAAI,IACRA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAA,aAAA,EAAA,IAAA,EAEE,SAAS,EAAC,qFAAqF,EAC/F,KAAK,EAAE,CAAA,WAAA,EAAc,MAAM,CAAC,OAAO,CAAA,CAAE,EAAA;QAErCA,cAAA,CAAA,aAAA,CAAC,IAAI,yBAAa,MAAM,EAAC,8BAA8B,EAAA,CAAG,CACrD,CACR;AACD,IAAA,IAAI,CAAC,KAAK;AAAE,QAAA,OAAO,IAAI;IACvB,QACEA,cAAA,CAAA,aAAA,CAAA,MAAA,EAAA,EAAA,aAAA,EAAA,IAAA,EAEE,SAAS,EAAC,sCAAsC,EAChD,KAAK,EAAE,MAAM,CAAC,OAAO,EAAA;QAEpB,IAAI;QACJ,KAAK,CACD;AAEX;;;;"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { NewSavedViewModalProps } from "./NewSavedViewModal";
|
|
2
|
+
import { type SavedView } from "./savedViews";
|
|
3
|
+
/**
|
|
4
|
+
* The name modal's state, for a new view, a duplicate, or a rename. Spread
|
|
5
|
+
* `modalProps` onto a `NewSavedViewModal`. What it saves outlives the modal's
|
|
6
|
+
* open state, so the title holds while the modal fades out.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useSavedViewNameModal(onUpdate?: (savedView: SavedView) => Promise<SavedView | undefined> | SavedView): {
|
|
9
|
+
modalProps: NewSavedViewModalProps;
|
|
10
|
+
open: (title: string, defaultName: string, save: (name: string) => void) => void;
|
|
11
|
+
openDuplicate: (view: SavedView, create: (duplicate: Omit<SavedView, "id">) => void) => void;
|
|
12
|
+
openRename: (view: SavedView, track: (saving: Promise<void>) => void) => void;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=useSavedViewNameModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSavedViewNameModal.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useSavedViewNameModal.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AACjE,OAAO,EAAE,KAAK,SAAS,EAAsB,MAAM,cAAc,CAAA;AAGjE;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,CAAC,EAAE,CACT,SAAS,EAAE,SAAS,KACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS;;kBAWtC,MAAM,eACA,MAAM,QACb,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI;0BAQtB,SAAS,UACP,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,IAAI;uBAS5C,SAAS,SACR,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI;EAmBzC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { savedViewDuplicate } from './savedViews.js';
|
|
3
|
+
import { useUpdateSavedViewName } from './useUpdateSavedViewName.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The name modal's state, for a new view, a duplicate, or a rename. Spread
|
|
7
|
+
* `modalProps` onto a `NewSavedViewModal`. What it saves outlives the modal's
|
|
8
|
+
* open state, so the title holds while the modal fades out.
|
|
9
|
+
*/
|
|
10
|
+
function useSavedViewNameModal(onUpdate) {
|
|
11
|
+
const [draft, setDraft] = useState();
|
|
12
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
13
|
+
const updateSavedViewName = useUpdateSavedViewName(onUpdate);
|
|
14
|
+
const open = (title, defaultName, save) => {
|
|
15
|
+
setDraft({ defaultName, save, title });
|
|
16
|
+
setIsOpen(true);
|
|
17
|
+
};
|
|
18
|
+
/** `create` gets the duplicate under the entered name, without an id. */
|
|
19
|
+
const openDuplicate = (view, create) => {
|
|
20
|
+
open("Duplicate view", `${view.name} copy`, (name) => create(savedViewDuplicate(view, name)));
|
|
21
|
+
};
|
|
22
|
+
/** `track` gets the rename's save. */
|
|
23
|
+
const openRename = (view, track) => {
|
|
24
|
+
open("Rename view", view.name, (name) => {
|
|
25
|
+
if (name !== view.name)
|
|
26
|
+
track(updateSavedViewName(view, name));
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
const modalProps = {
|
|
30
|
+
defaultName: draft?.defaultName,
|
|
31
|
+
onClose: () => setIsOpen(false),
|
|
32
|
+
onSave: (name) => {
|
|
33
|
+
setIsOpen(false);
|
|
34
|
+
draft?.save(name);
|
|
35
|
+
},
|
|
36
|
+
open: isOpen,
|
|
37
|
+
title: draft?.title,
|
|
38
|
+
};
|
|
39
|
+
return { modalProps, open, openDuplicate, openRename };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export { useSavedViewNameModal };
|
|
43
|
+
//# sourceMappingURL=useSavedViewNameModal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSavedViewNameModal.js","sources":["../../../src/components/DataTable/useSavedViewNameModal.ts"],"sourcesContent":["import { useState } from \"react\"\n\nimport type { NewSavedViewModalProps } from \"./NewSavedViewModal\"\nimport { type SavedView, savedViewDuplicate } from \"./savedViews\"\nimport { useUpdateSavedViewName } from \"./useUpdateSavedViewName\"\n\n/**\n * The name modal's state, for a new view, a duplicate, or a rename. Spread\n * `modalProps` onto a `NewSavedViewModal`. What it saves outlives the modal's\n * open state, so the title holds while the modal fades out.\n */\nexport function useSavedViewNameModal(\n onUpdate?: (\n savedView: SavedView\n ) => Promise<SavedView | undefined> | SavedView\n) {\n const [draft, setDraft] = useState<{\n defaultName: string\n save: (name: string) => void\n title: string\n }>()\n const [isOpen, setIsOpen] = useState(false)\n const updateSavedViewName = useUpdateSavedViewName(onUpdate)\n\n const open = (\n title: string,\n defaultName: string,\n save: (name: string) => void\n ) => {\n setDraft({ defaultName, save, title })\n setIsOpen(true)\n }\n\n /** `create` gets the duplicate under the entered name, without an id. */\n const openDuplicate = (\n view: SavedView,\n create: (duplicate: Omit<SavedView, \"id\">) => void\n ) => {\n open(\"Duplicate view\", `${view.name} copy`, (name) =>\n create(savedViewDuplicate(view, name))\n )\n }\n\n /** `track` gets the rename's save. */\n const openRename = (\n view: SavedView,\n track: (saving: Promise<void>) => void\n ) => {\n open(\"Rename view\", view.name, (name) => {\n if (name !== view.name) track(updateSavedViewName(view, name))\n })\n }\n\n const modalProps: NewSavedViewModalProps = {\n defaultName: draft?.defaultName,\n onClose: () => setIsOpen(false),\n onSave: (name) => {\n setIsOpen(false)\n draft?.save(name)\n },\n open: isOpen,\n title: draft?.title,\n }\n\n return { modalProps, open, openDuplicate, openRename }\n}\n"],"names":[],"mappings":";;;;AAMA;;;;AAIG;AACG,SAAU,qBAAqB,CACnC,QAE+C,EAAA;IAE/C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,EAI9B;IACJ,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;AAC3C,IAAA,MAAM,mBAAmB,GAAG,sBAAsB,CAAC,QAAQ,CAAC;IAE5D,MAAM,IAAI,GAAG,CACX,KAAa,EACb,WAAmB,EACnB,IAA4B,KAC1B;QACF,QAAQ,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;QACtC,SAAS,CAAC,IAAI,CAAC;AACjB,IAAA,CAAC;;AAGD,IAAA,MAAM,aAAa,GAAG,CACpB,IAAe,EACf,MAAkD,KAChD;QACF,IAAI,CAAC,gBAAgB,EAAE,CAAA,EAAG,IAAI,CAAC,IAAI,CAAA,KAAA,CAAO,EAAE,CAAC,IAAI,KAC/C,MAAM,CAAC,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CACvC;AACH,IAAA,CAAC;;AAGD,IAAA,MAAM,UAAU,GAAG,CACjB,IAAe,EACf,KAAsC,KACpC;QACF,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,KAAI;AACtC,YAAA,IAAI,IAAI,KAAK,IAAI,CAAC,IAAI;gBAAE,KAAK,CAAC,mBAAmB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AAChE,QAAA,CAAC,CAAC;AACJ,IAAA,CAAC;AAED,IAAA,MAAM,UAAU,GAA2B;QACzC,WAAW,EAAE,KAAK,EAAE,WAAW;AAC/B,QAAA,OAAO,EAAE,MAAM,SAAS,CAAC,KAAK,CAAC;AAC/B,QAAA,MAAM,EAAE,CAAC,IAAI,KAAI;YACf,SAAS,CAAC,KAAK,CAAC;AAChB,YAAA,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC;QACnB,CAAC;AACD,QAAA,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,KAAK,EAAE,KAAK;KACpB;IAED,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE;AACxD;;;;"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type SavedView } from "./savedViews";
|
|
2
|
+
/**
|
|
3
|
+
* Sends requests to the saved views API. Matches
|
|
4
|
+
* `@planningcenter/api-client`'s `Client`, so auth, CSRF, and the API root stay
|
|
5
|
+
* in the app; pass one whose root serves the private graph. Each method
|
|
6
|
+
* resolves to the parsed response and rejects on failure.
|
|
7
|
+
*/
|
|
8
|
+
export interface ApiClient {
|
|
9
|
+
delete(request: {
|
|
10
|
+
url: string;
|
|
11
|
+
}): Promise<unknown>;
|
|
12
|
+
patch(request: {
|
|
13
|
+
data: object;
|
|
14
|
+
url: string;
|
|
15
|
+
}): Promise<unknown>;
|
|
16
|
+
post(request: {
|
|
17
|
+
data: object;
|
|
18
|
+
url: string;
|
|
19
|
+
}): Promise<unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface UseSavedViewsClientOptions {
|
|
22
|
+
client: ApiClient;
|
|
23
|
+
tableKey: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Returns DataTable's saved view callbacks, sending each change to the saved
|
|
27
|
+
* views endpoints that `tapestry:saved_views` adds to pco-api's private graph:
|
|
28
|
+
*
|
|
29
|
+
* - `POST /data_tables/:table_key/saved_views`
|
|
30
|
+
* - `PATCH /data_tables/:table_key/saved_views/:id` (also takes `after_id` to
|
|
31
|
+
* reorder)
|
|
32
|
+
* - `DELETE /data_tables/:table_key/saved_views/:id`
|
|
33
|
+
* - `POST` and `DELETE /data_tables/:table_key/saved_view_default`
|
|
34
|
+
*
|
|
35
|
+
* Each callback resolves once its request succeeds and rejects with the
|
|
36
|
+
* client's error. Default changes are sent one at a time, so the server keeps
|
|
37
|
+
* the last one chosen. DataTable shows the change right away and undoes it on
|
|
38
|
+
* rejection. Doesn't load views or hold state, so pass `data_table_props` to
|
|
39
|
+
* DataTable too:
|
|
40
|
+
*
|
|
41
|
+
* ```tsx
|
|
42
|
+
* <DataTable {...dataTableProps} {...useSavedViewsClient({ client, tableKey })} />
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function useSavedViewsClient({ client, tableKey, }: UseSavedViewsClientOptions): {
|
|
46
|
+
onDefaultViewIdChange: (id: number | string) => Promise<void>;
|
|
47
|
+
onSavedViewCreate: (view: Omit<SavedView, "id">) => Promise<{
|
|
48
|
+
id: string | number;
|
|
49
|
+
sort?: import("./DataTable").Sort | undefined;
|
|
50
|
+
name: string;
|
|
51
|
+
activeFilters: import("./FilterTypes").ActiveFilter[];
|
|
52
|
+
hiddenColumnNames?: string[] | undefined;
|
|
53
|
+
}>;
|
|
54
|
+
onSavedViewDelete: (view: SavedView) => Promise<void>;
|
|
55
|
+
onSavedViewReorder: (view: SavedView, after: SavedView | null) => Promise<void>;
|
|
56
|
+
onSavedViewUpdate: (view: SavedView) => Promise<SavedView>;
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=useSavedViewsClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSavedViewsClient.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useSavedViewsClient.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,cAAc,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAClD,KAAK,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAC/D,IAAI,CAAC,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC/D;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,SAAS,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,QAAQ,GACT,EAAE,0BAA0B;gCA0Cc,MAAM,GAAG,MAAM;8BAnCjB,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;;;;;;;8BA8BrB,SAAS;+BAdxC,SAAS,SACR,SAAS,GAAG,IAAI;8BARc,SAAS;EA0DjD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
import { initialView } from './savedViews.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns DataTable's saved view callbacks, sending each change to the saved
|
|
6
|
+
* views endpoints that `tapestry:saved_views` adds to pco-api's private graph:
|
|
7
|
+
*
|
|
8
|
+
* - `POST /data_tables/:table_key/saved_views`
|
|
9
|
+
* - `PATCH /data_tables/:table_key/saved_views/:id` (also takes `after_id` to
|
|
10
|
+
* reorder)
|
|
11
|
+
* - `DELETE /data_tables/:table_key/saved_views/:id`
|
|
12
|
+
* - `POST` and `DELETE /data_tables/:table_key/saved_view_default`
|
|
13
|
+
*
|
|
14
|
+
* Each callback resolves once its request succeeds and rejects with the
|
|
15
|
+
* client's error. Default changes are sent one at a time, so the server keeps
|
|
16
|
+
* the last one chosen. DataTable shows the change right away and undoes it on
|
|
17
|
+
* rejection. Doesn't load views or hold state, so pass `data_table_props` to
|
|
18
|
+
* DataTable too:
|
|
19
|
+
*
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <DataTable {...dataTableProps} {...useSavedViewsClient({ client, tableKey })} />
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
function useSavedViewsClient({ client, tableKey, }) {
|
|
25
|
+
const tableUrl = `/data_tables/${encodeURIComponent(tableKey)}`;
|
|
26
|
+
const viewUrl = (id) => `${tableUrl}/saved_views/${encodeURIComponent(String(id))}`;
|
|
27
|
+
const defaultUrl = `${tableUrl}/saved_view_default`;
|
|
28
|
+
const lastDefaultChange = useRef(Promise.resolve());
|
|
29
|
+
const onSavedViewCreate = async (view) => {
|
|
30
|
+
// Only the id is read, since clients may reshape the response
|
|
31
|
+
const { data } = (await client.post({
|
|
32
|
+
data: savedViewDocument(view),
|
|
33
|
+
url: `${tableUrl}/saved_views`,
|
|
34
|
+
}));
|
|
35
|
+
return { ...view, id: data.id };
|
|
36
|
+
};
|
|
37
|
+
const onSavedViewUpdate = async (view) => {
|
|
38
|
+
await client.patch({ data: savedViewDocument(view), url: viewUrl(view.id) });
|
|
39
|
+
return view;
|
|
40
|
+
};
|
|
41
|
+
/** Places the view after `after`, or first when it's `null`. */
|
|
42
|
+
const onSavedViewReorder = async (view, after) => {
|
|
43
|
+
await client.patch({
|
|
44
|
+
data: {
|
|
45
|
+
data: {
|
|
46
|
+
attributes: { after_id: after?.id ?? null },
|
|
47
|
+
type: "SavedView",
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
url: viewUrl(view.id),
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const onSavedViewDelete = async (view) => {
|
|
54
|
+
await client.delete({ url: viewUrl(view.id) });
|
|
55
|
+
};
|
|
56
|
+
/** Pass the initial view's id to reset the default. */
|
|
57
|
+
const onDefaultViewIdChange = async (id) => {
|
|
58
|
+
const send = () => id === initialView.id
|
|
59
|
+
? client.delete({ url: defaultUrl })
|
|
60
|
+
: client.post({
|
|
61
|
+
data: {
|
|
62
|
+
data: {
|
|
63
|
+
attributes: { view: String(id) },
|
|
64
|
+
type: "SavedViewDefault",
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
url: defaultUrl,
|
|
68
|
+
});
|
|
69
|
+
const change = lastDefaultChange.current.then(send, send);
|
|
70
|
+
lastDefaultChange.current = change;
|
|
71
|
+
await change;
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
onDefaultViewIdChange,
|
|
75
|
+
onSavedViewCreate,
|
|
76
|
+
onSavedViewDelete,
|
|
77
|
+
onSavedViewReorder,
|
|
78
|
+
onSavedViewUpdate,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function savedViewDocument(view) {
|
|
82
|
+
const { id: _id, name, ...state } = view;
|
|
83
|
+
return { data: { attributes: { name, state }, type: "SavedView" } };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { useSavedViewsClient };
|
|
87
|
+
//# sourceMappingURL=useSavedViewsClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSavedViewsClient.js","sources":["../../../src/components/DataTable/useSavedViewsClient.ts"],"sourcesContent":["import { useRef } from \"react\"\n\nimport type { DataTableProps } from \"./DataTable\"\nimport { initialView, type SavedView } from \"./savedViews\"\n\n/**\n * Sends requests to the saved views API. Matches\n * `@planningcenter/api-client`'s `Client`, so auth, CSRF, and the API root stay\n * in the app; pass one whose root serves the private graph. Each method\n * resolves to the parsed response and rejects on failure.\n */\nexport interface ApiClient {\n delete(request: { url: string }): Promise<unknown>\n patch(request: { data: object; url: string }): Promise<unknown>\n post(request: { data: object; url: string }): Promise<unknown>\n}\n\nexport interface UseSavedViewsClientOptions {\n client: ApiClient\n tableKey: string\n}\n\n/**\n * Returns DataTable's saved view callbacks, sending each change to the saved\n * views endpoints that `tapestry:saved_views` adds to pco-api's private graph:\n *\n * - `POST /data_tables/:table_key/saved_views`\n * - `PATCH /data_tables/:table_key/saved_views/:id` (also takes `after_id` to\n * reorder)\n * - `DELETE /data_tables/:table_key/saved_views/:id`\n * - `POST` and `DELETE /data_tables/:table_key/saved_view_default`\n *\n * Each callback resolves once its request succeeds and rejects with the\n * client's error. Default changes are sent one at a time, so the server keeps\n * the last one chosen. DataTable shows the change right away and undoes it on\n * rejection. Doesn't load views or hold state, so pass `data_table_props` to\n * DataTable too:\n *\n * ```tsx\n * <DataTable {...dataTableProps} {...useSavedViewsClient({ client, tableKey })} />\n * ```\n */\nexport function useSavedViewsClient({\n client,\n tableKey,\n}: UseSavedViewsClientOptions) {\n const tableUrl = `/data_tables/${encodeURIComponent(tableKey)}`\n const viewUrl = (id: SavedView[\"id\"]) =>\n `${tableUrl}/saved_views/${encodeURIComponent(String(id))}`\n const defaultUrl = `${tableUrl}/saved_view_default`\n const lastDefaultChange = useRef<Promise<unknown>>(Promise.resolve())\n\n const onSavedViewCreate = async (view: Omit<SavedView, \"id\">) => {\n // Only the id is read, since clients may reshape the response\n const { data } = (await client.post({\n data: savedViewDocument(view),\n url: `${tableUrl}/saved_views`,\n })) as { data: { id: number | string } }\n return { ...view, id: data.id }\n }\n\n const onSavedViewUpdate = async (view: SavedView) => {\n await client.patch({ data: savedViewDocument(view), url: viewUrl(view.id) })\n return view\n }\n\n /** Places the view after `after`, or first when it's `null`. */\n const onSavedViewReorder = async (\n view: SavedView,\n after: SavedView | null\n ) => {\n await client.patch({\n data: {\n data: {\n attributes: { after_id: after?.id ?? null },\n type: \"SavedView\",\n },\n },\n url: viewUrl(view.id),\n })\n }\n\n const onSavedViewDelete = async (view: SavedView) => {\n await client.delete({ url: viewUrl(view.id) })\n }\n\n /** Pass the initial view's id to reset the default. */\n const onDefaultViewIdChange = async (id: number | string) => {\n const send = () =>\n id === initialView.id\n ? client.delete({ url: defaultUrl })\n : client.post({\n data: {\n data: {\n attributes: { view: String(id) },\n type: \"SavedViewDefault\",\n },\n },\n url: defaultUrl,\n })\n const change = lastDefaultChange.current.then(send, send)\n lastDefaultChange.current = change\n await change\n }\n\n return {\n onDefaultViewIdChange,\n onSavedViewCreate,\n onSavedViewDelete,\n onSavedViewReorder,\n onSavedViewUpdate,\n } satisfies Pick<\n DataTableProps,\n | \"onDefaultViewIdChange\"\n | \"onSavedViewCreate\"\n | \"onSavedViewDelete\"\n | \"onSavedViewReorder\"\n | \"onSavedViewUpdate\"\n >\n}\n\nfunction savedViewDocument(view: Omit<SavedView, \"id\">) {\n const { id: _id, name, ...state } = view as SavedView\n return { data: { attributes: { name, state }, type: \"SavedView\" } }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type SavedView } from "./savedViews";
|
|
2
|
+
interface SavedViewsStateOptions {
|
|
3
|
+
initialDefaultViewId: NonNullable<SavedView["id"]>;
|
|
4
|
+
initialSavedViews: SavedView[] | undefined;
|
|
5
|
+
onCreate?: (savedView: Omit<SavedView, "id">) => Promise<SavedView> | SavedView;
|
|
6
|
+
onDelete?: (savedView: SavedView) => Promise<void> | void;
|
|
7
|
+
onReorder?: (savedView: SavedView, after: SavedView | null) => Promise<void> | void;
|
|
8
|
+
onSetDefault?: (id: NonNullable<SavedView["id"]>) => Promise<void> | void;
|
|
9
|
+
onUpdate?: (savedView: SavedView) => Promise<SavedView> | SavedView;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Keeps the saved views and default view id after mount. Each callback is
|
|
13
|
+
* wrapped to apply its change here, and stays undefined when the consumer's is,
|
|
14
|
+
* since that hides the matching action. Updates, deletes, and default changes
|
|
15
|
+
* show right away and are undone if the callback rejects, unless a newer
|
|
16
|
+
* change has replaced them. Reorders save one at a time, so a failed one puts
|
|
17
|
+
* back the order from before it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function useSavedViewsState({ initialDefaultViewId, initialSavedViews, onCreate, onDelete, onReorder, onSetDefault, onUpdate, }: SavedViewsStateOptions): {
|
|
20
|
+
defaultViewId: string | number;
|
|
21
|
+
isReordering: boolean;
|
|
22
|
+
onCreate: ((view: Omit<SavedView, "id">) => Promise<SavedView>) | undefined;
|
|
23
|
+
onDelete: ((view: SavedView) => Promise<void>) | undefined;
|
|
24
|
+
onReorder: ((view: SavedView, target: ReorderTarget) => Promise<void>) | undefined;
|
|
25
|
+
onSetDefault: ((id: NonNullable<SavedView["id"]>) => Promise<void>) | undefined;
|
|
26
|
+
onUpdate: ((view: SavedView) => Promise<SavedView>) | undefined;
|
|
27
|
+
savedViews: SavedView[] | undefined;
|
|
28
|
+
};
|
|
29
|
+
/** Where a view was dropped, as React Aria reports it. */
|
|
30
|
+
export interface ReorderTarget {
|
|
31
|
+
dropPosition: "after" | "before";
|
|
32
|
+
view: SavedView;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=useSavedViewsState.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSavedViewsState.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useSavedViewsState.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAiB,MAAM,cAAc,CAAA;AAE5D,UAAU,sBAAsB;IAC9B,oBAAoB,EAAE,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAA;IAClD,iBAAiB,EAAE,SAAS,EAAE,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,EAAE,CACT,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,KAC7B,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzD,SAAS,CAAC,EAAE,CACV,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,SAAS,GAAG,IAAI,KACpB,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzB,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACzE,QAAQ,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,KAAK,OAAO,CAAC,SAAS,CAAC,GAAG,SAAS,CAAA;CACpE;AAgCD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,oBAAoB,EACpB,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,QAAQ,GACT,EAAE,sBAAsB;;;sBAwDL,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;sBASrB,SAAS;uBA6BT,SAAS,UAAU,aAAa;wBA6BlC,WAAW,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;sBAa1B,SAAS;;EAoB5B;AAED,0DAA0D;AAC1D,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,OAAO,GAAG,QAAQ,CAAA;IAChC,IAAI,EAAE,SAAS,CAAA;CAChB"}
|