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.
- package/dist/components/data-table/data-table-head.svelte +164 -0
- package/dist/components/data-table/data-table-head.svelte.d.ts +34 -0
- package/dist/components/data-table/data-table-utils.d.ts +34 -0
- package/dist/components/data-table/data-table-utils.js +118 -0
- package/dist/components/data-table/data-table-virtual-rows.svelte +156 -0
- package/dist/components/data-table/data-table-virtual-rows.svelte.d.ts +32 -0
- package/dist/components/data-table/data-table-virtualized.svelte +101 -0
- package/dist/components/data-table/data-table-virtualized.svelte.d.ts +33 -0
- package/dist/components/data-table/data-table.svelte +42 -229
- package/dist/components/data-table/virtual/index.d.ts +3 -0
- package/dist/components/data-table/virtual/index.js +2 -0
- package/package.json +11 -2
|
@@ -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 {
|
|
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,
|
|
6
|
+
import { PhArrowSquareOut, PhCheck, PhX } from '../../icons';
|
|
7
7
|
import Checkbox from '../checkbox/checkbox.svelte';
|
|
8
|
-
import { type
|
|
9
|
-
import
|
|
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(
|
|
186
|
-
|
|
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
|
|
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
|
-
<
|
|
216
|
-
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
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"
|
|
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';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "compote-ui",
|
|
3
|
-
"version": "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.
|
|
92
|
+
"tailwind-merge": "^3.6.0",
|
|
84
93
|
"tailwind-variants": "^3.2.2"
|
|
85
94
|
}
|
|
86
95
|
}
|