compote-ui 0.33.1 → 0.34.0
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/column-helper.d.ts +12 -0
- package/dist/components/data-table/column-helper.js +36 -0
- package/dist/components/data-table/create-table.d.ts +38 -0
- package/dist/components/data-table/create-table.js +190 -0
- package/dist/components/data-table/data-table-column-filter.svelte +249 -0
- package/dist/components/data-table/data-table-column-filter.svelte.d.ts +29 -0
- package/dist/components/data-table/data-table-column-visibility.svelte +8 -13
- package/dist/components/data-table/data-table-column-visibility.svelte.d.ts +13 -13
- package/dist/components/data-table/data-table-title.svelte +2 -2
- package/dist/components/data-table/data-table-title.svelte.d.ts +2 -2
- package/dist/components/data-table/data-table-toolbar.svelte +28 -5
- package/dist/components/data-table/data-table-toolbar.svelte.d.ts +4 -2
- package/dist/components/data-table/data-table.svelte +293 -270
- package/dist/components/data-table/data-table.svelte.d.ts +19 -16
- package/dist/components/data-table/index.d.ts +5 -3
- package/dist/components/data-table/index.js +3 -3
- package/dist/components/data-table/types.d.ts +50 -0
- package/dist/components/data-table/types.js +1 -0
- package/dist/components/{data-table → data-table-old}/core/create-table.svelte.js +0 -8
- package/dist/components/data-table-old/data-table-column-visibility.svelte +79 -0
- package/dist/components/data-table-old/data-table-column-visibility.svelte.d.ts +29 -0
- package/dist/components/data-table-old/data-table-title.svelte +16 -0
- package/dist/components/data-table-old/data-table-title.svelte.d.ts +10 -0
- package/dist/components/data-table-old/data-table-toolbar.svelte +16 -0
- package/dist/components/data-table-old/data-table-toolbar.svelte.d.ts +10 -0
- package/dist/components/data-table-old/data-table.svelte +342 -0
- package/dist/components/data-table-old/data-table.svelte.d.ts +32 -0
- package/dist/components/data-table-old/index.d.ts +7 -0
- package/dist/components/data-table-old/index.js +7 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +5 -5
- /package/dist/components/{data-table → data-table-old}/core/cells.d.ts +0 -0
- /package/dist/components/{data-table → data-table-old}/core/cells.js +0 -0
- /package/dist/components/{data-table → data-table-old}/core/create-table.svelte.d.ts +0 -0
- /package/dist/components/{data-table → data-table-old}/core/index.d.ts +0 -0
- /package/dist/components/{data-table → data-table-old}/core/index.js +0 -0
- /package/dist/components/{data-table → data-table-old}/data-table-filters.svelte +0 -0
- /package/dist/components/{data-table → data-table-old}/data-table-filters.svelte.d.ts +0 -0
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
import { type ClassValue } from 'tailwind-variants';
|
|
2
|
-
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
1
|
import type { RowData } from '@tanstack/svelte-table';
|
|
4
|
-
import {
|
|
5
|
-
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type ClassValue } from 'tailwind-variants';
|
|
4
|
+
import { type DataTableInstance } from './create-table';
|
|
5
|
+
import type { DataTableColumn } from './types';
|
|
6
|
+
declare function $$render<T extends RowData>(): {
|
|
6
7
|
props: Omit<HTMLAttributes<HTMLDivElement>, "class"> & {
|
|
7
|
-
table:
|
|
8
|
+
table: DataTableInstance<T>;
|
|
9
|
+
columns: DataTableColumn<T>[];
|
|
10
|
+
caption?: string;
|
|
11
|
+
emptyMessage?: string;
|
|
8
12
|
class?: ClassValue;
|
|
9
|
-
tableClass?: ClassValue;
|
|
10
13
|
};
|
|
11
14
|
exports: {};
|
|
12
15
|
bindings: "";
|
|
13
16
|
slots: {};
|
|
14
17
|
events: {};
|
|
15
18
|
};
|
|
16
|
-
declare class __sveltets_Render<
|
|
17
|
-
props(): ReturnType<typeof $$render<
|
|
18
|
-
events(): ReturnType<typeof $$render<
|
|
19
|
-
slots(): ReturnType<typeof $$render<
|
|
19
|
+
declare class __sveltets_Render<T extends RowData> {
|
|
20
|
+
props(): ReturnType<typeof $$render<T>>['props'];
|
|
21
|
+
events(): ReturnType<typeof $$render<T>>['events'];
|
|
22
|
+
slots(): ReturnType<typeof $$render<T>>['slots'];
|
|
20
23
|
bindings(): "";
|
|
21
24
|
exports(): {};
|
|
22
25
|
}
|
|
23
26
|
interface $$IsomorphicComponent {
|
|
24
|
-
new <
|
|
25
|
-
$$bindings?: ReturnType<__sveltets_Render<
|
|
26
|
-
} & ReturnType<__sveltets_Render<
|
|
27
|
-
<
|
|
28
|
-
z_$$bindings?: ReturnType<__sveltets_Render<any
|
|
27
|
+
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']>> & {
|
|
28
|
+
$$bindings?: ReturnType<__sveltets_Render<T>['bindings']>;
|
|
29
|
+
} & ReturnType<__sveltets_Render<T>['exports']>;
|
|
30
|
+
<T extends RowData>(internal: unknown, props: ReturnType<__sveltets_Render<T>['props']> & {}): ReturnType<__sveltets_Render<T>['exports']>;
|
|
31
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
|
|
29
32
|
}
|
|
30
33
|
declare const DataTable: $$IsomorphicComponent;
|
|
31
|
-
type DataTable<
|
|
34
|
+
type DataTable<T extends RowData> = InstanceType<typeof DataTable<T>>;
|
|
32
35
|
export default DataTable;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
export { createDataTableColumnHelper } from './column-helper';
|
|
2
|
+
export { createTable } from './create-table';
|
|
1
3
|
export { default as Root } from './data-table.svelte';
|
|
2
4
|
export { default as Toolbar } from './data-table-toolbar.svelte';
|
|
3
5
|
export { default as Title } from './data-table-title.svelte';
|
|
4
6
|
export { default as ColumnVisibility } from './data-table-column-visibility.svelte';
|
|
5
|
-
export { default as
|
|
6
|
-
export
|
|
7
|
-
export
|
|
7
|
+
export { default as ColumnFilter } from './data-table-column-filter.svelte';
|
|
8
|
+
export type { CreateDataTableOptions, DataTableFeatures, DataTableInstance } from './create-table';
|
|
9
|
+
export type { DataTableAlign, DataTableAccessorFnColumn, DataTableAccessorKeyColumn, DataTableColumn, DataTableColumnBase, DataTableColumnOptions, DataTableColumnType, DataTableCellPropsResolver, DataTableCellRenderProps, DataTableGroupColumn, DataTableLeafColumnBase, DataTableLeafColumn } from './types';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
export { createDataTableColumnHelper } from './column-helper';
|
|
2
|
+
export { createTable } from './create-table';
|
|
1
3
|
export { default as Root } from './data-table.svelte';
|
|
2
4
|
export { default as Toolbar } from './data-table-toolbar.svelte';
|
|
3
5
|
export { default as Title } from './data-table-title.svelte';
|
|
4
6
|
export { default as ColumnVisibility } from './data-table-column-visibility.svelte';
|
|
5
|
-
export { default as
|
|
6
|
-
export * from './core';
|
|
7
|
-
export * from '@tanstack/svelte-table';
|
|
7
|
+
export { default as ColumnFilter } from './data-table-column-filter.svelte';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { CellData, ColumnDef, RowData, TableFeatures } from '@tanstack/svelte-table';
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
export type DataTableAlign = 'left' | 'center' | 'right';
|
|
4
|
+
export type DataTableColumnType = 'text' | 'number' | 'currency' | 'percent' | 'boolean' | 'select' | 'url';
|
|
5
|
+
export type DataTableCellRenderer<T extends RowData> = (value: unknown, row: T) => string | number | boolean | null | undefined;
|
|
6
|
+
export type DataTableCellRenderProps<T extends RowData> = {
|
|
7
|
+
value: unknown;
|
|
8
|
+
row: T;
|
|
9
|
+
};
|
|
10
|
+
export type DataTableCellPropsResolver<T extends RowData> = (value: unknown, row: T) => Record<string, unknown>;
|
|
11
|
+
export type DataTableColumnOptions<T extends RowData> = Partial<Pick<ColumnDef<TableFeatures, T, CellData>, 'size' | 'minSize' | 'maxSize' | 'enableResizing' | 'enableHiding' | 'enableSorting' | 'sortDescFirst' | 'enableColumnFilter' | 'filterFn'>>;
|
|
12
|
+
export type DataTableColumnBase = {
|
|
13
|
+
header: string;
|
|
14
|
+
align?: DataTableAlign;
|
|
15
|
+
};
|
|
16
|
+
export type DataTableLeafColumnBase<T extends RowData> = DataTableColumnOptions<T> & DataTableColumnBase & {
|
|
17
|
+
cell?: DataTableCellRenderer<T>;
|
|
18
|
+
cellComponent?: unknown;
|
|
19
|
+
cellProps?: DataTableCellPropsResolver<T>;
|
|
20
|
+
cellSnippet?: Snippet<[DataTableCellRenderProps<T>]>;
|
|
21
|
+
type?: DataTableColumnType;
|
|
22
|
+
formatOptions?: Intl.NumberFormatOptions;
|
|
23
|
+
formatLocale?: string;
|
|
24
|
+
columns?: never;
|
|
25
|
+
};
|
|
26
|
+
export type DataTableAccessorKeyColumn<T extends RowData> = DataTableLeafColumnBase<T> & {
|
|
27
|
+
accessorKey: Extract<keyof T, string>;
|
|
28
|
+
id?: string;
|
|
29
|
+
accessorFn?: never;
|
|
30
|
+
};
|
|
31
|
+
export type DataTableAccessorFnColumn<T extends RowData> = DataTableLeafColumnBase<T> & {
|
|
32
|
+
accessorFn: (row: T) => unknown;
|
|
33
|
+
id: string;
|
|
34
|
+
accessorKey?: never;
|
|
35
|
+
};
|
|
36
|
+
export type DataTableLeafColumn<T extends RowData> = DataTableAccessorKeyColumn<T> | DataTableAccessorFnColumn<T>;
|
|
37
|
+
export type DataTableGroupColumn<T extends RowData> = DataTableColumnBase & {
|
|
38
|
+
id?: string;
|
|
39
|
+
columns: DataTableColumn<T>[];
|
|
40
|
+
accessorKey?: never;
|
|
41
|
+
accessorFn?: never;
|
|
42
|
+
cell?: never;
|
|
43
|
+
cellComponent?: never;
|
|
44
|
+
cellProps?: never;
|
|
45
|
+
cellSnippet?: never;
|
|
46
|
+
type?: never;
|
|
47
|
+
formatOptions?: never;
|
|
48
|
+
formatLocale?: never;
|
|
49
|
+
};
|
|
50
|
+
export type DataTableColumn<T extends RowData> = DataTableLeafColumn<T> | DataTableGroupColumn<T>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -24,14 +24,6 @@ class DataTableState {
|
|
|
24
24
|
this.#options = options;
|
|
25
25
|
this.#columns = normalizeDataTableColumns(options.columns);
|
|
26
26
|
const getData = () => this.#getData();
|
|
27
|
-
$effect(() => {
|
|
28
|
-
$inspect.trace('[DataTable] #options changed');
|
|
29
|
-
void this.#options;
|
|
30
|
-
});
|
|
31
|
-
$effect(() => {
|
|
32
|
-
$inspect.trace('[DataTable] getData() re-read');
|
|
33
|
-
void getData();
|
|
34
|
-
});
|
|
35
27
|
this.#table = createTanStackTable({
|
|
36
28
|
...options,
|
|
37
29
|
columns: this.#columns,
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends RowData, TSelected = object">
|
|
2
|
+
import * as Popover from '../popover';
|
|
3
|
+
import * as ScrollArea from '../scroll-area';
|
|
4
|
+
import Checkbox from '../checkbox/checkbox.svelte';
|
|
5
|
+
import type { Column, RowData } from '@tanstack/svelte-table';
|
|
6
|
+
import type { DataTable, DataTableFeatures, DataTableSelectedState } from './core';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
table: DataTable<TData, TSelected>;
|
|
10
|
+
triggerLabel?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let { table, triggerLabel = 'Columns' }: Props = $props();
|
|
14
|
+
|
|
15
|
+
const columnVisibility = $derived(
|
|
16
|
+
(table.state as unknown as DataTableSelectedState).columnVisibility
|
|
17
|
+
);
|
|
18
|
+
const allColumnsVisible = $derived(table.getIsAllColumnsVisible());
|
|
19
|
+
const someColumnsVisible = $derived(
|
|
20
|
+
table.getAllLeafColumns().some((column) => column.getCanHide() && column.getIsVisible())
|
|
21
|
+
);
|
|
22
|
+
const allColumnsVisibilityState = $derived(
|
|
23
|
+
allColumnsVisible ? true : someColumnsVisible ? 'indeterminate' : false
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
function getColumnLabel(column: Column<DataTableFeatures, TData, unknown>) {
|
|
27
|
+
return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getColumnIsVisible(
|
|
31
|
+
column: Column<DataTableFeatures, TData, unknown>,
|
|
32
|
+
visibilityState: unknown
|
|
33
|
+
) {
|
|
34
|
+
void visibilityState;
|
|
35
|
+
return column.getIsVisible();
|
|
36
|
+
}
|
|
37
|
+
</script>
|
|
38
|
+
|
|
39
|
+
<Popover.Root positioning={{ placement: 'bottom-end' }}>
|
|
40
|
+
<Popover.Trigger
|
|
41
|
+
class="flex h-9 cursor-pointer items-center rounded-md border border-surface-3 bg-surface-1 px-3 text-sm font-medium text-ink shadow-sm outline-none hover:bg-surface-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring"
|
|
42
|
+
>
|
|
43
|
+
{triggerLabel}
|
|
44
|
+
</Popover.Trigger>
|
|
45
|
+
|
|
46
|
+
<Popover.Content class="w-56 p-2" showArrow={false}>
|
|
47
|
+
<div class="border-b border-surface-3 px-2 pb-2">
|
|
48
|
+
<Checkbox
|
|
49
|
+
size="sm"
|
|
50
|
+
label="All columns"
|
|
51
|
+
checked={allColumnsVisibilityState}
|
|
52
|
+
onCheckedChange={({ checked }) => table.toggleAllColumnsVisible(checked === true)}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<ScrollArea.Root class="h-72">
|
|
57
|
+
<ScrollArea.Viewport>
|
|
58
|
+
<ScrollArea.Content class="py-1 pe-3">
|
|
59
|
+
<div class="flex flex-col">
|
|
60
|
+
{#each table.getAllLeafColumns() as column (column.id)}
|
|
61
|
+
<Checkbox
|
|
62
|
+
size="md"
|
|
63
|
+
label={getColumnLabel(column)}
|
|
64
|
+
class="min-h-8 rounded-sm px-2 hover:bg-surface-2"
|
|
65
|
+
checked={getColumnIsVisible(column, columnVisibility)}
|
|
66
|
+
disabled={!column.getCanHide()}
|
|
67
|
+
onCheckedChange={({ checked }) => column.toggleVisibility(checked === true)}
|
|
68
|
+
/>
|
|
69
|
+
{/each}
|
|
70
|
+
</div>
|
|
71
|
+
</ScrollArea.Content>
|
|
72
|
+
</ScrollArea.Viewport>
|
|
73
|
+
<ScrollArea.Scrollbar orientation="vertical">
|
|
74
|
+
<ScrollArea.Thumb />
|
|
75
|
+
</ScrollArea.Scrollbar>
|
|
76
|
+
<ScrollArea.Corner />
|
|
77
|
+
</ScrollArea.Root>
|
|
78
|
+
</Popover.Content>
|
|
79
|
+
</Popover.Root>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { RowData } from '@tanstack/svelte-table';
|
|
2
|
+
import type { DataTable } from './core';
|
|
3
|
+
declare function $$render<TData extends RowData, TSelected = object>(): {
|
|
4
|
+
props: {
|
|
5
|
+
table: DataTable<TData, TSelected>;
|
|
6
|
+
triggerLabel?: string;
|
|
7
|
+
};
|
|
8
|
+
exports: {};
|
|
9
|
+
bindings: "";
|
|
10
|
+
slots: {};
|
|
11
|
+
events: {};
|
|
12
|
+
};
|
|
13
|
+
declare class __sveltets_Render<TData extends RowData, TSelected = object> {
|
|
14
|
+
props(): ReturnType<typeof $$render<TData, TSelected>>['props'];
|
|
15
|
+
events(): ReturnType<typeof $$render<TData, TSelected>>['events'];
|
|
16
|
+
slots(): ReturnType<typeof $$render<TData, TSelected>>['slots'];
|
|
17
|
+
bindings(): "";
|
|
18
|
+
exports(): {};
|
|
19
|
+
}
|
|
20
|
+
interface $$IsomorphicComponent {
|
|
21
|
+
new <TData extends RowData, TSelected = object>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<TData, TSelected>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<TData, TSelected>['props']>, ReturnType<__sveltets_Render<TData, TSelected>['events']>, ReturnType<__sveltets_Render<TData, TSelected>['slots']>> & {
|
|
22
|
+
$$bindings?: ReturnType<__sveltets_Render<TData, TSelected>['bindings']>;
|
|
23
|
+
} & ReturnType<__sveltets_Render<TData, TSelected>['exports']>;
|
|
24
|
+
<TData extends RowData, TSelected = object>(internal: unknown, props: ReturnType<__sveltets_Render<TData, TSelected>['props']> & {}): ReturnType<__sveltets_Render<TData, TSelected>['exports']>;
|
|
25
|
+
z_$$bindings?: ReturnType<__sveltets_Render<any, any>['bindings']>;
|
|
26
|
+
}
|
|
27
|
+
declare const DataTableColumnVisibility: $$IsomorphicComponent;
|
|
28
|
+
type DataTableColumnVisibility<TData extends RowData, TSelected = object> = InstanceType<typeof DataTableColumnVisibility<TData, TSelected>>;
|
|
29
|
+
export default DataTableColumnVisibility;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type ClassValue } from 'tailwind-variants';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLHeadingElement>, 'class'> & {
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<h2 class={cn('text-lg font-semibold text-ink', className)} {...rest}>
|
|
15
|
+
{@render children?.()}
|
|
16
|
+
</h2>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLHeadingElement>, 'class'> & {
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const DataTableTitle: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type DataTableTitle = ReturnType<typeof DataTableTitle>;
|
|
10
|
+
export default DataTableTitle;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn, type ClassValue } from 'tailwind-variants';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
|
|
7
|
+
class?: ClassValue;
|
|
8
|
+
children?: Snippet;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { class: className, children, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div class={cn('mb-4 flex flex-wrap items-center justify-between gap-3', className)} {...rest}>
|
|
15
|
+
{@render children?.()}
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ClassValue } from 'tailwind-variants';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
|
|
5
|
+
class?: ClassValue;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const DataTableToolbar: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type DataTableToolbar = ReturnType<typeof DataTableToolbar>;
|
|
10
|
+
export default DataTableToolbar;
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
<script lang="ts" generics="TData extends RowData, TSelected = object">
|
|
2
|
+
import { useLocaleContext } from '@ark-ui/svelte/locale';
|
|
3
|
+
import { FlexRender } from '@tanstack/svelte-table';
|
|
4
|
+
import { cn, type ClassValue } from 'tailwind-variants';
|
|
5
|
+
import { PhArrowSquareOut, PhCaretDown, PhCaretUp } from '../../icons';
|
|
6
|
+
import Checkbox from '../checkbox/checkbox.svelte';
|
|
7
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
8
|
+
import type { Header, RowData } from '@tanstack/svelte-table';
|
|
9
|
+
import {
|
|
10
|
+
getDataTableCellConfig,
|
|
11
|
+
hasCustomDataTableCell,
|
|
12
|
+
type DataTable,
|
|
13
|
+
type DataTableCell,
|
|
14
|
+
type DataTableCellConfig,
|
|
15
|
+
type DataTableColumnAlign,
|
|
16
|
+
type DataTableColumnMeta,
|
|
17
|
+
type DataTableFeatures
|
|
18
|
+
} from './core';
|
|
19
|
+
|
|
20
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'class'> & {
|
|
21
|
+
table: DataTable<TData, TSelected>;
|
|
22
|
+
class?: ClassValue;
|
|
23
|
+
tableClass?: ClassValue;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let { table, class: className, tableClass, ...rest }: Props = $props();
|
|
27
|
+
|
|
28
|
+
const locale = useLocaleContext();
|
|
29
|
+
const columnCount = $derived(table.getVisibleLeafColumns().length);
|
|
30
|
+
const headerGroupCount = $derived(table.getHeaderGroups().length);
|
|
31
|
+
const isRowSelectionEnabled = $derived(Boolean(table.options.enableRowSelection));
|
|
32
|
+
const isMultiRowSelectionEnabled = $derived(table.options.enableMultiRowSelection !== false);
|
|
33
|
+
const tableColumnCount = $derived(columnCount + (isRowSelectionEnabled ? 1 : 0));
|
|
34
|
+
const allRowsSelected = $derived(table.getIsAllRowsSelected());
|
|
35
|
+
const someRowsSelected = $derived(table.getIsSomeRowsSelected());
|
|
36
|
+
const allRowsSelectionState = $derived(
|
|
37
|
+
allRowsSelected ? true : someRowsSelected ? 'indeterminate' : false
|
|
38
|
+
);
|
|
39
|
+
const rowSelectionColumnSize = 40;
|
|
40
|
+
const tableSize = $derived(
|
|
41
|
+
table.getTotalSize() + (isRowSelectionEnabled ? rowSelectionColumnSize : 0)
|
|
42
|
+
);
|
|
43
|
+
const isColumnResizing = $derived(table.store.state.columnResizing.isResizingColumn !== false);
|
|
44
|
+
|
|
45
|
+
function formatNumericValue(
|
|
46
|
+
value: unknown,
|
|
47
|
+
options: Intl.NumberFormatOptions & { locale?: string }
|
|
48
|
+
) {
|
|
49
|
+
if (typeof value !== 'number') return formatTextValue(value);
|
|
50
|
+
const { locale: optionLocale, ...numberFormatOptions } = options;
|
|
51
|
+
return new Intl.NumberFormat(optionLocale ?? locale().locale, numberFormatOptions).format(
|
|
52
|
+
value
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatTextValue(value: unknown, fallback = '') {
|
|
57
|
+
if (value === null || value === undefined || value === '') return fallback;
|
|
58
|
+
return String(value);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function fractionOptions(options: { fractionDigits?: number }) {
|
|
62
|
+
if (options.fractionDigits === undefined) return {};
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
minimumFractionDigits: options.fractionDigits,
|
|
66
|
+
maximumFractionDigits: options.fractionDigits
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isNumericCellConfig(cellConfig: DataTableCellConfig<TData> | undefined) {
|
|
71
|
+
return (
|
|
72
|
+
cellConfig?.type === 'number' ||
|
|
73
|
+
cellConfig?.type === 'currency' ||
|
|
74
|
+
cellConfig?.type === 'percentage'
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getLinkHref<TValue>(
|
|
79
|
+
cell: DataTableCell<TData, TValue>,
|
|
80
|
+
href: string | ((value: TValue, row: TData) => string) | undefined
|
|
81
|
+
) {
|
|
82
|
+
const value = cell.getValue();
|
|
83
|
+
if (!href) return formatTextValue(value);
|
|
84
|
+
return typeof href === 'function' ? href(value, cell.row.original) : href;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function getLinkLabel(value: unknown, fallback = 'Open link') {
|
|
88
|
+
return formatTextValue(value, fallback);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function getCellMeta<TValue>(cell: DataTableCell<TData, TValue>) {
|
|
92
|
+
return (cell.column.columnDef.meta as DataTableColumnMeta<TData, TValue> | undefined)
|
|
93
|
+
?.dataTable;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getHeaderMeta(header: Header<DataTableFeatures, TData, unknown>) {
|
|
97
|
+
return header.column.columnDef.meta as DataTableColumnMeta<TData> | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getColumnAlign(
|
|
101
|
+
align: DataTableColumnAlign | undefined,
|
|
102
|
+
cellConfig: DataTableCellConfig<TData> | undefined
|
|
103
|
+
) {
|
|
104
|
+
return align ?? (isNumericCellConfig(cellConfig) ? 'right' : 'left');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function alignClass(align: DataTableColumnAlign | undefined) {
|
|
108
|
+
return {
|
|
109
|
+
left: 'text-left',
|
|
110
|
+
center: 'text-center',
|
|
111
|
+
right: 'text-right'
|
|
112
|
+
}[align ?? 'left'];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function justifyClass(align: DataTableColumnAlign | undefined) {
|
|
116
|
+
return {
|
|
117
|
+
left: 'justify-start',
|
|
118
|
+
center: 'justify-center',
|
|
119
|
+
right: 'justify-end'
|
|
120
|
+
}[align ?? 'left'];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sortButtonDirectionClass(align: DataTableColumnAlign | undefined) {
|
|
124
|
+
return align === 'right' ? 'flex-row-reverse' : 'flex-row';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function columnSizeStyle(size: number) {
|
|
128
|
+
return `width: ${size}px`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function tableSizeStyle() {
|
|
132
|
+
return `width: max(100%, ${tableSize}px)`;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function resizeHandleStyle(header: Header<DataTableFeatures, TData, unknown>) {
|
|
136
|
+
if (table.options.columnResizeMode !== 'onEnd') return undefined;
|
|
137
|
+
const deltaOffset = table.store.state.columnResizing.deltaOffset;
|
|
138
|
+
if (!header.column.getIsResizing() || deltaOffset === null) return undefined;
|
|
139
|
+
return `transform: translateX(${deltaOffset}px)`;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getHeaderSortDirection(
|
|
143
|
+
header: Header<DataTableFeatures, TData, unknown>,
|
|
144
|
+
sortingState: unknown
|
|
145
|
+
) {
|
|
146
|
+
void sortingState;
|
|
147
|
+
return header.column.getIsSorted();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function getHeaderSortLabel(sortDirection: false | 'asc' | 'desc') {
|
|
151
|
+
if (sortDirection === 'asc') return 'Sorted ascending';
|
|
152
|
+
if (sortDirection === 'desc') return 'Sorted descending';
|
|
153
|
+
return 'Not sorted';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function getHeaderAriaSort(sortDirection: false | 'asc' | 'desc') {
|
|
157
|
+
if (sortDirection === 'asc') return 'ascending';
|
|
158
|
+
if (sortDirection === 'desc') return 'descending';
|
|
159
|
+
return 'none';
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
<div
|
|
164
|
+
class={cn('max-h-full min-h-0 overflow-auto rounded-lg border border-surface-3', className)}
|
|
165
|
+
{...rest}
|
|
166
|
+
>
|
|
167
|
+
{#if isColumnResizing}
|
|
168
|
+
<div aria-hidden="true" class="fixed inset-0 z-50 cursor-col-resize select-none"></div>
|
|
169
|
+
{/if}
|
|
170
|
+
|
|
171
|
+
<table class={cn('table-fixed border-collapse text-sm', tableClass)} style={tableSizeStyle()}>
|
|
172
|
+
<colgroup>
|
|
173
|
+
{#if isRowSelectionEnabled}
|
|
174
|
+
<col style={columnSizeStyle(rowSelectionColumnSize)} />
|
|
175
|
+
{/if}
|
|
176
|
+
{#each table.getVisibleLeafColumns() as column (column.id)}
|
|
177
|
+
<col style={columnSizeStyle(column.getSize())} />
|
|
178
|
+
{/each}
|
|
179
|
+
</colgroup>
|
|
180
|
+
<thead class="sticky top-0 z-20 bg-surface-2 text-left text-ink-dim">
|
|
181
|
+
{#each table.getHeaderGroups() as headerGroup, headerGroupIndex (headerGroup.id)}
|
|
182
|
+
<tr>
|
|
183
|
+
{#if isRowSelectionEnabled && headerGroupIndex === 0}
|
|
184
|
+
<th
|
|
185
|
+
class="border-b border-surface-3 bg-surface-2 px-3 py-2 text-center align-middle font-medium"
|
|
186
|
+
rowspan={headerGroupCount}
|
|
187
|
+
>
|
|
188
|
+
{#if isMultiRowSelectionEnabled}
|
|
189
|
+
<Checkbox
|
|
190
|
+
size="sm"
|
|
191
|
+
aria-label="Select all rows"
|
|
192
|
+
class="mx-auto size-4"
|
|
193
|
+
checked={allRowsSelectionState}
|
|
194
|
+
onCheckedChange={({ checked }) => table.toggleAllRowsSelected(checked === true)}
|
|
195
|
+
/>
|
|
196
|
+
{/if}
|
|
197
|
+
</th>
|
|
198
|
+
{/if}
|
|
199
|
+
{#each headerGroup.headers as header (header.id)}
|
|
200
|
+
{@const headerMeta = getHeaderMeta(header)?.dataTable}
|
|
201
|
+
{@const headerAlign = getColumnAlign(headerMeta?.align, headerMeta?.cell)}
|
|
202
|
+
{@const sortDirection = getHeaderSortDirection(header, table.store.state.sorting)}
|
|
203
|
+
<th
|
|
204
|
+
class={cn(
|
|
205
|
+
'relative border-b border-surface-3 bg-surface-2 px-3 py-2 font-medium',
|
|
206
|
+
alignClass(headerAlign)
|
|
207
|
+
)}
|
|
208
|
+
colspan={header.colSpan}
|
|
209
|
+
aria-sort={header.column.getCanSort() ? getHeaderAriaSort(sortDirection) : undefined}
|
|
210
|
+
>
|
|
211
|
+
{#if !header.isPlaceholder}
|
|
212
|
+
{#if header.column.getCanSort()}
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
215
|
+
class={cn(
|
|
216
|
+
'inline-flex max-w-full items-center gap-1 rounded-sm outline-none hover:text-ink data-focus-visible:outline-2 data-focus-visible:outline-offset-2 data-focus-visible:outline-ring',
|
|
217
|
+
justifyClass(headerAlign),
|
|
218
|
+
sortButtonDirectionClass(headerAlign)
|
|
219
|
+
)}
|
|
220
|
+
aria-label={`${getHeaderSortLabel(sortDirection)}. Toggle sorting.`}
|
|
221
|
+
onclick={header.column.getToggleSortingHandler()}
|
|
222
|
+
>
|
|
223
|
+
<span class="min-w-0 truncate">
|
|
224
|
+
<FlexRender {header} />
|
|
225
|
+
</span>
|
|
226
|
+
<span
|
|
227
|
+
class="inline-flex size-3.5 shrink-0 items-center justify-center text-ink-dim"
|
|
228
|
+
>
|
|
229
|
+
{#if sortDirection === 'asc'}
|
|
230
|
+
<PhCaretUp class="size-3.5" />
|
|
231
|
+
{:else if sortDirection === 'desc'}
|
|
232
|
+
<PhCaretDown class="size-3.5" />
|
|
233
|
+
{/if}
|
|
234
|
+
</span>
|
|
235
|
+
</button>
|
|
236
|
+
{:else}
|
|
237
|
+
<FlexRender {header} />
|
|
238
|
+
{/if}
|
|
239
|
+
{/if}
|
|
240
|
+
{#if header.column.getCanResize()}
|
|
241
|
+
<div
|
|
242
|
+
aria-hidden="true"
|
|
243
|
+
class={cn(
|
|
244
|
+
'absolute top-0 -right-1 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-[""]'
|
|
245
|
+
)}
|
|
246
|
+
style={resizeHandleStyle(header)}
|
|
247
|
+
ondblclick={() => header.column.resetSize()}
|
|
248
|
+
onmousedown={header.getResizeHandler()}
|
|
249
|
+
ontouchstart={header.getResizeHandler()}
|
|
250
|
+
></div>
|
|
251
|
+
{/if}
|
|
252
|
+
</th>
|
|
253
|
+
{/each}
|
|
254
|
+
</tr>
|
|
255
|
+
{/each}
|
|
256
|
+
</thead>
|
|
257
|
+
<tbody>
|
|
258
|
+
{#each table.getRowModel().rows as row (row.id)}
|
|
259
|
+
<tr class="border-b border-surface-3 last:border-b-0">
|
|
260
|
+
{#if isRowSelectionEnabled}
|
|
261
|
+
<td class="px-3 py-2 text-center align-middle">
|
|
262
|
+
<Checkbox
|
|
263
|
+
size="sm"
|
|
264
|
+
aria-label="Select row"
|
|
265
|
+
class="mx-auto size-4"
|
|
266
|
+
checked={row.getIsSelected()}
|
|
267
|
+
disabled={!row.getCanSelect()}
|
|
268
|
+
onCheckedChange={({ checked }) => row.toggleSelected(checked === true)}
|
|
269
|
+
/>
|
|
270
|
+
</td>
|
|
271
|
+
{/if}
|
|
272
|
+
{#each row.getVisibleCells() as cell (cell.id)}
|
|
273
|
+
{@const cellMeta = getCellMeta(cell)}
|
|
274
|
+
{@const cellConfig = getDataTableCellConfig(cell)}
|
|
275
|
+
{@const cellAlign = getColumnAlign(cellMeta?.align, cellConfig)}
|
|
276
|
+
<td
|
|
277
|
+
class={cn(
|
|
278
|
+
'px-3 py-2',
|
|
279
|
+
alignClass(cellAlign),
|
|
280
|
+
isNumericCellConfig(cellConfig) && 'tabular-nums'
|
|
281
|
+
)}
|
|
282
|
+
>
|
|
283
|
+
{#if cellConfig && !hasCustomDataTableCell(cell)}
|
|
284
|
+
{@const value = cell.getValue()}
|
|
285
|
+
{#if cellConfig.type === 'number'}
|
|
286
|
+
{formatNumericValue(value, {
|
|
287
|
+
...cellConfig,
|
|
288
|
+
...fractionOptions(cellConfig)
|
|
289
|
+
})}
|
|
290
|
+
{:else if cellConfig.type === 'currency'}
|
|
291
|
+
{formatNumericValue(value, {
|
|
292
|
+
...cellConfig,
|
|
293
|
+
...fractionOptions(cellConfig),
|
|
294
|
+
style: 'currency'
|
|
295
|
+
})}
|
|
296
|
+
{:else if cellConfig.type === 'percentage'}
|
|
297
|
+
{formatNumericValue(value, {
|
|
298
|
+
...cellConfig,
|
|
299
|
+
...fractionOptions(cellConfig),
|
|
300
|
+
style: 'percent'
|
|
301
|
+
})}
|
|
302
|
+
{:else if cellConfig.type === 'boolean'}
|
|
303
|
+
{value === true
|
|
304
|
+
? (cellConfig.trueLabel ?? 'Yes')
|
|
305
|
+
: value === false
|
|
306
|
+
? (cellConfig.falseLabel ?? 'No')
|
|
307
|
+
: (cellConfig.nullLabel ?? '')}
|
|
308
|
+
{:else if cellConfig.type === 'link'}
|
|
309
|
+
{@const href = getLinkHref(cell, cellConfig.href)}
|
|
310
|
+
{#if href}
|
|
311
|
+
<a
|
|
312
|
+
class="inline-flex size-7 items-center justify-center rounded-md text-primary hover:bg-surface-2"
|
|
313
|
+
{href}
|
|
314
|
+
target={cellConfig.target ?? '_blank'}
|
|
315
|
+
rel="external noreferrer"
|
|
316
|
+
aria-label={getLinkLabel(value, cellConfig.fallback)}
|
|
317
|
+
title={getLinkLabel(value, cellConfig.fallback)}
|
|
318
|
+
>
|
|
319
|
+
<PhArrowSquareOut class="size-4" />
|
|
320
|
+
</a>
|
|
321
|
+
{:else}
|
|
322
|
+
{formatTextValue(value, cellConfig.fallback)}
|
|
323
|
+
{/if}
|
|
324
|
+
{:else}
|
|
325
|
+
{formatTextValue(value, cellConfig.fallback)}
|
|
326
|
+
{/if}
|
|
327
|
+
{:else}
|
|
328
|
+
<FlexRender {cell} />
|
|
329
|
+
{/if}
|
|
330
|
+
</td>
|
|
331
|
+
{/each}
|
|
332
|
+
</tr>
|
|
333
|
+
{:else}
|
|
334
|
+
<tr>
|
|
335
|
+
<td class="px-3 py-6 text-center text-ink-dim" colspan={tableColumnCount}>
|
|
336
|
+
No results.
|
|
337
|
+
</td>
|
|
338
|
+
</tr>
|
|
339
|
+
{/each}
|
|
340
|
+
</tbody>
|
|
341
|
+
</table>
|
|
342
|
+
</div>
|