@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,271 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { attachTableContext, createTableState } from '..';
|
|
4
|
+
import { useTableI18n } from '../i18n';
|
|
5
|
+
import { ColumnValidation } from '../factories/ColumnValidation';
|
|
6
|
+
import type { Column, TableItem, TableQuery, TableQueryResult } from '../types/tableTypes';
|
|
7
|
+
import type { SummaryConfig } from '../stores/TableStore.svelte';
|
|
8
|
+
|
|
9
|
+
const tt = useTableI18n();
|
|
10
|
+
|
|
11
|
+
export type TableProviderProps = {
|
|
12
|
+
items: TableItem[];
|
|
13
|
+
columns: Column[];
|
|
14
|
+
itemsPerPage?: number;
|
|
15
|
+
initialPage?: number;
|
|
16
|
+
groupOrder?: string[];
|
|
17
|
+
groupByKey?: string | null;
|
|
18
|
+
initialGroupBy?: string | null;
|
|
19
|
+
initialSummaryConfigs?: SummaryConfig[];
|
|
20
|
+
multiExpand?: boolean;
|
|
21
|
+
loading?: boolean;
|
|
22
|
+
children?: Snippet;
|
|
23
|
+
persistenceConfig?: {
|
|
24
|
+
tableId: string;
|
|
25
|
+
storage?: 'localStorage' | 'sessionStorage';
|
|
26
|
+
debounceMs?: number;
|
|
27
|
+
persistFilters?: boolean;
|
|
28
|
+
persistSearch?: boolean;
|
|
29
|
+
persistGroupByKey?: boolean;
|
|
30
|
+
persistSummaryConfigs?: boolean;
|
|
31
|
+
};
|
|
32
|
+
mode?: 'client' | 'server';
|
|
33
|
+
serverTotalItems?: number;
|
|
34
|
+
queryFn?: (query: TableQuery, options: { signal: AbortSignal }) => Promise<TableQueryResult>;
|
|
35
|
+
onQueryChange?: (query: TableQuery) => void;
|
|
36
|
+
queryDebounceMs?: number;
|
|
37
|
+
enableLiveUpdates?: boolean;
|
|
38
|
+
autoApplyOnNavigation?: boolean;
|
|
39
|
+
selectionMode?: 'none' | 'single' | 'multi';
|
|
40
|
+
selectedIds?: Array<string | number>;
|
|
41
|
+
onSelectionChange?: (selectedItems: TableItem[]) => void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let {
|
|
45
|
+
items = [],
|
|
46
|
+
columns = [],
|
|
47
|
+
itemsPerPage = 10,
|
|
48
|
+
initialPage = 1,
|
|
49
|
+
groupOrder = [],
|
|
50
|
+
groupByKey = null,
|
|
51
|
+
initialGroupBy = null,
|
|
52
|
+
initialSummaryConfigs = [],
|
|
53
|
+
multiExpand = false,
|
|
54
|
+
loading = false,
|
|
55
|
+
children,
|
|
56
|
+
persistenceConfig,
|
|
57
|
+
mode = 'client',
|
|
58
|
+
serverTotalItems = 0,
|
|
59
|
+
queryFn = undefined,
|
|
60
|
+
onQueryChange = undefined,
|
|
61
|
+
queryDebounceMs = 300,
|
|
62
|
+
enableLiveUpdates = false,
|
|
63
|
+
autoApplyOnNavigation = true,
|
|
64
|
+
selectionMode = 'none',
|
|
65
|
+
selectedIds = undefined,
|
|
66
|
+
onSelectionChange = undefined
|
|
67
|
+
}: TableProviderProps = $props();
|
|
68
|
+
|
|
69
|
+
const tableState = createTableState(persistenceConfig);
|
|
70
|
+
attachTableContext(tableState);
|
|
71
|
+
|
|
72
|
+
const {
|
|
73
|
+
state,
|
|
74
|
+
setItems,
|
|
75
|
+
setColumns,
|
|
76
|
+
setLoading,
|
|
77
|
+
setPage,
|
|
78
|
+
setItemsPerPage,
|
|
79
|
+
setGroupByKey,
|
|
80
|
+
setGroupOrder,
|
|
81
|
+
setError
|
|
82
|
+
} = tableState;
|
|
83
|
+
|
|
84
|
+
// ── Sync props → store ──
|
|
85
|
+
|
|
86
|
+
$effect(() => {
|
|
87
|
+
if (columns && columns.length > 0) {
|
|
88
|
+
setColumns(columns);
|
|
89
|
+
if (import.meta.env?.DEV) {
|
|
90
|
+
const result = ColumnValidation.validateColumns(columns);
|
|
91
|
+
if (!result.isValid) {
|
|
92
|
+
console.warn('[Table] Column validation:', result.errors);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (import.meta.env?.DEV && mode === 'server' && !queryFn && !onQueryChange) {
|
|
99
|
+
console.warn(
|
|
100
|
+
'[Table] mode="server" without queryFn or onQueryChange — the table has no way to fetch data.'
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
$effect(() => {
|
|
105
|
+
// In server mode, items come from queryFn or external; skip direct sync if queryFn manages it
|
|
106
|
+
if (items && items.length > 0 && !(mode === 'server' && queryFn)) {
|
|
107
|
+
setItems(items);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
$effect(() => {
|
|
112
|
+
if (groupOrder && groupOrder.length > 0) {
|
|
113
|
+
setGroupOrder(groupOrder);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
$effect(() => {
|
|
118
|
+
setItemsPerPage(itemsPerPage);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
$effect(() => {
|
|
122
|
+
setPage(initialPage);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
$effect(() => {
|
|
126
|
+
state.multiExpand = multiExpand;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
$effect(() => {
|
|
130
|
+
state.mode = mode;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
$effect(() => {
|
|
134
|
+
if (mode === 'server') {
|
|
135
|
+
state.serverTotalItems = serverTotalItems;
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
$effect(() => {
|
|
140
|
+
state.selectionMode = selectionMode;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
$effect(() => {
|
|
144
|
+
if (selectedIds) {
|
|
145
|
+
tableState.setSelectedIds(selectedIds);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
$effect(() => {
|
|
150
|
+
if (onSelectionChange && state.selectionMode !== 'none') {
|
|
151
|
+
const selected = tableState.selectedItems;
|
|
152
|
+
onSelectionChange(selected);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
$effect(() => {
|
|
157
|
+
setLoading(loading);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
$effect(() => {
|
|
161
|
+
if (groupByKey) {
|
|
162
|
+
setGroupByKey(groupByKey);
|
|
163
|
+
} else if (initialGroupBy && columns && columns.length > 0) {
|
|
164
|
+
setGroupByKey(initialGroupBy);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
$effect(() => {
|
|
169
|
+
if (
|
|
170
|
+
initialSummaryConfigs &&
|
|
171
|
+
initialSummaryConfigs.length > 0 &&
|
|
172
|
+
state.summaryConfigs.length === 0
|
|
173
|
+
) {
|
|
174
|
+
tableState.setSummaryConfigs(initialSummaryConfigs);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// ── Server mode: queryFn lifecycle ──
|
|
179
|
+
|
|
180
|
+
let abortController: AbortController | null = null;
|
|
181
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
182
|
+
let initialFetchDone = false;
|
|
183
|
+
|
|
184
|
+
$effect(() => {
|
|
185
|
+
if (mode !== 'server') return;
|
|
186
|
+
|
|
187
|
+
// Track the queryKey to detect changes
|
|
188
|
+
const currentQueryKey = tableState.queryKey;
|
|
189
|
+
|
|
190
|
+
// Debounce: clear previous timer
|
|
191
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
192
|
+
|
|
193
|
+
// For the initial fetch, don't debounce
|
|
194
|
+
const delay = initialFetchDone ? queryDebounceMs : 0;
|
|
195
|
+
|
|
196
|
+
debounceTimer = setTimeout(() => {
|
|
197
|
+
const currentQuery = tableState.query;
|
|
198
|
+
|
|
199
|
+
if (queryFn) {
|
|
200
|
+
executeManagedFetch(currentQuery);
|
|
201
|
+
} else if (onQueryChange) {
|
|
202
|
+
onQueryChange(currentQuery);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
initialFetchDone = true;
|
|
206
|
+
}, delay);
|
|
207
|
+
|
|
208
|
+
return () => {
|
|
209
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
async function executeManagedFetch(query: TableQuery) {
|
|
214
|
+
if (!queryFn) return;
|
|
215
|
+
|
|
216
|
+
// Cancel previous in-flight request
|
|
217
|
+
if (abortController) {
|
|
218
|
+
abortController.abort();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
abortController = new AbortController();
|
|
222
|
+
const { signal } = abortController;
|
|
223
|
+
|
|
224
|
+
tableState.setServerLoading();
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const result = await queryFn(query, { signal });
|
|
228
|
+
|
|
229
|
+
// Ignore result if this request was aborted (superseded by a newer one)
|
|
230
|
+
if (signal.aborted) return;
|
|
231
|
+
|
|
232
|
+
tableState.setServerResult(result);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
// Don't treat abort as an error
|
|
235
|
+
if (e instanceof DOMException && e.name === 'AbortError') return;
|
|
236
|
+
if (signal.aborted) return;
|
|
237
|
+
|
|
238
|
+
const message = e instanceof Error ? e.message : tt('error.fetchFailed');
|
|
239
|
+
tableState.setServerError(message);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Live updates: auto-apply on navigation ──
|
|
244
|
+
$effect(() => {
|
|
245
|
+
if (!enableLiveUpdates || !autoApplyOnNavigation) return;
|
|
246
|
+
if (!tableState.hasPendingUpdates) return;
|
|
247
|
+
|
|
248
|
+
// Track navigation state changes
|
|
249
|
+
void state.currentPage;
|
|
250
|
+
void state.sortColumn;
|
|
251
|
+
void state.sortDirection;
|
|
252
|
+
void state.searchTerm;
|
|
253
|
+
void state.activeFilters;
|
|
254
|
+
void state.groupByKey;
|
|
255
|
+
|
|
256
|
+
// Auto-apply pending changes when the user navigates (view is already changing)
|
|
257
|
+
tableState.applyAllUpdates();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Cleanup on component destroy
|
|
261
|
+
$effect(() => {
|
|
262
|
+
return () => {
|
|
263
|
+
if (abortController) abortController.abort();
|
|
264
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
</script>
|
|
268
|
+
|
|
269
|
+
{#if children}
|
|
270
|
+
{@render children()}
|
|
271
|
+
{/if}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { Column, TableItem, TableQuery, TableQueryResult } from '../types/tableTypes';
|
|
3
|
+
import type { SummaryConfig } from '../stores/TableStore.svelte';
|
|
4
|
+
export type TableProviderProps = {
|
|
5
|
+
items: TableItem[];
|
|
6
|
+
columns: Column[];
|
|
7
|
+
itemsPerPage?: number;
|
|
8
|
+
initialPage?: number;
|
|
9
|
+
groupOrder?: string[];
|
|
10
|
+
groupByKey?: string | null;
|
|
11
|
+
initialGroupBy?: string | null;
|
|
12
|
+
initialSummaryConfigs?: SummaryConfig[];
|
|
13
|
+
multiExpand?: boolean;
|
|
14
|
+
loading?: boolean;
|
|
15
|
+
children?: Snippet;
|
|
16
|
+
persistenceConfig?: {
|
|
17
|
+
tableId: string;
|
|
18
|
+
storage?: 'localStorage' | 'sessionStorage';
|
|
19
|
+
debounceMs?: number;
|
|
20
|
+
persistFilters?: boolean;
|
|
21
|
+
persistSearch?: boolean;
|
|
22
|
+
persistGroupByKey?: boolean;
|
|
23
|
+
persistSummaryConfigs?: boolean;
|
|
24
|
+
};
|
|
25
|
+
mode?: 'client' | 'server';
|
|
26
|
+
serverTotalItems?: number;
|
|
27
|
+
queryFn?: (query: TableQuery, options: {
|
|
28
|
+
signal: AbortSignal;
|
|
29
|
+
}) => Promise<TableQueryResult>;
|
|
30
|
+
onQueryChange?: (query: TableQuery) => void;
|
|
31
|
+
queryDebounceMs?: number;
|
|
32
|
+
enableLiveUpdates?: boolean;
|
|
33
|
+
autoApplyOnNavigation?: boolean;
|
|
34
|
+
selectionMode?: 'none' | 'single' | 'multi';
|
|
35
|
+
selectedIds?: Array<string | number>;
|
|
36
|
+
onSelectionChange?: (selectedItems: TableItem[]) => void;
|
|
37
|
+
};
|
|
38
|
+
declare const TableProvider: import("svelte").Component<TableProviderProps, {}, "">;
|
|
39
|
+
type TableProvider = ReturnType<typeof TableProvider>;
|
|
40
|
+
export default TableProvider;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
resolveIcon,
|
|
4
|
+
Checkbox,
|
|
5
|
+
ChevronDownIcon as ChevronDownIconDefault
|
|
6
|
+
} from '@urbicon-ui/blocks';
|
|
7
|
+
import { slide } from 'svelte/transition';
|
|
8
|
+
|
|
9
|
+
const ChevronDownIcon = resolveIcon('chevronDown', ChevronDownIconDefault);
|
|
10
|
+
import { getTableContext } from '../stores/TableStore.svelte';
|
|
11
|
+
import { useTableI18n } from '../i18n';
|
|
12
|
+
import { tableRowVariants } from '../variants';
|
|
13
|
+
import { getTableStyleConfig, resolveSlotClass } from './table-style-context';
|
|
14
|
+
import TableCell from './TableCell.svelte';
|
|
15
|
+
import { resolveColumnId } from '../utils';
|
|
16
|
+
import type { Column, TableItem } from '../types/tableTypes';
|
|
17
|
+
import type { Snippet } from 'svelte';
|
|
18
|
+
|
|
19
|
+
const tt = useTableI18n();
|
|
20
|
+
|
|
21
|
+
const tableContext = getTableContext();
|
|
22
|
+
const { state: tableState, toggleExpand, isItemExpanded } = tableContext;
|
|
23
|
+
const styleConfig = getTableStyleConfig();
|
|
24
|
+
|
|
25
|
+
let selectable = $derived(tableState.selectionMode !== 'none');
|
|
26
|
+
|
|
27
|
+
let {
|
|
28
|
+
item,
|
|
29
|
+
expandable = false,
|
|
30
|
+
virtualized = false,
|
|
31
|
+
virtualIndex = 0,
|
|
32
|
+
virtualItemHeight = 48,
|
|
33
|
+
rowIndex = 0,
|
|
34
|
+
expandedRowContent = undefined as Snippet<[item: TableItem]> | undefined,
|
|
35
|
+
cell = undefined as Snippet<[item: TableItem, value: unknown, column: Column]> | undefined,
|
|
36
|
+
onRowClick = undefined as ((item: TableItem) => void) | undefined,
|
|
37
|
+
size = 'md' as const
|
|
38
|
+
} = $props();
|
|
39
|
+
|
|
40
|
+
let isFocused = $derived(tableContext.isFocusedRow(rowIndex));
|
|
41
|
+
let interactive = $derived(selectable || expandable || !!onRowClick);
|
|
42
|
+
|
|
43
|
+
const itemId = $derived.by((): string | number => {
|
|
44
|
+
const candidate = item.id ?? item.__index;
|
|
45
|
+
return typeof candidate === 'string' || typeof candidate === 'number' ? candidate : -1;
|
|
46
|
+
});
|
|
47
|
+
let isExpanded = $derived(isItemExpanded(itemId));
|
|
48
|
+
|
|
49
|
+
let isRowSelected = $derived(selectable && tableContext.isSelected(itemId));
|
|
50
|
+
let isRecentlyUpdated = $derived(tableContext.isRecentlyUpdated(itemId));
|
|
51
|
+
|
|
52
|
+
const totalColumnsCount = $derived.by(() => {
|
|
53
|
+
let count = tableState.columns.length;
|
|
54
|
+
if (tableState.groupByKey) count += 1;
|
|
55
|
+
if (expandable) count += 1;
|
|
56
|
+
if (selectable) count += 1;
|
|
57
|
+
return count;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
function handleRowClick() {
|
|
61
|
+
if (onRowClick) {
|
|
62
|
+
onRowClick(item);
|
|
63
|
+
}
|
|
64
|
+
if (expandable) {
|
|
65
|
+
toggleExpand(itemId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleCheckboxClick(e: MouseEvent) {
|
|
70
|
+
e.stopPropagation();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function handleChevronClick(e: MouseEvent) {
|
|
74
|
+
e.stopPropagation();
|
|
75
|
+
if (expandable) {
|
|
76
|
+
toggleExpand(itemId);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rowStyles = $derived(
|
|
81
|
+
tableRowVariants({
|
|
82
|
+
state: isRowSelected ? 'selected' : isExpanded ? 'expanded' : 'default',
|
|
83
|
+
size
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
</script>
|
|
87
|
+
|
|
88
|
+
<!-- Main row -->
|
|
89
|
+
<tr
|
|
90
|
+
id={String(itemId)}
|
|
91
|
+
onclick={handleRowClick}
|
|
92
|
+
class={resolveSlotClass(
|
|
93
|
+
rowStyles.row(),
|
|
94
|
+
styleConfig.slotClasses.row,
|
|
95
|
+
styleConfig.unstyled,
|
|
96
|
+
[
|
|
97
|
+
expandable || onRowClick ? 'cursor-pointer select-none' : '',
|
|
98
|
+
isRecentlyUpdated
|
|
99
|
+
? 'ring-success/30 bg-success-subtle/30 ring-2 transition-[box-shadow,background-color] duration-1000 ring-inset'
|
|
100
|
+
: ''
|
|
101
|
+
]
|
|
102
|
+
.filter(Boolean)
|
|
103
|
+
.join(' ')
|
|
104
|
+
)}
|
|
105
|
+
style={virtualized
|
|
106
|
+
? `position: absolute; transform: translateY(${virtualIndex * virtualItemHeight}px);`
|
|
107
|
+
: ''}
|
|
108
|
+
tabindex={interactive ? (isFocused ? 0 : -1) : undefined}
|
|
109
|
+
aria-rowindex={rowIndex + 1}
|
|
110
|
+
aria-expanded={expandable ? isExpanded : undefined}
|
|
111
|
+
aria-selected={selectable ? isRowSelected : undefined}
|
|
112
|
+
data-row-index={rowIndex}
|
|
113
|
+
data-testid={`table-row-${itemId}`}
|
|
114
|
+
>
|
|
115
|
+
{#if selectable}
|
|
116
|
+
<td class="{rowStyles.cell()} w-12" onclick={handleCheckboxClick}>
|
|
117
|
+
<div class="flex h-full w-full items-center justify-center">
|
|
118
|
+
<Checkbox
|
|
119
|
+
checked={isRowSelected}
|
|
120
|
+
onchange={() => tableContext.toggleItem(itemId)}
|
|
121
|
+
aria-label={isRowSelected ? tt('selection.deselectRow') : tt('selection.selectRow')}
|
|
122
|
+
size="sm"
|
|
123
|
+
data-testid={`selection-checkbox-${itemId}`}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
</td>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
{#if expandable}
|
|
130
|
+
<td class="{rowStyles.cell()} w-10">
|
|
131
|
+
<div class="flex h-full w-full items-center justify-center px-2 py-2">
|
|
132
|
+
<button
|
|
133
|
+
class="table-expand-button rounded-modify flex h-6 w-6 items-center justify-center transition-transform duration-(--blocks-duration-fast) {isExpanded
|
|
134
|
+
? 'rotate-180'
|
|
135
|
+
: ''}"
|
|
136
|
+
onclick={handleChevronClick}
|
|
137
|
+
aria-label={isExpanded ? tt('actions.hideDetails') : tt('actions.showDetails')}
|
|
138
|
+
data-testid={`expand-button-${itemId}`}
|
|
139
|
+
>
|
|
140
|
+
<ChevronDownIcon class="h-4 w-4" />
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
</td>
|
|
144
|
+
{/if}
|
|
145
|
+
|
|
146
|
+
{#each tableContext.orderedColumns as column, colIdx (resolveColumnId(column))}
|
|
147
|
+
<TableCell
|
|
148
|
+
{item}
|
|
149
|
+
{column}
|
|
150
|
+
{cell}
|
|
151
|
+
{size}
|
|
152
|
+
colIndex={colIdx}
|
|
153
|
+
cellClass={resolveSlotClass(
|
|
154
|
+
rowStyles.cell(),
|
|
155
|
+
styleConfig.slotClasses.cell,
|
|
156
|
+
styleConfig.unstyled
|
|
157
|
+
)}
|
|
158
|
+
testIdPrefix="cell"
|
|
159
|
+
/>
|
|
160
|
+
{/each}
|
|
161
|
+
</tr>
|
|
162
|
+
|
|
163
|
+
{#if isExpanded && expandedRowContent}
|
|
164
|
+
<tr data-testid={`expanded-row-${itemId}`} class="border-b-0">
|
|
165
|
+
<td colspan={totalColumnsCount} class="p-0">
|
|
166
|
+
<div class="bg-surface-elevated/50 px-6 py-4" transition:slide={{ duration: 150 }}>
|
|
167
|
+
{@render expandedRowContent(item)}
|
|
168
|
+
</div>
|
|
169
|
+
</td>
|
|
170
|
+
</tr>
|
|
171
|
+
{/if}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Column, TableItem } from '../types/tableTypes';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
declare const TableRow: import("svelte").Component<{
|
|
4
|
+
item: any;
|
|
5
|
+
expandable?: boolean;
|
|
6
|
+
virtualized?: boolean;
|
|
7
|
+
virtualIndex?: number;
|
|
8
|
+
virtualItemHeight?: number;
|
|
9
|
+
rowIndex?: number;
|
|
10
|
+
expandedRowContent?: Snippet<[item: TableItem]> | undefined;
|
|
11
|
+
cell?: Snippet<[item: TableItem, value: unknown, column: Column]> | undefined;
|
|
12
|
+
onRowClick?: ((item: TableItem) => void) | undefined;
|
|
13
|
+
size?: const;
|
|
14
|
+
}, {}, "">;
|
|
15
|
+
type TableRow = ReturnType<typeof TableRow>;
|
|
16
|
+
export default TableRow;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { EmptyStateProps } from './EmptyState.svelte';
|
|
2
|
+
export { default as EmptyState } from './EmptyState.svelte';
|
|
3
|
+
export type { ErrorStateProps } from './ErrorState.svelte';
|
|
4
|
+
export { default as ErrorState } from './ErrorState.svelte';
|
|
5
|
+
export type { GroupedRowProps } from './GroupedRow.svelte';
|
|
6
|
+
export { default as GroupedRow } from './GroupedRow.svelte';
|
|
7
|
+
export type { LoadingStateProps } from './LoadingState.svelte';
|
|
8
|
+
export { default as LoadingState } from './LoadingState.svelte';
|
|
9
|
+
export type { MobileCardProps } from './MobileCard.svelte';
|
|
10
|
+
export { default as MobileCard } from './MobileCard.svelte';
|
|
11
|
+
export type { TableCellProps } from './TableCell.svelte';
|
|
12
|
+
export { default as TableCell } from './TableCell.svelte';
|
|
13
|
+
export { default as TableHead } from './TableHead.svelte';
|
|
14
|
+
export type { TableProviderProps } from './TableProvider.svelte';
|
|
15
|
+
export { default as TableProvider } from './TableProvider.svelte';
|
|
16
|
+
export { default as TableRow } from './TableRow.svelte';
|
|
17
|
+
export { default as Table } from './table/Table.svelte';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Core table components
|
|
2
|
+
// State components
|
|
3
|
+
export { default as EmptyState } from './EmptyState.svelte';
|
|
4
|
+
export { default as ErrorState } from './ErrorState.svelte';
|
|
5
|
+
// Advanced components
|
|
6
|
+
export { default as GroupedRow } from './GroupedRow.svelte';
|
|
7
|
+
export { default as LoadingState } from './LoadingState.svelte';
|
|
8
|
+
// Mobile components
|
|
9
|
+
export { default as MobileCard } from './MobileCard.svelte';
|
|
10
|
+
export { default as TableCell } from './TableCell.svelte';
|
|
11
|
+
export { default as TableHead } from './TableHead.svelte';
|
|
12
|
+
export { default as TableProvider } from './TableProvider.svelte';
|
|
13
|
+
export { default as TableRow } from './TableRow.svelte';
|
|
14
|
+
export { default as Table } from './table/Table.svelte';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type StickyProp = boolean | 'toolbar' | 'header' | 'both';
|
|
2
|
+
export interface StickyMode {
|
|
3
|
+
/** Sticky toolbar (L1) enabled */
|
|
4
|
+
toolbar: boolean;
|
|
5
|
+
/** Sticky thead (L2) enabled */
|
|
6
|
+
header: boolean;
|
|
7
|
+
/** Sticky group-header (L3) enabled */
|
|
8
|
+
group: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the `sticky` prop to a per-layer mode.
|
|
12
|
+
*
|
|
13
|
+
* | Prop value | toolbar | header | group |
|
|
14
|
+
* |-----------------|---------|--------|-------|
|
|
15
|
+
* | `false` (def.) | ❌ | ❌ | ❌ |
|
|
16
|
+
* | `true` / `'both'` | ✅ | ✅ | ✅ |
|
|
17
|
+
* | `'toolbar'` | ✅ | ❌ | ❌ |
|
|
18
|
+
* | `'header'` | ❌ | ✅ | ✅ |
|
|
19
|
+
*
|
|
20
|
+
* Note: `'header'` enables group-header pinning too, because the group-header
|
|
21
|
+
* is part of the "header" semantic (the contextual section marker).
|
|
22
|
+
*
|
|
23
|
+
* When `contained` is set (`fit="viewport"`), the table is its own scroll box:
|
|
24
|
+
* the header + group header always pin to the top of the box and the toolbar is
|
|
25
|
+
* a static flex sibling (never page-pinned), so `contained` supersedes the
|
|
26
|
+
* `sticky` prop entirely.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveStickyMode(sticky: StickyProp | undefined, contained?: boolean): StickyMode;
|
|
29
|
+
export interface StickyContext {
|
|
30
|
+
readonly mode: StickyMode;
|
|
31
|
+
/** Whether the toolbar is currently in its pinned state (sentinel out of view) */
|
|
32
|
+
readonly toolbarStuck: boolean;
|
|
33
|
+
/** Whether the thead is currently in its pinned state */
|
|
34
|
+
readonly headerStuck: boolean;
|
|
35
|
+
setToolbarStuck(value: boolean): void;
|
|
36
|
+
setHeaderStuck(value: boolean): void;
|
|
37
|
+
}
|
|
38
|
+
declare const setStickyContext: any;
|
|
39
|
+
export { setStickyContext };
|
|
40
|
+
export declare function getStickyContext(): StickyContext;
|
|
41
|
+
/**
|
|
42
|
+
* Reactive state holder for the sticky context. Backed by `$state` so child
|
|
43
|
+
* components see live updates of `toolbarStuck` / `headerStuck`.
|
|
44
|
+
*
|
|
45
|
+
* `getMode` is a getter so that switching the `sticky` prop at runtime
|
|
46
|
+
* propagates to all sub-components without re-creating the context.
|
|
47
|
+
*/
|
|
48
|
+
export declare function createStickyState(getMode: () => StickyMode): StickyContext;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STICKY-PINNING CONTEXT
|
|
3
|
+
*
|
|
4
|
+
* Shared state for the three Table sticky layers (toolbar, thead, group-header).
|
|
5
|
+
* - Sets the resolved `StickyMode` derived from the `sticky` prop.
|
|
6
|
+
* - Tracks `toolbarStuck` / `headerStuck` so child components can mirror the
|
|
7
|
+
* "stuck" data-attribute for the box-shadow + border accent.
|
|
8
|
+
*
|
|
9
|
+
* Background: docs/STICKY-PINNING.md
|
|
10
|
+
*/
|
|
11
|
+
import { createOptionalContext } from '@urbicon-ui/blocks';
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the `sticky` prop to a per-layer mode.
|
|
14
|
+
*
|
|
15
|
+
* | Prop value | toolbar | header | group |
|
|
16
|
+
* |-----------------|---------|--------|-------|
|
|
17
|
+
* | `false` (def.) | ❌ | ❌ | ❌ |
|
|
18
|
+
* | `true` / `'both'` | ✅ | ✅ | ✅ |
|
|
19
|
+
* | `'toolbar'` | ✅ | ❌ | ❌ |
|
|
20
|
+
* | `'header'` | ❌ | ✅ | ✅ |
|
|
21
|
+
*
|
|
22
|
+
* Note: `'header'` enables group-header pinning too, because the group-header
|
|
23
|
+
* is part of the "header" semantic (the contextual section marker).
|
|
24
|
+
*
|
|
25
|
+
* When `contained` is set (`fit="viewport"`), the table is its own scroll box:
|
|
26
|
+
* the header + group header always pin to the top of the box and the toolbar is
|
|
27
|
+
* a static flex sibling (never page-pinned), so `contained` supersedes the
|
|
28
|
+
* `sticky` prop entirely.
|
|
29
|
+
*/
|
|
30
|
+
export function resolveStickyMode(sticky, contained = false) {
|
|
31
|
+
if (contained) {
|
|
32
|
+
return { toolbar: false, header: true, group: true };
|
|
33
|
+
}
|
|
34
|
+
if (sticky === true || sticky === 'both') {
|
|
35
|
+
return { toolbar: true, header: true, group: true };
|
|
36
|
+
}
|
|
37
|
+
if (sticky === 'toolbar') {
|
|
38
|
+
return { toolbar: true, header: false, group: false };
|
|
39
|
+
}
|
|
40
|
+
if (sticky === 'header') {
|
|
41
|
+
return { toolbar: false, header: true, group: true };
|
|
42
|
+
}
|
|
43
|
+
return { toolbar: false, header: false, group: false };
|
|
44
|
+
}
|
|
45
|
+
const [getStickyContextRaw, setStickyContext] = createOptionalContext();
|
|
46
|
+
export { setStickyContext };
|
|
47
|
+
/**
|
|
48
|
+
* Off-tree default (used by sub-components rendered outside a `<Table>` wrapper
|
|
49
|
+
* — e.g. when consumers compose `<TableHead>` directly).
|
|
50
|
+
*/
|
|
51
|
+
const OFF = {
|
|
52
|
+
mode: { toolbar: false, header: false, group: false },
|
|
53
|
+
toolbarStuck: false,
|
|
54
|
+
headerStuck: false,
|
|
55
|
+
setToolbarStuck: () => { },
|
|
56
|
+
setHeaderStuck: () => { }
|
|
57
|
+
};
|
|
58
|
+
export function getStickyContext() {
|
|
59
|
+
return getStickyContextRaw() ?? OFF;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reactive state holder for the sticky context. Backed by `$state` so child
|
|
63
|
+
* components see live updates of `toolbarStuck` / `headerStuck`.
|
|
64
|
+
*
|
|
65
|
+
* `getMode` is a getter so that switching the `sticky` prop at runtime
|
|
66
|
+
* propagates to all sub-components without re-creating the context.
|
|
67
|
+
*/
|
|
68
|
+
export function createStickyState(getMode) {
|
|
69
|
+
let toolbarStuck = $state(false);
|
|
70
|
+
let headerStuck = $state(false);
|
|
71
|
+
return {
|
|
72
|
+
get mode() {
|
|
73
|
+
return getMode();
|
|
74
|
+
},
|
|
75
|
+
get toolbarStuck() {
|
|
76
|
+
return toolbarStuck;
|
|
77
|
+
},
|
|
78
|
+
get headerStuck() {
|
|
79
|
+
return headerStuck;
|
|
80
|
+
},
|
|
81
|
+
setToolbarStuck(value) {
|
|
82
|
+
toolbarStuck = value;
|
|
83
|
+
},
|
|
84
|
+
setHeaderStuck(value) {
|
|
85
|
+
headerStuck = value;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|