@humanspeak/svelte-headless-table 5.0.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.
Files changed (88) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +122 -0
  3. package/dist/bodyCells.d.ts +53 -0
  4. package/dist/bodyCells.js +90 -0
  5. package/dist/bodyRows.d.ts +82 -0
  6. package/dist/bodyRows.js +256 -0
  7. package/dist/columns.d.ts +76 -0
  8. package/dist/columns.js +96 -0
  9. package/dist/constants.d.ts +1 -0
  10. package/dist/constants.js +1 -0
  11. package/dist/createTable.d.ts +17 -0
  12. package/dist/createTable.js +36 -0
  13. package/dist/createViewModel.d.ts +38 -0
  14. package/dist/createViewModel.js +230 -0
  15. package/dist/headerCells.d.ts +80 -0
  16. package/dist/headerCells.js +147 -0
  17. package/dist/headerRows.d.ts +35 -0
  18. package/dist/headerRows.js +185 -0
  19. package/dist/index.d.ts +11 -0
  20. package/dist/index.js +12 -0
  21. package/dist/plugins/addColumnFilters.d.ts +39 -0
  22. package/dist/plugins/addColumnFilters.js +117 -0
  23. package/dist/plugins/addColumnOrder.d.ts +10 -0
  24. package/dist/plugins/addColumnOrder.js +24 -0
  25. package/dist/plugins/addDataExport.d.ts +21 -0
  26. package/dist/plugins/addDataExport.js +68 -0
  27. package/dist/plugins/addExpandedRows.d.ts +17 -0
  28. package/dist/plugins/addExpandedRows.js +51 -0
  29. package/dist/plugins/addFlatten.d.ts +19 -0
  30. package/dist/plugins/addFlatten.js +38 -0
  31. package/dist/plugins/addGridLayout.d.ts +2 -0
  32. package/dist/plugins/addGridLayout.js +73 -0
  33. package/dist/plugins/addGroupBy.d.ts +40 -0
  34. package/dist/plugins/addGroupBy.js +192 -0
  35. package/dist/plugins/addHiddenColumns.d.ts +9 -0
  36. package/dist/plugins/addHiddenColumns.js +17 -0
  37. package/dist/plugins/addPagination.d.ts +39 -0
  38. package/dist/plugins/addPagination.js +84 -0
  39. package/dist/plugins/addResizedColumns.d.ts +41 -0
  40. package/dist/plugins/addResizedColumns.js +252 -0
  41. package/dist/plugins/addSelectedRows.d.ts +29 -0
  42. package/dist/plugins/addSelectedRows.js +190 -0
  43. package/dist/plugins/addSortBy.d.ts +46 -0
  44. package/dist/plugins/addSortBy.js +176 -0
  45. package/dist/plugins/addSubRows.d.ts +9 -0
  46. package/dist/plugins/addSubRows.js +28 -0
  47. package/dist/plugins/addTableFilter.d.ts +28 -0
  48. package/dist/plugins/addTableFilter.js +95 -0
  49. package/dist/plugins/index.d.ts +15 -0
  50. package/dist/plugins/index.js +16 -0
  51. package/dist/plugins/package.json +5 -0
  52. package/dist/tableComponent.d.ts +19 -0
  53. package/dist/tableComponent.js +35 -0
  54. package/dist/types/Action.d.ts +5 -0
  55. package/dist/types/Action.js +1 -0
  56. package/dist/types/Entries.d.ts +3 -0
  57. package/dist/types/Entries.js +1 -0
  58. package/dist/types/KeyPath.d.ts +6 -0
  59. package/dist/types/KeyPath.js +1 -0
  60. package/dist/types/Label.d.ts +8 -0
  61. package/dist/types/Label.js +1 -0
  62. package/dist/types/Matrix.d.ts +1 -0
  63. package/dist/types/Matrix.js +1 -0
  64. package/dist/types/TablePlugin.d.ts +88 -0
  65. package/dist/types/TablePlugin.js +1 -0
  66. package/dist/utils/array.d.ts +2 -0
  67. package/dist/utils/array.js +9 -0
  68. package/dist/utils/attributes.d.ts +2 -0
  69. package/dist/utils/attributes.js +23 -0
  70. package/dist/utils/clone.d.ts +13 -0
  71. package/dist/utils/clone.js +18 -0
  72. package/dist/utils/compare.d.ts +2 -0
  73. package/dist/utils/compare.js +17 -0
  74. package/dist/utils/counter.d.ts +1 -0
  75. package/dist/utils/counter.js +7 -0
  76. package/dist/utils/css.d.ts +1 -0
  77. package/dist/utils/css.js +5 -0
  78. package/dist/utils/event.d.ts +1 -0
  79. package/dist/utils/event.js +5 -0
  80. package/dist/utils/filter.d.ts +4 -0
  81. package/dist/utils/filter.js +4 -0
  82. package/dist/utils/math.d.ts +2 -0
  83. package/dist/utils/math.js +2 -0
  84. package/dist/utils/matrix.d.ts +3 -0
  85. package/dist/utils/matrix.js +23 -0
  86. package/dist/utils/store.d.ts +37 -0
  87. package/dist/utils/store.js +123 -0
  88. package/package.json +98 -0
