@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,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
+ }