@svgrid/grid 3.0.5 → 3.0.7
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 +23 -3
- package/dist/SvGrid.controller.svelte.js +235 -54
- package/dist/SvGrid.css +143 -7
- package/dist/SvGrid.helpers.js +2 -4
- 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-BgUS-W3o.js} +151 -142
- package/dist/cdn/GridMenus-CcwU8I4t.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-9yz-SbdZ.js} +2 -2
- package/dist/cdn/{SvGridCellEditor-D_0q4xAu.js → SvGridCellEditor-D7-ql1Hd.js} +2 -2
- package/dist/cdn/{SvGridChart-C3EWAZaM.js → SvGridChart-DMUOFxOe.js} +4 -4
- package/dist/cdn/{SvGridChart-DzLkSwCH.js → SvGridChart-N7ik0oWy.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-CfII62sZ.js → SvGridChartBuilder-CB_Jnm2k.js} +4 -4
- package/dist/cdn/{SvGridChartBuilder-BE2T1ykB.js → SvGridChartBuilder-Ca7gF8ZK.js} +4 -4
- package/dist/cdn/{SvGridChartPanel-xf4sWVTo.js → SvGridChartPanel-9GOJRHP7.js} +5 -5
- package/dist/cdn/{SvGridChartPanel-CCX5_Wgd.js → SvGridChartPanel-Dl-FujN8.js} +5 -5
- package/dist/cdn/{SvGridChartView-CfuXmY5I.js → SvGridChartView-B1LFgoPP.js} +2 -2
- package/dist/cdn/{SvGridChartView-DYwabQWj.js → SvGridChartView-By3n7DGZ.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-Mnv84zoE.js} +28 -6
- package/dist/cdn/{chart-Dz7SqMXH.js → chart-By7h2Guv.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CUbf2R4i.js → chart-panel-messages-BC-tOgFY.js} +1 -1
- package/dist/cdn/{chart-panel-messages-CmNrMdsr.js → chart-panel-messages-DUaXr-sb.js} +1 -1
- package/dist/cdn/{chart-summary-BJW_tg_X.js → chart-summary-BDtrNEiA.js} +1 -1
- package/dist/cdn/{chart-trend-CaN9mDEV.js → chart-trend-C1aOAPBZ.js} +1 -1
- package/dist/cdn/{dismissable-DAHetSNk.js → dismissable-Brc4EZU1.js} +5 -1
- package/dist/cdn/{editor-registry-BhgE3S84.js → editor-registry-BVeHvlZR.js} +49 -50
- package/dist/cdn/{export-format-CUDLy2yn.js → export-format-y1wdyCcv.js} +93 -97
- 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-25JmJ_7g.js} +4768 -4149
- package/dist/cdn/{src-Bklofsad.js → src-kE9B98r6.js} +4796 -4177
- package/dist/cdn/svgrid.js +18 -18
- package/dist/cdn/svgrid.svelte-external.js +18 -18
- package/dist/cdn/{validate-C7Rn_Bns.js → validate-AZoD8BoR.js} +6 -2
- package/dist/cell-formatting.d.ts +9 -0
- package/dist/cell-formatting.js +52 -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 +478 -40
- package/dist/editing.js +24 -0
- package/dist/export-format.d.ts +8 -0
- package/dist/export-format.js +23 -16
- 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/dist/virtualization/column-virtualizer.d.ts +1 -0
- package/dist/virtualization/svelte-virtualizer.svelte.d.ts +1 -0
- package/dist/virtualization/svelte-virtualizer.svelte.js +1 -0
- package/dist/virtualization/virtualizer.d.ts +3 -0
- package/dist/virtualization/virtualizer.js +14 -1
- package/package.json +6 -1
- package/src/GridMenus.svelte +34 -4
- package/src/SvGrid.controller.svelte.ts +236 -57
- package/src/SvGrid.css +143 -7
- package/src/SvGrid.helpers.ts +2 -4
- 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 +49 -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.sort.test.ts +86 -0
- package/src/core.tick-repair.test.ts +287 -0
- package/src/core.ts +478 -40
- package/src/editing.test.ts +52 -0
- package/src/editing.ts +25 -0
- package/src/export-dates.test.ts +114 -0
- package/src/export-format.ts +26 -14
- 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.data-push.svelte.test.ts +94 -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 +186 -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/src/virtualization/svelte-virtualizer.svelte.ts +1 -0
- package/src/virtualization/virtualizer.test.ts +44 -0
- package/src/virtualization/virtualizer.ts +13 -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'),
|
|
@@ -810,7 +845,18 @@ export function createSvGridController<
|
|
|
810
845
|
// `data` is optional on the type (a `rowModel` may supply the rows
|
|
811
846
|
// instead), so every read falls back to empty rather than to undefined.
|
|
812
847
|
const EMPTY_DATA: ReadonlyArray<TData> = [];
|
|
813
|
-
|
|
848
|
+
// Seeded as a copy, for the same reason the sync effect below makes one: the
|
|
849
|
+
// row model tells data apart by array identity, so the grid has to own an
|
|
850
|
+
// array the consumer cannot grow behind its back.
|
|
851
|
+
let internalData = $state.raw<ReadonlyArray<TData>>(
|
|
852
|
+
props.data?.length ? props.data.slice() : EMPTY_DATA,
|
|
853
|
+
);
|
|
854
|
+
// What the last sync saw, so the effect can tell a replacement (drop the
|
|
855
|
+
// cell edits) from a push or splice (keep them) and skip a no-op run.
|
|
856
|
+
// svelte-ignore state_referenced_locally
|
|
857
|
+
let lastData: ReadonlyArray<TData> = props.data ?? EMPTY_DATA;
|
|
858
|
+
// svelte-ignore state_referenced_locally
|
|
859
|
+
let lastDataLength = props.data?.length ?? 0;
|
|
814
860
|
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
815
861
|
// format defaults once, up front, so every downstream reader sees a normal
|
|
816
862
|
// column. Explicit fields on the ColumnDef always win.
|
|
@@ -858,12 +904,27 @@ export function createSvGridController<
|
|
|
858
904
|
}
|
|
859
905
|
|
|
860
906
|
$effect(() => {
|
|
907
|
+
const next = props.data ?? EMPTY_DATA;
|
|
908
|
+
// Reading `length` is what subscribes this effect to a `$state` array's own
|
|
909
|
+
// length signal, so `rows.push(...)` and `rows.splice(...)` re-run it.
|
|
910
|
+
// Without this read nothing in the grid tracks the row count at all:
|
|
911
|
+
// `getRowModel` reads `.length` only inside its cache miss, and the miss is
|
|
912
|
+
// keyed on the array reference, which a mutation does not change.
|
|
913
|
+
const length = next.length;
|
|
914
|
+
const replaced = next !== lastData;
|
|
915
|
+
if (!replaced && length === lastDataLength) return;
|
|
916
|
+
lastData = next;
|
|
917
|
+
lastDataLength = length;
|
|
918
|
+
// A fresh array object, because `getRowModel` memoises on identity. The
|
|
919
|
+
// elements are the same objects, so a row that did not move keeps its row
|
|
920
|
+
// object and a field write still reaches its cell through the proxy.
|
|
921
|
+
internalData = length === 0 ? EMPTY_DATA : next.slice();
|
|
861
922
|
// When the consumer replaces `data` (e.g. a "Reset" button), drop any
|
|
862
923
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
863
924
|
// would keep returning the old edited values from `editedCellValues`
|
|
864
|
-
// even though the underlying data has been replaced.
|
|
865
|
-
|
|
866
|
-
editedCellValues = {};
|
|
925
|
+
// even though the underlying data has been replaced. Growing or shrinking
|
|
926
|
+
// the array is not a reset, so it keeps them.
|
|
927
|
+
if (replaced) editedCellValues = {};
|
|
867
928
|
});
|
|
868
929
|
$effect(() => {
|
|
869
930
|
internalColumns = resolveCols(props.columns);
|
|
@@ -890,36 +951,42 @@ export function createSvGridController<
|
|
|
890
951
|
// svelte-ignore state_referenced_locally
|
|
891
952
|
const treeDataConfig = props.treeData;
|
|
892
953
|
|
|
954
|
+
// Pagination is intentionally NOT in the grid's row-model pipeline. The
|
|
955
|
+
// wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
956
|
+
// valueFilters) on top of `grid.getRowModel().rows`. If pagination ran
|
|
957
|
+
// first, those filters would only see the visible page. Instead the wrapper
|
|
958
|
+
// paginates last - see `allRows` below.
|
|
959
|
+
//
|
|
960
|
+
// Built ONCE. Both inputs are captured at mount, and the stages keep state
|
|
961
|
+
// between runs: the sorted and filtered stages hold their last output so a
|
|
962
|
+
// data tick can repair it instead of recomputing (core.ts, `replacedRowsOf`).
|
|
963
|
+
// This used to be a getter that built fresh stages on every read, which
|
|
964
|
+
// threw that cache away on each run - a live feed re-sorted 100k rows on
|
|
965
|
+
// every tick and the row-model memo's `cachedPipeline` never matched.
|
|
966
|
+
const rowModels = {
|
|
967
|
+
coreRowModel: createCoreRowModel<TData>(),
|
|
968
|
+
filteredRowModel: createFilteredRowModel<TData>(),
|
|
969
|
+
// External-sort mode: pass the rows through untouched so the consumer
|
|
970
|
+
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
971
|
+
sortedRowModel: externalSortEnabled
|
|
972
|
+
? passthroughSortedRowModel
|
|
973
|
+
: createSortedRowModel<TData>(sortFns),
|
|
974
|
+
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
975
|
+
// be both a hierarchy node and bucketed under a group banner.
|
|
976
|
+
groupedRowModel: treeDataConfig
|
|
977
|
+
? createTreeRowModel<TData>({
|
|
978
|
+
parentField: treeDataConfig.parentField,
|
|
979
|
+
idField: treeDataConfig.idField,
|
|
980
|
+
})
|
|
981
|
+
: createGroupedRowModel<TData>(),
|
|
982
|
+
expandedRowModel: createExpandedRowModel<TData>(),
|
|
983
|
+
};
|
|
893
984
|
|
|
894
985
|
const grid = createSvGrid({
|
|
895
986
|
get _features() {
|
|
896
987
|
return resolveEffectiveFeatures();
|
|
897
988
|
},
|
|
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
|
-
},
|
|
989
|
+
_rowModels: rowModels,
|
|
923
990
|
get columns() {
|
|
924
991
|
return internalColumns;
|
|
925
992
|
},
|
|
@@ -1384,10 +1451,8 @@ export function createSvGridController<
|
|
|
1384
1451
|
|
|
1385
1452
|
/** Cumulative pixel offsets for left- and right-pinned columns. */
|
|
1386
1453
|
const pinnedOffsets = $derived.by(function pinnedOffsets_d() {
|
|
1387
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
1388
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
1389
1454
|
const left: Record<string, number> = {};
|
|
1390
|
-
let leftAcc =
|
|
1455
|
+
let leftAcc = systemColumnsWidth;
|
|
1391
1456
|
for (const id of effectivePinning.left) {
|
|
1392
1457
|
// Hidden pinned columns don't render, so they must contribute neither an
|
|
1393
1458
|
// offset entry nor width - otherwise the next visible pinned column is
|
|
@@ -1422,7 +1487,7 @@ export function createSvGridController<
|
|
|
1422
1487
|
// index currently hovered; `rowDropSide` says which edge the drop line paints.
|
|
1423
1488
|
let rowDragActive = $state<boolean>(false);
|
|
1424
1489
|
let rowDropIndex = $state<number | null>(null);
|
|
1425
|
-
let rowDropSide = $state<"before" | "after" | null>(null);
|
|
1490
|
+
let rowDropSide = $state<"before" | "after" | "into" | null>(null);
|
|
1426
1491
|
|
|
1427
1492
|
|
|
1428
1493
|
|
|
@@ -1952,17 +2017,16 @@ export function createSvGridController<
|
|
|
1952
2017
|
// so consumers don't see spurious callbacks during re-renders.
|
|
1953
2018
|
let lastCellRangeSerialized = "";
|
|
1954
2019
|
$effect(() => {
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
: [];
|
|
2020
|
+
// Every rectangle, the committed ones a Ctrl+click added and the
|
|
2021
|
+
// active one last, the order getSelectionRects gives: a consumer's
|
|
2022
|
+
// status bar and shading cover the whole selection, not only its last
|
|
2023
|
+
// range. Depend on selectionRanges so a change to the committed set
|
|
2024
|
+
// re-runs this too.
|
|
2025
|
+
void selectionRanges;
|
|
2026
|
+
const ranges: Array<[number, number, number, number]> = getSelectionRects().map(
|
|
2027
|
+
(r: { minRow: number; minCol: number; maxRow: number; maxCol: number }) =>
|
|
2028
|
+
[r.minRow, r.minCol, r.maxRow, r.maxCol] as [number, number, number, number],
|
|
2029
|
+
);
|
|
1966
2030
|
const serialized = JSON.stringify(ranges);
|
|
1967
2031
|
if (serialized === lastCellRangeSerialized) return;
|
|
1968
2032
|
lastCellRangeSerialized = serialized;
|
|
@@ -3063,11 +3127,21 @@ export function createSvGridController<
|
|
|
3063
3127
|
return total;
|
|
3064
3128
|
});
|
|
3065
3129
|
/** The declared or dragged height of one row; measured under auto height. */
|
|
3130
|
+
/** A detail row's declared height, or null when the row is not one (or none is declared). */
|
|
3131
|
+
function detailRowHeightPx(i: number): number | null {
|
|
3132
|
+
const dh = props.detailRowHeight;
|
|
3133
|
+
if (dh == null || !props.isDetailRow) return null;
|
|
3134
|
+
const row = allRows[i];
|
|
3135
|
+
if (!row || !props.isDetailRow(row.original as TData, i)) return null;
|
|
3136
|
+
return typeof dh === "function" ? dh(row.original as TData) : dh;
|
|
3137
|
+
}
|
|
3066
3138
|
function rowSizePxOf(i: number): number {
|
|
3067
3139
|
if (isRowCollapsed(i)) return 0;
|
|
3068
3140
|
if (autoRowHeightOn) return measuredRowHeights.get(i) ?? autoRowHeightFallback;
|
|
3069
3141
|
const own = rowResizeHeightPx(i);
|
|
3070
3142
|
if (own != null) return own;
|
|
3143
|
+
const detail = detailRowHeightPx(i);
|
|
3144
|
+
if (detail != null) return detail;
|
|
3071
3145
|
const rh = props.rowHeight;
|
|
3072
3146
|
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
3073
3147
|
}
|
|
@@ -3077,10 +3151,94 @@ export function createSvGridController<
|
|
|
3077
3151
|
for (let k = 0; k < i; k += 1) top += rowSizePxOf(k);
|
|
3078
3152
|
return top;
|
|
3079
3153
|
}
|
|
3154
|
+
// --- Sticky group rows --------------------------------------------------
|
|
3155
|
+
// The group a row belongs to, kept under the header while its rows scroll
|
|
3156
|
+
// past. The ancestors of the first row under the header are the nearest
|
|
3157
|
+
// group rows above it at each shallower level; under virtualization they
|
|
3158
|
+
// are usually outside the rendered window, so the band renders a copy of
|
|
3159
|
+
// each (its height taken off the top spacer, like the frozen band), and
|
|
3160
|
+
// without virtualization the rows themselves are made sticky.
|
|
3161
|
+
type StickyGroupRow = { row: Row<TData>; rowIndex: number; top: number; size: number };
|
|
3162
|
+
const NO_STICKY: StickyGroupRow[] = [];
|
|
3163
|
+
/** Whether a row is a group and how deep it sits, for any of the three tree sources. */
|
|
3164
|
+
function rowGroupLevel(row: Row<TData>): { group: boolean; level: number } {
|
|
3165
|
+
const sg = props.serverGroup;
|
|
3166
|
+
if (sg) {
|
|
3167
|
+
const data = row.original as TData;
|
|
3168
|
+
return { group: sg.isGroup(data), level: sg.level(data) };
|
|
3169
|
+
}
|
|
3170
|
+
// Client grouping and tree data: an expandable row is the group (a tree
|
|
3171
|
+
// row is a data row too, which is why isGroupRow() excludes it, but for
|
|
3172
|
+
// the band it is the parent its children scroll under).
|
|
3173
|
+
if (typeof row.getCanExpand === "function" && row.getCanExpand()) return { group: true, level: row.depth ?? 0 };
|
|
3174
|
+
return { group: false, level: row.depth ?? 0 };
|
|
3175
|
+
}
|
|
3176
|
+
/** The first row whose bottom edge is under the header, in row-index space. */
|
|
3177
|
+
const firstRowUnderHeader = $derived.by(function firstRowUnderHeader_d() {
|
|
3178
|
+
if (allRows.length === 0) return -1;
|
|
3179
|
+
if (rowVirtualizationEnabled) {
|
|
3180
|
+
virtualizer.version;
|
|
3181
|
+
const offset = virtualizer.getState().scrollOffset;
|
|
3182
|
+
const hit = virtualRows.find((it) => it.end > offset);
|
|
3183
|
+
return hit ? hit.index : -1;
|
|
3184
|
+
}
|
|
3185
|
+
rowResizeVersion;
|
|
3186
|
+
autoRowHeightVersion;
|
|
3187
|
+
const top = scrollMetrics.scrollTop;
|
|
3188
|
+
let acc = 0;
|
|
3189
|
+
let i = 0;
|
|
3190
|
+
while (i < allRows.length - 1 && acc + rowSizePxOf(i) <= top) {
|
|
3191
|
+
acc += rowSizePxOf(i);
|
|
3192
|
+
i += 1;
|
|
3193
|
+
}
|
|
3194
|
+
return i;
|
|
3195
|
+
});
|
|
3196
|
+
const stickyGroupRows = $derived.by(function stickyGroupRows_d(): StickyGroupRow[] {
|
|
3197
|
+
if (!props.stickyGroupRows) return NO_STICKY;
|
|
3198
|
+
const first = firstRowUnderHeader;
|
|
3199
|
+
if (first < 0) return NO_STICKY;
|
|
3200
|
+
const head = allRows[first];
|
|
3201
|
+
if (!head) return NO_STICKY;
|
|
3202
|
+
// A group row at level L, or a leaf at level L, has its ancestors at
|
|
3203
|
+
// levels L-1 .. 0, each the nearest group row above at that level.
|
|
3204
|
+
let want = rowGroupLevel(head).level - 1;
|
|
3205
|
+
if (want < 0) return NO_STICKY;
|
|
3206
|
+
const found: Array<{ row: Row<TData>; rowIndex: number }> = [];
|
|
3207
|
+
for (let i = first - 1; i >= frozenRowCount && want >= 0; i -= 1) {
|
|
3208
|
+
const row = allRows[i]!;
|
|
3209
|
+
const info = rowGroupLevel(row);
|
|
3210
|
+
if (info.group && info.level === want) {
|
|
3211
|
+
found.push({ row, rowIndex: i });
|
|
3212
|
+
want -= 1;
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
if (found.length === 0) return NO_STICKY;
|
|
3216
|
+
found.reverse();
|
|
3217
|
+
rowResizeVersion;
|
|
3218
|
+
autoRowHeightVersion;
|
|
3219
|
+
let top = headerHeight + frozenBandHeight;
|
|
3220
|
+
return found.map((f) => {
|
|
3221
|
+
const at = top;
|
|
3222
|
+
const size = rowSizePxOf(f.rowIndex);
|
|
3223
|
+
top += size;
|
|
3224
|
+
return { ...f, top: at, size };
|
|
3225
|
+
});
|
|
3226
|
+
});
|
|
3227
|
+
/** The band's height under virtualization, taken off the top spacer. */
|
|
3228
|
+
const stickyBandHeight = $derived(
|
|
3229
|
+
rowVirtualizationEnabled ? stickyGroupRows.reduce((sum, s) => sum + s.size, 0) : 0,
|
|
3230
|
+
);
|
|
3231
|
+
/** Without virtualization the real row sticks: its top, or undefined. */
|
|
3232
|
+
function stickyTopOfRow(rowIndex: number): number | undefined {
|
|
3233
|
+
if (rowVirtualizationEnabled || stickyGroupRows.length === 0) return undefined;
|
|
3234
|
+
return stickyGroupRows.find((s) => s.rowIndex === rowIndex)?.top;
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3080
3237
|
// 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
|
-
|
|
3238
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band and the sticky
|
|
3239
|
+
// group band are in the flow before the spacer whatever the window shows,
|
|
3240
|
+
// so they come off the spacer.
|
|
3241
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight - stickyBandHeight, 0));
|
|
3084
3242
|
const rowBottomSpacer = $derived(
|
|
3085
3243
|
Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0),
|
|
3086
3244
|
);
|
|
@@ -3165,10 +3323,7 @@ export function createSvGridController<
|
|
|
3165
3323
|
* `fittedColumnWidths`, so the overflow decision settles in the same
|
|
3166
3324
|
* render where fit-scaling lands - no race, no scrollbar flash. */
|
|
3167
3325
|
const hasHorizontalOverflow = $derived.by(function hasHorizontalOverflow_d() {
|
|
3168
|
-
|
|
3169
|
-
(showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
3170
|
-
(showRowSelectionEffective ? selectionColumnWidth : 0);
|
|
3171
|
-
let total = fixedCols;
|
|
3326
|
+
let total = systemColumnsWidth;
|
|
3172
3327
|
for (const column of allColumns) total += getColumnWidth(column.id);
|
|
3173
3328
|
// +1 to tolerate sub-pixel rounding residue from `fitColumns`.
|
|
3174
3329
|
return total > viewportWidth + 1;
|
|
@@ -3620,9 +3775,13 @@ export function createSvGridController<
|
|
|
3620
3775
|
: typeof rh === "function"
|
|
3621
3776
|
? rh
|
|
3622
3777
|
: (rh ?? 30);
|
|
3623
|
-
|
|
3778
|
+
// Detail rows have their own height; only when the props say so does
|
|
3779
|
+
// the per-index lookup replace the uniform fast path.
|
|
3780
|
+
const detailOn = props.detailRowHeight != null && !!props.isDetailRow;
|
|
3781
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0 || detailOn
|
|
3624
3782
|
? (index: number) =>
|
|
3625
3783
|
rowResizeHeightPx(index) ??
|
|
3784
|
+
detailRowHeightPx(index) ??
|
|
3626
3785
|
(typeof base === "function" ? base(index) : base)
|
|
3627
3786
|
: base;
|
|
3628
3787
|
virtualizer.setOptions({
|
|
@@ -3761,6 +3920,14 @@ export function createSvGridController<
|
|
|
3761
3920
|
// the only thing that can answer honestly once the data is a window
|
|
3762
3921
|
// onto a server.
|
|
3763
3922
|
if (props.rowSelectionModel) return props.rowSelectionModel.headerState();
|
|
3923
|
+
// Nothing checked is the common case and needs no walk: this derived
|
|
3924
|
+
// re-runs on every data change, and on a 100k-row live feed the filter
|
|
3925
|
+
// below was 30 ms per tick spent to conclude "none".
|
|
3926
|
+
let anyChecked = false;
|
|
3927
|
+
for (const id in rowSelectionState) {
|
|
3928
|
+
if (rowSelectionState[id]) { anyChecked = true; break; }
|
|
3929
|
+
}
|
|
3930
|
+
if (!anyChecked) return "none";
|
|
3764
3931
|
const selectable = allRows.filter(
|
|
3765
3932
|
(row) => !isGroupRow(row) && !placeholderStateOf(row),
|
|
3766
3933
|
);
|
|
@@ -3810,8 +3977,6 @@ export function createSvGridController<
|
|
|
3810
3977
|
// widths stay in the fit distribution and leave a blank gap at the edge.
|
|
3811
3978
|
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
3812
3979
|
if (!cols.length) return null;
|
|
3813
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
3814
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
3815
3980
|
// Reserve the custom vertical scrollbar's width when it's visible. It
|
|
3816
3981
|
// overlays the right 16px of the viewport (absolute, z-index 40) and
|
|
3817
3982
|
// does NOT shrink clientWidth, so without this the last fitted column
|
|
@@ -3820,8 +3985,7 @@ export function createSvGridController<
|
|
|
3820
3985
|
const scrollbarWidth = hasVerticalOverflow ? 16 : 0;
|
|
3821
3986
|
const target =
|
|
3822
3987
|
(scrollContainer?.clientWidth ?? 0) -
|
|
3823
|
-
|
|
3824
|
-
selectionWidth -
|
|
3988
|
+
systemColumnsWidth -
|
|
3825
3989
|
scrollbarWidth;
|
|
3826
3990
|
if (target <= 0) return null;
|
|
3827
3991
|
|
|
@@ -4319,6 +4483,8 @@ export function createSvGridController<
|
|
|
4319
4483
|
set historyPtr(v) { historyPtr = v as never; },
|
|
4320
4484
|
get historyGroupId() { return historyGroupId; },
|
|
4321
4485
|
set historyGroupId(v) { historyGroupId = v as never; },
|
|
4486
|
+
get historyTag() { return historyTag; },
|
|
4487
|
+
set historyTag(v) { historyTag = v; },
|
|
4322
4488
|
get historyVersion() { return historyVersion; },
|
|
4323
4489
|
set historyVersion(v) { historyVersion = v as never; },
|
|
4324
4490
|
get tooltip() { return tooltip; },
|
|
@@ -4355,6 +4521,11 @@ export function createSvGridController<
|
|
|
4355
4521
|
get gridStateVersion() { return gridStateVersion; },
|
|
4356
4522
|
set gridStateVersion(v) { gridStateVersion = v as never; },
|
|
4357
4523
|
get selectionColumnWidth() { return selectionColumnWidth; },
|
|
4524
|
+
get detailToggleColumnWidth() { return detailToggleColumnWidth; },
|
|
4525
|
+
get detailToggleColumnLeft() { return detailToggleColumnLeft; },
|
|
4526
|
+
get showDetailToggleEffective() { return showDetailToggleEffective; },
|
|
4527
|
+
get systemColumnsWidth() { return systemColumnsWidth; },
|
|
4528
|
+
get detailToggleOf() { return detailToggleOf; },
|
|
4358
4529
|
get rowNumberColumnWidth() { return rowNumberColumnWidth; },
|
|
4359
4530
|
get showRowNumbersEffective() { return showRowNumbersEffective; },
|
|
4360
4531
|
get filterOperatorOptions() { return filterOperatorOptions; },
|
|
@@ -4772,6 +4943,8 @@ export function createSvGridController<
|
|
|
4772
4943
|
get frozenRowList() { return frozenRowList; },
|
|
4773
4944
|
get frozenBandHeight() { return frozenBandHeight; },
|
|
4774
4945
|
get frozenRowTop() { return frozenRowTop; },
|
|
4946
|
+
get stickyGroupRows() { return stickyGroupRows; },
|
|
4947
|
+
get stickyTopOfRow() { return stickyTopOfRow; },
|
|
4775
4948
|
/** Record the height the user dragged a row to. */
|
|
4776
4949
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4777
4950
|
get isRowCollapsed() { return isRowCollapsed; },
|
|
@@ -4784,6 +4957,12 @@ export function createSvGridController<
|
|
|
4784
4957
|
rowResizeVersion;
|
|
4785
4958
|
return rowResizeHeightPx;
|
|
4786
4959
|
},
|
|
4960
|
+
/** The height row i is drawn at: dragged, measured, detail, declared or default. */
|
|
4961
|
+
get rowSizePxOf() {
|
|
4962
|
+
rowResizeVersion;
|
|
4963
|
+
autoRowHeightVersion;
|
|
4964
|
+
return rowSizePxOf;
|
|
4965
|
+
},
|
|
4787
4966
|
get virtualRowTotalSize() { return virtualRowTotalSize; },
|
|
4788
4967
|
get virtualRowStart() { return virtualRowStart; },
|
|
4789
4968
|
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. */
|
|
@@ -1311,9 +1387,6 @@ select.sv-grid-fr-editor {
|
|
|
1311
1387
|
.sv-grid-cell-active,
|
|
1312
1388
|
.sv-grid-cell-editing {
|
|
1313
1389
|
position: relative;
|
|
1314
|
-
/* High enough to sit above pinned-column stacking contexts (z=4) so the
|
|
1315
|
-
list/chips popover overlays adjacent cells instead of being clipped. */
|
|
1316
|
-
z-index: 20;
|
|
1317
1390
|
/* `box-shadow: inset` is bound to the cell's own box, so the ring
|
|
1318
1391
|
clips cleanly with the cell when it's partially scrolled out of
|
|
1319
1392
|
view (and shows up consistently at the right edge of a virtualized
|
|
@@ -1323,10 +1396,33 @@ select.sv-grid-fr-editor {
|
|
|
1323
1396
|
inconsistent at scroll boundaries. */
|
|
1324
1397
|
box-shadow: inset 0 0 0 2px var(--sg-accent, #0b63f3);
|
|
1325
1398
|
outline: none;
|
|
1326
|
-
|
|
1327
|
-
|
|
1399
|
+
}
|
|
1400
|
+
/* Only the EDITING cell escapes its own box and outranks the pinned-column
|
|
1401
|
+
stacking contexts (z=4), so its list/chips popout overlays the neighbours
|
|
1402
|
+
instead of being clipped. The merely-ACTIVE cell used to get the same
|
|
1403
|
+
treatment, and on a grid scrolled sideways that made it paint straight over
|
|
1404
|
+
the sticky selection / row-number column: the checkbox disappeared and the
|
|
1405
|
+
cell's text - unclipped, because overflow was visible - spilled across it.
|
|
1406
|
+
A grid seeds its active cell at (0,0), so this hit the FIRST row of any
|
|
1407
|
+
horizontally scrolled grid with `showRowSelection`, untouched by the user. */
|
|
1408
|
+
.sv-grid-cell-editing {
|
|
1409
|
+
z-index: 20;
|
|
1328
1410
|
overflow: visible;
|
|
1329
1411
|
}
|
|
1412
|
+
/* Above ordinary cells so the ring is not overdrawn by a neighbour, below the
|
|
1413
|
+
sticky columns (z=4) so those keep covering it as they should. */
|
|
1414
|
+
.sv-grid-cell-active:not(.sv-grid-cell-editing) {
|
|
1415
|
+
z-index: 3;
|
|
1416
|
+
}
|
|
1417
|
+
/* The active cell a grid mounts with is seeded at (0,0), not chosen: no
|
|
1418
|
+
ring until a click, an arrow key or the api activates a cell (the same
|
|
1419
|
+
gate the fill handle has always had). Once activated it stays through a
|
|
1420
|
+
blur, so a toolbar acting on "the focused row" keeps its cue. A page with
|
|
1421
|
+
six grids on it, or a detail grid inside each open row, used to open
|
|
1422
|
+
with an accent ring in every one of them. */
|
|
1423
|
+
.sv-grid-active-seeded .sv-grid-cell-active {
|
|
1424
|
+
box-shadow: none;
|
|
1425
|
+
}
|
|
1330
1426
|
|
|
1331
1427
|
/* ---- The editing cell's border -------------------------------------------
|
|
1332
1428
|
One ring for every editor, drawn by the CELL rather than by each editor.
|
|
@@ -1625,6 +1721,40 @@ select.sv-grid-fr-editor {
|
|
|
1625
1721
|
border-bottom: 1px solid var(--sg-frozen-edge, var(--sg-border, #94a3b8));
|
|
1626
1722
|
}
|
|
1627
1723
|
|
|
1724
|
+
/* A detail row with a declared height (detailRowHeight): the row keeps the
|
|
1725
|
+
height the virtualizer counted on - the auto-height rule above must not
|
|
1726
|
+
win - and the panel's body, a block inside the cell, takes that height
|
|
1727
|
+
and scrolls what does not fit. (A table cell's own height is only a
|
|
1728
|
+
minimum, so the height goes on the block.) */
|
|
1729
|
+
.sv-grid-row.sv-grid-detail-row.sv-grid-detail-row-sized {
|
|
1730
|
+
height: var(--sv-detail-h) !important;
|
|
1731
|
+
}
|
|
1732
|
+
.sv-grid-row.sv-grid-detail-row-sized > .sv-grid-detail-cell {
|
|
1733
|
+
vertical-align: top;
|
|
1734
|
+
box-sizing: border-box;
|
|
1735
|
+
height: var(--sv-detail-h);
|
|
1736
|
+
}
|
|
1737
|
+
.sv-grid-detail-body {
|
|
1738
|
+
box-sizing: border-box;
|
|
1739
|
+
height: 100%;
|
|
1740
|
+
overflow: auto;
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
/* Sticky group rows: the group a row belongs to, held under the header
|
|
1744
|
+
(and under the frozen band) while its rows scroll past. Same layer as
|
|
1745
|
+
the frozen band; the last one carries the band's edge. */
|
|
1746
|
+
.sv-grid-row.sv-grid-row-sticky-group {
|
|
1747
|
+
position: sticky;
|
|
1748
|
+
z-index: 31;
|
|
1749
|
+
background: var(--sg-bg, #fff);
|
|
1750
|
+
}
|
|
1751
|
+
.sv-grid-row.sv-grid-row-sticky-group > .sv-grid-cell {
|
|
1752
|
+
background: var(--sg-bg, #fff);
|
|
1753
|
+
}
|
|
1754
|
+
.sv-grid-row.sv-grid-row-sticky-group-last > .sv-grid-cell {
|
|
1755
|
+
border-bottom: 1px solid var(--sg-frozen-edge, var(--sg-border, #94a3b8));
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1628
1758
|
/* The multiline text editor sizes itself by its lines and keeps the
|
|
1629
1759
|
input's single-line look until a break is typed. */
|
|
1630
1760
|
.sv-grid-cell-editor-multiline {
|
|
@@ -3082,6 +3212,12 @@ select.sv-grid-fr-editor {
|
|
|
3082
3212
|
.sv-grid-row-number-cell {
|
|
3083
3213
|
border-right-color: var(--sg-header-bg, #f1f5f9);
|
|
3084
3214
|
}
|
|
3215
|
+
/* The detail-toggle gutter fuses with the row numbers the same way; only
|
|
3216
|
+
its own right edge keeps a line, between the chrome and the data. */
|
|
3217
|
+
.sv-grid-row-number-cell:has(+ .sv-grid-detail-toggle-cell),
|
|
3218
|
+
.sv-grid-row-number-column:has(+ .sv-grid-detail-toggle-column) {
|
|
3219
|
+
border-right-color: var(--sg-header-bg, #f1f5f9);
|
|
3220
|
+
}
|
|
3085
3221
|
|
|
3086
3222
|
/* Group row label colour. */
|
|
3087
3223
|
.sv-grid-group-row > .sv-grid-cell {
|