compote-ui 0.35.1 → 0.36.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.
Files changed (23) hide show
  1. package/dist/components/data-table/create-table.d.ts +4 -0
  2. package/dist/components/data-table/create-table.js +11 -1
  3. package/dist/components/data-table/data-table.svelte +418 -365
  4. package/dist/components/data-table/types.d.ts +1 -0
  5. package/package.json +1 -1
  6. package/dist/components/data-table-old/core/cells.d.ts +0 -78
  7. package/dist/components/data-table-old/core/cells.js +0 -66
  8. package/dist/components/data-table-old/core/create-table.svelte.d.ts +0 -24
  9. package/dist/components/data-table-old/core/create-table.svelte.js +0 -74
  10. package/dist/components/data-table-old/core/index.d.ts +0 -3
  11. package/dist/components/data-table-old/core/index.js +0 -2
  12. package/dist/components/data-table-old/data-table-column-visibility.svelte +0 -79
  13. package/dist/components/data-table-old/data-table-column-visibility.svelte.d.ts +0 -29
  14. package/dist/components/data-table-old/data-table-filters.svelte +0 -285
  15. package/dist/components/data-table-old/data-table-filters.svelte.d.ts +0 -29
  16. package/dist/components/data-table-old/data-table-title.svelte +0 -16
  17. package/dist/components/data-table-old/data-table-title.svelte.d.ts +0 -10
  18. package/dist/components/data-table-old/data-table-toolbar.svelte +0 -16
  19. package/dist/components/data-table-old/data-table-toolbar.svelte.d.ts +0 -10
  20. package/dist/components/data-table-old/data-table.svelte +0 -342
  21. package/dist/components/data-table-old/data-table.svelte.d.ts +0 -32
  22. package/dist/components/data-table-old/index.d.ts +0 -7
  23. package/dist/components/data-table-old/index.js +0 -7
@@ -21,6 +21,7 @@ export type DataTableLeafColumnBase<T extends RowData> = DataTableColumnOptions<
21
21
  type?: DataTableColumnType;
22
22
  formatOptions?: Intl.NumberFormatOptions;
23
23
  formatLocale?: string;
24
+ pinned?: 'left' | 'right';
24
25
  columns?: never;
25
26
  };
26
27
  export type DataTableAccessorKeyColumn<T extends RowData> = DataTableLeafColumnBase<T> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compote-ui",
