@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
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
|
+
import { withoutSearch } from './savedViews.js';
|
|
3
|
+
|
|
4
|
+
function startSaving(updates, view) {
|
|
5
|
+
updates.saving.push(view);
|
|
6
|
+
}
|
|
7
|
+
function stopSaving(updates, view) {
|
|
8
|
+
updates.saving = updates.saving.filter((pending) => pending !== view);
|
|
9
|
+
}
|
|
10
|
+
// A slower save of an older update doesn't replace a newer one.
|
|
11
|
+
function confirmIfNewest(updates, saved, order) {
|
|
12
|
+
if (order <= updates.confirmedOrder)
|
|
13
|
+
return;
|
|
14
|
+
updates.confirmed = saved;
|
|
15
|
+
updates.confirmedOrder = order;
|
|
16
|
+
}
|
|
17
|
+
// The newest update still saving, or else the last saved version.
|
|
18
|
+
function viewToRestore(updates) {
|
|
19
|
+
return updates.saving.at(-1) ?? updates.confirmed;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Keeps the saved views and default view id after mount. Each callback is
|
|
23
|
+
* wrapped to apply its change here, and stays undefined when the consumer's is,
|
|
24
|
+
* since that hides the matching action. Updates, deletes, and default changes
|
|
25
|
+
* show right away and are undone if the callback rejects, unless a newer
|
|
26
|
+
* change has replaced them. Reorders save one at a time, so a failed one puts
|
|
27
|
+
* back the order from before it.
|
|
28
|
+
*/
|
|
29
|
+
function useSavedViewsState({ initialDefaultViewId, initialSavedViews, onCreate, onDelete, onReorder, onSetDefault, onUpdate, }) {
|
|
30
|
+
const [savedViews, setSavedViews] = useState(() => initialSavedViews?.map(withoutSearch));
|
|
31
|
+
const [defaultViewId, setDefaultViewId] = useState(initialDefaultViewId);
|
|
32
|
+
const latestDefaultChange = useRef(0);
|
|
33
|
+
const latestUpdate = useRef(0);
|
|
34
|
+
const updates = useRef(new Map());
|
|
35
|
+
const [isReordering, setIsReordering] = useState(false);
|
|
36
|
+
// So a second drop before a render is skipped too.
|
|
37
|
+
const isReorderingNow = useRef(false);
|
|
38
|
+
// Only swaps out `current` if it's still there, so a slow save doesn't
|
|
39
|
+
// overwrite a newer change to the same view.
|
|
40
|
+
const replaceView = (current, next) => setSavedViews((views) => views?.map((view) => (view === current ? next : view)));
|
|
41
|
+
// Starts from the view as it was last saved, to restore if every update fails.
|
|
42
|
+
const trackUpdates = (view) => {
|
|
43
|
+
let tracked = updates.current.get(view.id);
|
|
44
|
+
if (!tracked) {
|
|
45
|
+
const original = savedViews?.find(({ id }) => id === view.id);
|
|
46
|
+
tracked = { confirmed: original ?? view, confirmedOrder: 0, saving: [] };
|
|
47
|
+
updates.current.set(view.id, tracked);
|
|
48
|
+
}
|
|
49
|
+
return tracked;
|
|
50
|
+
};
|
|
51
|
+
const showUpdate = (view) => setSavedViews((views) => views?.map((current) => (current.id === view.id ? view : current)));
|
|
52
|
+
// Returns an undo that does nothing once a newer default has been set.
|
|
53
|
+
const changeDefault = (id) => {
|
|
54
|
+
const change = ++latestDefaultChange.current;
|
|
55
|
+
let previous = id;
|
|
56
|
+
setDefaultViewId((current) => {
|
|
57
|
+
previous = current;
|
|
58
|
+
return id;
|
|
59
|
+
});
|
|
60
|
+
// Read in an updater, since a callback that rejects right away runs this
|
|
61
|
+
// before the change above has been applied.
|
|
62
|
+
return () => {
|
|
63
|
+
if (latestDefaultChange.current === change)
|
|
64
|
+
setDefaultViewId(() => previous);
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
return {
|
|
68
|
+
defaultViewId,
|
|
69
|
+
isReordering,
|
|
70
|
+
onCreate: onCreate &&
|
|
71
|
+
(async (view) => {
|
|
72
|
+
const created = await onCreate(view);
|
|
73
|
+
const saved = created && withoutSearch(created);
|
|
74
|
+
// Tolerates a consumer that returns nothing, leaving the list as is.
|
|
75
|
+
if (saved)
|
|
76
|
+
setSavedViews((views = []) => [...views, saved]);
|
|
77
|
+
return saved;
|
|
78
|
+
}),
|
|
79
|
+
onDelete: onDelete &&
|
|
80
|
+
(async (view) => {
|
|
81
|
+
let index = -1;
|
|
82
|
+
let removed;
|
|
83
|
+
setSavedViews((views) => {
|
|
84
|
+
index = views?.findIndex(({ id }) => id === view.id) ?? -1;
|
|
85
|
+
removed = views?.[index];
|
|
86
|
+
return views?.filter(({ id }) => id !== view.id);
|
|
87
|
+
});
|
|
88
|
+
const undoDefault = view.id === defaultViewId ? changeDefault("initial") : undefined;
|
|
89
|
+
try {
|
|
90
|
+
await onDelete(view);
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
setSavedViews((views) => !views || !removed || views.some(({ id }) => id === view.id)
|
|
94
|
+
? views
|
|
95
|
+
: [...views.slice(0, index), removed, ...views.slice(index)]);
|
|
96
|
+
undoDefault?.();
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
// Saved only once the view is gone, so a failed delete has no reset
|
|
100
|
+
// to take back.
|
|
101
|
+
if (undoDefault)
|
|
102
|
+
await onSetDefault?.("initial");
|
|
103
|
+
}),
|
|
104
|
+
// Moves the view right away, and moves it back if the save fails. A drop
|
|
105
|
+
// while another reorder saves is skipped.
|
|
106
|
+
onReorder: onReorder &&
|
|
107
|
+
(async (view, target) => {
|
|
108
|
+
if (isReorderingNow.current)
|
|
109
|
+
return;
|
|
110
|
+
const views = savedViews ?? [];
|
|
111
|
+
const from = views.indexOf(view);
|
|
112
|
+
const moved = moveView(views, view, target);
|
|
113
|
+
const to = moved.indexOf(view);
|
|
114
|
+
const after = moved[to - 1] ?? null;
|
|
115
|
+
if (from === -1 || from === to)
|
|
116
|
+
return;
|
|
117
|
+
// The position is saved by the view it follows, which needs an id.
|
|
118
|
+
if (after && after.id === undefined)
|
|
119
|
+
return;
|
|
120
|
+
isReorderingNow.current = true;
|
|
121
|
+
setIsReordering(true);
|
|
122
|
+
const previousOrder = views.map(({ id }) => id);
|
|
123
|
+
setSavedViews((current) => current && moveView(current, view, target));
|
|
124
|
+
try {
|
|
125
|
+
await onReorder(view, after);
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
setSavedViews((current) => current && restoreOrder(current, previousOrder));
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
finally {
|
|
132
|
+
isReorderingNow.current = false;
|
|
133
|
+
setIsReordering(false);
|
|
134
|
+
}
|
|
135
|
+
}),
|
|
136
|
+
onSetDefault: onSetDefault &&
|
|
137
|
+
(async (id) => {
|
|
138
|
+
const undo = changeDefault(id);
|
|
139
|
+
try {
|
|
140
|
+
await onSetDefault(id);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
undo();
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
}),
|
|
147
|
+
// Shows the change right away. If the save fails, puts back the newest
|
|
148
|
+
// update still saving, or else the last saved version.
|
|
149
|
+
onUpdate: onUpdate &&
|
|
150
|
+
(async (view) => {
|
|
151
|
+
const order = ++latestUpdate.current;
|
|
152
|
+
const tracked = trackUpdates(view);
|
|
153
|
+
startSaving(tracked, view);
|
|
154
|
+
showUpdate(view);
|
|
155
|
+
try {
|
|
156
|
+
const updated = await onUpdate(view);
|
|
157
|
+
const saved = updated && withoutSearch(updated);
|
|
158
|
+
stopSaving(tracked, view);
|
|
159
|
+
confirmIfNewest(tracked, saved ?? view, order);
|
|
160
|
+
if (saved)
|
|
161
|
+
replaceView(view, saved);
|
|
162
|
+
return saved;
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
stopSaving(tracked, view);
|
|
166
|
+
replaceView(view, viewToRestore(tracked));
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}),
|
|
170
|
+
savedViews,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function moveView(views, view, { dropPosition, view: target }) {
|
|
174
|
+
const rest = views.filter((existing) => existing !== view);
|
|
175
|
+
const targetIndex = rest.indexOf(target);
|
|
176
|
+
if (targetIndex === -1)
|
|
177
|
+
return views;
|
|
178
|
+
const to = dropPosition === "after" ? targetIndex + 1 : targetIndex;
|
|
179
|
+
return [...rest.slice(0, to), view, ...rest.slice(to)];
|
|
180
|
+
}
|
|
181
|
+
// Matched by id, so views updated or deleted since keep those changes. Views
|
|
182
|
+
// created since stay at the end.
|
|
183
|
+
function restoreOrder(views, order) {
|
|
184
|
+
const indexOf = (view) => {
|
|
185
|
+
const index = order.indexOf(view.id);
|
|
186
|
+
return index === -1 ? order.length : index;
|
|
187
|
+
};
|
|
188
|
+
return [...views].sort((a, b) => indexOf(a) - indexOf(b));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export { useSavedViewsState };
|
|
192
|
+
//# sourceMappingURL=useSavedViewsState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSavedViewsState.js","sources":["../../../src/components/DataTable/useSavedViewsState.ts"],"sourcesContent":["import { useRef, useState } from \"react\"\n\nimport { type SavedView, withoutSearch } from \"./savedViews\"\n\ninterface SavedViewsStateOptions {\n initialDefaultViewId: NonNullable<SavedView[\"id\"]>\n initialSavedViews: SavedView[] | undefined\n onCreate?: (\n savedView: Omit<SavedView, \"id\">\n ) => Promise<SavedView> | SavedView\n onDelete?: (savedView: SavedView) => Promise<void> | void\n onReorder?: (\n savedView: SavedView,\n after: SavedView | null\n ) => Promise<void> | void\n onSetDefault?: (id: NonNullable<SavedView[\"id\"]>) => Promise<void> | void\n onUpdate?: (savedView: SavedView) => Promise<SavedView> | SavedView\n}\n\ninterface ViewUpdates {\n confirmed: SavedView\n confirmedOrder: number\n saving: SavedView[]\n}\n\nfunction startSaving(updates: ViewUpdates, view: SavedView) {\n updates.saving.push(view)\n}\n\nfunction stopSaving(updates: ViewUpdates, view: SavedView) {\n updates.saving = updates.saving.filter((pending) => pending !== view)\n}\n\n// A slower save of an older update doesn't replace a newer one.\nfunction confirmIfNewest(\n updates: ViewUpdates,\n saved: SavedView,\n order: number\n) {\n if (order <= updates.confirmedOrder) return\n updates.confirmed = saved\n updates.confirmedOrder = order\n}\n\n// The newest update still saving, or else the last saved version.\nfunction viewToRestore(updates: ViewUpdates) {\n return updates.saving.at(-1) ?? updates.confirmed\n}\n\n/**\n * Keeps the saved views and default view id after mount. Each callback is\n * wrapped to apply its change here, and stays undefined when the consumer's is,\n * since that hides the matching action. Updates, deletes, and default changes\n * show right away and are undone if the callback rejects, unless a newer\n * change has replaced them. Reorders save one at a time, so a failed one puts\n * back the order from before it.\n */\nexport function useSavedViewsState({\n initialDefaultViewId,\n initialSavedViews,\n onCreate,\n onDelete,\n onReorder,\n onSetDefault,\n onUpdate,\n}: SavedViewsStateOptions) {\n const [savedViews, setSavedViews] = useState(() =>\n initialSavedViews?.map(withoutSearch)\n )\n const [defaultViewId, setDefaultViewId] = useState(initialDefaultViewId)\n const latestDefaultChange = useRef(0)\n const latestUpdate = useRef(0)\n const updates = useRef(new Map<SavedView[\"id\"], ViewUpdates>())\n const [isReordering, setIsReordering] = useState(false)\n // So a second drop before a render is skipped too.\n const isReorderingNow = useRef(false)\n\n // Only swaps out `current` if it's still there, so a slow save doesn't\n // overwrite a newer change to the same view.\n const replaceView = (current: SavedView, next: SavedView) =>\n setSavedViews((views) =>\n views?.map((view) => (view === current ? next : view))\n )\n\n // Starts from the view as it was last saved, to restore if every update fails.\n const trackUpdates = (view: SavedView) => {\n let tracked = updates.current.get(view.id)\n if (!tracked) {\n const original = savedViews?.find(({ id }) => id === view.id)\n tracked = { confirmed: original ?? view, confirmedOrder: 0, saving: [] }\n updates.current.set(view.id, tracked)\n }\n return tracked\n }\n\n const showUpdate = (view: SavedView) =>\n setSavedViews((views) =>\n views?.map((current) => (current.id === view.id ? view : current))\n )\n\n // Returns an undo that does nothing once a newer default has been set.\n const changeDefault = (id: NonNullable<SavedView[\"id\"]>) => {\n const change = ++latestDefaultChange.current\n let previous = id\n setDefaultViewId((current) => {\n previous = current\n return id\n })\n // Read in an updater, since a callback that rejects right away runs this\n // before the change above has been applied.\n return () => {\n if (latestDefaultChange.current === change)\n setDefaultViewId(() => previous)\n }\n }\n\n return {\n defaultViewId,\n isReordering,\n onCreate:\n onCreate &&\n (async (view: Omit<SavedView, \"id\">) => {\n const created = await onCreate(view)\n const saved = created && withoutSearch(created)\n // Tolerates a consumer that returns nothing, leaving the list as is.\n if (saved) setSavedViews((views = []) => [...views, saved])\n return saved\n }),\n onDelete:\n onDelete &&\n (async (view: SavedView) => {\n let index = -1\n let removed: SavedView | undefined\n setSavedViews((views) => {\n index = views?.findIndex(({ id }) => id === view.id) ?? -1\n removed = views?.[index]\n return views?.filter(({ id }) => id !== view.id)\n })\n const undoDefault =\n view.id === defaultViewId ? changeDefault(\"initial\") : undefined\n try {\n await onDelete(view)\n } catch (error) {\n setSavedViews((views) =>\n !views || !removed || views.some(({ id }) => id === view.id)\n ? views\n : [...views.slice(0, index), removed, ...views.slice(index)]\n )\n undoDefault?.()\n throw error\n }\n // Saved only once the view is gone, so a failed delete has no reset\n // to take back.\n if (undoDefault) await onSetDefault?.(\"initial\")\n }),\n // Moves the view right away, and moves it back if the save fails. A drop\n // while another reorder saves is skipped.\n onReorder:\n onReorder &&\n (async (view: SavedView, target: ReorderTarget) => {\n if (isReorderingNow.current) return\n const views = savedViews ?? []\n const from = views.indexOf(view)\n const moved = moveView(views, view, target)\n const to = moved.indexOf(view)\n const after = moved[to - 1] ?? null\n if (from === -1 || from === to) return\n // The position is saved by the view it follows, which needs an id.\n if (after && after.id === undefined) return\n\n isReorderingNow.current = true\n setIsReordering(true)\n const previousOrder = views.map(({ id }) => id)\n setSavedViews((current) => current && moveView(current, view, target))\n try {\n await onReorder(view, after)\n } catch (error) {\n setSavedViews(\n (current) => current && restoreOrder(current, previousOrder)\n )\n throw error\n } finally {\n isReorderingNow.current = false\n setIsReordering(false)\n }\n }),\n onSetDefault:\n onSetDefault &&\n (async (id: NonNullable<SavedView[\"id\"]>) => {\n const undo = changeDefault(id)\n try {\n await onSetDefault(id)\n } catch (error) {\n undo()\n throw error\n }\n }),\n // Shows the change right away. If the save fails, puts back the newest\n // update still saving, or else the last saved version.\n onUpdate:\n onUpdate &&\n (async (view: SavedView) => {\n const order = ++latestUpdate.current\n const tracked = trackUpdates(view)\n startSaving(tracked, view)\n showUpdate(view)\n try {\n const updated = await onUpdate(view)\n const saved = updated && withoutSearch(updated)\n stopSaving(tracked, view)\n confirmIfNewest(tracked, saved ?? view, order)\n if (saved) replaceView(view, saved)\n return saved\n } catch (error) {\n stopSaving(tracked, view)\n replaceView(view, viewToRestore(tracked))\n throw error\n }\n }),\n savedViews,\n }\n}\n\n/** Where a view was dropped, as React Aria reports it. */\nexport interface ReorderTarget {\n dropPosition: \"after\" | \"before\"\n view: SavedView\n}\n\nfunction moveView(\n views: SavedView[],\n view: SavedView,\n { dropPosition, view: target }: ReorderTarget\n) {\n const rest = views.filter((existing) => existing !== view)\n const targetIndex = rest.indexOf(target)\n if (targetIndex === -1) return views\n const to = dropPosition === \"after\" ? targetIndex + 1 : targetIndex\n return [...rest.slice(0, to), view, ...rest.slice(to)]\n}\n\n// Matched by id, so views updated or deleted since keep those changes. Views\n// created since stay at the end.\nfunction restoreOrder(views: SavedView[], order: SavedView[\"id\"][]) {\n const indexOf = (view: SavedView) => {\n const index = order.indexOf(view.id)\n return index === -1 ? order.length : index\n }\n return [...views].sort((a, b) => indexOf(a) - indexOf(b))\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SavedView } from "./savedViews";
|
|
2
2
|
/**
|
|
3
|
-
* Saves a view's new name, trimmed. Callers skip blank and unchanged names
|
|
4
|
-
* since `view.name` is out of date while a save is pending.
|
|
3
|
+
* Saves a view's new name, trimmed. Callers skip blank and unchanged names.
|
|
5
4
|
*/
|
|
6
|
-
export declare function useUpdateSavedViewName(onUpdate?: (savedView: SavedView) => Promise<SavedView> | SavedView): (view: SavedView, name: string) => Promise<void>;
|
|
5
|
+
export declare function useUpdateSavedViewName(onUpdate?: (savedView: SavedView) => Promise<SavedView | undefined> | SavedView): (view: SavedView, name: string) => Promise<void>;
|
|
7
6
|
//# sourceMappingURL=useUpdateSavedViewName.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUpdateSavedViewName.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C
|
|
1
|
+
{"version":3,"file":"useUpdateSavedViewName.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,CAAC,EAAE,CACT,SAAS,EAAE,SAAS,KACjB,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,GAAG,SAAS,IAEjC,MAAM,SAAS,EAAE,MAAM,MAAM,mBAG5C"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Saves a view's new name, trimmed. Callers skip blank and unchanged names
|
|
3
|
-
* since `view.name` is out of date while a save is pending.
|
|
2
|
+
* Saves a view's new name, trimmed. Callers skip blank and unchanged names.
|
|
4
3
|
*/
|
|
5
4
|
function useUpdateSavedViewName(onUpdate) {
|
|
6
5
|
return async (view, name) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useUpdateSavedViewName.js","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"sourcesContent":["import type { SavedView } from \"./savedViews\"\n\n/**\n * Saves a view's new name, trimmed. Callers skip blank and unchanged names
|
|
1
|
+
{"version":3,"file":"useUpdateSavedViewName.js","sources":["../../../src/components/DataTable/useUpdateSavedViewName.ts"],"sourcesContent":["import type { SavedView } from \"./savedViews\"\n\n/**\n * Saves a view's new name, trimmed. Callers skip blank and unchanged names.\n */\nexport function useUpdateSavedViewName(\n onUpdate?: (\n savedView: SavedView\n ) => Promise<SavedView | undefined> | SavedView\n) {\n return async (view: SavedView, name: string) => {\n await onUpdate?.({ ...view, name: name.trim() })\n }\n}\n"],"names":[],"mappings":"AAEA;;AAEG;AACG,SAAU,sBAAsB,CACpC,QAE+C,EAAA;AAE/C,IAAA,OAAO,OAAO,IAAe,EAAE,IAAY,KAAI;AAC7C,QAAA,MAAM,QAAQ,GAAG,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAClD,IAAA,CAAC;AACH;;;;"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import type { Key } from "react-aria-components/ListBox";
|
|
3
|
+
/**
|
|
4
|
+
* Splits `items` into the ones that fit on one line in `listRef` and the
|
|
5
|
+
* ones that go in the overflow menu. Items keep their order. The active item
|
|
6
|
+
* always shows, taking the last visible slot when it would otherwise
|
|
7
|
+
* overflow.
|
|
8
|
+
*
|
|
9
|
+
* Item widths are kept from when each item last rendered, and a resize
|
|
10
|
+
* observer on the rendered items, the overflow trigger, the list, and
|
|
11
|
+
* `newViewRef` keeps them current. An item with no width yet, such as a new
|
|
12
|
+
* one, renders every item and the overflow trigger for one measuring pass.
|
|
13
|
+
* The list's items land a render after the list, so call `measure` once
|
|
14
|
+
* they're in the DOM.
|
|
15
|
+
*/
|
|
16
|
+
export declare function useVisibleSavedViews<T extends {
|
|
17
|
+
id: Key;
|
|
18
|
+
}>({ activeKey, items, listRef, newViewRef, }: {
|
|
19
|
+
activeKey: Key | null;
|
|
20
|
+
items: T[];
|
|
21
|
+
listRef: RefObject<HTMLElement>;
|
|
22
|
+
/** The new view button and its save status, which follow the items. */
|
|
23
|
+
newViewRef: RefObject<HTMLElement>;
|
|
24
|
+
}): {
|
|
25
|
+
isCollapsed: boolean;
|
|
26
|
+
isMeasuring: boolean;
|
|
27
|
+
measure: () => void;
|
|
28
|
+
overflowItems: T[];
|
|
29
|
+
visibleItems: T[];
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=useVisibleSavedViews.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVisibleSavedViews.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/useVisibleSavedViews.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAAqC,MAAM,OAAO,CAAA;AACzE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,+BAA+B,CAAA;AAExD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,GAAG,CAAA;CAAE,EAAE,EAC1D,SAAS,EACT,KAAK,EACL,OAAO,EACP,UAAU,GACX,EAAE;IACD,SAAS,EAAE,GAAG,GAAG,IAAI,CAAA;IACrB,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,OAAO,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;IAC/B,uEAAuE;IACvE,UAAU,EAAE,SAAS,CAAC,WAAW,CAAC,CAAA;CACnC;;;;;;EAiHA"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useState, useRef, useLayoutEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Splits `items` into the ones that fit on one line in `listRef` and the
|
|
5
|
+
* ones that go in the overflow menu. Items keep their order. The active item
|
|
6
|
+
* always shows, taking the last visible slot when it would otherwise
|
|
7
|
+
* overflow.
|
|
8
|
+
*
|
|
9
|
+
* Item widths are kept from when each item last rendered, and a resize
|
|
10
|
+
* observer on the rendered items, the overflow trigger, the list, and
|
|
11
|
+
* `newViewRef` keeps them current. An item with no width yet, such as a new
|
|
12
|
+
* one, renders every item and the overflow trigger for one measuring pass.
|
|
13
|
+
* The list's items land a render after the list, so call `measure` once
|
|
14
|
+
* they're in the DOM.
|
|
15
|
+
*/
|
|
16
|
+
function useVisibleSavedViews({ activeKey, items, listRef, newViewRef, }) {
|
|
17
|
+
// By `data-key`, which is a string.
|
|
18
|
+
const [widths, setWidths] = useState(() => new Map());
|
|
19
|
+
const [overflowWidth, setOverflowWidth] = useState();
|
|
20
|
+
const [available, setAvailable] = useState(0);
|
|
21
|
+
const [gap, setGap] = useState(0);
|
|
22
|
+
const observerRef = useRef();
|
|
23
|
+
const observedRef = useRef(new Set());
|
|
24
|
+
const isMeasuring = overflowWidth === undefined ||
|
|
25
|
+
items.some(({ id }) => !widths.has(String(id)));
|
|
26
|
+
// Only reads refs and sets state, so the observer can keep the first one.
|
|
27
|
+
const read = () => {
|
|
28
|
+
const list = listRef.current;
|
|
29
|
+
if (!list)
|
|
30
|
+
return;
|
|
31
|
+
// Hidden, everything reads 0, so widths from before are kept.
|
|
32
|
+
const isHidden = list.clientWidth === 0;
|
|
33
|
+
if (!isHidden) {
|
|
34
|
+
const gap = readGap(list);
|
|
35
|
+
setAvailable(readAvailable(list, newViewRef.current, gap));
|
|
36
|
+
setGap(gap);
|
|
37
|
+
}
|
|
38
|
+
// Its count changes its width, which changes the count, so keep the
|
|
39
|
+
// widest to settle on one count.
|
|
40
|
+
const overflow = list.querySelector(OVERFLOW_SELECTOR);
|
|
41
|
+
if (overflow) {
|
|
42
|
+
const width = widthOf(overflow);
|
|
43
|
+
setOverflowWidth((current) => Math.max(current ?? 0, width));
|
|
44
|
+
}
|
|
45
|
+
const itemElements = list.querySelectorAll(ITEM_SELECTOR);
|
|
46
|
+
setWidths((current) => {
|
|
47
|
+
let next = current;
|
|
48
|
+
for (const itemElement of itemElements) {
|
|
49
|
+
const key = itemElement.dataset.key;
|
|
50
|
+
const width = fullWidthOf(itemElement);
|
|
51
|
+
if (key === undefined || current.get(key) === width)
|
|
52
|
+
continue;
|
|
53
|
+
if (isHidden && current.has(key))
|
|
54
|
+
continue;
|
|
55
|
+
if (next === current)
|
|
56
|
+
next = new Map(current);
|
|
57
|
+
next.set(key, width);
|
|
58
|
+
}
|
|
59
|
+
return next;
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
const measure = () => {
|
|
63
|
+
const list = listRef.current;
|
|
64
|
+
if (!list)
|
|
65
|
+
return;
|
|
66
|
+
const elements = new Set([
|
|
67
|
+
list,
|
|
68
|
+
...(newViewRef.current ? [newViewRef.current] : []),
|
|
69
|
+
...list.querySelectorAll(`${ITEM_SELECTOR}, ${OVERFLOW_SELECTOR}`),
|
|
70
|
+
]);
|
|
71
|
+
const observed = observedRef.current;
|
|
72
|
+
// The observer reads size changes, so only added or removed elements
|
|
73
|
+
// need reading here.
|
|
74
|
+
const isSame = elements.size === observed.size &&
|
|
75
|
+
[...elements].every((element) => observed.has(element));
|
|
76
|
+
if (isSame)
|
|
77
|
+
return;
|
|
78
|
+
observedRef.current = elements;
|
|
79
|
+
read();
|
|
80
|
+
if (typeof ResizeObserver === "undefined")
|
|
81
|
+
return;
|
|
82
|
+
observerRef.current ??= new ResizeObserver(read);
|
|
83
|
+
for (const element of observed) {
|
|
84
|
+
if (!elements.has(element))
|
|
85
|
+
observerRef.current.unobserve(element);
|
|
86
|
+
}
|
|
87
|
+
for (const element of elements) {
|
|
88
|
+
if (!observed.has(element))
|
|
89
|
+
observerRef.current.observe(element);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
// Cleared too, so a remount, such as StrictMode's, observes again.
|
|
93
|
+
useLayoutEffect(() => () => {
|
|
94
|
+
observerRef.current?.disconnect();
|
|
95
|
+
observerRef.current = undefined;
|
|
96
|
+
observedRef.current = new Set();
|
|
97
|
+
}, []);
|
|
98
|
+
// Drops the widths of deleted items.
|
|
99
|
+
useLayoutEffect(() => {
|
|
100
|
+
const keys = new Set(items.map(({ id }) => String(id)));
|
|
101
|
+
if ([...widths.keys()].some((key) => !keys.has(key))) {
|
|
102
|
+
setWidths(new Map([...widths].filter(([key]) => keys.has(key))));
|
|
103
|
+
}
|
|
104
|
+
}, [items, widths]);
|
|
105
|
+
let visibleItems = items;
|
|
106
|
+
let overflowItems = [];
|
|
107
|
+
if (!isMeasuring) {
|
|
108
|
+
let remaining = countFitting({
|
|
109
|
+
activeIndex: items.findIndex(({ id }) => id === activeKey),
|
|
110
|
+
available,
|
|
111
|
+
gap,
|
|
112
|
+
overflowWidth,
|
|
113
|
+
widths: items.map(({ id }) => widths.get(String(id)) ?? 0),
|
|
114
|
+
});
|
|
115
|
+
visibleItems = [];
|
|
116
|
+
overflowItems = [];
|
|
117
|
+
for (const item of items) {
|
|
118
|
+
if (item.id === activeKey || remaining-- > 0)
|
|
119
|
+
visibleItems.push(item);
|
|
120
|
+
else
|
|
121
|
+
overflowItems.push(item);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Alone, the view truncates rather than overflow, whether the rest are in
|
|
125
|
+
// the overflow menu or it's the only one.
|
|
126
|
+
const isCollapsed = visibleItems.length === 1;
|
|
127
|
+
return { isCollapsed, isMeasuring, measure, overflowItems, visibleItems };
|
|
128
|
+
}
|
|
129
|
+
const ITEM_SELECTOR = ".tds-data-table-saved-view-list-item";
|
|
130
|
+
const NAME_SELECTOR = ".tds-data-table-saved-view-name";
|
|
131
|
+
const OVERFLOW_SELECTOR = ".tds-btn--data-table-saved-views-overflow";
|
|
132
|
+
/** The list's width, less the new view button, its save status, and a gap. */
|
|
133
|
+
function readAvailable(list, newView, gap) {
|
|
134
|
+
const newViewWidth = widthOf(newView);
|
|
135
|
+
return list.clientWidth - (newViewWidth ? newViewWidth + gap : 0);
|
|
136
|
+
}
|
|
137
|
+
function readGap(element) {
|
|
138
|
+
return parseFloat(getComputedStyle(element).columnGap) || 0;
|
|
139
|
+
}
|
|
140
|
+
function widthOf(element) {
|
|
141
|
+
return element?.getBoundingClientRect().width ?? 0;
|
|
142
|
+
}
|
|
143
|
+
/** Includes the part of its name cut off by truncation. */
|
|
144
|
+
function fullWidthOf(itemElement) {
|
|
145
|
+
const name = itemElement.querySelector(NAME_SELECTOR);
|
|
146
|
+
const truncated = name ? name.scrollWidth - name.clientWidth : 0;
|
|
147
|
+
return widthOf(itemElement) + truncated;
|
|
148
|
+
}
|
|
149
|
+
// Measured widths can be fractional, but the list's width is rounded.
|
|
150
|
+
const SUBPIXEL_SLACK = 1;
|
|
151
|
+
function countFitting({ activeIndex, available, gap, overflowWidth, widths, }) {
|
|
152
|
+
const limit = available + SUBPIXEL_SLACK;
|
|
153
|
+
// Each item counts with the gap after it, which comes before the overflow
|
|
154
|
+
// trigger, or after the last item doesn't exist. The active item's width is
|
|
155
|
+
// set aside first so it always shows.
|
|
156
|
+
let used = activeIndex === -1 ? 0 : widths[activeIndex] + gap;
|
|
157
|
+
// Items that still fit with the overflow trigger beside them.
|
|
158
|
+
let fitsWithOverflow = 0;
|
|
159
|
+
for (let index = 0; index < widths.length; index++) {
|
|
160
|
+
if (index === activeIndex)
|
|
161
|
+
continue;
|
|
162
|
+
used += widths[index] + gap;
|
|
163
|
+
// Out of room, so the overflow trigger is needed. With no active item,
|
|
164
|
+
// the first item shows anyway.
|
|
165
|
+
if (used - gap > limit) {
|
|
166
|
+
return activeIndex === -1
|
|
167
|
+
? Math.max(fitsWithOverflow, 1)
|
|
168
|
+
: fitsWithOverflow;
|
|
169
|
+
}
|
|
170
|
+
if (used + overflowWidth <= limit)
|
|
171
|
+
fitsWithOverflow++;
|
|
172
|
+
}
|
|
173
|
+
// Every item fits, so there's no overflow trigger.
|
|
174
|
+
return Infinity;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export { useVisibleSavedViews };
|
|
178
|
+
//# sourceMappingURL=useVisibleSavedViews.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVisibleSavedViews.js","sources":["../../../src/components/DataTable/useVisibleSavedViews.ts"],"sourcesContent":["import { type RefObject, useLayoutEffect, useRef, useState } from \"react\"\nimport type { Key } from \"react-aria-components/ListBox\"\n\n/**\n * Splits `items` into the ones that fit on one line in `listRef` and the\n * ones that go in the overflow menu. Items keep their order. The active item\n * always shows, taking the last visible slot when it would otherwise\n * overflow.\n *\n * Item widths are kept from when each item last rendered, and a resize\n * observer on the rendered items, the overflow trigger, the list, and\n * `newViewRef` keeps them current. An item with no width yet, such as a new\n * one, renders every item and the overflow trigger for one measuring pass.\n * The list's items land a render after the list, so call `measure` once\n * they're in the DOM.\n */\nexport function useVisibleSavedViews<T extends { id: Key }>({\n activeKey,\n items,\n listRef,\n newViewRef,\n}: {\n activeKey: Key | null\n items: T[]\n listRef: RefObject<HTMLElement>\n /** The new view button and its save status, which follow the items. */\n newViewRef: RefObject<HTMLElement>\n}) {\n // By `data-key`, which is a string.\n const [widths, setWidths] = useState(() => new Map<string, number>())\n const [overflowWidth, setOverflowWidth] = useState<number>()\n const [available, setAvailable] = useState(0)\n const [gap, setGap] = useState(0)\n const observerRef = useRef<ResizeObserver>()\n const observedRef = useRef(new Set<Element>())\n\n const isMeasuring =\n overflowWidth === undefined ||\n items.some(({ id }) => !widths.has(String(id)))\n\n // Only reads refs and sets state, so the observer can keep the first one.\n const read = () => {\n const list = listRef.current\n if (!list) return\n // Hidden, everything reads 0, so widths from before are kept.\n const isHidden = list.clientWidth === 0\n if (!isHidden) {\n const gap = readGap(list)\n setAvailable(readAvailable(list, newViewRef.current, gap))\n setGap(gap)\n }\n // Its count changes its width, which changes the count, so keep the\n // widest to settle on one count.\n const overflow = list.querySelector(OVERFLOW_SELECTOR)\n if (overflow) {\n const width = widthOf(overflow)\n setOverflowWidth((current) => Math.max(current ?? 0, width))\n }\n const itemElements = list.querySelectorAll<HTMLElement>(ITEM_SELECTOR)\n setWidths((current) => {\n let next = current\n for (const itemElement of itemElements) {\n const key = itemElement.dataset.key\n const width = fullWidthOf(itemElement)\n if (key === undefined || current.get(key) === width) continue\n if (isHidden && current.has(key)) continue\n if (next === current) next = new Map(current)\n next.set(key, width)\n }\n return next\n })\n }\n\n const measure = () => {\n const list = listRef.current\n if (!list) return\n const elements = new Set<Element>([\n list,\n ...(newViewRef.current ? [newViewRef.current] : []),\n ...list.querySelectorAll(`${ITEM_SELECTOR}, ${OVERFLOW_SELECTOR}`),\n ])\n const observed = observedRef.current\n // The observer reads size changes, so only added or removed elements\n // need reading here.\n const isSame =\n elements.size === observed.size &&\n [...elements].every((element) => observed.has(element))\n if (isSame) return\n observedRef.current = elements\n read()\n if (typeof ResizeObserver === \"undefined\") return\n observerRef.current ??= new ResizeObserver(read)\n for (const element of observed) {\n if (!elements.has(element)) observerRef.current.unobserve(element)\n }\n for (const element of elements) {\n if (!observed.has(element)) observerRef.current.observe(element)\n }\n }\n\n // Cleared too, so a remount, such as StrictMode's, observes again.\n useLayoutEffect(\n () => () => {\n observerRef.current?.disconnect()\n observerRef.current = undefined\n observedRef.current = new Set()\n },\n []\n )\n\n // Drops the widths of deleted items.\n useLayoutEffect(() => {\n const keys = new Set(items.map(({ id }) => String(id)))\n if ([...widths.keys()].some((key) => !keys.has(key))) {\n setWidths(new Map([...widths].filter(([key]) => keys.has(key))))\n }\n }, [items, widths])\n\n let visibleItems = items\n let overflowItems: T[] = []\n if (!isMeasuring) {\n let remaining = countFitting({\n activeIndex: items.findIndex(({ id }) => id === activeKey),\n available,\n gap,\n overflowWidth,\n widths: items.map(({ id }) => widths.get(String(id)) ?? 0),\n })\n visibleItems = []\n overflowItems = []\n for (const item of items) {\n if (item.id === activeKey || remaining-- > 0) visibleItems.push(item)\n else overflowItems.push(item)\n }\n }\n // Alone, the view truncates rather than overflow, whether the rest are in\n // the overflow menu or it's the only one.\n const isCollapsed = visibleItems.length === 1\n\n return { isCollapsed, isMeasuring, measure, overflowItems, visibleItems }\n}\n\nconst ITEM_SELECTOR = \".tds-data-table-saved-view-list-item\"\nconst NAME_SELECTOR = \".tds-data-table-saved-view-name\"\nconst OVERFLOW_SELECTOR = \".tds-btn--data-table-saved-views-overflow\"\n\n/** The list's width, less the new view button, its save status, and a gap. */\nfunction readAvailable(\n list: HTMLElement,\n newView: HTMLElement | null,\n gap: number\n) {\n const newViewWidth = widthOf(newView)\n return list.clientWidth - (newViewWidth ? newViewWidth + gap : 0)\n}\n\nfunction readGap(element: Element) {\n return parseFloat(getComputedStyle(element).columnGap) || 0\n}\n\nfunction widthOf(element: Element | null) {\n return element?.getBoundingClientRect().width ?? 0\n}\n\n/** Includes the part of its name cut off by truncation. */\nfunction fullWidthOf(itemElement: Element) {\n const name = itemElement.querySelector(NAME_SELECTOR)\n const truncated = name ? name.scrollWidth - name.clientWidth : 0\n return widthOf(itemElement) + truncated\n}\n\n// Measured widths can be fractional, but the list's width is rounded.\nconst SUBPIXEL_SLACK = 1\n\nfunction countFitting({\n activeIndex,\n available,\n gap,\n overflowWidth,\n widths,\n}: {\n activeIndex: number\n available: number\n gap: number\n overflowWidth: number\n widths: number[]\n}) {\n const limit = available + SUBPIXEL_SLACK\n // Each item counts with the gap after it, which comes before the overflow\n // trigger, or after the last item doesn't exist. The active item's width is\n // set aside first so it always shows.\n let used = activeIndex === -1 ? 0 : widths[activeIndex] + gap\n // Items that still fit with the overflow trigger beside them.\n let fitsWithOverflow = 0\n for (let index = 0; index < widths.length; index++) {\n if (index === activeIndex) continue\n used += widths[index] + gap\n // Out of room, so the overflow trigger is needed. With no active item,\n // the first item shows anyway.\n if (used - gap > limit) {\n return activeIndex === -1\n ? Math.max(fitsWithOverflow, 1)\n : fitsWithOverflow\n }\n if (used + overflowWidth <= limit) fitsWithOverflow++\n }\n // Every item fits, so there's no overflow trigger.\n return Infinity\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import "./btn.css";
|
|
|
2
2
|
import { BaseComponentProps, ComponentKind } from "../../utilities/buttonLinkShared";
|
|
3
3
|
import React, { ButtonHTMLAttributes } from "react";
|
|
4
4
|
export interface BaseButtonProps extends Omit<BaseComponentProps, "kind"> {
|
|
5
|
+
/** Keeps a disabled button focusable, using `aria-disabled` without the native `disabled` attribute. Use when focus would otherwise be lost, such as a button that disables itself while focused. */
|
|
6
|
+
focusableWhenDisabled?: boolean;
|
|
5
7
|
/** The visual style of the button. */
|
|
6
8
|
kind?: ComponentKind;
|
|
7
9
|
/** The text content of the button. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseButton.d.ts","sourceRoot":"","sources":["../../../src/components/button/BaseButton.tsx"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAIlB,OAAO,EACL,kBAAkB,EAGlB,aAAa,EAEd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"BaseButton.d.ts","sourceRoot":"","sources":["../../../src/components/button/BaseButton.tsx"],"names":[],"mappings":"AAAA,OAAO,WAAW,CAAA;AAIlB,OAAO,EACL,kBAAkB,EAGlB,aAAa,EAEd,MAAM,6BAA6B,CAAA;AAEpC,OAAO,KAAK,EAAE,EAAE,oBAAoB,EAAc,MAAM,OAAO,CAAA;AAE/D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACvE,qMAAqM;IACrM,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B,sCAAsC;IACtC,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,sCAAsC;IACtC,KAAK,EAAE,KAAK,CAAC,SAAS,CAAA;IACtB,+GAA+G;IAC/G,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,sBAAsB,GAAG,IAAI,CACvC,oBAAoB,CAAC,iBAAiB,CAAC,EACvC,MAAM,eAAe,GAAG,UAAU,CACnC,GACC,eAAe,CAAA;AAEjB,eAAO,MAAM,UAAU,qLA4DtB,CAAA"}
|
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
import LoadingSpinner from '../internal/LoadingSpinner.js';
|
|
2
|
-
import
|
|
3
|
-
import 'react-aria/private/live-announcer/LiveAnnouncer';
|
|
2
|
+
import '../internal/LoadingAnnouncement.js';
|
|
4
3
|
import '../internal/tooltip/Tooltip.js';
|
|
5
|
-
import {
|
|
4
|
+
import { useTooltipTriggerProps } from '../internal/tooltip/useTooltipTriggerProps.js';
|
|
6
5
|
import { COMPONENT_SIZE_CLASS_MAP, COMPONENT_KIND_CLASS_MAP, wrapStringWithSpan } from '../../utilities/buttonLinkShared.js';
|
|
7
6
|
import classNames from 'classnames';
|
|
8
|
-
import {
|
|
9
|
-
import { mergeRefs } from 'react-aria/mergeRefs';
|
|
7
|
+
import React, { forwardRef } from 'react';
|
|
10
8
|
|
|
11
|
-
const BaseButton = forwardRef(({ "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, className, disabled = false, fullWidth, kind = "secondary", label, loading = false, loadingAriaLabel = "Loading...", prefix, size, suffix, ...restProps }, ref) => {
|
|
12
|
-
const
|
|
13
|
-
const { "aria-describedby": tooltipId, ...tooltipTriggerProps } = tooltip?.triggerProps ?? {};
|
|
14
|
-
const mergedProps = tooltip
|
|
15
|
-
? mergeProps(restProps, tooltipTriggerProps)
|
|
16
|
-
: restProps;
|
|
17
|
-
const mergedRef = tooltip
|
|
18
|
-
? mergeRefs(ref, tooltip.ref)
|
|
19
|
-
: ref;
|
|
20
|
-
const describedBy = [ariaDescribedBy, tooltipId].filter(Boolean).join(" ") || undefined;
|
|
21
|
-
const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", loading && "tds-btn--loading", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
|
|
9
|
+
const BaseButton = forwardRef(({ "aria-describedby": ariaDescribedBy, "aria-label": ariaLabel, className, disabled = false, focusableWhenDisabled = false, fullWidth, kind = "secondary", label, loading = false, loadingAriaLabel = "Loading...", prefix, size, suffix, ...restProps }, ref) => {
|
|
10
|
+
const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(restProps, ref, ariaDescribedBy);
|
|
22
11
|
const isDisabled = disabled || loading;
|
|
23
|
-
|
|
12
|
+
const isFocusableDisabled = isDisabled && focusableWhenDisabled;
|
|
13
|
+
const combinedClassName = classNames(kind && "tds-btn", size && size !== "md" && COMPONENT_SIZE_CLASS_MAP[size], kind && COMPONENT_KIND_CLASS_MAP[kind], fullWidth && "tds-btn--full-width", loading && "tds-btn--loading", isFocusableDisabled && "disabled", className, { "tds-btn--prefix": prefix, "tds-btn--suffix": suffix });
|
|
14
|
+
return (React.createElement("button", { type: "button", className: combinedClassName, ref: mergedRef, ...mergedProps, "aria-busy": loading || undefined, "aria-describedby": describedBy, "aria-disabled": isDisabled || undefined, disabled: isDisabled && !focusableWhenDisabled, onClick: isFocusableDisabled ? undefined : mergedProps.onClick, "aria-label": loading ? loadingAriaLabel : ariaLabel },
|
|
24
15
|
loading && React.createElement(LoadingSpinner, null),
|
|
25
16
|
wrapStringWithSpan(prefix),
|
|
26
17
|
loading ? React.createElement("span", null, label) : label,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BaseButton.js","sources":["../../../src/components/button/BaseButton.tsx"],"sourcesContent":["import \"./btn.css\"\n\nimport { LoadingSpinner } from \"@components/internal\"\nimport {
|
|
1
|
+
{"version":3,"file":"BaseButton.js","sources":["../../../src/components/button/BaseButton.tsx"],"sourcesContent":["import \"./btn.css\"\n\nimport { LoadingSpinner } from \"@components/internal\"\nimport { useTooltipTriggerProps } from \"@components/internal/tooltip/useTooltipTriggerProps\"\nimport {\n BaseComponentProps,\n COMPONENT_KIND_CLASS_MAP,\n COMPONENT_SIZE_CLASS_MAP,\n ComponentKind,\n wrapStringWithSpan,\n} from \"@utilities/buttonLinkShared\"\nimport classNames from \"classnames\"\nimport React, { ButtonHTMLAttributes, forwardRef } from \"react\"\n\nexport interface BaseButtonProps extends Omit<BaseComponentProps, \"kind\"> {\n /** Keeps a disabled button focusable, using `aria-disabled` without the native `disabled` attribute. Use when focus would otherwise be lost, such as a button that disables itself while focused. */\n focusableWhenDisabled?: boolean\n /** The visual style of the button. */\n kind?: ComponentKind\n /** The text content of the button. */\n label: React.ReactNode\n /** Whether the button is in a loading state. When true, the button is disabled and shows a loading spinner. */\n loading?: boolean\n /** Accessible label to use when the button is in loading state. */\n loadingAriaLabel?: string\n}\n\nexport type BaseButtonElementProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n keyof BaseButtonProps | \"children\"\n> &\n BaseButtonProps\n\nexport const BaseButton = forwardRef<HTMLButtonElement, BaseButtonElementProps>(\n (\n {\n \"aria-describedby\": ariaDescribedBy,\n \"aria-label\": ariaLabel,\n className,\n disabled = false,\n focusableWhenDisabled = false,\n fullWidth,\n kind = \"secondary\",\n label,\n loading = false,\n loadingAriaLabel = \"Loading...\",\n prefix,\n size,\n suffix,\n ...restProps\n }: BaseButtonElementProps,\n ref\n ) => {\n const { describedBy, mergedProps, mergedRef } = useTooltipTriggerProps(\n restProps,\n ref,\n ariaDescribedBy\n )\n\n const isDisabled = disabled || loading\n const isFocusableDisabled = isDisabled && focusableWhenDisabled\n\n const combinedClassName = classNames(\n kind && \"tds-btn\",\n size && size !== \"md\" && COMPONENT_SIZE_CLASS_MAP[size],\n kind && COMPONENT_KIND_CLASS_MAP[kind],\n fullWidth && \"tds-btn--full-width\",\n loading && \"tds-btn--loading\",\n isFocusableDisabled && \"disabled\",\n className,\n { \"tds-btn--prefix\": prefix, \"tds-btn--suffix\": suffix }\n )\n\n return (\n <button\n type=\"button\"\n className={combinedClassName}\n ref={mergedRef}\n {...mergedProps}\n aria-busy={loading || undefined}\n aria-describedby={describedBy}\n aria-disabled={isDisabled || undefined}\n disabled={isDisabled && !focusableWhenDisabled}\n onClick={isFocusableDisabled ? undefined : mergedProps.onClick}\n aria-label={loading ? loadingAriaLabel : ariaLabel}\n >\n {loading && <LoadingSpinner />}\n {wrapStringWithSpan(prefix)}\n {loading ? <span>{label}</span> : label}\n {wrapStringWithSpan(suffix)}\n </button>\n )\n }\n)\n\nBaseButton.displayName = \"BaseButton\"\n"],"names":[],"mappings":";;;;;;;;AAiCO,MAAM,UAAU,GAAG,UAAU,CAClC,CACE,EACE,kBAAkB,EAAE,eAAe,EACnC,YAAY,EAAE,SAAS,EACvB,SAAS,EACT,QAAQ,GAAG,KAAK,EAChB,qBAAqB,GAAG,KAAK,EAC7B,SAAS,EACT,IAAI,GAAG,WAAW,EAClB,KAAK,EACL,OAAO,GAAG,KAAK,EACf,gBAAgB,GAAG,YAAY,EAC/B,MAAM,EACN,IAAI,EACJ,MAAM,EACN,GAAG,SAAS,EACW,EACzB,GAAG,KACD;AACF,IAAA,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,SAAS,EAAE,GAAG,sBAAsB,CACpE,SAAS,EACT,GAAG,EACH,eAAe,CAChB;AAED,IAAA,MAAM,UAAU,GAAG,QAAQ,IAAI,OAAO;AACtC,IAAA,MAAM,mBAAmB,GAAG,UAAU,IAAI,qBAAqB;IAE/D,MAAM,iBAAiB,GAAG,UAAU,CAClC,IAAI,IAAI,SAAS,EACjB,IAAI,IAAI,IAAI,KAAK,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACvD,IAAI,IAAI,wBAAwB,CAAC,IAAI,CAAC,EACtC,SAAS,IAAI,qBAAqB,EAClC,OAAO,IAAI,kBAAkB,EAC7B,mBAAmB,IAAI,UAAU,EACjC,SAAS,EACT,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,CACzD;IAED,QACE,KAAA,CAAA,aAAA,CAAA,QAAA,EAAA,EACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAE,iBAAiB,EAC5B,GAAG,EAAE,SAAS,EAAA,GACV,WAAW,EAAA,WAAA,EACJ,OAAO,IAAI,SAAS,EAAA,kBAAA,EACb,WAAW,EAAA,eAAA,EACd,UAAU,IAAI,SAAS,EACtC,QAAQ,EAAE,UAAU,IAAI,CAAC,qBAAqB,EAC9C,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,WAAW,CAAC,OAAO,EAAA,YAAA,EAClD,OAAO,GAAG,gBAAgB,GAAG,SAAS,EAAA;QAEjD,OAAO,IAAI,KAAA,CAAA,aAAA,CAAC,cAAc,EAAA,IAAA,CAAG;QAC7B,kBAAkB,CAAC,MAAM,CAAC;QAC1B,OAAO,GAAG,KAAA,CAAA,aAAA,CAAA,MAAA,EAAA,IAAA,EAAO,KAAK,CAAQ,GAAG,KAAK;AACtC,QAAA,kBAAkB,CAAC,MAAM,CAAC,CACpB;AAEb,CAAC;AAGH,UAAU,CAAC,WAAW,GAAG,YAAY;;;;"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export declare const LOADING_ANNOUNCEMENT_DELAY_MS = 500;
|
|
2
|
+
/**
|
|
3
|
+
* Turns off every {@link LoadingAnnouncement} inside it while `true`. Use it
|
|
4
|
+
* when focus moves into a field during the load, because the screen reader
|
|
5
|
+
* then speaks the message only after the results appear.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SuppressLoadingAnnouncementContext: import("react").Context<boolean>;
|
|
2
8
|
/**
|
|
3
9
|
* Announces `message` to screen reader users while a loading indicator is
|
|
4
10
|
* shown. Render it next to the spinner, only while loading. It waits
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingAnnouncement.d.ts","sourceRoot":"","sources":["../../../src/components/internal/LoadingAnnouncement.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,6BAA6B,MAAM,CAAA;AAEhD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"LoadingAnnouncement.d.ts","sourceRoot":"","sources":["../../../src/components/internal/LoadingAnnouncement.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,6BAA6B,MAAM,CAAA;AAEhD;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,kCAAuB,CAAA;AAEtE;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,QAcnE"}
|