@human-kit/svelte-components 1.0.0-alpha.20 → 1.0.0-alpha.21
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/dist/button/root/button-root.svelte +32 -1
- package/dist/dialog/trigger/dialog-trigger.svelte +3 -0
- package/dist/popover/content/popover-content-controlled-close-test.svelte +30 -0
- package/dist/popover/content/popover-content-controlled-close-test.svelte.d.ts +3 -0
- package/dist/popover/content/popover-content.svelte +38 -0
- package/dist/popover/trigger/popover-trigger-button-root-test.svelte +17 -0
- package/dist/popover/trigger/popover-trigger-button-root-test.svelte.d.ts +18 -0
- package/dist/popover/trigger/popover-trigger-button.svelte +1 -0
- package/dist/popover/trigger/popover-trigger.svelte +3 -0
- package/dist/table/body/README.md +18 -5
- package/dist/table/body/table-body-items-test.svelte +45 -0
- package/dist/table/body/table-body-items-test.svelte.d.ts +18 -0
- package/dist/table/body/table-body.svelte +153 -6
- package/dist/table/body/table-body.svelte.d.ts +43 -2
- package/dist/table/cell/table-cell.svelte +5 -17
- package/dist/table/checkbox/table-checkbox-test.svelte +116 -74
- package/dist/table/checkbox/table-checkbox-test.svelte.d.ts +4 -0
- package/dist/table/empty-state/table-empty-state.svelte +1 -1
- package/dist/table/index.d.ts +1 -1
- package/dist/table/root/context.d.ts +5 -0
- package/dist/table/root/context.js +51 -9
- package/dist/table/root/table-root.svelte +17 -9
- package/dist/table/root/table-ssr-wrapper-column.svelte +48 -0
- package/dist/table/root/table-ssr-wrapper-column.svelte.d.ts +4 -0
- package/dist/table/root/table-ssr-wrapper-context.d.ts +11 -0
- package/dist/table/root/table-ssr-wrapper-context.js +13 -0
- package/dist/table/root/table-ssr-wrapper-test.svelte +57 -0
- package/dist/table/root/table-ssr-wrapper-test.svelte.d.ts +3 -0
- package/dist/table/types.d.ts +20 -3
- package/package.json +3 -2
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
TableSelectionKey,
|
|
7
7
|
TableSelectionMode
|
|
8
8
|
} from '../root/context';
|
|
9
|
+
import type { TableBodyVirtualizer } from '../types.js';
|
|
9
10
|
|
|
10
11
|
type DemoRow = {
|
|
11
12
|
id: string;
|
|
@@ -32,6 +33,9 @@
|
|
|
32
33
|
disallowEmptySelection?: boolean;
|
|
33
34
|
disabledKeys?: Iterable<TableSelectionKey>;
|
|
34
35
|
initialSelectedKeys?: Iterable<TableSelectionKey>;
|
|
36
|
+
useItemsMode?: boolean;
|
|
37
|
+
virtualizer?: TableBodyVirtualizer;
|
|
38
|
+
containerHeight?: string;
|
|
35
39
|
};
|
|
36
40
|
|
|
37
41
|
let {
|
|
@@ -41,7 +45,10 @@
|
|
|
41
45
|
disabledBehavior = 'all',
|
|
42
46
|
disallowEmptySelection = false,
|
|
43
47
|
disabledKeys,
|
|
44
|
-
initialSelectedKeys
|
|
48
|
+
initialSelectedKeys,
|
|
49
|
+
useItemsMode = false,
|
|
50
|
+
virtualizer,
|
|
51
|
+
containerHeight = '160px'
|
|
45
52
|
}: CheckboxTestProps = $props();
|
|
46
53
|
|
|
47
54
|
let currentSelectedKeys = $state<Set<TableSelectionKey>>(
|
|
@@ -49,80 +56,115 @@
|
|
|
49
56
|
);
|
|
50
57
|
</script>
|
|
51
58
|
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<Table.
|
|
63
|
-
<Table.
|
|
64
|
-
<Table.
|
|
65
|
-
<Table.
|
|
66
|
-
<Table.
|
|
67
|
-
<
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
<Table.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<Table.
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</Table.Header>
|
|
89
|
-
|
|
90
|
-
<Table.Body>
|
|
91
|
-
{#each rows as row (row.id)}
|
|
92
|
-
<Table.Row
|
|
93
|
-
id={row.id}
|
|
94
|
-
isDisabled={disabledKeys ? Array.from(disabledKeys).includes(row.id) : false}
|
|
95
|
-
>
|
|
96
|
-
<Table.Cell data-testid={`selection-cell-${row.id}`}>
|
|
97
|
-
<Table.Checkbox style={checkboxStyle} data-testid={`row-checkbox-${row.id}`}>
|
|
98
|
-
<Table.CheckboxIndicator style={indicatorStyle}>
|
|
99
|
-
<svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5">
|
|
100
|
-
<path
|
|
101
|
-
d="M3.75 8.5 6.75 11.5 12.25 5.5"
|
|
102
|
-
fill="none"
|
|
103
|
-
stroke="currentColor"
|
|
104
|
-
stroke-linecap="round"
|
|
105
|
-
stroke-linejoin="round"
|
|
106
|
-
stroke-width="2"
|
|
107
|
-
/>
|
|
108
|
-
</svg>
|
|
109
|
-
</Table.CheckboxIndicator>
|
|
110
|
-
</Table.Checkbox>
|
|
111
|
-
</Table.Cell>
|
|
112
|
-
<Table.Cell data-testid={`email-cell-${row.id}`}>{row.email}</Table.Cell>
|
|
113
|
-
<Table.Cell data-testid={`group-cell-${row.id}`}>{row.group}</Table.Cell>
|
|
59
|
+
<div style={useItemsMode ? `max-height:${containerHeight};overflow:auto;` : undefined}>
|
|
60
|
+
<Table.Root
|
|
61
|
+
aria-label="Users table"
|
|
62
|
+
{selectionMode}
|
|
63
|
+
{selectionBehavior}
|
|
64
|
+
{disabledBehavior}
|
|
65
|
+
{disallowEmptySelection}
|
|
66
|
+
bind:selectedKeys={currentSelectedKeys}
|
|
67
|
+
{disabledKeys}
|
|
68
|
+
>
|
|
69
|
+
<Table.Header>
|
|
70
|
+
<Table.Row>
|
|
71
|
+
<Table.Column id="selection" textValue="Selection">
|
|
72
|
+
<Table.ColumnHeaderCell data-testid="selection-header-cell">
|
|
73
|
+
<Table.Checkbox style={checkboxStyle} data-testid="header-checkbox">
|
|
74
|
+
<Table.CheckboxIndicator style={indicatorStyle}>
|
|
75
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5">
|
|
76
|
+
<path
|
|
77
|
+
d="M3.75 8.5 6.75 11.5 12.25 5.5"
|
|
78
|
+
fill="none"
|
|
79
|
+
stroke="currentColor"
|
|
80
|
+
stroke-linecap="round"
|
|
81
|
+
stroke-linejoin="round"
|
|
82
|
+
stroke-width="2"
|
|
83
|
+
/>
|
|
84
|
+
</svg>
|
|
85
|
+
</Table.CheckboxIndicator>
|
|
86
|
+
</Table.Checkbox>
|
|
87
|
+
</Table.ColumnHeaderCell>
|
|
88
|
+
</Table.Column>
|
|
89
|
+
<Table.Column id="email" isRowHeader textValue="Email">
|
|
90
|
+
<Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
|
|
91
|
+
</Table.Column>
|
|
92
|
+
<Table.Column id="group" textValue="Group">
|
|
93
|
+
<Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
|
|
94
|
+
</Table.Column>
|
|
114
95
|
</Table.Row>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
96
|
+
</Table.Header>
|
|
97
|
+
|
|
98
|
+
{#if useItemsMode}
|
|
99
|
+
<Table.Body items={rows} {virtualizer}>
|
|
100
|
+
{#snippet children(row)}
|
|
101
|
+
<Table.Row
|
|
102
|
+
id={row.id}
|
|
103
|
+
isDisabled={disabledKeys ? Array.from(disabledKeys).includes(row.id) : false}
|
|
104
|
+
>
|
|
105
|
+
<Table.Cell data-testid={`selection-cell-${row.id}`}>
|
|
106
|
+
<Table.Checkbox style={checkboxStyle} data-testid={`row-checkbox-${row.id}`}>
|
|
107
|
+
<Table.CheckboxIndicator style={indicatorStyle}>
|
|
108
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5">
|
|
109
|
+
<path
|
|
110
|
+
d="M3.75 8.5 6.75 11.5 12.25 5.5"
|
|
111
|
+
fill="none"
|
|
112
|
+
stroke="currentColor"
|
|
113
|
+
stroke-linecap="round"
|
|
114
|
+
stroke-linejoin="round"
|
|
115
|
+
stroke-width="2"
|
|
116
|
+
/>
|
|
117
|
+
</svg>
|
|
118
|
+
</Table.CheckboxIndicator>
|
|
119
|
+
</Table.Checkbox>
|
|
120
|
+
</Table.Cell>
|
|
121
|
+
<Table.Cell data-testid={`email-cell-${row.id}`}>{row.email}</Table.Cell>
|
|
122
|
+
<Table.Cell data-testid={`group-cell-${row.id}`}>{row.group}</Table.Cell>
|
|
123
|
+
</Table.Row>
|
|
124
|
+
{/snippet}
|
|
125
|
+
{#snippet empty()}
|
|
126
|
+
<Table.EmptyState>No users found.</Table.EmptyState>
|
|
127
|
+
{/snippet}
|
|
128
|
+
</Table.Body>
|
|
129
|
+
{:else}
|
|
130
|
+
<Table.Body>
|
|
131
|
+
{#each rows as row (row.id)}
|
|
132
|
+
<Table.Row
|
|
133
|
+
id={row.id}
|
|
134
|
+
isDisabled={disabledKeys ? Array.from(disabledKeys).includes(row.id) : false}
|
|
135
|
+
>
|
|
136
|
+
<Table.Cell data-testid={`selection-cell-${row.id}`}>
|
|
137
|
+
<Table.Checkbox style={checkboxStyle} data-testid={`row-checkbox-${row.id}`}>
|
|
138
|
+
<Table.CheckboxIndicator style={indicatorStyle}>
|
|
139
|
+
<svg aria-hidden="true" viewBox="0 0 16 16" class="h-3.5 w-3.5">
|
|
140
|
+
<path
|
|
141
|
+
d="M3.75 8.5 6.75 11.5 12.25 5.5"
|
|
142
|
+
fill="none"
|
|
143
|
+
stroke="currentColor"
|
|
144
|
+
stroke-linecap="round"
|
|
145
|
+
stroke-linejoin="round"
|
|
146
|
+
stroke-width="2"
|
|
147
|
+
/>
|
|
148
|
+
</svg>
|
|
149
|
+
</Table.CheckboxIndicator>
|
|
150
|
+
</Table.Checkbox>
|
|
151
|
+
</Table.Cell>
|
|
152
|
+
<Table.Cell data-testid={`email-cell-${row.id}`}>{row.email}</Table.Cell>
|
|
153
|
+
<Table.Cell data-testid={`group-cell-${row.id}`}>{row.group}</Table.Cell>
|
|
154
|
+
</Table.Row>
|
|
155
|
+
{/each}
|
|
156
|
+
<Table.EmptyState>No users found.</Table.EmptyState>
|
|
157
|
+
</Table.Body>
|
|
158
|
+
{/if}
|
|
118
159
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
</Table.Root>
|
|
160
|
+
<Table.Footer>
|
|
161
|
+
<Table.Row>
|
|
162
|
+
<Table.Cell />
|
|
163
|
+
<Table.Cell>Total</Table.Cell>
|
|
164
|
+
<Table.Cell>{rows.length} users</Table.Cell>
|
|
165
|
+
</Table.Row>
|
|
166
|
+
</Table.Footer>
|
|
167
|
+
</Table.Root>
|
|
168
|
+
</div>
|
|
127
169
|
|
|
128
170
|
<output data-testid="selected-keys">{JSON.stringify([...currentSelectedKeys])}</output>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { TableDisabledBehavior, TableSelectionBehavior, TableSelectionKey, TableSelectionMode } from '../root/context';
|
|
2
|
+
import type { TableBodyVirtualizer } from '../types.js';
|
|
2
3
|
type DemoRow = {
|
|
3
4
|
id: string;
|
|
4
5
|
email: string;
|
|
@@ -12,6 +13,9 @@ type CheckboxTestProps = {
|
|
|
12
13
|
disallowEmptySelection?: boolean;
|
|
13
14
|
disabledKeys?: Iterable<TableSelectionKey>;
|
|
14
15
|
initialSelectedKeys?: Iterable<TableSelectionKey>;
|
|
16
|
+
useItemsMode?: boolean;
|
|
17
|
+
virtualizer?: TableBodyVirtualizer;
|
|
18
|
+
containerHeight?: string;
|
|
15
19
|
};
|
|
16
20
|
declare const TableCheckboxTest: import("svelte").Component<CheckboxTestProps, {}, "">;
|
|
17
21
|
type TableCheckboxTest = ReturnType<typeof TableCheckboxTest>;
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
const layoutVersion = table.layoutVersion;
|
|
14
14
|
const isVisible = $derived.by(() => {
|
|
15
15
|
void $layoutVersion;
|
|
16
|
-
return table.
|
|
16
|
+
return table.getLogicalBodyRowCount() === 0;
|
|
17
17
|
});
|
|
18
18
|
const columnCount = $derived.by(() => {
|
|
19
19
|
void $layoutVersion;
|
package/dist/table/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * as Table from './index.parts.js';
|
|
2
|
-
export type { TableBodyProps, TableCellProps, TableCheckboxIndicatorProps, TableCheckboxProps, TableColumnHeaderCellProps, TableColumnProps, TableColumnResizerProps, TableSortTriggerRenderState, TableSortTriggerProps, TableEmptyStateProps, TableFooterProps, TableHeaderProps, TableRowProps, TableRootProps } from './types.js';
|
|
2
|
+
export type { TableBodyProps, TableBodyVirtualizer, TableCellProps, TableCheckboxIndicatorProps, TableCheckboxProps, TableColumnHeaderCellProps, TableColumnProps, TableColumnResizerProps, TableSortTriggerRenderState, TableSortTriggerProps, TableEmptyStateProps, TableFooterProps, TableHeaderProps, TableRowProps, TableRootProps } from './types.js';
|
|
3
3
|
export { default as TableRoot } from './root/table-root.svelte';
|
|
4
4
|
export { default as TableColumn } from './column/table-column.svelte';
|
|
5
5
|
export { default as TableHeader } from './header/table-header.svelte';
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { type Readable } from 'svelte/store';
|
|
2
2
|
export type TableSelectionKey = string | number;
|
|
3
|
+
export type TableRowItem = Record<string, unknown> & {
|
|
4
|
+
id: TableSelectionKey;
|
|
5
|
+
};
|
|
3
6
|
export type TableSelectionMode = 'none' | 'single' | 'multiple';
|
|
4
7
|
export type TableSelectionBehavior = 'toggle' | 'replace';
|
|
5
8
|
export type TableDisabledBehavior = 'selection' | 'all';
|
|
@@ -132,9 +135,11 @@ export type TableContext = {
|
|
|
132
135
|
hasResizableColumns: () => boolean;
|
|
133
136
|
registerRow: (row: TableRowRegistration) => void;
|
|
134
137
|
unregisterRow: (token: string) => void;
|
|
138
|
+
setLogicalBodyRows: (ids?: Iterable<TableSelectionKey>) => void;
|
|
135
139
|
markBodyRowsInitialized: () => void;
|
|
136
140
|
getHeaderRowCount: () => number;
|
|
137
141
|
getBodyRowCount: () => number;
|
|
142
|
+
getLogicalBodyRowCount: () => number;
|
|
138
143
|
isRowSelected: (id: TableSelectionKey | undefined) => boolean;
|
|
139
144
|
isRowFocused: (token: string) => boolean;
|
|
140
145
|
isRowFocusTarget: (token: string) => boolean;
|
|
@@ -75,6 +75,7 @@ export function createTableContext(options = {}) {
|
|
|
75
75
|
const bodyRowOrder = [];
|
|
76
76
|
let bodyRowsInitialized = false;
|
|
77
77
|
let selectableBodyRowCount = 0;
|
|
78
|
+
let logicalBodyRowIds = null;
|
|
78
79
|
const cells = new Map();
|
|
79
80
|
const cellOrder = [];
|
|
80
81
|
let orderedRowTokensCache = {
|
|
@@ -86,6 +87,8 @@ export function createTableContext(options = {}) {
|
|
|
86
87
|
let columnWidthsCache = null;
|
|
87
88
|
let visibleColumnWidthsCache = null;
|
|
88
89
|
let resolvedVisibleColumnWidthsCache = null;
|
|
90
|
+
let measuredTableWidthCache;
|
|
91
|
+
let hasMeasuredTableWidthCache = false;
|
|
89
92
|
let navigableCellsCache = null;
|
|
90
93
|
let rowsWithCellsCache = null;
|
|
91
94
|
const layoutVersion = writable(0);
|
|
@@ -109,6 +112,8 @@ export function createTableContext(options = {}) {
|
|
|
109
112
|
columnWidthsCache = null;
|
|
110
113
|
visibleColumnWidthsCache = null;
|
|
111
114
|
resolvedVisibleColumnWidthsCache = null;
|
|
115
|
+
measuredTableWidthCache = undefined;
|
|
116
|
+
hasMeasuredTableWidthCache = false;
|
|
112
117
|
navigableCellsCache = null;
|
|
113
118
|
rowsWithCellsCache = null;
|
|
114
119
|
}
|
|
@@ -153,6 +158,8 @@ export function createTableContext(options = {}) {
|
|
|
153
158
|
columnWidthsCache = null;
|
|
154
159
|
visibleColumnWidthsCache = null;
|
|
155
160
|
resolvedVisibleColumnWidthsCache = null;
|
|
161
|
+
measuredTableWidthCache = undefined;
|
|
162
|
+
hasMeasuredTableWidthCache = false;
|
|
156
163
|
if (!widthNotifyScheduled) {
|
|
157
164
|
widthNotifyScheduled = true;
|
|
158
165
|
queueMicrotask(() => {
|
|
@@ -165,6 +172,8 @@ export function createTableContext(options = {}) {
|
|
|
165
172
|
columnWidthsCache = null;
|
|
166
173
|
visibleColumnWidthsCache = null;
|
|
167
174
|
resolvedVisibleColumnWidthsCache = null;
|
|
175
|
+
measuredTableWidthCache = undefined;
|
|
176
|
+
hasMeasuredTableWidthCache = false;
|
|
168
177
|
flushSync(() => {
|
|
169
178
|
widthVersion.update((value) => value + 1);
|
|
170
179
|
});
|
|
@@ -259,6 +268,9 @@ export function createTableContext(options = {}) {
|
|
|
259
268
|
return resizerLayoutReady && columnsWithResizers.size > 0 ? '1fr' : undefined;
|
|
260
269
|
}
|
|
261
270
|
function getMeasuredTableWidth() {
|
|
271
|
+
if (hasMeasuredTableWidthCache) {
|
|
272
|
+
return measuredTableWidthCache;
|
|
273
|
+
}
|
|
262
274
|
const tableCell = Array.from(cells.values()).find((cell) => cell.element)?.element;
|
|
263
275
|
const tableElement = tableCell?.closest('table');
|
|
264
276
|
const tableWidth = tableElement?.getBoundingClientRect().width;
|
|
@@ -277,9 +289,13 @@ export function createTableContext(options = {}) {
|
|
|
277
289
|
// the actual available space the table should fill.
|
|
278
290
|
const width = containerWidth ?? tableWidth;
|
|
279
291
|
if (width === undefined || width <= 0 || !Number.isFinite(width)) {
|
|
292
|
+
hasMeasuredTableWidthCache = true;
|
|
293
|
+
measuredTableWidthCache = undefined;
|
|
280
294
|
return undefined;
|
|
281
295
|
}
|
|
282
|
-
|
|
296
|
+
measuredTableWidthCache = Math.round(width);
|
|
297
|
+
hasMeasuredTableWidthCache = true;
|
|
298
|
+
return measuredTableWidthCache;
|
|
283
299
|
}
|
|
284
300
|
function getColumnWidthBounds(columnId) {
|
|
285
301
|
const registration = getColumnRegistrationById(columnId);
|
|
@@ -1066,6 +1082,27 @@ export function createTableContext(options = {}) {
|
|
|
1066
1082
|
}
|
|
1067
1083
|
notifyLayout();
|
|
1068
1084
|
}
|
|
1085
|
+
function hasSameLogicalBodyRows(nextIds) {
|
|
1086
|
+
if (logicalBodyRowIds === nextIds)
|
|
1087
|
+
return true;
|
|
1088
|
+
if (logicalBodyRowIds === null || nextIds === null)
|
|
1089
|
+
return false;
|
|
1090
|
+
if (logicalBodyRowIds.length !== nextIds.length)
|
|
1091
|
+
return false;
|
|
1092
|
+
for (let index = 0; index < logicalBodyRowIds.length; index += 1) {
|
|
1093
|
+
if (logicalBodyRowIds[index] !== nextIds[index])
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
return true;
|
|
1097
|
+
}
|
|
1098
|
+
function setLogicalBodyRows(ids) {
|
|
1099
|
+
const nextIds = ids ? [...ids] : null;
|
|
1100
|
+
if (hasSameLogicalBodyRows(nextIds))
|
|
1101
|
+
return;
|
|
1102
|
+
logicalBodyRowIds = nextIds;
|
|
1103
|
+
notifyLayout();
|
|
1104
|
+
notifySelection();
|
|
1105
|
+
}
|
|
1069
1106
|
function unregisterRow(token) {
|
|
1070
1107
|
const row = rows.get(token);
|
|
1071
1108
|
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
@@ -1107,7 +1144,7 @@ export function createTableContext(options = {}) {
|
|
|
1107
1144
|
return;
|
|
1108
1145
|
const optimisticHasSelectableRows = selectionMode === 'multiple' || selectedKeys.size > 0;
|
|
1109
1146
|
bodyRowsInitialized = true;
|
|
1110
|
-
const actualHasSelectableRows =
|
|
1147
|
+
const actualHasSelectableRows = hasSelectableRows();
|
|
1111
1148
|
if (optimisticHasSelectableRows !== actualHasSelectableRows) {
|
|
1112
1149
|
notifySelection();
|
|
1113
1150
|
}
|
|
@@ -1115,6 +1152,9 @@ export function createTableContext(options = {}) {
|
|
|
1115
1152
|
function getBodyRowCount() {
|
|
1116
1153
|
return getOrderedRowTokens('body').length;
|
|
1117
1154
|
}
|
|
1155
|
+
function getLogicalBodyRowCount() {
|
|
1156
|
+
return logicalBodyRowIds?.length ?? getBodyRowCount();
|
|
1157
|
+
}
|
|
1118
1158
|
function getHeaderRowCount() {
|
|
1119
1159
|
return getOrderedRowTokens('header').length;
|
|
1120
1160
|
}
|
|
@@ -1143,6 +1183,9 @@ export function createTableContext(options = {}) {
|
|
|
1143
1183
|
return sorted;
|
|
1144
1184
|
}
|
|
1145
1185
|
function getOrderedSelectableRowIds() {
|
|
1186
|
+
if (logicalBodyRowIds) {
|
|
1187
|
+
return logicalBodyRowIds.filter((id) => !isRowSelectionDisabled(id));
|
|
1188
|
+
}
|
|
1146
1189
|
const rowIds = [];
|
|
1147
1190
|
for (const token of getOrderedRowTokens('body')) {
|
|
1148
1191
|
const row = rows.get(token);
|
|
@@ -1213,6 +1256,9 @@ export function createTableContext(options = {}) {
|
|
|
1213
1256
|
if (!bodyRowsInitialized) {
|
|
1214
1257
|
return selectionMode === 'multiple' || selectedKeys.size > 0;
|
|
1215
1258
|
}
|
|
1259
|
+
if (logicalBodyRowIds) {
|
|
1260
|
+
return getOrderedSelectableRowIds().length > 0 || selectedKeys.size > 0;
|
|
1261
|
+
}
|
|
1216
1262
|
return selectableBodyRowCount > 0 || selectedKeys.size > 0;
|
|
1217
1263
|
}
|
|
1218
1264
|
function isRowFocused(token) {
|
|
@@ -1811,13 +1857,7 @@ export function createTableContext(options = {}) {
|
|
|
1811
1857
|
function selectAllRows() {
|
|
1812
1858
|
if (selectionMode !== 'multiple')
|
|
1813
1859
|
return;
|
|
1814
|
-
const next = new Set();
|
|
1815
|
-
for (const token of getOrderedRowTokens('body')) {
|
|
1816
|
-
const row = rows.get(token);
|
|
1817
|
-
if (!row?.id || isRowSelectionDisabled(row.id, row.disabled))
|
|
1818
|
-
continue;
|
|
1819
|
-
next.add(row.id);
|
|
1820
|
-
}
|
|
1860
|
+
const next = new Set(getOrderedSelectableRowIds());
|
|
1821
1861
|
setSelectedKeys(next, next.values().next().value ?? null);
|
|
1822
1862
|
emitSelectionChange();
|
|
1823
1863
|
}
|
|
@@ -1992,9 +2032,11 @@ export function createTableContext(options = {}) {
|
|
|
1992
2032
|
hasResizableColumns,
|
|
1993
2033
|
registerRow,
|
|
1994
2034
|
unregisterRow,
|
|
2035
|
+
setLogicalBodyRows,
|
|
1995
2036
|
markBodyRowsInitialized,
|
|
1996
2037
|
getHeaderRowCount,
|
|
1997
2038
|
getBodyRowCount,
|
|
2039
|
+
getLogicalBodyRowCount,
|
|
1998
2040
|
isRowSelected,
|
|
1999
2041
|
isRowFocused,
|
|
2000
2042
|
isRowFocusTarget,
|
|
@@ -159,7 +159,7 @@
|
|
|
159
159
|
});
|
|
160
160
|
const ariaRowCount = $derived.by(() => {
|
|
161
161
|
void $layoutVersion;
|
|
162
|
-
const rowCount = ctx.getHeaderRowCount() + ctx.
|
|
162
|
+
const rowCount = ctx.getHeaderRowCount() + ctx.getLogicalBodyRowCount();
|
|
163
163
|
return rowCount > 0 ? rowCount : undefined;
|
|
164
164
|
});
|
|
165
165
|
|
|
@@ -313,27 +313,35 @@
|
|
|
313
313
|
if (!tableElement) return;
|
|
314
314
|
|
|
315
315
|
const resizeTarget = tableElement.parentElement ?? tableElement;
|
|
316
|
-
|
|
317
|
-
|
|
316
|
+
let refreshFrame = 0;
|
|
317
|
+
const scheduleMeasuredLayoutRefresh = () => {
|
|
318
|
+
if (refreshFrame !== 0) return;
|
|
319
|
+
refreshFrame = window.requestAnimationFrame(() => {
|
|
320
|
+
refreshFrame = 0;
|
|
321
|
+
ctx.refreshMeasuredLayout();
|
|
322
|
+
});
|
|
318
323
|
};
|
|
319
324
|
|
|
320
|
-
|
|
325
|
+
scheduleMeasuredLayoutRefresh();
|
|
321
326
|
|
|
322
|
-
window.addEventListener('resize',
|
|
323
|
-
window.visualViewport?.addEventListener('resize',
|
|
327
|
+
window.addEventListener('resize', scheduleMeasuredLayoutRefresh);
|
|
328
|
+
window.visualViewport?.addEventListener('resize', scheduleMeasuredLayoutRefresh);
|
|
324
329
|
|
|
325
330
|
const resizeObserver =
|
|
326
331
|
typeof ResizeObserver !== 'undefined'
|
|
327
332
|
? new ResizeObserver(() => {
|
|
328
|
-
|
|
333
|
+
scheduleMeasuredLayoutRefresh();
|
|
329
334
|
})
|
|
330
335
|
: null;
|
|
331
336
|
|
|
332
337
|
resizeObserver?.observe(resizeTarget);
|
|
333
338
|
|
|
334
339
|
return () => {
|
|
335
|
-
|
|
336
|
-
|
|
340
|
+
if (refreshFrame !== 0) {
|
|
341
|
+
window.cancelAnimationFrame(refreshFrame);
|
|
342
|
+
}
|
|
343
|
+
window.removeEventListener('resize', scheduleMeasuredLayoutRefresh);
|
|
344
|
+
window.visualViewport?.removeEventListener('resize', scheduleMeasuredLayoutRefresh);
|
|
337
345
|
resizeObserver?.disconnect();
|
|
338
346
|
};
|
|
339
347
|
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy, untrack } from 'svelte';
|
|
3
|
+
import {
|
|
4
|
+
useTableSsrWrapperRegistry,
|
|
5
|
+
type TableSsrWrapperColumn
|
|
6
|
+
} from './table-ssr-wrapper-context';
|
|
7
|
+
|
|
8
|
+
let { id, header }: TableSsrWrapperColumn = $props();
|
|
9
|
+
|
|
10
|
+
const registry = useTableSsrWrapperRegistry();
|
|
11
|
+
|
|
12
|
+
function getToken() {
|
|
13
|
+
return `ssr-column-${id}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let registeredToken = getToken();
|
|
17
|
+
|
|
18
|
+
function getColumn() {
|
|
19
|
+
return { id, header };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function syncRegistration() {
|
|
23
|
+
const nextToken = getToken();
|
|
24
|
+
|
|
25
|
+
if (nextToken !== registeredToken) {
|
|
26
|
+
registry.removeColumn(registeredToken);
|
|
27
|
+
registeredToken = nextToken;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
registry.upsertColumn(registeredToken, getColumn());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
untrack(() => {
|
|
34
|
+
syncRegistration();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
$effect(() => {
|
|
38
|
+
untrack(() => {
|
|
39
|
+
syncRegistration();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
onDestroy(() => {
|
|
44
|
+
registry.removeColumn(registeredToken);
|
|
45
|
+
});
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<span hidden aria-hidden="true" data-ssr-wrapper-column={id}></span>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type TableSsrWrapperColumn = {
|
|
2
|
+
id: string;
|
|
3
|
+
header: string;
|
|
4
|
+
};
|
|
5
|
+
type TableSsrWrapperRegistry = {
|
|
6
|
+
upsertColumn: (token: string, column: TableSsrWrapperColumn) => void;
|
|
7
|
+
removeColumn: (token: string) => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function setTableSsrWrapperRegistry(registry: TableSsrWrapperRegistry): TableSsrWrapperRegistry;
|
|
10
|
+
export declare function useTableSsrWrapperRegistry(): TableSsrWrapperRegistry;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const TABLE_SSR_WRAPPER_KEY = Symbol('table-ssr-wrapper');
|
|
3
|
+
export function setTableSsrWrapperRegistry(registry) {
|
|
4
|
+
setContext(TABLE_SSR_WRAPPER_KEY, registry);
|
|
5
|
+
return registry;
|
|
6
|
+
}
|
|
7
|
+
export function useTableSsrWrapperRegistry() {
|
|
8
|
+
const registry = getContext(TABLE_SSR_WRAPPER_KEY);
|
|
9
|
+
if (!registry) {
|
|
10
|
+
throw new Error('Table SSR wrapper registry is not available.');
|
|
11
|
+
}
|
|
12
|
+
return registry;
|
|
13
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import TableSsrWrapperColumn from './table-ssr-wrapper-column.svelte';
|
|
3
|
+
import {
|
|
4
|
+
setTableSsrWrapperRegistry,
|
|
5
|
+
type TableSsrWrapperColumn as RegisteredColumn
|
|
6
|
+
} from './table-ssr-wrapper-context';
|
|
7
|
+
|
|
8
|
+
type DemoRow = {
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
group: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const rows: DemoRow[] = [{ id: 'danilo', email: 'danilo@example.com', group: 'Developer' }];
|
|
15
|
+
let registeredColumns = $state<Array<{ token: string; column: RegisteredColumn }>>([]);
|
|
16
|
+
|
|
17
|
+
setTableSsrWrapperRegistry({
|
|
18
|
+
upsertColumn(token, column) {
|
|
19
|
+
const index = registeredColumns.findIndex((entry) => entry.token === token);
|
|
20
|
+
|
|
21
|
+
if (index === -1) {
|
|
22
|
+
registeredColumns = [...registeredColumns, { token, column }];
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
registeredColumns = registeredColumns.map((entry) =>
|
|
27
|
+
entry.token === token ? { token, column } : entry
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
removeColumn(token) {
|
|
31
|
+
registeredColumns = registeredColumns.filter((entry) => entry.token !== token);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
function getResolvedColumns() {
|
|
36
|
+
return registeredColumns.map((entry) => entry.column);
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<TableSsrWrapperColumn id="email" header="Email" />
|
|
41
|
+
<TableSsrWrapperColumn id="group" header="Group" />
|
|
42
|
+
|
|
43
|
+
<div data-testid="ssr-column-count">{(() => getResolvedColumns().length)()}</div>
|
|
44
|
+
|
|
45
|
+
<div data-testid="ssr-headers">
|
|
46
|
+
{#each (() => getResolvedColumns())() as column (column.id)}
|
|
47
|
+
<span>{column.header}</span>
|
|
48
|
+
{/each}
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div data-testid="ssr-body-cells">
|
|
52
|
+
{#each rows as row (row.id)}
|
|
53
|
+
{#each (() => getResolvedColumns())() as column (column.id)}
|
|
54
|
+
<span>{row[column.id as keyof DemoRow]}</span>
|
|
55
|
+
{/each}
|
|
56
|
+
{/each}
|
|
57
|
+
</div>
|
package/dist/table/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
import type { TableColumnWidth, TableContext, TableDisabledBehavior, TableRowActionHandler, TableSelectionBehavior, TableSelectionKey, TableSelectionMode, TableSortDirection, TableSortDescriptor } from './root/context.js';
|
|
3
|
+
import type { TableColumnWidth, TableContext, TableDisabledBehavior, TableRowActionHandler, TableRowItem, TableSelectionBehavior, TableSelectionKey, TableSelectionMode, TableSortDirection, TableSortDescriptor } from './root/context.js';
|
|
4
4
|
export type TableColumnProps = {
|
|
5
5
|
id: string;
|
|
6
6
|
isRowHeader?: boolean;
|
|
@@ -15,10 +15,26 @@ export type TableHeaderProps = Omit<HTMLAttributes<HTMLTableSectionElement>, 'ch
|
|
|
15
15
|
children?: Snippet;
|
|
16
16
|
class?: string;
|
|
17
17
|
};
|
|
18
|
-
export type
|
|
19
|
-
|
|
18
|
+
export type TableBodyVirtualizer = {
|
|
19
|
+
rowHeight: number;
|
|
20
|
+
overscan?: number;
|
|
21
|
+
};
|
|
22
|
+
type TableBodyBaseProps = Omit<HTMLAttributes<HTMLTableSectionElement>, 'children'> & {
|
|
20
23
|
class?: string;
|
|
21
24
|
};
|
|
25
|
+
export type TableBodyManualProps = TableBodyBaseProps & {
|
|
26
|
+
items?: undefined;
|
|
27
|
+
virtualizer?: undefined;
|
|
28
|
+
children?: Snippet;
|
|
29
|
+
empty?: undefined;
|
|
30
|
+
};
|
|
31
|
+
export type TableBodyItemsProps<T extends TableRowItem = TableRowItem> = TableBodyBaseProps & {
|
|
32
|
+
items: readonly T[];
|
|
33
|
+
virtualizer?: TableBodyVirtualizer;
|
|
34
|
+
children?: Snippet<[T]>;
|
|
35
|
+
empty?: Snippet;
|
|
36
|
+
};
|
|
37
|
+
export type TableBodyProps<T extends TableRowItem = TableRowItem> = TableBodyManualProps | TableBodyItemsProps<T>;
|
|
22
38
|
export type TableFooterProps = Omit<HTMLAttributes<HTMLTableSectionElement>, 'children'> & {
|
|
23
39
|
children?: Snippet;
|
|
24
40
|
class?: string;
|
|
@@ -93,3 +109,4 @@ export type TableCheckboxIndicatorProps = Omit<HTMLAttributes<HTMLSpanElement>,
|
|
|
93
109
|
children?: Snippet;
|
|
94
110
|
class?: string;
|
|
95
111
|
};
|
|
112
|
+
export {};
|