@urbicon-ui/table 6.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +153 -0
- package/dist/cells/ActionButtons.svelte +224 -0
- package/dist/cells/ActionButtons.svelte.d.ts +74 -0
- package/dist/cells/CopyButton.svelte +89 -0
- package/dist/cells/CopyButton.svelte.d.ts +33 -0
- package/dist/cells/CustomCell.svelte +136 -0
- package/dist/cells/CustomCell.svelte.d.ts +44 -0
- package/dist/cells/DateCell.svelte +194 -0
- package/dist/cells/DateCell.svelte.d.ts +39 -0
- package/dist/cells/LinkCell.svelte +240 -0
- package/dist/cells/LinkCell.svelte.d.ts +42 -0
- package/dist/cells/NumberCell.svelte +225 -0
- package/dist/cells/NumberCell.svelte.d.ts +47 -0
- package/dist/cells/StatusBadge.svelte +121 -0
- package/dist/cells/StatusBadge.svelte.d.ts +44 -0
- package/dist/cells/UserAvatar.svelte +71 -0
- package/dist/cells/UserAvatar.svelte.d.ts +37 -0
- package/dist/cells/index.d.ts +8 -0
- package/dist/cells/index.js +9 -0
- package/dist/core/EmptyState.svelte +161 -0
- package/dist/core/EmptyState.svelte.d.ts +16 -0
- package/dist/core/ErrorState.svelte +158 -0
- package/dist/core/ErrorState.svelte.d.ts +15 -0
- package/dist/core/GroupedRow.svelte +239 -0
- package/dist/core/GroupedRow.svelte.d.ts +18 -0
- package/dist/core/LoadingState.svelte +75 -0
- package/dist/core/LoadingState.svelte.d.ts +14 -0
- package/dist/core/MobileCard.svelte +151 -0
- package/dist/core/MobileCard.svelte.d.ts +15 -0
- package/dist/core/TableCell.svelte +105 -0
- package/dist/core/TableCell.svelte.d.ts +14 -0
- package/dist/core/TableDesktop.svelte +480 -0
- package/dist/core/TableDesktop.svelte.d.ts +26 -0
- package/dist/core/TableHead.svelte +314 -0
- package/dist/core/TableHead.svelte.d.ts +7 -0
- package/dist/core/TableMobile.svelte +112 -0
- package/dist/core/TableMobile.svelte.d.ts +13 -0
- package/dist/core/TableProvider.svelte +271 -0
- package/dist/core/TableProvider.svelte.d.ts +40 -0
- package/dist/core/TableRow.svelte +171 -0
- package/dist/core/TableRow.svelte.d.ts +16 -0
- package/dist/core/index.d.ts +17 -0
- package/dist/core/index.js +14 -0
- package/dist/core/sticky-context.svelte.d.ts +48 -0
- package/dist/core/sticky-context.svelte.js +88 -0
- package/dist/core/table/Table.svelte +304 -0
- package/dist/core/table/Table.svelte.d.ts +26 -0
- package/dist/core/table/index.d.ts +448 -0
- package/dist/core/table/index.js +1 -0
- package/dist/core/table-style-context.d.ts +66 -0
- package/dist/core/table-style-context.js +26 -0
- package/dist/factories/ColumnValidation.d.ts +49 -0
- package/dist/factories/ColumnValidation.js +188 -0
- package/dist/factories/TableColumns.d.ts +97 -0
- package/dist/factories/TableColumns.js +262 -0
- package/dist/factories/TypedColumnBuilder.d.ts +41 -0
- package/dist/factories/TypedColumnBuilder.js +72 -0
- package/dist/factories/index.d.ts +12 -0
- package/dist/factories/index.js +13 -0
- package/dist/features/HeaderMenu.svelte +236 -0
- package/dist/features/HeaderMenu.svelte.d.ts +8 -0
- package/dist/features/LiveUpdateBanner.svelte +66 -0
- package/dist/features/LiveUpdateBanner.svelte.d.ts +6 -0
- package/dist/features/SearchHighlight.svelte +21 -0
- package/dist/features/SearchHighlight.svelte.d.ts +8 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte +104 -0
- package/dist/features/SmartFilterBar/ChipsField.svelte.d.ts +5 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte +84 -0
- package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte +367 -0
- package/dist/features/SmartFilterBar/FilterMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte +82 -0
- package/dist/features/SmartFilterBar/GroupingMenu.svelte.d.ts +3 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte +109 -0
- package/dist/features/SmartFilterBar/SmartFilterBar.svelte.d.ts +11 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte +118 -0
- package/dist/features/SmartFilterBar/SummaryMenu.svelte.d.ts +3 -0
- package/dist/features/SummaryRow.svelte +97 -0
- package/dist/features/SummaryRow.svelte.d.ts +8 -0
- package/dist/features/index.d.ts +4 -0
- package/dist/features/index.js +4 -0
- package/dist/i18n/index.d.ts +366 -0
- package/dist/i18n/index.js +21 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +41 -0
- package/dist/stores/TableStore.svelte.d.ts +192 -0
- package/dist/stores/TableStore.svelte.js +362 -0
- package/dist/stores/concerns/index.d.ts +15 -0
- package/dist/stores/concerns/index.js +14 -0
- package/dist/stores/concerns/types.d.ts +31 -0
- package/dist/stores/concerns/types.js +1 -0
- package/dist/stores/concerns/useColumnOrder.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnOrder.svelte.js +81 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.d.ts +16 -0
- package/dist/stores/concerns/useColumnVisibility.svelte.js +58 -0
- package/dist/stores/concerns/useExpansion.svelte.d.ts +9 -0
- package/dist/stores/concerns/useExpansion.svelte.js +32 -0
- package/dist/stores/concerns/useFiltering.svelte.d.ts +20 -0
- package/dist/stores/concerns/useFiltering.svelte.js +109 -0
- package/dist/stores/concerns/useFocusManagement.svelte.d.ts +15 -0
- package/dist/stores/concerns/useFocusManagement.svelte.js +52 -0
- package/dist/stores/concerns/useGrouping.svelte.d.ts +15 -0
- package/dist/stores/concerns/useGrouping.svelte.js +86 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.d.ts +45 -0
- package/dist/stores/concerns/useLiveUpdates.svelte.js +175 -0
- package/dist/stores/concerns/usePagination.svelte.d.ts +18 -0
- package/dist/stores/concerns/usePagination.svelte.js +54 -0
- package/dist/stores/concerns/usePersistence.svelte.d.ts +36 -0
- package/dist/stores/concerns/usePersistence.svelte.js +167 -0
- package/dist/stores/concerns/useRemoteData.svelte.d.ts +21 -0
- package/dist/stores/concerns/useRemoteData.svelte.js +64 -0
- package/dist/stores/concerns/useSearch.svelte.d.ts +8 -0
- package/dist/stores/concerns/useSearch.svelte.js +16 -0
- package/dist/stores/concerns/useSelection.svelte.d.ts +21 -0
- package/dist/stores/concerns/useSelection.svelte.js +110 -0
- package/dist/stores/concerns/useSorting.svelte.d.ts +11 -0
- package/dist/stores/concerns/useSorting.svelte.js +70 -0
- package/dist/stores/concerns/useSummary.svelte.d.ts +18 -0
- package/dist/stores/concerns/useSummary.svelte.js +96 -0
- package/dist/stores/index.d.ts +1 -0
- package/dist/stores/index.js +1 -0
- package/dist/style/index.css +137 -0
- package/dist/style/index.d.ts +2 -0
- package/dist/style/index.js +2 -0
- package/dist/style/table-theme.css +131 -0
- package/dist/style/themes/comfortable.css +20 -0
- package/dist/style/themes/compact.css +20 -0
- package/dist/translations/de.d.ts +177 -0
- package/dist/translations/de.js +176 -0
- package/dist/translations/en.d.ts +177 -0
- package/dist/translations/en.js +176 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/tableTypes.d.ts +262 -0
- package/dist/types/tableTypes.js +1 -0
- package/dist/utils/index.d.ts +165 -0
- package/dist/utils/index.js +330 -0
- package/dist/utils/sticky-measure.d.ts +54 -0
- package/dist/utils/sticky-measure.js +107 -0
- package/dist/utils/virtualizer.d.ts +43 -0
- package/dist/utils/virtualizer.js +43 -0
- package/dist/variants/index.d.ts +11 -0
- package/dist/variants/index.js +15 -0
- package/dist/variants/table-cells.variants.d.ts +827 -0
- package/dist/variants/table-cells.variants.js +627 -0
- package/dist/variants/table-features.variants.d.ts +547 -0
- package/dist/variants/table-features.variants.js +412 -0
- package/dist/variants/table-states.variants.d.ts +594 -0
- package/dist/variants/table-states.variants.js +394 -0
- package/dist/variants/table.system.d.ts +301 -0
- package/dist/variants/table.system.js +314 -0
- package/dist/variants/table.variants.d.ts +428 -0
- package/dist/variants/table.variants.js +360 -0
- package/package.json +93 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
2
|
+
import { normalizeItems } from '../../utils';
|
|
3
|
+
function pickRowId(item) {
|
|
4
|
+
const id = item.id ?? item.__index;
|
|
5
|
+
return typeof id === 'string' || typeof id === 'number' ? id : undefined;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Live updates concern: manages a pending buffer of inserts, updates, and deletes
|
|
9
|
+
* that are NOT immediately applied to the visible table.
|
|
10
|
+
*
|
|
11
|
+
* Instead, the UI shows a notification (e.g. "3 new, 2 updated") and the user
|
|
12
|
+
* decides when to merge them. This avoids disorienting UX issues like rows
|
|
13
|
+
* jumping, disappearing, or conflicting with active selection/editing.
|
|
14
|
+
*
|
|
15
|
+
* @param state - Shared table state (items are modified on apply).
|
|
16
|
+
*/
|
|
17
|
+
export function useLiveUpdates(state) {
|
|
18
|
+
let inserts = $state([]);
|
|
19
|
+
let updates = $state([]);
|
|
20
|
+
let deletes = new SvelteSet();
|
|
21
|
+
/** IDs of rows that were recently updated (for visual highlight). Auto-cleared after timeout. */
|
|
22
|
+
let recentlyUpdatedIds = new SvelteSet();
|
|
23
|
+
let highlightTimer = null;
|
|
24
|
+
const counts = $derived({
|
|
25
|
+
inserts: inserts.length,
|
|
26
|
+
updates: updates.length,
|
|
27
|
+
deletes: deletes.size,
|
|
28
|
+
total: inserts.length + updates.length + deletes.size
|
|
29
|
+
});
|
|
30
|
+
const hasPending = $derived(counts.total > 0);
|
|
31
|
+
// ── Push methods (called by the developer from WebSocket/SSE/Polling) ──
|
|
32
|
+
function pushInsert(item) {
|
|
33
|
+
// Deduplicate: if an item with the same ID is already pending, replace it
|
|
34
|
+
const id = pickRowId(item);
|
|
35
|
+
if (id !== undefined) {
|
|
36
|
+
inserts = inserts.filter((i) => pickRowId(i) !== id);
|
|
37
|
+
}
|
|
38
|
+
inserts = [...inserts, item];
|
|
39
|
+
}
|
|
40
|
+
function pushUpdate(id, changes) {
|
|
41
|
+
// Merge with existing pending update for the same ID
|
|
42
|
+
const existing = updates.findIndex((u) => u.id === id);
|
|
43
|
+
if (existing >= 0) {
|
|
44
|
+
updates[existing] = { id, changes: { ...updates[existing].changes, ...changes } };
|
|
45
|
+
updates = [...updates]; // trigger reactivity
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
updates = [...updates, { id, changes }];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function pushDelete(id) {
|
|
52
|
+
const next = new SvelteSet(deletes);
|
|
53
|
+
next.add(id);
|
|
54
|
+
deletes = next;
|
|
55
|
+
// If the item was pending insert, remove it from inserts instead
|
|
56
|
+
const pendingInsertIdx = inserts.findIndex((i) => pickRowId(i) === id);
|
|
57
|
+
if (pendingInsertIdx >= 0) {
|
|
58
|
+
inserts = inserts.filter((_, idx) => idx !== pendingInsertIdx);
|
|
59
|
+
// Also remove from deletes since it was never in items
|
|
60
|
+
const cleaned = new SvelteSet(deletes);
|
|
61
|
+
cleaned.delete(id);
|
|
62
|
+
deletes = cleaned;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// ── Apply methods (merge pending changes into state.items) ──
|
|
66
|
+
function applyAll() {
|
|
67
|
+
applyDeletes();
|
|
68
|
+
applyUpdates();
|
|
69
|
+
applyInserts();
|
|
70
|
+
}
|
|
71
|
+
function applyInserts() {
|
|
72
|
+
if (inserts.length === 0)
|
|
73
|
+
return;
|
|
74
|
+
state.items = normalizeItems([...state.items, ...inserts]);
|
|
75
|
+
inserts = [];
|
|
76
|
+
}
|
|
77
|
+
function applyUpdates() {
|
|
78
|
+
if (updates.length === 0)
|
|
79
|
+
return;
|
|
80
|
+
const updateMap = new SvelteMap(updates.map((u) => [u.id, u.changes]));
|
|
81
|
+
const updatedIds = new SvelteSet(updateMap.keys());
|
|
82
|
+
state.items = state.items.map((item) => {
|
|
83
|
+
const id = pickRowId(item);
|
|
84
|
+
if (id !== undefined && updateMap.has(id)) {
|
|
85
|
+
updatedIds.delete(id);
|
|
86
|
+
return { ...item, ...updateMap.get(id) };
|
|
87
|
+
}
|
|
88
|
+
return item;
|
|
89
|
+
});
|
|
90
|
+
if (import.meta.env?.DEV && updatedIds.size > 0) {
|
|
91
|
+
console.warn(`[Table] Live update: ${updatedIds.size} orphaned ID(s) not found in items:`, [
|
|
92
|
+
...updatedIds
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
// Mark as recently updated for visual highlight
|
|
96
|
+
startHighlight(new SvelteSet(updateMap.keys()));
|
|
97
|
+
updates = [];
|
|
98
|
+
}
|
|
99
|
+
function applyDeletes() {
|
|
100
|
+
if (deletes.size === 0)
|
|
101
|
+
return;
|
|
102
|
+
const deletedIds = new SvelteSet(deletes);
|
|
103
|
+
state.items = state.items.filter((item) => {
|
|
104
|
+
const id = pickRowId(item);
|
|
105
|
+
if (id !== undefined && deletedIds.has(id)) {
|
|
106
|
+
deletedIds.delete(id);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
});
|
|
111
|
+
if (import.meta.env?.DEV && deletedIds.size > 0) {
|
|
112
|
+
console.warn(`[Table] Live delete: ${deletedIds.size} orphaned ID(s) not found in items:`, [
|
|
113
|
+
...deletedIds
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
// Also remove from selection if selected
|
|
117
|
+
if (state.selectionMode !== 'none' && state.selectedIds.size > 0) {
|
|
118
|
+
const nextSelected = new SvelteSet(state.selectedIds);
|
|
119
|
+
for (const id of deletes) {
|
|
120
|
+
nextSelected.delete(id);
|
|
121
|
+
}
|
|
122
|
+
state.selectedIds = nextSelected;
|
|
123
|
+
}
|
|
124
|
+
deletes = new SvelteSet();
|
|
125
|
+
}
|
|
126
|
+
function dismissAll() {
|
|
127
|
+
inserts = [];
|
|
128
|
+
updates = [];
|
|
129
|
+
deletes = new SvelteSet();
|
|
130
|
+
}
|
|
131
|
+
// ── Highlight management ──
|
|
132
|
+
function startHighlight(ids) {
|
|
133
|
+
recentlyUpdatedIds = new SvelteSet([...recentlyUpdatedIds, ...ids]);
|
|
134
|
+
if (highlightTimer)
|
|
135
|
+
clearTimeout(highlightTimer);
|
|
136
|
+
highlightTimer = setTimeout(() => {
|
|
137
|
+
recentlyUpdatedIds = new SvelteSet();
|
|
138
|
+
highlightTimer = null;
|
|
139
|
+
}, 3000);
|
|
140
|
+
}
|
|
141
|
+
function isRecentlyUpdated(id) {
|
|
142
|
+
return recentlyUpdatedIds.has(id);
|
|
143
|
+
}
|
|
144
|
+
/** Check if an item is pending deletion (for strikethrough/fade indicator). */
|
|
145
|
+
function isPendingDelete(id) {
|
|
146
|
+
return deletes.has(id);
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
get counts() {
|
|
150
|
+
return counts;
|
|
151
|
+
},
|
|
152
|
+
get hasPending() {
|
|
153
|
+
return hasPending;
|
|
154
|
+
},
|
|
155
|
+
get pendingInserts() {
|
|
156
|
+
return inserts;
|
|
157
|
+
},
|
|
158
|
+
get pendingUpdates() {
|
|
159
|
+
return updates;
|
|
160
|
+
},
|
|
161
|
+
get pendingDeletes() {
|
|
162
|
+
return deletes;
|
|
163
|
+
},
|
|
164
|
+
pushInsert,
|
|
165
|
+
pushUpdate,
|
|
166
|
+
pushDelete,
|
|
167
|
+
applyAll,
|
|
168
|
+
applyInserts,
|
|
169
|
+
applyUpdates,
|
|
170
|
+
applyDeletes,
|
|
171
|
+
dismissAll,
|
|
172
|
+
isRecentlyUpdated,
|
|
173
|
+
isPendingDelete
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TableItem } from '../../types/tableTypes';
|
|
2
|
+
import type { TableState } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Pagination concern: manages page state and computes paginated items.
|
|
5
|
+
* In server mode, items are already paginated by the server — this concern
|
|
6
|
+
* only computes `totalItems`/`totalPages` from `serverTotalItems`.
|
|
7
|
+
* @param state - Shared table state.
|
|
8
|
+
* @param getFilteredItems - Getter for filtered items (client mode: local count).
|
|
9
|
+
* @param getSortedItems - Getter for upstream sorted items.
|
|
10
|
+
*/
|
|
11
|
+
export declare function usePagination(state: TableState, getFilteredItems: () => TableItem[], getSortedItems: () => TableItem[]): {
|
|
12
|
+
readonly totalItems: number;
|
|
13
|
+
readonly totalPages: number;
|
|
14
|
+
readonly paginatedItems: TableItem[];
|
|
15
|
+
setPage: (page: number) => void;
|
|
16
|
+
goToPage: (page: number) => void;
|
|
17
|
+
setItemsPerPage: (count: number) => void;
|
|
18
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination concern: manages page state and computes paginated items.
|
|
3
|
+
* In server mode, items are already paginated by the server — this concern
|
|
4
|
+
* only computes `totalItems`/`totalPages` from `serverTotalItems`.
|
|
5
|
+
* @param state - Shared table state.
|
|
6
|
+
* @param getFilteredItems - Getter for filtered items (client mode: local count).
|
|
7
|
+
* @param getSortedItems - Getter for upstream sorted items.
|
|
8
|
+
*/
|
|
9
|
+
export function usePagination(state, getFilteredItems, getSortedItems) {
|
|
10
|
+
const totalItems = $derived(state.mode === 'server' ? state.serverTotalItems : getFilteredItems().length);
|
|
11
|
+
const totalPages = $derived.by(() => {
|
|
12
|
+
if (state.groupByKey)
|
|
13
|
+
return 1;
|
|
14
|
+
if (totalItems === 0)
|
|
15
|
+
return 1;
|
|
16
|
+
return Math.ceil(totalItems / state.itemsPerPage);
|
|
17
|
+
});
|
|
18
|
+
const paginatedItems = $derived.by(() => {
|
|
19
|
+
const items = getSortedItems();
|
|
20
|
+
// In server mode, items are already paginated by the server
|
|
21
|
+
if (state.mode === 'server')
|
|
22
|
+
return items;
|
|
23
|
+
// Skip pagination when grouped (groups should be fully visible)
|
|
24
|
+
if (state.groupByKey)
|
|
25
|
+
return items;
|
|
26
|
+
return items.slice((state.currentPage - 1) * state.itemsPerPage, state.currentPage * state.itemsPerPage);
|
|
27
|
+
});
|
|
28
|
+
function setPage(page) {
|
|
29
|
+
state.currentPage = page;
|
|
30
|
+
}
|
|
31
|
+
function goToPage(page) {
|
|
32
|
+
if (page >= 1 && page <= totalPages) {
|
|
33
|
+
state.currentPage = page;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function setItemsPerPage(count) {
|
|
37
|
+
state.itemsPerPage = count;
|
|
38
|
+
state.currentPage = 1;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
get totalItems() {
|
|
42
|
+
return totalItems;
|
|
43
|
+
},
|
|
44
|
+
get totalPages() {
|
|
45
|
+
return totalPages;
|
|
46
|
+
},
|
|
47
|
+
get paginatedItems() {
|
|
48
|
+
return paginatedItems;
|
|
49
|
+
},
|
|
50
|
+
setPage,
|
|
51
|
+
goToPage,
|
|
52
|
+
setItemsPerPage
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TablePersistenceConfig } from '../TableStore.svelte';
|
|
2
|
+
import type { TableState } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Persistence concern: manages syncing table state to/from storage.
|
|
5
|
+
*
|
|
6
|
+
* State on the shared `state` object (search, filters, group, summary,
|
|
7
|
+
* sort) is hydrated directly here before downstream concerns initialise.
|
|
8
|
+
* State that lives inside other concerns (column visibility, column
|
|
9
|
+
* order) cannot be reached from here — those values are exposed via the
|
|
10
|
+
* `initialHiddenColumnIds` / `initialColumnOrder` getters so the host
|
|
11
|
+
* `TableStore` can hand them back to the owning concern after init.
|
|
12
|
+
*
|
|
13
|
+
* Mutating concerns call the matching `sync*` function after each
|
|
14
|
+
* mutation; `usePersistence` debounces the write to storage internally
|
|
15
|
+
* (default 500 ms, configurable via `persistenceConfig.debounceMs`).
|
|
16
|
+
*/
|
|
17
|
+
export declare function usePersistence(state: TableState, persistenceConfig?: TablePersistenceConfig): {
|
|
18
|
+
syncFilters: () => void;
|
|
19
|
+
syncSearch: () => void;
|
|
20
|
+
syncGroupByKey: () => void;
|
|
21
|
+
syncSummaryConfigs: () => void;
|
|
22
|
+
syncSortState: () => void;
|
|
23
|
+
syncHiddenColumns: (hiddenIds: string[]) => void;
|
|
24
|
+
syncColumnOrder: (order: string[]) => void;
|
|
25
|
+
readonly initialHiddenColumnIds: string[];
|
|
26
|
+
readonly initialColumnOrder: string[];
|
|
27
|
+
clearPersistedFilters: () => void | undefined;
|
|
28
|
+
clearPersistedSearchTerm: () => void | undefined;
|
|
29
|
+
clearPersistedGroupByKey: () => void | undefined;
|
|
30
|
+
clearPersistedSummaryConfigs: () => void | undefined;
|
|
31
|
+
clearPersistedSortState: () => void | undefined;
|
|
32
|
+
clearPersistedHiddenColumns: () => void | undefined;
|
|
33
|
+
clearPersistedColumnOrder: () => void | undefined;
|
|
34
|
+
clearAllPersistentData: () => void;
|
|
35
|
+
forceSavePersistentData: () => void;
|
|
36
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { createPersistentColumnOrder, createPersistentFilters, createPersistentGroupByKey, createPersistentHiddenColumns, createPersistentSearchTerm, createPersistentSortState, createPersistentSummaryConfigs } from '../../utils';
|
|
2
|
+
/**
|
|
3
|
+
* Persistence concern: manages syncing table state to/from storage.
|
|
4
|
+
*
|
|
5
|
+
* State on the shared `state` object (search, filters, group, summary,
|
|
6
|
+
* sort) is hydrated directly here before downstream concerns initialise.
|
|
7
|
+
* State that lives inside other concerns (column visibility, column
|
|
8
|
+
* order) cannot be reached from here — those values are exposed via the
|
|
9
|
+
* `initialHiddenColumnIds` / `initialColumnOrder` getters so the host
|
|
10
|
+
* `TableStore` can hand them back to the owning concern after init.
|
|
11
|
+
*
|
|
12
|
+
* Mutating concerns call the matching `sync*` function after each
|
|
13
|
+
* mutation; `usePersistence` debounces the write to storage internally
|
|
14
|
+
* (default 500 ms, configurable via `persistenceConfig.debounceMs`).
|
|
15
|
+
*/
|
|
16
|
+
export function usePersistence(state, persistenceConfig) {
|
|
17
|
+
let persistentFilters;
|
|
18
|
+
let persistentSearchTerm;
|
|
19
|
+
let persistentGroupByKey;
|
|
20
|
+
let persistentSummaryConfigs;
|
|
21
|
+
let persistentSortState;
|
|
22
|
+
let persistentHiddenColumns;
|
|
23
|
+
let persistentColumnOrder;
|
|
24
|
+
if (persistenceConfig) {
|
|
25
|
+
if (persistenceConfig.persistFilters !== false) {
|
|
26
|
+
persistentFilters = createPersistentFilters({
|
|
27
|
+
tableId: persistenceConfig.tableId,
|
|
28
|
+
storage: persistenceConfig.storage,
|
|
29
|
+
debounceMs: persistenceConfig.debounceMs
|
|
30
|
+
});
|
|
31
|
+
if (persistentFilters.value.length > 0) {
|
|
32
|
+
state.activeFilters = persistentFilters.value;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (persistenceConfig.persistSearch !== false) {
|
|
36
|
+
persistentSearchTerm = createPersistentSearchTerm({
|
|
37
|
+
tableId: persistenceConfig.tableId,
|
|
38
|
+
storage: persistenceConfig.storage,
|
|
39
|
+
debounceMs: persistenceConfig.debounceMs
|
|
40
|
+
});
|
|
41
|
+
if (persistentSearchTerm.value) {
|
|
42
|
+
state.searchTerm = persistentSearchTerm.value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (persistenceConfig.persistGroupByKey !== false) {
|
|
46
|
+
persistentGroupByKey = createPersistentGroupByKey({
|
|
47
|
+
tableId: persistenceConfig.tableId,
|
|
48
|
+
storage: persistenceConfig.storage,
|
|
49
|
+
debounceMs: persistenceConfig.debounceMs
|
|
50
|
+
});
|
|
51
|
+
if (persistentGroupByKey.value) {
|
|
52
|
+
state.groupByKey = persistentGroupByKey.value;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (persistenceConfig.persistSummaryConfigs !== false) {
|
|
56
|
+
persistentSummaryConfigs = createPersistentSummaryConfigs({
|
|
57
|
+
tableId: persistenceConfig.tableId,
|
|
58
|
+
storage: persistenceConfig.storage,
|
|
59
|
+
debounceMs: persistenceConfig.debounceMs
|
|
60
|
+
});
|
|
61
|
+
if (persistentSummaryConfigs.value.length > 0) {
|
|
62
|
+
state.summaryConfigs = persistentSummaryConfigs.value;
|
|
63
|
+
state.showSummary = true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (persistenceConfig.persistSort !== false) {
|
|
67
|
+
persistentSortState = createPersistentSortState({
|
|
68
|
+
tableId: persistenceConfig.tableId,
|
|
69
|
+
storage: persistenceConfig.storage,
|
|
70
|
+
debounceMs: persistenceConfig.debounceMs
|
|
71
|
+
});
|
|
72
|
+
if (persistentSortState.value.column) {
|
|
73
|
+
state.sortColumn = persistentSortState.value.column;
|
|
74
|
+
state.sortDirection = persistentSortState.value.direction;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (persistenceConfig.persistColumnVisibility !== false) {
|
|
78
|
+
persistentHiddenColumns = createPersistentHiddenColumns({
|
|
79
|
+
tableId: persistenceConfig.tableId,
|
|
80
|
+
storage: persistenceConfig.storage,
|
|
81
|
+
debounceMs: persistenceConfig.debounceMs
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (persistenceConfig.persistColumnOrder !== false) {
|
|
85
|
+
persistentColumnOrder = createPersistentColumnOrder({
|
|
86
|
+
tableId: persistenceConfig.tableId,
|
|
87
|
+
storage: persistenceConfig.storage,
|
|
88
|
+
debounceMs: persistenceConfig.debounceMs
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Sync functions called by other concerns after mutations
|
|
93
|
+
function syncFilters() {
|
|
94
|
+
if (persistentFilters)
|
|
95
|
+
persistentFilters.value = state.activeFilters;
|
|
96
|
+
}
|
|
97
|
+
function syncSearch() {
|
|
98
|
+
if (persistentSearchTerm)
|
|
99
|
+
persistentSearchTerm.value = state.searchTerm;
|
|
100
|
+
}
|
|
101
|
+
function syncGroupByKey() {
|
|
102
|
+
if (persistentGroupByKey)
|
|
103
|
+
persistentGroupByKey.value = state.groupByKey;
|
|
104
|
+
}
|
|
105
|
+
function syncSummaryConfigs() {
|
|
106
|
+
if (persistentSummaryConfigs)
|
|
107
|
+
persistentSummaryConfigs.value = state.summaryConfigs;
|
|
108
|
+
}
|
|
109
|
+
function syncSortState() {
|
|
110
|
+
if (persistentSortState)
|
|
111
|
+
persistentSortState.value = {
|
|
112
|
+
column: state.sortColumn,
|
|
113
|
+
direction: state.sortDirection
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function syncHiddenColumns(hiddenIds) {
|
|
117
|
+
if (persistentHiddenColumns)
|
|
118
|
+
persistentHiddenColumns.value = hiddenIds;
|
|
119
|
+
}
|
|
120
|
+
function syncColumnOrder(order) {
|
|
121
|
+
if (persistentColumnOrder)
|
|
122
|
+
persistentColumnOrder.value = order;
|
|
123
|
+
}
|
|
124
|
+
// Public API
|
|
125
|
+
function clearAllPersistentData() {
|
|
126
|
+
persistentFilters?.reset();
|
|
127
|
+
persistentSearchTerm?.reset();
|
|
128
|
+
persistentGroupByKey?.reset();
|
|
129
|
+
persistentSummaryConfigs?.reset();
|
|
130
|
+
persistentSortState?.reset();
|
|
131
|
+
persistentHiddenColumns?.reset();
|
|
132
|
+
persistentColumnOrder?.reset();
|
|
133
|
+
}
|
|
134
|
+
function forceSavePersistentData() {
|
|
135
|
+
persistentFilters?.forceSave();
|
|
136
|
+
persistentSearchTerm?.forceSave();
|
|
137
|
+
persistentGroupByKey?.forceSave();
|
|
138
|
+
persistentSummaryConfigs?.forceSave();
|
|
139
|
+
persistentSortState?.forceSave();
|
|
140
|
+
persistentHiddenColumns?.forceSave();
|
|
141
|
+
persistentColumnOrder?.forceSave();
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
syncFilters,
|
|
145
|
+
syncSearch,
|
|
146
|
+
syncGroupByKey,
|
|
147
|
+
syncSummaryConfigs,
|
|
148
|
+
syncSortState,
|
|
149
|
+
syncHiddenColumns,
|
|
150
|
+
syncColumnOrder,
|
|
151
|
+
get initialHiddenColumnIds() {
|
|
152
|
+
return persistentHiddenColumns?.value ?? [];
|
|
153
|
+
},
|
|
154
|
+
get initialColumnOrder() {
|
|
155
|
+
return persistentColumnOrder?.value ?? [];
|
|
156
|
+
},
|
|
157
|
+
clearPersistedFilters: () => persistentFilters?.reset(),
|
|
158
|
+
clearPersistedSearchTerm: () => persistentSearchTerm?.reset(),
|
|
159
|
+
clearPersistedGroupByKey: () => persistentGroupByKey?.reset(),
|
|
160
|
+
clearPersistedSummaryConfigs: () => persistentSummaryConfigs?.reset(),
|
|
161
|
+
clearPersistedSortState: () => persistentSortState?.reset(),
|
|
162
|
+
clearPersistedHiddenColumns: () => persistentHiddenColumns?.reset(),
|
|
163
|
+
clearPersistedColumnOrder: () => persistentColumnOrder?.reset(),
|
|
164
|
+
clearAllPersistentData,
|
|
165
|
+
forceSavePersistentData
|
|
166
|
+
};
|
|
167
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TableItem, TableQuery } from '../../types/tableTypes';
|
|
2
|
+
import type { TableState } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Remote data concern: derives the current query state for server-side fetching.
|
|
5
|
+
*
|
|
6
|
+
* This concern does NOT fetch data itself (keeping the store synchronous).
|
|
7
|
+
* Instead, it exposes a reactive `query` object and `setServerResult()` for
|
|
8
|
+
* the component layer to drive the async lifecycle.
|
|
9
|
+
*
|
|
10
|
+
* @param state - Shared table state.
|
|
11
|
+
*/
|
|
12
|
+
export declare function useRemoteData(state: TableState): {
|
|
13
|
+
readonly query: TableQuery;
|
|
14
|
+
readonly queryKey: string;
|
|
15
|
+
setServerResult: (result: {
|
|
16
|
+
items: TableItem[];
|
|
17
|
+
totalItems: number;
|
|
18
|
+
}) => void;
|
|
19
|
+
setServerError: (error: string) => void;
|
|
20
|
+
setServerLoading: () => void;
|
|
21
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote data concern: derives the current query state for server-side fetching.
|
|
3
|
+
*
|
|
4
|
+
* This concern does NOT fetch data itself (keeping the store synchronous).
|
|
5
|
+
* Instead, it exposes a reactive `query` object and `setServerResult()` for
|
|
6
|
+
* the component layer to drive the async lifecycle.
|
|
7
|
+
*
|
|
8
|
+
* @param state - Shared table state.
|
|
9
|
+
*/
|
|
10
|
+
export function useRemoteData(state) {
|
|
11
|
+
/**
|
|
12
|
+
* Current query derived from table state.
|
|
13
|
+
* Changes reactively when any filter/sort/page/search state changes.
|
|
14
|
+
*/
|
|
15
|
+
const query = $derived.by(() => ({
|
|
16
|
+
page: state.currentPage,
|
|
17
|
+
itemsPerPage: state.itemsPerPage,
|
|
18
|
+
sortColumn: state.sortColumn,
|
|
19
|
+
sortDirection: state.sortDirection,
|
|
20
|
+
searchTerm: state.searchTerm,
|
|
21
|
+
activeFilters: [...state.activeFilters],
|
|
22
|
+
groupByKey: state.groupByKey
|
|
23
|
+
}));
|
|
24
|
+
/**
|
|
25
|
+
* Serialized query string for change detection.
|
|
26
|
+
* Allows `$effect` to trigger only when the query actually changes.
|
|
27
|
+
*/
|
|
28
|
+
const queryKey = $derived(JSON.stringify(query));
|
|
29
|
+
/**
|
|
30
|
+
* Apply server result to the table state.
|
|
31
|
+
* Called by the component layer when `queryFn` resolves or when
|
|
32
|
+
* the developer provides new items in manual mode.
|
|
33
|
+
*/
|
|
34
|
+
function setServerResult(result) {
|
|
35
|
+
state.items = result.items;
|
|
36
|
+
state.serverTotalItems = result.totalItems;
|
|
37
|
+
state.loading = false;
|
|
38
|
+
state.error = null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Set server-side error state.
|
|
42
|
+
*/
|
|
43
|
+
function setServerError(error) {
|
|
44
|
+
state.loading = false;
|
|
45
|
+
state.error = error;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Set loading state (used before fetch).
|
|
49
|
+
*/
|
|
50
|
+
function setServerLoading() {
|
|
51
|
+
state.loading = true;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
get query() {
|
|
55
|
+
return query;
|
|
56
|
+
},
|
|
57
|
+
get queryKey() {
|
|
58
|
+
return queryKey;
|
|
59
|
+
},
|
|
60
|
+
setServerResult,
|
|
61
|
+
setServerError,
|
|
62
|
+
setServerLoading
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Search concern: manages search term and advanced search toggle.
|
|
3
|
+
*/
|
|
4
|
+
export function useSearch(state) {
|
|
5
|
+
function setSearchTerm(term) {
|
|
6
|
+
state.searchTerm = term;
|
|
7
|
+
state.currentPage = 1;
|
|
8
|
+
}
|
|
9
|
+
function toggleAdvancedSearch() {
|
|
10
|
+
state.showAdvancedSearch = !state.showAdvancedSearch;
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
setSearchTerm,
|
|
14
|
+
toggleAdvancedSearch
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { TableItem } from '../../types/tableTypes';
|
|
2
|
+
import type { TableState } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Selection concern: manages row selection state.
|
|
5
|
+
* Supports single-select and multi-select modes.
|
|
6
|
+
* @param state - Shared table state.
|
|
7
|
+
* @param getFilteredItems - Getter for filtered items (used for selectAll scope).
|
|
8
|
+
*/
|
|
9
|
+
export declare function useSelection(state: TableState, getFilteredItems: () => TableItem[]): {
|
|
10
|
+
readonly selectedItems: TableItem[];
|
|
11
|
+
readonly allSelected: boolean;
|
|
12
|
+
readonly someSelected: boolean;
|
|
13
|
+
selectItem: (id: string | number) => void;
|
|
14
|
+
deselectItem: (id: string | number) => void;
|
|
15
|
+
toggleItem: (id: string | number) => void;
|
|
16
|
+
selectAll: () => void;
|
|
17
|
+
deselectAll: () => void;
|
|
18
|
+
toggleAll: () => void;
|
|
19
|
+
isSelected: (id: string | number) => boolean;
|
|
20
|
+
setSelectedIds: (ids: Array<string | number>) => void;
|
|
21
|
+
};
|