@svgrid/grid 2.6.21 → 2.6.22

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +22 -0
  3. package/dist/GridMenus.svelte +17 -12
  4. package/dist/SvGrid.controller.svelte.d.ts +13 -8
  5. package/dist/SvGrid.controller.svelte.js +150 -72
  6. package/dist/SvGrid.css +1 -1
  7. package/dist/SvGrid.svelte +110 -56
  8. package/dist/SvGrid.types.d.ts +41 -1
  9. package/dist/cdn/{GridMenus-BfTAKn84.js → GridMenus-BuoBPqxx.js} +137 -132
  10. package/dist/cdn/GridMenus-n4llxoOI.js +494 -0
  11. package/dist/cdn/column-resize-DsfNXMom.js +102 -0
  12. package/dist/cdn/row-resize-BRcimkUT.js +95 -0
  13. package/dist/cdn/{src-BYq-qyrp.js → src-C9Hihx1W.js} +3456 -3459
  14. package/dist/cdn/{src-DBel9wRZ.js → src-D1lXwq1l.js} +8283 -8286
  15. package/dist/cdn/svgrid.js +10 -8
  16. package/dist/cdn/svgrid.svelte-external.js +10 -8
  17. package/dist/column-groups.js +1 -1
  18. package/dist/column-resize.d.ts +46 -0
  19. package/dist/column-resize.js +205 -0
  20. package/dist/columns.d.ts +0 -3
  21. package/dist/columns.js +0 -57
  22. package/dist/core.d.ts +19 -4
  23. package/dist/core.js +460 -119
  24. package/dist/filtering/excel-filters.js +28 -0
  25. package/dist/group-display.d.ts +1 -1
  26. package/dist/index.d.ts +2 -1
  27. package/dist/index.js +6 -0
  28. package/dist/menus.js +1 -1
  29. package/dist/row-resize.d.ts +11 -0
  30. package/dist/row-resize.js +7 -1
  31. package/dist/selection.js +9 -0
  32. package/dist/spreadsheet.d.ts +1 -1
  33. package/dist/spreadsheet.js +1 -1
  34. package/package.json +1 -1
  35. package/src/GridMenus.svelte +17 -12
  36. package/src/SvGrid.controller.svelte.ts +155 -74
  37. package/src/SvGrid.css +1 -1
  38. package/src/SvGrid.svelte +110 -56
  39. package/src/SvGrid.types.ts +41 -1
  40. package/src/column-groups.ts +1 -1
  41. package/src/column-resize.test.ts +381 -0
  42. package/src/column-resize.ts +227 -0
  43. package/src/columns.test.ts +0 -103
  44. package/src/columns.ts +0 -58
  45. package/src/core.aggregate.test.ts +134 -0
  46. package/src/core.filter.test.ts +156 -0
  47. package/src/core.grouping.test.ts +146 -0
  48. package/src/core.row-shape.test.ts +119 -0
  49. package/src/core.rowmodel-cache.test.ts +121 -0
  50. package/src/core.sort.test.ts +293 -0
  51. package/src/core.ts +516 -119
  52. package/src/filtering/excel-filters.ts +30 -0
  53. package/src/filtering/normalize-fast-path.test.ts +104 -0
  54. package/src/group-display.ts +1 -1
  55. package/src/index.ts +12 -1
  56. package/src/menus.ts +1 -1
  57. package/src/resize-props.test.ts +361 -0
  58. package/src/row-resize.test.ts +31 -0
  59. package/src/row-resize.ts +21 -3
  60. package/src/selection.ts +9 -0
  61. package/src/spreadsheet.ts +1 -1
  62. package/dist/cdn/GridMenus-C3bJd7w8.js +0 -489
@@ -15,6 +15,8 @@
15
15
  } from "./index";
16
16
  import "./sv-grid-scrollbar";
17
17
  import "./SvGrid.css";
