@human-kit/svelte-components 1.0.0-alpha.17 → 1.0.0-alpha.19
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-narrow-min-width-test.svelte +76 -0
- package/dist/table/column-resizer/table-column-resizer-narrow-min-width-test.svelte.d.ts +3 -0
- 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 +196 -26
- package/dist/table/root/table-root.svelte +48 -1
- 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,76 @@
|
|
|
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
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<div data-testid="narrow-min-width-container" style="width: 240px; overflow-x: auto;">
|
|
9
|
+
<Table.Root
|
|
10
|
+
aria-label="Narrow min width table"
|
|
11
|
+
bind:columnWidths={currentColumnWidths}
|
|
12
|
+
class="min-w-full border-collapse text-left"
|
|
13
|
+
>
|
|
14
|
+
<Table.Header>
|
|
15
|
+
<Table.Row>
|
|
16
|
+
<Table.Column id="request" isRowHeader textValue="Request" minWidth={75}>
|
|
17
|
+
<Table.ColumnHeaderCell data-testid="narrow-request-header-cell">
|
|
18
|
+
<div class="flex items-center justify-between gap-3">
|
|
19
|
+
<span>Request</span>
|
|
20
|
+
<Table.ColumnResizer
|
|
21
|
+
data-testid="narrow-request-resizer"
|
|
22
|
+
class="inline-flex w-3 cursor-col-resize justify-center"
|
|
23
|
+
/>
|
|
24
|
+
</div>
|
|
25
|
+
</Table.ColumnHeaderCell>
|
|
26
|
+
</Table.Column>
|
|
27
|
+
<Table.Column id="requester" textValue="Requester" minWidth={140}>
|
|
28
|
+
<Table.ColumnHeaderCell data-testid="narrow-requester-header-cell">
|
|
29
|
+
<div class="flex items-center justify-between gap-3">
|
|
30
|
+
<span>Requester</span>
|
|
31
|
+
<Table.ColumnResizer class="inline-flex w-3 cursor-col-resize justify-center" />
|
|
32
|
+
</div>
|
|
33
|
+
</Table.ColumnHeaderCell>
|
|
34
|
+
</Table.Column>
|
|
35
|
+
<Table.Column id="area" textValue="Area">
|
|
36
|
+
<Table.ColumnHeaderCell data-testid="narrow-area-header-cell">
|
|
37
|
+
<div class="flex items-center justify-between gap-3">
|
|
38
|
+
<span>Area</span>
|
|
39
|
+
<Table.ColumnResizer class="inline-flex w-3 cursor-col-resize justify-center" />
|
|
40
|
+
</div>
|
|
41
|
+
</Table.ColumnHeaderCell>
|
|
42
|
+
</Table.Column>
|
|
43
|
+
<Table.Column id="status" textValue="Status">
|
|
44
|
+
<Table.ColumnHeaderCell data-testid="narrow-status-header-cell">
|
|
45
|
+
<div class="flex items-center justify-between gap-3">
|
|
46
|
+
<span>Status</span>
|
|
47
|
+
<Table.ColumnResizer class="inline-flex w-3 cursor-col-resize justify-center" />
|
|
48
|
+
</div>
|
|
49
|
+
</Table.ColumnHeaderCell>
|
|
50
|
+
</Table.Column>
|
|
51
|
+
<Table.Column id="total" textValue="Total">
|
|
52
|
+
<Table.ColumnHeaderCell data-testid="narrow-total-header-cell">
|
|
53
|
+
<div class="flex items-center justify-between gap-3">
|
|
54
|
+
<span>Total</span>
|
|
55
|
+
<Table.ColumnResizer class="inline-flex w-3 cursor-col-resize justify-center" />
|
|
56
|
+
</div>
|
|
57
|
+
</Table.ColumnHeaderCell>
|
|
58
|
+
</Table.Column>
|
|
59
|
+
</Table.Row>
|
|
60
|
+
</Table.Header>
|
|
61
|
+
|
|
62
|
+
<Table.Body>
|
|
63
|
+
<Table.Row id="pr-001">
|
|
64
|
+
<Table.Cell>PR-001</Table.Cell>
|
|
65
|
+
<Table.Cell>Ana Gomez</Table.Cell>
|
|
66
|
+
<Table.Cell>Ops</Table.Cell>
|
|
67
|
+
<Table.Cell>Pending</Table.Cell>
|
|
68
|
+
<Table.Cell>$1,520</Table.Cell>
|
|
69
|
+
</Table.Row>
|
|
70
|
+
</Table.Body>
|
|
71
|
+
</Table.Root>
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
<output data-testid="narrow-min-width-widths"
|
|
75
|
+
>{JSON.stringify(Object.fromEntries(currentColumnWidths ?? new Map()))}</output
|
|
76
|
+
>
|
|
@@ -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
|
}
|
|
@@ -163,7 +213,16 @@ export function createTableContext(options = {}) {
|
|
|
163
213
|
return hiddenColumnIds.has(columnId);
|
|
164
214
|
}
|
|
165
215
|
function getColumnMinWidth(columnId) {
|
|
166
|
-
|
|
216
|
+
const registration = getColumnRegistrationById(columnId);
|
|
217
|
+
if (!registration)
|
|
218
|
+
return undefined;
|
|
219
|
+
if (registration.minWidth !== undefined)
|
|
220
|
+
return registration.minWidth;
|
|
221
|
+
if (!isColumnResizable(columnId) &&
|
|
222
|
+
!isRelativeColumnWidth(getEffectiveColumnWidthSpec(columnId))) {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
return getColumnWidthBounds(columnId).minWidth;
|
|
167
226
|
}
|
|
168
227
|
function getColumnMaxWidth(columnId) {
|
|
169
228
|
return getColumnRegistrationById(columnId)?.maxWidth;
|
|
@@ -220,10 +279,20 @@ export function createTableContext(options = {}) {
|
|
|
220
279
|
}
|
|
221
280
|
return Math.round(width);
|
|
222
281
|
}
|
|
223
|
-
function
|
|
282
|
+
function getColumnWidthBounds(columnId) {
|
|
224
283
|
const registration = getColumnRegistrationById(columnId);
|
|
225
|
-
const
|
|
226
|
-
const
|
|
284
|
+
const fixedWidth = parseColumnWidth(registration?.width);
|
|
285
|
+
const minWidth = registration?.minWidth ??
|
|
286
|
+
(fixedWidth?.unit === 'px'
|
|
287
|
+
? Math.min(fixedWidth.value, DEFAULT_TABLE_COLUMN_MIN_WIDTH)
|
|
288
|
+
: DEFAULT_TABLE_COLUMN_MIN_WIDTH);
|
|
289
|
+
return {
|
|
290
|
+
minWidth,
|
|
291
|
+
maxWidth: registration?.maxWidth
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
function clampColumnWidth(columnId, width) {
|
|
295
|
+
const { minWidth, maxWidth } = getColumnWidthBounds(columnId);
|
|
227
296
|
let next = Math.round(width);
|
|
228
297
|
if (Number.isNaN(next) || !Number.isFinite(next)) {
|
|
229
298
|
next = minWidth;
|
|
@@ -234,6 +303,19 @@ export function createTableContext(options = {}) {
|
|
|
234
303
|
}
|
|
235
304
|
return next;
|
|
236
305
|
}
|
|
306
|
+
function resolveRelativeColumnWidthAllocations(entries, targetTotal) {
|
|
307
|
+
const allocations = entries.map((entry) => {
|
|
308
|
+
const { minWidth, maxWidth } = getColumnWidthBounds(entry.columnId);
|
|
309
|
+
return {
|
|
310
|
+
...entry,
|
|
311
|
+
width: clampColumnWidth(entry.columnId, entry.exactWidth),
|
|
312
|
+
minWidth,
|
|
313
|
+
maxWidth,
|
|
314
|
+
remainder: entry.exactWidth - Math.floor(entry.exactWidth)
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
return distributeRoundedWidths(allocations, targetTotal);
|
|
318
|
+
}
|
|
237
319
|
function hasResizableColumns() {
|
|
238
320
|
for (const column of columns.values()) {
|
|
239
321
|
if (isColumnResizable(column.id))
|
|
@@ -410,6 +492,8 @@ export function createTableContext(options = {}) {
|
|
|
410
492
|
let fixedPxTotal = 0;
|
|
411
493
|
let percentTotal = 0;
|
|
412
494
|
let totalFr = 0;
|
|
495
|
+
const resolvedVisibleWidths = getResolvedVisibleColumnWidths();
|
|
496
|
+
const tableWidth = getMeasuredTableWidth();
|
|
413
497
|
for (const token of getVisibleOrderedColumnTokens()) {
|
|
414
498
|
const column = columns.get(token);
|
|
415
499
|
if (!column)
|
|
@@ -440,6 +524,25 @@ export function createTableContext(options = {}) {
|
|
|
440
524
|
const availableWidthExpression = availableWidthTerms.length === 1
|
|
441
525
|
? availableWidthTerms[0]
|
|
442
526
|
: `(${availableWidthTerms.join(' - ')})`;
|
|
527
|
+
if (tableWidth !== undefined) {
|
|
528
|
+
const targetRelativeTotal = Math.max(tableWidth - fixedPxTotal, 0);
|
|
529
|
+
let actualRelativeTotal = 0;
|
|
530
|
+
for (const token of getVisibleOrderedColumnTokens()) {
|
|
531
|
+
const column = columns.get(token);
|
|
532
|
+
if (!column)
|
|
533
|
+
continue;
|
|
534
|
+
const spec = parseColumnWidth(getEffectiveColumnWidthSpec(column.id));
|
|
535
|
+
if (!spec || spec.unit === 'px')
|
|
536
|
+
continue;
|
|
537
|
+
actualRelativeTotal += resolvedVisibleWidths.get(column.id) ?? 0;
|
|
538
|
+
}
|
|
539
|
+
if (Math.abs(actualRelativeTotal - targetRelativeTotal) > 1) {
|
|
540
|
+
const constrainedWidth = resolvedVisibleWidths.get(columnId);
|
|
541
|
+
if (constrainedWidth !== undefined) {
|
|
542
|
+
return `${constrainedWidth}px`;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
443
546
|
if (frShare === 1) {
|
|
444
547
|
return `calc(${availableWidthExpression})`;
|
|
445
548
|
}
|
|
@@ -497,10 +600,12 @@ export function createTableContext(options = {}) {
|
|
|
497
600
|
return resolvedVisibleColumnWidthsCache;
|
|
498
601
|
const widths = new Map();
|
|
499
602
|
const flexibleColumns = [];
|
|
603
|
+
const relativeColumns = [];
|
|
500
604
|
const tableWidth = getMeasuredTableWidth();
|
|
501
|
-
let
|
|
605
|
+
let fixedWidthTotal = 0;
|
|
606
|
+
let exactRelativeWidthTotal = 0;
|
|
502
607
|
let totalFr = 0;
|
|
503
|
-
for (const token of getVisibleOrderedColumnTokens()) {
|
|
608
|
+
for (const [index, token] of getVisibleOrderedColumnTokens().entries()) {
|
|
504
609
|
const column = columns.get(token);
|
|
505
610
|
if (!column)
|
|
506
611
|
continue;
|
|
@@ -510,29 +615,34 @@ export function createTableContext(options = {}) {
|
|
|
510
615
|
if (parsed.unit === 'px') {
|
|
511
616
|
const nextWidth = clampColumnWidth(column.id, parsed.value);
|
|
512
617
|
widths.set(column.id, nextWidth);
|
|
513
|
-
|
|
514
|
-
remainingWidth -= nextWidth;
|
|
515
|
-
}
|
|
618
|
+
fixedWidthTotal += nextWidth;
|
|
516
619
|
continue;
|
|
517
620
|
}
|
|
518
621
|
if (parsed.unit === '%') {
|
|
519
622
|
if (tableWidth === undefined)
|
|
520
623
|
continue;
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
remainingWidth -= nextWidth;
|
|
525
|
-
}
|
|
624
|
+
const exactWidth = (tableWidth * parsed.value) / 100;
|
|
625
|
+
relativeColumns.push({ columnId: column.id, index, exactWidth });
|
|
626
|
+
exactRelativeWidthTotal += exactWidth;
|
|
526
627
|
continue;
|
|
527
628
|
}
|
|
528
|
-
flexibleColumns.push({ columnId: column.id, fr: parsed.value });
|
|
629
|
+
flexibleColumns.push({ columnId: column.id, fr: parsed.value, index });
|
|
529
630
|
totalFr += parsed.value;
|
|
530
631
|
}
|
|
531
632
|
if (tableWidth !== undefined && flexibleColumns.length > 0 && totalFr > 0) {
|
|
532
|
-
const distributableWidth = Math.max(
|
|
633
|
+
const distributableWidth = Math.max(tableWidth - fixedWidthTotal - exactRelativeWidthTotal, 0);
|
|
533
634
|
for (const entry of flexibleColumns) {
|
|
534
|
-
|
|
535
|
-
|
|
635
|
+
relativeColumns.push({
|
|
636
|
+
columnId: entry.columnId,
|
|
637
|
+
index: entry.index,
|
|
638
|
+
exactWidth: (distributableWidth * entry.fr) / totalFr
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (tableWidth !== undefined && relativeColumns.length > 0) {
|
|
643
|
+
const targetRelativeTotal = Math.max(tableWidth - fixedWidthTotal, 0);
|
|
644
|
+
for (const entry of resolveRelativeColumnWidthAllocations(relativeColumns, targetRelativeTotal)) {
|
|
645
|
+
widths.set(entry.columnId, entry.width);
|
|
536
646
|
}
|
|
537
647
|
}
|
|
538
648
|
resolvedVisibleColumnWidthsCache = widths;
|
|
@@ -546,6 +656,22 @@ export function createTableContext(options = {}) {
|
|
|
546
656
|
}
|
|
547
657
|
return Math.round(width);
|
|
548
658
|
}
|
|
659
|
+
function getFixedVisibleColumnWidthTotal() {
|
|
660
|
+
let total = 0;
|
|
661
|
+
for (const token of getVisibleOrderedColumnTokens()) {
|
|
662
|
+
const column = columns.get(token);
|
|
663
|
+
if (!column)
|
|
664
|
+
continue;
|
|
665
|
+
if (getFixedColumnWidthSpec(column.id) === undefined)
|
|
666
|
+
continue;
|
|
667
|
+
const measuredWidth = getMeasuredHeaderWidth(token);
|
|
668
|
+
const resolvedWidth = getColumnWidth(column.id) ?? measuredWidth;
|
|
669
|
+
if (resolvedWidth === undefined)
|
|
670
|
+
continue;
|
|
671
|
+
total += clampColumnWidth(column.id, resolvedWidth);
|
|
672
|
+
}
|
|
673
|
+
return total;
|
|
674
|
+
}
|
|
549
675
|
function getResizableRelativeTailColumnId(activeColumnId) {
|
|
550
676
|
return getVisibleOrderedColumnTokens()
|
|
551
677
|
.map((token) => columns.get(token))
|
|
@@ -567,6 +693,7 @@ export function createTableContext(options = {}) {
|
|
|
567
693
|
? normalizeColumnWidth(getEffectiveColumnWidthSpec(preservedFlexibleColumnId))
|
|
568
694
|
: undefined;
|
|
569
695
|
const measuredTableWidth = getMeasuredTableWidth();
|
|
696
|
+
const fixedVisibleColumnWidthTotal = getFixedVisibleColumnWidthTotal();
|
|
570
697
|
for (const token of getVisibleOrderedColumnTokens()) {
|
|
571
698
|
const column = columns.get(token);
|
|
572
699
|
if (!column)
|
|
@@ -606,7 +733,9 @@ export function createTableContext(options = {}) {
|
|
|
606
733
|
? preservedFlexibleEffectiveWidth
|
|
607
734
|
: undefined,
|
|
608
735
|
baselineWidths,
|
|
609
|
-
|
|
736
|
+
baselineAvailableTableWidth: measuredTableWidth !== undefined
|
|
737
|
+
? Math.max(measuredTableWidth - fixedVisibleColumnWidthTotal, 0)
|
|
738
|
+
: baselineTotalWidth,
|
|
610
739
|
baselineTotalWidth
|
|
611
740
|
};
|
|
612
741
|
options.onColumnWidthsChange?.(getColumnWidths());
|
|
@@ -655,7 +784,7 @@ export function createTableContext(options = {}) {
|
|
|
655
784
|
const baselineTailWidth = resizeSession.baselineWidths.get(resizeSession.flexibleTailColumnId);
|
|
656
785
|
if (baselineActiveWidth !== undefined && baselineTailWidth !== undefined) {
|
|
657
786
|
const widthDelta = nextWidth - baselineActiveWidth;
|
|
658
|
-
const overflowWidth = Math.max(resizeSession.baselineTotalWidth - resizeSession.
|
|
787
|
+
const overflowWidth = Math.max(resizeSession.baselineTotalWidth - resizeSession.baselineAvailableTableWidth, 0);
|
|
659
788
|
const tailTargetWidth = widthDelta >= 0
|
|
660
789
|
? baselineTailWidth - widthDelta
|
|
661
790
|
: baselineTailWidth + Math.max(-widthDelta - overflowWidth, 0);
|
|
@@ -892,6 +1021,7 @@ export function createTableContext(options = {}) {
|
|
|
892
1021
|
}
|
|
893
1022
|
function registerRow(row) {
|
|
894
1023
|
const existing = rows.get(row.token);
|
|
1024
|
+
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
895
1025
|
const targetOrder = row.section === 'header' ? headerRowOrder : row.section === 'body' ? bodyRowOrder : null;
|
|
896
1026
|
const alreadyOrdered = targetOrder ? targetOrder.includes(row.token) : false;
|
|
897
1027
|
const wasInHeader = headerRowOrder.includes(row.token);
|
|
@@ -907,16 +1037,29 @@ export function createTableContext(options = {}) {
|
|
|
907
1037
|
if (wasInBody) {
|
|
908
1038
|
bodyRowOrder.splice(bodyRowOrder.indexOf(row.token), 1);
|
|
909
1039
|
}
|
|
1040
|
+
const previousSelectableBodyRow = isSelectableBodyRow(existing);
|
|
910
1041
|
rows.set(row.token, row);
|
|
911
1042
|
if (targetOrder && !targetOrder.includes(row.token)) {
|
|
912
1043
|
targetOrder.push(row.token);
|
|
913
1044
|
}
|
|
914
|
-
|
|
1045
|
+
const nextSelectableBodyRow = isSelectableBodyRow(row);
|
|
1046
|
+
if (previousSelectableBodyRow !== nextSelectableBodyRow) {
|
|
1047
|
+
selectableBodyRowCount += nextSelectableBodyRow ? 1 : -1;
|
|
1048
|
+
}
|
|
1049
|
+
if ((selectedKeys.size > 0 && (existing?.section === 'body' || row.section === 'body')) ||
|
|
1050
|
+
(bodyRowsInitialized &&
|
|
1051
|
+
(previousSelectableBodyRowCount === 0) !== (selectableBodyRowCount === 0))) {
|
|
1052
|
+
notifySelection();
|
|
1053
|
+
}
|
|
915
1054
|
notifyLayout();
|
|
916
1055
|
}
|
|
917
1056
|
function unregisterRow(token) {
|
|
918
1057
|
const row = rows.get(token);
|
|
1058
|
+
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
919
1059
|
rows.delete(token);
|
|
1060
|
+
if (isSelectableBodyRow(row)) {
|
|
1061
|
+
selectableBodyRowCount = Math.max(0, selectableBodyRowCount - 1);
|
|
1062
|
+
}
|
|
920
1063
|
if (focusedRowTarget?.rowToken === token) {
|
|
921
1064
|
focusedRowTarget = null;
|
|
922
1065
|
notifyFocus();
|
|
@@ -939,7 +1082,11 @@ export function createTableContext(options = {}) {
|
|
|
939
1082
|
notifyFocus();
|
|
940
1083
|
}
|
|
941
1084
|
}
|
|
942
|
-
|
|
1085
|
+
if ((selectedKeys.size > 0 && row?.section === 'body') ||
|
|
1086
|
+
(bodyRowsInitialized &&
|
|
1087
|
+
(previousSelectableBodyRowCount === 0) !== (selectableBodyRowCount === 0))) {
|
|
1088
|
+
notifySelection();
|
|
1089
|
+
}
|
|
943
1090
|
notifyLayout();
|
|
944
1091
|
}
|
|
945
1092
|
function markBodyRowsInitialized() {
|
|
@@ -947,7 +1094,7 @@ export function createTableContext(options = {}) {
|
|
|
947
1094
|
return;
|
|
948
1095
|
const optimisticHasSelectableRows = selectionMode === 'multiple' || selectedKeys.size > 0;
|
|
949
1096
|
bodyRowsInitialized = true;
|
|
950
|
-
const actualHasSelectableRows =
|
|
1097
|
+
const actualHasSelectableRows = selectableBodyRowCount > 0 || selectedKeys.size > 0;
|
|
951
1098
|
if (optimisticHasSelectableRows !== actualHasSelectableRows) {
|
|
952
1099
|
notifySelection();
|
|
953
1100
|
}
|
|
@@ -1001,6 +1148,20 @@ export function createTableContext(options = {}) {
|
|
|
1001
1148
|
return false;
|
|
1002
1149
|
return disabledKeys.has(id);
|
|
1003
1150
|
}
|
|
1151
|
+
function isSelectableBodyRow(row) {
|
|
1152
|
+
return (row?.section === 'body' &&
|
|
1153
|
+
row.id !== undefined &&
|
|
1154
|
+
!isRowSelectionDisabled(row.id, row.disabled));
|
|
1155
|
+
}
|
|
1156
|
+
function recomputeSelectableBodyRowCount() {
|
|
1157
|
+
let nextSelectableBodyRowCount = 0;
|
|
1158
|
+
for (const token of bodyRowOrder) {
|
|
1159
|
+
if (isSelectableBodyRow(rows.get(token))) {
|
|
1160
|
+
nextSelectableBodyRowCount += 1;
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
selectableBodyRowCount = nextSelectableBodyRowCount;
|
|
1164
|
+
}
|
|
1004
1165
|
function isRowDisabled(id, localDisabled = false) {
|
|
1005
1166
|
return disabledBehavior === 'all' && isRowSelectionDisabled(id, localDisabled);
|
|
1006
1167
|
}
|
|
@@ -1039,7 +1200,7 @@ export function createTableContext(options = {}) {
|
|
|
1039
1200
|
if (!bodyRowsInitialized) {
|
|
1040
1201
|
return selectionMode === 'multiple' || selectedKeys.size > 0;
|
|
1041
1202
|
}
|
|
1042
|
-
return
|
|
1203
|
+
return selectableBodyRowCount > 0 || selectedKeys.size > 0;
|
|
1043
1204
|
}
|
|
1044
1205
|
function isRowFocused(token) {
|
|
1045
1206
|
if (focusedRowTarget?.rowToken === token)
|
|
@@ -1064,6 +1225,7 @@ export function createTableContext(options = {}) {
|
|
|
1064
1225
|
cellOrder.push(cell.key);
|
|
1065
1226
|
}
|
|
1066
1227
|
notifyLayout();
|
|
1228
|
+
notifyWidth();
|
|
1067
1229
|
}
|
|
1068
1230
|
function unregisterCell(key) {
|
|
1069
1231
|
cells.delete(key);
|
|
@@ -1076,6 +1238,7 @@ export function createTableContext(options = {}) {
|
|
|
1076
1238
|
notifyFocus();
|
|
1077
1239
|
}
|
|
1078
1240
|
notifyLayout();
|
|
1241
|
+
notifyWidth();
|
|
1079
1242
|
}
|
|
1080
1243
|
function isCellFocused(key) {
|
|
1081
1244
|
return focusedCellKey === key;
|
|
@@ -1698,16 +1861,22 @@ export function createTableContext(options = {}) {
|
|
|
1698
1861
|
notifySelection();
|
|
1699
1862
|
}
|
|
1700
1863
|
function setDisabledKeys(keys) {
|
|
1864
|
+
const previousSelectableBodyRowCount = selectableBodyRowCount;
|
|
1701
1865
|
disabledKeys.clear();
|
|
1702
1866
|
if (keys) {
|
|
1703
1867
|
for (const key of keys) {
|
|
1704
1868
|
disabledKeys.add(key);
|
|
1705
1869
|
}
|
|
1706
1870
|
}
|
|
1871
|
+
recomputeSelectableBodyRowCount();
|
|
1707
1872
|
invalidateLayoutCaches();
|
|
1708
1873
|
reconcileFocusAfterDisabledStateChange();
|
|
1709
1874
|
notifyLayout();
|
|
1710
|
-
|
|
1875
|
+
if (selectedKeys.size > 0 ||
|
|
1876
|
+
(bodyRowsInitialized &&
|
|
1877
|
+
(previousSelectableBodyRowCount === 0) !== (selectableBodyRowCount === 0))) {
|
|
1878
|
+
notifySelection();
|
|
1879
|
+
}
|
|
1711
1880
|
}
|
|
1712
1881
|
function setRowActionHandler(handler) {
|
|
1713
1882
|
onRowAction = handler;
|
|
@@ -1795,6 +1964,7 @@ export function createTableContext(options = {}) {
|
|
|
1795
1964
|
getVisibleColumnWidths,
|
|
1796
1965
|
getResolvedVisibleColumnWidths,
|
|
1797
1966
|
hasRelativeVisibleColumnWidths,
|
|
1967
|
+
refreshMeasuredLayout,
|
|
1798
1968
|
setColumnWidths,
|
|
1799
1969
|
setColumnWidth,
|
|
1800
1970
|
setHiddenColumns,
|
|
@@ -210,6 +210,20 @@
|
|
|
210
210
|
return columns;
|
|
211
211
|
});
|
|
212
212
|
|
|
213
|
+
const minimumTableWidth = $derived.by(() => {
|
|
214
|
+
if (!hasResizable && !ctx.hasRelativeVisibleColumnWidths()) return undefined;
|
|
215
|
+
|
|
216
|
+
let total = 0;
|
|
217
|
+
let hasMinimumWidth = false;
|
|
218
|
+
for (const column of layoutColumns) {
|
|
219
|
+
if (column.minWidth === undefined) continue;
|
|
220
|
+
total += column.minWidth;
|
|
221
|
+
hasMinimumWidth = true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return hasMinimumWidth ? total : undefined;
|
|
225
|
+
});
|
|
226
|
+
|
|
213
227
|
const explicitManagedTableWidth = $derived.by(() => {
|
|
214
228
|
void $layoutVersion;
|
|
215
229
|
if (ctx.hasRelativeVisibleColumnWidths()) return undefined;
|
|
@@ -295,6 +309,35 @@
|
|
|
295
309
|
element = tableElement;
|
|
296
310
|
});
|
|
297
311
|
|
|
312
|
+
$effect(() => {
|
|
313
|
+
if (!tableElement) return;
|
|
314
|
+
|
|
315
|
+
const resizeTarget = tableElement.parentElement ?? tableElement;
|
|
316
|
+
const refreshMeasuredLayout = () => {
|
|
317
|
+
ctx.refreshMeasuredLayout();
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
refreshMeasuredLayout();
|
|
321
|
+
|
|
322
|
+
window.addEventListener('resize', refreshMeasuredLayout);
|
|
323
|
+
window.visualViewport?.addEventListener('resize', refreshMeasuredLayout);
|
|
324
|
+
|
|
325
|
+
const resizeObserver =
|
|
326
|
+
typeof ResizeObserver !== 'undefined'
|
|
327
|
+
? new ResizeObserver(() => {
|
|
328
|
+
refreshMeasuredLayout();
|
|
329
|
+
})
|
|
330
|
+
: null;
|
|
331
|
+
|
|
332
|
+
resizeObserver?.observe(resizeTarget);
|
|
333
|
+
|
|
334
|
+
return () => {
|
|
335
|
+
window.removeEventListener('resize', refreshMeasuredLayout);
|
|
336
|
+
window.visualViewport?.removeEventListener('resize', refreshMeasuredLayout);
|
|
337
|
+
resizeObserver?.disconnect();
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
|
|
298
341
|
$effect(() => {
|
|
299
342
|
ctx.setSelectionMode(selectionMode);
|
|
300
343
|
});
|
|
@@ -448,7 +491,11 @@
|
|
|
448
491
|
style:width={resolvedTableWidth !== undefined
|
|
449
492
|
? `${resolvedTableWidth}px`
|
|
450
493
|
: fallbackRelativeTableWidth}
|
|
451
|
-
style:min-width={
|
|
494
|
+
style:min-width={minimumTableWidth !== undefined
|
|
495
|
+
? `${minimumTableWidth}px`
|
|
496
|
+
: resolvedTableWidth !== undefined
|
|
497
|
+
? '0'
|
|
498
|
+
: undefined}
|
|
452
499
|
aria-label={ariaLabel}
|
|
453
500
|
aria-labelledby={ariaLabelledby}
|
|
454
501
|
aria-colcount={ariaColCount}
|
|
@@ -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
|
|