3
- "version": "0.35.1",
3
+ "version": "0.36.1",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev --open",
@@ -1,78 +0,0 @@
1
- import type { AccessorFnColumnDef, AccessorKeyColumnDef, Cell, CellData, ColumnDef, DisplayColumnDef, GroupColumnDef, RowData } from '@tanstack/svelte-table';
2
- import type { DataTableFeatures } from './create-table.svelte';
3
- type NumericFormat = {
4
- locale?: string;
5
- minimumFractionDigits?: number;
6
- maximumFractionDigits?: number;
7
- fractionDigits?: number;
8
- };
9
- export type DataTableCellConfig<TData extends RowData = RowData, TValue = unknown> = ({
10
- type: 'text';
11
- } & {
12
- fallback?: string;
13
- }) | ({
14
- type: 'number';
15
- } & NumericFormat) | ({
16
- type: 'currency';
17
- currency: string;
18
- currencyDisplay?: Intl.NumberFormatOptions['currencyDisplay'];
19
- } & NumericFormat) | ({
20
- type: 'percentage';
21
- } & NumericFormat) | {
22
- type: 'boolean';
23
- trueLabel?: string;
24
- falseLabel?: string;
25
- nullLabel?: string;
26
- } | {
27
- type: 'link';
28
- href?: string | ((value: TValue, row: TData) => string);
29
- target?: HTMLAnchorElement['target'];
30
- fallback?: string;
31
- };
32
- export type DataTableCellType<TData extends RowData = RowData, TValue = unknown> = DataTableCellConfig<TData, TValue> | Exclude<DataTableCellConfig<TData, TValue>['type'], 'currency' | 'link'>;
33
- export type DataTableColumnMeta<TData extends RowData = RowData, TValue = unknown> = {
34
- dataTable?: {
35
- align?: DataTableColumnAlign;
36
- cell?: DataTableCellConfig<TData, TValue>;
37
- filter?: DataTableFilterConfig<TValue>;
38
- hasCustomCell?: boolean;
39
- };
40
- };
41
- export type DataTableColumnAlign = 'left' | 'center' | 'right';
42
- export type DataTableFilterOption<TValue = unknown> = {
43
- label: string;
44
- value: TValue;
45
- };
46
- export type DataTableFilterConfig<TValue = unknown> = {
47
- type: 'text';
48
- placeholder?: string;
49
- } | {
50
- type: 'facet';
51
- options?: DataTableFilterOption<TValue>[];
52
- maxOptions?: number;
53
- } | {
54
- type: 'number-range';
55
- min?: number;
56
- max?: number;
57
- step?: number;
58
- formatOptions?: Intl.NumberFormatOptions;
59
- } | {
60
- type: 'boolean';
61
- trueLabel?: string;
62
- falseLabel?: string;
63
- };
64
- export type DataTableFilterType<TValue = unknown> = DataTableFilterConfig<TValue> | DataTableFilterConfig<TValue>['type'];
65
- type DataTableColumnDefOptions<TData extends RowData, TValue> = {
66
- align?: DataTableColumnAlign;
67
- cellType?: DataTableCellType<TData, TValue>;
68
- filter?: DataTableFilterType<TValue>;
69
- };
70
- export type DataTableColumnDef<TData extends RowData, TValue extends CellData = CellData> = (AccessorKeyColumnDef<DataTableFeatures, TData, TValue> & DataTableColumnDefOptions<TData, TValue>) | (AccessorFnColumnDef<DataTableFeatures, TData, TValue> & DataTableColumnDefOptions<TData, TValue>) | (DisplayColumnDef<DataTableFeatures, TData, TValue> & DataTableColumnDefOptions<TData, TValue>) | (Omit<GroupColumnDef<DataTableFeatures, TData, TValue>, 'columns'> & DataTableColumnDefOptions<TData, TValue> & {
71
- columns?: DataTableColumnDef<TData, CellData>[];
72
- });
73
- export type DataTableCell<TData extends RowData = RowData, TValue = unknown> = Cell<DataTableFeatures, TData, TValue>;
74
- export declare function getDataTableCellConfig<TData extends RowData, TValue>(cell: DataTableCell<TData, TValue>): DataTableCellConfig<TData, TValue> | undefined;
75
- export declare function getDataTableFilterConfig<TData extends RowData, TValue>(columnDef: ColumnDef<DataTableFeatures, TData, TValue>): DataTableFilterConfig<TValue> | undefined;
76
- export declare function hasCustomDataTableCell<TData extends RowData, TValue>(cell: DataTableCell<TData, TValue>): boolean;
77
- export declare function normalizeDataTableColumns<TData extends RowData>(columns: DataTableColumnDef<TData>[]): ColumnDef<DataTableFeatures, TData, CellData>[];
78
- export {};
@@ -1,66 +0,0 @@
1
- export function getDataTableCellConfig(cell) {
2
- return cell.column.columnDef.meta?.dataTable
3
- ?.cell;
4
- }
5
- export function getDataTableFilterConfig(columnDef) {
6
- return columnDef.meta?.dataTable?.filter;
7
- }
8
- export function hasCustomDataTableCell(cell) {
9
- return (cell.column.columnDef.meta?.dataTable
10
- ?.hasCustomCell ?? false);
11
- }
12
- export function normalizeDataTableColumns(columns) {
13
- return columns.map((columnDef) => {
14
- const { cellType, filter, align, columns: childColumns, meta, ...column } = columnDef;
15
- const cell = normalizeDataTableCellType(cellType);
16
- const filterConfig = normalizeDataTableFilterType(filter);
17
- const hasCustomCell = 'cell' in column;
18
- const filterFn = 'filterFn' in column ? column.filterFn : getDefaultDataTableFilterFn(filterConfig);
19
- const nextMeta = cell || align || filterConfig
20
- ? {
21
- ...(meta ?? {}),
22
- dataTable: {
23
- ...(meta?.dataTable ?? {}),
24
- ...(align ? { align } : {}),
25
- ...(cell ? { cell } : {}),
26
- ...(filterConfig ? { filter: filterConfig } : {}),
27
- hasCustomCell
28
- }
29
- }
30
- : meta;
31
- return {
32
- ...column,
33
- ...(childColumns ? { columns: normalizeDataTableColumns(childColumns) } : {}),
34
- ...(filterFn ? { filterFn } : {}),
35
- ...(nextMeta ? { meta: nextMeta } : {})
36
- };
37
- });
38
- }
39
- function normalizeDataTableCellType(cellType) {
40
- if (!cellType)
41
- return undefined;
42
- return typeof cellType === 'string' ? { type: cellType } : cellType;
43
- }
44
- function normalizeDataTableFilterType(filter) {
45
- if (!filter)
46
- return undefined;
47
- return typeof filter === 'string' ? { type: filter } : filter;
48
- }
49
- function getDefaultDataTableFilterFn(filter) {
50
- if (!filter)
51
- return undefined;
52
- if (filter.type === 'facet')
53
- return dataTableFacetFilter;
54
- if (filter.type === 'number-range')
55
- return 'inNumberRange';
56
- if (filter.type === 'boolean')
57
- return 'equals';
58
- return 'includesString';
59
- }
60
- const dataTableFacetFilter = (row, columnId, filterValue) => {
61
- if (!Array.isArray(filterValue) || filterValue.length === 0)
62
- return true;
63
- const value = row.getValue(columnId);
64
- return filterValue.some((filterItem) => Object.is(filterItem, value));
65
- };
66
- dataTableFacetFilter.autoRemove = (filterValue) => !Array.isArray(filterValue) || filterValue.length === 0;
@@ -1,24 +0,0 @@
1
- import type { RowData, SvelteTable, TableOptions, TableState, Updater } from '@tanstack/svelte-table';
2
- import { type DataTableColumnDef } from './cells';
3
- declare const dataTableFeatures: {
4
- columnFilteringFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").ColumnFilteringFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
5
- columnFacetingFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").ColumnFacetingFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
6
- columnVisibilityFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").ColumnVisibilityFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
7
- columnSizingFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").ColumnSizingFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
8
- columnResizingFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").ColumnResizingFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
9
- rowSelectionFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").RowSelectionFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
10
- rowSortingFeature: import("@tanstack/table-core").TableFeature<import("@tanstack/table-core").RowSortingFeatureConstructors<import("@tanstack/table-core").TableFeatures, RowData>>;
11
- };
12
- export type DataTableFeatures = typeof dataTableFeatures;
13
- type DataTableData<TData extends RowData> = ReadonlyArray<TData> | (() => ReadonlyArray<TData>);
14
- export type DataTableSelectedState = Pick<TableState<DataTableFeatures>, 'columnFilters' | 'columnResizing' | 'columnVisibility' | 'rowSelection' | 'sorting'>;
15
- export type DataTableOptions<TData extends RowData> = Omit<TableOptions<DataTableFeatures, TData>, '_features' | 'data' | 'columns'> & {
16
- data: DataTableData<TData>;
17
- columns: DataTableColumnDef<TData>[];
18
- } & Partial<Pick<TableOptions<DataTableFeatures, TData>, '_features'>>;
19
- export type DataTable<TData extends RowData, TSelected = DataTableSelectedState> = SvelteTable<DataTableFeatures, TData, TSelected> & {
20
- setData: (data: ReadonlyArray<TData>) => void;
21
- updateData: (updater: Updater<ReadonlyArray<TData>>) => void;
22
- };
23
- export declare function createDataTable<TData extends RowData, TSelected = DataTableSelectedState>(options: DataTableOptions<TData>, selector?: (state: TableState<DataTableFeatures>) => TSelected): DataTable<TData, TSelected>;
24
- export {};
@@ -1,74 +0,0 @@
1
- import { columnFacetingFeature, columnFilteringFeature, columnVisibilityFeature, columnResizingFeature, columnSizingFeature, createFacetedMinMaxValues, createFacetedRowModel, createFacetedUniqueValues, createCoreRowModel, createFilteredRowModel, createSortedRowModel, createTable as createTanStackTable, filterFns, functionalUpdate, rowSortingFeature, rowSelectionFeature, sortFns, tableFeatures } from '@tanstack/svelte-table';
2
- import { normalizeDataTableColumns } from './cells';
3
- const dataTableFeatures = tableFeatures({
4
- columnFilteringFeature,
5
- columnFacetingFeature,
6
- columnVisibilityFeature,
7
- columnSizingFeature,
8
- columnResizingFeature,
9
- rowSelectionFeature,
10
- rowSortingFeature
11
- });
12
- const dataTableStateSelector = (state) => ({
13
- columnFilters: state.columnFilters,
14
- columnResizing: state.columnResizing,
15
- columnVisibility: state.columnVisibility,
16
- rowSelection: state.rowSelection,
17
- sorting: state.sorting
18
- });
19
- class DataTableState {
20
- #options = $state.raw({});
21
- #table;
22
- #columns;
23
- constructor(options, selector) {
24
- this.#options = options;
25
- this.#columns = normalizeDataTableColumns(options.columns);
26
- const getData = () => this.#getData();
27
- this.#table = createTanStackTable({
28
- ...options,
29
- columns: this.#columns,
30
- enableRowSelection: options.enableRowSelection ?? false,
31
- get data() {
32
- return getData();
33
- },
34
- _features: options._features ?? dataTableFeatures,
35
- _rowModels: {
36
- coreRowModel: createCoreRowModel(),
37
- filteredRowModel: createFilteredRowModel(filterFns),
38
- facetedRowModel: createFacetedRowModel(),
39
- facetedUniqueValues: createFacetedUniqueValues(),
40
- facetedMinMaxValues: createFacetedMinMaxValues(),
41
- sortedRowModel: createSortedRowModel(sortFns),
42
- ...options._rowModels
43
- }
44
- }, selector ?? dataTableStateSelector);
45
- }
46
- get table() {
47
- const table = this.#table;
48
- table.setData = (data) => {
49
- this.setData(data);
50
- };
51
- table.updateData = (updater) => {
52
- this.updateData(updater);
53
- };
54
- return table;
55
- }
56
- setData(data) {
57
- this.#options = {
58
- ...this.#options,
59
- data
60
- };
61
- }
62
- updateData(updater) {
63
- this.setData(functionalUpdate(updater, this.#getData()));
64
- }
65
- #resolveData(data) {
66
- return typeof data === 'function' ? data() : data;
67
- }
68
- #getData() {
69
- return this.#resolveData(this.#options.data);
70
- }
71
- }
72
- export function createDataTable(options, selector) {
73
- return new DataTableState(options, selector).table;
74
- }
@@ -1,3 +0,0 @@
1
- export { createDataTable } from './create-table.svelte';
2
- export type { DataTable, DataTableFeatures, DataTableOptions, DataTableSelectedState } from './create-table.svelte';
3
- export { getDataTableCellConfig, getDataTableFilterConfig, hasCustomDataTableCell, normalizeDataTableColumns, type DataTableCell, type DataTableCellConfig, type DataTableCellType, type DataTableColumnAlign, type DataTableColumnDef, type DataTableColumnMeta, type DataTableFilterConfig, type DataTableFilterOption, type DataTableFilterType } from './cells';
@@ -1,2 +0,0 @@
1
- export { createDataTable } from './create-table.svelte';
2
- export { getDataTableCellConfig, getDataTableFilterConfig, hasCustomDataTableCell, normalizeDataTableColumns } from './cells';
@@ -1,79 +0,0 @@
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>
@@ -1,29 +0,0 @@
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;
@@ -1,285 +0,0 @@
1
- <script lang="ts" generics="TData extends RowData, TSelected = object">
2
- import * as Field from '../field';
3
- import * as Popover from '../popover';
4
- import * as ScrollArea from '../scroll-area';
5
- import * as Fieldset from '../fieldset';
6
- import Checkbox from '../checkbox/checkbox.svelte';
7
- import Button from '../button/button.svelte';
8
- import NumberInput from '../number-input/number-input.svelte';
9
- import Select from '../select/select.svelte';
10
- import type { Column, RowData } from '@tanstack/svelte-table';
11
- import {
12
- getDataTableFilterConfig,
13
- type DataTable,
14
- type DataTableFeatures,
15
- type DataTableFilterConfig,
16
- type DataTableFilterOption,
17
- type DataTableSelectedState
18
- } from './core';
19
-
20
- type Props = {
21
- table: DataTable<TData, TSelected>;
22
- triggerLabel?: string;
23
- };
24
-
25
- type FilterableColumn = Column<DataTableFeatures, TData, unknown>;
26
-
27
- let { table, triggerLabel = 'Filters' }: Props = $props();
28
-
29
- const columnFilters = $derived((table.state as unknown as DataTableSelectedState).columnFilters);
30
- const activeFilterCount = $derived(columnFilters.length);
31
- const filterableColumns = $derived(
32
- table
33
- .getAllLeafColumns()
34
- .filter((column) => column.getCanFilter() && getDataTableFilterConfig(column.columnDef))
35
- );
36
-
37
- function getColumnLabel(column: FilterableColumn) {
38
- return typeof column.columnDef.header === 'string' ? column.columnDef.header : column.id;
39
- }
40
-
41
- function getFilterConfig(column: FilterableColumn, filtersState: unknown) {
42
- void filtersState;
43
- return getDataTableFilterConfig(column.columnDef);
44
- }
45
-
46
- function getFilterValue(column: FilterableColumn, filtersState: unknown) {
47
- void filtersState;
48
- return column.getFilterValue();
49
- }
50
-
51
- function setTextFilter(column: FilterableColumn, value: string) {
52
- column.setFilterValue(value || undefined);
53
- }
54
-
55
- function setBooleanFilter(column: FilterableColumn, value: string) {
56
- if (value === 'true') {
57
- column.setFilterValue(true);
58
- return;
59
- }
60
-
61
- if (value === 'false') {
62
- column.setFilterValue(false);
63
- return;
64
- }
65
-
66
- column.setFilterValue(undefined);
67
- }
68
-
69
- function setNumberRangeFilter(column: FilterableColumn, index: 0 | 1, value: number | null) {
70
- const currentValue = column.getFilterValue();
71
- const current = Array.isArray(currentValue) ? currentValue : [];
72
- const next = [current[0], current[1]] as [number | undefined, number | undefined];
73
- next[index] = value ?? undefined;
74
-
75
- column.setFilterValue(next[0] === undefined && next[1] === undefined ? undefined : next);
76
- }
77
-
78
- function getNumberRangeValue(
79
- column: FilterableColumn,
80
- filtersState: unknown
81
- ): [number | undefined, number | undefined] {
82
- const value = getFilterValue(column, filtersState);
83
- return Array.isArray(value) ? [value[0], value[1]] : [undefined, undefined];
84
- }
85
-
86
- function getFacetOptions(
87
- column: FilterableColumn,
88
- config: DataTableFilterConfig,
89
- filtersState: unknown
90
- ): DataTableFilterOption[] {
91
- void filtersState;
92
-
93
- if (config.type !== 'facet') return [];
94
- if (config.options) return config.options;
95
-
96
- return Array.from(column.getFacetedUniqueValues().entries())
97
- .sort(([a], [b]) => String(a).localeCompare(String(b)))
98
- .slice(0, config.maxOptions ?? 20)
99
- .map(([value, count]) => ({
100
- value,
101
- label: `${String(value)} (${count})`
102
- }));
103
- }
104
-
105
- function getFacetFilterValue(column: FilterableColumn, filtersState: unknown) {
106
- const value = getFilterValue(column, filtersState);
107
- return Array.isArray(value) ? value : [];
108
- }
109
-
110
- function getFacetOptionChecked(
111
- column: FilterableColumn,
112
- option: DataTableFilterOption,
113
- filtersState: unknown
114
- ) {
115
- return getFacetFilterValue(column, filtersState).some((value) =>
116
- Object.is(value, option.value)
117
- );
118
- }
119
-
120
- function toggleFacetFilterValue(
121
- column: FilterableColumn,
122
- option: DataTableFilterOption,
123
- checked: boolean
124
- ) {
125
- const currentValue = column.getFilterValue();
126
- const current = Array.isArray(currentValue) ? currentValue : [];
127
- const next = checked
128
- ? [...current, option.value]
129
- : current.filter((value) => !Object.is(value, option.value));
130
-
131
- column.setFilterValue(next.length > 0 ? next : undefined);
132
- }
133
-
134
- function getBooleanFilterValue(column: FilterableColumn, filtersState: unknown) {
135
- const value = getFilterValue(column, filtersState);
136
- if (value === true) return 'true';
137
- if (value === false) return 'false';
138
- return null;
139
- }
140
-
141
- function getBooleanLabel(config: DataTableFilterConfig, value: boolean) {
142
- if (config.type !== 'boolean') return value ? 'True' : 'False';
143
- return value ? (config.trueLabel ?? 'True') : (config.falseLabel ?? 'False');
144
- }
145
-
146
- function getBooleanFilterItems(config: DataTableFilterConfig) {
147
- return [
148
- { value: 'true', label: getBooleanLabel(config, true) },
149
- { value: 'false', label: getBooleanLabel(config, false) }
150
- ];
151
- }
152
- </script>
153
-
154
- <Popover.Root positioning={{ placement: 'bottom-end' }}>
155
- <Popover.Trigger
156
- class="flex h-9 cursor-pointer items-center gap-2 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"
157
- >
158
- <span>{triggerLabel}</span>
159
- {#if activeFilterCount > 0}
160
- <span class="rounded-full bg-surface-3 px-1.5 text-xs text-ink-dim">
161
- {activeFilterCount}
162
- </span>
163
- {/if}
164
- </Popover.Trigger>
165
-
166
- <Popover.Content class="w-80 p-0" showArrow={false}>
167
- <Popover.Title class="flex items-center justify-between gap-3 border-b px-4 py-2">
168
- <div class="text-sm font-medium text-ink">Filters</div>
169
- <Button
170
- variant="ghost"
171
- disabled={activeFilterCount === 0}
172
- onclick={() => table.resetColumnFilters(true)}
173
- >
174
- Clear
175
- </Button>
176
- </Popover.Title>
177
-
178
- {#if filterableColumns.length === 0}
179
- <div class="py-2 text-sm text-ink-dim">No filters available</div>
180
- {:else}
181
- <ScrollArea.Root class="h-96">
182
- <ScrollArea.Viewport>
183
- <ScrollArea.Content>
184
- <div class="flex flex-col gap-4">
185
- {#each filterableColumns as column (column.id)}
186
- {@const config = getFilterConfig(column, columnFilters)}
187
- {#if config}
188
- <div class="space-y-2">
189
- {#if config.type === 'text'}
190
- <Field.Root>
191
- <Field.Label>{getColumnLabel(column)}</Field.Label>
192
- <Field.Input
193
- type="search"
194
- placeholder={config.placeholder ?? `Search ${getColumnLabel(column)}`}
195
- value={String(getFilterValue(column, columnFilters) ?? '')}
196
- oninput={(event) =>
197
- setTextFilter(column, (event.currentTarget as HTMLInputElement).value)}
198
- />
199
- </Field.Root>
200
- {:else if config.type === 'number-range'}
201
- {@const rangeValue = getNumberRangeValue(column, columnFilters)}
202
- <div class="text-sm font-medium text-ink">{getColumnLabel(column)}</div>
203
- <div class="grid grid-cols-2 gap-2">
204
- <NumberInput
205
- label="Min"
206
- min={config.min}
207
- max={config.max}
208
- step={config.step}
209
- formatOptions={config.formatOptions ?? { minimumFractionDigits: 0 }}
210
- value={rangeValue[0] ?? null}
211
- onValueChange={({ valueAsNumber }) =>
212
- setNumberRangeFilter(
213
- column,
214
- 0,
215
- Number.isNaN(valueAsNumber) ? null : valueAsNumber
216
- )}
217
- />
218
- <NumberInput
219
- label="Max"
220
- min={config.min}
221
- max={config.max}
222
- step={config.step}
223
- formatOptions={config.formatOptions ?? { minimumFractionDigits: 0 }}
224
- value={rangeValue[1] ?? null}
225
- onValueChange={({ valueAsNumber }) =>
226
- setNumberRangeFilter(
227
- column,
228
- 1,
229
- Number.isNaN(valueAsNumber) ? null : valueAsNumber
230
- )}
231
- />
232
- </div>
233
- {:else if config.type === 'boolean'}
234
- <Select
235
- label={getColumnLabel(column)}
236
- items={getBooleanFilterItems(config)}
237
- placeholder="All"
238
- value={getBooleanFilterValue(column, columnFilters)}
239
- onValueChange={({ value }) =>
240
- setBooleanFilter(
241
- column,
242
- Array.isArray(value) && value.length > 0 ? value[0] : ''
243
- )}
244
- />
245
- {:else if config.type === 'facet'}
246
- <Fieldset.Root class="max-h-44">
247
- <Fieldset.Legend>{getColumnLabel(column)}</Fieldset.Legend>
248
- <ScrollArea.Root class="hrounded-md border border-surface-3">
249
- <ScrollArea.Viewport>
250
- <ScrollArea.Content class="p-1 pe-3">
251
- <div class="flex flex-col">
252
- {#each getFacetOptions(column, config, columnFilters) as option (String(option.value))}
253
- <Checkbox
254
- size="sm"
255
- label={option.label}
256
- class="min-h-8 rounded-sm px-2 hover:bg-surface-2"
257
- checked={getFacetOptionChecked(column, option, columnFilters)}
258
- onCheckedChange={({ checked }) =>
259
- toggleFacetFilterValue(column, option, checked === true)}
260
- />
261
- {/each}
262
- </div>
263
- </ScrollArea.Content>
264
- </ScrollArea.Viewport>
265
- <ScrollArea.Scrollbar orientation="vertical">
266
- <ScrollArea.Thumb />
267
- </ScrollArea.Scrollbar>
268
- <ScrollArea.Corner />
269
- </ScrollArea.Root>
270
- </Fieldset.Root>
271
- {/if}
272
- </div>
273
- {/if}
274
- {/each}
275
- </div>
276
- </ScrollArea.Content>
277
- </ScrollArea.Viewport>
278
- <ScrollArea.Scrollbar orientation="vertical">
279
- <ScrollArea.Thumb />
280
- </ScrollArea.Scrollbar>
281
- <ScrollArea.Corner />
282
- </ScrollArea.Root>
283
- {/if}
284
- </Popover.Content>
285
- </Popover.Root>