18
+ import type { RowResizeOptions } from "./row-resize";
19
+ import type { ColumnResizeOptions } from "./column-resize";
18
20
  import {
19
21
  RenderSnippetConfig,
20
22
  RenderComponentConfig,
@@ -167,7 +169,9 @@
167
169
  const hideTooltip = $derived(ctrl.hideTooltip);
168
170
  const findHits = $derived(ctrl.findHits);
169
171
  const headerHeight = $derived(ctrl.headerHeight);
170
- const resizingColumnId = $derived(ctrl.resizingColumnId);
172
+ // Column resizing has always been unconditional; `columnResize={false}` is
173
+ // the opt-out, so the default has to stay on when the prop is absent.
174
+ const columnResizeEnabled = $derived(opt.columnResize === true);
171
175
  const selectionColumnWidth = $derived(ctrl.selectionColumnWidth);
172
176
  const rowNumberColumnWidth = $derived(ctrl.rowNumberColumnWidth);
173
177
  const showRowNumbersEffective = $derived(ctrl.showRowNumbersEffective);
@@ -264,6 +268,23 @@
264
268
  // Row-grouping display modes: group state lives in synthetic columns instead
265
269
  // of a full-width banner row.
266
270
  const groupColumnMode = $derived(ctrl.groupColumnMode);
271
+ /**
272
+ * True when a cell's content needs none of the wrapper snippets.
273
+ *
274
+ * A cell body normally renders through `cellBodyWithFormat` ->
275
+ * `cellBodyFormatted` -> `cellBody`, and with no grouping, no tree data and
276
+ * no conditional formats the first two are pure pass-throughs: three snippet
277
+ * renders and two `{#if}` blocks per cell to reach content that one render
278
+ * produces. Measured by ablation, that chain is 4.2 ms of a 17.4 ms mount at
279
+ * 28 rows x 9 columns - about a quarter of the whole mount, and the largest
280
+ * single per-cell cost.
281
+ *
282
+ * Computed once per render rather than per cell, so the fast path costs one
283
+ * boolean read at each of the ~250 cells instead of two snippet frames.
284
+ */
285
+ const plainCellBody = $derived(
286
+ !groupColumnMode && !treeData && !hasConditionalFormats,
287
+ );
267
288
  const autoGroupCell = $derived(ctrl.autoGroupCell);
268
289
  const isAutoGroupColumn = (id: string): boolean =>
269
290
  id === "__autoGroup" || id.startsWith("__group_");
@@ -280,7 +301,60 @@
280
301
  // The fixed row height, matching what the virtualized path takes from the virtualizer.
281
302
  // The non-virtualized (`virtualization={false}`) body must apply this too, else its rows
282
303
  // fall back to content height and look shorter than a virtualized grid's.
304
+ /**
305
+ * `rowResize` and `columnResize` are both opt-in and both default to OFF, so
306
+ * neither module is imported statically - a grid that leaves them alone must
307
+ * not carry their bytes. This wraps an action so its module is fetched on
308
+ * first enable and never otherwise. The handles appear a microtask after the
309
+ * first paint, which is invisible for a drag affordance.
310
+ *
311
+ * One helper for both: two hand-rolled copies of this bookkeeping would cost
312
+ * more base bundle than the deferral saves.
313
+ */
314
+ type LazyHandle<O> = { update(o: O): void; destroy(): void };
315
+ function lazyAction<O extends { disabled?: boolean }>(
316
+ load: () => Promise<(node: HTMLElement, opts: O) => LazyHandle<O>>,
317
+ ) {
318
+ return (node: HTMLElement, opts: O): LazyHandle<O> => {
319
+ let handle: LazyHandle<O> | null = null;
320
+ let current = opts;
321
+ let destroyed = false;
322
+ const ensure = () => {
323
+ if (handle || destroyed || current.disabled) return;
324
+ void load().then((make) => {
325
+ // The prop can be switched back off, or the grid unmounted, while
326
+ // the chunk is still in flight.
327
+ if (destroyed || current.disabled || handle) return;
328
+ handle = make(node, current);
329
+ });
330
+ };
331
+ ensure();
332
+ return {
333
+ update(next: O) {
334
+ current = next;
335
+ if (handle) handle.update(next);
336
+ else ensure();
337
+ },
338
+ destroy() {
339
+ destroyed = true;
340
+ handle?.destroy();
341
+ },
342
+ };
343
+ };
344
+ }
345
+
346
+ const lazyRowResize = lazyAction<RowResizeOptions>(() =>
347
+ import("./row-resize").then((m) => m.rowResize),
348
+ );
349
+ const lazyColumnResize = lazyAction<ColumnResizeOptions>(() =>
350
+ import("./column-resize").then((m) => m.columnResize),
351
+ );
352
+
283
353
  const rowSizePx = (i: number): number => {
354
+ // A height the user dragged wins over the declared one, so the row stays
355
+ // where they put it across re-renders and virtualization recycling.
356
+ const dragged = ctrl.rowResizeHeightPx(i);
357
+ if (dragged != null) return dragged;
284
358
  const rh = opt.rowHeight;
285
359
  return typeof rh === "function" ? rh(i) : (rh ?? 30);
286
360
  };
@@ -358,26 +432,10 @@
358
432
  return "sv-grid-cell-flash";
359
433
  }
