@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.
- package/LICENSE +22 -0
- package/README.md +122 -0
- package/dist/bodyCells.d.ts +53 -0
- package/dist/bodyCells.js +90 -0
- package/dist/bodyRows.d.ts +82 -0
- package/dist/bodyRows.js +256 -0
- package/dist/columns.d.ts +76 -0
- package/dist/columns.js +96 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +1 -0
- package/dist/createTable.d.ts +17 -0
- package/dist/createTable.js +36 -0
- package/dist/createViewModel.d.ts +38 -0
- package/dist/createViewModel.js +230 -0
- package/dist/headerCells.d.ts +80 -0
- package/dist/headerCells.js +147 -0
- package/dist/headerRows.d.ts +35 -0
- package/dist/headerRows.js +185 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -0
- package/dist/plugins/addColumnFilters.d.ts +39 -0
- package/dist/plugins/addColumnFilters.js +117 -0
- package/dist/plugins/addColumnOrder.d.ts +10 -0
- package/dist/plugins/addColumnOrder.js +24 -0
- package/dist/plugins/addDataExport.d.ts +21 -0
- package/dist/plugins/addDataExport.js +68 -0
- package/dist/plugins/addExpandedRows.d.ts +17 -0
- package/dist/plugins/addExpandedRows.js +51 -0
- package/dist/plugins/addFlatten.d.ts +19 -0
- package/dist/plugins/addFlatten.js +38 -0
- package/dist/plugins/addGridLayout.d.ts +2 -0
- package/dist/plugins/addGridLayout.js +73 -0
- package/dist/plugins/addGroupBy.d.ts +40 -0
- package/dist/plugins/addGroupBy.js +192 -0
- package/dist/plugins/addHiddenColumns.d.ts +9 -0
- package/dist/plugins/addHiddenColumns.js +17 -0
- package/dist/plugins/addPagination.d.ts +39 -0
- package/dist/plugins/addPagination.js +84 -0
- package/dist/plugins/addResizedColumns.d.ts +41 -0
- package/dist/plugins/addResizedColumns.js +252 -0
- package/dist/plugins/addSelectedRows.d.ts +29 -0
- package/dist/plugins/addSelectedRows.js +190 -0
- package/dist/plugins/addSortBy.d.ts +46 -0
- package/dist/plugins/addSortBy.js +176 -0
- package/dist/plugins/addSubRows.d.ts +9 -0
- package/dist/plugins/addSubRows.js +28 -0
- package/dist/plugins/addTableFilter.d.ts +28 -0
- package/dist/plugins/addTableFilter.js +95 -0
- package/dist/plugins/index.d.ts +15 -0
- package/dist/plugins/index.js +16 -0
- package/dist/plugins/package.json +5 -0
- package/dist/tableComponent.d.ts +19 -0
- package/dist/tableComponent.js +35 -0
- package/dist/types/Action.d.ts +5 -0
- package/dist/types/Action.js +1 -0
- package/dist/types/Entries.d.ts +3 -0
- package/dist/types/Entries.js +1 -0
- package/dist/types/KeyPath.d.ts +6 -0
- package/dist/types/KeyPath.js +1 -0
- package/dist/types/Label.d.ts +8 -0
- package/dist/types/Label.js +1 -0
- package/dist/types/Matrix.d.ts +1 -0
- package/dist/types/Matrix.js +1 -0
- package/dist/types/TablePlugin.d.ts +88 -0
- package/dist/types/TablePlugin.js +1 -0
- package/dist/utils/array.d.ts +2 -0
- package/dist/utils/array.js +9 -0
- package/dist/utils/attributes.d.ts +2 -0
- package/dist/utils/attributes.js +23 -0
- package/dist/utils/clone.d.ts +13 -0
- package/dist/utils/clone.js +18 -0
- package/dist/utils/compare.d.ts +2 -0
- package/dist/utils/compare.js +17 -0
- package/dist/utils/counter.d.ts +1 -0
- package/dist/utils/counter.js +7 -0
- package/dist/utils/css.d.ts +1 -0
- package/dist/utils/css.js +5 -0
- package/dist/utils/event.d.ts +1 -0
- package/dist/utils/event.js +5 -0
- package/dist/utils/filter.d.ts +4 -0
- package/dist/utils/filter.js +4 -0
- package/dist/utils/math.d.ts +2 -0
- package/dist/utils/math.js +2 -0
- package/dist/utils/matrix.d.ts +3 -0
- package/dist/utils/matrix.js +23 -0
- package/dist/utils/store.d.ts +37 -0
- package/dist/utils/store.js +123 -0
- 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
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
};
|