@humanspeak/svelte-headless-table 6.0.6 → 6.0.8
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/dist/bodyRows.js +13 -9
- package/dist/createViewModel.d.ts +22 -1
- package/dist/createViewModel.js +40 -1
- package/package.json +28 -26
package/dist/bodyRows.js
CHANGED
|
@@ -235,19 +235,23 @@ export const getColumnedBodyRows = (rows, columnIdOrder) => {
|
|
|
235
235
|
if (rows.length === 0 || columnIdOrder.length === 0)
|
|
236
236
|
return rows;
|
|
237
237
|
rows.forEach((row, rowIdx) => {
|
|
238
|
-
//
|
|
239
|
-
//
|
|
240
|
-
|
|
238
|
+
// Build `cellForId` directly during the clone pass so we don't
|
|
239
|
+
// pay for an intermediate Map + Object.fromEntries detour
|
|
240
|
+
// (which is what the previous shape did — see plan-1A in
|
|
241
|
+
// .notes/performance-optimization-plan.md). The id-keyed object
|
|
242
|
+
// gives us O(1) lookups for `visibleCells` and is the same shape
|
|
243
|
+
// BodyRow.cellForId already expects.
|
|
244
|
+
const cellForId = {};
|
|
245
|
+
row.cells.forEach((cell) => {
|
|
241
246
|
const clonedCell = cell.clone();
|
|
242
247
|
clonedCell.row = columnedRows[rowIdx];
|
|
243
|
-
|
|
248
|
+
cellForId[clonedCell.id] = clonedCell;
|
|
244
249
|
});
|
|
245
|
-
const
|
|
246
|
-
const visibleCells = columnIdOrder.map((cid) => cellById.get(cid)).filter(nonUndefined);
|
|
250
|
+
const visibleCells = columnIdOrder.map((cid) => cellForId[cid]).filter(nonUndefined);
|
|
247
251
|
columnedRows[rowIdx].cells = visibleCells;
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
columnedRows[rowIdx].cellForId =
|
|
252
|
+
// `cellForId` includes hidden cells so row transformations can
|
|
253
|
+
// still reach them.
|
|
254
|
+
columnedRows[rowIdx].cellForId = cellForId;
|
|
251
255
|
});
|
|
252
256
|
return columnedRows;
|
|
253
257
|
};
|
|
@@ -60,10 +60,31 @@ export interface ViewModelDebug {
|
|
|
60
60
|
injectedPageRows: number;
|
|
61
61
|
headerRows: number;
|
|
62
62
|
};
|
|
63
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Per-derivation cumulative wall-clock in milliseconds, accumulated
|
|
65
|
+
* via `performance.now()` deltas inside each `derived(...)` body.
|
|
66
|
+
* Mirrors `derivationCalls` so the perf bench can attribute a
|
|
67
|
+
* scenario's render budget to a specific derivation rather than the
|
|
68
|
+
* aggregated `firstPaintMs`. Reset by `resetCounters()`.
|
|
69
|
+
*/
|
|
70
|
+
derivationTimings: {
|
|
71
|
+
tableAttrs: number;
|
|
72
|
+
tableHeadAttrs: number;
|
|
73
|
+
tableBodyAttrs: number;
|
|
74
|
+
visibleColumns: number;
|
|
75
|
+
columnedRows: number;
|
|
76
|
+
rows: number;
|
|
77
|
+
injectedRows: number;
|
|
78
|
+
pageRows: number;
|
|
79
|
+
injectedPageRows: number;
|
|
80
|
+
headerRows: number;
|
|
81
|
+
};
|
|
82
|
+
/** Reset all derivation call counters and timings to 0 */
|
|
64
83
|
resetCounters: () => void;
|
|
65
84
|
/** Get total derivation calls since last reset */
|
|
66
85
|
getTotalCalls: () => number;
|
|
86
|
+
/** Get total derivation wall-clock (ms) since last reset */
|
|
87
|
+
getTotalMs: () => number;
|
|
67
88
|
}
|
|
68
89
|
/**
|
|
69
90
|
* The view model for a table, containing all reactive stores and state.
|
package/dist/createViewModel.js
CHANGED
|
@@ -30,6 +30,23 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
30
30
|
injectedPageRows: 0,
|
|
31
31
|
headerRows: 0
|
|
32
32
|
};
|
|
33
|
+
// Per-derivation cumulative ms, populated alongside derivationCalls.
|
|
34
|
+
// Each `derived(...)` body wraps its work in performance.now() pairs
|
|
35
|
+
// so the perf bench can attribute a scenario's render budget to a
|
|
36
|
+
// specific derivation. `rows` / `pageRows` stay at 0 — they're
|
|
37
|
+
// plugin-pipeline pass-throughs that don't run a body of their own.
|
|
38
|
+
const derivationTimings = {
|
|
39
|
+
tableAttrs: 0,
|
|
40
|
+
tableHeadAttrs: 0,
|
|
41
|
+
tableBodyAttrs: 0,
|
|
42
|
+
visibleColumns: 0,
|
|
43
|
+
columnedRows: 0,
|
|
44
|
+
rows: 0,
|
|
45
|
+
injectedRows: 0,
|
|
46
|
+
pageRows: 0,
|
|
47
|
+
injectedPageRows: 0,
|
|
48
|
+
headerRows: 0
|
|
49
|
+
};
|
|
33
50
|
const $flatColumns = getFlatColumns(columns);
|
|
34
51
|
const flatColumns = readable($flatColumns);
|
|
35
52
|
const originalRows = derived([data, flatColumns], ([$data, $flatColumns]) => {
|
|
@@ -103,9 +120,11 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
103
120
|
tableAttrs = fn(tableAttrs);
|
|
104
121
|
});
|
|
105
122
|
const finalizedTableAttrs = derived(tableAttrs, ($tableAttrs) => {
|
|
123
|
+
const _t0 = performance.now();
|
|
106
124
|
derivationCalls.tableAttrs++;
|
|
107
125
|
const $finalizedAttrs = finalizeAttributes($tableAttrs);
|
|
108
126
|
_tableAttrs.set($finalizedAttrs);
|
|
127
|
+
derivationTimings.tableAttrs += performance.now() - _t0;
|
|
109
128
|
return $finalizedAttrs;
|
|
110
129
|
});
|
|
111
130
|
const deriveTableHeadAttrsFns = Object.values(pluginInstances)
|
|
@@ -116,9 +135,11 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
116
135
|
tableHeadAttrs = fn(tableHeadAttrs);
|
|
117
136
|
});
|
|
118
137
|
const finalizedTableHeadAttrs = derived(tableHeadAttrs, ($tableHeadAttrs) => {
|
|
138
|
+
const _t0 = performance.now();
|
|
119
139
|
derivationCalls.tableHeadAttrs++;
|
|
120
140
|
const $finalizedAttrs = finalizeAttributes($tableHeadAttrs);
|
|
121
141
|
_tableHeadAttrs.set($finalizedAttrs);
|
|
142
|
+
derivationTimings.tableHeadAttrs += performance.now() - _t0;
|
|
122
143
|
return $finalizedAttrs;
|
|
123
144
|
});
|
|
124
145
|
const deriveTableBodyAttrsFns = Object.values(pluginInstances)
|
|
@@ -131,9 +152,11 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
131
152
|
tableBodyAttrs = fn(tableBodyAttrs);
|
|
132
153
|
});
|
|
133
154
|
const finalizedTableBodyAttrs = derived(tableBodyAttrs, ($tableBodyAttrs) => {
|
|
155
|
+
const _t0 = performance.now();
|
|
134
156
|
derivationCalls.tableBodyAttrs++;
|
|
135
157
|
const $finalizedAttrs = finalizeAttributes($tableBodyAttrs);
|
|
136
158
|
_tableBodyAttrs.set($finalizedAttrs);
|
|
159
|
+
derivationTimings.tableBodyAttrs += performance.now() - _t0;
|
|
137
160
|
return $finalizedAttrs;
|
|
138
161
|
});
|
|
139
162
|
const deriveFlatColumnsFns = Object.values(pluginInstances)
|
|
@@ -146,13 +169,18 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
146
169
|
visibleColumns = fn(visibleColumns);
|
|
147
170
|
});
|
|
148
171
|
const injectedColumns = derived(visibleColumns, ($visibleColumns) => {
|
|
172
|
+
const _t0 = performance.now();
|
|
149
173
|
derivationCalls.visibleColumns++;
|
|
150
174
|
_visibleColumns.set($visibleColumns);
|
|
175
|
+
derivationTimings.visibleColumns += performance.now() - _t0;
|
|
151
176
|
return $visibleColumns;
|
|
152
177
|
});
|
|
153
178
|
const columnedRows = derived([originalRows, injectedColumns], ([$originalRows, $injectedColumns]) => {
|
|
179
|
+
const _t0 = performance.now();
|
|
154
180
|
derivationCalls.columnedRows++;
|
|
155
|
-
|
|
181
|
+
const result = getColumnedBodyRows($originalRows, $injectedColumns.map((c) => c.id));
|
|
182
|
+
derivationTimings.columnedRows += performance.now() - _t0;
|
|
183
|
+
return result;
|
|
156
184
|
});
|
|
157
185
|
const deriveRowsFns = Object.values(pluginInstances)
|
|
158
186
|
.map((pluginInstance) => pluginInstance.deriveRows)
|
|
@@ -164,6 +192,7 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
164
192
|
});
|
|
165
193
|
const pluginEntries = Object.entries(pluginInstances);
|
|
166
194
|
const injectedRows = derived(rows, ($rows) => {
|
|
195
|
+
const _t0 = performance.now();
|
|
167
196
|
derivationCalls.injectedRows++;
|
|
168
197
|
$rows.forEach((row) => {
|
|
169
198
|
row.injectState(tableState);
|
|
@@ -180,6 +209,7 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
180
209
|
}
|
|
181
210
|
});
|
|
182
211
|
_rows.set($rows);
|
|
212
|
+
derivationTimings.injectedRows += performance.now() - _t0;
|
|
183
213
|
return $rows;
|
|
184
214
|
});
|
|
185
215
|
const derivePageRowsFns = Object.values(pluginInstances)
|
|
@@ -194,11 +224,14 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
194
224
|
// Page rows are a subset of the same object references already processed
|
|
195
225
|
// by injectedRows — no need to re-inject state or re-apply hooks.
|
|
196
226
|
const injectedPageRows = derived(pageRows, ($pageRows) => {
|
|
227
|
+
const _t0 = performance.now();
|
|
197
228
|
derivationCalls.injectedPageRows++;
|
|
198
229
|
_pageRows.set($pageRows);
|
|
230
|
+
derivationTimings.injectedPageRows += performance.now() - _t0;
|
|
199
231
|
return $pageRows;
|
|
200
232
|
});
|
|
201
233
|
const headerRows = derived(injectedColumns, ($injectedColumns) => {
|
|
234
|
+
const _t0 = performance.now();
|
|
202
235
|
derivationCalls.headerRows++;
|
|
203
236
|
const $headerRows = getHeaderRows(columns, $injectedColumns.map((c) => c.id));
|
|
204
237
|
$headerRows.forEach((row) => {
|
|
@@ -216,6 +249,7 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
216
249
|
}
|
|
217
250
|
});
|
|
218
251
|
_headerRows.set($headerRows);
|
|
252
|
+
derivationTimings.headerRows += performance.now() - _t0;
|
|
219
253
|
return $headerRows;
|
|
220
254
|
});
|
|
221
255
|
const _debug = {
|
|
@@ -230,13 +264,18 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
|
|
|
230
264
|
pageRows: derivePageRowsFns.length + 1 // +1 for injected
|
|
231
265
|
},
|
|
232
266
|
derivationCalls,
|
|
267
|
+
derivationTimings,
|
|
233
268
|
resetCounters: () => {
|
|
234
269
|
Object.keys(derivationCalls).forEach((key) => {
|
|
235
270
|
derivationCalls[key] = 0;
|
|
271
|
+
derivationTimings[key] = 0;
|
|
236
272
|
});
|
|
237
273
|
},
|
|
238
274
|
getTotalCalls: () => {
|
|
239
275
|
return Object.values(derivationCalls).reduce((sum, count) => sum + count, 0);
|
|
276
|
+
},
|
|
277
|
+
getTotalMs: () => {
|
|
278
|
+
return Object.values(derivationTimings).reduce((sum, ms) => sum + ms, 0);
|
|
240
279
|
}
|
|
241
280
|
};
|
|
242
281
|
return {
|
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.8",
|
|
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,57 +58,58 @@
|
|
|
58
58
|
"!dist/**/*.spec.*"
|
|
59
59
|
],
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@humanspeak/memory-cache": "^1.0.
|
|
61
|
+
"@humanspeak/memory-cache": "^1.0.6",
|
|
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
|
-
"@eslint/compat": "^2.0
|
|
67
|
+
"@eslint/compat": "^2.1.0",
|
|
68
68
|
"@eslint/js": "^10.0.1",
|
|
69
|
-
"@faker-js/faker": "^10.
|
|
70
|
-
"@playwright/test": "^1.
|
|
69
|
+
"@faker-js/faker": "^10.4.0",
|
|
70
|
+
"@playwright/test": "^1.60.0",
|
|
71
71
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
72
|
-
"@sveltejs/kit": "^2.
|
|
72
|
+
"@sveltejs/kit": "^2.60.1",
|
|
73
73
|
"@sveltejs/package": "^2.5.7",
|
|
74
|
-
"@sveltejs/vite-plugin-svelte": "^
|
|
74
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.2",
|
|
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.
|
|
81
|
-
"@vitest/coverage-v8": "^4.
|
|
82
|
-
"eslint": "^10.0
|
|
78
|
+
"@types/node": "^25.9.1",
|
|
79
|
+
"@typescript-eslint/eslint-plugin": "^8.59.4",
|
|
80
|
+
"@typescript-eslint/parser": "^8.59.4",
|
|
81
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
82
|
+
"eslint": "^10.4.0",
|
|
83
83
|
"eslint-config-prettier": "10.1.8",
|
|
84
84
|
"eslint-plugin-import": "2.32.0",
|
|
85
|
-
"eslint-plugin-svelte": "3.
|
|
85
|
+
"eslint-plugin-svelte": "3.17.1",
|
|
86
86
|
"eslint-plugin-unused-imports": "4.4.1",
|
|
87
|
-
"globals": "^17.
|
|
87
|
+
"globals": "^17.6.0",
|
|
88
88
|
"husky": "^9.1.7",
|
|
89
|
-
"prettier": "^3.8.
|
|
89
|
+
"prettier": "^3.8.3",
|
|
90
90
|
"prettier-plugin-organize-imports": "^4.3.0",
|
|
91
91
|
"prettier-plugin-sort-json": "^4.2.0",
|
|
92
|
-
"prettier-plugin-svelte": "^
|
|
93
|
-
"prettier-plugin-tailwindcss": "^0.
|
|
94
|
-
"publint": "^0.3.
|
|
95
|
-
"svelte": "^5.
|
|
96
|
-
"svelte-check": "^4.4.
|
|
92
|
+
"prettier-plugin-svelte": "^4.0.1",
|
|
93
|
+
"prettier-plugin-tailwindcss": "^0.8.0",
|
|
94
|
+
"publint": "^0.3.21",
|
|
95
|
+
"svelte": "^5.55.9",
|
|
96
|
+
"svelte-check": "^4.4.8",
|
|
97
97
|
"tslib": "^2.8.1",
|
|
98
|
-
"type-fest": "^5.
|
|
99
|
-
"typescript": "^
|
|
100
|
-
"typescript-eslint": "^8.
|
|
101
|
-
"vite": "^
|
|
102
|
-
"vitest": "^4.
|
|
98
|
+
"type-fest": "^5.6.0",
|
|
99
|
+
"typescript": "^6.0.3",
|
|
100
|
+
"typescript-eslint": "^8.59.4",
|
|
101
|
+
"vite": "^8.0.14",
|
|
102
|
+
"vitest": "^4.1.7"
|
|
103
103
|
},
|
|
104
104
|
"peerDependencies": {
|
|
105
105
|
"svelte": "^5.30.0"
|
|
106
106
|
},
|
|
107
107
|
"volta": {
|
|
108
|
-
"node": "24.
|
|
108
|
+
"node": "24.15.0"
|
|
109
109
|
},
|
|
110
110
|
"scripts": {
|
|
111
111
|
"build": "vite build && npm run package",
|
|
112
|
+
"cf-typegen": "pnpm --filter docs cf-typegen",
|
|
112
113
|
"changeset": "npx @changesets/cli",
|
|
113
114
|
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
114
115
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
@@ -119,6 +120,7 @@
|
|
|
119
120
|
"lint": "prettier --check . && eslint .",
|
|
120
121
|
"lint:fix": "npm run format && eslint . --fix",
|
|
121
122
|
"package": "svelte-kit sync && svelte-package && publint",
|
|
123
|
+
"perf:bench": "node scripts/perf-bench.mjs",
|
|
122
124
|
"preview": "vite preview",
|
|
123
125
|
"test": "vitest run --coverage",
|
|
124
126
|
"test:all": "npm run test && npm run test:e2e",
|