360
434
 
361
- // Keyboard-accessible column resize (#79): focus a resize handle and use the
362
- // arrow keys (Shift = fine 1px step). Complements the pointer-drag resize.
363
- function resizeColumnByKeyboard(e: KeyboardEvent, columnId: string) {
364
- let delta = 0;
365
- const step = e.shiftKey ? 1 : 10;
366
- if (e.key === "ArrowLeft") delta = -step;
367
- else if (e.key === "ArrowRight") delta = step;
368
- else return;
369
- e.preventDefault();
370
- const current = ctrl.getColumnWidth(columnId);
371
- ctrl.columnWidths = {
372
- ...ctrl.columnWidths,
373
- [columnId]: Math.max(40, current + delta),
374
- };
375
- }
376
-
377
435
  // ---- Full-row editing -------------------------------------------------
378
436
  const fullRowEdit = $derived(ctrl.fullRowEdit);
379
437
  // Commit the whole row when the user clicks away from its editors (Excel /
380
- // AG-Grid feel). Clicking within any full-row editor keeps editing.
438
+ // spreadsheet feel). Clicking within any full-row editor keeps editing.
381
439
  // Stays here rather than in SvGridCellEditor: that component is instantiated
382
440
  // once PER editable cell during a full-row edit, so a document listener there
383
441
  // would be attached N times and commit N times.
@@ -429,7 +487,6 @@
429
487
  const setActiveCell = $derived(ctrl.setActiveCell);
430
488
  const scrollActiveCellIntoView = $derived(ctrl.scrollActiveCellIntoView);
431
489
  const getColumnWidth = $derived(ctrl.getColumnWidth);
432
- const startColumnResize = $derived(ctrl.startColumnResize);
433
490
  const getCellRangeEdges = $derived(ctrl.getCellRangeEdges);
434
491
  const fillHandleCell = $derived(ctrl.fillHandleCell);
435
492
  const isInFillPreview = $derived(ctrl.isInFillPreview);
@@ -1294,6 +1351,26 @@
1294
1351
  class="sv-grid-root"
1295
1352
  class:sv-grid-root-fill={opt.containerHeight === "100%"}
1296
1353
  style={chartDockReserveStyle}
