@humanspeak/svelte-headless-table 6.0.8 → 6.0.9

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.
@@ -191,21 +191,35 @@ export const createViewModel = (table, columns, { rowDataId } = {}) => {
191
191
  rows = fn(rows);
192
192
  });
193
193
  const pluginEntries = Object.entries(pluginInstances);
194
+ const trHookEntries = [];
195
+ const tdHookEntries = [];
196
+ for (const [name, instance] of pluginEntries) {
197
+ const trHook = instance.hooks?.['tbody.tr'];
198
+ if (trHook !== undefined)
199
+ trHookEntries.push([name, trHook]);
200
+ const tdHook = instance.hooks?.['tbody.tr.td'];
201
+ if (tdHook !== undefined)
202
+ tdHookEntries.push([name, tdHook]);
203
+ }
204
+ // Hoisted out of the per-row loop so we don't allocate a fresh
205
+ // closure on every iteration. For rows-10k that's 10,000 fewer
206
+ // closure allocations per derivation pass.
207
+ const injectCellState = (cell) => cell.injectState(tableState);
194
208
  const injectedRows = derived(rows, ($rows) => {
195
209
  const _t0 = performance.now();
196
210
  derivationCalls.injectedRows++;
197
211
  $rows.forEach((row) => {
198
212
  row.injectState(tableState);
199
- row.cells.forEach((cell) => cell.injectState(tableState));
200
- for (const [pluginName, pluginInstance] of pluginEntries) {
201
- const trHook = pluginInstance.hooks?.['tbody.tr'];
202
- if (trHook !== undefined) {
203
- row.applyHook(pluginName, trHook(row));
204
- }
205
- const tdHook = pluginInstance.hooks?.['tbody.tr.td'];
206
- if (tdHook !== undefined) {
207
- row.cells.forEach((cell) => cell.applyHook(pluginName, tdHook(cell)));
208
- }
213
+ row.cells.forEach(injectCellState);
214
+ for (const [pluginName, trHook] of trHookEntries) {
215
+ row.applyHook(pluginName, trHook(row));
216
+ }
217
+ if (tdHookEntries.length > 0) {
218
+ row.cells.forEach((cell) => {
219
+ for (const [pluginName, tdHook] of tdHookEntries) {
220
+ cell.applyHook(pluginName, tdHook(cell));
221
+ }
222
+ });
209
223
  }
210
224
  });
211
225
  _rows.set($rows);
@@ -215,7 +215,9 @@ export const getMergedRow = (cells) => {
215
215
  }
216
216
  const mergedCells = [];
217
217
  let startIdx = 0;
218
- let endIdx = 1;
218
+ // endIdx is initialized inside the merge branch (line ~293) before
219
+ // it's read; declared here only so the inner-while loop can see it.
220
+ let endIdx;
219
221
  while (startIdx < cells.length) {
220
222
  const cell = cells[startIdx].clone();
221
223
  if (!cell.isGroup()) {
@@ -1,3 +1,4 @@
1
+ import { MemoryCache } from '@humanspeak/memory-cache';
1
2
  import { derived, writable } from 'svelte/store';
2
3
  import { compare } from '../utils/compare.js';
3
4
  import { isShiftClick } from '../utils/event.js';
@@ -169,6 +170,16 @@ export const addSortBy = ({ initialSortKeys = [], disableMultiSort = false, isMu
169
170
  });
170
171
  };
171
172
  const pluginState = { sortKeys, preSortedRows };
173
+ // The `tbody.tr.td` hook output only depends on `cell.id` (the
174
+ // column ID — closed-over) and the reactive `sortKeys` store.
175
+ // Two body cells in the same column produce identical Readables,
176
+ // so we can share one Readable per column ID across every row.
177
+ // For rows-10k × 8 cols that collapses 80,000 Readable allocations
178
+ // per cold mount into 8. LRU eviction means we stay bounded even
179
+ // if column IDs churn over the view-model's lifetime.
180
+ const tdPropsCache = new MemoryCache({
181
+ maxSize: 256
182
+ });
172
183
  return {
173
184
  pluginState,
174
185
  deriveRows,
@@ -204,12 +215,16 @@ export const addSortBy = ({ initialSortKeys = [], disableMultiSort = false, isMu
204
215
  return { props };
205
216
  },
206
217
  'tbody.tr.td': (cell) => {
207
- const props = derived(sortKeys, ($sortKeys) => {
208
- const key = $sortKeys.find((k) => k.id === cell.id);
209
- return {
210
- order: key?.order
211
- };
212
- });
218
+ let props = tdPropsCache.get(cell.id);
219
+ if (props === undefined) {
220
+ props = derived(sortKeys, ($sortKeys) => {
221
+ const key = $sortKeys.find((k) => k.id === cell.id);
222
+ return {
223
+ order: key?.order
224
+ };
225
+ });
226
+ tdPropsCache.set(cell.id, props);
227
+ }
213
228
  return { props };
214
229
  }
215
230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@humanspeak/svelte-headless-table",
3
- "version": "6.0.8",
3
+ "version": "6.0.9",
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",
@@ -69,15 +69,15 @@
69
69
  "@faker-js/faker": "^10.4.0",
70
70
  "@playwright/test": "^1.60.0",
71
71
  "@sveltejs/adapter-auto": "^7.0.1",
72
- "@sveltejs/kit": "^2.60.1",
72
+ "@sveltejs/kit": "^2.61.1",
73
73
  "@sveltejs/package": "^2.5.7",
74
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
78
  "@types/node": "^25.9.1",
79
- "@typescript-eslint/eslint-plugin": "^8.59.4",
80
- "@typescript-eslint/parser": "^8.59.4",
79
+ "@typescript-eslint/eslint-plugin": "^8.60.0",
80
+ "@typescript-eslint/parser": "^8.60.0",
81
81
  "@vitest/coverage-v8": "^4.1.7",
82
82
  "eslint": "^10.4.0",
83
83
  "eslint-config-prettier": "10.1.8",
@@ -97,7 +97,7 @@
97
97
  "tslib": "^2.8.1",
98
98
  "type-fest": "^5.6.0",
99
99
  "typescript": "^6.0.3",
100
- "typescript-eslint": "^8.59.4",
100
+ "typescript-eslint": "^8.60.0",
101
101
  "vite": "^8.0.14",
102
102
  "vitest": "^4.1.7"
103
103
  },