@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.
- package/CHANGELOG.md +62 -0
- package/README.md +22 -0
- package/dist/GridMenus.svelte +17 -12
- package/dist/SvGrid.controller.svelte.d.ts +13 -8
- package/dist/SvGrid.controller.svelte.js +150 -72
- package/dist/SvGrid.css +1 -1
- package/dist/SvGrid.svelte +110 -56
- package/dist/SvGrid.types.d.ts +41 -1
- package/dist/cdn/{GridMenus-BfTAKn84.js → GridMenus-BuoBPqxx.js} +137 -132
- package/dist/cdn/GridMenus-n4llxoOI.js +494 -0
- package/dist/cdn/column-resize-DsfNXMom.js +102 -0
- package/dist/cdn/row-resize-BRcimkUT.js +95 -0
- package/dist/cdn/{src-BYq-qyrp.js → src-C9Hihx1W.js} +3456 -3459
- package/dist/cdn/{src-DBel9wRZ.js → src-D1lXwq1l.js} +8283 -8286
- package/dist/cdn/svgrid.js +10 -8
- package/dist/cdn/svgrid.svelte-external.js +10 -8
- package/dist/column-groups.js +1 -1
- package/dist/column-resize.d.ts +46 -0
- package/dist/column-resize.js +205 -0
- package/dist/columns.d.ts +0 -3
- package/dist/columns.js +0 -57
- package/dist/core.d.ts +19 -4
- package/dist/core.js +460 -119
- package/dist/filtering/excel-filters.js +28 -0
- package/dist/group-display.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -0
- package/dist/menus.js +1 -1
- package/dist/row-resize.d.ts +11 -0
- package/dist/row-resize.js +7 -1
- package/dist/selection.js +9 -0
- package/dist/spreadsheet.d.ts +1 -1
- package/dist/spreadsheet.js +1 -1
- package/package.json +1 -1
- package/src/GridMenus.svelte +17 -12
- package/src/SvGrid.controller.svelte.ts +155 -74
- package/src/SvGrid.css +1 -1
- package/src/SvGrid.svelte +110 -56
- package/src/SvGrid.types.ts +41 -1
- package/src/column-groups.ts +1 -1
- package/src/column-resize.test.ts +381 -0
- package/src/column-resize.ts +227 -0
- package/src/columns.test.ts +0 -103
- package/src/columns.ts +0 -58
- package/src/core.aggregate.test.ts +134 -0
- package/src/core.filter.test.ts +156 -0
- package/src/core.grouping.test.ts +146 -0
- package/src/core.row-shape.test.ts +119 -0
- package/src/core.rowmodel-cache.test.ts +121 -0
- package/src/core.sort.test.ts +293 -0
- package/src/core.ts +516 -119
- package/src/filtering/excel-filters.ts +30 -0
- package/src/filtering/normalize-fast-path.test.ts +104 -0
- package/src/group-display.ts +1 -1
- package/src/index.ts +12 -1
- package/src/menus.ts +1 -1
- package/src/resize-props.test.ts +361 -0
- package/src/row-resize.test.ts +31 -0
- package/src/row-resize.ts +21 -3
- package/src/selection.ts +9 -0
- package/src/spreadsheet.ts +1 -1
- package/dist/cdn/GridMenus-C3bJd7w8.js +0 -489
package/dist/SvGrid.svelte
CHANGED
|
@@ -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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
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
|
-
{
|
|
2198
|
-
row,
|
|
2199
|
-
|
|
2200
|
-
|
|
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
|
package/dist/SvGrid.types.d.ts
CHANGED
|
@@ -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
|
|
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;
|