@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
|
@@ -93,10 +93,13 @@
|
|
|
93
93
|
let focused = $state(false);
|
|
94
94
|
let focusVisible = $state(false);
|
|
95
95
|
let pressedKey: 'Enter' | 'Space' | null = $state(null);
|
|
96
|
+
let expandedPressed = $state(false);
|
|
96
97
|
|
|
97
98
|
const renderedType = $derived(type === 'submit' && isPending ? 'button' : type);
|
|
98
99
|
const renderedPressed = $derived(
|
|
99
|
-
pressedOverride !== undefined
|
|
100
|
+
pressedOverride !== undefined
|
|
101
|
+
? Boolean(pressedOverride) && !isPending
|
|
102
|
+
: (pressed || expandedPressed) && !isPending
|
|
100
103
|
);
|
|
101
104
|
const renderState = $derived.by<ButtonRenderState>(() => ({
|
|
102
105
|
isHovered: hovered,
|
|
@@ -149,6 +152,34 @@
|
|
|
149
152
|
element = buttonRef;
|
|
150
153
|
});
|
|
151
154
|
|
|
155
|
+
function syncExpandedPressed() {
|
|
156
|
+
expandedPressed =
|
|
157
|
+
buttonRef?.getAttribute('data-pressed-when-expanded') === 'true' &&
|
|
158
|
+
buttonRef?.getAttribute('aria-expanded') === 'true';
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
$effect(() => {
|
|
162
|
+
if (!buttonRef) {
|
|
163
|
+
expandedPressed = false;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
syncExpandedPressed();
|
|
168
|
+
|
|
169
|
+
const observer = new MutationObserver(() => {
|
|
170
|
+
syncExpandedPressed();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
observer.observe(buttonRef, {
|
|
174
|
+
attributes: true,
|
|
175
|
+
attributeFilter: ['aria-expanded', 'data-pressed-when-expanded']
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
return () => {
|
|
179
|
+
observer.disconnect();
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
152
183
|
$effect(() => {
|
|
153
184
|
if (!isPending && !isDisabled) return;
|
|
154
185
|
clearInteractionState();
|
|
@@ -29,12 +29,14 @@
|
|
|
29
29
|
function setActiveTrigger(button: HTMLElement) {
|
|
30
30
|
if (activeTrigger && activeTrigger !== button) {
|
|
31
31
|
activeTrigger.setAttribute('aria-expanded', 'false');
|
|
32
|
+
delete activeTrigger.dataset.pressedWhenExpanded;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
activeTrigger = button;
|
|
35
36
|
dialogCtx.setTriggerRef(button);
|
|
36
37
|
button.setAttribute('aria-haspopup', 'dialog');
|
|
37
38
|
button.setAttribute('aria-expanded', String(dialogCtx.isOpen));
|
|
39
|
+
button.dataset.pressedWhenExpanded = 'true';
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
function handleClick(event: MouseEvent) {
|
|
@@ -72,6 +74,7 @@
|
|
|
72
74
|
}
|
|
73
75
|
dialogCtx.triggerRef.setAttribute('aria-haspopup', 'dialog');
|
|
74
76
|
dialogCtx.triggerRef.setAttribute('aria-expanded', String(dialogCtx.isOpen));
|
|
77
|
+
dialogCtx.triggerRef.dataset.pressedWhenExpanded = 'true';
|
|
75
78
|
}
|
|
76
79
|
});
|
|
77
80
|
</script>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Popover } from '../index';
|
|
3
|
+
|
|
4
|
+
let open = $state(false);
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<Popover.Root bind:open>
|
|
8
|
+
<Popover.Trigger>
|
|
9
|
+
<button type="button">Open Popover</button>
|
|
10
|
+
</Popover.Trigger>
|
|
11
|
+
|
|
12
|
+
<Popover.Content class="popover-content presence-animation">
|
|
13
|
+
<div class="popover-body">
|
|
14
|
+
<h3>Popover Title</h3>
|
|
15
|
+
<p>Popover content goes here.</p>
|
|
16
|
+
<button type="button" class="apply-btn" onclick={() => (open = false)}>Apply</button>
|
|
17
|
+
</div>
|
|
18
|
+
</Popover.Content>
|
|
19
|
+
</Popover.Root>
|
|
20
|
+
|
|
21
|
+
<style>
|
|
22
|
+
:global(.presence-animation) {
|
|
23
|
+
opacity: 1;
|
|
24
|
+
transition: opacity 0.2s linear;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
:global(.presence-animation[data-exiting]) {
|
|
28
|
+
opacity: 0;
|
|
29
|
+
}
|
|
30
|
+
</style>
|
|
@@ -96,6 +96,8 @@
|
|
|
96
96
|
let pendingMotionFrame: number | undefined;
|
|
97
97
|
let cleanupMotionListeners: (() => void) | undefined;
|
|
98
98
|
let motionId = 0;
|
|
99
|
+
let previousOpen = $state(false);
|
|
100
|
+
let closeHandledInternally = false;
|
|
99
101
|
|
|
100
102
|
function parseTimeList(value: string) {
|
|
101
103
|
return value
|
|
@@ -239,7 +241,21 @@
|
|
|
239
241
|
});
|
|
240
242
|
}
|
|
241
243
|
|
|
244
|
+
function releaseFocusedDescendantBeforeHide() {
|
|
245
|
+
if (!browser || !popoverRef || !triggerRef) return;
|
|
246
|
+
|
|
247
|
+
const activeElement = document.activeElement;
|
|
248
|
+
if (!(activeElement instanceof HTMLElement)) return;
|
|
249
|
+
if (activeElement === triggerRef || triggerRef.contains(activeElement)) return;
|
|
250
|
+
if (activeElement !== popoverRef && !popoverRef.contains(activeElement)) return;
|
|
251
|
+
|
|
252
|
+
activeElement.blur();
|
|
253
|
+
}
|
|
254
|
+
|
|
242
255
|
function close(reason: PopoverCloseReason = 'imperative-action', event?: Event) {
|
|
256
|
+
closeHandledInternally = true;
|
|
257
|
+
releaseFocusedDescendantBeforeHide();
|
|
258
|
+
|
|
243
259
|
if (isStandalone) {
|
|
244
260
|
let canceled = false;
|
|
245
261
|
const details: PopoverOpenChangeDetails = {
|
|
@@ -334,6 +350,28 @@
|
|
|
334
350
|
document.removeEventListener('scroll', handleScroll, true);
|
|
335
351
|
});
|
|
336
352
|
|
|
353
|
+
$effect(() => {
|
|
354
|
+
const wasOpen = previousOpen;
|
|
355
|
+
previousOpen = isOpen;
|
|
356
|
+
|
|
357
|
+
if (isOpen) {
|
|
358
|
+
closeHandledInternally = false;
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (!wasOpen) {
|
|
363
|
+
closeHandledInternally = false;
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (closeHandledInternally) {
|
|
368
|
+
closeHandledInternally = false;
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
releaseFocusedDescendantBeforeHide();
|
|
373
|
+
});
|
|
374
|
+
|
|
337
375
|
$effect(() => {
|
|
338
376
|
if (isOpen) {
|
|
339
377
|
const shouldAnimateIn = !isMounted || isExiting;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Button } from '../../button';
|
|
3
|
+
import { Popover } from '../index';
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<Popover.Root>
|
|
7
|
+
<Popover.Trigger>
|
|
8
|
+
<Button.Root>Open Button Root Popover</Button.Root>
|
|
9
|
+
</Popover.Trigger>
|
|
10
|
+
|
|
11
|
+
<Popover.Content class="popover-content">
|
|
12
|
+
<div class="popover-body">
|
|
13
|
+
<h3>Popover Title</h3>
|
|
14
|
+
<p>Popover content goes here.</p>
|
|
15
|
+
</div>
|
|
16
|
+
</Popover.Content>
|
|
17
|
+
</Popover.Root>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const PopoverTriggerButtonRootTest: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type PopoverTriggerButtonRootTest = InstanceType<typeof PopoverTriggerButtonRootTest>;
|
|
18
|
+
export default PopoverTriggerButtonRootTest;
|
|
@@ -30,8 +30,10 @@
|
|
|
30
30
|
button.setAttribute('aria-haspopup', 'dialog');
|
|
31
31
|
button.setAttribute('aria-expanded', String(popoverCtx.isOpen));
|
|
32
32
|
if (popoverCtx.isOpen) {
|
|
33
|
+
button.dataset.pressedWhenExpanded = 'true';
|
|
33
34
|
button.dataset.pressed = 'true';
|
|
34
35
|
} else {
|
|
36
|
+
delete button.dataset.pressedWhenExpanded;
|
|
35
37
|
delete button.dataset.pressed;
|
|
36
38
|
}
|
|
37
39
|
}
|
|
@@ -40,6 +42,7 @@
|
|
|
40
42
|
if (activeTrigger && activeTrigger !== button) {
|
|
41
43
|
activeTrigger.setAttribute('aria-expanded', 'false');
|
|
42
44
|
delete activeTrigger.dataset.pressed;
|
|
45
|
+
delete activeTrigger.dataset.pressedWhenExpanded;
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
activeTrigger = button;
|
|
@@ -7,14 +7,27 @@
|
|
|
7
7
|
### Table.Body
|
|
8
8
|
|
|
9
9
|
Name: `Table.Body`
|
|
10
|
-
Description: Body rowgroup for table data rows. It also exposes empty-state markers when no body rows are
|
|
10
|
+
Description: Body rowgroup for table data rows. It can render authored rows directly, or render item-driven rows with optional fixed-height virtualization. It also exposes empty-state markers when no logical body rows are available.
|
|
11
11
|
|
|
12
12
|
Public prop type: `TableBodyProps`
|
|
13
13
|
|
|
14
|
-
| Prop
|
|
15
|
-
|
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
14
|
+
| Prop | Type | Default | Description |
|
|
15
|
+
| ------------- | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
|
16
|
+
| `items` | `readonly T[]` | `undefined` | Logical row collection for item-driven mode. When present, `children(item)` renders once per item with `item` inferred from `items`. |
|
|
17
|
+
| `virtualizer` | `TableBodyVirtualizer` | `undefined` | Optional fixed-height row virtualization config with `rowHeight` and `overscan`. |
|
|
18
|
+
| `class` | `string` | `''` | Class names for the `tbody` element. |
|
|
19
|
+
| `children` | `Snippet` or `Snippet<[T]>` | `undefined` | Manual body content when `items` is omitted, or `children(item)` in item-driven mode. |
|
|
20
|
+
| `empty` | `Snippet` | `undefined` | Optional empty-state snippet for item-driven mode. |
|
|
21
|
+
|
|
22
|
+
### TableBodyVirtualizer
|
|
23
|
+
|
|
24
|
+
Name: `TableBodyVirtualizer`
|
|
25
|
+
Description: Fixed-height body virtualization settings.
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Default | Description |
|
|
28
|
+
| ----------- | -------- | ------- | -------------------------------------------------------------- |
|
|
29
|
+
| `rowHeight` | `number` | `-` | Required fixed pixel height used to compute the visible range. |
|
|
30
|
+
| `overscan` | `number` | `18` | Extra rows rendered above and below the viewport. |
|
|
18
31
|
|
|
19
32
|
### Context utilities
|
|
20
33
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Table } from '../index';
|
|
3
|
+
|
|
4
|
+
type DemoRow = {
|
|
5
|
+
id: string;
|
|
6
|
+
email: string;
|
|
7
|
+
group: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const rows: DemoRow[] = Array.from({ length: 120 }, (_, index) => {
|
|
11
|
+
const sequence = index + 1;
|
|
12
|
+
return {
|
|
13
|
+
id: `row-${String(sequence).padStart(3, '0')}`,
|
|
14
|
+
email: `user${sequence}@example.com`,
|
|
15
|
+
group: sequence % 2 === 0 ? 'Admin' : 'Developer'
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div style="max-height: 240px; overflow: auto;">
|
|
21
|
+
<Table.Root aria-label="Virtualized users table">
|
|
22
|
+
<Table.Header>
|
|
23
|
+
<Table.Row>
|
|
24
|
+
<Table.Column id="email" isRowHeader textValue="Email">
|
|
25
|
+
<Table.ColumnHeaderCell>Email</Table.ColumnHeaderCell>
|
|
26
|
+
</Table.Column>
|
|
27
|
+
<Table.Column id="group" textValue="Group">
|
|
28
|
+
<Table.ColumnHeaderCell>Group</Table.ColumnHeaderCell>
|
|
29
|
+
</Table.Column>
|
|
30
|
+
</Table.Row>
|
|
31
|
+
</Table.Header>
|
|
32
|
+
|
|
33
|
+
<Table.Body items={rows} virtualizer={{ rowHeight: 32 }}>
|
|
34
|
+
{#snippet children(row)}
|
|
35
|
+
<Table.Row id={row.id} data-item-id={row.id}>
|
|
36
|
+
<Table.Cell>{row.email}</Table.Cell>
|
|
37
|
+
<Table.Cell>{row.group}</Table.Cell>
|
|
38
|
+
</Table.Row>
|
|
39
|
+
{/snippet}
|
|
40
|
+
{#snippet empty()}
|
|
41
|
+
<Table.EmptyState>No users found.</Table.EmptyState>
|
|
42
|
+
{/snippet}
|
|
43
|
+
</Table.Body>
|
|
44
|
+
</Table.Root>
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const TableBodyItemsTest: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
}, {}, {}, string>;
|
|
17
|
+
type TableBodyItemsTest = InstanceType<typeof TableBodyItemsTest>;
|
|
18
|
+
export default TableBodyItemsTest;
|
|
@@ -1,24 +1,171 @@
|
|
|
1
|
-
<script
|
|
1
|
+
<script
|
|
2
|
+
lang="ts"
|
|
3
|
+
generics="T extends Record<string, unknown> & { id: string | number } = Record<string, unknown> & { id: string | number }"
|
|
4
|
+
>
|
|
5
|
+
import { flushSync, type Snippet } from 'svelte';
|
|
2
6
|
import { setTableSectionContext, useTableContext } from '../root/context';
|
|
3
7
|
import type { TableBodyProps } from '../types.js';
|
|
4
8
|
|
|
5
|
-
let {
|
|
9
|
+
let {
|
|
10
|
+
items,
|
|
11
|
+
virtualizer,
|
|
12
|
+
children,
|
|
13
|
+
empty,
|
|
14
|
+
class: className = '',
|
|
15
|
+
...restProps
|
|
16
|
+
}: TableBodyProps<T> = $props();
|
|
6
17
|
setTableSectionContext({ section: 'body' });
|
|
7
18
|
const table = useTableContext();
|
|
8
19
|
const layoutVersion = table.layoutVersion;
|
|
20
|
+
let bodyElement = $state<HTMLTableSectionElement | undefined>(undefined);
|
|
21
|
+
let scrollTop = $state(0);
|
|
22
|
+
let viewportHeight = $state(0);
|
|
9
23
|
|
|
10
24
|
$effect(() => {
|
|
11
25
|
table.markBodyRowsInitialized();
|
|
12
26
|
});
|
|
13
27
|
|
|
28
|
+
$effect(() => {
|
|
29
|
+
table.setLogicalBodyRows(items?.map((item) => item.id));
|
|
30
|
+
return () => {
|
|
31
|
+
table.setLogicalBodyRows(undefined);
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const itemList = $derived(items ?? []);
|
|
36
|
+
const isItemsMode = $derived(items !== undefined);
|
|
37
|
+
const virtualizerEnabled = $derived(Boolean(virtualizer && itemList.length > 0));
|
|
38
|
+
const overscan = $derived(Math.max(0, virtualizer?.overscan ?? 18));
|
|
39
|
+
const bodyColumnCount = $derived.by(() => {
|
|
40
|
+
void $layoutVersion;
|
|
41
|
+
return Math.max(table.getVisibleColumnCount(), 1);
|
|
42
|
+
});
|
|
43
|
+
const effectiveViewportHeight = $derived.by(() => {
|
|
44
|
+
if (!virtualizer) return 0;
|
|
45
|
+
if (viewportHeight > 0) return viewportHeight;
|
|
46
|
+
return Math.min(itemList.length, 12) * virtualizer.rowHeight;
|
|
47
|
+
});
|
|
48
|
+
const visibleRange = $derived.by(() => {
|
|
49
|
+
if (!virtualizer || !virtualizerEnabled || itemList.length === 0) {
|
|
50
|
+
return {
|
|
51
|
+
from: 0,
|
|
52
|
+
to: Math.max(itemList.length - 1, 0),
|
|
53
|
+
topSpacerHeight: 0,
|
|
54
|
+
bottomSpacerHeight: 0
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const rowHeight = virtualizer.rowHeight;
|
|
59
|
+
const rowCount = itemList.length;
|
|
60
|
+
const startIndex = Math.min(rowCount - 1, Math.floor(Math.max(0, scrollTop) / rowHeight));
|
|
61
|
+
const visibleCount = Math.max(1, Math.ceil(effectiveViewportHeight / rowHeight));
|
|
62
|
+
const endIndex = Math.min(rowCount - 1, startIndex + visibleCount - 1);
|
|
63
|
+
const from = Math.max(0, startIndex - overscan);
|
|
64
|
+
const to = Math.max(from, Math.min(rowCount - 1, endIndex + overscan));
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
from,
|
|
68
|
+
to,
|
|
69
|
+
topSpacerHeight: from * rowHeight,
|
|
70
|
+
bottomSpacerHeight: Math.max(0, (rowCount - to - 1) * rowHeight)
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
const renderedItems = $derived.by(() => {
|
|
74
|
+
if (!virtualizerEnabled) return itemList;
|
|
75
|
+
return itemList.slice(visibleRange.from, visibleRange.to + 1);
|
|
76
|
+
});
|
|
77
|
+
|
|
14
78
|
const isEmpty = $derived.by(() => {
|
|
15
79
|
void $layoutVersion;
|
|
16
|
-
return table.
|
|
80
|
+
return table.getLogicalBodyRowCount() === 0;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
function findScrollContainer(node: HTMLElement) {
|
|
84
|
+
const tableElement = node.closest('table');
|
|
85
|
+
let current = tableElement?.parentElement ?? node.parentElement;
|
|
86
|
+
|
|
87
|
+
while (current) {
|
|
88
|
+
const style = window.getComputedStyle(current);
|
|
89
|
+
if (
|
|
90
|
+
/(auto|scroll|overlay)/.test(style.overflowY) ||
|
|
91
|
+
/(auto|scroll|overlay)/.test(style.overflow)
|
|
92
|
+
) {
|
|
93
|
+
return current;
|
|
94
|
+
}
|
|
95
|
+
current = current.parentElement;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
$effect(() => {
|
|
102
|
+
if (!bodyElement || !virtualizerEnabled || typeof window === 'undefined') return;
|
|
103
|
+
|
|
104
|
+
const scrollContainer = findScrollContainer(bodyElement);
|
|
105
|
+
if (!scrollContainer) return;
|
|
106
|
+
|
|
107
|
+
const setMetrics = () => {
|
|
108
|
+
viewportHeight = scrollContainer.clientHeight;
|
|
109
|
+
scrollTop = scrollContainer.scrollTop;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const updateMetrics = () => {
|
|
113
|
+
flushSync(setMetrics);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleScroll = () => {
|
|
117
|
+
flushSync(setMetrics);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
updateMetrics();
|
|
121
|
+
|
|
122
|
+
const resizeObserver =
|
|
123
|
+
typeof ResizeObserver !== 'undefined' ? new ResizeObserver(updateMetrics) : null;
|
|
124
|
+
|
|
125
|
+
scrollContainer.addEventListener('scroll', handleScroll, { passive: true });
|
|
126
|
+
resizeObserver?.observe(scrollContainer);
|
|
127
|
+
|
|
128
|
+
return () => {
|
|
129
|
+
scrollContainer.removeEventListener('scroll', handleScroll);
|
|
130
|
+
resizeObserver?.disconnect();
|
|
131
|
+
};
|
|
17
132
|
});
|
|
18
133
|
</script>
|
|
19
134
|
|
|
20
|
-
<tbody
|
|
21
|
-
{
|
|
22
|
-
|
|
135
|
+
<tbody
|
|
136
|
+
bind:this={bodyElement}
|
|
137
|
+
class={className}
|
|
138
|
+
data-table-body
|
|
139
|
+
data-empty={isEmpty || undefined}
|
|
140
|
+
{...restProps}
|
|
141
|
+
>
|
|
142
|
+
{#if isItemsMode}
|
|
143
|
+
{#if virtualizerEnabled && visibleRange.topSpacerHeight > 0}
|
|
144
|
+
<tr aria-hidden="true" data-virtual-spacer="top">
|
|
145
|
+
<td colspan={bodyColumnCount} style="padding:0;border:0;height:0;">
|
|
146
|
+
<div aria-hidden="true" style={`height:${visibleRange.topSpacerHeight}px;`}></div>
|
|
147
|
+
</td>
|
|
148
|
+
</tr>
|
|
149
|
+
{/if}
|
|
150
|
+
|
|
151
|
+
{#each renderedItems as item (item.id)}
|
|
152
|
+
{#if children}
|
|
153
|
+
{@render (children as Snippet<[T]>)(item)}
|
|
154
|
+
{/if}
|
|
155
|
+
{/each}
|
|
156
|
+
|
|
157
|
+
{#if virtualizerEnabled && visibleRange.bottomSpacerHeight > 0}
|
|
158
|
+
<tr aria-hidden="true" data-virtual-spacer="bottom">
|
|
159
|
+
<td colspan={bodyColumnCount} style="padding:0;border:0;height:0;">
|
|
160
|
+
<div aria-hidden="true" style={`height:${visibleRange.bottomSpacerHeight}px;`}></div>
|
|
161
|
+
</td>
|
|
162
|
+
</tr>
|
|
163
|
+
{/if}
|
|
164
|
+
|
|
165
|
+
{#if empty}
|
|
166
|
+
{@render (empty as Snippet)()}
|
|
167
|
+
{/if}
|
|
168
|
+
{:else if children}
|
|
169
|
+
{@render (children as Snippet)()}
|
|
23
170
|
{/if}
|
|
24
171
|
</tbody>
|
|
@@ -1,4 +1,45 @@
|
|
|
1
1
|
import type { TableBodyProps } from '../types.js';
|
|
2
|
-
declare
|
|
3
|
-
|
|
2
|
+
declare function $$render<T extends Record<string, unknown> & {
|
|
3
|
+
id: string | number;
|
|
4
|
+
} = Record<string, unknown> & {
|
|
5
|
+
id: string | number;
|
|
6
|
+
}>(): {
|
|
7
|
+
props: TableBodyProps<T>;
|
|
8
|
+
exports: {};
|
|
9
|
+
bindings: "";
|
|
10
|
+
slots: {};
|
|
11
|
+
events: {};
|
|
12
|
+
};
|
|
13
|
+
declare class __sveltets_Render<T extends Record<string, unknown> & {
|
|
14
|
+
id: string | number;
|
|
15
|
+
} = Record<string, unknown> & {
|
|
16
|
+
id: string | number;
|
|
17
|
+
}> {
|
|
18
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
19
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
20
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
21
|
+
bindings(): "";
|
|
22
|
+
exports(): {};
|
|
23
|
+
}
|
|
24
|
+
interface $$IsomorphicComponent {
|
|
25
|
+
new <T extends Record<string, unknown> & {
|
|
26
|
+
id: string | number;
|
|
27
|
+
} = Record<string, unknown> & {
|
|
28
|
+
id: string | number;
|
|
29
|
+
}>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<T>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<T>['props']>, ReturnType<__sveltets_Render<T>['events']>, ReturnType<__sveltets_Render<T>['slots']>> & {
|
|
30
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
31
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
32
|
+
<T extends Record<string, unknown> & {
|
|
33
|
+
id: string | number;
|
|
34
|
+
} = Record<string, unknown> & {
|
|
35
|
+
id: string | number;
|
|
36
|
+
}>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
37
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
38
|
+
}
|
|
39
|
+
declare const TableBody: $$IsomorphicComponent;
|
|
40
|
+
type TableBody<T extends Record<string, unknown> & {
|
|
41
|
+
id: string | number;
|
|
42
|
+
} = Record<string, unknown> & {
|
|
43
|
+
id: string | number;
|
|
44
|
+
}> = InstanceType<typeof TableBody<T>>;
|
|
4
45
|
export default TableBody;
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
const layoutVersion = table.layoutVersion;
|
|
22
22
|
const focusVersion = table.focusVersion;
|
|
23
23
|
const selectionVersion = table.selectionVersion;
|
|
24
|
-
const widthVersion = table.widthVersion;
|
|
25
24
|
const cellOrderVersion = row.cellOrderVersion;
|
|
26
25
|
|
|
27
26
|
let element = $state<HTMLElement | undefined>(undefined);
|
|
@@ -52,13 +51,17 @@
|
|
|
52
51
|
focusDelegate = undefined;
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
const shouldSeedClientRegistration = typeof window === 'undefined';
|
|
55
|
+
|
|
55
56
|
setTableCellContext({
|
|
56
57
|
cellKey: key,
|
|
57
58
|
registerFocusDelegate,
|
|
58
59
|
unregisterFocusDelegate
|
|
59
60
|
});
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
if (shouldSeedClientRegistration) {
|
|
63
|
+
syncCellRegistration();
|
|
64
|
+
}
|
|
62
65
|
|
|
63
66
|
$effect(() => {
|
|
64
67
|
syncCellRegistration();
|
|
@@ -83,18 +86,6 @@
|
|
|
83
86
|
void $layoutVersion;
|
|
84
87
|
return column ? table.getVisibleColumnIndexByToken(column.token) : -1;
|
|
85
88
|
});
|
|
86
|
-
const columnWidthStyle = $derived.by(() => {
|
|
87
|
-
void $widthVersion;
|
|
88
|
-
return column ? table.getColumnWidthStyle(column.id) : undefined;
|
|
89
|
-
});
|
|
90
|
-
const columnMinWidth = $derived.by(() => {
|
|
91
|
-
void $widthVersion;
|
|
92
|
-
return column ? table.getColumnMinWidth(column.id) : undefined;
|
|
93
|
-
});
|
|
94
|
-
const columnMaxWidth = $derived.by(() => {
|
|
95
|
-
void $widthVersion;
|
|
96
|
-
return column ? table.getColumnMaxWidth(column.id) : undefined;
|
|
97
|
-
});
|
|
98
89
|
const tagName = $derived(row.section === 'body' && column?.isRowHeader ? 'th' : 'td');
|
|
99
90
|
const role = $derived.by(() => {
|
|
100
91
|
if (isColumnHidden) return undefined;
|
|
@@ -248,9 +239,6 @@
|
|
|
248
239
|
data-disabled={isRowDisabled || undefined}
|
|
249
240
|
data-column-index={visibleColumnIndex >= 0 ? visibleColumnIndex : undefined}
|
|
250
241
|
style:box-sizing="border-box"
|
|
251
|
-
style:width={columnWidthStyle}
|
|
252
|
-
style:min-width={columnMinWidth !== undefined ? `${columnMinWidth}px` : undefined}
|
|
253
|
-
style:max-width={columnMaxWidth !== undefined ? `${columnMaxWidth}px` : undefined}
|
|
254
242
|
style:display={isColumnHidden ? 'none' : 'table-cell'}
|
|
255
243
|
onfocus={row.section === 'body' ? handleFocus : undefined}
|
|
256
244
|
onclick={row.section === 'body' ? handleClick : undefined}
|