@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,252 @@
|
|
|
1
|
+
import { sum } from '../utils/math.js';
|
|
2
|
+
import { keyed } from '@humanspeak/svelte-keyed';
|
|
3
|
+
import { derived, writable } from 'svelte/store';
|
|
4
|
+
const getDragXPos = (event) => {
|
|
5
|
+
if (event instanceof MouseEvent)
|
|
6
|
+
return event.clientX;
|
|
7
|
+
if (event instanceof TouchEvent)
|
|
8
|
+
return event.targetTouches[0].pageX;
|
|
9
|
+
return 0;
|
|
10
|
+
};
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
const isCellDisabled = (cell, disabledIds) => {
|
|
13
|
+
if (disabledIds.includes(cell.id))
|
|
14
|
+
return true;
|
|
15
|
+
if (cell.isGroup() && cell.ids.every((id) => disabledIds.includes(id))) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return false;
|
|
19
|
+
};
|
|
20
|
+
export const addResizedColumns = ({ onResizeEnd } = {}) => ({ columnOptions }) => {
|
|
21
|
+
const disabledResizeIds = Object.entries(columnOptions)
|
|
22
|
+
.filter(([, option]) => option.disable === true)
|
|
23
|
+
.map(([columnId]) => columnId);
|
|
24
|
+
const initialWidths = Object.fromEntries(Object.entries(columnOptions)
|
|
25
|
+
.filter(([, option]) => option.initialWidth !== undefined)
|
|
26
|
+
.map(([columnId, { initialWidth }]) => [columnId, initialWidth]));
|
|
27
|
+
const columnsWidthState = writable({
|
|
28
|
+
current: initialWidths,
|
|
29
|
+
start: {}
|
|
30
|
+
});
|
|
31
|
+
const columnWidths = keyed(columnsWidthState, 'current');
|
|
32
|
+
const pluginState = { columnWidths };
|
|
33
|
+
const dragStartXPosForId = {};
|
|
34
|
+
const nodeForId = {};
|
|
35
|
+
return {
|
|
36
|
+
pluginState,
|
|
37
|
+
hooks: {
|
|
38
|
+
'thead.tr.th': (cell) => {
|
|
39
|
+
const dblClick = (event) => {
|
|
40
|
+
if (isCellDisabled(cell, disabledResizeIds))
|
|
41
|
+
return;
|
|
42
|
+
const { target } = event;
|
|
43
|
+
if (target === null)
|
|
44
|
+
return;
|
|
45
|
+
event.stopPropagation();
|
|
46
|
+
event.preventDefault();
|
|
47
|
+
if (cell.isGroup()) {
|
|
48
|
+
cell.ids.forEach((id) => {
|
|
49
|
+
const node = nodeForId[id];
|
|
50
|
+
if (node !== undefined) {
|
|
51
|
+
columnWidths.update(($columnWidths) => ({
|
|
52
|
+
...$columnWidths,
|
|
53
|
+
[id]: initialWidths[id]
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const node = nodeForId[cell.id];
|
|
60
|
+
if (node !== undefined) {
|
|
61
|
+
columnWidths.update(($columnWidths) => ({
|
|
62
|
+
...$columnWidths,
|
|
63
|
+
[cell.id]: initialWidths[cell.id]
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
let tapedTwice = false;
|
|
69
|
+
const checkDoubleTap = (event) => {
|
|
70
|
+
if (!tapedTwice) {
|
|
71
|
+
tapedTwice = true;
|
|
72
|
+
setTimeout(function () {
|
|
73
|
+
tapedTwice = false;
|
|
74
|
+
}, 300);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
dblClick(event);
|
|
79
|
+
};
|
|
80
|
+
const dragStart = (event) => {
|
|
81
|
+
if (isCellDisabled(cell, disabledResizeIds))
|
|
82
|
+
return;
|
|
83
|
+
const { target } = event;
|
|
84
|
+
if (target === null)
|
|
85
|
+
return;
|
|
86
|
+
event.stopPropagation();
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
dragStartXPosForId[cell.id] = getDragXPos(event);
|
|
89
|
+
columnsWidthState.update(($columnsWidthState) => {
|
|
90
|
+
const $updatedState = {
|
|
91
|
+
...$columnsWidthState,
|
|
92
|
+
start: { ...$columnsWidthState.start }
|
|
93
|
+
};
|
|
94
|
+
if (cell.isGroup()) {
|
|
95
|
+
cell.ids.forEach((id) => {
|
|
96
|
+
$updatedState.start[id] = $columnsWidthState.current[id];
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
$updatedState.start[cell.id] = $columnsWidthState.current[cell.id];
|
|
101
|
+
}
|
|
102
|
+
return $updatedState;
|
|
103
|
+
});
|
|
104
|
+
if (event instanceof MouseEvent) {
|
|
105
|
+
window.addEventListener('mousemove', dragMove);
|
|
106
|
+
window.addEventListener('mouseup', dragEnd);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
window.addEventListener('touchmove', dragMove);
|
|
110
|
+
window.addEventListener('touchend', dragEnd);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const dragMove = (event) => {
|
|
114
|
+
event.stopPropagation();
|
|
115
|
+
event.preventDefault();
|
|
116
|
+
const deltaWidth = getDragXPos(event) - dragStartXPosForId[cell.id];
|
|
117
|
+
columnsWidthState.update(($columnsWidthState) => {
|
|
118
|
+
const $updatedState = {
|
|
119
|
+
...$columnsWidthState,
|
|
120
|
+
current: { ...$columnsWidthState.current }
|
|
121
|
+
};
|
|
122
|
+
if (cell.isGroup()) {
|
|
123
|
+
const enabledIds = cell.ids.filter((id) => !disabledResizeIds.includes(id));
|
|
124
|
+
const totalStartWidth = sum(enabledIds.map((id) => $columnsWidthState.start[id]));
|
|
125
|
+
enabledIds.forEach((id) => {
|
|
126
|
+
const startWidth = $columnsWidthState.start[id];
|
|
127
|
+
if (startWidth !== undefined) {
|
|
128
|
+
$updatedState.current[id] = Math.max(0, startWidth + deltaWidth * (startWidth / totalStartWidth));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
const startWidth = $columnsWidthState.start[cell.id];
|
|
134
|
+
const { minWidth = 0, maxWidth } = columnOptions[cell.id] ?? {};
|
|
135
|
+
if (startWidth !== undefined) {
|
|
136
|
+
$updatedState.current[cell.id] = Math.min(Math.max(minWidth, startWidth + deltaWidth), ...(maxWidth === undefined ? [] : [maxWidth]));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return $updatedState;
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
const dragEnd = (event) => {
|
|
143
|
+
event.stopPropagation();
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
if (cell.isGroup()) {
|
|
146
|
+
cell.ids.forEach((id) => {
|
|
147
|
+
const node = nodeForId[id];
|
|
148
|
+
if (node !== undefined) {
|
|
149
|
+
columnWidths.update(($columnWidths) => ({
|
|
150
|
+
...$columnWidths,
|
|
151
|
+
[id]: node.getBoundingClientRect().width
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const node = nodeForId[cell.id];
|
|
158
|
+
if (node !== undefined) {
|
|
159
|
+
columnWidths.update(($columnWidths) => ({
|
|
160
|
+
...$columnWidths,
|
|
161
|
+
[cell.id]: node.getBoundingClientRect().width
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
onResizeEnd?.(event);
|
|
166
|
+
if (event instanceof MouseEvent) {
|
|
167
|
+
window.removeEventListener('mousemove', dragMove);
|
|
168
|
+
window.removeEventListener('mouseup', dragEnd);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
window.removeEventListener('touchmove', dragMove);
|
|
172
|
+
window.removeEventListener('touchend', dragEnd);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const $props = (node) => {
|
|
176
|
+
nodeForId[cell.id] = node;
|
|
177
|
+
if (cell.isFlat()) {
|
|
178
|
+
columnWidths.update(($columnWidths) => ({
|
|
179
|
+
...$columnWidths,
|
|
180
|
+
[cell.id]: node.getBoundingClientRect().width
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
return {
|
|
184
|
+
destroy() {
|
|
185
|
+
delete nodeForId[cell.id];
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
$props.drag = (node) => {
|
|
190
|
+
node.addEventListener('mousedown', dragStart);
|
|
191
|
+
node.addEventListener('touchstart', dragStart);
|
|
192
|
+
return {
|
|
193
|
+
destroy() {
|
|
194
|
+
node.removeEventListener('mousedown', dragStart);
|
|
195
|
+
node.removeEventListener('touchstart', dragStart);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
$props.reset = (node) => {
|
|
200
|
+
node.addEventListener('dblclick', dblClick);
|
|
201
|
+
node.addEventListener('touchend', checkDoubleTap);
|
|
202
|
+
return {
|
|
203
|
+
destroy() {
|
|
204
|
+
node.removeEventListener('dblckick', dblClick);
|
|
205
|
+
node.removeEventListener('touchend', checkDoubleTap);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
$props.disabled = isCellDisabled(cell, disabledResizeIds);
|
|
210
|
+
const props = derived([], () => {
|
|
211
|
+
return $props;
|
|
212
|
+
});
|
|
213
|
+
const attrs = derived(columnWidths, ($columnWidths) => {
|
|
214
|
+
const width = cell.isGroup()
|
|
215
|
+
? sum(cell.ids.map((id) => $columnWidths[id]))
|
|
216
|
+
: $columnWidths[cell.id];
|
|
217
|
+
if (width === undefined) {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
const widthPx = `${width}px`;
|
|
221
|
+
return {
|
|
222
|
+
style: {
|
|
223
|
+
width: widthPx,
|
|
224
|
+
'min-width': widthPx,
|
|
225
|
+
'max-width': widthPx,
|
|
226
|
+
'box-sizing': 'border-box'
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
return { props, attrs };
|
|
231
|
+
},
|
|
232
|
+
'tbody.tr.td': (cell) => {
|
|
233
|
+
const attrs = derived(columnWidths, ($columnWidths) => {
|
|
234
|
+
const width = $columnWidths[cell.id];
|
|
235
|
+
if (width === undefined) {
|
|
236
|
+
return {};
|
|
237
|
+
}
|
|
238
|
+
const widthPx = `${width}px`;
|
|
239
|
+
return {
|
|
240
|
+
style: {
|
|
241
|
+
width: widthPx,
|
|
242
|
+
'min-width': widthPx,
|
|
243
|
+
'max-width': widthPx,
|
|
244
|
+
'box-sizing': 'border-box'
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
return { attrs };
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BodyRow } from '../bodyRows.js';
|
|
2
|
+
import type { NewTablePropSet, TablePlugin } from '../types/TablePlugin.js';
|
|
3
|
+
import { type RecordSetStore } from '../utils/store.js';
|
|
4
|
+
import { type Readable, type Writable } from 'svelte/store';
|
|
5
|
+
export interface SelectedRowsConfig<Item> {
|
|
6
|
+
initialSelectedDataIds?: Record<string, boolean>;
|
|
7
|
+
linkDataSubRows?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface SelectedRowsState<Item> {
|
|
10
|
+
selectedDataIds: RecordSetStore<string>;
|
|
11
|
+
allRowsSelected: Writable<boolean>;
|
|
12
|
+
someRowsSelected: Readable<boolean>;
|
|
13
|
+
allPageRowsSelected: Writable<boolean>;
|
|
14
|
+
somePageRowsSelected: Readable<boolean>;
|
|
15
|
+
getRowState: (row: BodyRow<Item>) => SelectedRowsRowState;
|
|
16
|
+
}
|
|
17
|
+
export interface SelectedRowsRowState {
|
|
18
|
+
isSelected: Writable<boolean>;
|
|
19
|
+
isSomeSubRowsSelected: Readable<boolean>;
|
|
20
|
+
isAllSubRowsSelected: Readable<boolean>;
|
|
21
|
+
}
|
|
22
|
+
export type SelectedRowsPropSet = NewTablePropSet<{
|
|
23
|
+
'tbody.tr': {
|
|
24
|
+
selected: boolean;
|
|
25
|
+
someSubRowsSelected: boolean;
|
|
26
|
+
allSubRowsSelected: boolean;
|
|
27
|
+
};
|
|
28
|
+
}>;
|
|
29
|
+
export declare const addSelectedRows: <Item>({ initialSelectedDataIds, linkDataSubRows }?: SelectedRowsConfig<Item>) => TablePlugin<Item, SelectedRowsState<Item>, Record<string, never>, SelectedRowsPropSet>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { nonNull } from '../utils/filter.js';
|
|
2
|
+
import { recordSetStore } from '../utils/store.js';
|
|
3
|
+
import { derived, get } from 'svelte/store';
|
|
4
|
+
const isAllSubRowsSelectedForRow = (row, $selectedDataIds, linkDataSubRows) => {
|
|
5
|
+
if (row.isData()) {
|
|
6
|
+
if (!linkDataSubRows || row.subRows === undefined) {
|
|
7
|
+
return $selectedDataIds[row.dataId] === true;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
if (row.subRows === undefined) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
return row.subRows.every((subRow) => isAllSubRowsSelectedForRow(subRow, $selectedDataIds, linkDataSubRows));
|
|
14
|
+
};
|
|
15
|
+
const isSomeSubRowsSelectedForRow = (row, $selectedDataIds, linkDataSubRows) => {
|
|
16
|
+
if (row.isData()) {
|
|
17
|
+
if (!linkDataSubRows || row.subRows === undefined) {
|
|
18
|
+
return $selectedDataIds[row.dataId] === true;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (row.subRows === undefined) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
return row.subRows.some((subRow) => isSomeSubRowsSelectedForRow(subRow, $selectedDataIds, linkDataSubRows));
|
|
25
|
+
};
|
|
26
|
+
const writeSelectedDataIds = (row, value, $selectedDataIds, linkDataSubRows) => {
|
|
27
|
+
if (row.isData()) {
|
|
28
|
+
$selectedDataIds[row.dataId] = value;
|
|
29
|
+
if (!linkDataSubRows) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (row.subRows === undefined) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
row.subRows.forEach((subRow) => {
|
|
37
|
+
writeSelectedDataIds(subRow, value, $selectedDataIds, linkDataSubRows);
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const getRowIsSelectedStore = (row, selectedDataIds, linkDataSubRows) => {
|
|
41
|
+
const { subscribe } = derived(selectedDataIds, ($selectedDataIds) => {
|
|
42
|
+
if (row.isData()) {
|
|
43
|
+
if (!linkDataSubRows) {
|
|
44
|
+
return $selectedDataIds[row.dataId] === true;
|
|
45
|
+
}
|
|
46
|
+
if ($selectedDataIds[row.dataId] === true) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return isAllSubRowsSelectedForRow(row, $selectedDataIds, linkDataSubRows);
|
|
51
|
+
});
|
|
52
|
+
const update = (fn) => {
|
|
53
|
+
selectedDataIds.update(($selectedDataIds) => {
|
|
54
|
+
const oldValue = isAllSubRowsSelectedForRow(row, $selectedDataIds, linkDataSubRows);
|
|
55
|
+
const $updatedSelectedDataIds = { ...$selectedDataIds };
|
|
56
|
+
writeSelectedDataIds(row, fn(oldValue), $updatedSelectedDataIds, linkDataSubRows);
|
|
57
|
+
if (row.parentRow !== undefined && row.parentRow.isData()) {
|
|
58
|
+
$updatedSelectedDataIds[row.parentRow.dataId] = isAllSubRowsSelectedForRow(row.parentRow, $updatedSelectedDataIds, linkDataSubRows);
|
|
59
|
+
}
|
|
60
|
+
return $updatedSelectedDataIds;
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const set = (value) => update(() => value);
|
|
64
|
+
return {
|
|
65
|
+
subscribe,
|
|
66
|
+
update,
|
|
67
|
+
set
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
export const addSelectedRows = ({ initialSelectedDataIds = {}, linkDataSubRows = true } = {}) => ({ tableState }) => {
|
|
71
|
+
const selectedDataIds = recordSetStore(initialSelectedDataIds);
|
|
72
|
+
const getRowState = (row) => {
|
|
73
|
+
const isSelected = getRowIsSelectedStore(row, selectedDataIds, linkDataSubRows);
|
|
74
|
+
const isSomeSubRowsSelected = derived([isSelected, selectedDataIds], ([$isSelected, $selectedDataIds]) => {
|
|
75
|
+
if ($isSelected)
|
|
76
|
+
return false;
|
|
77
|
+
return isSomeSubRowsSelectedForRow(row, $selectedDataIds, linkDataSubRows);
|
|
78
|
+
});
|
|
79
|
+
const isAllSubRowsSelected = derived(selectedDataIds, ($selectedDataIds) => {
|
|
80
|
+
return isAllSubRowsSelectedForRow(row, $selectedDataIds, linkDataSubRows);
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
isSelected,
|
|
84
|
+
isSomeSubRowsSelected,
|
|
85
|
+
isAllSubRowsSelected
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
// all rows
|
|
89
|
+
const _allRowsSelected = derived([tableState.rows, selectedDataIds], ([$rows, $selectedDataIds]) => {
|
|
90
|
+
return $rows.every((row) => {
|
|
91
|
+
if (!row.isData()) {
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
return $selectedDataIds[row.dataId] === true;
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
const setAllRowsSelected = ($allRowsSelected) => {
|
|
98
|
+
if ($allRowsSelected) {
|
|
99
|
+
const $rows = get(tableState.rows);
|
|
100
|
+
const allDataIds = $rows
|
|
101
|
+
.map((row) => (row.isData() ? row.dataId : null))
|
|
102
|
+
.filter(nonNull);
|
|
103
|
+
selectedDataIds.addAll(allDataIds);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
selectedDataIds.clear();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
const allRowsSelected = {
|
|
110
|
+
subscribe: _allRowsSelected.subscribe,
|
|
111
|
+
update(fn) {
|
|
112
|
+
const $allRowsSelected = get(_allRowsSelected);
|
|
113
|
+
setAllRowsSelected(fn($allRowsSelected));
|
|
114
|
+
},
|
|
115
|
+
set: setAllRowsSelected
|
|
116
|
+
};
|
|
117
|
+
const someRowsSelected = derived([tableState.rows, selectedDataIds], ([$rows, $selectedDataIds]) => {
|
|
118
|
+
return $rows.some((row) => {
|
|
119
|
+
if (!row.isData()) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return $selectedDataIds[row.dataId] === true;
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
// page rows
|
|
126
|
+
const _allPageRowsSelected = derived([tableState.pageRows, selectedDataIds], ([$pageRows, $selectedDataIds]) => {
|
|
127
|
+
return $pageRows.every((row) => {
|
|
128
|
+
if (!row.isData()) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return $selectedDataIds[row.dataId] === true;
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
const setAllPageRowsSelected = ($allPageRowsSelected) => {
|
|
135
|
+
const $pageRows = get(tableState.pageRows);
|
|
136
|
+
const pageDataIds = $pageRows
|
|
137
|
+
.map((row) => (row.isData() ? row.dataId : null))
|
|
138
|
+
.filter(nonNull);
|
|
139
|
+
if ($allPageRowsSelected) {
|
|
140
|
+
selectedDataIds.addAll(pageDataIds);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
selectedDataIds.removeAll(pageDataIds);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const allPageRowsSelected = {
|
|
147
|
+
subscribe: _allPageRowsSelected.subscribe,
|
|
148
|
+
update(fn) {
|
|
149
|
+
const $allPageRowsSelected = get(_allPageRowsSelected);
|
|
150
|
+
setAllPageRowsSelected(fn($allPageRowsSelected));
|
|
151
|
+
},
|
|
152
|
+
set: setAllPageRowsSelected
|
|
153
|
+
};
|
|
154
|
+
const somePageRowsSelected = derived([tableState.pageRows, selectedDataIds], ([$pageRows, $selectedDataIds]) => {
|
|
155
|
+
return $pageRows.some((row) => {
|
|
156
|
+
if (!row.isData()) {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
return $selectedDataIds[row.dataId] === true;
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
const pluginState = {
|
|
163
|
+
selectedDataIds,
|
|
164
|
+
getRowState,
|
|
165
|
+
allRowsSelected,
|
|
166
|
+
someRowsSelected,
|
|
167
|
+
allPageRowsSelected,
|
|
168
|
+
somePageRowsSelected
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
pluginState,
|
|
172
|
+
hooks: {
|
|
173
|
+
'tbody.tr': (row) => {
|
|
174
|
+
const props = derived(selectedDataIds, ($selectedDataIds) => {
|
|
175
|
+
const someSubRowsSelected = isSomeSubRowsSelectedForRow(row, $selectedDataIds, linkDataSubRows);
|
|
176
|
+
const allSubRowsSelected = isAllSubRowsSelectedForRow(row, $selectedDataIds, linkDataSubRows);
|
|
177
|
+
const selected = row.isData()
|
|
178
|
+
? $selectedDataIds[row.dataId] === true
|
|
179
|
+
: allSubRowsSelected;
|
|
180
|
+
return {
|
|
181
|
+
selected,
|
|
182
|
+
someSubRowsSelected,
|
|
183
|
+
allSubRowsSelected
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
return { props };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { BodyRow } from '../bodyRows.js';
|
|
2
|
+
import type { TablePlugin, NewTablePropSet } from '../types/TablePlugin.js';
|
|
3
|
+
import { type Readable, type Writable } from 'svelte/store';
|
|
4
|
+
export interface SortByConfig {
|
|
5
|
+
initialSortKeys?: SortKey[];
|
|
6
|
+
disableMultiSort?: boolean;
|
|
7
|
+
isMultiSortEvent?: (event: Event) => boolean;
|
|
8
|
+
toggleOrder?: ('asc' | 'desc' | undefined)[];
|
|
9
|
+
serverSide?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface SortByState<Item> {
|
|
12
|
+
sortKeys: WritableSortKeys;
|
|
13
|
+
preSortedRows: Readable<BodyRow<Item>[]>;
|
|
14
|
+
}
|
|
15
|
+
export interface SortByColumnOptions {
|
|
16
|
+
disable?: boolean;
|
|
17
|
+
getSortValue?: (value: any) => string | number | (string | number)[];
|
|
18
|
+
compareFn?: (left: any, right: any) => number;
|
|
19
|
+
invert?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export type SortByPropSet = NewTablePropSet<{
|
|
22
|
+
'thead.tr.th': {
|
|
23
|
+
order: 'asc' | 'desc' | undefined;
|
|
24
|
+
toggle: (event: Event) => void;
|
|
25
|
+
clear: () => void;
|
|
26
|
+
disabled: boolean;
|
|
27
|
+
};
|
|
28
|
+
'tbody.tr.td': {
|
|
29
|
+
order: 'asc' | 'desc' | undefined;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
export interface SortKey {
|
|
33
|
+
id: string;
|
|
34
|
+
order: 'asc' | 'desc';
|
|
35
|
+
}
|
|
36
|
+
export declare const createSortKeysStore: (initKeys: SortKey[]) => WritableSortKeys;
|
|
37
|
+
interface ToggleOptions {
|
|
38
|
+
multiSort?: boolean;
|
|
39
|
+
toggleOrder?: ('asc' | 'desc' | undefined)[];
|
|
40
|
+
}
|
|
41
|
+
export type WritableSortKeys = Writable<SortKey[]> & {
|
|
42
|
+
toggleId: (id: string, options: ToggleOptions) => void;
|
|
43
|
+
clearId: (id: string) => void;
|
|
44
|
+
};
|
|
45
|
+
export declare const addSortBy: <Item>({ initialSortKeys, disableMultiSort, isMultiSortEvent, toggleOrder, serverSide }?: SortByConfig) => TablePlugin<Item, SortByState<Item>, SortByColumnOptions, SortByPropSet>;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { compare } from '../utils/compare.js';
|
|
2
|
+
import { isShiftClick } from '../utils/event.js';
|
|
3
|
+
import { derived, writable } from 'svelte/store';
|
|
4
|
+
const DEFAULT_TOGGLE_ORDER = ['asc', 'desc', undefined];
|
|
5
|
+
export const createSortKeysStore = (initKeys) => {
|
|
6
|
+
const { subscribe, update, set } = writable(initKeys);
|
|
7
|
+
const toggleId = (id, { multiSort = true, toggleOrder = DEFAULT_TOGGLE_ORDER } = {}) => {
|
|
8
|
+
update(($sortKeys) => {
|
|
9
|
+
const keyIdx = $sortKeys.findIndex((key) => key.id === id);
|
|
10
|
+
const key = $sortKeys[keyIdx];
|
|
11
|
+
const order = key?.order;
|
|
12
|
+
const orderIdx = toggleOrder.findIndex((o) => o === order);
|
|
13
|
+
const nextOrderIdx = (orderIdx + 1) % toggleOrder.length;
|
|
14
|
+
const nextOrder = toggleOrder[nextOrderIdx];
|
|
15
|
+
if (!multiSort) {
|
|
16
|
+
if (nextOrder === undefined) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return [{ id, order: nextOrder }];
|
|
20
|
+
}
|
|
21
|
+
if (keyIdx === -1 && nextOrder !== undefined) {
|
|
22
|
+
return [...$sortKeys, { id, order: nextOrder }];
|
|
23
|
+
}
|
|
24
|
+
if (nextOrder === undefined) {
|
|
25
|
+
return [...$sortKeys.slice(0, keyIdx), ...$sortKeys.slice(keyIdx + 1)];
|
|
26
|
+
}
|
|
27
|
+
return [
|
|
28
|
+
...$sortKeys.slice(0, keyIdx),
|
|
29
|
+
{ id, order: nextOrder },
|
|
30
|
+
...$sortKeys.slice(keyIdx + 1)
|
|
31
|
+
];
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
const clearId = (id) => {
|
|
35
|
+
update(($sortKeys) => {
|
|
36
|
+
const keyIdx = $sortKeys.findIndex((key) => key.id === id);
|
|
37
|
+
if (keyIdx === -1) {
|
|
38
|
+
return $sortKeys;
|
|
39
|
+
}
|
|
40
|
+
return [...$sortKeys.slice(0, keyIdx), ...$sortKeys.slice(keyIdx + 1)];
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
subscribe,
|
|
45
|
+
update,
|
|
46
|
+
set,
|
|
47
|
+
toggleId,
|
|
48
|
+
clearId
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const getSortedRows = (rows, sortKeys, columnOptions) => {
|
|
52
|
+
// Shallow clone to prevent sort affecting `preSortedRows`.
|
|
53
|
+
const $sortedRows = [...rows];
|
|
54
|
+
$sortedRows.sort((a, b) => {
|
|
55
|
+
for (const key of sortKeys) {
|
|
56
|
+
const invert = columnOptions[key.id]?.invert ?? false;
|
|
57
|
+
// TODO check why cellForId returns `undefined`.
|
|
58
|
+
const cellA = a.cellForId[key.id];
|
|
59
|
+
const cellB = b.cellForId[key.id];
|
|
60
|
+
let order = 0;
|
|
61
|
+
const compareFn = columnOptions[key.id]?.compareFn;
|
|
62
|
+
const getSortValue = columnOptions[key.id]?.getSortValue;
|
|
63
|
+
// Only need to check properties of `cellA` as both should have the same
|
|
64
|
+
// properties.
|
|
65
|
+
if (!cellA.isData()) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
const valueA = cellA.value;
|
|
69
|
+
const valueB = cellB.value;
|
|
70
|
+
if (compareFn !== undefined) {
|
|
71
|
+
order = compareFn(valueA, valueB);
|
|
72
|
+
}
|
|
73
|
+
else if (getSortValue !== undefined) {
|
|
74
|
+
const sortValueA = getSortValue(valueA);
|
|
75
|
+
const sortValueB = getSortValue(valueB);
|
|
76
|
+
order = compare(sortValueA, sortValueB);
|
|
77
|
+
}
|
|
78
|
+
else if (typeof valueA === 'string' || typeof valueA === 'number') {
|
|
79
|
+
// typeof `cellB.value` is logically equal to `cellA.value`.
|
|
80
|
+
order = compare(valueA, valueB);
|
|
81
|
+
}
|
|
82
|
+
else if (valueA instanceof Date || valueB instanceof Date) {
|
|
83
|
+
const sortValueA = valueA instanceof Date ? valueA.getTime() : 0;
|
|
84
|
+
const sortValueB = valueB instanceof Date ? valueB.getTime() : 0;
|
|
85
|
+
order = compare(sortValueA, sortValueB);
|
|
86
|
+
}
|
|
87
|
+
if (order !== 0) {
|
|
88
|
+
let orderFactor = 1;
|
|
89
|
+
// If the current key order is `'desc'`, reverse the order.
|
|
90
|
+
if (key.order === 'desc') {
|
|
91
|
+
orderFactor *= -1;
|
|
92
|
+
}
|
|
93
|
+
// If `invert` is `true`, we want to invert the sort without
|
|
94
|
+
// affecting the view model's indication.
|
|
95
|
+
if (invert) {
|
|
96
|
+
orderFactor *= -1;
|
|
97
|
+
}
|
|
98
|
+
return order * orderFactor;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return 0;
|
|
102
|
+
});
|
|
103
|
+
for (let i = 0; i < $sortedRows.length; i++) {
|
|
104
|
+
const { subRows } = $sortedRows[i];
|
|
105
|
+
if (subRows === undefined) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const sortedSubRows = getSortedRows(subRows, sortKeys, columnOptions);
|
|
109
|
+
const clonedRow = $sortedRows[i].clone();
|
|
110
|
+
clonedRow.subRows = sortedSubRows;
|
|
111
|
+
$sortedRows[i] = clonedRow;
|
|
112
|
+
}
|
|
113
|
+
return $sortedRows;
|
|
114
|
+
};
|
|
115
|
+
export const addSortBy = ({ initialSortKeys = [], disableMultiSort = false, isMultiSortEvent = isShiftClick, toggleOrder, serverSide = false } = {}) => ({ columnOptions }) => {
|
|
116
|
+
const disabledSortIds = Object.entries(columnOptions)
|
|
117
|
+
.filter(([, option]) => option.disable === true)
|
|
118
|
+
.map(([columnId]) => columnId);
|
|
119
|
+
const sortKeys = createSortKeysStore(initialSortKeys);
|
|
120
|
+
const preSortedRows = writable([]);
|
|
121
|
+
const deriveRows = (rows) => {
|
|
122
|
+
return derived([rows, sortKeys], ([$rows, $sortKeys]) => {
|
|
123
|
+
preSortedRows.set($rows);
|
|
124
|
+
if (serverSide) {
|
|
125
|
+
return $rows;
|
|
126
|
+
}
|
|
127
|
+
return getSortedRows($rows, $sortKeys, columnOptions);
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const pluginState = { sortKeys, preSortedRows };
|
|
131
|
+
return {
|
|
132
|
+
pluginState,
|
|
133
|
+
deriveRows,
|
|
134
|
+
hooks: {
|
|
135
|
+
'thead.tr.th': (cell) => {
|
|
136
|
+
const disabled = disabledSortIds.includes(cell.id);
|
|
137
|
+
const props = derived(sortKeys, ($sortKeys) => {
|
|
138
|
+
const key = $sortKeys.find((k) => k.id === cell.id);
|
|
139
|
+
const toggle = (event) => {
|
|
140
|
+
if (!cell.isData())
|
|
141
|
+
return;
|
|
142
|
+
if (disabled)
|
|
143
|
+
return;
|
|
144
|
+
sortKeys.toggleId(cell.id, {
|
|
145
|
+
multiSort: disableMultiSort ? false : isMultiSortEvent(event),
|
|
146
|
+
toggleOrder
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
const clear = () => {
|
|
150
|
+
if (!cell.isData())
|
|
151
|
+
return;
|
|
152
|
+
if (disabledSortIds.includes(cell.id))
|
|
153
|
+
return;
|
|
154
|
+
sortKeys.clearId(cell.id);
|
|
155
|
+
};
|
|
156
|
+
return {
|
|
157
|
+
order: key?.order,
|
|
158
|
+
toggle,
|
|
159
|
+
clear,
|
|
160
|
+
disabled
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
return { props };
|
|
164
|
+
},
|
|
165
|
+
'tbody.tr.td': (cell) => {
|
|
166
|
+
const props = derived(sortKeys, ($sortKeys) => {
|
|
167
|
+
const key = $sortKeys.find((k) => k.id === cell.id);
|
|
168
|
+
return {
|
|
169
|
+
order: key?.order
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
return { props };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
};
|