1354
+ use:lazyRowResize={{
1355
+ disabled: !ctrl.rowResizeOn,
1356
+ // No gutter column on most grids, so fall back to the first body cell -
1357
+ // otherwise `rowResize` would be a prop that silently does nothing.
1358
+ anchor: "row",
1359
+ onResize: (index, height) => ctrl.setRowResizeHeight(index, height),
1360
+ }}
1361
+ use:lazyColumnResize={{
1362
+ disabled: !columnResizeEnabled,
1363
+ getWidth: (columnId) => ctrl.getColumnWidth(columnId),
1364
+ onResize: (columnId, width) => {
1365
+ ctrl.columnWidths = { ...ctrl.columnWidths, [columnId]: width };
1366
+ },
1367
+ label: (columnId) => {
1368
+ const col = ctrl.findColumnById(columnId);
1369
+ return col ? toolPanelHeaderLabel(col) : columnId;
1370
+ },
1371
+ canResize: (columnId) => ctrl.columnResizable(columnId),
1372
+ onAutosize: (columnId) => ctrl.autosizeColumn(columnId),
1373
+ }}
1297
1374
  >
1298
1375
  {#if showGlobalFilterEffective}
1299
1376
  <label class="sv-grid-global-filter">
@@ -1747,32 +1824,6 @@
1747
1824
  }}
1748
1825
  />
1749
1826
  {/if}
1750
- <div
1751
- class="sv-grid-resize-handle"
1752
- class:is-resizing={resizingColumnId ===
1753
- header.column.id}
1754
- role="separator"
1755
- aria-orientation="vertical"
1756
- aria-label={`Resize ${toolPanelHeaderLabel(header.column)}`}
1757
- tabindex="0"
1758
- {...(() => {
1759
- // A focusable separator is a widget, so ARIA requires
1760
- // aria-valuenow. The value it exposes is the column
1761
- // width the arrow keys change; 40 is the floor
1762
- // enforced in resizeColumnByKeyboard.
1763
- const w = Math.round(ctrl.getColumnWidth(header.column.id));
1764
- return {
1765
- "aria-valuenow": w,
1766
- "aria-valuemin": 40,
1767
- "aria-valuetext": `${w} pixels`,
1768
- };
1769
- })()}
1770
- onpointerdown={(event) =>
1771
- startColumnResize(event, header.column.id)}
1772
- onkeydown={(event) =>
1773
- resizeColumnByKeyboard(event, header.column.id)}
1774
- ondblclick={(event) => event.stopPropagation()}
1775
- ></div>
1776
1827
  {/if}
1777
1828
  </th>
1778
1829
  {/if}
@@ -2177,12 +2228,11 @@
2177
2228
  rendered.column.columnDef.cellFlash,
2178
2229
  ),
2179
2230
  }}
2180
- {...getGridCellA11yProps({
2181
- id: getGridCellDomId(ctrl.gridDomId, rowIndex, colIndex),
2182
- rowIndex: rowIndex + 1,
2183
- colIndex: colIndex + 1,
2184
- selected: isRowSelected(row.id),
2185
- })}
2231
+ role="gridcell"
2232
+ id={getGridCellDomId(ctrl.gridDomId, rowIndex, colIndex)}
2233
+ aria-colindex={colIndex + 1}
2234
+ aria-rowindex={rowIndex + 1}
2235
+ aria-selected={isRowSelected(row.id)}
2186
2236
  >
