compote-ui 0.37.0 → 0.38.1

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.
@@ -0,0 +1,164 @@
1
+ <script lang="ts" generics="T extends RowData">
2
+ import { FlexRender } from '@tanstack/svelte-table';
3
+ import type { HeaderGroup, RowData } from '@tanstack/svelte-table';
4
+ import { cn } from 'tailwind-variants';
5
+ import { PhCaretDown, PhCaretUp } from '../../icons';
6
+ import Checkbox from '../checkbox/checkbox.svelte';
7
+ import type { DataTableFeatures, DataTableInstance } from './create-table';
8
+ import {
9
+ alignClass,
10
+ getColumnMeta,
11
+ getHeaderAriaSort,
12
+ getHeaderSortDirection,
13
+ getHeaderSortLabel,
14
+ getPinningStyle,
15
+ joinStyles,
16
+ justifyClass,
17
+ resizeHandleClass,
18
+ resizeHandleStyle,
19
+ sortButtonDirectionClass,
20
+ virtualColumnSizeStyle,
21
+ virtualSelectionColumnSizeStyle
22
+ } from './data-table-utils';
23
+
24
+ type Props = {
25
+ table: DataTableInstance<T>;
26
+ headerGroups: HeaderGroup<DataTableFeatures, T>[];
27
+ headerGroupCount: number;
28
+ isRowSelectionEnabled: boolean;
29
+ isMultiRowSelectionEnabled: boolean;
30
+ allRowsSelectionState: boolean | 'indeterminate';
31
+ isVirtual?: boolean;
32
+ };
33
+
34
+ let {
35
+ table,
36
+ headerGroups,
37
+ headerGroupCount,
38
+ isRowSelectionEnabled,
39
+ isMultiRowSelectionEnabled,
40
+ allRowsSelectionState,
41
+ isVirtual = false
42
+ }: Props = $props();
43
+
44
+ function headerCellStyle(header: HeaderGroup<DataTableFeatures, T>['headers'][number]) {
45
+ const canPinHeader = header.subHeaders.length === 0;
46
+
47
+ return joinStyles(
48
+ isVirtual ? virtualColumnSizeStyle(header.getSize()) : undefined,
49
+ canPinHeader ? getPinningStyle(header.column, true, isRowSelectionEnabled) : undefined
50
+ );
51
+ }
52
+
53
+ function selectionHeaderStyle() {
54
+ return isVirtual
55
+ ? joinStyles(
56
+ virtualSelectionColumnSizeStyle(),
57
+ 'min-width: 40px',
58
+ 'max-width: 40px',
59
+ 'position: sticky',
60
+ 'left: 0',
61
+ 'z-index: 30'
62
+ )
63
+ : 'width: 40px; min-width: 40px; max-width: 40px; position: sticky; left: 0; z-index: 15';
64
+ }
65
+
66
+ function headerRowStyle() {
67
+ if (!isVirtual) return undefined;
68
+ return joinStyles('display: flex', 'width: 100%');
69
+ }
70
+
71
+ function shouldRenderSelectionCheckbox(headerGroupIndex: number) {
72
+ if (!isMultiRowSelectionEnabled) return false;
73
+ return headerGroupIndex === headerGroupCount - 1;
74
+ }
75
+
76
+ </script>
77
+
78
+ <thead
79
+ class="sticky top-0 z-20 bg-surface-2 text-left text-ink-dim"
80
+ style={isVirtual ? 'display: grid; position: sticky; top: 0; z-index: 20' : undefined}
81
+ >
82
+ {#each headerGroups as headerGroup, headerGroupIndex (headerGroup.id)}
83
+ {@const visibleHeaders = headerGroup.headers.filter((header) => header.colSpan > 0)}
84
+ <tr class="h-9" style={headerRowStyle()}>
85
+ {#if isRowSelectionEnabled}
86
+ <th
87
+ class={cn(
88
+ 'h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 text-center align-middle leading-5 font-medium',
89
+ isVirtual && 'items-center justify-center'
90
+ )}
91
+ style={selectionHeaderStyle()}
92
+ >
93
+ {#if shouldRenderSelectionCheckbox(headerGroupIndex)}
94
+ <Checkbox
95
+ size="sm"
96
+ aria-label="Select all rows"
97
+ class="mx-auto size-4"
98
+ checked={allRowsSelectionState}
99
+ onCheckedChange={({ checked }) => table.toggleAllRowsSelected(checked === true)}
100
+ />
101
+ {/if}
102
+ </th>
103
+ {/if}
104
+ {#each visibleHeaders as header, headerIndex (header.id)}
105
+ {@const columnDef = getColumnMeta(header.column.columnDef)}
106
+ {@const sortDirection = getHeaderSortDirection(header, table.store.state.sorting)}
107
+ <th
108
+ class={cn(
109
+ 'relative h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 align-middle leading-5 font-medium',
110
+ isVirtual && 'items-center',
111
+ isVirtual && justifyClass(columnDef?.align),
112
+ alignClass(columnDef?.align)
113
+ )}
114
+ colspan={header.colSpan}
115
+ aria-sort={header.column.getCanSort() ? getHeaderAriaSort(sortDirection) : undefined}
116
+ style={headerCellStyle(header)}
117
+ >
118
+ {#if !header.isPlaceholder}
119
+ {#if header.column.getCanSort()}
120
+ <button
121
+ type="button"
122
+ class={cn(
123
+ 'inline-flex max-w-full appearance-none items-center gap-1 rounded-sm border-0 bg-transparent p-0 align-middle text-sm leading-5 text-inherit outline-none hover:text-ink data-focus-visible:outline-2 data-focus-visible:outline-offset-2 data-focus-visible:outline-ring',
124
+ justifyClass(columnDef?.align),
125
+ sortButtonDirectionClass(columnDef?.align)
126
+ )}
127
+ aria-label={`${getHeaderSortLabel(sortDirection)}. Toggle sorting.`}
128
+ onclick={header.column.getToggleSortingHandler()}
129
+ >
130
+ <span class="min-w-0 truncate">
131
+ <FlexRender {header} />
132
+ </span>
133
+ <span
134
+ class="inline-flex size-3.5 shrink-0 items-center justify-center text-ink-dim"
135
+ >
136
+ {#if sortDirection === 'asc'}
137
+ <PhCaretUp class="size-3.5" />
138
+ {:else if sortDirection === 'desc'}
139
+ <PhCaretDown class="size-3.5" />
140
+ {/if}
141
+ </span>
142
+ </button>
143
+ {:else}
144
+ <FlexRender {header} />
145
+ {/if}
146
+ {/if}
147
+ {#if header.column.getCanResize()}
148
+ <div
149
+ aria-hidden="true"
150
+ class={resizeHandleClass(headerIndex, visibleHeaders.length)}
151
+ style={resizeHandleStyle(table, header)}
152
+ ondblclick={() => header.column.resetSize()}
153
+ onmousedown={header.getResizeHandler()}
154
+ ontouchstart={header.getResizeHandler()}
155
+ ></div>
156
+ {/if}
157
+ </th>
158
+ {/each}
159
+ {#if !isVirtual}
160
+ <th aria-hidden="true" class="h-9 border-b border-surface-3 bg-surface-2 p-0"></th>
161
+ {/if}
162
+ </tr>
163
+ {/each}
164
+ </thead>
@@ -0,0 +1,34 @@
1
+ import type { HeaderGroup, RowData } from '@tanstack/svelte-table';
2
+ import type { DataTableFeatures, DataTableInstance } from './create-table';
3
+ declare function $$render<T extends RowData>(): {
4
+ props: {
5
+ table: DataTableInstance<T>;
6
+ headerGroups: HeaderGroup<DataTableFeatures, T>[];
7
+ headerGroupCount: number;
8
+ isRowSelectionEnabled: boolean;
9
+ isMultiRowSelectionEnabled: boolean;
10
+ allRowsSelectionState: boolean | "indeterminate";
11
+ isVirtual?: boolean;
12
+ };
13
+ exports: {};
14
+ bindings: "";
15
+ slots: {};
16
+ events: {};
17
+ };
18
+ declare class __sveltets_Render<T extends RowData> {
19
+ props(): ReturnType<typeof $$render<T>>['props'];
20
+ events(): ReturnType<typeof $$render<T>>['events'];
21
+ slots(): ReturnType<typeof $$render<T>>['slots'];
22
+ bindings(): "";
23
+ exports(): {};
24
+ }
25
+ interface $$IsomorphicComponent {
26
+ new <T extends RowData>(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']>> & {
27
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
28
+ } & ReturnType<__sveltets_Render<T>['exports']>;
29
+ <T extends RowData>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
30
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
31
+ }
32
+ declare const DataTableHead: $$IsomorphicComponent;
33
+ type DataTableHead<T extends RowData> = InstanceType<typeof DataTableHead<T>>;
34
+ export default DataTableHead;
@@ -0,0 +1,34 @@
1
+ import type { CellData, ColumnPinningPosition, Header, RowData } from '@tanstack/svelte-table';
2
+ import type { DataTableFeatures, DataTableInstance } from './create-table';
3
+ import type { DataTableColumnMeta } from './types';
4
+ export type DataTablePinnableColumn = {
5
+ getIsPinned(): ColumnPinningPosition;
6
+ getStart(position?: ColumnPinningPosition): number;
7
+ getAfter(position?: ColumnPinningPosition): number;
8
+ getIsLastColumn(position?: ColumnPinningPosition): boolean;
9
+ getIsFirstColumn(position?: ColumnPinningPosition): boolean;
10
+ };
11
+ export declare function alignClass(align: DataTableColumnMeta['align']): "text-right" | "text-center" | "text-left";
12
+ export declare function justifyClass(align: DataTableColumnMeta['align']): "justify-end" | "justify-center" | "justify-start";
13
+ export declare function sortButtonDirectionClass(align: DataTableColumnMeta['align']): "flex-row-reverse" | "flex-row";
14
+ export declare function columnSizeStyle(size: number): string;
15
+ export declare function selectionColumnSizeStyle(): string;
16
+ export declare function virtualColumnSizeStyle(size: number): string;
17
+ export declare function virtualSelectionColumnSizeStyle(): string;
18
+ export declare function tableSizeStyle<T extends RowData>(table: DataTableInstance<T>, isRowSelectionEnabled: boolean): string;
19
+ export declare function resizeHandleStyle<T extends RowData>(table: DataTableInstance<T>, header: Header<DataTableFeatures, T, CellData>): string | undefined;
20
+ export declare function resizeHandleClass(headerIndex: number, headerCount: number): import("tailwind-variants").CnReturn;
21
+ export declare function getHeaderSortDirection<T extends RowData>(header: Header<DataTableFeatures, T, CellData>, sortingState: unknown): false | import("@tanstack/table-core").SortDirection;
22
+ export declare function getHeaderSortLabel(sortDirection: false | 'asc' | 'desc'): "Sorted ascending" | "Sorted descending" | "Not sorted";
23
+ export declare function getHeaderAriaSort(sortDirection: false | 'asc' | 'desc'): "none" | "ascending" | "descending";
24
+ export declare function getAllRowsSelectionState<T extends RowData>(table: DataTableInstance<T>, rowSelection: unknown): boolean | "indeterminate";
25
+ export declare function getRowSelectionState<T extends RowData>(table: DataTableInstance<T>, rowSelection: unknown, rowId: string): boolean;
26
+ export declare function getSelectedRowCount<T extends RowData>(table: DataTableInstance<T>, rowSelection: unknown): number;
27
+ export declare function getBooleanCellValue(value: unknown): boolean | undefined;
28
+ export declare function getPinningStyle(column: DataTablePinnableColumn, isHeader?: boolean, isRowSelectionEnabled?: boolean): string | undefined;
29
+ export declare function getUrlCellValue(value: unknown): string | undefined;
30
+ export declare function openUrlCell(value: string): void;
31
+ export declare function getColumnMeta(columnDef: {
32
+ meta?: unknown;
33
+ }): DataTableColumnMeta | undefined;
34
+ export declare function joinStyles(...styles: Array<string | undefined>): string;
@@ -0,0 +1,118 @@
1
+ import { cn } from 'tailwind-variants';
2
+ export function alignClass(align) {
3
+ return align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
4
+ }
5
+ export function justifyClass(align) {
6
+ return align === 'right'
7
+ ? 'justify-end'
8
+ : align === 'center'
9
+ ? 'justify-center'
10
+ : 'justify-start';
11
+ }
12
+ export function sortButtonDirectionClass(align) {
13
+ return align === 'right' ? 'flex-row-reverse' : 'flex-row';
14
+ }
15
+ export function columnSizeStyle(size) {
16
+ return `width: ${size}px`;
17
+ }
18
+ export function selectionColumnSizeStyle() {
19
+ return 'width: 40px';
20
+ }
21
+ export function virtualColumnSizeStyle(size) {
22
+ return `display: flex; flex: 0 0 ${size}px; width: ${size}px`;
23
+ }
24
+ export function virtualSelectionColumnSizeStyle() {
25
+ return 'display: flex; flex: 0 0 40px; width: 40px';
26
+ }
27
+ export function tableSizeStyle(table, isRowSelectionEnabled) {
28
+ return `width: max(100%, ${table.getTotalSize() + (isRowSelectionEnabled ? 40 : 0)}px)`;
29
+ }
30
+ export function resizeHandleStyle(table, header) {
31
+ if (table.options.columnResizeMode !== 'onEnd')
32
+ return undefined;
33
+ const deltaOffset = table.store.state.columnResizing.deltaOffset;
34
+ if (!header.column.getIsResizing() || deltaOffset === null)
35
+ return undefined;
36
+ return `transform: translateX(${deltaOffset}px)`;
37
+ }
38
+ export function resizeHandleClass(headerIndex, headerCount) {
39
+ return cn('absolute top-0 z-10 flex h-full w-2 cursor-col-resize touch-none items-center justify-center select-none before:h-4 before:w-px before:bg-border before:content-[""]', headerIndex === headerCount - 1 ? 'right-0' : '-right-1');
40
+ }
41
+ export function getHeaderSortDirection(header, sortingState) {
42
+ void sortingState;
43
+ return header.column.getIsSorted();
44
+ }
45
+ export function getHeaderSortLabel(sortDirection) {
46
+ if (sortDirection === 'asc')
47
+ return 'Sorted ascending';
48
+ if (sortDirection === 'desc')
49
+ return 'Sorted descending';
50
+ return 'Not sorted';
51
+ }
52
+ export function getHeaderAriaSort(sortDirection) {
53
+ if (sortDirection === 'asc')
54
+ return 'ascending';
55
+ if (sortDirection === 'desc')
56
+ return 'descending';
57
+ return 'none';
58
+ }
59
+ export function getAllRowsSelectionState(table, rowSelection) {
60
+ void rowSelection;
61
+ const allRowsSelected = table.getIsAllRowsSelected();
62
+ const someRowsSelected = table.getIsSomeRowsSelected();
63
+ return allRowsSelected ? true : someRowsSelected ? 'indeterminate' : false;
64
+ }
65
+ export function getRowSelectionState(table, rowSelection, rowId) {
66
+ void rowSelection;
67
+ return table.getRow(rowId).getIsSelected();
68
+ }
69
+ export function getSelectedRowCount(table, rowSelection) {
70
+ void rowSelection;
71
+ return table.getSelectedRowModel().rows.length;
72
+ }
73
+ export function getBooleanCellValue(value) {
74
+ if (value === true)
75
+ return true;
76
+ if (value === false)
77
+ return false;
78
+ return undefined;
79
+ }
80
+ export function getPinningStyle(column, isHeader = false, isRowSelectionEnabled = false) {
81
+ const isPinned = column.getIsPinned();
82
+ if (!isPinned)
83
+ return undefined;
84
+ const zIndex = isHeader ? 15 : 1;
85
+ const selectionOffset = isRowSelectionEnabled ? 40 : 0;
86
+ if (isPinned === 'left') {
87
+ const left = column.getStart('left') + selectionOffset;
88
+ const shadow = !isHeader && column.getIsLastColumn('left')
89
+ ? 'box-shadow: -4px 0 4px -4px var(--compote-border) inset'
90
+ : undefined;
91
+ return ['position: sticky', `z-index: ${zIndex}`, `left: ${left}px`, shadow]
92
+ .filter(Boolean)
93
+ .join('; ');
94
+ }
95
+ else {
96
+ const right = column.getAfter('right');
97
+ const shadow = !isHeader && column.getIsFirstColumn('right')
98
+ ? 'box-shadow: 4px 0 4px -4px var(--compote-border) inset'
99
+ : undefined;
100
+ return ['position: sticky', `z-index: ${zIndex}`, `right: ${right}px`, shadow]
101
+ .filter(Boolean)
102
+ .join('; ');
103
+ }
104
+ }
105
+ export function getUrlCellValue(value) {
106
+ if (typeof value !== 'string' || value.trim() === '')
107
+ return undefined;
108
+ return value;
109
+ }
110
+ export function openUrlCell(value) {
111
+ window.open(value, '_blank', 'noopener,noreferrer');
112
+ }
113
+ export function getColumnMeta(columnDef) {
114
+ return columnDef.meta;
115
+ }
116
+ export function joinStyles(...styles) {
117
+ return styles.filter(Boolean).join('; ');
118
+ }
@@ -0,0 +1,156 @@
1
+ <script lang="ts" generics="T extends RowData">
2
+ import { FlexRender } from '@tanstack/svelte-table';
3
+ import type { Row, RowData } from '@tanstack/svelte-table';
4
+ import { createVirtualizer } from '@tanstack/svelte-virtual';
5
+ import { get } from 'svelte/store';
6
+ import { cn } from 'tailwind-variants';
7
+ import { PhArrowSquareOut, PhCheck, PhX } from '../../icons';
8
+ import Checkbox from '../checkbox/checkbox.svelte';
9
+ import type { DataTableFeatures, DataTableInstance } from './create-table';
10
+ import {
11
+ alignClass,
12
+ getBooleanCellValue,
13
+ getColumnMeta,
14
+ getPinningStyle,
15
+ getRowSelectionState,
16
+ getUrlCellValue,
17
+ joinStyles,
18
+ justifyClass,
19
+ openUrlCell,
20
+ virtualColumnSizeStyle,
21
+ virtualSelectionColumnSizeStyle
22
+ } from './data-table-utils';
23
+
24
+ type Props = {
25
+ rows: Row<DataTableFeatures, T>[];
26
+ scrollContainer: HTMLDivElement;
27
+ isRowSelectionEnabled: boolean;
28
+ table: DataTableInstance<T>;
29
+ emptyMessage: string;
30
+ };
31
+
32
+ let { rows, scrollContainer, isRowSelectionEnabled, table, emptyMessage }: Props = $props();
33
+
34
+ const rowVirtualizer = createVirtualizer<HTMLDivElement, HTMLTableRowElement>({
35
+ get count() {
36
+ return rows.length;
37
+ },
38
+ estimateSize: () => 37,
39
+ getScrollElement: () => scrollContainer,
40
+ measureElement:
41
+ typeof window !== 'undefined' && !navigator.userAgent.includes('Firefox')
42
+ ? (element) => element.getBoundingClientRect().height
43
+ : undefined,
44
+ overscan: 5
45
+ });
46
+
47
+ $effect(() => {
48
+ get(rowVirtualizer).setOptions({ count: rows.length });
49
+ });
50
+
51
+ function measureRowElement(node: HTMLTableRowElement) {
52
+ get(rowVirtualizer).measureElement(node);
53
+ }
54
+ </script>
55
+
56
+ <tbody style="display: grid; height: {$rowVirtualizer.getTotalSize()}px; position: relative">
57
+ {#if rows.length === 0}
58
+ <tr style="display: flex; position: absolute; width: 100%; top: 0">
59
+ <td class="px-3 py-10 text-center text-sm text-ink-dim" style="flex: 1">
60
+ {emptyMessage}
61
+ </td>
62
+ </tr>
63
+ {:else}
64
+ {#each $rowVirtualizer.getVirtualItems() as virtualRow (virtualRow.index)}
65
+ {@const row = rows[virtualRow.index]}
66
+ {@const rowSelected = getRowSelectionState(table, table.store.state.rowSelection, row.id)}
67
+ <tr
68
+ data-index={virtualRow.index}
69
+ use:measureRowElement
70
+ class={cn(
71
+ 'group/row border-b border-surface-2 last:border-b-0',
72
+ '[--row-bg:var(--compote-surface-1)]',
73
+ 'hover:bg-well/60 hover:[--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]',
74
+ rowSelected &&
75
+ 'bg-well/60 [--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]'
76
+ )}
77
+ style="display: flex; position: absolute; transform: translateY({virtualRow.start}px); width: 100%"
78
+ >
79
+ {#if isRowSelectionEnabled}
80
+ <td
81
+ class="items-center justify-center bg-(--row-bg) px-3 py-2 text-center align-middle"
82
+ style={joinStyles(
83
+ virtualSelectionColumnSizeStyle(),
84
+ 'position: sticky; left: 0; z-index: 1'
85
+ )}
86
+ >
87
+ <Checkbox
88
+ size="sm"
89
+ aria-label="Select row"
90
+ class="mx-auto size-4"
91
+ checked={rowSelected}
92
+ disabled={!row.getCanSelect()}
93
+ onCheckedChange={({ checked }) => row.toggleSelected(checked === true)}
94
+ />
95
+ </td>
96
+ {/if}
97
+ {#each row.getVisibleCells() as cell (cell.id)}
98
+ {@const columnDef = getColumnMeta(cell.column.columnDef)}
99
+ <td
100
+ class={cn(
101
+ 'items-center truncate px-3 py-2',
102
+ alignClass(columnDef?.align),
103
+ justifyClass(columnDef?.align),
104
+ cell.column.getIsPinned() && 'bg-(--row-bg)'
105
+ )}
106
+ style={joinStyles(
107
+ virtualColumnSizeStyle(cell.column.getSize()),
108
+ getPinningStyle(cell.column, false, isRowSelectionEnabled)
109
+ )}
110
+ >
111
+ {#if columnDef?.type === 'boolean'}
112
+ {@const value = getBooleanCellValue(cell.getValue())}
113
+ {#if value === true}
114
+ <span
115
+ class="inline-flex size-5 items-center justify-center text-success"
116
+ role="img"
117
+ aria-label="Yes"
118
+ >
119
+ <PhCheck class="size-4" />
120
+ </span>
121
+ {:else if value === false}
122
+ <span
123
+ class="inline-flex size-5 items-center justify-center text-danger"
124
+ role="img"
125
+ aria-label="No"
126
+ >
127
+ <PhX class="size-4" />
128
+ </span>
129
+ {:else}
130
+ -
131
+ {/if}
132
+ {:else if columnDef?.type === 'url'}
133
+ {@const value = getUrlCellValue(cell.getValue())}
134
+ {#if value}
135
+ <button
136
+ type="button"
137
+ class={cn(
138
+ 'inline-flex max-w-full appearance-none items-center gap-1.5 rounded-sm border-0 bg-transparent p-0 align-middle leading-5 font-medium text-ink underline decoration-border decoration-dotted underline-offset-4 outline-none hover:text-primary focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring',
139
+ justifyClass(columnDef.align)
140
+ )}
141
+ onclick={() => openUrlCell(value)}
142
+ >
143
+ <PhArrowSquareOut class="size-3.5 shrink-0" />
144
+ </button>
145
+ {:else}
146
+ -
147
+ {/if}
148
+ {:else}
149
+ <FlexRender {cell} />
150
+ {/if}
151
+ </td>
152
+ {/each}
153
+ </tr>
154
+ {/each}
155
+ {/if}
156
+ </tbody>
@@ -0,0 +1,32 @@
1
+ import type { Row, RowData } from '@tanstack/svelte-table';
2
+ import type { DataTableFeatures, DataTableInstance } from './create-table';
3
+ declare function $$render<T extends RowData>(): {
4
+ props: {
5
+ rows: Row<DataTableFeatures, T>[];
6
+ scrollContainer: HTMLDivElement;
7
+ isRowSelectionEnabled: boolean;
8
+ table: DataTableInstance<T>;
9
+ emptyMessage: string;
10
+ };
11
+ exports: {};
12
+ bindings: "";
13
+ slots: {};
14
+ events: {};
15
+ };
16
+ declare class __sveltets_Render<T extends RowData> {
17
+ props(): ReturnType<typeof $$render<T>>['props'];
18
+ events(): ReturnType<typeof $$render<T>>['events'];
19
+ slots(): ReturnType<typeof $$render<T>>['slots'];
20
+ bindings(): "";
21
+ exports(): {};
22
+ }
23
+ interface $$IsomorphicComponent {
24
+ new <T extends RowData>(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']>> & {
25
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
26
+ } & ReturnType<__sveltets_Render<T>['exports']>;
27
+ <T extends RowData>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
28
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
29
+ }
30
+ declare const DataTableVirtualRows: $$IsomorphicComponent;
31
+ type DataTableVirtualRows<T extends RowData> = InstanceType<typeof DataTableVirtualRows<T>>;
32
+ export default DataTableVirtualRows;
@@ -0,0 +1,101 @@
1
+ <script lang="ts" generics="T extends RowData">
2
+ import type { RowData } from '@tanstack/svelte-table';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+ import { cn, type ClassValue } from 'tailwind-variants';
5
+ import { type DataTableInstance } from './create-table';
6
+ import DataTableHead from './data-table-head.svelte';
7
+ import DataTableVirtualRows from './data-table-virtual-rows.svelte';
8
+ import {
9
+ getAllRowsSelectionState,
10
+ getSelectedRowCount,
11
+ tableSizeStyle
12
+ } from './data-table-utils';
13
+
14
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
15
+ table: DataTableInstance<T>;
16
+ caption?: string;
17
+ emptyMessage?: string;
18
+ class?: ClassValue;
19
+ };
20
+
21
+ let {
22
+ table,
23
+ caption,
24
+ emptyMessage = 'No rows found',
25
+ class: className,
26
+ ...rest
27
+ }: Props = $props();
28
+
29
+ let scrollContainerRef = $state<HTMLDivElement | undefined>(undefined);
30
+
31
+ const tableStateKey = $derived(JSON.stringify(table.store.state));
32
+ function trackTableState() {
33
+ return tableStateKey;
34
+ }
35
+
36
+ const rowModel = $derived.by(() => {
37
+ trackTableState();
38
+ return table.getRowModel();
39
+ });
40
+ const headerGroups = $derived.by(() => {
41
+ trackTableState();
42
+ return table.getHeaderGroups();
43
+ });
44
+ const isRowSelectionEnabled = $derived(Boolean(table.options.enableRowSelection));
45
+ const isMultiRowSelectionEnabled = $derived(table.options.enableMultiRowSelection !== false);
46
+ const headerGroupCount = $derived(headerGroups.length);
47
+ const allRowsSelectionState = $derived(
48
+ getAllRowsSelectionState(table, table.store.state.rowSelection)
49
+ );
50
+ const selectedRowCount = $derived(getSelectedRowCount(table, table.store.state.rowSelection));
51
+ const isColumnResizing = $derived(table.store.state.columnResizing.isResizingColumn !== false);
52
+ </script>
53
+
54
+ <div
55
+ class={cn(
56
+ 'flex max-h-full min-h-0 flex-col overflow-hidden rounded-lg border border-surface-3 bg-surface-1',
57
+ className
58
+ )}
59
+ {...rest}
60
+ >
61
+ {#if isColumnResizing}
62
+ <div aria-hidden="true" class="fixed inset-0 z-50 cursor-col-resize select-none"></div>
63
+ {/if}
64
+
65
+ <div class="min-h-0 flex-1 overflow-auto" bind:this={scrollContainerRef}>
66
+ <table
67
+ class="table-fixed border-separate border-spacing-0 text-sm"
68
+ style="display: grid; {tableSizeStyle(table, isRowSelectionEnabled)}"
69
+ >
70
+ {#if caption}
71
+ <caption class="sr-only">{caption}</caption>
72
+ {/if}
73
+ <DataTableHead
74
+ {table}
75
+ {headerGroups}
76
+ {headerGroupCount}
77
+ {isRowSelectionEnabled}
78
+ {isMultiRowSelectionEnabled}
79
+ {allRowsSelectionState}
80
+ isVirtual
81
+ />
82
+ {#if scrollContainerRef}
83
+ <DataTableVirtualRows
84
+ rows={rowModel.rows}
85
+ scrollContainer={scrollContainerRef}
86
+ {isRowSelectionEnabled}
87
+ {table}
88
+ {emptyMessage}
89
+ />
90
+ {/if}
91
+ </table>
92
+ </div>
93
+
94
+ <div class="shrink-0 border-t border-surface-3 bg-surface-2 px-3 py-2 text-sm text-ink-dim">
95
+ {#if isRowSelectionEnabled}
96
+ {selectedRowCount} of {rowModel.rows.length} rows selected
97
+ {:else}
98
+ {rowModel.rows.length} rows
99
+ {/if}
100
+ </div>
101
+ </div>
@@ -0,0 +1,33 @@
1
+ import type { RowData } from '@tanstack/svelte-table';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ import { type ClassValue } from 'tailwind-variants';
4
+ import { type DataTableInstance } from './create-table';
5
+ declare function $$render<T extends RowData>(): {
6
+ props: Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
7
+ table: DataTableInstance<T>;
8
+ caption?: string;
9
+ emptyMessage?: string;
10
+ class?: ClassValue;
11
+ };
12
+ exports: {};
13
+ bindings: "";
14
+ slots: {};
15
+ events: {};
16
+ };
17
+ declare class __sveltets_Render<T extends RowData> {
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 RowData>(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']>> & {
26
+ $$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
27
+ } & ReturnType<__sveltets_Render<T>['exports']>;
28
+ <T extends RowData>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
29
+ z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
30
+ }
31
+ declare const DataTableVirtualized: $$IsomorphicComponent;
32
+ type DataTableVirtualized<T extends RowData> = InstanceType<typeof DataTableVirtualized<T>>;
33
+ export default DataTableVirtualized;
@@ -1,12 +1,27 @@
1
1
  <script lang="ts" generics="T extends RowData">
2
2
  import { FlexRender } from '@tanstack/svelte-table';
3
- import type { CellData, ColumnPinningPosition, Header, RowData } from '@tanstack/svelte-table';
3
+ import type { RowData } from '@tanstack/svelte-table';
4
4
  import type { HTMLAttributes } from 'svelte/elements';
5
5
  import { cn, type ClassValue } from 'tailwind-variants';
6
- import { PhArrowSquareOut, PhCaretDown, PhCaretUp, PhCheck, PhX } from '../../icons';
6
+ import { PhArrowSquareOut, PhCheck, PhX } from '../../icons';
7
7
  import Checkbox from '../checkbox/checkbox.svelte';
8
- import { type DataTableFeatures, type DataTableInstance } from './create-table';
9
- import type { DataTableColumnMeta } from './types';
8
+ import { type DataTableInstance } from './create-table';
9
+ import DataTableHead from './data-table-head.svelte';
10
+ import {
11
+ alignClass,
12
+ columnSizeStyle,
13
+ getAllRowsSelectionState,
14
+ getBooleanCellValue,
15
+ getColumnMeta,
16
+ getPinningStyle,
17
+ getRowSelectionState,
18
+ getSelectedRowCount,
19
+ getUrlCellValue,
20
+ justifyClass,
21
+ openUrlCell,
22
+ selectionColumnSizeStyle,
23
+ tableSizeStyle
24
+ } from './data-table-utils';
10
25
 
11
26
  type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
12
27
  table: DataTableInstance<T>;
@@ -23,142 +38,6 @@
23
38
  ...rest
24
39
  }: Props = $props();
25
40
 
26
- function alignClass(align: DataTableColumnMeta['align']) {
27
- return align === 'right' ? 'text-right' : align === 'center' ? 'text-center' : 'text-left';
28
- }
29
-
30
- function justifyClass(align: DataTableColumnMeta['align']) {
31
- return align === 'right'
32
- ? 'justify-end'
33
- : align === 'center'
34
- ? 'justify-center'
35
- : 'justify-start';
36
- }
37
-
38
- function sortButtonDirectionClass(align: DataTableColumnMeta['align']) {
39
- return align === 'right' ? 'flex-row-reverse' : 'flex-row';
40
- }
41
-
42
- function columnSizeStyle(size: number) {
43
- return `width: ${size}px`;
44
- }
45
-
46
- function selectionColumnSizeStyle() {
47
- return 'width: 40px';
48
- }
49
-
50
- function tableSizeStyle() {
51
- return `width: max(100%, ${table.getTotalSize() + (isRowSelectionEnabled ? 40 : 0)}px)`;
52
- }
53
-
54
- function resizeHandleStyle(header: Header<DataTableFeatures, T, CellData>) {
55
- if (table.options.columnResizeMode !== 'onEnd') return undefined;
56
- const deltaOffset = table.store.state.columnResizing.deltaOffset;
57
- if (!header.column.getIsResizing() || deltaOffset === null) return undefined;
58
- return `transform: translateX(${deltaOffset}px)`;
59
- }
60
-
61
- function resizeHandleClass(headerIndex: number, headerCount: number) {
62
- return cn(
63
- 'absolute top-0 z-10 flex h-full w-2 cursor-col-resize touch-none items-center justify-center select-none before:h-4 before:w-px before:bg-border before:content-[""]',
64
- headerIndex === headerCount - 1 ? 'right-0' : '-right-1'
65
- );
66
- }
67
-
68
- function getHeaderSortDirection(
69
- header: Header<DataTableFeatures, T, CellData>,
70
- sortingState: unknown
71
- ) {
72
- void sortingState;
73
- return header.column.getIsSorted();
74
- }
75
-
76
- function getHeaderSortLabel(sortDirection: false | 'asc' | 'desc') {
77
- if (sortDirection === 'asc') return 'Sorted ascending';
78
- if (sortDirection === 'desc') return 'Sorted descending';
79
- return 'Not sorted';
80
- }
81
-
82
- function getHeaderAriaSort(sortDirection: false | 'asc' | 'desc') {
83
- if (sortDirection === 'asc') return 'ascending';
84
- if (sortDirection === 'desc') return 'descending';
85
- return 'none';
86
- }
87
-
88
- function getAllRowsSelectionState(rowSelection: unknown) {
89
- void rowSelection;
90
- const allRowsSelected = table.getIsAllRowsSelected();
91
- const someRowsSelected = table.getIsSomeRowsSelected();
92
- return allRowsSelected ? true : someRowsSelected ? 'indeterminate' : false;
93
- }
94
-
95
- function getRowSelectionState(rowSelection: unknown, rowId: string) {
96
- void rowSelection;
97
- return table.getRow(rowId).getIsSelected();
98
- }
99
-
100
- function getSelectedRowCount(rowSelection: unknown) {
101
- void rowSelection;
102
- return table.getSelectedRowModel().rows.length;
103
- }
104
-
105
- function getBooleanCellValue(value: unknown) {
106
- if (value === true) return true;
107
- if (value === false) return false;
108
- return undefined;
109
- }
110
-
111
- // type PinPosition = 'center' | false | 'left' | 'right';
112
- function getPinningStyle(
113
- column: {
114
- getIsPinned(): ColumnPinningPosition;
115
- getStart(position?: ColumnPinningPosition): number;
116
- getAfter(position?: ColumnPinningPosition): number;
117
- getIsLastColumn(position?: ColumnPinningPosition): boolean;
118
- getIsFirstColumn(position?: ColumnPinningPosition): boolean;
119
- },
120
- isHeader = false
121
- ): string | undefined {
122
- const isPinned = column.getIsPinned();
123
- if (!isPinned) return undefined;
124
-
125
- const zIndex = isHeader ? 15 : 1;
126
- const selectionOffset = isRowSelectionEnabled ? 40 : 0;
127
-
128
- if (isPinned === 'left') {
129
- const left = column.getStart('left') + selectionOffset;
130
- const shadow =
131
- !isHeader && column.getIsLastColumn('left')
132
- ? 'box-shadow: -4px 0 4px -4px var(--compote-border) inset'
133
- : undefined;
134
- return ['position: sticky', `z-index: ${zIndex}`, `left: ${left}px`, shadow]
135
- .filter(Boolean)
136
- .join('; ');
137
- } else {
138
- const right = column.getAfter('right');
139
- const shadow =
140
- !isHeader && column.getIsFirstColumn('right')
141
- ? 'box-shadow: 4px 0 4px -4px var(--compote-border) inset'
142
- : undefined;
143
- return ['position: sticky', `z-index: ${zIndex}`, `right: ${right}px`, shadow]
144
- .filter(Boolean)
145
- .join('; ');
146
- }
147
- }
148
-
149
- function getUrlCellValue(value: unknown) {
150
- if (typeof value !== 'string' || value.trim() === '') return undefined;
151
- return value;
152
- }
153
-
154
- function openUrlCell(value: string) {
155
- window.open(value, '_blank', 'noopener,noreferrer');
156
- }
157
-
158
- function getColumnMeta(columnDef: { meta?: unknown }): DataTableColumnMeta | undefined {
159
- return columnDef.meta as DataTableColumnMeta | undefined;
160
- }
161
-
162
41
  const tableStateKey = $derived(JSON.stringify(table.store.state));
163
42
  function trackTableState() {
164
43
  return tableStateKey;
@@ -182,8 +61,10 @@
182
61
  const tableColumnCount = $derived(visibleColumnCount + (isRowSelectionEnabled ? 1 : 0));
183
62
  const renderedColumnCount = $derived(tableColumnCount + 1);
184
63
  const headerGroupCount = $derived(headerGroups.length);
185
- const allRowsSelectionState = $derived(getAllRowsSelectionState(table.store.state.rowSelection));
186
- const selectedRowCount = $derived(getSelectedRowCount(table.store.state.rowSelection));
64
+ const allRowsSelectionState = $derived(
65
+ getAllRowsSelectionState(table, table.store.state.rowSelection)
66
+ );
67
+ const selectedRowCount = $derived(getSelectedRowCount(table, table.store.state.rowSelection));
187
68
  const isColumnResizing = $derived(table.store.state.columnResizing.isResizingColumn !== false);
188
69
  </script>
189
70
 
@@ -199,7 +80,10 @@
199
80
  {/if}
200
81
 
201
82
  <div class="min-h-0 flex-1 overflow-auto">
202
- <table class="table-fixed border-separate border-spacing-0 text-sm" style={tableSizeStyle()}>
83
+ <table
84
+ class="table-fixed border-separate border-spacing-0 text-sm"
85
+ style={tableSizeStyle(table, isRowSelectionEnabled)}
86
+ >
203
87
  <colgroup>
204
88
  {#if isRowSelectionEnabled}
205
89
  <col style={selectionColumnSizeStyle()} />
@@ -212,92 +96,20 @@
212
96
  {#if caption}
213
97
  <caption class="sr-only">{caption}</caption>
214
98
  {/if}
215
- <thead class="sticky top-0 z-20 bg-surface-2 text-left text-ink-dim">
216
- {#each headerGroups as headerGroup, headerGroupIndex (headerGroup.id)}
217
- {@const visibleHeaders = headerGroup.headers.filter((header) => header.colSpan > 0)}
218
- <tr class="h-9">
219
- {#if isRowSelectionEnabled && headerGroupIndex === 0}
220
- <th
221
- class="h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 text-center align-middle leading-5 font-medium"
222
- style="position: sticky; left: 0; z-index: 15"
223
- rowspan={headerGroupCount}
224
- >
225
- {#if isMultiRowSelectionEnabled}
226
- <Checkbox
227
- size="sm"
228
- aria-label="Select all rows"
229
- class="mx-auto size-4"
230
- checked={allRowsSelectionState}
231
- onCheckedChange={({ checked }) => table.toggleAllRowsSelected(checked === true)}
232
- />
233
- {/if}
234
- </th>
235
- {/if}
236
- {#each visibleHeaders as header, headerIndex (header.id)}
237
- {@const columnDef = getColumnMeta(header.column.columnDef)}
238
- {@const sortDirection = getHeaderSortDirection(header, table.store.state.sorting)}
239
- <th
240
- class={cn(
241
- 'relative h-9 border-b border-surface-3 bg-surface-2 px-3 py-0 align-middle leading-5 font-medium',
242
- alignClass(columnDef?.align)
243
- )}
244
- colspan={header.colSpan}
245
- aria-sort={header.column.getCanSort()
246
- ? getHeaderAriaSort(sortDirection)
247
- : undefined}
248
- style={getPinningStyle(header.column, true)}
249
- >
250
- {#if !header.isPlaceholder}
251
- {#if header.column.getCanSort()}
252
- <button
253
- type="button"
254
- class={cn(
255
- 'inline-flex max-w-full appearance-none items-center gap-1 rounded-sm border-0 bg-transparent p-0 align-middle text-sm leading-5 text-inherit outline-none hover:text-ink data-focus-visible:outline-2 data-focus-visible:outline-offset-2 data-focus-visible:outline-ring',
256
- justifyClass(columnDef?.align),
257
- sortButtonDirectionClass(columnDef?.align)
258
- )}
259
- aria-label={`${getHeaderSortLabel(sortDirection)}. Toggle sorting.`}
260
- onclick={header.column.getToggleSortingHandler()}
261
- >
262
- <span class="min-w-0 truncate">
263
- <FlexRender {header} />
264
- </span>
265
- <span
266
- class="inline-flex size-3.5 shrink-0 items-center justify-center text-ink-dim"
267
- >
268
- {#if sortDirection === 'asc'}
269
- <PhCaretUp class="size-3.5" />
270
- {:else if sortDirection === 'desc'}
271
- <PhCaretDown class="size-3.5" />
272
- {/if}
273
- </span>
274
- </button>
275
- {:else}
276
- <FlexRender {header} />
277
- {/if}
278
- {/if}
279
- {#if header.column.getCanResize()}
280
- <div
281
- aria-hidden="true"
282
- class={resizeHandleClass(headerIndex, visibleHeaders.length)}
283
- style={resizeHandleStyle(header)}
284
- ondblclick={() => header.column.resetSize()}
285
- onmousedown={header.getResizeHandler()}
286
- ontouchstart={header.getResizeHandler()}
287
- ></div>
288
- {/if}
289
- </th>
290
- {/each}
291
- <th aria-hidden="true" class="h-9 border-b border-surface-3 bg-surface-2 p-0"></th>
292
- </tr>
293
- {/each}
294
- </thead>
99
+ <DataTableHead
100
+ {table}
101
+ {headerGroups}
102
+ {headerGroupCount}
103
+ {isRowSelectionEnabled}
104
+ {isMultiRowSelectionEnabled}
105
+ {allRowsSelectionState}
106
+ />
295
107
  <tbody>
296
108
  {#each rowModel.rows as row (row.id)}
297
- {@const rowSelected = getRowSelectionState(table.store.state.rowSelection, row.id)}
109
+ {@const rowSelected = getRowSelectionState(table, table.store.state.rowSelection, row.id)}
298
110
  <tr
299
111
  class={cn(
300
- 'group/row border-b border-surface-3 last:border-b-0',
112
+ 'group/row',
301
113
  '[--row-bg:var(--compote-surface-1)]',
302
114
  'hover:bg-well/60 hover:[--row-bg:color-mix(in_srgb,var(--compote-well)_60%,var(--compote-surface-1))]',
303
115
  rowSelected &&
@@ -306,7 +118,7 @@
306
118
  >
307
119
  {#if isRowSelectionEnabled}
308
120
  <td
309
- class="bg-(--row-bg) px-3 py-2 text-center align-middle"
121
+ class="border-b border-surface-2 bg-(--row-bg) px-3 py-2 text-center align-middle group-last/row:border-b-0"
310
122
  style="position: sticky; left: 0; z-index: 1"
311
123
  >
312
124
  <Checkbox
@@ -323,11 +135,11 @@
323
135
  {@const columnDef = getColumnMeta(cell.column.columnDef)}
324
136
  <td
325
137
  class={cn(
326
- 'truncate px-3 py-2',
138
+ 'truncate border-b border-b-surface-2 px-3 py-2 group-last/row:border-b-0',
327
139
  alignClass(columnDef?.align),
328
140
  cell.column.getIsPinned() && 'bg-(--row-bg)'
329
141
  )}
330
- style={getPinningStyle(cell.column)}
142
+ style={getPinningStyle(cell.column, false, isRowSelectionEnabled)}
331
143
  >
332
144
  {#if columnDef?.type === 'boolean'}
333
145
  {@const value = getBooleanCellValue(cell.getValue())}
@@ -371,7 +183,8 @@
371
183
  {/if}
372
184
  </td>
373
185
  {/each}
374
- <td aria-hidden="true" class="p-0"></td>
186
+ <td aria-hidden="true" class="border-b border-surface-2 p-0 group-last/row:border-b-0"
187
+ ></td>
375
188
  </tr>
376
189
  {:else}
377
190
  <tr>
@@ -0,0 +1,3 @@
1
+ export { default as Root } from '../data-table-virtualized.svelte';
2
+ export { ColumnFilter, ColumnVisibility, Title, Toolbar, createDataTableColumnHelper, createTable } from '../index';
3
+ export type { CreateDataTableOptions, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableAlign, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableColumn, DataTableColumnBase, DataTableColumnOptions, DataTableColumnType, DataTableFeatures, DataTableGroupColumn, DataTableInstance, DataTableLeafColumn, DataTableLeafColumnBase } from '../index';
@@ -0,0 +1,2 @@
1
+ export { default as Root } from '../data-table-virtualized.svelte';
2
+ export { ColumnFilter, ColumnVisibility, Title, Toolbar, createDataTableColumnHelper, createTable } from '../index';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compote-ui",
3
- "version": "0.37.0",
3
+ "version": "0.38.1",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev --open",
@@ -33,15 +33,23 @@
33
33
  "types": "./dist/components/data-table/index.d.ts",
34
34
  "svelte": "./dist/components/data-table/index.js"
35
35
  },
36
+ "./data-table/virtual": {
37
+ "types": "./dist/components/data-table/virtual/index.d.ts",
38
+ "svelte": "./dist/components/data-table/virtual/index.js"
39
+ },
36
40
  "./theme.css": "./dist/theme.css"
37
41
  },
38
42
  "peerDependencies": {
39
43
  "@tanstack/svelte-table": "^9.0.0-alpha.45",
44
+ "@tanstack/svelte-virtual": ">=3.0.0",
40
45
  "svelte": "^5.0.0"
41
46
  },
42
47
  "peerDependenciesMeta": {
43
48
  "@tanstack/svelte-table": {
44
49
  "optional": true
50
+ },
51
+ "@tanstack/svelte-virtual": {
52
+ "optional": true
45
53
  }
46
54
  },
47
55
  "devDependencies": {
@@ -54,6 +62,7 @@
54
62
  "@sveltejs/vite-plugin-svelte": "7.1.2",
55
63
  "@tailwindcss/vite": "^4.2.4",
56
64
  "@tanstack/svelte-table": "9.0.0-alpha.45",
65
+ "@tanstack/svelte-virtual": "^3.13.24",
57
66
  "@types/node": "^22.19.18",
58
67
  "eslint": "^10.2.1",
59
68
  "eslint-config-prettier": "^10.1.8",
@@ -80,7 +89,7 @@
80
89
  "@fontsource-variable/nunito-sans": "^5.2.7",
81
90
  "@iconify/svelte": "^5.2.1",
82
91
  "runed": "^0.37.1",
83
- "tailwind-merge": "^3.5.0",
92
+ "tailwind-merge": "^3.6.0",
84
93
  "tailwind-variants": "^3.2.2"
85
94
  }
86
95
  }