@svgrid/grid 1.1.0 → 1.1.2
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/FlexRender.svelte +96 -96
- package/dist/GridMenus.svelte +205 -127
- package/dist/SvGrid.controller.svelte.d.ts +44 -1
- package/dist/SvGrid.controller.svelte.js +240 -44
- package/dist/SvGrid.css +325 -22
- package/dist/SvGrid.helpers.test.d.ts +1 -0
- package/dist/SvGrid.helpers.test.js +298 -0
- package/dist/SvGrid.svelte +729 -203
- 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 +61 -31
- package/dist/cell-render.test.d.ts +1 -0
- package/dist/cell-render.test.js +338 -0
- package/dist/cell-values.d.ts +1 -1
- package/dist/cell-values.js +7 -7
- 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 +86 -1
- package/dist/core.js +2 -2
- 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/summaries.js +4 -4
- 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/FlexRender.svelte +96 -96
- package/src/GridMenus.svelte +205 -127
- package/src/SvGrid.controller.svelte.ts +204 -36
- package/src/SvGrid.css +277 -12
- package/src/SvGrid.svelte +727 -205
- package/src/SvGrid.types.ts +85 -4
- package/src/a11y.contract.test.ts +49 -49
- package/src/a11y.test.ts +59 -59
- package/src/a11y.ts +59 -59
- package/src/aligned-grids.test.ts +80 -0
- package/src/aligned-grids.ts +87 -0
- package/src/build-api.ts +43 -23
- package/src/cell-formatting.ts +169 -169
- package/src/cell-values.ts +4 -4
- 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.performance.test.ts +30 -30
- package/src/core.ts +1077 -999
- package/src/createGrid.svelte.ts +42 -42
- package/src/createGrid.test.ts +10 -10
- package/src/createGridState.svelte.ts +17 -17
- package/src/editing.ts +161 -5
- package/src/flex-render.ts +3 -3
- package/src/index.ts +208 -196
- package/src/keyboard.test.ts +59 -59
- package/src/keyboard.ts +97 -97
- package/src/menus.ts +1 -0
- package/src/merge-objects.ts +48 -48
- package/src/render-component.ts +28 -28
- 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 +46 -2
- package/src/spreadsheet.ts +58 -0
- package/src/static-functions.ts +11 -11
- package/src/subscribe.ts +38 -38
- package/src/summaries.ts +4 -4
- package/src/svgrid-wrapper.types.ts +412 -393
- package/src/svgrid.features.test.ts +157 -157
- package/src/svgrid.new-features.wrapper.test.ts +2 -2
- package/src/svgrid.wrapper.test.ts +40 -40
- package/src/virtualization/column-virtualizer.test.ts +27 -27
- package/src/virtualization/column-virtualizer.ts +30 -30
- package/src/virtualization/svelte-virtualizer.svelte.ts +26 -26
- package/src/virtualization/types.ts +30 -30
- package/src/virtualization/virtualizer.test.ts +47 -47
- package/src/virtualization/virtualizer.ts +296 -296
|
@@ -69,6 +69,19 @@ import {
|
|
|
69
69
|
import {
|
|
70
70
|
createColumns,
|
|
71
71
|
} from "./columns";
|
|
72
|
+
import {
|
|
73
|
+
createRowDrag,
|
|
74
|
+
} from "./row-drag";
|
|
75
|
+
import {
|
|
76
|
+
createAlignedGrids,
|
|
77
|
+
} from "./aligned-grids";
|
|
78
|
+
import {
|
|
79
|
+
resolveColumnTypes,
|
|
80
|
+
} from "./column-types";
|
|
81
|
+
import {
|
|
82
|
+
computeColumnGroupMeta,
|
|
83
|
+
hiddenLeavesForCollapse,
|
|
84
|
+
} from "./column-groups";
|
|
72
85
|
import {
|
|
73
86
|
createGridApi,
|
|
74
87
|
} from "./build-api";
|
|
@@ -242,7 +255,20 @@ export function createSvGridController<
|
|
|
242
255
|
let gridRootEl: HTMLElement | null = $state(null);
|
|
243
256
|
let filterRowValues = $state<Record<string, string>>({});
|
|
244
257
|
let filterMenuValues = $state<
|
|
245
|
-
Record<
|
|
258
|
+
Record<
|
|
259
|
+
string,
|
|
260
|
+
{
|
|
261
|
+
operator: FilterOperator;
|
|
262
|
+
value: string;
|
|
263
|
+
valueTo?: string;
|
|
264
|
+
// Optional second condition + join for multi-condition filtering
|
|
265
|
+
// within a single column (AND / OR).
|
|
266
|
+
operator2?: FilterOperator;
|
|
267
|
+
value2?: string;
|
|
268
|
+
valueTo2?: string;
|
|
269
|
+
join?: "AND" | "OR";
|
|
270
|
+
}
|
|
271
|
+
>
|
|
246
272
|
>({});
|
|
247
273
|
let verticalScrollbarEl: HTMLElement | null = $state(null);
|
|
248
274
|
let horizontalScrollbarEl: HTMLElement | null = $state(null);
|
|
@@ -259,6 +285,10 @@ export function createSvGridController<
|
|
|
259
285
|
let pendingScrollLeft = 0;
|
|
260
286
|
let scrollSyncRaf: number | null = null;
|
|
261
287
|
let selectionRange = $state<SelectionRange>({ anchor: null, focus: null });
|
|
288
|
+
// Extra committed ranges for multi-range (Ctrl+drag) selection. The
|
|
289
|
+
// `selectionRange` above is always the ACTIVE range being manipulated; these
|
|
290
|
+
// are the finished ones. Full selection = these + the active range.
|
|
291
|
+
let selectionRanges = $state.raw<SelectionRange[]>([]);
|
|
262
292
|
let isDraggingSelection = $state(false);
|
|
263
293
|
/** Excel-style fill handle drag state. While non-null we paint a "fill
|
|
264
294
|
* preview" overlay on cells between the source range and the pointer
|
|
@@ -273,6 +303,9 @@ export function createSvGridController<
|
|
|
273
303
|
} | null>(null);
|
|
274
304
|
let activeAtPointerDown: { rowIndex: number; colIndex: number } | null = null;
|
|
275
305
|
let editingCell = $state<CellEditState>(null);
|
|
306
|
+
// Full-row editing: the row currently in whole-row edit + its per-column
|
|
307
|
+
// draft (keyed by column id). Null when not in full-row mode.
|
|
308
|
+
let fullRowEdit = $state<{ rowId: string; draft: Record<string, unknown> } | null>(null);
|
|
276
309
|
let editedCellValues = $state<Record<string, unknown>>({});
|
|
277
310
|
|
|
278
311
|
// ---- Undo / redo (history + pointer model) ---------------------------
|
|
@@ -351,6 +384,7 @@ export function createSvGridController<
|
|
|
351
384
|
const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
|
|
352
385
|
const showRowNumbersEffective = $derived(props.showRowNumbers ?? false);
|
|
353
386
|
let columnMenuFor = $state<string | null>(null);
|
|
387
|
+
let columnMenuTab = $state<"general" | "filter" | "columns">("general");
|
|
354
388
|
let columnMenuPos = $state<MenuPosition>({ x: 0, y: 0 });
|
|
355
389
|
let columnMenuSearch = $state("");
|
|
356
390
|
let filterMenuFor = $state<string | null>(null);
|
|
@@ -396,14 +430,25 @@ export function createSvGridController<
|
|
|
396
430
|
};
|
|
397
431
|
});
|
|
398
432
|
/** Vertical overflow from the virtualizer's authoritative total size,
|
|
399
|
-
* NOT from `scrollMetrics.scrollHeight
|
|
433
|
+
* NOT from `scrollMetrics.scrollHeight` alone. Reading DOM dimensions
|
|
400
434
|
* during a Svelte derived runs BEFORE the browser paints - the table
|
|
401
435
|
* hasn't laid out the new rows yet, so `scrollHeight` is briefly 0
|
|
402
436
|
* even after data loads. That made the overflow flag return false,
|
|
403
|
-
* hid the scrollbar, and broke dragging.
|
|
437
|
+
* hid the scrollbar, and broke dragging.
|
|
438
|
+
*
|
|
439
|
+
* However, virtualizer.getTotalSize() uses rowHeight * numRows which
|
|
440
|
+
* underestimates when variable-height rows are present (e.g. master-detail
|
|
441
|
+
* expanded rows). We therefore take the MAX of the two sources:
|
|
442
|
+
* - virtualizer.getTotalSize(): correct at initial load (before first paint)
|
|
443
|
+
* - scrollMetrics.scrollHeight: correct after detail rows expand (DOM is live,
|
|
444
|
+
* ResizeObserver on gridRootEl already bumps scrollVersion at that point) */
|
|
404
445
|
const hasVerticalOverflow = $derived.by(() => {
|
|
405
446
|
virtualizer.version;
|
|
406
|
-
|
|
447
|
+
const virtualizerSize = virtualizer.getTotalSize();
|
|
448
|
+
// scrollMetrics.scrollHeight is 0 before initial paint; once the table
|
|
449
|
+
// is in the DOM it reflects the true content height including expanded rows.
|
|
450
|
+
const domSize = scrollMetrics.scrollHeight;
|
|
451
|
+
return Math.max(virtualizerSize, domSize) > viewportHeight + 1;
|
|
407
452
|
});
|
|
408
453
|
|
|
409
454
|
// Effective filter-UI flags. Each show* prop wins when explicitly set;
|
|
@@ -444,15 +489,48 @@ export function createSvGridController<
|
|
|
444
489
|
// mutates these so add/remove operations don't need a callback round-trip.
|
|
445
490
|
// svelte-ignore state_referenced_locally
|
|
446
491
|
let internalData = $state.raw<ReadonlyArray<TData>>(props.data);
|
|
492
|
+
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
493
|
+
// format defaults once, up front, so every downstream reader sees a normal
|
|
494
|
+
// column. Explicit fields on the ColumnDef always win.
|
|
495
|
+
// svelte-ignore state_referenced_locally
|
|
496
|
+
const resolveCols = (cols: Array<ColumnDef<TFeatures, TData>>) =>
|
|
497
|
+
resolveColumnTypes(cols, props.data?.[0], props.inferColumnTypes === true);
|
|
447
498
|
// svelte-ignore state_referenced_locally
|
|
448
499
|
let internalColumns = $state.raw<Array<ColumnDef<TFeatures, TData>>>(
|
|
449
|
-
props.columns,
|
|
500
|
+
resolveCols(props.columns),
|
|
450
501
|
);
|
|
451
502
|
// svelte-ignore state_referenced_locally
|
|
452
503
|
let hiddenColumns = $state<Record<string, boolean>>(
|
|
453
504
|
initialHiddenColumns(props.columns),
|
|
454
505
|
);
|
|
455
506
|
|
|
507
|
+
// Collapsible column groups (columnGroupShow). Meta is derived from the tree;
|
|
508
|
+
// `collapsedColumnGroups` is the live set of collapsed group ids, seeded once
|
|
509
|
+
// from each collapsible group's `openByDefault` (default: collapsed).
|
|
510
|
+
const columnGroupMeta = $derived(computeColumnGroupMeta(props.columns as unknown as Array<any>));
|
|
511
|
+
// svelte-ignore state_referenced_locally
|
|
512
|
+
let collapsedColumnGroups = $state<Set<string>>(
|
|
513
|
+
(() => {
|
|
514
|
+
const meta = computeColumnGroupMeta(props.columns as unknown as Array<any>);
|
|
515
|
+
const s = new Set<string>();
|
|
516
|
+
for (const id of meta.collapsibleGroupIds) if (!meta.defaultOpen.get(id)) s.add(id);
|
|
517
|
+
return s;
|
|
518
|
+
})(),
|
|
519
|
+
);
|
|
520
|
+
// Leaf ids hidden right now because their group is collapsed/expanded.
|
|
521
|
+
const hiddenByGroupCollapse = $derived(
|
|
522
|
+
hiddenLeavesForCollapse(columnGroupMeta, collapsedColumnGroups),
|
|
523
|
+
);
|
|
524
|
+
function toggleColumnGroup(groupId: string) {
|
|
525
|
+
const next = new Set(collapsedColumnGroups);
|
|
526
|
+
if (next.has(groupId)) next.delete(groupId);
|
|
527
|
+
else next.add(groupId);
|
|
528
|
+
collapsedColumnGroups = next;
|
|
529
|
+
}
|
|
530
|
+
function isColumnGroupCollapsed(groupId: string) {
|
|
531
|
+
return collapsedColumnGroups.has(groupId);
|
|
532
|
+
}
|
|
533
|
+
|
|
456
534
|
$effect(() => {
|
|
457
535
|
// When the consumer replaces `data` (e.g. a "Reset" button), drop any
|
|
458
536
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
@@ -462,7 +540,7 @@ export function createSvGridController<
|
|
|
462
540
|
editedCellValues = {};
|
|
463
541
|
});
|
|
464
542
|
$effect(() => {
|
|
465
|
-
internalColumns = props.columns;
|
|
543
|
+
internalColumns = resolveCols(props.columns);
|
|
466
544
|
});
|
|
467
545
|
|
|
468
546
|
// Captured ONCE at mount: `externalSort` is a structural choice (tree vs
|
|
@@ -554,7 +632,7 @@ export function createSvGridController<
|
|
|
554
632
|
const allColumns = $derived.by(() => {
|
|
555
633
|
let raw = grid
|
|
556
634
|
.getAllColumns()
|
|
557
|
-
.filter((column) => !hiddenColumns[column.id]);
|
|
635
|
+
.filter((column) => !hiddenColumns[column.id] && !hiddenByGroupCollapse[column.id]);
|
|
558
636
|
// Apply user reorder (if any). Unknown ids in userColumnOrder are
|
|
559
637
|
// skipped; columns not in userColumnOrder keep their original
|
|
560
638
|
// relative order after the user-ordered ones.
|
|
@@ -626,6 +704,10 @@ export function createSvGridController<
|
|
|
626
704
|
* multi-level value tree). They render as empty cells so the
|
|
627
705
|
* layout stays aligned without showing duplicate labels. */
|
|
628
706
|
isPlaceholder: boolean;
|
|
707
|
+
/** Set when this group cell has a collapse toggle. */
|
|
708
|
+
groupId?: string;
|
|
709
|
+
collapsible: boolean;
|
|
710
|
+
collapsed: boolean;
|
|
629
711
|
};
|
|
630
712
|
const groupHeaderRows = $derived.by(() => {
|
|
631
713
|
const userCols: Array<ColumnDef<any, TData>> =
|
|
@@ -652,6 +734,10 @@ export function createSvGridController<
|
|
|
652
734
|
function buildId(def: ColumnDef<any, TData>, parentId: string | undefined, fallbackIx: number): string {
|
|
653
735
|
return def.id ?? def.field ?? `${parentId ?? 'col'}_d_${fallbackIx}`;
|
|
654
736
|
}
|
|
737
|
+
// Leaves hidden by a collapsed/expanded group are skipped everywhere here,
|
|
738
|
+
// so group colSpan + widthPx exclude them and stay aligned with the leaves
|
|
739
|
+
// the body actually renders.
|
|
740
|
+
const hiddenLeaf = hiddenByGroupCollapse;
|
|
655
741
|
function collectLeaves(
|
|
656
742
|
defs: Array<ColumnDef<any, TData>>,
|
|
657
743
|
parentId: string | undefined,
|
|
@@ -661,7 +747,7 @@ export function createSvGridController<
|
|
|
661
747
|
const id = buildId(def, parentId, ix);
|
|
662
748
|
if (def.columns?.length) {
|
|
663
749
|
collectLeaves(def.columns, id, depthHere + 1);
|
|
664
|
-
} else {
|
|
750
|
+
} else if (!hiddenLeaf[id]) {
|
|
665
751
|
leafEntries.push({ id, widthPx: getColumnWidth(id) });
|
|
666
752
|
}
|
|
667
753
|
});
|
|
@@ -679,6 +765,9 @@ export function createSvGridController<
|
|
|
679
765
|
const nodes: NodeAt[] = [];
|
|
680
766
|
for (const def of defs) {
|
|
681
767
|
const id = buildId(def, parentId, nodes.length);
|
|
768
|
+
// Skip leaves the collapse state hides, so leaf indices/colSpans match
|
|
769
|
+
// `leafEntries` (and the body's rendered columns) exactly.
|
|
770
|
+
if (!def.columns?.length && hiddenLeaf[id]) continue;
|
|
682
771
|
const leafStart = cursor.leaf;
|
|
683
772
|
if (def.columns?.length) {
|
|
684
773
|
indexTree(def.columns, id, cursor);
|
|
@@ -728,6 +817,7 @@ export function createSvGridController<
|
|
|
728
817
|
const isLeafEarly = !n.def.columns?.length;
|
|
729
818
|
const headerText =
|
|
730
819
|
typeof n.def.header === 'string' ? n.def.header : '';
|
|
820
|
+
const collapsible = columnGroupMeta.collapsibleGroupIds.has(n.id);
|
|
731
821
|
return {
|
|
732
822
|
key: `${n.id}_d${d}`,
|
|
733
823
|
label: isLeafEarly ? '' : headerText,
|
|
@@ -735,6 +825,9 @@ export function createSvGridController<
|
|
|
735
825
|
widthPx: sumLeafWidths(n.leafStart, n.leafEnd),
|
|
736
826
|
firstLeafIndex: n.leafStart,
|
|
737
827
|
isPlaceholder: isLeafEarly,
|
|
828
|
+
groupId: collapsible ? n.id : undefined,
|
|
829
|
+
collapsible,
|
|
830
|
+
collapsed: collapsible && collapsedColumnGroups.has(n.id),
|
|
738
831
|
};
|
|
739
832
|
});
|
|
740
833
|
rows.push({ id: `gh_${d}`, cells });
|
|
@@ -772,6 +865,13 @@ export function createSvGridController<
|
|
|
772
865
|
let colDropOnId = $state<string | null>(null);
|
|
773
866
|
let colDropSide = $state<"before" | "after" | null>(null);
|
|
774
867
|
|
|
868
|
+
// Live drag state for managed row dragging. Only meaningful while a row is
|
|
869
|
+
// being dragged (`props.rowDragManaged`). `rowDropIndex` is the visible row
|
|
870
|
+
// index currently hovered; `rowDropSide` says which edge the drop line paints.
|
|
871
|
+
let rowDragActive = $state<boolean>(false);
|
|
872
|
+
let rowDropIndex = $state<number | null>(null);
|
|
873
|
+
let rowDropSide = $state<"before" | "after" | null>(null);
|
|
874
|
+
|
|
775
875
|
|
|
776
876
|
|
|
777
877
|
|
|
@@ -800,13 +900,13 @@ export function createSvGridController<
|
|
|
800
900
|
);
|
|
801
901
|
if (!needs) continue;
|
|
802
902
|
const def = column.columnDef;
|
|
803
|
-
const
|
|
903
|
+
const fieldFn = def.fieldFn;
|
|
804
904
|
const field = def.field;
|
|
805
905
|
const stat = computeColumnStat(
|
|
806
906
|
(function* () {
|
|
807
907
|
for (const row of allRows) {
|
|
808
|
-
yield
|
|
809
|
-
?
|
|
908
|
+
yield fieldFn
|
|
909
|
+
? fieldFn(row.original)
|
|
810
910
|
: field
|
|
811
911
|
? (row.original as Record<string, unknown>)[field]
|
|
812
912
|
: row.getCellValueByColumnId(column.id);
|
|
@@ -872,29 +972,44 @@ export function createSvGridController<
|
|
|
872
972
|
);
|
|
873
973
|
}
|
|
874
974
|
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
)
|
|
975
|
+
// A single condition is "active" if it has the value(s) it needs.
|
|
976
|
+
const condActive = (op: FilterOperator, value: string, valueTo?: string): boolean => {
|
|
977
|
+
if (op === "isBlank") return true;
|
|
978
|
+
if (op === "between") return value.trim().length > 0 && (valueTo ?? "").trim().length > 0;
|
|
979
|
+
return value.trim().length > 0;
|
|
980
|
+
};
|
|
981
|
+
const evalCond = (
|
|
982
|
+
cellValue: unknown,
|
|
983
|
+
columnId: string,
|
|
984
|
+
op: FilterOperator,
|
|
985
|
+
value: string,
|
|
986
|
+
valueTo?: string,
|
|
987
|
+
): boolean =>
|
|
988
|
+
applyExcelFilter(
|
|
989
|
+
cellValue,
|
|
990
|
+
{ id: columnId, operator: op, value, valueTo: op === "between" ? valueTo : undefined },
|
|
991
|
+
{ locale: props.filterLocale },
|
|
992
|
+
);
|
|
993
|
+
// A column filter is active if either of its (up to two) conditions is.
|
|
994
|
+
const menuFilters = Object.entries(filterMenuValues).filter(([_, f]) => {
|
|
995
|
+
const a = condActive(f.operator, f.value, f.valueTo);
|
|
996
|
+
const b = !!f.operator2 && condActive(f.operator2, f.value2 ?? "", f.valueTo2);
|
|
997
|
+
return a || b;
|
|
998
|
+
});
|
|
888
999
|
if (menuFilters.length) {
|
|
889
1000
|
rows = rows.filter((row) =>
|
|
890
|
-
menuFilters.every(([columnId,
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1001
|
+
menuFilters.every(([columnId, f]) => {
|
|
1002
|
+
const cellValue = getRowColumnValue(row, columnId);
|
|
1003
|
+
const aActive = condActive(f.operator, f.value, f.valueTo);
|
|
1004
|
+
const bActive = !!f.operator2 && condActive(f.operator2, f.value2 ?? "", f.valueTo2);
|
|
1005
|
+
const ra = aActive ? evalCond(cellValue, columnId, f.operator, f.value, f.valueTo) : null;
|
|
1006
|
+
const rb = bActive
|
|
1007
|
+
? evalCond(cellValue, columnId, f.operator2 as FilterOperator, f.value2 ?? "", f.valueTo2)
|
|
1008
|
+
: null;
|
|
1009
|
+
if (ra === null) return rb ?? true;
|
|
1010
|
+
if (rb === null) return ra;
|
|
1011
|
+
return f.join === "OR" ? ra || rb : ra && rb;
|
|
1012
|
+
}),
|
|
898
1013
|
);
|
|
899
1014
|
}
|
|
900
1015
|
|
|
@@ -1040,7 +1155,10 @@ export function createSvGridController<
|
|
|
1040
1155
|
|
|
1041
1156
|
|
|
1042
1157
|
// ---- Tool panel (docked columns sidebar) -------------------------------
|
|
1043
|
-
|
|
1158
|
+
// svelte-ignore state_referenced_locally
|
|
1159
|
+
let toolPanelOpen = $state(props.toolPanelDefaultOpen === true);
|
|
1160
|
+
// svelte-ignore state_referenced_locally
|
|
1161
|
+
let toolPanelTab = $state<"columns" | "filters">(props.toolPanelDefaultTab ?? "columns");
|
|
1044
1162
|
const toolPanelEnabled = $derived(props.toolPanel === true);
|
|
1045
1163
|
// Every column (including hidden ones) in the user's current order, so the
|
|
1046
1164
|
// panel can toggle/reorder anything. Group columns are flagged live.
|
|
@@ -1393,6 +1511,7 @@ export function createSvGridController<
|
|
|
1393
1511
|
if (isFirstRender) return;
|
|
1394
1512
|
|
|
1395
1513
|
selectionRange = { anchor: null, focus: null };
|
|
1514
|
+
selectionRanges = [];
|
|
1396
1515
|
editingCell = null;
|
|
1397
1516
|
if (scrollContainer) {
|
|
1398
1517
|
scrollContainer.scrollTop = 0;
|
|
@@ -1773,7 +1892,9 @@ export function createSvGridController<
|
|
|
1773
1892
|
});
|
|
1774
1893
|
|
|
1775
1894
|
const columnMenuFacetValues = $derived.by(() => {
|
|
1776
|
-
|
|
1895
|
+
// The funnel popover drives via `filterMenuFor`; the column menu's Filter
|
|
1896
|
+
// tab drives via `columnMenuFor`. Support whichever is open.
|
|
1897
|
+
const columnId = filterMenuFor ?? columnMenuFor;
|
|
1777
1898
|
if (!columnId) return [] as Array<string>;
|
|
1778
1899
|
const column = allColumns.find((entry) => entry.id === columnId);
|
|
1779
1900
|
if (!column) return [] as Array<string>;
|
|
@@ -1853,6 +1974,8 @@ export function createSvGridController<
|
|
|
1853
1974
|
set scrollSyncRaf(v) { scrollSyncRaf = v as never; },
|
|
1854
1975
|
get selectionRange() { return selectionRange; },
|
|
1855
1976
|
set selectionRange(v) { selectionRange = v as never; },
|
|
1977
|
+
get selectionRanges() { return selectionRanges; },
|
|
1978
|
+
set selectionRanges(v) { selectionRanges = v as never; },
|
|
1856
1979
|
get isDraggingSelection() { return isDraggingSelection; },
|
|
1857
1980
|
set isDraggingSelection(v) { isDraggingSelection = v as never; },
|
|
1858
1981
|
get fillDrag() { return fillDrag; },
|
|
@@ -1861,6 +1984,8 @@ export function createSvGridController<
|
|
|
1861
1984
|
set activeAtPointerDown(v) { activeAtPointerDown = v as never; },
|
|
1862
1985
|
get editingCell() { return editingCell; },
|
|
1863
1986
|
set editingCell(v) { editingCell = v as never; },
|
|
1987
|
+
get fullRowEdit() { return fullRowEdit; },
|
|
1988
|
+
set fullRowEdit(v) { fullRowEdit = v as never; },
|
|
1864
1989
|
get editedCellValues() { return editedCellValues; },
|
|
1865
1990
|
set editedCellValues(v) { editedCellValues = v as never; },
|
|
1866
1991
|
get UNDO_LIMIT() { return UNDO_LIMIT; },
|
|
@@ -1913,6 +2038,8 @@ export function createSvGridController<
|
|
|
1913
2038
|
get DATE_OPERATORS() { return DATE_OPERATORS; },
|
|
1914
2039
|
get CHECKBOX_OPERATORS() { return CHECKBOX_OPERATORS; },
|
|
1915
2040
|
get columnMenuFor() { return columnMenuFor; },
|
|
2041
|
+
get columnMenuTab() { return columnMenuTab; },
|
|
2042
|
+
set columnMenuTab(v) { columnMenuTab = v as never; },
|
|
1916
2043
|
set columnMenuFor(v) { columnMenuFor = v as never; },
|
|
1917
2044
|
get columnMenuPos() { return columnMenuPos; },
|
|
1918
2045
|
set columnMenuPos(v) { columnMenuPos = v as never; },
|
|
@@ -1958,6 +2085,8 @@ export function createSvGridController<
|
|
|
1958
2085
|
set internalColumns(v) { internalColumns = v as never; },
|
|
1959
2086
|
get hiddenColumns() { return hiddenColumns; },
|
|
1960
2087
|
set hiddenColumns(v) { hiddenColumns = v as never; },
|
|
2088
|
+
get toggleColumnGroup() { return toggleColumnGroup; },
|
|
2089
|
+
get isColumnGroupCollapsed() { return isColumnGroupCollapsed; },
|
|
1961
2090
|
get externalSortEnabled() { return externalSortEnabled; },
|
|
1962
2091
|
get externalFilterEnabled() { return externalFilterEnabled; },
|
|
1963
2092
|
get passthroughSortedRowModel() { return passthroughSortedRowModel; },
|
|
@@ -1979,6 +2108,20 @@ export function createSvGridController<
|
|
|
1979
2108
|
set colDropOnId(v) { colDropOnId = v as never; },
|
|
1980
2109
|
get colDropSide() { return colDropSide; },
|
|
1981
2110
|
set colDropSide(v) { colDropSide = v as never; },
|
|
2111
|
+
get rowDragActive() { return rowDragActive; },
|
|
2112
|
+
set rowDragActive(v) { rowDragActive = v as never; },
|
|
2113
|
+
get rowDropIndex() { return rowDropIndex; },
|
|
2114
|
+
set rowDropIndex(v) { rowDropIndex = v as never; },
|
|
2115
|
+
get rowDropSide() { return rowDropSide; },
|
|
2116
|
+
set rowDropSide(v) { rowDropSide = v as never; },
|
|
2117
|
+
get onRowDragStart() { return onRowDragStart; },
|
|
2118
|
+
get onRowDragOver() { return onRowDragOver; },
|
|
2119
|
+
get onRowDragLeave() { return onRowDragLeave; },
|
|
2120
|
+
get onRowDrop() { return onRowDrop; },
|
|
2121
|
+
get onRowsContainerDragOver() { return onRowsContainerDragOver; },
|
|
2122
|
+
get onRowsContainerDrop() { return onRowsContainerDrop; },
|
|
2123
|
+
get onRowDragEnd() { return onRowDragEnd; },
|
|
2124
|
+
get broadcastAlignedScroll() { return broadcastAlignedScroll; },
|
|
1982
2125
|
get getCurrentColumnOrder() { return getCurrentColumnOrder; },
|
|
1983
2126
|
get emitColumnOrder() { return emitColumnOrder; },
|
|
1984
2127
|
get setColumnOrderInternal() { return setColumnOrderInternal; },
|
|
@@ -2014,6 +2157,8 @@ export function createSvGridController<
|
|
|
2014
2157
|
get statusBarStats() { return statusBarStats; },
|
|
2015
2158
|
get toolPanelOpen() { return toolPanelOpen; },
|
|
2016
2159
|
set toolPanelOpen(v) { toolPanelOpen = v as never; },
|
|
2160
|
+
get toolPanelTab() { return toolPanelTab; },
|
|
2161
|
+
set toolPanelTab(v) { toolPanelTab = v as never; },
|
|
2017
2162
|
get toolPanelEnabled() { return toolPanelEnabled; },
|
|
2018
2163
|
get toolPanelColumns() { return toolPanelColumns; },
|
|
2019
2164
|
get toolPanelHeaderLabel() { return toolPanelHeaderLabel; },
|
|
@@ -2096,6 +2241,7 @@ export function createSvGridController<
|
|
|
2096
2241
|
get extendSelection() { return extendSelection; },
|
|
2097
2242
|
get isCellInSelectedRange() { return isCellInSelectedRange; },
|
|
2098
2243
|
get getCellRangeEdges() { return getCellRangeEdges; },
|
|
2244
|
+
get getSelectionRects() { return getSelectionRects; },
|
|
2099
2245
|
get fillHandleCell() { return fillHandleCell; },
|
|
2100
2246
|
get isInFillPreview() { return isInFillPreview; },
|
|
2101
2247
|
get findColumnById() { return findColumnById; },
|
|
@@ -2120,6 +2266,12 @@ export function createSvGridController<
|
|
|
2120
2266
|
get clearSelectedCells() { return clearSelectedCells; },
|
|
2121
2267
|
get onCellDoubleClick() { return onCellDoubleClick; },
|
|
2122
2268
|
get startEditingWithChar() { return startEditingWithChar; },
|
|
2269
|
+
get startEditing() { return startEditing; },
|
|
2270
|
+
get stopEditing() { return stopEditing; },
|
|
2271
|
+
get startFullRowEdit() { return startFullRowEdit; },
|
|
2272
|
+
get setFullRowDraft() { return setFullRowDraft; },
|
|
2273
|
+
get commitFullRowEdit() { return commitFullRowEdit; },
|
|
2274
|
+
get cancelFullRowEdit() { return cancelFullRowEdit; },
|
|
2123
2275
|
get saveEditingCell() { return saveEditingCell; },
|
|
2124
2276
|
get applyHistoryStep() { return applyHistoryStep; },
|
|
2125
2277
|
get updateEditingCellValue() { return updateEditingCellValue; },
|
|
@@ -2186,10 +2338,26 @@ export function createSvGridController<
|
|
|
2186
2338
|
const { computeSummaries, hasRenderedColumn } = createSummaries<TFeatures, TData>(ctx);
|
|
2187
2339
|
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<TFeatures, TData>(ctx);
|
|
2188
2340
|
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellNote, getColumnEditorOptions, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
2189
|
-
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing<TFeatures, TData>(ctx);
|
|
2190
|
-
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, isInFillPreview, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
2341
|
+
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing<TFeatures, TData>(ctx);
|
|
2342
|
+
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
2191
2343
|
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<TFeatures, TData>(ctx);
|
|
2344
|
+
const { onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, onRowsContainerDragOver, onRowsContainerDrop, onRowDragEnd } = createRowDrag<TFeatures, TData>(ctx);
|
|
2345
|
+
const { register: registerAlignedGrid, broadcastScroll: broadcastAlignedScroll, broadcastWidths: broadcastAlignedWidths } = createAlignedGrids<TFeatures, TData>(ctx);
|
|
2192
2346
|
const { buildApi } = createGridApi<TFeatures, TData>(ctx);
|
|
2193
2347
|
const { readCellRaw, writeCellRaw, applyFillPattern, clearSelectedCellValues, startFillDrag, onFillPointerMove, onFillPointerUp, toggleBooleanCell, copySelectionToClipboard, clearSelectedCells, cutSelectionToClipboard } = createClipboard(ctx);
|
|
2348
|
+
|
|
2349
|
+
// Aligned grids: register in the shared group on mount, and mirror column
|
|
2350
|
+
// resizes to peers whenever columnWidths changes. Horizontal-scroll mirroring
|
|
2351
|
+
// is driven from onBodyScroll (via ctx.broadcastAlignedScroll).
|
|
2352
|
+
$effect(() => {
|
|
2353
|
+
if (props.alignedGridGroup == null) return;
|
|
2354
|
+
return registerAlignedGrid();
|
|
2355
|
+
});
|
|
2356
|
+
$effect(() => {
|
|
2357
|
+
// Track columnWidths reactively, then broadcast to aligned peers.
|
|
2358
|
+
void columnWidths;
|
|
2359
|
+
broadcastAlignedWidths();
|
|
2360
|
+
});
|
|
2361
|
+
|
|
2194
2362
|
return ctx;
|
|
2195
2363
|
}
|