@humanspeak/svelte-headless-table 6.0.4 → 6.0.5
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 +1 -1
- package/dist/bodyRows.js +3 -8
- package/dist/createViewModel.js +23 -52
- package/dist/plugins/addColumnOrder.js +10 -5
- package/dist/plugins/addGroupBy.js +6 -2
- package/dist/plugins/addVirtualScroll.js +18 -6
- package/package.json +22 -23
package/LICENSE
CHANGED
package/dist/bodyRows.js
CHANGED
|
@@ -242,17 +242,12 @@ export const getColumnedBodyRows = (rows, columnIdOrder) => {
|
|
|
242
242
|
clonedCell.row = columnedRows[rowIdx];
|
|
243
243
|
return clonedCell;
|
|
244
244
|
});
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
return cells.find((c) => c.id === cid);
|
|
248
|
-
})
|
|
249
|
-
.filter(nonUndefined);
|
|
245
|
+
const cellById = new Map(cells.map((c) => [c.id, c]));
|
|
246
|
+
const visibleCells = columnIdOrder.map((cid) => cellById.get(cid)).filter(nonUndefined);
|
|
250
247
|
columnedRows[rowIdx].cells = visibleCells;
|
|
251
248
|
// Include hidden cells in `cellForId` to allow row transformations on
|
|
252
249
|
// hidden cells.
|
|
253
|
-
|
|
254
|
-
columnedRows[rowIdx].cellForId[cell.id] = cell;
|
|
255
|
-
});
|
|
250
|
+
columnedRows[rowIdx].cellForId = Object.fromEntries(cellById);
|
|
256
251
|
});
|
|
257
252
|
return columnedRows;
|
|
258
253
|
};
|
package/dist/createViewModel.js
CHANGED
|
@@ -162,27 +162,22 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
162
162
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
163
163
|
rows = fn(rows);
|
|
164
164
|
});
|
|
165
|
+
const pluginEntries = Object.entries(pluginInstances);
|
|
165
166
|
const injectedRows = derived(rows, ($rows) => {
|
|
166
167
|
derivationCalls.injectedRows++;
|
|
167
|
-
// Inject state.
|
|
168
168
|
$rows.forEach((row) => {
|
|
169
169
|
row.injectState(tableState);
|
|
170
|
-
row.cells.forEach((cell) =>
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
Object.entries(pluginInstances).forEach(([pluginName, pluginInstance]) => {
|
|
176
|
-
$rows.forEach((row) => {
|
|
177
|
-
if (pluginInstance.hooks?.['tbody.tr'] !== undefined) {
|
|
178
|
-
row.applyHook(pluginName, pluginInstance.hooks['tbody.tr'](row));
|
|
170
|
+
row.cells.forEach((cell) => cell.injectState(tableState));
|
|
171
|
+
for (const [pluginName, pluginInstance] of pluginEntries) {
|
|
172
|
+
const trHook = pluginInstance.hooks?.['tbody.tr'];
|
|
173
|
+
if (trHook !== undefined) {
|
|
174
|
+
row.applyHook(pluginName, trHook(row));
|
|
179
175
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
});
|
|
176
|
+
const tdHook = pluginInstance.hooks?.['tbody.tr.td'];
|
|
177
|
+
if (tdHook !== undefined) {
|
|
178
|
+
row.cells.forEach((cell) => cell.applyHook(pluginName, tdHook(cell)));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
186
181
|
});
|
|
187
182
|
_rows.set($rows);
|
|
188
183
|
return $rows;
|
|
@@ -196,53 +191,29 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
196
191
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
197
192
|
pageRows = fn(pageRows);
|
|
198
193
|
});
|
|
194
|
+
// Page rows are a subset of the same object references already processed
|
|
195
|
+
// by injectedRows — no need to re-inject state or re-apply hooks.
|
|
199
196
|
const injectedPageRows = derived(pageRows, ($pageRows) => {
|
|
200
197
|
derivationCalls.injectedPageRows++;
|
|
201
|
-
// Inject state.
|
|
202
|
-
$pageRows.forEach((row) => {
|
|
203
|
-
row.injectState(tableState);
|
|
204
|
-
row.cells.forEach((cell) => {
|
|
205
|
-
cell.injectState(tableState);
|
|
206
|
-
});
|
|
207
|
-
});
|
|
208
|
-
// Apply plugin component hooks.
|
|
209
|
-
Object.entries(pluginInstances).forEach(([pluginName, pluginInstance]) => {
|
|
210
|
-
$pageRows.forEach((row) => {
|
|
211
|
-
if (pluginInstance.hooks?.['tbody.tr'] !== undefined) {
|
|
212
|
-
row.applyHook(pluginName, pluginInstance.hooks['tbody.tr'](row));
|
|
213
|
-
}
|
|
214
|
-
row.cells.forEach((cell) => {
|
|
215
|
-
if (pluginInstance.hooks?.['tbody.tr.td'] !== undefined) {
|
|
216
|
-
cell.applyHook(pluginName, pluginInstance.hooks['tbody.tr.td'](cell));
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
198
|
_pageRows.set($pageRows);
|
|
222
199
|
return $pageRows;
|
|
223
200
|
});
|
|
224
201
|
const headerRows = derived(injectedColumns, ($injectedColumns) => {
|
|
225
202
|
derivationCalls.headerRows++;
|
|
226
203
|
const $headerRows = getHeaderRows(columns, $injectedColumns.map((c) => c.id));
|
|
227
|
-
// Inject state.
|
|
228
204
|
$headerRows.forEach((row) => {
|
|
229
205
|
row.injectState(tableState);
|
|
230
|
-
row.cells.forEach((cell) =>
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
Object.entries(pluginInstances).forEach(([pluginName, pluginInstance]) => {
|
|
236
|
-
$headerRows.forEach((row) => {
|
|
237
|
-
if (pluginInstance.hooks?.['thead.tr'] !== undefined) {
|
|
238
|
-
row.applyHook(pluginName, pluginInstance.hooks['thead.tr'](row));
|
|
206
|
+
row.cells.forEach((cell) => cell.injectState(tableState));
|
|
207
|
+
for (const [pluginName, pluginInstance] of pluginEntries) {
|
|
208
|
+
const trHook = pluginInstance.hooks?.['thead.tr'];
|
|
209
|
+
if (trHook !== undefined) {
|
|
210
|
+
row.applyHook(pluginName, trHook(row));
|
|
239
211
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
});
|
|
212
|
+
const thHook = pluginInstance.hooks?.['thead.tr.th'];
|
|
213
|
+
if (thHook !== undefined) {
|
|
214
|
+
row.cells.forEach((cell) => cell.applyHook(pluginName, thHook(cell)));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
246
217
|
});
|
|
247
218
|
_headerRows.set($headerRows);
|
|
248
219
|
return $headerRows;
|
|
@@ -25,15 +25,20 @@ export const addColumnOrder = ({ initialColumnIdOrder = [], hideUnspecifiedColum
|
|
|
25
25
|
const pluginState = { columnIdOrder };
|
|
26
26
|
const deriveFlatColumns = (flatColumns) => {
|
|
27
27
|
return derived([flatColumns, columnIdOrder], ([$flatColumns, $columnIdOrder]) => {
|
|
28
|
-
const
|
|
28
|
+
const colById = new Map($flatColumns.map((c) => [c.id, c]));
|
|
29
29
|
const orderedFlatColumns = [];
|
|
30
30
|
$columnIdOrder.forEach((id) => {
|
|
31
|
-
const
|
|
32
|
-
|
|
31
|
+
const col = colById.get(id);
|
|
32
|
+
if (col !== undefined) {
|
|
33
|
+
orderedFlatColumns.push(col);
|
|
34
|
+
colById.delete(id);
|
|
35
|
+
}
|
|
33
36
|
});
|
|
34
37
|
if (!hideUnspecifiedColumns) {
|
|
35
|
-
//
|
|
36
|
-
|
|
38
|
+
// Remaining entries preserve original $flatColumns order.
|
|
39
|
+
for (const col of colById.values()) {
|
|
40
|
+
orderedFlatColumns.push(col);
|
|
41
|
+
}
|
|
37
42
|
}
|
|
38
43
|
return orderedFlatColumns;
|
|
39
44
|
});
|
|
@@ -59,8 +59,12 @@ export const getGroupedRows = (rows, groupByIds, columnOptions, { repeatCellIds,
|
|
|
59
59
|
if (typeof groupOnValue === 'function' || typeof groupOnValue === 'object') {
|
|
60
60
|
console.warn(`Missing \`getGroupOn\` column option to aggregate column "${groupById}" with object values`);
|
|
61
61
|
}
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
let subRows = subRowsForGroupOnValue.get(groupOnValue);
|
|
63
|
+
if (subRows === undefined) {
|
|
64
|
+
subRows = [];
|
|
65
|
+
subRowsForGroupOnValue.set(groupOnValue, subRows);
|
|
66
|
+
}
|
|
67
|
+
subRows.push(row);
|
|
64
68
|
}
|
|
65
69
|
const groupedRows = [];
|
|
66
70
|
let groupRowIdx = 0;
|
|
@@ -59,10 +59,18 @@ export const addVirtualScroll = ({ onLoadMore, hasMore: hasMoreConfig, loadMoreT
|
|
|
59
59
|
let scrollContainer = null;
|
|
60
60
|
// Cache for row lookup (set by derivePageRows)
|
|
61
61
|
let allRowsCache = [];
|
|
62
|
-
// Visible range calculation
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
62
|
+
// Visible range calculation.
|
|
63
|
+
// Return the same object reference when the range hasn't changed to avoid
|
|
64
|
+
// unnecessary downstream store updates (spacer heights, rendered rows).
|
|
65
|
+
let currentRange = { start: 0, end: 0 };
|
|
66
|
+
const visibleRange = derived([rowIds, scrollTop, viewportHeight], ([$rowIds, $scrollTop, $viewportHeight], set) => {
|
|
67
|
+
const range = heightManager.getVisibleRange($rowIds, $scrollTop, $viewportHeight, bufferSize);
|
|
68
|
+
if (range.start === currentRange.start && range.end === currentRange.end) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
currentRange = range;
|
|
72
|
+
set(range);
|
|
73
|
+
}, currentRange);
|
|
66
74
|
// Total height of all rows
|
|
67
75
|
const totalHeight = derived(rowIds, ($rowIds) => {
|
|
68
76
|
return heightManager.getTotalHeight($rowIds);
|
|
@@ -110,8 +118,7 @@ export const addVirtualScroll = ({ onLoadMore, hasMore: hasMoreConfig, loadMoreT
|
|
|
110
118
|
*/
|
|
111
119
|
const handleScroll = (event) => {
|
|
112
120
|
const target = event.target;
|
|
113
|
-
|
|
114
|
-
scrollTop.set(newScrollTop);
|
|
121
|
+
scrollTop.set(target.scrollTop);
|
|
115
122
|
checkLoadMore();
|
|
116
123
|
};
|
|
117
124
|
/**
|
|
@@ -119,6 +126,11 @@ export const addVirtualScroll = ({ onLoadMore, hasMore: hasMoreConfig, loadMoreT
|
|
|
119
126
|
*/
|
|
120
127
|
const virtualScroll = (node) => {
|
|
121
128
|
scrollContainer = node;
|
|
129
|
+
// Disable overflow-anchor to prevent the browser from adjusting
|
|
130
|
+
// scrollTop when spacer heights change. Without this, a feedback
|
|
131
|
+
// loop occurs: spacer change → browser adjusts scrollTop → scroll
|
|
132
|
+
// event → new visible range → spacer change → cascades to bottom.
|
|
133
|
+
node.style.overflowAnchor = 'none';
|
|
122
134
|
// Set initial viewport height
|
|
123
135
|
const initialHeight = node.clientHeight;
|
|
124
136
|
viewportHeight.set(initialHeight);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@humanspeak/svelte-headless-table",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.5",
|
|
4
4
|
"description": "A powerful, headless table library for Svelte that provides complete control over table UI while handling complex data operations like sorting, filtering, pagination, grouping, and row expansion. Build custom, accessible data tables with zero styling opinions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|
|
@@ -58,47 +58,46 @@
|
|
|
58
58
|
"!dist/**/*.spec.*"
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@humanspeak/memory-cache": "^1.0.
|
|
61
|
+
"@humanspeak/memory-cache": "^1.0.5",
|
|
62
62
|
"@humanspeak/svelte-keyed": "^5.0.1",
|
|
63
63
|
"@humanspeak/svelte-render": "^5.1.1",
|
|
64
64
|
"@humanspeak/svelte-subscribe": "^5.0.0"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"@eslint/compat": "^2.0.2",
|
|
68
|
-
"@eslint/js": "^
|
|
69
|
-
"@faker-js/faker": "^10.
|
|
70
|
-
"@playwright/test": "^1.58.
|
|
71
|
-
"@sveltejs/adapter-auto": "^7.0.
|
|
72
|
-
"@sveltejs/kit": "^2.
|
|
68
|
+
"@eslint/js": "^10.0.1",
|
|
69
|
+
"@faker-js/faker": "^10.3.0",
|
|
70
|
+
"@playwright/test": "^1.58.2",
|
|
71
|
+
"@sveltejs/adapter-auto": "^7.0.1",
|
|
72
|
+
"@sveltejs/kit": "^2.53.4",
|
|
73
73
|
"@sveltejs/package": "^2.5.7",
|
|
74
74
|
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
75
75
|
"@testing-library/jest-dom": "^6.9.1",
|
|
76
76
|
"@testing-library/svelte": "^5.3.1",
|
|
77
77
|
"@types/eslint": "9.6.1",
|
|
78
|
-
"@types/node": "^25.
|
|
79
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
80
|
-
"@typescript-eslint/parser": "^8.
|
|
78
|
+
"@types/node": "^25.3.3",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
80
|
+
"@typescript-eslint/parser": "^8.56.1",
|
|
81
81
|
"@vitest/coverage-v8": "^4.0.18",
|
|
82
|
-
"
|
|
83
|
-
"eslint": "^9.39.2",
|
|
82
|
+
"eslint": "^10.0.2",
|
|
84
83
|
"eslint-config-prettier": "10.1.8",
|
|
85
84
|
"eslint-plugin-import": "2.32.0",
|
|
86
|
-
"eslint-plugin-svelte": "3.
|
|
87
|
-
"eslint-plugin-unused-imports": "4.
|
|
88
|
-
"globals": "^17.
|
|
85
|
+
"eslint-plugin-svelte": "3.15.0",
|
|
86
|
+
"eslint-plugin-unused-imports": "4.4.1",
|
|
87
|
+
"globals": "^17.4.0",
|
|
89
88
|
"husky": "^9.1.7",
|
|
90
89
|
"prettier": "^3.8.1",
|
|
91
90
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
92
91
|
"prettier-plugin-sort-json": "^4.2.0",
|
|
93
|
-
"prettier-plugin-svelte": "^3.
|
|
92
|
+
"prettier-plugin-svelte": "^3.5.1",
|
|
94
93
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
95
|
-
"publint": "^0.3.
|
|
96
|
-
"svelte": "^5.
|
|
97
|
-
"svelte-check": "^4.
|
|
94
|
+
"publint": "^0.3.18",
|
|
95
|
+
"svelte": "^5.53.7",
|
|
96
|
+
"svelte-check": "^4.4.4",
|
|
98
97
|
"tslib": "^2.8.1",
|
|
99
|
-
"type-fest": "^5.4.
|
|
98
|
+
"type-fest": "^5.4.4",
|
|
100
99
|
"typescript": "^5.9.3",
|
|
101
|
-
"typescript-eslint": "^8.
|
|
100
|
+
"typescript-eslint": "^8.56.1",
|
|
102
101
|
"vite": "^7.3.1",
|
|
103
102
|
"vitest": "^4.0.18"
|
|
104
103
|
},
|
|
@@ -106,7 +105,7 @@
|
|
|
106
105
|
"svelte": "^5.30.0"
|
|
107
106
|
},
|
|
108
107
|
"volta": {
|
|
109
|
-
"node": "24.
|
|
108
|
+
"node": "24.14.0"
|
|
110
109
|
},
|
|
111
110
|
"scripts": {
|
|
112
111
|
"build": "vite build && npm run package",
|
|
@@ -114,7 +113,7 @@
|
|
|
114
113
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
115
114
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
116
115
|
"dev": "vite dev",
|
|
117
|
-
"dev:all": "
|
|
116
|
+
"dev:all": "mprocs",
|
|
118
117
|
"dev:pkg": "svelte-kit sync && svelte-package --watch",
|
|
119
118
|
"format": "prettier --write .",
|
|
120
119
|
"lint": "prettier --check . && eslint .",
|