@@ -0,0 +1,147 @@
1
+ import { derived } from 'svelte/store';
2
+ import { NBSP } from './constants.js';
3
+ import { TableComponent } from './tableComponent.js';
4
+ export class HeaderCell extends TableComponent {
5
+ label;
6
+ colspan;
7
+ colstart;
8
+ constructor({ id, label, colspan, colstart }) {
9
+ super({ id });
10
+ this.label = label;
11
+ this.colspan = colspan;
12
+ this.colstart = colstart;
13
+ }
14
+ render() {
15
+ if (this.label instanceof Function) {
16
+ if (this.state === undefined) {
17
+ throw new Error('Missing `state` reference');
18
+ }
19
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
+ return this.label(this, this.state);
21
+ }
22
+ return this.label;
23
+ }
24
+ attrs() {
25
+ return derived(super.attrs(), ($baseAttrs) => {
26
+ return {
27
+ ...$baseAttrs,
28
+ role: 'columnheader',
29
+ colspan: this.colspan
30
+ };
31
+ });
32
+ }
33
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
34
+ isFlat() {
35
+ return '__flat' in this;
36
+ }
37
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
38
+ isData() {
39
+ return '__data' in this;
40
+ }
41
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
42
+ isFlatDisplay() {
43
+ return '__flat' in this && '__display' in this;
44
+ }
45
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
46
+ isGroup() {
47
+ return '__group' in this;
48
+ }
49
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
50
+ isGroupDisplay() {
51
+ return '__group' in this && '__display' in this;
52
+ }
53
+ }
54
+ export class FlatHeaderCell extends HeaderCell {
55
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
56
+ __flat = true;
57
+ constructor({ id, label, colstart }) {
58
+ super({ id, label, colspan: 1, colstart });
59
+ }
60
+ clone() {
61
+ return new FlatHeaderCell({
62
+ id: this.id,
63
+ label: this.label,
64
+ colstart: this.colstart
65
+ });
66
+ }
67
+ }
68
+ export class DataHeaderCell extends FlatHeaderCell {
69
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
70
+ __data = true;
71
+ accessorKey;
72
+ accessorFn;
73
+ constructor({ id, label, accessorKey, accessorFn, colstart }) {
74
+ super({ id, label, colstart });
75
+ this.accessorKey = accessorKey;
76
+ this.accessorFn = accessorFn;
77
+ }
78
+ clone() {
79
+ return new DataHeaderCell({
80
+ id: this.id,
81
+ label: this.label,
82
+ accessorFn: this.accessorFn,
83
+ accessorKey: this.accessorKey,
84
+ colstart: this.colstart
85
+ });
86
+ }
87
+ }
88
+ export class FlatDisplayHeaderCell extends FlatHeaderCell {
89
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
90
+ __display = true;
91
+ constructor({ id, label = NBSP, colstart }) {
92
+ super({ id, label, colstart });
93
+ }
94
+ clone() {
95
+ return new FlatDisplayHeaderCell({
96
+ id: this.id,
97
+ label: this.label,
98
+ colstart: this.colstart
99
+ });
100
+ }
101
+ }
102
+ export class GroupHeaderCell extends HeaderCell {
103
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
104
+ __group = true;
105
+ ids;
106
+ allId;
107
+ allIds;
108
+ constructor({ label, ids, allIds, colspan, colstart }) {
109
+ super({ id: `[${ids.join(',')}]`, label, colspan, colstart });
110
+ this.ids = ids;
111
+ this.allId = `[${allIds.join(',')}]`;
112
+ this.allIds = allIds;
113
+ }
114
+ setIds(ids) {
115
+ this.ids = ids;
116
+ this.id = `[${this.ids.join(',')}]`;
117
+ }
118
+ pushId(id) {
119
+ this.ids = [...this.ids, id];
120
+ this.id = `[${this.ids.join(',')}]`;
121
+ }
122
+ clone() {
123
+ return new GroupHeaderCell({
124
+ label: this.label,
125
+ ids: this.ids,
126
+ allIds: this.allIds,
127
+ colspan: this.colspan,
128
+ colstart: this.colstart
129
+ });
130
+ }
131
+ }
132
+ export class GroupDisplayHeaderCell extends GroupHeaderCell {
133
+ // TODO Workaround for https://github.com/vitejs/vite/issues/9528
134
+ __display = true;
135
+ constructor({ label = NBSP, ids, allIds, colspan = 1, colstart }) {
136
+ super({ label, ids, allIds, colspan, colstart });
137
+ }
138
+ clone() {
139
+ return new GroupDisplayHeaderCell({
140
+ label: this.label,
141
+ ids: this.ids,
142
+ allIds: this.allIds,
143
+ colspan: this.colspan,
144
+ colstart: this.colstart
145
+ });
146
+ }
147
+ }
@@ -0,0 +1,35 @@
1
+ import type { Column } from './columns.js';
2
+ import { type HeaderCell } from './headerCells.js';
3
+ import { TableComponent } from './tableComponent.js';
4
+ import type { Matrix } from './types/Matrix.js';
5
+ import type { AnyPlugins } from './types/TablePlugin.js';
6
+ export type HeaderRowAttributes<Item, Plugins extends AnyPlugins = AnyPlugins> = {
7
+ role: 'row';
8
+ };
9
+ export interface HeaderRowInit<Item, Plugins extends AnyPlugins = AnyPlugins> {
10
+ id: string;
11
+ cells: HeaderCell<Item, Plugins>[];
12
+ }
13
+ export declare class HeaderRow<Item, Plugins extends AnyPlugins = AnyPlugins> extends TableComponent<Item, Plugins, 'thead.tr'> {
14
+ cells: HeaderCell<Item, Plugins>[];
15
+ constructor({ id, cells }: HeaderRowInit<Item, Plugins>);
16
+ attrs(): import("svelte/store").Readable<{
17
+ role: "row";
18
+ }>;
19
+ clone(): HeaderRow<Item, Plugins>;
20
+ }
21
+ export declare const getHeaderRows: <Item, Plugins extends AnyPlugins = AnyPlugins>(columns: Column<Item, Plugins>[], flatColumnIds?: string[]) => HeaderRow<Item, Plugins>[];
22
+ export declare const getHeaderRowMatrix: <Item, Plugins extends AnyPlugins = AnyPlugins>(columns: Column<Item, Plugins>[]) => Matrix<HeaderCell<Item, Plugins>>;
23
+ export declare const getOrderedColumnMatrix: <Item, Plugins extends AnyPlugins = AnyPlugins>(columnMatrix: Matrix<HeaderCell<Item, Plugins>>, flatColumnIds: string[]) => Matrix<HeaderCell<Item, Plugins>>;
24
+ export declare const headerRowsForRowMatrix: <Item, Plugins extends AnyPlugins = AnyPlugins>(rowMatrix: Matrix<HeaderCell<Item, Plugins>>) => HeaderRow<Item, Plugins>[];
25
+ /**
26
+ * Multi-colspan cells will appear as multiple adjacent cells on the same row.
27
+ * Join these adjacent multi-colspan cells and update the colspan property.
28
+ *
29
+ * Non-adjacent multi-colspan cells (due to column ordering) must be cloned
30
+ * from the original .
31
+ *
32
+ * @param cells An array of cells.
33
+ * @returns An array of cells with no duplicate consecutive cells.
34
+ */
35
+ export declare const getMergedRow: <Item, Plugins extends AnyPlugins = AnyPlugins>(cells: HeaderCell<Item, Plugins>[]) => HeaderCell<Item, Plugins>[];
@@ -0,0 +1,185 @@
1
+ import { derived } from 'svelte/store';
2
+ import { DataHeaderCell, FlatDisplayHeaderCell, GroupDisplayHeaderCell, GroupHeaderCell } from './headerCells.js';
3
+ import { TableComponent } from './tableComponent.js';
4
+ import { sum } from './utils/math.js';
5
+ import { getNullMatrix, getTransposed } from './utils/matrix.js';
6
+ export class HeaderRow extends TableComponent {
7
+ cells;
8
+ constructor({ id, cells }) {
9
+ super({ id });
10
+ this.cells = cells;
11
+ }
12
+ attrs() {
13
+ return derived(super.attrs(), ($baseAttrs) => {
14
+ return {
15
+ ...$baseAttrs,
16
+ role: 'row'
17
+ };
18
+ });
19
+ }
20
+ clone() {
21
+ return new HeaderRow({
22
+ id: this.id,
23
+ cells: this.cells
24
+ });
25
+ }
26
+ }
27
+ export const getHeaderRows = (columns, flatColumnIds = []) => {
28
+ const rowMatrix = getHeaderRowMatrix(columns);
29
+ // Perform all column operations on the transposed columnMatrix. This helps
30
+ // to reduce the number of expensive transpose operations required.
31
+ let columnMatrix = getTransposed(rowMatrix);
32
+ columnMatrix = getOrderedColumnMatrix(columnMatrix, flatColumnIds);
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ populateGroupHeaderCellIds(columnMatrix);
35
+ return headerRowsForRowMatrix(getTransposed(columnMatrix));
36
+ };
37
+ export const getHeaderRowMatrix = (columns) => {
38
+ const maxColspan = sum(columns.map((c) => (c.isGroup() ? c.ids.length : 1)));
39
+ const maxHeight = Math.max(...columns.map((c) => c.height));
40
+ const rowMatrix = getNullMatrix(maxColspan, maxHeight);
41
+ let cellOffset = 0;
42
+ columns.forEach((c) => {
43
+ const heightOffset = maxHeight - c.height;
44
+ loadHeaderRowMatrix(rowMatrix, c, heightOffset, cellOffset);
45
+ cellOffset += c.isGroup() ? c.ids.length : 1;
46
+ });
47
+ // Replace null cells with blank display cells.
48
+ return rowMatrix.map((cells, rowIdx) => cells.map((cell, columnIdx) => {
49
+ if (cell !== null)
50
+ return cell;
51
+ if (rowIdx === maxHeight - 1)
52
+ return new FlatDisplayHeaderCell({ id: columnIdx.toString(), colstart: columnIdx });
53
+ const flatId = rowMatrix[maxHeight - 1][columnIdx]?.id ?? columnIdx.toString();
54
+ return new GroupDisplayHeaderCell({ ids: [], allIds: [flatId], colstart: columnIdx });
55
+ }));
56
+ };
57
+ const loadHeaderRowMatrix = (rowMatrix, column, rowOffset, cellOffset) => {
58
+ if (column.isData()) {
59
+ // `DataHeaderCell` should always be in the last row.
60
+ rowMatrix[rowMatrix.length - 1][cellOffset] = new DataHeaderCell({
61
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
62
+ label: column.header,
63
+ accessorFn: column.accessorFn,
64
+ accessorKey: column.accessorKey,
65
+ id: column.id,
66
+ colstart: cellOffset
67
+ });
68
+ return;
69
+ }
70
+ if (column.isDisplay()) {
71
+ rowMatrix[rowMatrix.length - 1][cellOffset] = new FlatDisplayHeaderCell({
72
+ id: column.id,
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ label: column.header,
75
+ colstart: cellOffset
76
+ });
77
+ return;
78
+ }
79
+ if (column.isGroup()) {
80
+ // Fill multi-colspan cells.
81
+ for (let i = 0; i < column.ids.length; i++) {
82
+ rowMatrix[rowOffset][cellOffset + i] = new GroupHeaderCell({
83
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
+ label: column.header,
85
+ colspan: 1,
86
+ allIds: column.ids,
87
+ ids: [],
88
+ colstart: cellOffset
89
+ });
90
+ }
91
+ let childCellOffset = 0;
92
+ column.columns.forEach((c) => {
93
+ loadHeaderRowMatrix(rowMatrix, c, rowOffset + 1, cellOffset + childCellOffset);
94
+ childCellOffset += c.isGroup() ? c.ids.length : 1;
95
+ });
96
+ return;
97
+ }
98
+ };
99
+ export const getOrderedColumnMatrix = (columnMatrix, flatColumnIds) => {
100
+ if (flatColumnIds.length === 0) {
101
+ return columnMatrix;
102
+ }
103
+ const orderedColumnMatrix = [];
104
+ // Each row of the transposed matrix represents a column.
105
+ // The `FlatHeaderCell` should be the last cell of each column.
106
+ flatColumnIds.forEach((key, columnIdx) => {
107
+ const nextColumn = columnMatrix.find((columnCells) => {
108
+ const flatCell = columnCells[columnCells.length - 1];
109
+ if (!flatCell.isFlat()) {
110
+ throw new Error('The last element of each column must be a `FlatHeaderCell`');
111
+ }
112
+ return flatCell.id === key;
113
+ });
114
+ if (nextColumn !== undefined) {
115
+ orderedColumnMatrix.push(nextColumn.map((column) => {
116
+ const clonedColumn = column.clone();
117
+ clonedColumn.colstart = columnIdx;
118
+ return clonedColumn;
119
+ }));
120
+ }
121
+ });
122
+ return orderedColumnMatrix;
123
+ };
124
+ const populateGroupHeaderCellIds = (columnMatrix) => {
125
+ columnMatrix.forEach((columnCells) => {
126
+ const lastCell = columnCells[columnCells.length - 1];
127
+ if (!lastCell.isFlat()) {
128
+ throw new Error('The last element of each column must be a `FlatHeaderCell`');
129
+ }
130
+ columnCells.forEach((c) => {
131
+ if (c.isGroup()) {
132
+ c.pushId(lastCell.id);
133
+ }
134
+ });
135
+ });
136
+ };
137
+ export const headerRowsForRowMatrix = (rowMatrix) => {
138
+ return rowMatrix.map((rowCells, rowIdx) => {
139
+ return new HeaderRow({ id: rowIdx.toString(), cells: getMergedRow(rowCells) });
140
+ });
141
+ };
142
+ /**
143
+ * Multi-colspan cells will appear as multiple adjacent cells on the same row.
144
+ * Join these adjacent multi-colspan cells and update the colspan property.
145
+ *
146
+ * Non-adjacent multi-colspan cells (due to column ordering) must be cloned
147
+ * from the original .
148
+ *
149
+ * @param cells An array of cells.
150
+ * @returns An array of cells with no duplicate consecutive cells.
151
+ */
152
+ export const getMergedRow = (cells) => {
153
+ if (cells.length === 0) {
154
+ return cells;
155
+ }
156
+ const mergedCells = [];
157
+ let startIdx = 0;
158
+ let endIdx = 1;
159
+ while (startIdx < cells.length) {
160
+ const cell = cells[startIdx].clone();
161
+ if (!cell.isGroup()) {
162
+ mergedCells.push(cell);
163
+ startIdx++;
164
+ continue;
165
+ }
166
+ endIdx = startIdx + 1;
167
+ const ids = [...cell.ids];
168
+ while (endIdx < cells.length) {
169
+ const nextCell = cells[endIdx];
170
+ if (!nextCell.isGroup()) {
171
+ break;
172
+ }
173
+ if (cell.allId !== nextCell.allId) {
174
+ break;
175
+ }
176
+ ids.push(...nextCell.ids);
177
+ endIdx++;
178
+ }
179
+ cell.setIds(ids);
180
+ cell.colspan = endIdx - startIdx;
181
+ mergedCells.push(cell);
182
+ startIdx = endIdx;
183
+ }
184
+ return mergedCells;
185
+ };
@@ -0,0 +1,11 @@
1
+ export * from '@humanspeak/svelte-render';
2
+ export { Subscribe } from '@humanspeak/svelte-subscribe';
3
+ export { createTable } from './createTable.js';
4
+ export { Table } from './createTable.js';
5
+ export { HeaderRow } from './headerRows.js';
6
+ export { HeaderCell, FlatHeaderCell, DataHeaderCell, FlatDisplayHeaderCell, GroupHeaderCell, GroupDisplayHeaderCell } from './headerCells.js';
7
+ export * from './bodyRows.js';
8
+ export * from './bodyCells.js';
9
+ export * from './columns.js';
10
+ export type * from './createViewModel.js';
11
+ export type * from './types/Label.js';
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // components
2
+ export * from '@humanspeak/svelte-render';
3
+ export { Subscribe } from '@humanspeak/svelte-subscribe';
4
+ // table core
5
+ export { createTable } from './createTable.js';
6
+ // models
7
+ export { Table } from './createTable.js';
8
+ export { HeaderRow } from './headerRows.js';
9
+ export { HeaderCell, FlatHeaderCell, DataHeaderCell, FlatDisplayHeaderCell, GroupHeaderCell, GroupDisplayHeaderCell } from './headerCells.js';
10
+ export * from './bodyRows.js';
11
+ export * from './bodyCells.js';
12
+ export * from './columns.js';
@@ -0,0 +1,39 @@
1
+ import type { RenderConfig } from '@humanspeak/svelte-subscribe';
2
+ import type { BodyRow } from '../bodyRows.js';
3
+ import type { TablePlugin, NewTablePropSet } from '../types/TablePlugin.js';
4
+ import { type Readable, type Writable } from 'svelte/store';
5
+ import type { PluginInitTableState } from '../createViewModel.js';
6
+ export interface ColumnFiltersConfig {
7
+ serverSide?: boolean;
8
+ }
9
+ export interface ColumnFiltersState<Item> {
10
+ filterValues: Writable<Record<string, unknown>>;
11
+ preFilteredRows: Readable<BodyRow<Item>[]>;
12
+ }
13
+ export interface ColumnFiltersColumnOptions<Item, FilterValue = any> {
14
+ fn: ColumnFilterFn<FilterValue>;
15
+ initialFilterValue?: FilterValue;
16
+ render?: (props: ColumnRenderConfigPropArgs<Item, FilterValue>) => RenderConfig;
17
+ }
18
+ interface ColumnRenderConfigPropArgs<Item, FilterValue = any, Value = any> extends PluginInitTableState<Item> {
19
+ id: string;
20
+ filterValue: Writable<FilterValue>;
21
+ values: Readable<Value[]>;
22
+ preFilteredRows: Readable<BodyRow<Item>[]>;
23
+ preFilteredValues: Readable<Value[]>;
24
+ }
25
+ export type ColumnFilterFn<FilterValue = any, Value = any> = (props: ColumnFilterFnProps<FilterValue, Value>) => boolean;
26
+ export type ColumnFilterFnProps<FilterValue = any, Value = any> = {
27
+ filterValue: FilterValue;
28
+ value: Value;
29
+ };
30
+ export type ColumnFiltersPropSet = NewTablePropSet<{
31
+ 'thead.tr.th': {
32
+ render?: RenderConfig;
33
+ } | undefined;
34
+ }>;
35
+ export declare const addColumnFilters: <Item>({ serverSide }?: ColumnFiltersConfig) => TablePlugin<Item, ColumnFiltersState<Item>, ColumnFiltersColumnOptions<Item>, ColumnFiltersPropSet>;
36
+ export declare const matchFilter: ColumnFilterFn<unknown, unknown>;
37
+ export declare const textPrefixFilter: ColumnFilterFn<string, string>;
38
+ export declare const numberRangeFilter: ColumnFilterFn<[number | null, number | null], number>;
39
+ export {};
@@ -0,0 +1,117 @@
1
+ import { keyed } from '@humanspeak/svelte-keyed';
2
+ import { derived, writable } from 'svelte/store';
3
+ const getFilteredRows = (rows, filterValues, columnOptions) => {
4
+ const $filteredRows = rows
5
+ // Filter `subRows`
6
+ .map((row) => {
7
+ const { subRows } = row;
8
+ if (subRows === undefined) {
9
+ return row;
10
+ }
11
+ const filteredSubRows = getFilteredRows(subRows, filterValues, columnOptions);
12
+ const clonedRow = row.clone();
13
+ clonedRow.subRows = filteredSubRows;
14
+ return clonedRow;
15
+ })
16
+ .filter((row) => {
17
+ if ((row.subRows?.length ?? 0) !== 0) {
18
+ return true;
19
+ }
20
+ for (const [columnId, columnOption] of Object.entries(columnOptions)) {
21
+ const bodyCell = row.cellForId[columnId];
22
+ if (!bodyCell.isData()) {
23
+ continue;
24
+ }
25
+ const { value } = bodyCell;
26
+ const filterValue = filterValues[columnId];
27
+ if (filterValue === undefined) {
28
+ continue;
29
+ }
30
+ const isMatch = columnOption.fn({ value, filterValue });
31
+ if (!isMatch) {
32
+ return false;
33
+ }
34
+ }
35
+ return true;
36
+ });
37
+ return $filteredRows;
38
+ };
39
+ export const addColumnFilters = ({ serverSide = false } = {}) => ({ columnOptions, tableState }) => {
40
+ const filterValues = writable({});
41
+ const preFilteredRows = writable([]);
42
+ const filteredRows = writable([]);
43
+ const pluginState = { filterValues, preFilteredRows };
44
+ const deriveRows = (rows) => {
45
+ return derived([rows, filterValues], ([$rows, $filterValues]) => {
46
+ preFilteredRows.set($rows);
47
+ if (serverSide) {
48
+ filteredRows.set($rows);
49
+ return $rows;
50
+ }
51
+ const _filteredRows = getFilteredRows($rows, $filterValues, columnOptions);
52
+ filteredRows.set(_filteredRows);
53
+ return _filteredRows;
54
+ });
55
+ };
56
+ return {
57
+ pluginState,
58
+ deriveRows,
59
+ hooks: {
60
+ 'thead.tr.th': (headerCell) => {
61
+ const filterValue = keyed(filterValues, headerCell.id);
62
+ const props = derived([], () => {
63
+ const columnOption = columnOptions[headerCell.id];
64
+ if (columnOption === undefined) {
65
+ return undefined;
66
+ }
67
+ filterValue.set(columnOption.initialFilterValue);
68
+ const preFilteredValues = derived(preFilteredRows, ($rows) => {
69
+ if (headerCell.isData()) {
70
+ return $rows.map((row) => {
71
+ // TODO check and handle different BodyCell types
72
+ const cell = row.cellForId[headerCell.id];
73
+ return cell?.value;
74
+ });
75
+ }
76
+ return [];
77
+ });
78
+ const values = derived(filteredRows, ($rows) => {
79
+ if (headerCell.isData()) {
80
+ return $rows.map((row) => {
81
+ // TODO check and handle different BodyCell types
82
+ const cell = row.cellForId[headerCell.id];
83
+ return cell?.value;
84
+ });
85
+ }
86
+ return [];
87
+ });
88
+ const render = columnOption.render?.({
89
+ id: headerCell.id,
90
+ filterValue,
91
+ ...tableState,
92
+ values,
93
+ preFilteredRows,
94
+ preFilteredValues
95
+ });
96
+ return { render };
97
+ });
98
+ return { props };
99
+ }
100
+ }
101
+ };
102
+ };
103
+ export const matchFilter = ({ filterValue, value }) => {
104
+ if (filterValue === undefined) {
105
+ return true;
106
+ }
107
+ return filterValue === value;
108
+ };
109
+ export const textPrefixFilter = ({ filterValue, value }) => {
110
+ if (filterValue === '') {
111
+ return true;
112
+ }
113
+ return String(value).toLowerCase().startsWith(String(filterValue).toLowerCase());
114
+ };
115
+ export const numberRangeFilter = ({ filterValue: [min, max], value }) => {
116
+ return (min ?? -Infinity) <= value && value <= (max ?? Infinity);
117
+ };
@@ -0,0 +1,10 @@
1
+ import type { NewTablePropSet, TablePlugin } from '../types/TablePlugin.js';
2
+ import { type Writable } from 'svelte/store';
3
+ export interface ColumnOrderConfig {
4
+ initialColumnIdOrder?: string[];
5
+ hideUnspecifiedColumns?: boolean;
6
+ }
7
+ export interface ColumnOrderState {
8
+ columnIdOrder: Writable<string[]>;
9
+ }
10
+ export declare const addColumnOrder: <Item>({ initialColumnIdOrder, hideUnspecifiedColumns }?: ColumnOrderConfig) => TablePlugin<Item, ColumnOrderState, Record<string, never>, NewTablePropSet<never>>;
@@ -0,0 +1,24 @@
1
+ import { derived, writable } from 'svelte/store';
2
+ export const addColumnOrder = ({ initialColumnIdOrder = [], hideUnspecifiedColumns = false } = {}) => () => {
3
+ const columnIdOrder = writable(initialColumnIdOrder);
4
+ const pluginState = { columnIdOrder };
5
+ const deriveFlatColumns = (flatColumns) => {
6
+ return derived([flatColumns, columnIdOrder], ([$flatColumns, $columnIdOrder]) => {
7
+ const _flatColumns = [...$flatColumns];
8
+ const orderedFlatColumns = [];
9
+ $columnIdOrder.forEach((id) => {
10
+ const colIdx = _flatColumns.findIndex((c) => c.id === id);
11
+ orderedFlatColumns.push(..._flatColumns.splice(colIdx, 1));
12
+ });
13
+ if (!hideUnspecifiedColumns) {
14
+ // Push the remaining unspecified columns.
15
+ orderedFlatColumns.push(..._flatColumns);
16
+ }
17
+ return orderedFlatColumns;
18
+ });
19
+ };
20
+ return {
21
+ pluginState,
22
+ deriveFlatColumns
23
+ };
24
+ };
@@ -0,0 +1,21 @@
1
+ import type { TablePlugin } from '../types/TablePlugin.js';
2
+ import { type Readable } from 'svelte/store';
3
+ export type DataExportFormat = 'object' | 'json' | 'csv';
4
+ type ExportForFormat = {
5
+ object: Record<string, unknown>[];
6
+ json: string;
7
+ csv: string;
8
+ };
9
+ export type DataExport<F extends DataExportFormat> = ExportForFormat[F];
10
+ export interface DataExportConfig<F extends DataExportFormat> {
11
+ childrenKey?: string;
12
+ format?: F;
13
+ }
14
+ export interface DataExportState<F extends DataExportFormat> {
15
+ exportedData: Readable<DataExport<F>>;
16
+ }
17
+ export interface DataExportColumnOptions {
18
+ exclude?: boolean;
19
+ }
20
+ export declare const addDataExport: <Item, F extends DataExportFormat = "object">({ format, childrenKey }?: DataExportConfig<F>) => TablePlugin<Item, DataExportState<F>, DataExportColumnOptions>;
21
+ export {};
@@ -0,0 +1,68 @@
1
+ import { isReadable } from '../utils/store.js';
2
+ import { derived, get } from 'svelte/store';
3
+ const getObjectsFromRows = (rows, ids, childrenKey) => {
4
+ return rows.map((row) => {
5
+ const dataObject = Object.fromEntries(ids.map((id) => {
6
+ const cell = row.cellForId[id];
7
+ if (cell.isData()) {
8
+ return [id, cell.value];
9
+ }
10
+ if (cell.isDisplay() && cell.column.data !== undefined) {
11
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
+ let data = cell.column.data(cell, row.state);
13
+ if (isReadable(data)) {
14
+ data = get(data);
15
+ }
16
+ return [id, data];
17
+ }
18
+ return [id, null];
19
+ }));
20
+ if (row.subRows !== undefined) {
21
+ dataObject[childrenKey] = getObjectsFromRows(row.subRows, ids, childrenKey);
22
+ }
23
+ return dataObject;
24
+ });
25
+ };
26
+ const getCsvFromRows = (rows, ids) => {
27
+ const dataLines = rows.map((row) => {
28
+ const line = ids.map((id) => {
29
+ const cell = row.cellForId[id];
30
+ if (cell.isData()) {
31
+ return cell.value;
32
+ }
33
+ if (cell.isDisplay() && cell.column.data !== undefined) {
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ let data = cell.column.data(cell, row.state);
36
+ if (isReadable(data)) {
37
+ data = get(data);
38
+ }
39
+ return data;
40
+ }
41
+ return null;
42
+ });
43
+ return line.join(',');
44
+ });
45
+ const headerLine = ids.join(',');
46
+ return headerLine + '\n' + dataLines.join('\n');
47
+ };
48
+ export const addDataExport = ({ format = 'object', childrenKey = 'children' } = {}) => ({ tableState, columnOptions }) => {
49
+ const excludedIds = Object.entries(columnOptions)
50
+ .filter(([, option]) => option.exclude === true)
51
+ .map(([columnId]) => columnId);
52
+ const { visibleColumns, rows } = tableState;
53
+ const exportedIds = derived(visibleColumns, ($visibleColumns) => $visibleColumns.map((c) => c.id).filter((id) => !excludedIds.includes(id)));
54
+ const exportedData = derived([rows, exportedIds], ([$rows, $exportedIds]) => {
55
+ switch (format) {
56
+ case 'json':
57
+ return JSON.stringify(getObjectsFromRows($rows, $exportedIds, childrenKey));
58
+ case 'csv':
59
+ return getCsvFromRows($rows, $exportedIds);
60
+ default:
61
+ return getObjectsFromRows($rows, $exportedIds, childrenKey);
62
+ }
63
+ });
64
+ const pluginState = { exportedData };
65
+ return {
66
+ pluginState
67
+ };
68
+ };