@human-kit/svelte-components 1.0.0-alpha.17 → 1.0.0-alpha.18
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/checkbox/root/checkbox-root.svelte +9 -4
- package/dist/checkbox/root/checkbox-root.svelte.d.ts +4 -1
- package/dist/table/cell/table-cell.svelte +1 -1
- package/dist/table/checkbox/table-checkbox.svelte +3 -33
- package/dist/table/column-header-cell/table-column-header-cell.svelte +1 -1
- package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte +87 -0
- package/dist/table/column-resizer/table-column-resizer-sandbox-overflow-test.svelte.d.ts +3 -0
- package/dist/table/root/context.d.ts +11 -0
- package/dist/table/root/context.js +163 -25
- package/dist/table/root/table-root.svelte +27 -0
- package/dist/table/row/table-row.svelte +20 -60
- package/package.json +1 -1
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
| 'class'
|
|
15
15
|
| 'id'
|
|
16
16
|
| 'role'
|
|
17
|
-
| 'tabindex'
|
|
18
17
|
| 'aria-checked'
|
|
19
18
|
| 'aria-disabled'
|
|
20
19
|
| 'aria-readonly'
|
|
@@ -40,8 +39,11 @@
|
|
|
40
39
|
class?: string;
|
|
41
40
|
'aria-label'?: string;
|
|
42
41
|
'aria-labelledby'?: string;
|
|
42
|
+
tabindex?: number;
|
|
43
43
|
onclick?: HTMLAttributes<HTMLSpanElement>['onclick'];
|
|
44
44
|
onkeydown?: HTMLAttributes<HTMLSpanElement>['onkeydown'];
|
|
45
|
+
onfocus?: HTMLAttributes<HTMLSpanElement>['onfocus'];
|
|
46
|
+
onmousedown?: HTMLAttributes<HTMLSpanElement>['onmousedown'];
|
|
45
47
|
};
|
|
46
48
|
|
|
47
49
|
function composeEventHandlers<TEvent extends Event>(
|
|
@@ -85,8 +87,11 @@
|
|
|
85
87
|
class: className = '',
|
|
86
88
|
'aria-label': ariaLabel,
|
|
87
89
|
'aria-labelledby': ariaLabelledby,
|
|
90
|
+
tabindex,
|
|
88
91
|
onclick: onClickExternal,
|
|
89
92
|
onkeydown: onKeyDownExternal,
|
|
93
|
+
onfocus: onFocusExternal,
|
|
94
|
+
onmousedown: onMouseDownExternal,
|
|
90
95
|
...restProps
|
|
91
96
|
}: CheckboxRootProps = $props();
|
|
92
97
|
|
|
@@ -329,7 +334,7 @@
|
|
|
329
334
|
bind:this={rootRef}
|
|
330
335
|
id={rootId}
|
|
331
336
|
role="checkbox"
|
|
332
|
-
tabindex={isDisabled ? undefined : 0}
|
|
337
|
+
tabindex={isDisabled ? undefined : (tabindex ?? 0)}
|
|
333
338
|
aria-checked={currentIndeterminate ? 'mixed' : currentChecked ? 'true' : 'false'}
|
|
334
339
|
aria-disabled={isDisabled || undefined}
|
|
335
340
|
aria-readonly={isReadOnly || undefined}
|
|
@@ -350,8 +355,8 @@
|
|
|
350
355
|
onkeydown={composeEventHandlers(handleKeyDown, onKeyDownExternal ?? undefined)}
|
|
351
356
|
onkeyup={handleKeyUp}
|
|
352
357
|
onpointerdown={handlePointerDown}
|
|
353
|
-
onmousedown={handlePointerDown}
|
|
354
|
-
onfocus={handleFocus}
|
|
358
|
+
onmousedown={composeEventHandlers(onMouseDownExternal ?? undefined, handlePointerDown)}
|
|
359
|
+
onfocus={composeEventHandlers(handleFocus, onFocusExternal ?? undefined)}
|
|
355
360
|
onblur={handleBlur}
|
|
356
361
|
class={cn(
|
|
357
362
|
'relative inline-flex shrink-0 items-center justify-center align-middle outline-none',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Snippet } from 'svelte';
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
type CheckboxRootProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class' | 'id' | 'role' | '
|
|
3
|
+
type CheckboxRootProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'class' | 'id' | 'role' | 'aria-checked' | 'aria-disabled' | 'aria-readonly' | 'aria-required' | 'onclick' | 'onkeydown' | 'value'> & {
|
|
4
4
|
id?: string;
|
|
5
5
|
element?: HTMLSpanElement | null;
|
|
6
6
|
name?: string;
|
|
@@ -18,8 +18,11 @@ type CheckboxRootProps = Omit<HTMLAttributes<HTMLSpanElement>, 'children' | 'cla
|
|
|
18
18
|
class?: string;
|
|
19
19
|
'aria-label'?: string;
|
|
20
20
|
'aria-labelledby'?: string;
|
|
21
|
+
tabindex?: number;
|
|
21
22
|
onclick?: HTMLAttributes<HTMLSpanElement>['onclick'];
|
|
22
23
|
onkeydown?: HTMLAttributes<HTMLSpanElement>['onkeydown'];
|
|
24
|
+
onfocus?: HTMLAttributes<HTMLSpanElement>['onfocus'];
|
|
25
|
+
onmousedown?: HTMLAttributes<HTMLSpanElement>['onmousedown'];
|
|
23
26
|
};
|
|
24
27
|
declare const CheckboxRoot: import("svelte").Component<CheckboxRootProps, {}, "element" | "isChecked" | "isIndeterminate">;
|
|
25
28
|
type CheckboxRoot = ReturnType<typeof CheckboxRoot>;
|
|
@@ -251,7 +251,7 @@
|
|
|
251
251
|
style:width={columnWidthStyle}
|
|
252
252
|
style:min-width={columnMinWidth !== undefined ? `${columnMinWidth}px` : undefined}
|
|
253
253
|
style:max-width={columnMaxWidth !== undefined ? `${columnMaxWidth}px` : undefined}
|
|
254
|
-
style:display={isColumnHidden ? 'none' :
|
|
254
|
+
style:display={isColumnHidden ? 'none' : 'table-cell'}
|
|
255
255
|
onfocus={row.section === 'body' ? handleFocus : undefined}
|
|
256
256
|
onclick={row.section === 'body' ? handleClick : undefined}
|
|
257
257
|
ondblclick={row.section === 'body' ? handleDoubleClick : undefined}
|
|
@@ -96,39 +96,6 @@
|
|
|
96
96
|
};
|
|
97
97
|
});
|
|
98
98
|
|
|
99
|
-
$effect(() => {
|
|
100
|
-
const checkboxElement = getCheckboxRootElement();
|
|
101
|
-
if (!checkboxElement) return;
|
|
102
|
-
|
|
103
|
-
if (!isVisible || isDisabled || tabIndex === undefined) {
|
|
104
|
-
checkboxElement.removeAttribute('tabindex');
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
checkboxElement.tabIndex = tabIndex;
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
$effect(() => {
|
|
112
|
-
const checkboxElement = getCheckboxRootElement();
|
|
113
|
-
if (!checkboxElement) return;
|
|
114
|
-
|
|
115
|
-
const handleElementFocus = (event: FocusEvent) => {
|
|
116
|
-
handleFocusIn(event);
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const handleElementMouseDown = (event: MouseEvent) => {
|
|
120
|
-
handleMouseDown(event);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
checkboxElement.addEventListener('focus', handleElementFocus);
|
|
124
|
-
checkboxElement.addEventListener('mousedown', handleElementMouseDown);
|
|
125
|
-
|
|
126
|
-
return () => {
|
|
127
|
-
checkboxElement.removeEventListener('focus', handleElementFocus);
|
|
128
|
-
checkboxElement.removeEventListener('mousedown', handleElementMouseDown);
|
|
129
|
-
};
|
|
130
|
-
});
|
|
131
|
-
|
|
132
99
|
function applySelection(nextChecked: boolean) {
|
|
133
100
|
if (isDisabled) return;
|
|
134
101
|
|
|
@@ -255,8 +222,11 @@
|
|
|
255
222
|
aria-label={accessibleLabel}
|
|
256
223
|
aria-labelledby={ariaLabelledby}
|
|
257
224
|
data-table-checkbox="true"
|
|
225
|
+
tabindex={tabIndex}
|
|
258
226
|
onclick={handleClick}
|
|
259
227
|
onkeydown={handleKeyDown}
|
|
228
|
+
onfocus={handleFocusIn}
|
|
229
|
+
onmousedown={handleMouseDown}
|
|
260
230
|
class={className}
|
|
261
231
|
{...restProps}
|
|
262
232
|
>
|
|
@@ -262,7 +262,7 @@
|
|
|
262
262
|
style:width={columnWidthStyle}
|
|
263
263
|
style:min-width={columnMinWidth !== undefined ? `${columnMinWidth}px` : undefined}
|
|
264
264
|
style:max-width={columnMaxWidth !== undefined ? `${columnMaxWidth}px` : undefined}
|
|
265
|
-
style:display={isHidden ? 'none' :
|
|
265
|
+
style:display={isHidden ? 'none' : 'table-cell'}
|
|
266
266
|
onfocusin={handleFocusIn}
|
|
267
267
|
onfocusout={handleFocusOut}
|
|
268
268
|
onfocus={handleFocus}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Table } from '../index';
|
|
3
|
+
import type { TableColumnWidth } from '../root/context';
|
|
4
|
+
|
|
5
|
+
let currentColumnWidths = $state<Map<string, TableColumnWidth> | undefined>(undefined);
|
|
6
|
+
const rows = Array.from({ length: 24 }, (_, index) => ({
|
|
7
|
+
id: `row-${index + 1}`,
|
|
8
|
+
request: `PR-${String(index + 1).padStart(4, '0')}`,
|
|
9
|
+
requester: ['Ana Gomez', 'Lucas Perez', 'Mara Silva', 'Juan Torres'][index % 4],
|
|
10
|
+
area: ['Production', 'Logistics', 'Maintenance', 'Quality'][index % 4],
|
|
11
|
+
status: ['Pending', 'Review', 'Approved'][index % 3],
|
|
12
|
+
priority: ['Low', 'Medium', 'High'][index % 3],
|
|
13
|
+
total: 850 + index * 137
|
|
14
|
+
}));
|
|
15
|
+
</script>
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
data-testid="sandbox-overflow-container"
|
|
19
|
+
style="width: 920px; max-height: 320px; overflow: auto;"
|
|
20
|
+
>
|
|
21
|
+
<Table.Root
|
|
22
|
+
aria-label="Sandbox overflow table"
|
|
23
|
+
selectionMode="multiple"
|
|
24
|
+
bind:columnWidths={currentColumnWidths}
|
|
25
|
+
class="min-w-full border-collapse text-left"
|
|
26
|
+
>
|
|
27
|
+
<Table.Header>
|
|
28
|
+
<Table.Row>
|
|
29
|
+
<Table.Column id="selection" textValue="Selection" width={44}>
|
|
30
|
+
<Table.ColumnHeaderCell data-testid="sandbox-selection-header-cell">
|
|
31
|
+
<Table.Checkbox />
|
|
32
|
+
</Table.ColumnHeaderCell>
|
|
33
|
+
</Table.Column>
|
|
34
|
+
<Table.Column id="request" textValue="Request" isRowHeader defaultWidth={350}>
|
|
35
|
+
<Table.ColumnHeaderCell data-testid="sandbox-request-header-cell">
|
|
36
|
+
<div class="flex items-center justify-between gap-3">
|
|
37
|
+
<span>Request</span>
|
|
38
|
+
<Table.ColumnResizer
|
|
39
|
+
data-testid="sandbox-request-resizer"
|
|
40
|
+
class="inline-flex w-3 cursor-col-resize justify-center"
|
|
41
|
+
/>
|
|
42
|
+
</div>
|
|
43
|
+
</Table.ColumnHeaderCell>
|
|
44
|
+
</Table.Column>
|
|
45
|
+
<Table.Column id="requester" textValue="Requester">
|
|
46
|
+
<Table.ColumnHeaderCell>Requester</Table.ColumnHeaderCell>
|
|
47
|
+
</Table.Column>
|
|
48
|
+
<Table.Column id="area" textValue="Area">
|
|
49
|
+
<Table.ColumnHeaderCell>Area</Table.ColumnHeaderCell>
|
|
50
|
+
</Table.Column>
|
|
51
|
+
<Table.Column id="status" textValue="Status">
|
|
52
|
+
<Table.ColumnHeaderCell>Status</Table.ColumnHeaderCell>
|
|
53
|
+
</Table.Column>
|
|
54
|
+
<Table.Column id="priority" textValue="Priority">
|
|
55
|
+
<Table.ColumnHeaderCell>Priority</Table.ColumnHeaderCell>
|
|
56
|
+
</Table.Column>
|
|
57
|
+
<Table.Column id="total" textValue="Total">
|
|
58
|
+
<Table.ColumnHeaderCell data-testid="sandbox-total-header-cell">
|
|
59
|
+
<div class="flex w-full justify-end">Total</div>
|
|
60
|
+
</Table.ColumnHeaderCell>
|
|
61
|
+
</Table.Column>
|
|
62
|
+
</Table.Row>
|
|
63
|
+
</Table.Header>
|
|
64
|
+
|
|
65
|
+
<Table.Body>
|
|
66
|
+
{#each rows as row (row.id)}
|
|
67
|
+
<Table.Row id={row.id}>
|
|
68
|
+
<Table.Cell>
|
|
69
|
+
<Table.Checkbox />
|
|
70
|
+
</Table.Cell>
|
|
71
|
+
<Table.Cell>{row.request}</Table.Cell>
|
|
72
|
+
<Table.Cell>{row.requester}</Table.Cell>
|
|
73
|
+
<Table.Cell>{row.area}</Table.Cell>
|
|
74
|
+
<Table.Cell>{row.status}</Table.Cell>
|
|
75
|
+
<Table.Cell>{row.priority}</Table.Cell>
|
|
76
|
+
<Table.Cell>
|
|
77
|
+
<div class="flex w-full justify-end">${row.total.toLocaleString()}</div>
|
|
78
|
+
</Table.Cell>
|
|
79
|
+
</Table.Row>
|
|
80
|
+
{/each}
|
|
81
|
+
</Table.Body>
|
|
82
|
+
</Table.Root>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<output data-testid="sandbox-overflow-widths"
|
|
86
|
+
>{JSON.stringify(Object.fromEntries(currentColumnWidths ?? new Map()))}</output
|
|
87
|
+
>
|
|
@@ -19,6 +19,16 @@ export type TableSortDescriptor = {
|
|
|
19
19
|
};
|
|
20
20
|
export type TableColumnWidth = number | `${number}px` | `${number}%` | `${number}fr`;
|
|
21
21
|
export declare const DEFAULT_TABLE_COLUMN_MIN_WIDTH = 60;
|
|
22
|
+
export type RoundedWidthDistributionEntry = {
|
|
23
|
+
columnId: string;
|
|
24
|
+
index: number;
|
|
25
|
+
exactWidth: number;
|
|
26
|
+
width: number;
|
|
27
|
+
minWidth: number;
|
|
28
|
+
maxWidth?: number;
|
|
29
|
+
remainder: number;
|
|
30
|
+
};
|
|
31
|
+
export declare function distributeRoundedWidths(entries: RoundedWidthDistributionEntry[], targetTotal: number): RoundedWidthDistributionEntry[];
|
|
22
32
|
export type TableGridCoord = {
|
|
23
33
|
row: number;
|
|
24
34
|
col: number;
|
|
@@ -109,6 +119,7 @@ export type TableContext = {
|
|
|
109
119
|
getVisibleColumnWidths: () => Map<string, TableColumnWidth>;
|
|
110
120
|
getResolvedVisibleColumnWidths: () => Map<string, number>;
|
|
111
121
|
hasRelativeVisibleColumnWidths: () => boolean;
|
|
122
|
+
refreshMeasuredLayout: () => void;
|
|
112
123
|
setColumnWidths: (widths?: Iterable<readonly [string, TableColumnWidth]>) => void;
|
|
113
124
|
setColumnWidth: (columnId: string, width: number) => void;
|
|
114
125
|
setHiddenColumns: (columnIds?: Iterable<string>) => void;
|
|
@@ -7,6 +7,45 @@ const TABLE_COLUMN_KEY = Symbol('table-column');
|
|
|
7
7
|
const TABLE_CELL_KEY = Symbol('table-cell');
|
|
8
8
|
const IS_BROWSER = typeof window !== 'undefined';
|
|
9
9
|
export const DEFAULT_TABLE_COLUMN_MIN_WIDTH = 60;
|
|
10
|
+
export function distributeRoundedWidths(entries, targetTotal) {
|
|
11
|
+
let delta = targetTotal - entries.reduce((total, entry) => total + entry.width, 0);
|
|
12
|
+
if (delta === 0)
|
|
13
|
+
return entries;
|
|
14
|
+
const prioritizedEntries = [...entries].sort((left, right) => {
|
|
15
|
+
if (delta > 0) {
|
|
16
|
+
if (right.remainder !== left.remainder)
|
|
17
|
+
return right.remainder - left.remainder;
|
|
18
|
+
return right.index - left.index;
|
|
19
|
+
}
|
|
20
|
+
if (left.remainder !== right.remainder)
|
|
21
|
+
return left.remainder - right.remainder;
|
|
22
|
+
return left.index - right.index;
|
|
23
|
+
});
|
|
24
|
+
while (delta !== 0) {
|
|
25
|
+
let updated = false;
|
|
26
|
+
for (const entry of prioritizedEntries) {
|
|
27
|
+
if (delta > 0) {
|
|
28
|
+
if (entry.maxWidth !== undefined && entry.width >= entry.maxWidth)
|
|
29
|
+
continue;
|
|
30
|
+
entry.width += 1;
|
|
31
|
+
delta -= 1;
|
|
32
|
+
updated = true;
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (entry.width <= entry.minWidth)
|
|
36
|
+
continue;
|
|
37
|
+
entry.width -= 1;
|
|
38
|
+
delta += 1;
|
|
39
|
+
updated = true;
|
|
40
|
+
}
|
|
41
|
+
if (delta === 0)
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (!updated)
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
return entries;
|
|
48
|
+
}
|
|
10
49
|
export function createTableContext(options = {}) {
|
|
11
50
|
let selectionMode = options.selectionMode ?? 'none';
|
|
12
51
|
let selectionBehavior = options.selectionBehavior ?? 'toggle';
|
|
@@ -34,6 +73,7 @@ export function createTableContext(options = {}) {
|
|
|
34
73
|
const headerRowOrder = [];
|
|
35
74
|
const bodyRowOrder = [];
|
|
36
75
|
let bodyRowsInitialized = false;
|
|
76
|
+
let selectableBodyRowCount = 0;
|
|
37
77
|
const cells = new Map();
|
|
38
78
|
const cellOrder = [];
|
|
39
79
|
let orderedRowTokensCache = {
|
|
@@ -71,6 +111,7 @@ export function createTableContext(options = {}) {
|
|
|
71
111
|
rowsWithCellsCache = null;
|
|
72
112
|
}
|
|
73
113
|
let layoutNotifyScheduled = false;
|
|
114
|
+
let selectionNotifyScheduled = false;
|
|
74
115
|
let widthNotifyScheduled = false;
|
|
75
116
|
function syncResizerLayoutReady(nextReady) {
|
|
76
117
|
if (resizerLayoutReady === nextReady)
|
|
@@ -91,7 +132,13 @@ export function createTableContext(options = {}) {
|
|
|
91
132
|
}
|
|
92
133
|
}
|
|
93
134
|
function notifySelection() {
|
|
94
|
-
|
|
135
|
+
if (!selectionNotifyScheduled) {
|
|
136
|
+
selectionNotifyScheduled = true;
|
|
137
|
+
queueMicrotask(() => {
|
|
138
|
+
selectionNotifyScheduled = false;
|
|
139
|
+
selectionVersion.update((value) => value + 1);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
95
142
|
}
|
|
96
143
|
function notifyFocus() {
|
|
97
144
|
focusVersion.update((value) => value + 1);
|
|
@@ -120,6 +167,9 @@ export function createTableContext(options = {}) {
|
|
|
120
167
|
widthVersion.update((value) => value + 1);
|
|
121
168
|
});
|
|
122
169
|
}
|
|
170
|
+
function refreshMeasuredLayout() {
|
|
171
|
+
notifyWidthImmediately();
|
|
172
|
+
}
|
|
123
173
|
function notifyResize() {
|
|
124
174
|
resizeVersion.update((value) => value + 1);
|
|
125
175
|
}
|
|
@@ -220,10 +270,20 @@ export function createTableContext(options = {}) {
|
|
|
220
270
|
}
|
|
221
271
|
return Math.round(width);
|
|
222
272
|
}
|
|
223
|
-
function
|
|
273
|
+
function getColumnWidthBounds(columnId) {
|
|
224
274
|
const registration = getColumnRegistrationById(columnId);
|
|
225
|
-
const
|
|
226
|
-
const
|
|
275
|
+
const fixedWidth = parseColumnWidth(registration?.width);
|
|
276
|
+
const minWidth = registration?.minWidth ??
|
|
277
|
+
(fixedWidth?.unit === 'px'
|
|
278
|
+
? Math.min(fixedWidth.value, DEFAULT_TABLE_COLUMN_MIN_WIDTH)
|
|
279
|
+
: DEFAULT_TABLE_COLUMN_MIN_WIDTH);
|
|
280
|
+
return {
|
|
281
|
+
minWidth,
|
|
282
|
+
maxWidth: registration?.maxWidth
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
function clampColumnWidth(columnId, width) {
|
|
286
|
+
const { minWidth, maxWidth } = getColumnWidthBounds(columnId);
|
|
227
287
|
let next = Math.round(width);
|
|
228
288
|
if (Number.isNaN(next) || !Number.isFinite(next)) {
|
|
229
289
|
next = minWidth;
|
|
@@ -234,6 +294,19 @@ export function createTableContext(options = {}) {
|
|
|
234
294
|
}
|
|
235
295
|
return next;
|
|
236
296
|
}
|
|
297
|
+
function resolveRelativeColumnWidthAllocations(entries, targetTotal) {
|
|
298
|
+
const allocations = entries.map((entry) => {
|
|
299
|
+
const { minWidth, maxWidth } = getColumnWidthBounds(entry.columnId);
|
|
300
|
+
return {
|
|
301
|
+
...entry,
|
|
302
|
+
width: clampColumnWidth(entry.columnId, entry.exactWidth),
|
|
303
|
+
minWidth,
|
|
304
|
+
maxWidth,
|
|
305
|
+
remainder: entry.exactWidth - Math.floor(entry.exactWidth)
|
|
306
|
+
};
|
|
307
|
+
});
|
|
308
|
+
return distributeRoundedWidths(allocations, targetTotal);
|
|
309
|
+
}
|
|
237
310
|
function hasResizableColumns() {
|
|
238
311
|
for (const column of columns.values()) {
|
|
239
312
|
if (isColumnResizable(column.id))
|
|
@@ -497,10 +570,12 @@ export function createTableContext(options = {}) {
|
|
|
497
570
|
return resolvedVisibleColumnWidthsCache;
|
|
498
571
|
const widths = new Map();
|
|
499
572
|
const flexibleColumns = [];
|
|
573
|
+
const relativeColumns = [];
|
|
500
574
|
const tableWidth = getMeasuredTableWidth();
|
|
501
|
-
let
|
|
575
|
+
let fixedWidthTotal = 0;
|
|
576
|
+
let exactRelativeWidthTotal = 0;
|
|
502
577
|
let totalFr = 0;
|
|
503
|
-
for (const token of getVisibleOrderedColumnTokens()) {
|
|
578
|
+
for (const [index, token] of getVisibleOrderedColumnTokens().entries()) {
|
|
504
579
|
const column = columns.get(token);
|
|
505
580
|
if (!column)
|
|
506
581
|
continue;
|
|
@@ -510,29 +585,34 @@ export function createTableContext(options = {}) {
|
|
|
510
585
|
if (parsed.unit === 'px') {
|
|
511
586
|
const nextWidth = clampColumnWidth(column.id, parsed.value);
|
|
512
587
|
widths.set(column.id, nextWidth);
|
|
513
|
-
|
|
514
|
-
remainingWidth -= nextWidth;
|
|
515
|
-
}
|
|
588
|
+
fixedWidthTotal += nextWidth;
|
|
516
589
|
continue;
|
|
517
590
|
}
|
|
518
591
|
if (parsed.unit === '%') {
|
|
519
592
|
if (tableWidth === undefined)
|
|
520
593
|
continue;
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
remainingWidth -= nextWidth;
|
|
525
|
-
}
|
|
594
|
+
const exactWidth = (tableWidth * parsed.value) / 100;
|
|
595
|
+
relativeColumns.push({ columnId: column.id, index, exactWidth });
|
|
596
|
+
exactRelativeWidthTotal += exactWidth;
|
|
526
597
|
continue;
|
|
527
598
|
}
|
|
528
|
-
flexibleColumns.push({ columnId: column.id, fr: parsed.value });
|
|
599
|
+
flexibleColumns.push({ columnId: column.id, fr: parsed.value, index });
|
|
529
600
|
totalFr += parsed.value;
|
|
530
601
|
}
|
|
531
602
|
if (tableWidth !== undefined && flexibleColumns.length > 0 && totalFr > 0) {
|
|
532
|
-
const distributableWidth = Math.max(
|
|
603
|
+
const distributableWidth = Math.max(tableWidth - fixedWidthTotal - exactRelativeWidthTotal, 0);
|
|
533
604
|
for (const entry of flexibleColumns) {
|
|
534
|
-
|
|
535
|
-
|
|
605
|
+
relativeColumns.push({
|
|
606
|
+
columnId: entry.columnId,
|
|
607
|
+
index: entry.index,
|
|
608
|
+
exactWidth: (distributableWidth * entry.fr) / totalFr
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (tableWidth !== undefined && relativeColumns.length > 0) {
|
|
613
|
+
const targetRelativeTotal = Math.max(tableWidth - fixedWidthTotal, 0);
|
|
614
|
+
for (const entry of resolveRelativeColumnWidthAllocations(relativeColumns, targetRelativeTotal)) {
|
|
615
|
+
widths.set(entry.columnId, entry.width);
|
|
536
616
|
}
|
|
537
617
|
}
|
|
538
618
|
resolvedVisibleColumnWidthsCache = widths;
|
|
@@ -546,6 +626,22 @@ export function createTableContext(options = {}) {
|
|
|
546
626
|
}
|
|
547
627
|
return Math.round(width);
|
|
548
628
|
}
|
|
629
|
+
function getFixedVisibleColumnWidthTotal() {
|
|
630
|
+
let total = 0;
|
|
631
|
+
for (const token of getVisibleOrderedColumnTokens()) {
|
|
632
|
+
const column = columns.get(token);
|
|
633
|
+
if (!column)
|
|
634
|
+
continue;
|
|
635
|
+
if (getFixedColumnWidthSpec(column.id) === undefined)
|
|
636
|
+
continue;
|
|
637
|
+
const measuredWidth = getMeasuredHeaderWidth(token);
|
|
638
|
+
const resolvedWidth = getColumnWidth(column.id) ?? measuredWidth;
|
|
639
|
+
if (resolvedWidth === undefined)
|
|
640
|
+
continue;
|
|
641
|
+
total += clampColumnWidth(column.id, resolvedWidth);
|
|
642
|
+
}
|
|
643
|
+
return total;
|
|
644
|
+
}
|
|
549
645
|
function getResizableRelativeTailColumnId(activeColumnId) {
|
|
550
646
|
return getVisibleOrderedColumnTokens()
|
|
551
647
|
.map((token) => columns.get(token))
|
|
@@ -567,6 +663,7 @@ export function createTableContext(options = {}) {
|
|
|
567
663
|
? normalizeColumnWidth(getEffectiveColumnWidthSpec(preservedFlexibleColumnId))
|
|
568
664
|
: undefined;
|
|
569
665
|
const measuredTableWidth = getMeasuredTableWidth();
|
|
666
|
+
const fixedVisibleColumnWidthTotal = getFixedVisibleColumnWidthTotal();
|
|
570
667
|
for (const token of getVisibleOrderedColumnTokens()) {
|
|
571
668
|
const column = columns.get(token);
|
|
572
669
|
if (!column)
|
|
@@ -606,7 +703,9 @@ export function createTableContext(options = {}) {
|
|
|
606
703
|
? preservedFlexibleEffectiveWidth
|
|
607
704
|
: undefined,
|
|
608
705
|
baselineWidths,
|
|
609
|
-
|
|
706
|
+
baselineAvailableTableWidth: measuredTableWidth !== undefined
|
|
707
|
+
? Math.max(measuredTableWidth - fixedVisibleColumnWidthTotal, 0)
|
|
708
|
+
: baselineTotalWidth,
|
|
610
709
|
baselineTotalWidth
|
|
611
710
|
};
|
|
612
711
|
options.onColumnWidthsChange?.(getColumnWidths());
|
|
@@ -655,7 +754,7 @@ export function createTableContext(options = {}) {
|
|
|
655
754
|
const baselineTailWidth = resizeSession.baselineWidths.get(resizeSession.flexibleTailColumnId);
|
|
656
755
|
if (baselineActiveWidth !== undefined && baselineTailWidth !== undefined) {
|
|
657
756
|
const widthDelta = nextWidth - baselineActiveWidth;
|
|
658
|
-
const overflowWidth = Math.max(resizeSession.baselineTotalWidth - resizeSession.
|
|
757
|
+
const overflowWidth = Math.max(resizeSession.baselineTotalWidth - resizeSession.baselineAvailableTableWidth, 0);
|
|
659
758
|
const tailTargetWidth = widthDelta >= 0
|
|
660
759
|
? baselineTailWidth - widthDelta
|
|
661
760
|
: baselineTailWidth + Math.max(-widthDelta - overflowWidth, 0);
|
|
@@ -892,6 +991,7 @@ export function createTableContext(options = {}) {
|
|
|
892
991
|
}
|
|
893
992
|
function registerRow(row) {
|
|
894
993
|
const existing = rows.get(row.token);
|
|
994
|
+
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
895
995
|
const targetOrder = row.section === 'header' ? headerRowOrder : row.section === 'body' ? bodyRowOrder : null;
|
|
896
996
|
const alreadyOrdered = targetOrder ? targetOrder.includes(row.token) : false;
|
|
897
997
|
const wasInHeader = headerRowOrder.includes(row.token);
|
|
@@ -907,16 +1007,29 @@ export function createTableContext(options = {}) {
|
|
|
907
1007
|
if (wasInBody) {
|
|
908
1008
|
bodyRowOrder.splice(bodyRowOrder.indexOf(row.token), 1);
|
|
909
1009
|
}
|
|
1010
|
+
const previousSelectableBodyRow = isSelectableBodyRow(existing);
|
|
910
1011
|
rows.set(row.token, row);
|
|
911
1012
|
if (targetOrder && !targetOrder.includes(row.token)) {
|
|
912
1013
|
targetOrder.push(row.token);
|
|
913
1014
|
}
|
|
914
|
-
|
|
1015
|
+
const nextSelectableBodyRow = isSelectableBodyRow(row);
|
|
1016
|
+
if (previousSelectableBodyRow !== nextSelectableBodyRow) {
|
|
1017
|
+
selectableBodyRowCount += nextSelectableBodyRow ? 1 : -1;
|
|
1018
|
+
}
|
|
1019
|
+
if ((selectedKeys.size > 0 && (existing?.section === 'body' || row.section === 'body')) ||
|
|
1020
|
+
(bodyRowsInitialized &&
|
|
1021
|
+
(previousSelectableBodyRowCount === 0) !== (selectableBodyRowCount === 0))) {
|
|
1022
|
+
notifySelection();
|
|
1023
|
+
}
|
|
915
1024
|
notifyLayout();
|
|
916
1025
|
}
|
|
917
1026
|
function unregisterRow(token) {
|
|
918
1027
|
const row = rows.get(token);
|
|
1028
|
+
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
919
1029
|
rows.delete(token);
|
|
1030
|
+
if (isSelectableBodyRow(row)) {
|
|
1031
|
+
selectableBodyRowCount = Math.max(0, selectableBodyRowCount - 1);
|
|
1032
|
+
}
|
|
920
1033
|
if (focusedRowTarget?.rowToken === token) {
|
|
921
1034
|
focusedRowTarget = null;
|
|
922
1035
|
notifyFocus();
|
|
@@ -939,7 +1052,11 @@ export function createTableContext(options = {}) {
|
|
|
939
1052
|
notifyFocus();
|
|
940
1053
|
}
|
|
941
1054
|
}
|
|
942
|
-
|
|
1055
|
+
if ((selectedKeys.size > 0 && row?.section === 'body') ||
|
|
1056
|
+
(bodyRowsInitialized &&
|
|
1057
|
+
(previousSelectableBodyRowCount === 0) !== (selectableBodyRowCount === 0))) {
|
|
1058
|
+
notifySelection();
|
|
1059
|
+
}
|
|
943
1060
|
notifyLayout();
|
|
944
1061
|
}
|
|
945
1062
|
function markBodyRowsInitialized() {
|
|
@@ -947,7 +1064,7 @@ export function createTableContext(options = {}) {
|
|
|
947
1064
|
return;
|
|
948
1065
|
const optimisticHasSelectableRows = selectionMode === 'multiple' || selectedKeys.size > 0;
|
|
949
1066
|
bodyRowsInitialized = true;
|
|
950
|
-
const actualHasSelectableRows =
|
|
1067
|
+
const actualHasSelectableRows = selectableBodyRowCount > 0 || selectedKeys.size > 0;
|
|
951
1068
|
if (optimisticHasSelectableRows !== actualHasSelectableRows) {
|
|
952
1069
|
notifySelection();
|
|
953
1070
|
}
|
|
@@ -1001,6 +1118,20 @@ export function createTableContext(options = {}) {
|
|
|
1001
1118
|
return false;
|
|
1002
1119
|
return disabledKeys.has(id);
|
|
1003
1120
|
}
|
|
1121
|
+
function isSelectableBodyRow(row) {
|
|
1122
|
+
return (row?.section === 'body' &&
|
|
1123
|
+
row.id !== undefined &&
|
|
1124
|
+
!isRowSelectionDisabled(row.id, row.disabled));
|
|
1125
|
+
}
|
|
1126
|
+
function recomputeSelectableBodyRowCount() {
|
|
1127
|
+
let nextSelectableBodyRowCount = 0;
|
|
1128
|
+
for (const token of bodyRowOrder) {
|
|
1129
|
+
if (isSelectableBodyRow(rows.get(token))) {
|
|
1130
|
+
nextSelectableBodyRowCount += 1;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
selectableBodyRowCount = nextSelectableBodyRowCount;
|
|
1134
|
+
}
|
|
1004
1135
|
function isRowDisabled(id, localDisabled = false) {
|
|
1005
1136
|
return disabledBehavior === 'all' && isRowSelectionDisabled(id, localDisabled);
|
|
1006
1137
|
}
|
|
@@ -1039,7 +1170,7 @@ export function createTableContext(options = {}) {
|
|
|
1039
1170
|
if (!bodyRowsInitialized) {
|
|
1040
1171
|
return selectionMode === 'multiple' || selectedKeys.size > 0;
|
|
1041
1172
|
}
|
|
1042
|
-
return
|
|
1173
|
+
return selectableBodyRowCount > 0 || selectedKeys.size > 0;
|
|
1043
1174
|
}
|
|
1044
1175
|
function isRowFocused(token) {
|
|
1045
1176
|
if (focusedRowTarget?.rowToken === token)
|
|
@@ -1698,16 +1829,22 @@ export function createTableContext(options = {}) {
|
|
|
1698
1829
|
notifySelection();
|
|
1699
1830
|
}
|
|
1700
1831
|
function setDisabledKeys(keys) {
|
|
1832
|
+
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
1701
1833
|
disabledKeys.clear();
|
|
1702
1834
|
if (keys) {
|
|
1703
1835
|
for (const key of keys) {
|
|
1704
1836
|
disabledKeys.add(key);
|
|
1705
1837
|
}
|
|
1706
1838
|
}
|
|
1839
|
+
recomputeSelectableBodyRowCount();
|
|
1707
1840
|
invalidateLayoutCaches();
|
|
1708
1841
|
reconcileFocusAfterDisabledStateChange();
|
|
1709
1842
|
notifyLayout();
|
|
1710
|
-
|
|
1843
|
+
if (selectedKeys.size > 0 ||
|
|
1844
|
+
(bodyRowsInitialized &&
|
|
1845
|
+
(previousSelectableBodyRowCount === 0) !== (selectableBodyRowCount === 0))) {
|
|
1846
|
+
notifySelection();
|
|
1847
|
+
}
|
|
1711
1848
|
}
|
|
1712
1849
|
function setRowActionHandler(handler) {
|
|
1713
1850
|
onRowAction = handler;
|
|
@@ -1795,6 +1932,7 @@ export function createTableContext(options = {}) {
|
|
|
1795
1932
|
getVisibleColumnWidths,
|
|
1796
1933
|
getResolvedVisibleColumnWidths,
|
|
1797
1934
|
hasRelativeVisibleColumnWidths,
|
|
1935
|
+
refreshMeasuredLayout,
|
|
1798
1936
|
setColumnWidths,
|
|
1799
1937
|
setColumnWidth,
|
|
1800
1938
|
setHiddenColumns,
|
|
@@ -295,6 +295,33 @@
|
|
|
295
295
|
element = tableElement;
|
|
296
296
|
});
|
|
297
297
|
|
|
298
|
+
$effect(() => {
|
|
299
|
+
if (!tableElement) return;
|
|
300
|
+
|
|
301
|
+
const resizeTarget = tableElement.parentElement ?? tableElement;
|
|
302
|
+
const refreshMeasuredLayout = () => {
|
|
303
|
+
ctx.refreshMeasuredLayout();
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
window.addEventListener('resize', refreshMeasuredLayout);
|
|
307
|
+
window.visualViewport?.addEventListener('resize', refreshMeasuredLayout);
|
|
308
|
+
|
|
309
|
+
const resizeObserver =
|
|
310
|
+
typeof ResizeObserver !== 'undefined'
|
|
311
|
+
? new ResizeObserver(() => {
|
|
312
|
+
refreshMeasuredLayout();
|
|
313
|
+
})
|
|
314
|
+
: null;
|
|
315
|
+
|
|
316
|
+
resizeObserver?.observe(resizeTarget);
|
|
317
|
+
|
|
318
|
+
return () => {
|
|
319
|
+
window.removeEventListener('resize', refreshMeasuredLayout);
|
|
320
|
+
window.visualViewport?.removeEventListener('resize', refreshMeasuredLayout);
|
|
321
|
+
resizeObserver?.disconnect();
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
|
|
298
325
|
$effect(() => {
|
|
299
326
|
ctx.setSelectionMode(selectionMode);
|
|
300
327
|
});
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte';
|
|
3
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
4
3
|
import { writable } from 'svelte/store';
|
|
5
4
|
import { setTableRowContext, useTableContext, useTableSectionContext } from '../root/context';
|
|
6
5
|
import type { TableRowProps } from '../types.js';
|
|
@@ -22,14 +21,12 @@
|
|
|
22
21
|
const section = useTableSectionContext();
|
|
23
22
|
const rowToken = table.createInstanceToken('row');
|
|
24
23
|
const cellOrder: string[] = [];
|
|
25
|
-
const cellElements
|
|
24
|
+
const cellElements: Record<string, () => HTMLElement | undefined> = {};
|
|
26
25
|
const cellOrderVersion = writable(0);
|
|
27
26
|
|
|
28
27
|
let rowElement = $state<HTMLTableRowElement | undefined>(undefined);
|
|
29
|
-
let childListObserver: MutationObserver | null = null;
|
|
30
28
|
|
|
31
29
|
function notifyCellOrderChange() {
|
|
32
|
-
cellIndexCache = null;
|
|
33
30
|
cellOrderVersion.update((value) => value + 1);
|
|
34
31
|
}
|
|
35
32
|
|
|
@@ -39,12 +36,12 @@
|
|
|
39
36
|
notifyCellOrderChange();
|
|
40
37
|
}
|
|
41
38
|
if (getElement) {
|
|
42
|
-
cellElements
|
|
39
|
+
cellElements[token] = getElement;
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
42
|
|
|
46
43
|
function unregisterCellToken(token: string) {
|
|
47
|
-
cellElements
|
|
44
|
+
delete cellElements[token];
|
|
48
45
|
const index = cellOrder.indexOf(token);
|
|
49
46
|
if (index >= 0) {
|
|
50
47
|
cellOrder.splice(index, 1);
|
|
@@ -52,40 +49,12 @@
|
|
|
52
49
|
}
|
|
53
50
|
}
|
|
54
51
|
|
|
55
|
-
let cellIndexCache: Map<string, number> | null = null;
|
|
56
|
-
|
|
57
|
-
function buildCellIndexCache(): Map<string, number> {
|
|
58
|
-
if (cellIndexCache) return cellIndexCache;
|
|
59
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- plain cache, not reactive state
|
|
60
|
-
const cache = new Map<string, number>();
|
|
61
|
-
if (rowElement) {
|
|
62
|
-
const directCells = rowElement.children;
|
|
63
|
-
// eslint-disable-next-line svelte/prefer-svelte-reactivity -- plain cache, not reactive state
|
|
64
|
-
const elementToIndex = new Map<HTMLElement, number>();
|
|
65
|
-
for (let i = 0; i < directCells.length; i += 1) {
|
|
66
|
-
const child = directCells[i];
|
|
67
|
-
if (child instanceof HTMLElement) {
|
|
68
|
-
elementToIndex.set(child, i);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
for (const registeredToken of cellOrder) {
|
|
72
|
-
const element = cellElements.get(registeredToken)?.();
|
|
73
|
-
if (element) {
|
|
74
|
-
const idx = elementToIndex.get(element);
|
|
75
|
-
if (idx !== undefined) {
|
|
76
|
-
cache.set(registeredToken, idx);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
cellIndexCache = cache;
|
|
82
|
-
return cache;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
52
|
function getCellIndex(token: string) {
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
53
|
+
const element = cellElements[token]?.();
|
|
54
|
+
if (rowElement && element) {
|
|
55
|
+
const index = Array.from(rowElement.children).indexOf(element);
|
|
56
|
+
if (index >= 0) return index;
|
|
57
|
+
}
|
|
89
58
|
return cellOrder.indexOf(token);
|
|
90
59
|
}
|
|
91
60
|
|
|
@@ -120,23 +89,7 @@
|
|
|
120
89
|
syncRowRegistration();
|
|
121
90
|
});
|
|
122
91
|
|
|
123
|
-
let pendingCellOrderNotify = false;
|
|
124
|
-
|
|
125
|
-
function scheduleCellOrderNotify() {
|
|
126
|
-
if (!pendingCellOrderNotify) {
|
|
127
|
-
pendingCellOrderNotify = true;
|
|
128
|
-
queueMicrotask(() => {
|
|
129
|
-
pendingCellOrderNotify = false;
|
|
130
|
-
cellIndexCache = null;
|
|
131
|
-
notifyCellOrderChange();
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
92
|
$effect(() => {
|
|
137
|
-
childListObserver?.disconnect();
|
|
138
|
-
childListObserver = null;
|
|
139
|
-
|
|
140
93
|
if (!rowElement) {
|
|
141
94
|
return;
|
|
142
95
|
}
|
|
@@ -145,18 +98,25 @@
|
|
|
145
98
|
scheduleCellOrderNotify();
|
|
146
99
|
});
|
|
147
100
|
observer.observe(rowElement, { childList: true });
|
|
148
|
-
childListObserver = observer;
|
|
149
101
|
|
|
150
102
|
return () => {
|
|
151
103
|
observer.disconnect();
|
|
152
|
-
if (childListObserver === observer) {
|
|
153
|
-
childListObserver = null;
|
|
154
|
-
}
|
|
155
104
|
};
|
|
156
105
|
});
|
|
157
106
|
|
|
107
|
+
let pendingCellOrderNotify = false;
|
|
108
|
+
|
|
109
|
+
function scheduleCellOrderNotify() {
|
|
110
|
+
if (!pendingCellOrderNotify) {
|
|
111
|
+
pendingCellOrderNotify = true;
|
|
112
|
+
queueMicrotask(() => {
|
|
113
|
+
pendingCellOrderNotify = false;
|
|
114
|
+
notifyCellOrderChange();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
158
119
|
onDestroy(() => {
|
|
159
|
-
childListObserver?.disconnect();
|
|
160
120
|
table.unregisterRow(rowToken);
|
|
161
121
|
});
|
|
162
122
|
|