@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,330 @@
|
|
|
1
|
+
import { createPersistentState } from '@urbicon-ui/blocks';
|
|
2
|
+
/**
|
|
3
|
+
* Retrieves a nested value from an object using dot notation
|
|
4
|
+
* e.g. getNestedValue(user, 'address.city')
|
|
5
|
+
*/
|
|
6
|
+
export function getNestedValue(item, key) {
|
|
7
|
+
if (!item || !key)
|
|
8
|
+
return undefined;
|
|
9
|
+
const keys = key.split('.');
|
|
10
|
+
let value = item;
|
|
11
|
+
for (const k of keys) {
|
|
12
|
+
if (value === undefined || value === null)
|
|
13
|
+
return undefined;
|
|
14
|
+
value = value[k];
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Resolves the stable identifier of a column. Mirrors the discriminated
|
|
20
|
+
* union shape from `tableTypes.ts`:
|
|
21
|
+
*
|
|
22
|
+
* - string accessor → defaults to `accessor` if `id` is omitted
|
|
23
|
+
* - function accessor → `id` is required
|
|
24
|
+
* - synthetic column → `id` is required
|
|
25
|
+
*
|
|
26
|
+
* Throws when neither `id` nor a string `accessor` is present — this
|
|
27
|
+
* combination can only occur when a caller bypasses the type system, and
|
|
28
|
+
* returning a sentinel (e.g. empty string) would silently corrupt the
|
|
29
|
+
* filter/sort/group/visibility state that targets columns by id.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveColumnId(column) {
|
|
32
|
+
if (column.id !== undefined && column.id !== '')
|
|
33
|
+
return column.id;
|
|
34
|
+
// DataColumnString fallback: id defaults to the accessor string.
|
|
35
|
+
if (typeof column.accessor === 'string' && column.accessor !== '')
|
|
36
|
+
return column.accessor;
|
|
37
|
+
throw new Error('Column has no resolvable id. Provide `id` for function-accessor or synthetic columns, or a non-empty string `accessor`.');
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the human-readable label of a column for use in table chrome
|
|
41
|
+
* (column-visibility menu, header menu, filter/grouping/summary menus).
|
|
42
|
+
*
|
|
43
|
+
* Resolution order: `menuTitle` → `title` → humanized column id. The last
|
|
44
|
+
* step guarantees a non-empty label even for icon-only columns
|
|
45
|
+
* (`{ id: 'actions', title: '' }` → "Actions") so menu entries never render
|
|
46
|
+
* blank. Header cells render `title` verbatim on purpose — an empty header
|
|
47
|
+
* is a deliberate design choice, an empty menu entry is not.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveColumnLabel(column) {
|
|
50
|
+
return column.menuTitle || column.title || humanizeColumnId(resolveColumnId(column));
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Turns a column id into a readable Title-Case label: splits camelCase,
|
|
54
|
+
* kebab-case, snake_case and dot-paths into words and capitalizes each
|
|
55
|
+
* (`quickActions` / `quick-actions` / `quick_actions` → "Quick Actions").
|
|
56
|
+
*/
|
|
57
|
+
function humanizeColumnId(id) {
|
|
58
|
+
return id
|
|
59
|
+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
60
|
+
.split(/[-_.\s]+/)
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
63
|
+
.join(' ');
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolves the canonical value of a cell — the single source of truth that
|
|
67
|
+
* search, sort, group, summary and the default cell renderer all read from.
|
|
68
|
+
* Display via `cell`/`component`/`formatter` is decoupled and may transform
|
|
69
|
+
* this value further for rendering, but the derived operations always
|
|
70
|
+
* operate on the accessor's output.
|
|
71
|
+
*
|
|
72
|
+
* - string accessor → property lookup (dot-paths supported via `getNestedValue`)
|
|
73
|
+
* - function accessor → invoke with the item
|
|
74
|
+
* - synthetic column (no accessor) → `undefined`
|
|
75
|
+
*/
|
|
76
|
+
export function resolveColumnValue(column, item) {
|
|
77
|
+
if (typeof column.accessor === 'function') {
|
|
78
|
+
return column.accessor(item);
|
|
79
|
+
}
|
|
80
|
+
if (typeof column.accessor === 'string') {
|
|
81
|
+
return getNestedValue(item, column.accessor);
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Looks up a column from the table's column list by its resolved id.
|
|
87
|
+
* Returns `undefined` when no column matches — used by concerns that
|
|
88
|
+
* keep only the id in their state (filters, sort, group, summary).
|
|
89
|
+
*/
|
|
90
|
+
export function findColumnById(columns, id) {
|
|
91
|
+
return columns.find((col) => resolveColumnId(col) === id);
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolves a column's value by its id, falling back to a raw nested-key
|
|
95
|
+
* lookup when no matching column is registered. The fallback preserves the
|
|
96
|
+
* pre-2.0 behaviour for transient state (e.g. persisted filters that
|
|
97
|
+
* reference a column which has since been removed from the definition).
|
|
98
|
+
*/
|
|
99
|
+
export function resolveValueById(columns, item, id) {
|
|
100
|
+
const column = findColumnById(columns, id);
|
|
101
|
+
if (column)
|
|
102
|
+
return resolveColumnValue(column, item);
|
|
103
|
+
return getNestedValue(item, id);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Formats a cell value based on the column definition. The displayed value
|
|
107
|
+
* starts from the accessor's output — `formatter` then has a chance to
|
|
108
|
+
* transform it into a presentation string.
|
|
109
|
+
*/
|
|
110
|
+
export function formatCellValue(item, column) {
|
|
111
|
+
const value = resolveColumnValue(column, item);
|
|
112
|
+
// Use custom formatter when available
|
|
113
|
+
if (column.formatter) {
|
|
114
|
+
// The discriminated union narrows `formatter`'s value parameter to
|
|
115
|
+
// either `unknown` (string accessor / synthetic) or the function's
|
|
116
|
+
// return type V. Cast through `never` to bridge that to a callable
|
|
117
|
+
// signature without losing the runtime narrowing above.
|
|
118
|
+
const formatted = column.formatter(value, item);
|
|
119
|
+
if (formatted !== null)
|
|
120
|
+
return formatted;
|
|
121
|
+
}
|
|
122
|
+
// Default formatting based on data type
|
|
123
|
+
if (value === undefined || value === null)
|
|
124
|
+
return '';
|
|
125
|
+
if (typeof value === 'boolean') {
|
|
126
|
+
return value ? 'Yes' : 'No';
|
|
127
|
+
}
|
|
128
|
+
if (value instanceof Date) {
|
|
129
|
+
if (Number.isNaN(value.getTime()))
|
|
130
|
+
return 'Invalid Date';
|
|
131
|
+
return value.toLocaleDateString();
|
|
132
|
+
}
|
|
133
|
+
return String(value);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Groups items by a column-id, routing the lookup through {@link resolveValueById}
|
|
137
|
+
* so that function-accessor columns aggregate the computed value (not the
|
|
138
|
+
* non-existent property of the same name). Returns `{ ungrouped: items }`
|
|
139
|
+
* when no group id is provided.
|
|
140
|
+
*/
|
|
141
|
+
export function groupItems(items, columns, groupByKey) {
|
|
142
|
+
if (!groupByKey)
|
|
143
|
+
return { ungrouped: items };
|
|
144
|
+
const grouped = {};
|
|
145
|
+
for (const item of items) {
|
|
146
|
+
const groupValue = resolveValueById(columns, item, groupByKey);
|
|
147
|
+
const groupKey = groupValue !== undefined && groupValue !== null ? String(groupValue) : 'Unassigned';
|
|
148
|
+
if (!grouped[groupKey]) {
|
|
149
|
+
grouped[groupKey] = [];
|
|
150
|
+
}
|
|
151
|
+
grouped[groupKey].push(item);
|
|
152
|
+
}
|
|
153
|
+
return grouped;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Normalizes items for table consumption. Items without an `id` property
|
|
157
|
+
* get an `__index` assigned as stable fallback key.
|
|
158
|
+
*/
|
|
159
|
+
export function normalizeItems(items) {
|
|
160
|
+
return items.map((item, i) => (item.id !== undefined ? item : { ...item, __index: i }));
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Resolves the ID of an item
|
|
164
|
+
*/
|
|
165
|
+
export function getItemId(item) {
|
|
166
|
+
if (!item)
|
|
167
|
+
return -1;
|
|
168
|
+
if (item.id !== undefined)
|
|
169
|
+
return item.id;
|
|
170
|
+
if (item.ID !== undefined)
|
|
171
|
+
return item.ID;
|
|
172
|
+
if (item._id !== undefined)
|
|
173
|
+
return item._id;
|
|
174
|
+
return -1;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Computes aggregate summary values for a set of items given summary configs.
|
|
178
|
+
* Extracted from the store for testability.
|
|
179
|
+
*
|
|
180
|
+
* The optional `getValue` resolver lets callers supply a column-aware lookup
|
|
181
|
+
* (typically `(item, id) => resolveValueById(state.columns, item, id)`) so
|
|
182
|
+
* that summary aggregations honour function accessors and computed values.
|
|
183
|
+
* When omitted, the legacy `getNestedValue` fallback is used — sufficient
|
|
184
|
+
* for primitive property keys and required by the standalone unit tests.
|
|
185
|
+
*/
|
|
186
|
+
export function calculateSummary(items, configs, getValue = (item, columnId) => getNestedValue(item, columnId)) {
|
|
187
|
+
const result = {};
|
|
188
|
+
configs.forEach((config) => {
|
|
189
|
+
const values = items
|
|
190
|
+
.map((item) => getValue(item, config.column))
|
|
191
|
+
.filter((value) => {
|
|
192
|
+
if (value === undefined || value === null || value === '')
|
|
193
|
+
return false;
|
|
194
|
+
if (['sum', 'avg', 'min', 'max'].includes(config.type)) {
|
|
195
|
+
return !Number.isNaN(Number(value));
|
|
196
|
+
}
|
|
197
|
+
return true;
|
|
198
|
+
});
|
|
199
|
+
if (config.type === 'count') {
|
|
200
|
+
result[config.column] = values.length;
|
|
201
|
+
}
|
|
202
|
+
else if (values.length === 0) {
|
|
203
|
+
result[config.column] = NaN;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
switch (config.type) {
|
|
207
|
+
case 'sum':
|
|
208
|
+
result[config.column] = values.reduce((s, val) => s + Number(val ?? 0), 0);
|
|
209
|
+
break;
|
|
210
|
+
case 'avg': {
|
|
211
|
+
const total = values.reduce((s, val) => s + Number(val ?? 0), 0);
|
|
212
|
+
result[config.column] = total / values.length;
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
case 'min':
|
|
216
|
+
result[config.column] = Math.min(...values.map((v) => Number(v)));
|
|
217
|
+
break;
|
|
218
|
+
case 'max':
|
|
219
|
+
result[config.column] = Math.max(...values.map((v) => Number(v)));
|
|
220
|
+
break;
|
|
221
|
+
default:
|
|
222
|
+
result[config.column] = NaN;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Tests whether a single item value matches a filter condition.
|
|
230
|
+
*/
|
|
231
|
+
export function matchesFilter(itemValue, filterValue, operator) {
|
|
232
|
+
const value = itemValue.toLowerCase();
|
|
233
|
+
const filter = filterValue.toLowerCase();
|
|
234
|
+
switch (operator) {
|
|
235
|
+
case 'contains':
|
|
236
|
+
return value.includes(filter);
|
|
237
|
+
case 'equals':
|
|
238
|
+
return value === filter;
|
|
239
|
+
case 'startsWith':
|
|
240
|
+
return value.startsWith(filter);
|
|
241
|
+
case 'endsWith':
|
|
242
|
+
return value.endsWith(filter);
|
|
243
|
+
case 'greaterThan':
|
|
244
|
+
return Number(value) > Number(filter);
|
|
245
|
+
case 'lessThan':
|
|
246
|
+
return Number(value) < Number(filter);
|
|
247
|
+
default:
|
|
248
|
+
if (import.meta.env?.DEV)
|
|
249
|
+
console.warn(`[Table] Unknown filter operator "${operator}" — row excluded.`);
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Splits text into segments based on a search term, marking matching parts as highlighted.
|
|
255
|
+
* Used by SearchHighlight to render matches without {@html} (XSS-safe).
|
|
256
|
+
*/
|
|
257
|
+
export function splitSearchSegments(text, searchTerm) {
|
|
258
|
+
if (!searchTerm || !text)
|
|
259
|
+
return [{ text, highlighted: false }];
|
|
260
|
+
const escaped = searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
261
|
+
const regex = new RegExp(`(${escaped})`, 'gi');
|
|
262
|
+
const parts = text.split(regex);
|
|
263
|
+
return parts
|
|
264
|
+
.filter((part) => part !== '')
|
|
265
|
+
.map((part) => ({
|
|
266
|
+
text: part,
|
|
267
|
+
highlighted: part.toLowerCase() === searchTerm.toLowerCase()
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
export function createPersistentFilters(config) {
|
|
271
|
+
return createPersistentState({
|
|
272
|
+
key: `table_filters_${config.tableId}`,
|
|
273
|
+
defaultValue: [],
|
|
274
|
+
storage: config.storage || 'localStorage',
|
|
275
|
+
debounceMs: config.debounceMs || 500
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Specialized hook for search term persistence
|
|
280
|
+
*/
|
|
281
|
+
export function createPersistentSearchTerm(config) {
|
|
282
|
+
return createPersistentState({
|
|
283
|
+
key: `table_search_${config.tableId}`,
|
|
284
|
+
defaultValue: '',
|
|
285
|
+
storage: config.storage || 'localStorage',
|
|
286
|
+
debounceMs: config.debounceMs || 500
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
export function createPersistentGroupByKey(config) {
|
|
290
|
+
return createPersistentState({
|
|
291
|
+
key: `table_group_by_${config.tableId}`,
|
|
292
|
+
defaultValue: null,
|
|
293
|
+
storage: config.storage || 'localStorage',
|
|
294
|
+
debounceMs: config.debounceMs || 500
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
export function createPersistentSummaryConfigs(config) {
|
|
298
|
+
return createPersistentState({
|
|
299
|
+
key: `table_summary_configs_${config.tableId}`,
|
|
300
|
+
defaultValue: [],
|
|
301
|
+
storage: config.storage || 'localStorage',
|
|
302
|
+
debounceMs: config.debounceMs || 500
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
export function createPersistentSortState(config) {
|
|
306
|
+
return createPersistentState({
|
|
307
|
+
key: `table_sort_${config.tableId}`,
|
|
308
|
+
defaultValue: { column: '', direction: 'asc' },
|
|
309
|
+
storage: config.storage || 'localStorage',
|
|
310
|
+
debounceMs: config.debounceMs || 500
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
export function createPersistentHiddenColumns(config) {
|
|
314
|
+
return createPersistentState({
|
|
315
|
+
key: `table_hidden_columns_${config.tableId}`,
|
|
316
|
+
defaultValue: [],
|
|
317
|
+
storage: config.storage || 'localStorage',
|
|
318
|
+
debounceMs: config.debounceMs || 500
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
export function createPersistentColumnOrder(config) {
|
|
322
|
+
return createPersistentState({
|
|
323
|
+
key: `table_column_order_${config.tableId}`,
|
|
324
|
+
defaultValue: [],
|
|
325
|
+
storage: config.storage || 'localStorage',
|
|
326
|
+
debounceMs: config.debounceMs || 500
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
// Sticky-pinning measurement helpers
|
|
330
|
+
export { measureToCssVar, measureViewportOffsetTop, observeStuck } from './sticky-measure';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `{@attach}` factory that observes the height of an element with `ResizeObserver`
|
|
3
|
+
* and writes it to a CSS custom property on a target element.
|
|
4
|
+
*
|
|
5
|
+
* The target defaults to the closest ancestor matching `[data-table-container]`,
|
|
6
|
+
* which is the outer container set by `Table.svelte`. This keeps the three
|
|
7
|
+
* sticky-pinning layers (toolbar, thead, group-header) reading from the same
|
|
8
|
+
* coordinate origin.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <div {@attach measureToCssVar('--blocks-table-toolbar-h')}>
|
|
13
|
+
* ...toolbar...
|
|
14
|
+
* </div>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function measureToCssVar(property: string, targetSelector?: string): (element: HTMLElement) => () => void;
|
|
18
|
+
/**
|
|
19
|
+
* `{@attach}` factory that measures an element's distance from the top of the
|
|
20
|
+
* document (`getBoundingClientRect().top + scrollY`) and writes it to a CSS
|
|
21
|
+
* custom property on the closest `[data-table-container]` (the element itself
|
|
22
|
+
* when attached to the container).
|
|
23
|
+
*
|
|
24
|
+
* Used by `fit="viewport"` to size the contained scroll box via
|
|
25
|
+
* `max-height: calc(100dvh - <offset>)`. The offset is measured
|
|
26
|
+
* document-relative — i.e. invariant under page scroll — so re-measuring can
|
|
27
|
+
* never feed back into the page's own scroll position. It re-measures on
|
|
28
|
+
* viewport resize and when content *above* the table reflows (observed via the
|
|
29
|
+
* document body, whose height tracks the document flow).
|
|
30
|
+
*
|
|
31
|
+
* Offsets *inside* the container (e.g. a growing toolbar / filter chips) do NOT
|
|
32
|
+
* change this value and are absorbed by the flex layout instead.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```svelte
|
|
36
|
+
* <div data-table-container {@attach measureViewportOffsetTop('--blocks-table-avail-top')}>
|
|
37
|
+
* ...
|
|
38
|
+
* </div>
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function measureViewportOffsetTop(property: string, targetSelector?: string): (element: HTMLElement) => () => void;
|
|
42
|
+
/**
|
|
43
|
+
* `{@attach}` factory that toggles `data-stuck` on an element based on whether
|
|
44
|
+
* a sentinel placed just above it is visible.
|
|
45
|
+
*
|
|
46
|
+
* The sentinel is the previous-sibling element with the `data-sticky-sentinel`
|
|
47
|
+
* attribute. When the sentinel scrolls out of view (intersectionRatio = 0),
|
|
48
|
+
* the element is considered "stuck" and the callback fires with `true`.
|
|
49
|
+
*
|
|
50
|
+
* @param onStuck callback receiving the latest stuck-state
|
|
51
|
+
* @param rootMarginTop CSS rootMargin top offset (negative pixel string), used
|
|
52
|
+
* to fire the stuck transition exactly at the pin line (sticky-top).
|
|
53
|
+
*/
|
|
54
|
+
export declare function observeStuck(onStuck: (stuck: boolean) => void, rootMarginTop?: string): (element: HTMLElement) => (() => void) | undefined;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `{@attach}` factory that observes the height of an element with `ResizeObserver`
|
|
3
|
+
* and writes it to a CSS custom property on a target element.
|
|
4
|
+
*
|
|
5
|
+
* The target defaults to the closest ancestor matching `[data-table-container]`,
|
|
6
|
+
* which is the outer container set by `Table.svelte`. This keeps the three
|
|
7
|
+
* sticky-pinning layers (toolbar, thead, group-header) reading from the same
|
|
8
|
+
* coordinate origin.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```svelte
|
|
12
|
+
* <div {@attach measureToCssVar('--blocks-table-toolbar-h')}>
|
|
13
|
+
* ...toolbar...
|
|
14
|
+
* </div>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function measureToCssVar(property, targetSelector = '[data-table-container]') {
|
|
18
|
+
return (element) => {
|
|
19
|
+
const target = element.closest(targetSelector) ?? element;
|
|
20
|
+
const apply = (height) => {
|
|
21
|
+
target.style.setProperty(property, `${Math.round(height)}px`);
|
|
22
|
+
};
|
|
23
|
+
apply(element.getBoundingClientRect().height);
|
|
24
|
+
const observer = new ResizeObserver((entries) => {
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
apply(entry.contentRect.height);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
observer.observe(element);
|
|
30
|
+
return () => {
|
|
31
|
+
observer.disconnect();
|
|
32
|
+
target.style.removeProperty(property);
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* `{@attach}` factory that measures an element's distance from the top of the
|
|
38
|
+
* document (`getBoundingClientRect().top + scrollY`) and writes it to a CSS
|
|
39
|
+
* custom property on the closest `[data-table-container]` (the element itself
|
|
40
|
+
* when attached to the container).
|
|
41
|
+
*
|
|
42
|
+
* Used by `fit="viewport"` to size the contained scroll box via
|
|
43
|
+
* `max-height: calc(100dvh - <offset>)`. The offset is measured
|
|
44
|
+
* document-relative — i.e. invariant under page scroll — so re-measuring can
|
|
45
|
+
* never feed back into the page's own scroll position. It re-measures on
|
|
46
|
+
* viewport resize and when content *above* the table reflows (observed via the
|
|
47
|
+
* document body, whose height tracks the document flow).
|
|
48
|
+
*
|
|
49
|
+
* Offsets *inside* the container (e.g. a growing toolbar / filter chips) do NOT
|
|
50
|
+
* change this value and are absorbed by the flex layout instead.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```svelte
|
|
54
|
+
* <div data-table-container {@attach measureViewportOffsetTop('--blocks-table-avail-top')}>
|
|
55
|
+
* ...
|
|
56
|
+
* </div>
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
export function measureViewportOffsetTop(property, targetSelector = '[data-table-container]') {
|
|
60
|
+
return (element) => {
|
|
61
|
+
const target = element.closest(targetSelector) ?? element;
|
|
62
|
+
const apply = () => {
|
|
63
|
+
const offset = element.getBoundingClientRect().top + window.scrollY;
|
|
64
|
+
target.style.setProperty(property, `${Math.max(0, Math.round(offset))}px`);
|
|
65
|
+
};
|
|
66
|
+
apply();
|
|
67
|
+
// `resize` covers viewport changes; observing the body covers reflow of
|
|
68
|
+
// content above the table (tabs, banners) that shifts the container down.
|
|
69
|
+
const observer = new ResizeObserver(apply);
|
|
70
|
+
observer.observe(document.body);
|
|
71
|
+
window.addEventListener('resize', apply);
|
|
72
|
+
return () => {
|
|
73
|
+
observer.disconnect();
|
|
74
|
+
window.removeEventListener('resize', apply);
|
|
75
|
+
target.style.removeProperty(property);
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `{@attach}` factory that toggles `data-stuck` on an element based on whether
|
|
81
|
+
* a sentinel placed just above it is visible.
|
|
82
|
+
*
|
|
83
|
+
* The sentinel is the previous-sibling element with the `data-sticky-sentinel`
|
|
84
|
+
* attribute. When the sentinel scrolls out of view (intersectionRatio = 0),
|
|
85
|
+
* the element is considered "stuck" and the callback fires with `true`.
|
|
86
|
+
*
|
|
87
|
+
* @param onStuck callback receiving the latest stuck-state
|
|
88
|
+
* @param rootMarginTop CSS rootMargin top offset (negative pixel string), used
|
|
89
|
+
* to fire the stuck transition exactly at the pin line (sticky-top).
|
|
90
|
+
*/
|
|
91
|
+
export function observeStuck(onStuck, rootMarginTop = '0px') {
|
|
92
|
+
return (element) => {
|
|
93
|
+
const sentinel = element.previousElementSibling;
|
|
94
|
+
if (!sentinel || sentinel.dataset.stickySentinel === undefined)
|
|
95
|
+
return;
|
|
96
|
+
const observer = new IntersectionObserver(([entry]) => {
|
|
97
|
+
if (!entry)
|
|
98
|
+
return;
|
|
99
|
+
onStuck(!entry.isIntersecting);
|
|
100
|
+
}, {
|
|
101
|
+
threshold: [0, 1],
|
|
102
|
+
rootMargin: `${rootMarginTop} 0px 0px 0px`
|
|
103
|
+
});
|
|
104
|
+
observer.observe(sentinel);
|
|
105
|
+
return () => observer.disconnect();
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight row virtualizer for fixed-height table rows.
|
|
3
|
+
* Only renders rows visible in the scroll viewport plus an overscan buffer.
|
|
4
|
+
*
|
|
5
|
+
* This avoids adding @tanstack/virtual as an external dependency and is fully
|
|
6
|
+
* compatible with Svelte 5 runes.
|
|
7
|
+
*/
|
|
8
|
+
export interface VirtualItem {
|
|
9
|
+
/** Index in the source data array */
|
|
10
|
+
index: number;
|
|
11
|
+
/** Pixel offset from the top of the scroll container */
|
|
12
|
+
offsetTop: number;
|
|
13
|
+
/** Height of this item in pixels */
|
|
14
|
+
height: number;
|
|
15
|
+
}
|
|
16
|
+
export interface VirtualizerOptions {
|
|
17
|
+
/** Total number of items */
|
|
18
|
+
count: number;
|
|
19
|
+
/** Fixed height per row in pixels */
|
|
20
|
+
rowHeight: number;
|
|
21
|
+
/** Number of rows to render above/below the viewport */
|
|
22
|
+
overscan?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface VirtualizerResult {
|
|
25
|
+
/** Items to render */
|
|
26
|
+
virtualItems: VirtualItem[];
|
|
27
|
+
/** Total height of all rows (sets the scroll container's inner height) */
|
|
28
|
+
totalHeight: number;
|
|
29
|
+
/** Index of the first rendered item */
|
|
30
|
+
startIndex: number;
|
|
31
|
+
/** Index of the last rendered item (exclusive) */
|
|
32
|
+
endIndex: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Row height in pixels for each table size variant.
|
|
36
|
+
* Matches TABLE_DIMENSIONS.height.row in table.system.ts.
|
|
37
|
+
*/
|
|
38
|
+
export declare const ROW_HEIGHTS: Record<string, number>;
|
|
39
|
+
/**
|
|
40
|
+
* Computes which rows are visible given a scroll position and viewport height.
|
|
41
|
+
* Pure function – no side effects, easy to test.
|
|
42
|
+
*/
|
|
43
|
+
export declare function computeVirtualItems(scrollTop: number, viewportHeight: number, options: VirtualizerOptions): VirtualizerResult;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight row virtualizer for fixed-height table rows.
|
|
3
|
+
* Only renders rows visible in the scroll viewport plus an overscan buffer.
|
|
4
|
+
*
|
|
5
|
+
* This avoids adding @tanstack/virtual as an external dependency and is fully
|
|
6
|
+
* compatible with Svelte 5 runes.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Row height in pixels for each table size variant.
|
|
10
|
+
* Matches TABLE_DIMENSIONS.height.row in table.system.ts.
|
|
11
|
+
*/
|
|
12
|
+
export const ROW_HEIGHTS = {
|
|
13
|
+
sm: 48,
|
|
14
|
+
md: 56,
|
|
15
|
+
lg: 64
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Computes which rows are visible given a scroll position and viewport height.
|
|
19
|
+
* Pure function – no side effects, easy to test.
|
|
20
|
+
*/
|
|
21
|
+
export function computeVirtualItems(scrollTop, viewportHeight, options) {
|
|
22
|
+
const { count, rowHeight, overscan = 5 } = options;
|
|
23
|
+
if (count === 0 || viewportHeight === 0) {
|
|
24
|
+
return { virtualItems: [], totalHeight: 0, startIndex: 0, endIndex: 0 };
|
|
25
|
+
}
|
|
26
|
+
const totalHeight = count * rowHeight;
|
|
27
|
+
// Calculate visible range
|
|
28
|
+
const rawStart = Math.floor(scrollTop / rowHeight);
|
|
29
|
+
const rawEnd = Math.ceil((scrollTop + viewportHeight) / rowHeight);
|
|
30
|
+
// Apply overscan
|
|
31
|
+
const startIndex = Math.max(0, rawStart - overscan);
|
|
32
|
+
const endIndex = Math.min(count, rawEnd + overscan);
|
|
33
|
+
// Build virtual items
|
|
34
|
+
const virtualItems = [];
|
|
35
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
36
|
+
virtualItems.push({
|
|
37
|
+
index: i,
|
|
38
|
+
offsetTop: i * rowHeight,
|
|
39
|
+
height: rowHeight
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return { virtualItems, totalHeight, startIndex, endIndex };
|
|
43
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TABLE VARIANTS EXPORTS
|
|
3
|
+
*
|
|
4
|
+
* Internal system tokens (TABLE_DIMENSIONS, TABLE_LAYOUTS, TABLE_STATES, etc.)
|
|
5
|
+
* are intentionally NOT re-exported here — they are implementation details
|
|
6
|
+
* consumed by the variant files, not part of the public API.
|
|
7
|
+
*/
|
|
8
|
+
export * from './table.variants';
|
|
9
|
+
export * from './table-cells.variants';
|
|
10
|
+
export * from './table-features.variants';
|
|
11
|
+
export * from './table-states.variants';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TABLE VARIANTS EXPORTS
|
|
3
|
+
*
|
|
4
|
+
* Internal system tokens (TABLE_DIMENSIONS, TABLE_LAYOUTS, TABLE_STATES, etc.)
|
|
5
|
+
* are intentionally NOT re-exported here — they are implementation details
|
|
6
|
+
* consumed by the variant files, not part of the public API.
|
|
7
|
+
*/
|
|
8
|
+
// Core table variants
|
|
9
|
+
export * from './table.variants';
|
|
10
|
+
// Cell variants
|
|
11
|
+
export * from './table-cells.variants';
|
|
12
|
+
// Feature variants
|
|
13
|
+
export * from './table-features.variants';
|
|
14
|
+
// State variants
|
|
15
|
+
export * from './table-states.variants';
|