@svgrid/grid 3.0.5 → 3.0.6
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/README.md +28 -7
- package/dist/GridMenus.svelte +34 -4
- package/dist/SvGrid.controller.svelte.d.ts +21 -3
- package/dist/SvGrid.controller.svelte.js +205 -50
- package/dist/SvGrid.css +127 -2
- package/dist/SvGrid.svelte +223 -38
- package/dist/SvGrid.types.d.ts +77 -4
- package/dist/SvGridDropdown.svelte +20 -0
- package/dist/SvGridDropdown.svelte.d.ts +12 -0
- package/dist/SvModal.svelte +22 -0
- package/dist/SvModal.svelte.d.ts +7 -0
- package/dist/a11y/dismissable.d.ts +10 -0
- package/dist/a11y/dismissable.js +24 -2
- package/dist/build-api.js +81 -31
- package/dist/cdn/{GridMenus-CID62rlI.js → GridMenus-C-NtjlrJ.js} +150 -141
- package/dist/cdn/GridMenus-TssHej3q.js +644 -0
- package/dist/cdn/{SvChartMenu-FBSMINA6.js → SvChartMenu-BFSTpvB-.js} +1 -1
- package/dist/cdn/{SvChartMenu-Bl6PBbkT.js → SvChartMenu-BZ1Iwv0y.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-DQwt4UAs.js → SvDateTimePicker-D9u_cOf8.js} +1 -1
- package/dist/cdn/{SvDateTimePicker-vNU6bZ-q.js → SvDateTimePicker-s_6IUpI7.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-B1p-vCK5.js → SvGridCellEditor-BlyCSp0q.js} +1 -1
- package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-ClRRA9lW.js} +1 -1
- package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-B4EV8U6t.js} +4 -4
- package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DsmwTyZF.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-cHloHobN.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-vOl7C0Za.js} +4 -4
- package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-AcqF-Ll5.js} +5 -5
- package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-biMtSBPA.js} +5 -5
- package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-Dx_GmZuL.js} +2 -2
- package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-I3rDHXAq.js} +2 -2
- package/dist/cdn/{SvGridDropdown-D0VdjeR8.js → SvGridDropdown-BWeM3lMe.js} +46 -46
- package/dist/cdn/{SvGridDropdown-D13MtJ2j.js → SvGridDropdown-CJ6QqTxu.js} +67 -67
- package/dist/cdn/{SvModal-CIZWCcad.js → SvModal-ClxGmpAc.js} +187 -186
- package/dist/cdn/{SvModal-BAE-pjZX.js → SvModal-DhB_VMFR.js} +148 -147
- package/dist/cdn/{cell-formatting-C2Hf5gqd.js → cell-formatting-DVVKOSI9.js} +1 -1
- package/dist/cdn/{chart-Dz7SqMXH.js → chart-DEZxwC0i.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-D12XcTZJ.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DguS7gC5.js} +1 -1
- package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-CMAXBGY-.js} +1 -1
- package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-BkEp61Gj.js} +1 -1
- package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
- package/dist/cdn/{export-format-CUDLy2yn.js → export-format-YzdwBww2.js} +1 -1
- package/dist/cdn/{row-resize-niQCp040.js → row-resize-DtOS1KG6.js} +41 -32
- package/dist/cdn/{server-block-cache-CGoWz-87.js → server-block-cache-DIqb3VZn.js} +4 -0
- package/dist/cdn/{src-jcFMkBnN.js → src-CgoeSM8k.js} +4697 -4131
- package/dist/cdn/{src-Bklofsad.js → src-DfwxB1TH.js} +4727 -4161
- package/dist/cdn/svgrid.js +17 -17
- package/dist/cdn/svgrid.svelte-external.js +17 -17
- package/dist/cdn/{validate-C7Rn_Bns.js → validate-AZoD8BoR.js} +6 -2
- package/dist/cell-formatting.js +18 -1
- package/dist/cell-render.js +25 -6
- package/dist/clipboard.d.ts +2 -2
- package/dist/clipboard.js +72 -23
- package/dist/columns.js +7 -2
- package/dist/conditional-formatting.d.ts +2 -0
- package/dist/conditional-formatting.js +2 -0
- package/dist/core.d.ts +22 -6
- package/dist/core.js +365 -25
- package/dist/editing.js +24 -0
- package/dist/grid-messages.d.ts +4 -0
- package/dist/grid-messages.js +4 -0
- package/dist/headless.d.ts +5 -0
- package/dist/headless.js +12 -0
- package/dist/history.d.ts +21 -0
- package/dist/history.js +34 -4
- package/dist/index.d.ts +1 -1
- package/dist/keyboard-handlers.js +24 -1
- package/dist/keyboard.js +4 -0
- package/dist/row-drag-touch.d.ts +1 -1
- package/dist/row-drag.js +35 -1
- package/dist/row-model.d.ts +6 -0
- package/dist/row-resize.js +22 -1
- package/dist/selection.js +1 -1
- package/dist/server-block-cache.d.ts +5 -0
- package/dist/server-block-cache.js +8 -0
- package/dist/server-data-source.d.ts +35 -3
- package/dist/server-data-source.js +11 -2
- package/dist/server.d.ts +1 -1
- package/dist/sparkline.js +7 -5
- package/dist/svgrid-wrapper.types.d.ts +33 -0
- package/dist/validate.js +9 -1
- package/package.json +6 -1
- package/src/GridMenus.svelte +34 -4
- package/src/SvGrid.controller.svelte.ts +206 -53
- package/src/SvGrid.css +127 -2
- package/src/SvGrid.svelte +223 -38
- package/src/SvGrid.types.ts +77 -4
- package/src/SvGridDropdown.svelte +20 -0
- package/src/SvModal.svelte +22 -0
- package/src/SvModal.test.ts +30 -1
- package/src/a11y/dismissable.test.ts +10 -1
- package/src/a11y/dismissable.ts +22 -2
- package/src/build-api.ts +85 -27
- package/src/cell-formatting.test.ts +17 -0
- package/src/cell-formatting.ts +18 -1
- package/src/cell-render.test.ts +24 -2
- package/src/cell-render.ts +24 -8
- package/src/clipboard.test.ts +107 -0
- package/src/clipboard.ts +62 -23
- package/src/columns.test.ts +4 -4
- package/src/columns.ts +7 -2
- package/src/conditional-formatting.test.ts +8 -0
- package/src/conditional-formatting.ts +3 -0
- package/src/core.tick-repair.test.ts +287 -0
- package/src/core.ts +374 -26
- package/src/editing.test.ts +52 -0
- package/src/editing.ts +25 -0
- package/src/grid-messages.test.ts +7 -0
- package/src/grid-messages.ts +10 -0
- package/src/headless.ts +24 -0
- package/src/history.test.ts +49 -1
- package/src/history.ts +48 -2
- package/src/index.ts +2 -0
- package/src/keyboard-handlers.coverage.test.ts +65 -0
- package/src/keyboard-handlers.ts +26 -1
- package/src/keyboard.ts +3 -0
- package/src/row-drag-touch.ts +2 -2
- package/src/row-drag.test.ts +51 -0
- package/src/row-drag.ts +35 -2
- package/src/row-model.ts +6 -0
- package/src/row-resize.ts +24 -1
- package/src/selection.ts +1 -1
- package/src/server-block-cache.test.ts +27 -0
- package/src/server-block-cache.ts +13 -0
- package/src/server-data-source.infinite.test.ts +62 -0
- package/src/server-data-source.test.ts +19 -0
- package/src/server-data-source.ts +48 -4
- package/src/server.ts +2 -0
- package/src/sparkline.test.ts +7 -0
- package/src/sparkline.ts +6 -4
- package/src/svgrid-wrapper.types.ts +32 -0
- package/src/svgrid.api.test.ts +21 -0
- package/src/svgrid.charting.test.ts +12 -4
- package/src/svgrid.context-menu.test.ts +12 -0
- package/src/svgrid.detail-rows.svelte.test.ts +187 -0
- package/src/svgrid.filter-menu-scroll.test.ts +9 -0
- package/src/svgrid.gantt-seam.test.ts +1 -1
- package/src/svgrid.live-update-paths.svelte.test.ts +184 -0
- package/src/svgrid.menu-scroll-close.test.ts +10 -0
- package/src/svgrid.row-model-seam.svelte.test.ts +16 -0
- package/src/svgrid.row-pinning.test.ts +15 -1
- package/src/svgrid.selection-bar-seam.test.ts +1 -1
- package/src/svgrid.sticky-groups.svelte.test.ts +145 -0
- package/src/svgrid.upsell-license.test.ts +1 -1
- package/src/test-setup.ts +12 -0
- package/src/transaction.test.ts +58 -0
- package/src/validate.ts +7 -1
- package/dist/cdn/GridMenus-DLL-qZeu.js +0 -635
|
@@ -515,7 +515,11 @@ export function createSvGridController<
|
|
|
515
515
|
// previously declared it twice and open-coded the push in three places.
|
|
516
516
|
type HistoryStep = SharedHistoryStep
|
|
517
517
|
const UNDO_LIMIT = 200
|
|
518
|
-
|
|
518
|
+
// Raw: pushHistory always assigns a fresh array and nothing renders from a
|
|
519
|
+
// step, so deep proxies would cost 200 wrapped objects for nothing. It also
|
|
520
|
+
// keeps a consumer's `tag` the object it passed, which a proxy would not be:
|
|
521
|
+
// the sheet shell compares tags by identity.
|
|
522
|
+
let history = $state.raw<HistoryStep[]>([])
|
|
519
523
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
520
524
|
* undo() decrements; redo() increments. New edits truncate everything
|
|
521
525
|
* past the pointer (the classic "you can't redo after editing" rule). */
|
|
@@ -528,6 +532,9 @@ export function createSvGridController<
|
|
|
528
532
|
* $state: it is written and read synchronously inside a single call and
|
|
529
533
|
* nothing renders from it. */
|
|
530
534
|
let historyGroupId: string | undefined = undefined
|
|
535
|
+
/** The consumer's mark for the steps recorded from now on (`api.setHistoryTag`);
|
|
536
|
+
* not $state for the same reason. */
|
|
537
|
+
let historyTag: unknown = undefined
|
|
531
538
|
|
|
532
539
|
// ---- Hover tooltip (custom popover, not native title=) ---------------
|
|
533
540
|
// Triggered by per-column `tooltip` field OR per-cell `notes` prop.
|
|
@@ -599,7 +606,25 @@ export function createSvGridController<
|
|
|
599
606
|
// catch-all gridStateVersion, so navigation doesn't rebuild 1M rows.
|
|
600
607
|
let dataStateVersion = $state(0);
|
|
601
608
|
const selectionColumnWidth = 44;
|
|
609
|
+
const detailToggleColumnWidth = 36;
|
|
602
610
|
const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
|
|
611
|
+
const showDetailToggleEffective = $derived(props.showDetailToggle === true);
|
|
612
|
+
/**
|
|
613
|
+
* What the chevron on a row does, or null when the row has no detail to
|
|
614
|
+
* open: a detail row itself, a group or placeholder on a server model, a
|
|
615
|
+
* row `hasDetail` rules out, or a grid with nothing to call.
|
|
616
|
+
*/
|
|
617
|
+
function detailToggleOf(row: Row<TData>, rowIndex: number): { open: boolean; toggle: () => void } | null {
|
|
618
|
+
const data = row.original;
|
|
619
|
+
if (props.isDetailRow?.(data, rowIndex)) return null;
|
|
620
|
+
const sg = props.serverGroup;
|
|
621
|
+
if (sg) {
|
|
622
|
+
if (!sg.toggleDetail || sg.isGroup(data) || (sg.hasDetail && !sg.hasDetail(data))) return null;
|
|
623
|
+
return { open: sg.detailOpen?.(data) ?? false, toggle: () => sg.toggleDetail!(data) };
|
|
624
|
+
}
|
|
625
|
+
if (!props.onDetailToggle || (props.hasDetail && !props.hasDetail(data))) return null;
|
|
626
|
+
return { open: props.isDetailOpen?.(data) ?? false, toggle: () => props.onDetailToggle!(data, rowIndex) };
|
|
627
|
+
}
|
|
603
628
|
// A per-row `rowHeight` function already supplies heights, so auto-measuring
|
|
604
629
|
// would fight it. Fixed numbers are fine - they become the pre-measure estimate.
|
|
605
630
|
const autoRowHeightOn = $derived(
|
|
@@ -771,6 +796,16 @@ export function createSvGridController<
|
|
|
771
796
|
((props.selectionMode ?? "both") === "row" ||
|
|
772
797
|
(props.selectionMode ?? "both") === "both"),
|
|
773
798
|
);
|
|
799
|
+
/** The system columns to the left of the data: row numbers, selection, the detail toggle. */
|
|
800
|
+
const systemColumnsWidth = $derived(
|
|
801
|
+
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
802
|
+
(showRowSelectionEffective ? selectionColumnWidth : 0) +
|
|
803
|
+
(showDetailToggleEffective ? detailToggleColumnWidth : 0),
|
|
804
|
+
);
|
|
805
|
+
/** Where the detail-toggle column sticks: after the row numbers and the selection column. */
|
|
806
|
+
const detailToggleColumnLeft = $derived(
|
|
807
|
+
(showRowNumbersEffective ? rowNumberColumnWidth : 0) + (showRowSelectionEffective ? selectionColumnWidth : 0),
|
|
808
|
+
);
|
|
774
809
|
// `selectable` is the shortcut alias, so it wins over the fine-grained prop,
|
|
775
810
|
// which in turn wins over `selectionMode` - the same precedence the other
|
|
776
811
|
// shortcuts use. Unlike them it starts ON (selectionMode defaults to 'both'),
|
|
@@ -890,36 +925,42 @@ export function createSvGridController<
|
|
|
890
925
|
// svelte-ignore state_referenced_locally
|
|
891
926
|
const treeDataConfig = props.treeData;
|
|
892
927
|
|
|
928
|
+
// Pagination is intentionally NOT in the grid's row-model pipeline. The
|
|
929
|
+
// wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
930
|
+
// valueFilters) on top of `grid.getRowModel().rows`. If pagination ran
|
|
931
|
+
// first, those filters would only see the visible page. Instead the wrapper
|
|
932
|
+
// paginates last - see `allRows` below.
|
|
933
|
+
//
|
|
934
|
+
// Built ONCE. Both inputs are captured at mount, and the stages keep state
|
|
935
|
+
// between runs: the sorted and filtered stages hold their last output so a
|
|
936
|
+
// data tick can repair it instead of recomputing (core.ts, `replacedRowsOf`).
|
|
937
|
+
// This used to be a getter that built fresh stages on every read, which
|
|
938
|
+
// threw that cache away on each run - a live feed re-sorted 100k rows on
|
|
939
|
+
// every tick and the row-model memo's `cachedPipeline` never matched.
|
|
940
|
+
const rowModels = {
|
|
941
|
+
coreRowModel: createCoreRowModel<TData>(),
|
|
942
|
+
filteredRowModel: createFilteredRowModel<TData>(),
|
|
943
|
+
// External-sort mode: pass the rows through untouched so the consumer
|
|
944
|
+
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
945
|
+
sortedRowModel: externalSortEnabled
|
|
946
|
+
? passthroughSortedRowModel
|
|
947
|
+
: createSortedRowModel<TData>(sortFns),
|
|
948
|
+
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
949
|
+
// be both a hierarchy node and bucketed under a group banner.
|
|
950
|
+
groupedRowModel: treeDataConfig
|
|
951
|
+
? createTreeRowModel<TData>({
|
|
952
|
+
parentField: treeDataConfig.parentField,
|
|
953
|
+
idField: treeDataConfig.idField,
|
|
954
|
+
})
|
|
955
|
+
: createGroupedRowModel<TData>(),
|
|
956
|
+
expandedRowModel: createExpandedRowModel<TData>(),
|
|
957
|
+
};
|
|
893
958
|
|
|
894
959
|
const grid = createSvGrid({
|
|
895
960
|
get _features() {
|
|
896
961
|
return resolveEffectiveFeatures();
|
|
897
962
|
},
|
|
898
|
-
|
|
899
|
-
// Pagination is intentionally NOT in the grid's row-model pipeline.
|
|
900
|
-
// The wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
901
|
-
// valueFilters) on top of `grid.getRowModel().rows`. If pagination
|
|
902
|
-
// ran first, those filters would only see the visible page. Instead
|
|
903
|
-
// the wrapper paginates last - see `allRows` below.
|
|
904
|
-
return {
|
|
905
|
-
coreRowModel: createCoreRowModel<TData>(),
|
|
906
|
-
filteredRowModel: createFilteredRowModel<TData>(),
|
|
907
|
-
// External-sort mode: pass the rows through untouched so the consumer
|
|
908
|
-
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
909
|
-
sortedRowModel: externalSortEnabled
|
|
910
|
-
? passthroughSortedRowModel
|
|
911
|
-
: createSortedRowModel<TData>(sortFns),
|
|
912
|
-
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
913
|
-
// be both a hierarchy node and bucketed under a group banner.
|
|
914
|
-
groupedRowModel: treeDataConfig
|
|
915
|
-
? createTreeRowModel<TData>({
|
|
916
|
-
parentField: treeDataConfig.parentField,
|
|
917
|
-
idField: treeDataConfig.idField,
|
|
918
|
-
})
|
|
919
|
-
: createGroupedRowModel<TData>(),
|
|
920
|
-
expandedRowModel: createExpandedRowModel<TData>(),
|
|
921
|
-
};
|
|
922
|
-
},
|
|
963
|
+
_rowModels: rowModels,
|
|
923
964
|
get columns() {
|
|
924
965
|
return internalColumns;
|
|
925
966
|
},
|
|
@@ -1384,10 +1425,8 @@ export function createSvGridController<
|
|
|
1384
1425
|
|
|
1385
1426
|
/** Cumulative pixel offsets for left- and right-pinned columns. */
|
|
1386
1427
|
const pinnedOffsets = $derived.by(function pinnedOffsets_d() {
|
|
1387
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
1388
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
1389
1428
|
const left: Record<string, number> = {};
|
|
1390
|
-
let leftAcc =
|
|
1429
|
+
let leftAcc = systemColumnsWidth;
|
|
1391
1430
|
for (const id of effectivePinning.left) {
|
|
1392
1431
|
// Hidden pinned columns don't render, so they must contribute neither an
|
|
1393
1432
|
// offset entry nor width - otherwise the next visible pinned column is
|
|
@@ -1422,7 +1461,7 @@ export function createSvGridController<
|
|
|
1422
1461
|
// index currently hovered; `rowDropSide` says which edge the drop line paints.
|
|
1423
1462
|
let rowDragActive = $state<boolean>(false);
|
|
1424
1463
|
let rowDropIndex = $state<number | null>(null);
|
|
1425
|
-
let rowDropSide = $state<"before" | "after" | null>(null);
|
|
1464
|
+
let rowDropSide = $state<"before" | "after" | "into" | null>(null);
|
|
1426
1465
|
|
|
1427
1466
|
|
|
1428
1467
|
|
|
@@ -1952,17 +1991,16 @@ export function createSvGridController<
|
|
|
1952
1991
|
// so consumers don't see spurious callbacks during re-renders.
|
|
1953
1992
|
let lastCellRangeSerialized = "";
|
|
1954
1993
|
$effect(() => {
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
: [];
|
|
1994
|
+
// Every rectangle, the committed ones a Ctrl+click added and the
|
|
1995
|
+
// active one last, the order getSelectionRects gives: a consumer's
|
|
1996
|
+
// status bar and shading cover the whole selection, not only its last
|
|
1997
|
+
// range. Depend on selectionRanges so a change to the committed set
|
|
1998
|
+
// re-runs this too.
|
|
1999
|
+
void selectionRanges;
|
|
2000
|
+
const ranges: Array<[number, number, number, number]> = getSelectionRects().map(
|
|
2001
|
+
(r: { minRow: number; minCol: number; maxRow: number; maxCol: number }) =>
|
|
2002
|
+
[r.minRow, r.minCol, r.maxRow, r.maxCol] as [number, number, number, number],
|
|
2003
|
+
);
|
|
1966
2004
|
const serialized = JSON.stringify(ranges);
|
|
1967
2005
|
if (serialized === lastCellRangeSerialized) return;
|
|
1968
2006
|
lastCellRangeSerialized = serialized;
|
|
@@ -3063,11 +3101,21 @@ export function createSvGridController<
|
|
|
3063
3101
|
return total;
|
|
3064
3102
|
});
|
|
3065
3103
|
/** The declared or dragged height of one row; measured under auto height. */
|
|
3104
|
+
/** A detail row's declared height, or null when the row is not one (or none is declared). */
|
|
3105
|
+
function detailRowHeightPx(i: number): number | null {
|
|
3106
|
+
const dh = props.detailRowHeight;
|
|
3107
|
+
if (dh == null || !props.isDetailRow) return null;
|
|
3108
|
+
const row = allRows[i];
|
|
3109
|
+
if (!row || !props.isDetailRow(row.original as TData, i)) return null;
|
|
3110
|
+
return typeof dh === "function" ? dh(row.original as TData) : dh;
|
|
3111
|
+
}
|
|
3066
3112
|
function rowSizePxOf(i: number): number {
|
|
3067
3113
|
if (isRowCollapsed(i)) return 0;
|
|
3068
3114
|
if (autoRowHeightOn) return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
3069
3115
|
const own = rowResizeHeightPx(i);
|
|
3070
3116
|
if (own != null) return own;
|
|
3117
|
+
const detail = detailRowHeightPx(i);
|
|
3118
|
+
if (detail != null) return detail;
|
|
3071
3119
|
const rh = props.rowHeight;
|
|
3072
3120
|
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
3073
3121
|
}
|
|
@@ -3077,10 +3125,94 @@ export function createSvGridController<
|
|
|
3077
3125
|
for (let k = 0; k < i; k += 1) top += rowSizePxOf(k);
|
|
3078
3126
|
return top;
|
|
3079
3127
|
}
|
|
3128
|
+
// --- Sticky group rows --------------------------------------------------
|
|
3129
|
+
// The group a row belongs to, kept under the header while its rows scroll
|
|
3130
|
+
// past. The ancestors of the first row under the header are the nearest
|
|
3131
|
+
// group rows above it at each shallower level; under virtualization they
|
|
3132
|
+
// are usually outside the rendered window, so the band renders a copy of
|
|
3133
|
+
// each (its height taken off the top spacer, like the frozen band), and
|
|
3134
|
+
// without virtualization the rows themselves are made sticky.
|
|
3135
|
+
type StickyGroupRow = { row: Row<TData>; rowIndex: number; top: number; size: number };
|
|
3136
|
+
const NO_STICKY: StickyGroupRow[] = [];
|
|
3137
|
+
/** Whether a row is a group and how deep it sits, for any of the three tree sources. */
|
|
3138
|
+
function rowGroupLevel(row: Row<TData>): { group: boolean; level: number } {
|
|
3139
|
+
const sg = props.serverGroup;
|
|
3140
|
+
if (sg) {
|
|
3141
|
+
const data = row.original as TData;
|
|
3142
|
+
return { group: sg.isGroup(data), level: sg.level(data) };
|
|
3143
|
+
}
|
|
3144
|
+
// Client grouping and tree data: an expandable row is the group (a tree
|
|
3145
|
+
// row is a data row too, which is why isGroupRow() excludes it, but for
|
|
3146
|
+
// the band it is the parent its children scroll under).
|
|
3147
|
+
if (typeof row.getCanExpand === "function" && row.getCanExpand()) return { group: true, level: row.depth ?? 0 };
|
|
3148
|
+
return { group: false, level: row.depth ?? 0 };
|
|
3149
|
+
}
|
|
3150
|
+
/** The first row whose bottom edge is under the header, in row-index space. */
|
|
3151
|
+
const firstRowUnderHeader = $derived.by(function firstRowUnderHeader_d() {
|
|
3152
|
+
if (allRows.length === 0) return -1;
|
|
3153
|
+
if (rowVirtualizationEnabled) {
|
|
3154
|
+
virtualizer.version;
|
|
3155
|
+
const offset = virtualizer.getState().scrollOffset;
|
|
3156
|
+
const hit = virtualRows.find((it) => it.end > offset);
|
|
3157
|
+
return hit ? hit.index : -1;
|
|
3158
|
+
}
|
|
3159
|
+
rowResizeVersion;
|
|
3160
|
+
autoRowHeightVersion;
|
|
3161
|
+
const top = scrollMetrics.scrollTop;
|
|
3162
|
+
let acc = 0;
|
|
3163
|
+
let i = 0;
|
|
3164
|
+
while (i < allRows.length - 1 && acc + rowSizePxOf(i) <= top) {
|
|
3165
|
+
acc += rowSizePxOf(i);
|
|
3166
|
+
i += 1;
|
|
3167
|
+
}
|
|
3168
|
+
return i;
|
|
3169
|
+
});
|
|
3170
|
+
const stickyGroupRows = $derived.by(function stickyGroupRows_d(): StickyGroupRow[] {
|
|
3171
|
+
if (!props.stickyGroupRows) return NO_STICKY;
|
|
3172
|
+
const first = firstRowUnderHeader;
|
|
3173
|
+
if (first < 0) return NO_STICKY;
|
|
3174
|
+
const head = allRows[first];
|
|
3175
|
+
if (!head) return NO_STICKY;
|
|
3176
|
+
// A group row at level L, or a leaf at level L, has its ancestors at
|
|
3177
|
+
// levels L-1 .. 0, each the nearest group row above at that level.
|
|
3178
|
+
let want = rowGroupLevel(head).level - 1;
|
|
3179
|
+
if (want < 0) return NO_STICKY;
|
|
3180
|
+
const found: Array<{ row: Row<TData>; rowIndex: number }> = [];
|
|
3181
|
+
for (let i = first - 1; i >= frozenRowCount && want >= 0; i -= 1) {
|
|
3182
|
+
const row = allRows[i]!;
|
|
3183
|
+
const info = rowGroupLevel(row);
|
|
3184
|
+
if (info.group && info.level === want) {
|
|
3185
|
+
found.push({ row, rowIndex: i });
|
|
3186
|
+
want -= 1;
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
if (found.length === 0) return NO_STICKY;
|
|
3190
|
+
found.reverse();
|
|
3191
|
+
rowResizeVersion;
|
|
3192
|
+
autoRowHeightVersion;
|
|
3193
|
+
let top = headerHeight + frozenBandHeight;
|
|
3194
|
+
return found.map((f) => {
|
|
3195
|
+
const at = top;
|
|
3196
|
+
const size = rowSizePxOf(f.rowIndex);
|
|
3197
|
+
top += size;
|
|
3198
|
+
return { ...f, top: at, size };
|
|
3199
|
+
});
|
|
3200
|
+
});
|
|
3201
|
+
/** The band's height under virtualization, taken off the top spacer. */
|
|
3202
|
+
const stickyBandHeight = $derived(
|
|
3203
|
+
rowVirtualizationEnabled ? stickyGroupRows.reduce((sum, s) => sum + s.size, 0) : 0,
|
|
3204
|
+
);
|
|
3205
|
+
/** Without virtualization the real row sticks: its top, or undefined. */
|
|
3206
|
+
function stickyTopOfRow(rowIndex: number): number | undefined {
|
|
3207
|
+
if (rowVirtualizationEnabled || stickyGroupRows.length === 0) return undefined;
|
|
3208
|
+
return stickyGroupRows.find((s) => s.rowIndex === rowIndex)?.top;
|
|
3209
|
+
}
|
|
3210
|
+
|
|
3080
3211
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
3081
|
-
// virtualRowStart / virtualRowBottomSpacer. The frozen band
|
|
3082
|
-
// before the spacer whatever the window shows,
|
|
3083
|
-
|
|
3212
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band and the sticky
|
|
3213
|
+
// group band are in the flow before the spacer whatever the window shows,
|
|
3214
|
+
// so they come off the spacer.
|
|
3215
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight - stickyBandHeight, 0));
|
|
3084
3216
|
const rowBottomSpacer = $derived(
|
|
3085
3217
|
Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0),
|
|
3086
3218
|
);
|
|
@@ -3165,10 +3297,7 @@ export function createSvGridController<
|
|
|
3165
3297
|
* `fittedColumnWidths`, so the overflow decision settles in the same
|
|
3166
3298
|
* render where fit-scaling lands - no race, no scrollbar flash. */
|
|
3167
3299
|
const hasHorizontalOverflow = $derived.by(function hasHorizontalOverflow_d() {
|
|
3168
|
-
|
|
3169
|
-
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
3170
|
-
(showRowSelectionEffective ? selectionColumnWidth : 0);
|
|
3171
|
-
let total = fixedCols;
|
|
3300
|
+
let total = systemColumnsWidth;
|
|
3172
3301
|
for (const column of allColumns) total += getColumnWidth(column.id);
|
|
3173
3302
|
// +1 to tolerate sub-pixel rounding residue from `fitColumns`.
|
|
3174
3303
|
return total > viewportWidth + 1;
|
|
@@ -3620,9 +3749,13 @@ export function createSvGridController<
|
|
|
3620
3749
|
: typeof rh === "function"
|
|
3621
3750
|
? rh
|
|
3622
3751
|
: (rh ?? 30);
|
|
3623
|
-
|
|
3752
|
+
// Detail rows have their own height; only when the props say so does
|
|
3753
|
+
// the per-index lookup replace the uniform fast path.
|
|
3754
|
+
const detailOn = props.detailRowHeight != null && !!props.isDetailRow;
|
|
3755
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0 || detailOn
|
|
3624
3756
|
? (index: number) =>
|
|
3625
3757
|
rowResizeHeightPx(index) ??
|
|
3758
|
+
detailRowHeightPx(index) ??
|
|
3626
3759
|
(typeof base === "function" ? base(index) : base)
|
|
3627
3760
|
: base;
|
|
3628
3761
|
virtualizer.setOptions({
|
|
@@ -3761,6 +3894,14 @@ export function createSvGridController<
|
|
|
3761
3894
|
// the only thing that can answer honestly once the data is a window
|
|
3762
3895
|
// onto a server.
|
|
3763
3896
|
if (props.rowSelectionModel) return props.rowSelectionModel.headerState();
|
|
3897
|
+
// Nothing checked is the common case and needs no walk: this derived
|
|
3898
|
+
// re-runs on every data change, and on a 100k-row live feed the filter
|
|
3899
|
+
// below was 30 ms per tick spent to conclude "none".
|
|
3900
|
+
let anyChecked = false;
|
|
3901
|
+
for (const id in rowSelectionState) {
|
|
3902
|
+
if (rowSelectionState[id]) { anyChecked = true; break; }
|
|
3903
|
+
}
|
|
3904
|
+
if (!anyChecked) return "none";
|
|
3764
3905
|
const selectable = allRows.filter(
|
|
3765
3906
|
(row) => !isGroupRow(row) && !placeholderStateOf(row),
|
|
3766
3907
|
);
|
|
@@ -3810,8 +3951,6 @@ export function createSvGridController<
|
|
|
3810
3951
|
// widths stay in the fit distribution and leave a blank gap at the edge.
|
|
3811
3952
|
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
3812
3953
|
if (!cols.length) return null;
|
|
3813
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
3814
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
3815
3954
|
// Reserve the custom vertical scrollbar's width when it's visible. It
|
|
3816
3955
|
// overlays the right 16px of the viewport (absolute, z-index 40) and
|
|
3817
3956
|
// does NOT shrink clientWidth, so without this the last fitted column
|
|
@@ -3820,8 +3959,7 @@ export function createSvGridController<
|
|
|
3820
3959
|
const scrollbarWidth = hasVerticalOverflow ? 16 : 0;
|
|
3821
3960
|
const target =
|
|
3822
3961
|
(scrollContainer?.clientWidth ?? 0) -
|
|
3823
|
-
|
|
3824
|
-
selectionWidth -
|
|
3962
|
+
systemColumnsWidth -
|
|
3825
3963
|
scrollbarWidth;
|
|
3826
3964
|
if (target <= 0) return null;
|
|
3827
3965
|
|
|
@@ -4319,6 +4457,8 @@ export function createSvGridController<
|
|
|
4319
4457
|
set historyPtr(v) { historyPtr = v as never; },
|
|
4320
4458
|
get historyGroupId() { return historyGroupId; },
|
|
4321
4459
|
set historyGroupId(v) { historyGroupId = v as never; },
|
|
4460
|
+
get historyTag() { return historyTag; },
|
|
4461
|
+
set historyTag(v) { historyTag = v; },
|
|
4322
4462
|
get historyVersion() { return historyVersion; },
|
|
4323
4463
|
set historyVersion(v) { historyVersion = v as never; },
|
|
4324
4464
|
get tooltip() { return tooltip; },
|
|
@@ -4355,6 +4495,11 @@ export function createSvGridController<
|
|
|
4355
4495
|
get gridStateVersion() { return gridStateVersion; },
|
|
4356
4496
|
set gridStateVersion(v) { gridStateVersion = v as never; },
|
|
4357
4497
|
get selectionColumnWidth() { return selectionColumnWidth; },
|
|
4498
|
+
get detailToggleColumnWidth() { return detailToggleColumnWidth; },
|
|
4499
|
+
get detailToggleColumnLeft() { return detailToggleColumnLeft; },
|
|
4500
|
+
get showDetailToggleEffective() { return showDetailToggleEffective; },
|
|
4501
|
+
get systemColumnsWidth() { return systemColumnsWidth; },
|
|
4502
|
+
get detailToggleOf() { return detailToggleOf; },
|
|
4358
4503
|
get rowNumberColumnWidth() { return rowNumberColumnWidth; },
|
|
4359
4504
|
get showRowNumbersEffective() { return showRowNumbersEffective; },
|
|
4360
4505
|
get filterOperatorOptions() { return filterOperatorOptions; },
|
|
@@ -4772,6 +4917,8 @@ export function createSvGridController<
|
|
|
4772
4917
|
get frozenRowList() { return frozenRowList; },
|
|
4773
4918
|
get frozenBandHeight() { return frozenBandHeight; },
|
|
4774
4919
|
get frozenRowTop() { return frozenRowTop; },
|
|
4920
|
+
get stickyGroupRows() { return stickyGroupRows; },
|
|
4921
|
+
get stickyTopOfRow() { return stickyTopOfRow; },
|
|
4775
4922
|
/** Record the height the user dragged a row to. */
|
|
4776
4923
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4777
4924
|
get isRowCollapsed() { return isRowCollapsed; },
|
|
@@ -4784,6 +4931,12 @@ export function createSvGridController<
|
|
|
4784
4931
|
rowResizeVersion;
|
|
4785
4932
|
return rowResizeHeightPx;
|
|
4786
4933
|
},
|
|
4934
|
+
/** The height row i is drawn at: dragged, measured, detail, declared or default. */
|
|
4935
|
+
get rowSizePxOf() {
|
|
4936
|
+
rowResizeVersion;
|
|
4937
|
+
autoRowHeightVersion;
|
|
4938
|
+
return rowSizePxOf;
|
|
4939
|
+
},
|
|
4787
4940
|
get virtualRowTotalSize() { return virtualRowTotalSize; },
|
|
4788
4941
|
get virtualRowStart() { return virtualRowStart; },
|
|
4789
4942
|
get virtualRowEnd() { return virtualRowEnd; },
|
package/src/SvGrid.css
CHANGED
|
@@ -535,6 +535,10 @@
|
|
|
535
535
|
.sv-grid-placeholder-retry:hover {
|
|
536
536
|
background: color-mix(in srgb, var(--sg-fg, #0f172a) 6%, transparent);
|
|
537
537
|
}
|
|
538
|
+
.sv-grid-placeholder-retry:focus-visible {
|
|
539
|
+
outline: 2px solid var(--sg-accent, #2563eb);
|
|
540
|
+
outline-offset: 2px;
|
|
541
|
+
}
|
|
538
542
|
|
|
539
543
|
/* Pinned columns read as "frozen" via three layered cues:
|
|
540
544
|
1. A distinct fill (slightly stronger than the header) so the
|
|
@@ -813,6 +817,12 @@
|
|
|
813
817
|
.sv-grid-row-drop-after > td {
|
|
814
818
|
box-shadow: inset 0 -2px 0 0 var(--sg-accent, #6366f1);
|
|
815
819
|
}
|
|
820
|
+
/* Landing inside a group or tree row (the row becomes its child): the
|
|
821
|
+
* whole row is the target, so it takes a tint and a ring rather than a line. */
|
|
822
|
+
.sv-grid-row-drop-into > td {
|
|
823
|
+
box-shadow: inset 0 2px 0 0 var(--sg-accent, #6366f1), inset 0 -2px 0 0 var(--sg-accent, #6366f1);
|
|
824
|
+
background: color-mix(in srgb, var(--sg-accent, #6366f1) 10%, transparent);
|
|
825
|
+
}
|
|
816
826
|
|
|
817
827
|
/* Pinned rows (top / bottom). Each cell sticks individually so the
|
|
818
828
|
* row tracks scroll on every browser. Top rows stick below the sticky
|
|
@@ -837,6 +847,27 @@
|
|
|
837
847
|
.sv-grid-pinned-row-bottom[data-pinned-index="1"] td { bottom: var(--sg-pinned-row-h, 32px); }
|
|
838
848
|
.sv-grid-pinned-row-bottom[data-pinned-index="2"] td { bottom: calc(var(--sg-pinned-row-h, 32px) * 2); }
|
|
839
849
|
.sv-grid-pinned-row-bottom[data-pinned-index="3"] td { bottom: calc(var(--sg-pinned-row-h, 32px) * 3); }
|
|
850
|
+
/* While the selection bar is up the scroller carries bottom padding (see
|
|
851
|
+
--sg-selbar-reserve), and a sticky bottom edge is inset by that padding:
|
|
852
|
+
the lowest pinned row then holds 68px above the viewport's edge, with the
|
|
853
|
+
body rows scrolling through the strip beneath it, in and out from behind
|
|
854
|
+
the bar. That strip is meant to be the bar's own ground, so the lowest
|
|
855
|
+
pinned row paints it: a shadow in the body colour, spread by half the
|
|
856
|
+
reserve and pushed down by the same, fills the strip under each cell; the
|
|
857
|
+
clip cuts the spread's sideways spill at the cell's own edges so a later
|
|
858
|
+
cell's shadow never paints over its neighbour. The cells sit at z-index 4,
|
|
859
|
+
above the rows and under the floating bar. */
|
|
860
|
+
.sv-grid-root[data-selbar$='bottom'] .sv-grid-pinned-row-bottom[data-pinned-index="0"] td {
|
|
861
|
+
box-shadow: 0 calc(var(--sg-selbar-reserve) / 2) 0 calc(var(--sg-selbar-reserve) / 2) var(--sg-bg, #fff);
|
|
862
|
+
clip-path: inset(0 0 calc(-1 * var(--sg-selbar-reserve)) 0);
|
|
863
|
+
}
|
|
864
|
+
/* The same strip above a sticky header when the bar sits at the top: the
|
|
865
|
+
header holds under the reserve and rows scrolled up showed through above
|
|
866
|
+
it. The first header row's cells paint the strip the same way. */
|
|
867
|
+
.sv-grid-root[data-selbar$='top'] .sv-grid-head > tr:first-child > th {
|
|
868
|
+
box-shadow: 0 calc(-1 * var(--sg-selbar-reserve) / 2) 0 calc(var(--sg-selbar-reserve) / 2) var(--sg-bg, #fff);
|
|
869
|
+
clip-path: inset(calc(-1 * var(--sg-selbar-reserve)) 0 0 0);
|
|
870
|
+
}
|
|
840
871
|
/* Sticky-left columns keep their left position; just override their
|
|
841
872
|
* z so they sit above non-pinned pinned-row cells. */
|
|
842
873
|
.sv-grid-pinned-row td[data-pinned] { z-index: 5; }
|
|
@@ -1018,13 +1049,58 @@
|
|
|
1018
1049
|
z-index: 4;
|
|
1019
1050
|
}
|
|
1020
1051
|
|
|
1021
|
-
/*
|
|
1052
|
+
/* Detail-toggle column (`showDetailToggle`): a row-header gutter of chevrons
|
|
1053
|
+
that open and close detail rows. Sticky beside the row numbers and the
|
|
1054
|
+
selection column, on the header band's colour so it reads as chrome, not
|
|
1055
|
+
data; no menu, no resize handle, outside the active cell. */
|
|
1056
|
+
.sv-grid-detail-toggle-column,
|
|
1057
|
+
.sv-grid-detail-toggle-cell {
|
|
1058
|
+
text-align: center;
|
|
1059
|
+
padding: 0;
|
|
1060
|
+
position: sticky;
|
|
1061
|
+
z-index: 4;
|
|
1062
|
+
background: var(--sg-header-bg, #f5f7fb);
|
|
1063
|
+
vertical-align: middle;
|
|
1064
|
+
}
|
|
1065
|
+
.sv-grid-detail-toggle {
|
|
1066
|
+
display: inline-flex;
|
|
1067
|
+
align-items: center;
|
|
1068
|
+
justify-content: center;
|
|
1069
|
+
width: 24px;
|
|
1070
|
+
height: 24px;
|
|
1071
|
+
padding: 0;
|
|
1072
|
+
border: 0;
|
|
1073
|
+
border-radius: 5px;
|
|
1074
|
+
background: transparent;
|
|
1075
|
+
color: var(--sg-muted, #64748b);
|
|
1076
|
+
cursor: pointer;
|
|
1077
|
+
vertical-align: middle;
|
|
1078
|
+
}
|
|
1079
|
+
.sv-grid-detail-toggle:hover {
|
|
1080
|
+
background: color-mix(in srgb, var(--sg-accent, #2563eb) 12%, transparent);
|
|
1081
|
+
color: var(--sg-accent, #2563eb);
|
|
1082
|
+
}
|
|
1083
|
+
.sv-grid-detail-toggle:focus-visible {
|
|
1084
|
+
outline: 2px solid var(--sg-accent, #2563eb);
|
|
1085
|
+
outline-offset: 1px;
|
|
1086
|
+
}
|
|
1087
|
+
.sv-grid-detail-toggle > .sv-grid-icon {
|
|
1088
|
+
width: 12px;
|
|
1089
|
+
height: 12px;
|
|
1090
|
+
transition: transform 0.15s ease;
|
|
1091
|
+
}
|
|
1092
|
+
.sv-grid-detail-toggle[aria-expanded="true"] > .sv-grid-icon {
|
|
1093
|
+
transform: rotate(90deg);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/* Row-number column: sticks to the inline-start edge of the scroll viewport,
|
|
1097
|
+
which is the right edge under `dir="rtl"`. */
|
|
1022
1098
|
.sv-grid-row-number-column,
|
|
1023
1099
|
.sv-grid-row-number-cell {
|
|
1024
1100
|
text-align: right;
|
|
1025
1101
|
padding-inline: 8px;
|
|
1026
1102
|
position: sticky;
|
|
1027
|
-
|
|
1103
|
+
inset-inline-start: 0;
|
|
1028
1104
|
z-index: 4;
|
|
1029
1105
|
/* Match the column-header band so the row-number gutter and the column
|
|
1030
1106
|
headers form one continuous header chrome. */
|
|
@@ -1327,6 +1403,15 @@ select.sv-grid-fr-editor {
|
|
|
1327
1403
|
overflow: hidden so the chips picker would otherwise be clipped). */
|
|
1328
1404
|
overflow: visible;
|
|
1329
1405
|
}
|
|
1406
|
+
/* The active cell a grid mounts with is seeded at (0,0), not chosen: no
|
|
1407
|
+
ring until a click, an arrow key or the api activates a cell (the same
|
|
1408
|
+
gate the fill handle has always had). Once activated it stays through a
|
|
1409
|
+
blur, so a toolbar acting on "the focused row" keeps its cue. A page with
|
|
1410
|
+
six grids on it, or a detail grid inside each open row, used to open
|
|
1411
|
+
with an accent ring in every one of them. */
|
|
1412
|
+
.sv-grid-active-seeded .sv-grid-cell-active {
|
|
1413
|
+
box-shadow: none;
|
|
1414
|
+
}
|
|
1330
1415
|
|
|
1331
1416
|
/* ---- The editing cell's border -------------------------------------------
|
|
1332
1417
|
One ring for every editor, drawn by the CELL rather than by each editor.
|
|
@@ -1625,6 +1710,40 @@ select.sv-grid-fr-editor {
|
|
|
1625
1710
|
border-bottom: 1px solid var(--sg-frozen-edge, var(--sg-border, #94a3b8));
|
|
1626
1711
|
}
|
|
1627
1712
|
|
|
1713
|
+
/* A detail row with a declared height (detailRowHeight): the row keeps the
|
|
1714
|
+
height the virtualizer counted on - the auto-height rule above must not
|
|
1715
|
+
win - and the panel's body, a block inside the cell, takes that height
|
|
1716
|
+
and scrolls what does not fit. (A table cell's own height is only a
|
|
1717
|
+
minimum, so the height goes on the block.) */
|
|
1718
|
+
.sv-grid-row.sv-grid-detail-row.sv-grid-detail-row-sized {
|
|
1719
|
+
height: var(--sv-detail-h) !important;
|
|
1720
|
+
}
|
|
1721
|
+
.sv-grid-row.sv-grid-detail-row-sized > .sv-grid-detail-cell {
|
|
1722
|
+
vertical-align: top;
|
|
1723
|
+
box-sizing: border-box;
|
|
1724
|
+
height: var(--sv-detail-h);
|
|
1725
|
+
}
|
|
1726
|
+
.sv-grid-detail-body {
|
|
1727
|
+
box-sizing: border-box;
|
|
1728
|
+
height: 100%;
|
|
1729
|
+
overflow: auto;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
/* Sticky group rows: the group a row belongs to, held under the header
|
|
1733
|
+
(and under the frozen band) while its rows scroll past. Same layer as
|
|
1734
|
+
the frozen band; the last one carries the band's edge. */
|
|
1735
|
+
.sv-grid-row.sv-grid-row-sticky-group {
|
|
1736
|
+
position: sticky;
|
|
1737
|
+
z-index: 31;
|
|
1738
|
+
background: var(--sg-bg, #fff);
|
|
1739
|
+
}
|
|
1740
|
+
.sv-grid-row.sv-grid-row-sticky-group > .sv-grid-cell {
|
|
1741
|
+
background: var(--sg-bg, #fff);
|
|
1742
|
+
}
|
|
1743
|
+
.sv-grid-row.sv-grid-row-sticky-group-last > .sv-grid-cell {
|
|
1744
|
+
border-bottom: 1px solid var(--sg-frozen-edge, var(--sg-border, #94a3b8));
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1628
1747
|
/* The multiline text editor sizes itself by its lines and keeps the
|
|
1629
1748
|
input's single-line look until a break is typed. */
|
|
1630
1749
|
.sv-grid-cell-editor-multiline {
|
|
@@ -3082,6 +3201,12 @@ select.sv-grid-fr-editor {
|
|
|
3082
3201
|
.sv-grid-row-number-cell {
|
|
3083
3202
|
border-right-color: var(--sg-header-bg, #f1f5f9);
|
|
3084
3203
|
}
|
|
3204
|
+
/* The detail-toggle gutter fuses with the row numbers the same way; only
|
|
3205
|
+
its own right edge keeps a line, between the chrome and the data. */
|
|
3206
|
+
.sv-grid-row-number-cell:has(+ .sv-grid-detail-toggle-cell),
|
|
3207
|
+
.sv-grid-row-number-column:has(+ .sv-grid-detail-toggle-column) {
|
|
3208
|
+
border-right-color: var(--sg-header-bg, #f1f5f9);
|
|
3209
|
+
}
|
|
3085
3210
|
|
|
3086
3211
|
/* Group row label colour. */
|
|
3087
3212
|
.sv-grid-group-row > .sv-grid-cell {
|