@svgrid/grid 1.1.0 → 1.1.1
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/GridMenus.svelte +205 -127
- package/dist/SvGrid.controller.svelte.d.ts +44 -1
- package/dist/SvGrid.controller.svelte.js +223 -38
- package/dist/SvGrid.css +2012 -1709
- package/dist/SvGrid.helpers.test.d.ts +1 -0
- package/dist/SvGrid.helpers.test.js +298 -0
- package/dist/SvGrid.svelte +2346 -2043
- package/dist/SvGrid.types.d.ts +91 -4
- package/dist/aligned-grids.d.ts +6 -0
- package/dist/aligned-grids.js +84 -0
- package/dist/aligned-grids.test.d.ts +1 -0
- package/dist/aligned-grids.test.js +75 -0
- package/dist/build-api.coverage.test.d.ts +20 -0
- package/dist/build-api.coverage.test.js +505 -0
- package/dist/build-api.js +59 -29
- package/dist/cell-render.test.d.ts +1 -0
- package/dist/cell-render.test.js +338 -0
- package/dist/chart-export.test.d.ts +1 -0
- package/dist/chart-export.test.js +302 -0
- package/dist/chart.coverage.test.d.ts +1 -0
- package/dist/chart.coverage.test.js +748 -0
- package/dist/clipboard.js +88 -24
- package/dist/clipboard.test.d.ts +1 -0
- package/dist/clipboard.test.js +700 -0
- package/dist/collaboration.coverage.test.d.ts +1 -0
- package/dist/collaboration.coverage.test.js +200 -0
- package/dist/column-groups.d.ts +19 -0
- package/dist/column-groups.js +62 -0
- package/dist/column-groups.test.d.ts +1 -0
- package/dist/column-groups.test.js +56 -0
- package/dist/column-types.d.ts +10 -0
- package/dist/column-types.js +63 -0
- package/dist/column-types.test.d.ts +1 -0
- package/dist/column-types.test.js +62 -0
- package/dist/columns.test.d.ts +1 -0
- package/dist/columns.test.js +625 -0
- package/dist/core.d.ts +85 -0
- package/dist/editing.d.ts +7 -0
- package/dist/editing.js +191 -5
- package/dist/editing.test.d.ts +1 -0
- package/dist/editing.test.js +732 -0
- package/dist/editors/cell-editors.coverage.test.d.ts +1 -0
- package/dist/editors/cell-editors.coverage.test.js +139 -0
- package/dist/facet-buckets.test.d.ts +1 -0
- package/dist/facet-buckets.test.js +296 -0
- package/dist/filter-operators.test.d.ts +1 -0
- package/dist/filter-operators.test.js +135 -0
- package/dist/hyperformula-adapter.test.d.ts +1 -0
- package/dist/hyperformula-adapter.test.js +205 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -1
- package/dist/keyboard-handlers.coverage.test.d.ts +1 -0
- package/dist/keyboard-handlers.coverage.test.js +495 -0
- package/dist/keyboard-handlers.js +7 -2
- package/dist/menus.js +1 -0
- package/dist/menus.test.d.ts +1 -0
- package/dist/menus.test.js +560 -0
- package/dist/named-views.coverage.test.d.ts +1 -0
- package/dist/named-views.coverage.test.js +180 -0
- package/dist/row-drag.d.ts +49 -0
- package/dist/row-drag.js +221 -0
- package/dist/row-drag.test.d.ts +1 -0
- package/dist/row-drag.test.js +142 -0
- package/dist/row-resize.test.d.ts +1 -0
- package/dist/row-resize.test.js +329 -0
- package/dist/scroll-sync.js +3 -0
- package/dist/scroll-sync.test.d.ts +1 -0
- package/dist/scroll-sync.test.js +290 -0
- package/dist/selection.d.ts +7 -1
- package/dist/selection.js +76 -36
- package/dist/selection.multi-range.test.d.ts +1 -0
- package/dist/selection.multi-range.test.js +55 -0
- package/dist/selection.test.d.ts +1 -0
- package/dist/selection.test.js +647 -0
- package/dist/server-data-source.coverage.test.d.ts +1 -0
- package/dist/server-data-source.coverage.test.js +154 -0
- package/dist/spreadsheet.d.ts +30 -0
- package/dist/spreadsheet.js +48 -0
- package/dist/spreadsheet.test.d.ts +1 -0
- package/dist/spreadsheet.test.js +446 -0
- package/dist/sv-grid-scrollbar.js +13 -1
- package/dist/svgrid-wrapper.types.d.ts +19 -0
- package/dist/svgrid.behavior.test.js +20 -0
- package/dist/svgrid.interaction.test.js +31 -0
- package/dist/svgrid.new-features.wrapper.test.js +34 -2
- package/dist/test-setup.js +9 -3
- package/dist/virtualization/scroll-scaling.d.ts +17 -0
- package/dist/virtualization/scroll-scaling.js +35 -0
- package/dist/virtualization/scroll-scaling.test.js +42 -1
- package/package.json +2 -1
- package/src/GridMenus.svelte +205 -127
- package/src/SvGrid.controller.svelte.ts +2352 -2195
- package/src/SvGrid.css +2012 -1747
- package/src/SvGrid.svelte +2346 -2047
- package/src/SvGrid.types.ts +537 -456
- package/src/aligned-grids.test.ts +80 -0
- package/src/aligned-grids.ts +87 -0
- package/src/build-api.ts +683 -663
- package/src/clipboard.test.ts +49 -0
- package/src/clipboard.ts +51 -23
- package/src/column-groups.test.ts +59 -0
- package/src/column-groups.ts +80 -0
- package/src/column-types.test.ts +68 -0
- package/src/column-types.ts +82 -0
- package/src/core.ts +78 -0
- package/src/editing.ts +669 -513
- package/src/index.ts +12 -0
- package/src/menus.ts +1 -0
- package/src/row-drag.test.ts +168 -0
- package/src/row-drag.ts +255 -0
- package/src/scroll-sync.ts +2 -0
- package/src/selection.multi-range.test.ts +61 -0
- package/src/selection.ts +71 -37
- package/src/spreadsheet.test.ts +489 -445
- package/src/spreadsheet.ts +304 -246
- package/src/svgrid-wrapper.types.ts +19 -0
- package/src/svgrid.new-features.wrapper.test.ts +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyExcelFilter, normalizeForFilter, createColumnVirtualizer, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createSvelteVirtualizer, createSortedRowModel, createSvGrid, getGridCellDomId, sortFns, } from "./index";
|
|
2
|
-
import { createRowScrollScaling } from "./virtualization/scroll-scaling";
|
|
2
|
+
import { createRowScrollScaling, resolveMaxDomHeight, } from "./virtualization/scroll-scaling";
|
|
3
3
|
import "./sv-grid-scrollbar";
|
|
4
4
|
import { computeColumnStat, formatsNeedingStats, } from "./conditional-formatting";
|
|
5
5
|
import { rawToNumber, } from "./SvGrid.helpers";
|
|
@@ -12,6 +12,10 @@ import { createCellRender, } from "./cell-render";
|
|
|
12
12
|
import { createEditing, } from "./editing";
|
|
13
13
|
import { createSelection, } from "./selection";
|
|
14
14
|
import { createColumns, } from "./columns";
|
|
15
|
+
import { createRowDrag, } from "./row-drag";
|
|
16
|
+
import { createAlignedGrids, } from "./aligned-grids";
|
|
17
|
+
import { resolveColumnTypes, } from "./column-types";
|
|
18
|
+
import { computeColumnGroupMeta, hiddenLeavesForCollapse, } from "./column-groups";
|
|
15
19
|
import { createGridApi, } from "./build-api";
|
|
16
20
|
import { createClipboard, } from "./clipboard";
|
|
17
21
|
import { filterOperatorOptions, fallbackOperatorOption, TEXT_OPERATORS, NUMBER_OPERATORS, DATE_OPERATORS, CHECKBOX_OPERATORS, operatorOption, operatorsForColumn, defaultOperatorFor, operatorLabelFor, } from "./filter-operators";
|
|
@@ -24,35 +28,81 @@ import { getColumnBaseValue, isGroupRow, toolPanelHeaderLabel, formatSummaryNume
|
|
|
24
28
|
*/
|
|
25
29
|
const MAX_DOM_SCROLL_HEIGHT_FALLBACK = 8000000;
|
|
26
30
|
/**
|
|
27
|
-
* The browser's actual maximum element height in CSS px. Browsers
|
|
28
|
-
* tall a single element may be
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
31
|
+
* The browser's actual maximum *scrollable* element height in CSS px. Browsers
|
|
32
|
+
* clamp how tall a single element may be, and the cap is lower on mobile /
|
|
33
|
+
* high-DPR devices (the physical limit is in device px, so a 3x-DPR phone has
|
|
34
|
+
* ~1/3 the CSS-px cap of a 1x desktop). Past that cap a scroll container
|
|
35
|
+
* silently clamps its `scrollHeight` and the tail rows of a huge virtualized
|
|
36
|
+
* grid become unreachable.
|
|
37
|
+
*
|
|
38
|
+
* We measure two signals from one offscreen probe and keep the smaller (see
|
|
39
|
+
* `resolveMaxDomHeight`): the probe's clamped `offsetHeight`, AND the
|
|
40
|
+
* `scrollHeight` a real `overflow:auto` container exposes for it. The second
|
|
41
|
+
* matters because mobile WebKit/Blink can report a generous `offsetHeight` yet
|
|
42
|
+
* expose a smaller scrollable range - trusting the layout height alone is what
|
|
43
|
+
* stranded the last rows on phones. Using a real scroll container also folds in
|
|
44
|
+
* DPR clamping for free. Cached for the page lifetime; constant per browser.
|
|
32
45
|
*/
|
|
33
46
|
let detectedMaxDomHeight = null;
|
|
47
|
+
/**
|
|
48
|
+
* Seed the `hiddenColumns` map from any column def marked `visible: false`.
|
|
49
|
+
* Walks groups so a hidden group hides all of its leaf columns. Keyed by the
|
|
50
|
+
* same id `setColumnVisible` uses (`id ?? field`), so user toggles afterward
|
|
51
|
+
* stay consistent. Run once at mount; prop changes don't re-apply it.
|
|
52
|
+
*/
|
|
53
|
+
function initialHiddenColumns(defs) {
|
|
54
|
+
const hidden = {};
|
|
55
|
+
const walk = (cols, inheritedHidden) => {
|
|
56
|
+
for (const def of cols) {
|
|
57
|
+
const hide = inheritedHidden || def.visible === false;
|
|
58
|
+
if (def.columns?.length) {
|
|
59
|
+
walk(def.columns, hide);
|
|
60
|
+
}
|
|
61
|
+
else if (hide) {
|
|
62
|
+
const id = def.id ?? def.field;
|
|
63
|
+
if (id)
|
|
64
|
+
hidden[id] = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
walk(defs, false);
|
|
69
|
+
return hidden;
|
|
70
|
+
}
|
|
34
71
|
function getMaxDomScrollHeight() {
|
|
72
|
+
// Escape hatch: a page may pin the cap via `window.__svgridMaxDomHeight`.
|
|
73
|
+
// Checked before the cache so it always wins. Two uses: reproducing a
|
|
74
|
+
// phone's lower element-height limit on desktop (and our e2e coverage of
|
|
75
|
+
// the huge-list path), and overriding detection on a device where it reads
|
|
76
|
+
// wrong. A non-positive / non-finite value is ignored.
|
|
77
|
+
if (typeof window !== "undefined") {
|
|
78
|
+
const forced = window
|
|
79
|
+
.__svgridMaxDomHeight;
|
|
80
|
+
if (typeof forced === "number" && Number.isFinite(forced) && forced > 0) {
|
|
81
|
+
return forced;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
35
84
|
if (detectedMaxDomHeight != null)
|
|
36
85
|
return detectedMaxDomHeight;
|
|
37
86
|
if (typeof document === "undefined" || !document.body) {
|
|
38
87
|
return MAX_DOM_SCROLL_HEIGHT_FALLBACK;
|
|
39
88
|
}
|
|
40
89
|
try {
|
|
90
|
+
// The wrapper is itself an `overflow:auto` scroll container (kept tiny and
|
|
91
|
+
// offscreen so it never affects page layout or scroll), so we can read the
|
|
92
|
+
// height it actually exposes as scrollable - not just the probe's layout
|
|
93
|
+
// height. On high-DPR mobile the two diverge and the scrollable one is the
|
|
94
|
+
// limit that matters.
|
|
41
95
|
const wrap = document.createElement("div");
|
|
42
96
|
wrap.style.cssText =
|
|
43
|
-
"position:fixed;top:0;left:-9999px;width:1px;height:
|
|
97
|
+
"position:fixed;top:0;left:-9999px;width:1px;height:100px;overflow:auto;visibility:hidden;pointer-events:none;";
|
|
44
98
|
const probe = document.createElement("div");
|
|
45
99
|
probe.style.cssText = "width:1px;height:1000000000px;";
|
|
46
100
|
wrap.appendChild(probe);
|
|
47
101
|
document.body.appendChild(wrap);
|
|
48
|
-
const
|
|
102
|
+
const layoutCap = probe.offsetHeight;
|
|
103
|
+
const scrollCap = wrap.scrollHeight;
|
|
49
104
|
document.body.removeChild(wrap);
|
|
50
|
-
|
|
51
|
-
// If the browser did not clamp (returned ~1e9) or returned junk, fall back.
|
|
52
|
-
detectedMaxDomHeight =
|
|
53
|
-
measured > 100000 && measured < 900000000
|
|
54
|
-
? measured
|
|
55
|
-
: MAX_DOM_SCROLL_HEIGHT_FALLBACK;
|
|
105
|
+
detectedMaxDomHeight = resolveMaxDomHeight(layoutCap, scrollCap, MAX_DOM_SCROLL_HEIGHT_FALLBACK);
|
|
56
106
|
}
|
|
57
107
|
catch {
|
|
58
108
|
detectedMaxDomHeight = MAX_DOM_SCROLL_HEIGHT_FALLBACK;
|
|
@@ -91,6 +141,10 @@ export function createSvGridController(props) {
|
|
|
91
141
|
let pendingScrollLeft = 0;
|
|
92
142
|
let scrollSyncRaf = null;
|
|
93
143
|
let selectionRange = $state({ anchor: null, focus: null });
|
|
144
|
+
// Extra committed ranges for multi-range (Ctrl+drag) selection. The
|
|
145
|
+
// `selectionRange` above is always the ACTIVE range being manipulated; these
|
|
146
|
+
// are the finished ones. Full selection = these + the active range.
|
|
147
|
+
let selectionRanges = $state.raw([]);
|
|
94
148
|
let isDraggingSelection = $state(false);
|
|
95
149
|
/** Excel-style fill handle drag state. While non-null we paint a "fill
|
|
96
150
|
* preview" overlay on cells between the source range and the pointer
|
|
@@ -98,6 +152,9 @@ export function createSvGridController(props) {
|
|
|
98
152
|
let fillDrag = $state(null);
|
|
99
153
|
let activeAtPointerDown = null;
|
|
100
154
|
let editingCell = $state(null);
|
|
155
|
+
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
156
|
+
// draft (keyed by column id). Null when not in full-row mode.
|
|
157
|
+
let fullRowEdit = $state(null);
|
|
101
158
|
let editedCellValues = $state({});
|
|
102
159
|
const UNDO_LIMIT = 200;
|
|
103
160
|
let history = $state([]);
|
|
@@ -160,6 +217,7 @@ export function createSvGridController(props) {
|
|
|
160
217
|
const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
|
|
161
218
|
const showRowNumbersEffective = $derived(props.showRowNumbers ?? false);
|
|
162
219
|
let columnMenuFor = $state(null);
|
|
220
|
+
let columnMenuTab = $state("general");
|
|
163
221
|
let columnMenuPos = $state({ x: 0, y: 0 });
|
|
164
222
|
let columnMenuSearch = $state("");
|
|
165
223
|
let filterMenuFor = $state(null);
|
|
@@ -236,9 +294,41 @@ export function createSvGridController(props) {
|
|
|
236
294
|
// mutates these so add/remove operations don't need a callback round-trip.
|
|
237
295
|
// svelte-ignore state_referenced_locally
|
|
238
296
|
let internalData = $state.raw(props.data);
|
|
297
|
+
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
298
|
+
// format defaults once, up front, so every downstream reader sees a normal
|
|
299
|
+
// column. Explicit fields on the ColumnDef always win.
|
|
300
|
+
// svelte-ignore state_referenced_locally
|
|
301
|
+
const resolveCols = (cols) => resolveColumnTypes(cols, props.data?.[0], props.inferColumnTypes === true);
|
|
239
302
|
// svelte-ignore state_referenced_locally
|
|
240
|
-
let internalColumns = $state.raw(props.columns);
|
|
241
|
-
|
|
303
|
+
let internalColumns = $state.raw(resolveCols(props.columns));
|
|
304
|
+
// svelte-ignore state_referenced_locally
|
|
305
|
+
let hiddenColumns = $state(initialHiddenColumns(props.columns));
|
|
306
|
+
// Collapsible column groups (columnGroupShow). Meta is derived from the tree;
|
|
307
|
+
// `collapsedColumnGroups` is the live set of collapsed group ids, seeded once
|
|
308
|
+
// from each collapsible group's `openByDefault` (default: collapsed).
|
|
309
|
+
const columnGroupMeta = $derived(computeColumnGroupMeta(props.columns));
|
|
310
|
+
// svelte-ignore state_referenced_locally
|
|
311
|
+
let collapsedColumnGroups = $state((() => {
|
|
312
|
+
const meta = computeColumnGroupMeta(props.columns);
|
|
313
|
+
const s = new Set();
|
|
314
|
+
for (const id of meta.collapsibleGroupIds)
|
|
315
|
+
if (!meta.defaultOpen.get(id))
|
|
316
|
+
s.add(id);
|
|
317
|
+
return s;
|
|
318
|
+
})());
|
|
319
|
+
// Leaf ids hidden right now because their group is collapsed/expanded.
|
|
320
|
+
const hiddenByGroupCollapse = $derived(hiddenLeavesForCollapse(columnGroupMeta, collapsedColumnGroups));
|
|
321
|
+
function toggleColumnGroup(groupId) {
|
|
322
|
+
const next = new Set(collapsedColumnGroups);
|
|
323
|
+
if (next.has(groupId))
|
|
324
|
+
next.delete(groupId);
|
|
325
|
+
else
|
|
326
|
+
next.add(groupId);
|
|
327
|
+
collapsedColumnGroups = next;
|
|
328
|
+
}
|
|
329
|
+
function isColumnGroupCollapsed(groupId) {
|
|
330
|
+
return collapsedColumnGroups.has(groupId);
|
|
331
|
+
}
|
|
242
332
|
$effect(() => {
|
|
243
333
|
// When the consumer replaces `data` (e.g. a "Reset" button), drop any
|
|
244
334
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
@@ -248,7 +338,7 @@ export function createSvGridController(props) {
|
|
|
248
338
|
editedCellValues = {};
|
|
249
339
|
});
|
|
250
340
|
$effect(() => {
|
|
251
|
-
internalColumns = props.columns;
|
|
341
|
+
internalColumns = resolveCols(props.columns);
|
|
252
342
|
});
|
|
253
343
|
// Captured ONCE at mount: `externalSort` is a structural choice (tree vs
|
|
254
344
|
// flat data) so toggling it after mount is not supported. Reading it here
|
|
@@ -334,7 +424,7 @@ export function createSvGridController(props) {
|
|
|
334
424
|
const allColumns = $derived.by(() => {
|
|
335
425
|
let raw = grid
|
|
336
426
|
.getAllColumns()
|
|
337
|
-
.filter((column) => !hiddenColumns[column.id]);
|
|
427
|
+
.filter((column) => !hiddenColumns[column.id] && !hiddenByGroupCollapse[column.id]);
|
|
338
428
|
// Apply user reorder (if any). Unknown ids in userColumnOrder are
|
|
339
429
|
// skipped; columns not in userColumnOrder keep their original
|
|
340
430
|
// relative order after the user-ordered ones.
|
|
@@ -403,13 +493,17 @@ export function createSvGridController(props) {
|
|
|
403
493
|
function buildId(def, parentId, fallbackIx) {
|
|
404
494
|
return def.id ?? def.field ?? `${parentId ?? 'col'}_d_${fallbackIx}`;
|
|
405
495
|
}
|
|
496
|
+
// Leaves hidden by a collapsed/expanded group are skipped everywhere here,
|
|
497
|
+
// so group colSpan + widthPx exclude them and stay aligned with the leaves
|
|
498
|
+
// the body actually renders.
|
|
499
|
+
const hiddenLeaf = hiddenByGroupCollapse;
|
|
406
500
|
function collectLeaves(defs, parentId, depthHere) {
|
|
407
501
|
defs.forEach((def, ix) => {
|
|
408
502
|
const id = buildId(def, parentId, ix);
|
|
409
503
|
if (def.columns?.length) {
|
|
410
504
|
collectLeaves(def.columns, id, depthHere + 1);
|
|
411
505
|
}
|
|
412
|
-
else {
|
|
506
|
+
else if (!hiddenLeaf[id]) {
|
|
413
507
|
leafEntries.push({ id, widthPx: getColumnWidth(id) });
|
|
414
508
|
}
|
|
415
509
|
});
|
|
@@ -419,6 +513,10 @@ export function createSvGridController(props) {
|
|
|
419
513
|
const nodes = [];
|
|
420
514
|
for (const def of defs) {
|
|
421
515
|
const id = buildId(def, parentId, nodes.length);
|
|
516
|
+
// Skip leaves the collapse state hides, so leaf indices/colSpans match
|
|
517
|
+
// `leafEntries` (and the body's rendered columns) exactly.
|
|
518
|
+
if (!def.columns?.length && hiddenLeaf[id])
|
|
519
|
+
continue;
|
|
422
520
|
const leafStart = cursor.leaf;
|
|
423
521
|
if (def.columns?.length) {
|
|
424
522
|
indexTree(def.columns, id, cursor);
|
|
@@ -464,6 +562,7 @@ export function createSvGridController(props) {
|
|
|
464
562
|
const cells = at.map((n) => {
|
|
465
563
|
const isLeafEarly = !n.def.columns?.length;
|
|
466
564
|
const headerText = typeof n.def.header === 'string' ? n.def.header : '';
|
|
565
|
+
const collapsible = columnGroupMeta.collapsibleGroupIds.has(n.id);
|
|
467
566
|
return {
|
|
468
567
|
key: `${n.id}_d${d}`,
|
|
469
568
|
label: isLeafEarly ? '' : headerText,
|
|
@@ -471,6 +570,9 @@ export function createSvGridController(props) {
|
|
|
471
570
|
widthPx: sumLeafWidths(n.leafStart, n.leafEnd),
|
|
472
571
|
firstLeafIndex: n.leafStart,
|
|
473
572
|
isPlaceholder: isLeafEarly,
|
|
573
|
+
groupId: collapsible ? n.id : undefined,
|
|
574
|
+
collapsible,
|
|
575
|
+
collapsed: collapsible && collapsedColumnGroups.has(n.id),
|
|
474
576
|
};
|
|
475
577
|
});
|
|
476
578
|
rows.push({ id: `gh_${d}`, cells });
|
|
@@ -504,6 +606,12 @@ export function createSvGridController(props) {
|
|
|
504
606
|
let colDragId = $state(null);
|
|
505
607
|
let colDropOnId = $state(null);
|
|
506
608
|
let colDropSide = $state(null);
|
|
609
|
+
// Live drag state for managed row dragging. Only meaningful while a row is
|
|
610
|
+
// being dragged (`props.rowDragManaged`). `rowDropIndex` is the visible row
|
|
611
|
+
// index currently hovered; `rowDropSide` says which edge the drop line paints.
|
|
612
|
+
let rowDragActive = $state(false);
|
|
613
|
+
let rowDropIndex = $state(null);
|
|
614
|
+
let rowDropSide = $state(null);
|
|
507
615
|
// ---- Conditional formatting --------------------------------------------
|
|
508
616
|
// True when the feature is in use; gates the per-cell positioning context
|
|
509
617
|
// (cells are otherwise non-relative for scroll performance).
|
|
@@ -577,23 +685,36 @@ export function createSvGridController(props) {
|
|
|
577
685
|
.some((cell) => normalizeForFilter(String(cell.getValue() ?? ""), props.filterLocale)
|
|
578
686
|
.includes(needle)));
|
|
579
687
|
}
|
|
580
|
-
|
|
581
|
-
|
|
688
|
+
// A single condition is "active" if it has the value(s) it needs.
|
|
689
|
+
const condActive = (op, value, valueTo) => {
|
|
690
|
+
if (op === "isBlank")
|
|
582
691
|
return true;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
692
|
+
if (op === "between")
|
|
693
|
+
return value.trim().length > 0 && (valueTo ?? "").trim().length > 0;
|
|
694
|
+
return value.trim().length > 0;
|
|
695
|
+
};
|
|
696
|
+
const evalCond = (cellValue, columnId, op, value, valueTo) => applyExcelFilter(cellValue, { id: columnId, operator: op, value, valueTo: op === "between" ? valueTo : undefined }, { locale: props.filterLocale });
|
|
697
|
+
// A column filter is active if either of its (up to two) conditions is.
|
|
698
|
+
const menuFilters = Object.entries(filterMenuValues).filter(([_, f]) => {
|
|
699
|
+
const a = condActive(f.operator, f.value, f.valueTo);
|
|
700
|
+
const b = !!f.operator2 && condActive(f.operator2, f.value2 ?? "", f.valueTo2);
|
|
701
|
+
return a || b;
|
|
589
702
|
});
|
|
590
703
|
if (menuFilters.length) {
|
|
591
|
-
rows = rows.filter((row) => menuFilters.every(([columnId,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
704
|
+
rows = rows.filter((row) => menuFilters.every(([columnId, f]) => {
|
|
705
|
+
const cellValue = getRowColumnValue(row, columnId);
|
|
706
|
+
const aActive = condActive(f.operator, f.value, f.valueTo);
|
|
707
|
+
const bActive = !!f.operator2 && condActive(f.operator2, f.value2 ?? "", f.valueTo2);
|
|
708
|
+
const ra = aActive ? evalCond(cellValue, columnId, f.operator, f.value, f.valueTo) : null;
|
|
709
|
+
const rb = bActive
|
|
710
|
+
? evalCond(cellValue, columnId, f.operator2, f.value2 ?? "", f.valueTo2)
|
|
711
|
+
: null;
|
|
712
|
+
if (ra === null)
|
|
713
|
+
return rb ?? true;
|
|
714
|
+
if (rb === null)
|
|
715
|
+
return ra;
|
|
716
|
+
return f.join === "OR" ? ra || rb : ra && rb;
|
|
717
|
+
}));
|
|
597
718
|
}
|
|
598
719
|
const valueFilterEntries = Object.entries(valueFilters);
|
|
599
720
|
if (valueFilterEntries.length) {
|
|
@@ -739,7 +860,10 @@ export function createSvGridController(props) {
|
|
|
739
860
|
};
|
|
740
861
|
});
|
|
741
862
|
// ---- Tool panel (docked columns sidebar) -------------------------------
|
|
742
|
-
|
|
863
|
+
// svelte-ignore state_referenced_locally
|
|
864
|
+
let toolPanelOpen = $state(props.toolPanelDefaultOpen === true);
|
|
865
|
+
// svelte-ignore state_referenced_locally
|
|
866
|
+
let toolPanelTab = $state(props.toolPanelDefaultTab ?? "columns");
|
|
743
867
|
const toolPanelEnabled = $derived(props.toolPanel === true);
|
|
744
868
|
// Every column (including hidden ones) in the user's current order, so the
|
|
745
869
|
// panel can toggle/reorder anything. Group columns are flagged live.
|
|
@@ -1053,6 +1177,7 @@ export function createSvGridController(props) {
|
|
|
1053
1177
|
if (isFirstRender)
|
|
1054
1178
|
return;
|
|
1055
1179
|
selectionRange = { anchor: null, focus: null };
|
|
1180
|
+
selectionRanges = [];
|
|
1056
1181
|
editingCell = null;
|
|
1057
1182
|
if (scrollContainer) {
|
|
1058
1183
|
scrollContainer.scrollTop = 0;
|
|
@@ -1218,7 +1343,16 @@ export function createSvGridController(props) {
|
|
|
1218
1343
|
return null;
|
|
1219
1344
|
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
1220
1345
|
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
1221
|
-
|
|
1346
|
+
// Reserve the custom vertical scrollbar's width when it's visible. It
|
|
1347
|
+
// overlays the right 16px of the viewport (absolute, z-index 40) and
|
|
1348
|
+
// does NOT shrink clientWidth, so without this the last fitted column
|
|
1349
|
+
// slides under it and its right-aligned content (e.g. a number column)
|
|
1350
|
+
// is hidden behind the opaque scrollbar.
|
|
1351
|
+
const scrollbarWidth = hasVerticalOverflow ? 16 : 0;
|
|
1352
|
+
const target = (scrollContainer?.clientWidth ?? 0) -
|
|
1353
|
+
rowNumberWidth -
|
|
1354
|
+
selectionWidth -
|
|
1355
|
+
scrollbarWidth;
|
|
1222
1356
|
if (target <= 0)
|
|
1223
1357
|
return null;
|
|
1224
1358
|
// Split base widths into pinned (user-resized) and scalable.
|
|
@@ -1322,7 +1456,9 @@ export function createSvGridController(props) {
|
|
|
1322
1456
|
return map;
|
|
1323
1457
|
});
|
|
1324
1458
|
const columnMenuFacetValues = $derived.by(() => {
|
|
1325
|
-
|
|
1459
|
+
// The funnel popover drives via `filterMenuFor`; the column menu's Filter
|
|
1460
|
+
// tab drives via `columnMenuFor`. Support whichever is open.
|
|
1461
|
+
const columnId = filterMenuFor ?? columnMenuFor;
|
|
1326
1462
|
if (!columnId)
|
|
1327
1463
|
return [];
|
|
1328
1464
|
const column = allColumns.find((entry) => entry.id === columnId);
|
|
@@ -1393,6 +1529,8 @@ export function createSvGridController(props) {
|
|
|
1393
1529
|
set scrollSyncRaf(v) { scrollSyncRaf = v; },
|
|
1394
1530
|
get selectionRange() { return selectionRange; },
|
|
1395
1531
|
set selectionRange(v) { selectionRange = v; },
|
|
1532
|
+
get selectionRanges() { return selectionRanges; },
|
|
1533
|
+
set selectionRanges(v) { selectionRanges = v; },
|
|
1396
1534
|
get isDraggingSelection() { return isDraggingSelection; },
|
|
1397
1535
|
set isDraggingSelection(v) { isDraggingSelection = v; },
|
|
1398
1536
|
get fillDrag() { return fillDrag; },
|
|
@@ -1401,6 +1539,8 @@ export function createSvGridController(props) {
|
|
|
1401
1539
|
set activeAtPointerDown(v) { activeAtPointerDown = v; },
|
|
1402
1540
|
get editingCell() { return editingCell; },
|
|
1403
1541
|
set editingCell(v) { editingCell = v; },
|
|
1542
|
+
get fullRowEdit() { return fullRowEdit; },
|
|
1543
|
+
set fullRowEdit(v) { fullRowEdit = v; },
|
|
1404
1544
|
get editedCellValues() { return editedCellValues; },
|
|
1405
1545
|
set editedCellValues(v) { editedCellValues = v; },
|
|
1406
1546
|
get UNDO_LIMIT() { return UNDO_LIMIT; },
|
|
@@ -1453,6 +1593,8 @@ export function createSvGridController(props) {
|
|
|
1453
1593
|
get DATE_OPERATORS() { return DATE_OPERATORS; },
|
|
1454
1594
|
get CHECKBOX_OPERATORS() { return CHECKBOX_OPERATORS; },
|
|
1455
1595
|
get columnMenuFor() { return columnMenuFor; },
|
|
1596
|
+
get columnMenuTab() { return columnMenuTab; },
|
|
1597
|
+
set columnMenuTab(v) { columnMenuTab = v; },
|
|
1456
1598
|
set columnMenuFor(v) { columnMenuFor = v; },
|
|
1457
1599
|
get columnMenuPos() { return columnMenuPos; },
|
|
1458
1600
|
set columnMenuPos(v) { columnMenuPos = v; },
|
|
@@ -1498,6 +1640,8 @@ export function createSvGridController(props) {
|
|
|
1498
1640
|
set internalColumns(v) { internalColumns = v; },
|
|
1499
1641
|
get hiddenColumns() { return hiddenColumns; },
|
|
1500
1642
|
set hiddenColumns(v) { hiddenColumns = v; },
|
|
1643
|
+
get toggleColumnGroup() { return toggleColumnGroup; },
|
|
1644
|
+
get isColumnGroupCollapsed() { return isColumnGroupCollapsed; },
|
|
1501
1645
|
get externalSortEnabled() { return externalSortEnabled; },
|
|
1502
1646
|
get externalFilterEnabled() { return externalFilterEnabled; },
|
|
1503
1647
|
get passthroughSortedRowModel() { return passthroughSortedRowModel; },
|
|
@@ -1519,6 +1663,20 @@ export function createSvGridController(props) {
|
|
|
1519
1663
|
set colDropOnId(v) { colDropOnId = v; },
|
|
1520
1664
|
get colDropSide() { return colDropSide; },
|
|
1521
1665
|
set colDropSide(v) { colDropSide = v; },
|
|
1666
|
+
get rowDragActive() { return rowDragActive; },
|
|
1667
|
+
set rowDragActive(v) { rowDragActive = v; },
|
|
1668
|
+
get rowDropIndex() { return rowDropIndex; },
|
|
1669
|
+
set rowDropIndex(v) { rowDropIndex = v; },
|
|
1670
|
+
get rowDropSide() { return rowDropSide; },
|
|
1671
|
+
set rowDropSide(v) { rowDropSide = v; },
|
|
1672
|
+
get onRowDragStart() { return onRowDragStart; },
|
|
1673
|
+
get onRowDragOver() { return onRowDragOver; },
|
|
1674
|
+
get onRowDragLeave() { return onRowDragLeave; },
|
|
1675
|
+
get onRowDrop() { return onRowDrop; },
|
|
1676
|
+
get onRowsContainerDragOver() { return onRowsContainerDragOver; },
|
|
1677
|
+
get onRowsContainerDrop() { return onRowsContainerDrop; },
|
|
1678
|
+
get onRowDragEnd() { return onRowDragEnd; },
|
|
1679
|
+
get broadcastAlignedScroll() { return broadcastAlignedScroll; },
|
|
1522
1680
|
get getCurrentColumnOrder() { return getCurrentColumnOrder; },
|
|
1523
1681
|
get emitColumnOrder() { return emitColumnOrder; },
|
|
1524
1682
|
get setColumnOrderInternal() { return setColumnOrderInternal; },
|
|
@@ -1554,6 +1712,8 @@ export function createSvGridController(props) {
|
|
|
1554
1712
|
get statusBarStats() { return statusBarStats; },
|
|
1555
1713
|
get toolPanelOpen() { return toolPanelOpen; },
|
|
1556
1714
|
set toolPanelOpen(v) { toolPanelOpen = v; },
|
|
1715
|
+
get toolPanelTab() { return toolPanelTab; },
|
|
1716
|
+
set toolPanelTab(v) { toolPanelTab = v; },
|
|
1557
1717
|
get toolPanelEnabled() { return toolPanelEnabled; },
|
|
1558
1718
|
get toolPanelColumns() { return toolPanelColumns; },
|
|
1559
1719
|
get toolPanelHeaderLabel() { return toolPanelHeaderLabel; },
|
|
@@ -1636,6 +1796,7 @@ export function createSvGridController(props) {
|
|
|
1636
1796
|
get extendSelection() { return extendSelection; },
|
|
1637
1797
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
1638
1798
|
get getCellRangeEdges() { return getCellRangeEdges; },
|
|
1799
|
+
get getSelectionRects() { return getSelectionRects; },
|
|
1639
1800
|
get fillHandleCell() { return fillHandleCell; },
|
|
1640
1801
|
get isInFillPreview() { return isInFillPreview; },
|
|
1641
1802
|
get findColumnById() { return findColumnById; },
|
|
@@ -1654,9 +1815,18 @@ export function createSvGridController(props) {
|
|
|
1654
1815
|
get onCellClick() { return onCellClick; },
|
|
1655
1816
|
get emitCellDoubleClick() { return emitCellDoubleClick; },
|
|
1656
1817
|
get copySelectionToClipboard() { return copySelectionToClipboard; },
|
|
1818
|
+
get cutSelectionToClipboard() { return cutSelectionToClipboard; },
|
|
1819
|
+
get pasteFromClipboard() { return pasteFromClipboard; },
|
|
1820
|
+
get onGridPaste() { return onGridPaste; },
|
|
1657
1821
|
get clearSelectedCells() { return clearSelectedCells; },
|
|
1658
1822
|
get onCellDoubleClick() { return onCellDoubleClick; },
|
|
1659
1823
|
get startEditingWithChar() { return startEditingWithChar; },
|
|
1824
|
+
get startEditing() { return startEditing; },
|
|
1825
|
+
get stopEditing() { return stopEditing; },
|
|
1826
|
+
get startFullRowEdit() { return startFullRowEdit; },
|
|
1827
|
+
get setFullRowDraft() { return setFullRowDraft; },
|
|
1828
|
+
get commitFullRowEdit() { return commitFullRowEdit; },
|
|
1829
|
+
get cancelFullRowEdit() { return cancelFullRowEdit; },
|
|
1660
1830
|
get saveEditingCell() { return saveEditingCell; },
|
|
1661
1831
|
get applyHistoryStep() { return applyHistoryStep; },
|
|
1662
1832
|
get updateEditingCellValue() { return updateEditingCellValue; },
|
|
@@ -1723,10 +1893,25 @@ export function createSvGridController(props) {
|
|
|
1723
1893
|
const { computeSummaries, hasRenderedColumn } = createSummaries(ctx);
|
|
1724
1894
|
const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus(ctx);
|
|
1725
1895
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellNote, getColumnEditorOptions, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender(ctx);
|
|
1726
|
-
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, focusOnMount, onCellDoubleClick, pasteFromClipboard } = createEditing(ctx);
|
|
1727
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, isInFillPreview, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
|
|
1896
|
+
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing(ctx);
|
|
1897
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection(ctx);
|
|
1728
1898
|
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, startColumnResize, onColumnResizeMove, endColumnResize, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns(ctx);
|
|
1899
|
+
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd } = createRowDrag(ctx);
|
|
1900
|
+
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids(ctx);
|
|
1729
1901
|
const { buildApi } = createGridApi(ctx);
|
|
1730
1902
|
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
1903
|
+
// Aligned grids: register in the shared group on mount, and mirror column
|
|
1904
|
+
// resizes to peers whenever columnWidths changes. Horizontal-scroll mirroring
|
|
1905
|
+
// is driven from onBodyScroll (via ctx.broadcastAlignedScroll).
|
|
1906
|
+
$effect(() => {
|
|
1907
|
+
if (props.alignedGridGroup == null)
|
|
1908
|
+
return;
|
|
1909
|
+
return registerAlignedGrid();
|
|
1910
|
+
});
|
|
1911
|
+
$effect(() => {
|
|
1912
|
+
// Track columnWidths reactively, then broadcast to aligned peers.
|
|
1913
|
+
void columnWidths;
|
|
1914
|
+
broadcastAlignedWidths();
|
|
1915
|
+
});
|
|
1731
1916
|
return ctx;
|
|
1732
1917
|
}
|