2187
2237
  {#if inRowEdit || isEditing}
2188
2238
  <!-- The editing cell stays empty until the lazy
@@ -2194,11 +2244,15 @@
2194
2244
  <CellEditor {ctrl} column={rendered.column} {row} fullRow={inRowEdit} />
2195
2245
  {/if}
2196
2246
  {:else}
2197
- {@render cellBodyWithFormat(
2198
- row,
2199
- rendered.column,
2200
- cellValue,
2201
- )}
2247
+ {#if plainCellBody}
2248
+ {@render cellBody(row, rendered.column, cellValue)}
2249
+ {:else}
2250
+ {@render cellBodyWithFormat(
2251
+ row,
2252
+ rendered.column,
2253
+ cellValue,
2254
+ )}
2255
+ {/if}
2202
2256
  {/if}
2203
2257
  {#if !isEditing && fillHandleCell && fillHandleCell.rowIndex === rowIndex && fillHandleCell.colIndex === colIndex}
2204
2258
  <!-- Excel-style fill handle: drag down/right to
@@ -1296,6 +1296,28 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
1296
1296
  * supplying per-row heights).
1297
1297
  */
1298
1298
  autoRowHeight?: boolean;
1299
+ /**
1300
+ * Let the user drag a row's bottom edge to change its height. **Off by
1301
+ * default**, because a resizable row needs somewhere to grab and most grids
1302
+ * do not want a drag target on every row.
1303
+ *
1304
+ * The grid remembers the heights itself, so this works as a bare boolean -
1305
+ * no `rowHeight` function required. Drag, or focus the grip and use Up/Down
1306
+ * (Shift for 1px steps).
1307
+ *
1308
+ * Turning this on also turns on the **row header column** ({@link
1309
+ * showRowNumbers}), because that is where the grip lives and where a
1310
+ * spreadsheet puts it - a drag target on the edge of a data cell works but
1311
+ * reads as an accident. An explicit `showRowNumbers={false}` still wins; the
1312
+ * grip then falls back to the row's first cell, and a column of your own
1313
+ * carrying `cellClass: 'sv-row-gutter'` is used ahead of either.
1314
+ *
1315
+ * Ignored under `autoRowHeight`, where the content decides the height and a
1316
+ * manual one would immediately be overwritten. For full control of the
1317
+ * heights - persisting them, sharing them between grids - pass a
1318
+ * function-valued {@link rowHeight} and use the `rowResize` action directly.
1319
+ */
1320
+ rowResize?: boolean;
1299
1321
  /**
1300
1322
  * Height (px) of a single column-header level row. With multi-level
1301
1323
  * (grouped) headers the total header height is `levels * headerHeight`,
@@ -1354,6 +1376,24 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
1354
1376
  * win once they happen.
1355
1377
  */
1356
1378
  fitColumns?: boolean;
1379
+ /**
1380
+ * Let the user drag a column's edge to change its width. **Off by default**,
1381
+ * so `width` means the width you asked for until you say otherwise.
1382
+ *
1383
+ * Turn it on for grids the reader is meant to arrange - a spreadsheet, a
1384
+ * dense report, anything with columns whose content varies in length. Leave
1385
+ * it off for layouts you control, and for columns that must keep their size:
1386
+ * a row-number gutter, a checkbox column, an icon column.
1387
+ *
1388
+ * It is grid-wide rather than per-column: there is no `resizable: false` on a
1389
+ * single column definition. `api.autosizeColumn()`, `fitColumns` and
1390
+ * programmatic width changes work either way - this only governs the drag
1391
+ * handle.
1392
+ *
1393
+ * The handles come from the `columnResize` action, loaded on demand: a grid
1394
+ * that leaves this off never fetches that code.
1395
+ */
1396
+ columnResize?: boolean;
1357
1397
  /**
1358
1398
  * Make the grid usable on narrow screens. When the grid's own width drops
1359
1399
  * below the breakpoint (default `640`px), pinned columns are un-pinned so the
@@ -1463,7 +1503,7 @@ export type Props<TFeatures extends TableFeatures = TableFeatures, TData extends
1463
1503
  charting?: boolean | ChartingConfig<TData>;
1464
1504
  /**
1465
1505
  * Render the header column menu (⋮) as a tabbed popover - **General**,
1466
- * **Filter**, and **Columns** tabs (the AG-Grid layout). Defaults to `false`,
1506
+ * **Filter**, and **Columns** tabs (the tabbed layout). Defaults to `false`,
1467
1507
  * which keeps the flat menu (actions list + "Choose columns" submenu).
1468
1508
  */
1469
1509
  columnMenuTabs?: boolean;