@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.
Files changed (154) hide show
  1. package/README.md +153 -0
  2. package/dist/cells/ActionButtons.svelte +224 -0
  3. package/dist/cells/ActionButtons.svelte.d.ts +74 -0
  4. package/dist/cells/CopyButton.svelte +89 -0
  5. package/dist/cells/CopyButton.svelte.d.ts +33 -0
  6. package/dist/cells/CustomCell.svelte +136 -0
  7. package/dist/cells/CustomCell.svelte.d.ts +44 -0
  8. package/dist/cells/DateCell.svelte +194 -0
  9. package/dist/cells/DateCell.svelte.d.ts +39 -0
  10. package/dist/cells/LinkCell.svelte +240 -0
  11. package/dist/cells/LinkCell.svelte.d.ts +42 -0
  12. package/dist/cells/NumberCell.svelte +225 -0
  13. package/dist/cells/NumberCell.svelte.d.ts +47 -0
  14. package/dist/cells/StatusBadge.svelte +121 -0
  15. package/dist/cells/StatusBadge.svelte.d.ts +44 -0
  16. package/dist/cells/UserAvatar.svelte +71 -0
  17. package/dist/cells/UserAvatar.svelte.d.ts +37 -0
  18. package/dist/cells/index.d.ts +8 -0
  19. package/dist/cells/index.js +9 -0
  20. package/dist/core/EmptyState.svelte +161 -0
  21. package/dist/core/EmptyState.svelte.d.ts +16 -0
  22. package/dist/core/ErrorState.svelte +158 -0
  23. package/dist/core/ErrorState.svelte.d.ts +15 -0
  24. package/dist/core/GroupedRow.svelte +239 -0
  25. package/dist/core/GroupedRow.svelte.d.ts +18 -0
  26. package/dist/core/LoadingState.svelte +75 -0
  27. package/dist/core/LoadingState.svelte.d.ts +14 -0
  28. package/dist/core/MobileCard.svelte +151 -0
  29. package/dist/core/MobileCard.svelte.d.ts +15 -0
  30. package/dist/core/TableCell.svelte +105 -0
  31. package/dist/core/TableCell.svelte.d.ts +14 -0
  32. package/dist/core/TableDesktop.svelte +480 -0
  33. package/dist/core/TableDesktop.svelte.d.ts +26 -0
  34. package/dist/core/TableHead.svelte +314 -0
  35. package/dist/core/TableHead.svelte.d.ts +7 -0
  36. package/dist/core/TableMobile.svelte +112 -0
  37. package/dist/core/TableMobile.svelte.d.ts +13 -0
  38. package/dist/core/TableProvider.svelte +271 -0
  39. package/dist/core/TableProvider.svelte.d.ts +40 -0
  40. package/dist/core/TableRow.svelte +171 -0
  41. package/dist/core/TableRow.svelte.d.ts +16 -0
  42. package/dist/core/index.d.ts +17 -0
  43. package/dist/core/index.js +14 -0
  44. package/dist/core/sticky-context.svelte.d.ts +48 -0
  45. package/dist/core/sticky-context.svelte.js +88 -0
  46. package/dist/core/table/Table.svelte +304 -0
  47. package/dist/core/table/Table.svelte.d.ts +26 -0
  48. package/dist/core/table/index.d.ts +448 -0
  49. package/dist/core/table/index.js +1 -0
  50. package/dist/core/table-style-context.d.ts +66 -0
  51. package/dist/core/table-style-context.js +26 -0
  52. package/dist/factories/ColumnValidation.d.ts +49 -0
  53. package/dist/factories/ColumnValidation.js +188 -0
  54. package/dist/factories/TableColumns.d.ts +97 -0
  55. package/dist/factories/TableColumns.js +262 -0
  56. package/dist/factories/TypedColumnBuilder.d.ts +41 -0
  57. package/dist/factories/TypedColumnBuilder.js +72 -0
  58. package/dist/factories/index.d.ts +12 -0
  59. package/dist/factories/index.js +13 -0
  60. package/dist/features/HeaderMenu.svelte +236 -0
  61. package/dist/features/HeaderMenu.svelte.d.ts +8 -0
  62. package/dist/features/LiveUpdateBanner.svelte +66 -0
  63. package/dist/features/LiveUpdateBanner.svelte.d.ts +6 -0
  64. package/dist/features/SearchHighlight.svelte +21 -0
  65. package/dist/features/SearchHighlight.svelte.d.ts +8 -0
  66. package/dist/features/SmartFilterBar/ChipsField.svelte +104 -0
  67. package/dist/features/SmartFilterBar/ChipsField.svelte.d.ts +5 -0
  68. package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte +84 -0
  69. package/dist/features/SmartFilterBar/ColumnVisibilityMenu.svelte.d.ts +3 -0
  70. package/dist/features/SmartFilterBar/FilterMenu.svelte +367 -0
  71. package/dist/features/SmartFilterBar/FilterMenu.svelte.d.ts +3 -0
  72. package/dist/features/SmartFilterBar/GroupingMenu.svelte +82 -0
  73. package/dist/features/SmartFilterBar/GroupingMenu.svelte.d.ts +3 -0
  74. package/dist/features/SmartFilterBar/SmartFilterBar.svelte +109 -0
  75. package/dist/features/SmartFilterBar/SmartFilterBar.svelte.d.ts +11 -0
  76. package/dist/features/SmartFilterBar/SummaryMenu.svelte +118 -0
  77. package/dist/features/SmartFilterBar/SummaryMenu.svelte.d.ts +3 -0
  78. package/dist/features/SummaryRow.svelte +97 -0
  79. package/dist/features/SummaryRow.svelte.d.ts +8 -0
  80. package/dist/features/index.d.ts +4 -0
  81. package/dist/features/index.js +4 -0
  82. package/dist/i18n/index.d.ts +366 -0
  83. package/dist/i18n/index.js +21 -0
  84. package/dist/index.d.ts +28 -0
  85. package/dist/index.js +41 -0
  86. package/dist/stores/TableStore.svelte.d.ts +192 -0
  87. package/dist/stores/TableStore.svelte.js +362 -0
  88. package/dist/stores/concerns/index.d.ts +15 -0
  89. package/dist/stores/concerns/index.js +14 -0
  90. package/dist/stores/concerns/types.d.ts +31 -0
  91. package/dist/stores/concerns/types.js +1 -0
  92. package/dist/stores/concerns/useColumnOrder.svelte.d.ts +16 -0
  93. package/dist/stores/concerns/useColumnOrder.svelte.js +81 -0
  94. package/dist/stores/concerns/useColumnVisibility.svelte.d.ts +16 -0
  95. package/dist/stores/concerns/useColumnVisibility.svelte.js +58 -0
  96. package/dist/stores/concerns/useExpansion.svelte.d.ts +9 -0
  97. package/dist/stores/concerns/useExpansion.svelte.js +32 -0
  98. package/dist/stores/concerns/useFiltering.svelte.d.ts +20 -0
  99. package/dist/stores/concerns/useFiltering.svelte.js +109 -0
  100. package/dist/stores/concerns/useFocusManagement.svelte.d.ts +15 -0
  101. package/dist/stores/concerns/useFocusManagement.svelte.js +52 -0
  102. package/dist/stores/concerns/useGrouping.svelte.d.ts +15 -0
  103. package/dist/stores/concerns/useGrouping.svelte.js +86 -0
  104. package/dist/stores/concerns/useLiveUpdates.svelte.d.ts +45 -0
  105. package/dist/stores/concerns/useLiveUpdates.svelte.js +175 -0
  106. package/dist/stores/concerns/usePagination.svelte.d.ts +18 -0
  107. package/dist/stores/concerns/usePagination.svelte.js +54 -0
  108. package/dist/stores/concerns/usePersistence.svelte.d.ts +36 -0
  109. package/dist/stores/concerns/usePersistence.svelte.js +167 -0
  110. package/dist/stores/concerns/useRemoteData.svelte.d.ts +21 -0
  111. package/dist/stores/concerns/useRemoteData.svelte.js +64 -0
  112. package/dist/stores/concerns/useSearch.svelte.d.ts +8 -0
  113. package/dist/stores/concerns/useSearch.svelte.js +16 -0
  114. package/dist/stores/concerns/useSelection.svelte.d.ts +21 -0
  115. package/dist/stores/concerns/useSelection.svelte.js +110 -0
  116. package/dist/stores/concerns/useSorting.svelte.d.ts +11 -0
  117. package/dist/stores/concerns/useSorting.svelte.js +70 -0
  118. package/dist/stores/concerns/useSummary.svelte.d.ts +18 -0
  119. package/dist/stores/concerns/useSummary.svelte.js +96 -0
  120. package/dist/stores/index.d.ts +1 -0
  121. package/dist/stores/index.js +1 -0
  122. package/dist/style/index.css +137 -0
  123. package/dist/style/index.d.ts +2 -0
  124. package/dist/style/index.js +2 -0
  125. package/dist/style/table-theme.css +131 -0
  126. package/dist/style/themes/comfortable.css +20 -0
  127. package/dist/style/themes/compact.css +20 -0
  128. package/dist/translations/de.d.ts +177 -0
  129. package/dist/translations/de.js +176 -0
  130. package/dist/translations/en.d.ts +177 -0
  131. package/dist/translations/en.js +176 -0
  132. package/dist/types/index.d.ts +1 -0
  133. package/dist/types/index.js +1 -0
  134. package/dist/types/tableTypes.d.ts +262 -0
  135. package/dist/types/tableTypes.js +1 -0
  136. package/dist/utils/index.d.ts +165 -0
  137. package/dist/utils/index.js +330 -0
  138. package/dist/utils/sticky-measure.d.ts +54 -0
  139. package/dist/utils/sticky-measure.js +107 -0
  140. package/dist/utils/virtualizer.d.ts +43 -0
  141. package/dist/utils/virtualizer.js +43 -0
  142. package/dist/variants/index.d.ts +11 -0
  143. package/dist/variants/index.js +15 -0
  144. package/dist/variants/table-cells.variants.d.ts +827 -0
  145. package/dist/variants/table-cells.variants.js +627 -0
  146. package/dist/variants/table-features.variants.d.ts +547 -0
  147. package/dist/variants/table-features.variants.js +412 -0
  148. package/dist/variants/table-states.variants.d.ts +594 -0
  149. package/dist/variants/table-states.variants.js +394 -0
  150. package/dist/variants/table.system.d.ts +301 -0
  151. package/dist/variants/table.system.js +314 -0
  152. package/dist/variants/table.variants.d.ts +428 -0
  153. package/dist/variants/table.variants.js +360 -0
  154. 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';