@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
|
@@ -327,7 +327,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
327
327
|
// grows with rows x columns and a Map lookup per cell per render.
|
|
328
328
|
let editedCellValues = $state.raw({});
|
|
329
329
|
const UNDO_LIMIT = 200;
|
|
330
|
-
|
|
330
|
+
// Raw: pushHistory always assigns a fresh array and nothing renders from a
|
|
331
|
+
// step, so deep proxies would cost 200 wrapped objects for nothing. It also
|
|
332
|
+
// keeps a consumer's `tag` the object it passed, which a proxy would not be:
|
|
333
|
+
// the sheet shell compares tags by identity.
|
|
334
|
+
let history = $state.raw([]);
|
|
331
335
|
/** Index in `history` of the LAST applied step. -1 means "nothing applied".
|
|
332
336
|
* undo() decrements; redo() increments. New edits truncate everything
|
|
333
337
|
* past the pointer (the classic "you can't redo after editing" rule). */
|
|
@@ -340,6 +344,9 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
340
344
|
* $state: it is written and read synchronously inside a single call and
|
|
341
345
|
* nothing renders from it. */
|
|
342
346
|
let historyGroupId = undefined;
|
|
347
|
+
/** The consumer's mark for the steps recorded from now on (`api.setHistoryTag`);
|
|
348
|
+
* not $state for the same reason. */
|
|
349
|
+
let historyTag = undefined;
|
|
343
350
|
let tooltip = $state(null);
|
|
344
351
|
let tooltipTimer = null;
|
|
345
352
|
// ---- Find-in-grid ----------------------------------------------------
|
|
@@ -410,7 +417,28 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
410
417
|
// catch-all gridStateVersion, so navigation doesn't rebuild 1M rows.
|
|
411
418
|
let dataStateVersion = $state(0);
|
|
412
419
|
const selectionColumnWidth = 44;
|
|
420
|
+
const detailToggleColumnWidth = 36;
|
|
413
421
|
const rowNumberColumnWidth = $derived(props.rowNumberWidth ?? 56);
|
|
422
|
+
const showDetailToggleEffective = $derived(props.showDetailToggle === true);
|
|
423
|
+
/**
|
|
424
|
+
* What the chevron on a row does, or null when the row has no detail to
|
|
425
|
+
* open: a detail row itself, a group or placeholder on a server model, a
|
|
426
|
+
* row `hasDetail` rules out, or a grid with nothing to call.
|
|
427
|
+
*/
|
|
428
|
+
function detailToggleOf(row, rowIndex) {
|
|
429
|
+
const data = row.original;
|
|
430
|
+
if (props.isDetailRow?.(data, rowIndex))
|
|
431
|
+
return null;
|
|
432
|
+
const sg = props.serverGroup;
|
|
433
|
+
if (sg) {
|
|
434
|
+
if (!sg.toggleDetail || sg.isGroup(data) || (sg.hasDetail && !sg.hasDetail(data)))
|
|
435
|
+
return null;
|
|
436
|
+
return { open: sg.detailOpen?.(data) ?? false, toggle: () => sg.toggleDetail(data) };
|
|
437
|
+
}
|
|
438
|
+
if (!props.onDetailToggle || (props.hasDetail && !props.hasDetail(data)))
|
|
439
|
+
return null;
|
|
440
|
+
return { open: props.isDetailOpen?.(data) ?? false, toggle: () => props.onDetailToggle(data, rowIndex) };
|
|
441
|
+
}
|
|
414
442
|
// A per-row `rowHeight` function already supplies heights, so auto-measuring
|
|
415
443
|
// would fight it. Fixed numbers are fine - they become the pre-measure estimate.
|
|
416
444
|
const autoRowHeightOn = $derived(props.autoRowHeight === true && typeof props.rowHeight !== "function");
|
|
@@ -563,6 +591,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
563
591
|
const showRowSelectionEffective = $derived(props.showRowSelection ??
|
|
564
592
|
((props.selectionMode ?? "both") === "row" ||
|
|
565
593
|
(props.selectionMode ?? "both") === "both"));
|
|
594
|
+
/** The system columns to the left of the data: row numbers, selection, the detail toggle. */
|
|
595
|
+
const systemColumnsWidth = $derived((showRowNumbersEffective ? rowNumberColumnWidth : 0) +
|
|
596
|
+
(showRowSelectionEffective ? selectionColumnWidth : 0) +
|
|
597
|
+
(showDetailToggleEffective ? detailToggleColumnWidth : 0));
|
|
598
|
+
/** Where the detail-toggle column sticks: after the row numbers and the selection column. */
|
|
599
|
+
const detailToggleColumnLeft = $derived((showRowNumbersEffective ? rowNumberColumnWidth : 0) + (showRowSelectionEffective ? selectionColumnWidth : 0));
|
|
566
600
|
// `selectable` is the shortcut alias, so it wins over the fine-grained prop,
|
|
567
601
|
// which in turn wins over `selectionMode` - the same precedence the other
|
|
568
602
|
// shortcuts use. Unlike them it starts ON (selectionMode defaults to 'both'),
|
|
@@ -591,7 +625,16 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
591
625
|
// `data` is optional on the type (a `rowModel` may supply the rows
|
|
592
626
|
// instead), so every read falls back to empty rather than to undefined.
|
|
593
627
|
const EMPTY_DATA = [];
|
|
594
|
-
|
|
628
|
+
// Seeded as a copy, for the same reason the sync effect below makes one: the
|
|
629
|
+
// row model tells data apart by array identity, so the grid has to own an
|
|
630
|
+
// array the consumer cannot grow behind its back.
|
|
631
|
+
let internalData = $state.raw(props.data?.length ? props.data.slice() : EMPTY_DATA);
|
|
632
|
+
// What the last sync saw, so the effect can tell a replacement (drop the
|
|
633
|
+
// cell edits) from a push or splice (keep them) and skip a no-op run.
|
|
634
|
+
// svelte-ignore state_referenced_locally
|
|
635
|
+
let lastData = props.data ?? EMPTY_DATA;
|
|
636
|
+
// svelte-ignore state_referenced_locally
|
|
637
|
+
let lastDataLength = props.data?.length ?? 0;
|
|
595
638
|
// Resolve `cellDataType` / `inferColumnTypes` into concrete editorType +
|
|
596
639
|
// format defaults once, up front, so every downstream reader sees a normal
|
|
597
640
|
// column. Explicit fields on the ColumnDef always win.
|
|
@@ -632,12 +675,29 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
632
675
|
return collapsedColumnGroups.has(groupId);
|
|
633
676
|
}
|
|
634
677
|
$effect(() => {
|
|
678
|
+
const next = props.data ?? EMPTY_DATA;
|
|
679
|
+
// Reading `length` is what subscribes this effect to a `$state` array's own
|
|
680
|
+
// length signal, so `rows.push(...)` and `rows.splice(...)` re-run it.
|
|
681
|
+
// Without this read nothing in the grid tracks the row count at all:
|
|
682
|
+
// `getRowModel` reads `.length` only inside its cache miss, and the miss is
|
|
683
|
+
// keyed on the array reference, which a mutation does not change.
|
|
684
|
+
const length = next.length;
|
|
685
|
+
const replaced = next !== lastData;
|
|
686
|
+
if (!replaced && length === lastDataLength)
|
|
687
|
+
return;
|
|
688
|
+
lastData = next;
|
|
689
|
+
lastDataLength = length;
|
|
690
|
+
// A fresh array object, because `getRowModel` memoises on identity. The
|
|
691
|
+
// elements are the same objects, so a row that did not move keeps its row
|
|
692
|
+
// object and a field write still reaches its cell through the proxy.
|
|
693
|
+
internalData = length === 0 ? EMPTY_DATA : next.slice();
|
|
635
694
|
// When the consumer replaces `data` (e.g. a "Reset" button), drop any
|
|
636
695
|
// accumulated cell-edit overrides - otherwise `getCellDisplayValue`
|
|
637
696
|
// would keep returning the old edited values from `editedCellValues`
|
|
638
|
-
// even though the underlying data has been replaced.
|
|
639
|
-
|
|
640
|
-
|
|
697
|
+
// even though the underlying data has been replaced. Growing or shrinking
|
|
698
|
+
// the array is not a reset, so it keeps them.
|
|
699
|
+
if (replaced)
|
|
700
|
+
editedCellValues = {};
|
|
641
701
|
});
|
|
642
702
|
$effect(() => {
|
|
643
703
|
internalColumns = resolveCols(props.columns);
|
|
@@ -661,35 +721,41 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
661
721
|
// the row-model pipeline is read before any later prop change could apply.
|
|
662
722
|
// svelte-ignore state_referenced_locally
|
|
663
723
|
const treeDataConfig = props.treeData;
|
|
724
|
+
// Pagination is intentionally NOT in the grid's row-model pipeline. The
|
|
725
|
+
// wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
726
|
+
// valueFilters) on top of `grid.getRowModel().rows`. If pagination ran
|
|
727
|
+
// first, those filters would only see the visible page. Instead the wrapper
|
|
728
|
+
// paginates last - see `allRows` below.
|
|
729
|
+
//
|
|
730
|
+
// Built ONCE. Both inputs are captured at mount, and the stages keep state
|
|
731
|
+
// between runs: the sorted and filtered stages hold their last output so a
|
|
732
|
+
// data tick can repair it instead of recomputing (core.ts, `replacedRowsOf`).
|
|
733
|
+
// This used to be a getter that built fresh stages on every read, which
|
|
734
|
+
// threw that cache away on each run - a live feed re-sorted 100k rows on
|
|
735
|
+
// every tick and the row-model memo's `cachedPipeline` never matched.
|
|
736
|
+
const rowModels = {
|
|
737
|
+
coreRowModel: createCoreRowModel(),
|
|
738
|
+
filteredRowModel: createFilteredRowModel(),
|
|
739
|
+
// External-sort mode: pass the rows through untouched so the consumer
|
|
740
|
+
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
741
|
+
sortedRowModel: externalSortEnabled
|
|
742
|
+
? passthroughSortedRowModel
|
|
743
|
+
: createSortedRowModel(sortFns),
|
|
744
|
+
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
745
|
+
// be both a hierarchy node and bucketed under a group banner.
|
|
746
|
+
groupedRowModel: treeDataConfig
|
|
747
|
+
? createTreeRowModel({
|
|
748
|
+
parentField: treeDataConfig.parentField,
|
|
749
|
+
idField: treeDataConfig.idField,
|
|
750
|
+
})
|
|
751
|
+
: createGroupedRowModel(),
|
|
752
|
+
expandedRowModel: createExpandedRowModel(),
|
|
753
|
+
};
|
|
664
754
|
const grid = createSvGrid({
|
|
665
755
|
get _features() {
|
|
666
756
|
return resolveEffectiveFeatures();
|
|
667
757
|
},
|
|
668
|
-
|
|
669
|
-
// Pagination is intentionally NOT in the grid's row-model pipeline.
|
|
670
|
-
// The wrapper applies its own filters (filterMenuValues, globalFilter,
|
|
671
|
-
// valueFilters) on top of `grid.getRowModel().rows`. If pagination
|
|
672
|
-
// ran first, those filters would only see the visible page. Instead
|
|
673
|
-
// the wrapper paginates last - see `allRows` below.
|
|
674
|
-
return {
|
|
675
|
-
coreRowModel: createCoreRowModel(),
|
|
676
|
-
filteredRowModel: createFilteredRowModel(),
|
|
677
|
-
// External-sort mode: pass the rows through untouched so the consumer
|
|
678
|
-
// controls ordering (e.g. tree data that must preserve hierarchy).
|
|
679
|
-
sortedRowModel: externalSortEnabled
|
|
680
|
-
? passthroughSortedRowModel
|
|
681
|
-
: createSortedRowModel(sortFns),
|
|
682
|
-
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
683
|
-
// be both a hierarchy node and bucketed under a group banner.
|
|
684
|
-
groupedRowModel: treeDataConfig
|
|
685
|
-
? createTreeRowModel({
|
|
686
|
-
parentField: treeDataConfig.parentField,
|
|
687
|
-
idField: treeDataConfig.idField,
|
|
688
|
-
})
|
|
689
|
-
: createGroupedRowModel(),
|
|
690
|
-
expandedRowModel: createExpandedRowModel(),
|
|
691
|
-
};
|
|
692
|
-
},
|
|
758
|
+
_rowModels: rowModels,
|
|
693
759
|
get columns() {
|
|
694
760
|
return internalColumns;
|
|
695
761
|
},
|
|
@@ -1091,10 +1157,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1091
1157
|
});
|
|
1092
1158
|
/** Cumulative pixel offsets for left- and right-pinned columns. */
|
|
1093
1159
|
const pinnedOffsets = $derived.by(function pinnedOffsets_d() {
|
|
1094
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
1095
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
1096
1160
|
const left = {};
|
|
1097
|
-
let leftAcc =
|
|
1161
|
+
let leftAcc = systemColumnsWidth;
|
|
1098
1162
|
for (const id of effectivePinning.left) {
|
|
1099
1163
|
// Hidden pinned columns don't render, so they must contribute neither an
|
|
1100
1164
|
// offset entry nor width - otherwise the next visible pinned column is
|
|
@@ -1611,16 +1675,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1611
1675
|
// so consumers don't see spurious callbacks during re-renders.
|
|
1612
1676
|
let lastCellRangeSerialized = "";
|
|
1613
1677
|
$effect(() => {
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
Math.max(a.colIndex, f.colIndex),
|
|
1622
|
-
]]
|
|
1623
|
-
: [];
|
|
1678
|
+
// Every rectangle, the committed ones a Ctrl+click added and the
|
|
1679
|
+
// active one last, the order getSelectionRects gives: a consumer's
|
|
1680
|
+
// status bar and shading cover the whole selection, not only its last
|
|
1681
|
+
// range. Depend on selectionRanges so a change to the committed set
|
|
1682
|
+
// re-runs this too.
|
|
1683
|
+
void selectionRanges;
|
|
1684
|
+
const ranges = getSelectionRects().map((r) => [r.minRow, r.minCol, r.maxRow, r.maxCol]);
|
|
1624
1685
|
const serialized = JSON.stringify(ranges);
|
|
1625
1686
|
if (serialized === lastCellRangeSerialized)
|
|
1626
1687
|
return;
|
|
@@ -2651,6 +2712,16 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2651
2712
|
return total;
|
|
2652
2713
|
});
|
|
2653
2714
|
/** The declared or dragged height of one row; measured under auto height. */
|
|
2715
|
+
/** A detail row's declared height, or null when the row is not one (or none is declared). */
|
|
2716
|
+
function detailRowHeightPx(i) {
|
|
2717
|
+
const dh = props.detailRowHeight;
|
|
2718
|
+
if (dh == null || !props.isDetailRow)
|
|
2719
|
+
return null;
|
|
2720
|
+
const row = allRows[i];
|
|
2721
|
+
if (!row || !props.isDetailRow(row.original, i))
|
|
2722
|
+
return null;
|
|
2723
|
+
return typeof dh === "function" ? dh(row.original) : dh;
|
|
2724
|
+
}
|
|
2654
2725
|
function rowSizePxOf(i) {
|
|
2655
2726
|
if (isRowCollapsed(i))
|
|
2656
2727
|
return 0;
|
|
@@ -2659,6 +2730,9 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2659
2730
|
const own = rowResizeHeightPx(i);
|
|
2660
2731
|
if (own != null)
|
|
2661
2732
|
return own;
|
|
2733
|
+
const detail = detailRowHeightPx(i);
|
|
2734
|
+
if (detail != null)
|
|
2735
|
+
return detail;
|
|
2662
2736
|
const rh = props.rowHeight;
|
|
2663
2737
|
return typeof rh === "function" ? rh(i) : (rh ?? 30);
|
|
2664
2738
|
}
|
|
@@ -2669,10 +2743,91 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2669
2743
|
top += rowSizePxOf(k);
|
|
2670
2744
|
return top;
|
|
2671
2745
|
}
|
|
2746
|
+
const NO_STICKY = [];
|
|
2747
|
+
/** Whether a row is a group and how deep it sits, for any of the three tree sources. */
|
|
2748
|
+
function rowGroupLevel(row) {
|
|
2749
|
+
const sg = props.serverGroup;
|
|
2750
|
+
if (sg) {
|
|
2751
|
+
const data = row.original;
|
|
2752
|
+
return { group: sg.isGroup(data), level: sg.level(data) };
|
|
2753
|
+
}
|
|
2754
|
+
// Client grouping and tree data: an expandable row is the group (a tree
|
|
2755
|
+
// row is a data row too, which is why isGroupRow() excludes it, but for
|
|
2756
|
+
// the band it is the parent its children scroll under).
|
|
2757
|
+
if (typeof row.getCanExpand === "function" && row.getCanExpand())
|
|
2758
|
+
return { group: true, level: row.depth ?? 0 };
|
|
2759
|
+
return { group: false, level: row.depth ?? 0 };
|
|
2760
|
+
}
|
|
2761
|
+
/** The first row whose bottom edge is under the header, in row-index space. */
|
|
2762
|
+
const firstRowUnderHeader = $derived.by(function firstRowUnderHeader_d() {
|
|
2763
|
+
if (allRows.length === 0)
|
|
2764
|
+
return -1;
|
|
2765
|
+
if (rowVirtualizationEnabled) {
|
|
2766
|
+
virtualizer.version;
|
|
2767
|
+
const offset = virtualizer.getState().scrollOffset;
|
|
2768
|
+
const hit = virtualRows.find((it) => it.end > offset);
|
|
2769
|
+
return hit ? hit.index : -1;
|
|
2770
|
+
}
|
|
2771
|
+
rowResizeVersion;
|
|
2772
|
+
autoRowHeightVersion;
|
|
2773
|
+
const top = scrollMetrics.scrollTop;
|
|
2774
|
+
let acc = 0;
|
|
2775
|
+
let i = 0;
|
|
2776
|
+
while (i < allRows.length - 1 && acc + rowSizePxOf(i) <= top) {
|
|
2777
|
+
acc += rowSizePxOf(i);
|
|
2778
|
+
i += 1;
|
|
2779
|
+
}
|
|
2780
|
+
return i;
|
|
2781
|
+
});
|
|
2782
|
+
const stickyGroupRows = $derived.by(function stickyGroupRows_d() {
|
|
2783
|
+
if (!props.stickyGroupRows)
|
|
2784
|
+
return NO_STICKY;
|
|
2785
|
+
const first = firstRowUnderHeader;
|
|
2786
|
+
if (first < 0)
|
|
2787
|
+
return NO_STICKY;
|
|
2788
|
+
const head = allRows[first];
|
|
2789
|
+
if (!head)
|
|
2790
|
+
return NO_STICKY;
|
|
2791
|
+
// A group row at level L, or a leaf at level L, has its ancestors at
|
|
2792
|
+
// levels L-1 .. 0, each the nearest group row above at that level.
|
|
2793
|
+
let want = rowGroupLevel(head).level - 1;
|
|
2794
|
+
if (want < 0)
|
|
2795
|
+
return NO_STICKY;
|
|
2796
|
+
const found = [];
|
|
2797
|
+
for (let i = first - 1; i >= frozenRowCount && want >= 0; i -= 1) {
|
|
2798
|
+
const row = allRows[i];
|
|
2799
|
+
const info = rowGroupLevel(row);
|
|
2800
|
+
if (info.group && info.level === want) {
|
|
2801
|
+
found.push({ row, rowIndex: i });
|
|
2802
|
+
want -= 1;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
if (found.length === 0)
|
|
2806
|
+
return NO_STICKY;
|
|
2807
|
+
found.reverse();
|
|
2808
|
+
rowResizeVersion;
|
|
2809
|
+
autoRowHeightVersion;
|
|
2810
|
+
let top = headerHeight + frozenBandHeight;
|
|
2811
|
+
return found.map((f) => {
|
|
2812
|
+
const at = top;
|
|
2813
|
+
const size = rowSizePxOf(f.rowIndex);
|
|
2814
|
+
top += size;
|
|
2815
|
+
return { ...f, top: at, size };
|
|
2816
|
+
});
|
|
2817
|
+
});
|
|
2818
|
+
/** The band's height under virtualization, taken off the top spacer. */
|
|
2819
|
+
const stickyBandHeight = $derived(rowVirtualizationEnabled ? stickyGroupRows.reduce((sum, s) => sum + s.size, 0) : 0);
|
|
2820
|
+
/** Without virtualization the real row sticks: its top, or undefined. */
|
|
2821
|
+
function stickyTopOfRow(rowIndex) {
|
|
2822
|
+
if (rowVirtualizationEnabled || stickyGroupRows.length === 0)
|
|
2823
|
+
return undefined;
|
|
2824
|
+
return stickyGroupRows.find((s) => s.rowIndex === rowIndex)?.top;
|
|
2825
|
+
}
|
|
2672
2826
|
// Spacer heights in DOM space. With scaling inert these equal the original
|
|
2673
|
-
// virtualRowStart / virtualRowBottomSpacer. The frozen band
|
|
2674
|
-
// before the spacer whatever the window shows,
|
|
2675
|
-
|
|
2827
|
+
// virtualRowStart / virtualRowBottomSpacer. The frozen band and the sticky
|
|
2828
|
+
// group band are in the flow before the spacer whatever the window shows,
|
|
2829
|
+
// so they come off the spacer.
|
|
2830
|
+
const rowTopSpacer = $derived(Math.max(virtualRowStart - rowOffsetAdjustment - frozenBandHeight - stickyBandHeight, 0));
|
|
2676
2831
|
const rowBottomSpacer = $derived(Math.max(rowDomTotalSize - (virtualRowEnd - rowOffsetAdjustment), 0));
|
|
2677
2832
|
const virtualColumns = $derived.by(function virtualColumns_d() {
|
|
2678
2833
|
columnVirtualizerVersion;
|
|
@@ -2757,9 +2912,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
2757
2912
|
* `fittedColumnWidths`, so the overflow decision settles in the same
|
|
2758
2913
|
* render where fit-scaling lands - no race, no scrollbar flash. */
|
|
2759
2914
|
const hasHorizontalOverflow = $derived.by(function hasHorizontalOverflow_d() {
|
|
2760
|
-
|
|
2761
|
-
(showRowSelectionEffective ? selectionColumnWidth : 0);
|
|
2762
|
-
let total = fixedCols;
|
|
2915
|
+
let total = systemColumnsWidth;
|
|
2763
2916
|
for (const column of allColumns)
|
|
2764
2917
|
total += getColumnWidth(column.id);
|
|
2765
2918
|
// +1 to tolerate sub-pixel rounding residue from `fitColumns`.
|
|
@@ -3208,8 +3361,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3208
3361
|
: typeof rh === "function"
|
|
3209
3362
|
? rh
|
|
3210
3363
|
: (rh ?? 30);
|
|
3211
|
-
|
|
3364
|
+
// Detail rows have their own height; only when the props say so does
|
|
3365
|
+
// the per-index lookup replace the uniform fast path.
|
|
3366
|
+
const detailOn = props.detailRowHeight != null && !!props.isDetailRow;
|
|
3367
|
+
const estimateSize = rowResizeHeights.size > 0 || collapsedRows.size > 0 || detailOn
|
|
3212
3368
|
? (index) => rowResizeHeightPx(index) ??
|
|
3369
|
+
detailRowHeightPx(index) ??
|
|
3213
3370
|
(typeof base === "function" ? base(index) : base)
|
|
3214
3371
|
: base;
|
|
3215
3372
|
virtualizer.setOptions({
|
|
@@ -3320,6 +3477,18 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3320
3477
|
// onto a server.
|
|
3321
3478
|
if (props.rowSelectionModel)
|
|
3322
3479
|
return props.rowSelectionModel.headerState();
|
|
3480
|
+
// Nothing checked is the common case and needs no walk: this derived
|
|
3481
|
+
// re-runs on every data change, and on a 100k-row live feed the filter
|
|
3482
|
+
// below was 30 ms per tick spent to conclude "none".
|
|
3483
|
+
let anyChecked = false;
|
|
3484
|
+
for (const id in rowSelectionState) {
|
|
3485
|
+
if (rowSelectionState[id]) {
|
|
3486
|
+
anyChecked = true;
|
|
3487
|
+
break;
|
|
3488
|
+
}
|
|
3489
|
+
}
|
|
3490
|
+
if (!anyChecked)
|
|
3491
|
+
return "none";
|
|
3323
3492
|
const selectable = allRows.filter((row) => !isGroupRow(row) && !placeholderStateOf(row));
|
|
3324
3493
|
if (!selectable.length)
|
|
3325
3494
|
return "none";
|
|
@@ -3367,8 +3536,6 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3367
3536
|
const cols = grid.getAllColumns().filter((c) => !hiddenColumns[c.id] && !hiddenByGroupCollapse[c.id] && !collapsedColumns[c.id]);
|
|
3368
3537
|
if (!cols.length)
|
|
3369
3538
|
return null;
|
|
3370
|
-
const rowNumberWidth = showRowNumbersEffective ? rowNumberColumnWidth : 0;
|
|
3371
|
-
const selectionWidth = showRowSelectionEffective ? selectionColumnWidth : 0;
|
|
3372
3539
|
// Reserve the custom vertical scrollbar's width when it's visible. It
|
|
3373
3540
|
// overlays the right 16px of the viewport (absolute, z-index 40) and
|
|
3374
3541
|
// does NOT shrink clientWidth, so without this the last fitted column
|
|
@@ -3376,8 +3543,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3376
3543
|
// is hidden behind the opaque scrollbar.
|
|
3377
3544
|
const scrollbarWidth = hasVerticalOverflow ? 16 : 0;
|
|
3378
3545
|
const target = (scrollContainer?.clientWidth ?? 0) -
|
|
3379
|
-
|
|
3380
|
-
selectionWidth -
|
|
3546
|
+
systemColumnsWidth -
|
|
3381
3547
|
scrollbarWidth;
|
|
3382
3548
|
if (target <= 0)
|
|
3383
3549
|
return null;
|
|
@@ -3779,6 +3945,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3779
3945
|
set historyPtr(v) { historyPtr = v; },
|
|
3780
3946
|
get historyGroupId() { return historyGroupId; },
|
|
3781
3947
|
set historyGroupId(v) { historyGroupId = v; },
|
|
3948
|
+
get historyTag() { return historyTag; },
|
|
3949
|
+
set historyTag(v) { historyTag = v; },
|
|
3782
3950
|
get historyVersion() { return historyVersion; },
|
|
3783
3951
|
set historyVersion(v) { historyVersion = v; },
|
|
3784
3952
|
get tooltip() { return tooltip; },
|
|
@@ -3815,6 +3983,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3815
3983
|
get gridStateVersion() { return gridStateVersion; },
|
|
3816
3984
|
set gridStateVersion(v) { gridStateVersion = v; },
|
|
3817
3985
|
get selectionColumnWidth() { return selectionColumnWidth; },
|
|
3986
|
+
get detailToggleColumnWidth() { return detailToggleColumnWidth; },
|
|
3987
|
+
get detailToggleColumnLeft() { return detailToggleColumnLeft; },
|
|
3988
|
+
get showDetailToggleEffective() { return showDetailToggleEffective; },
|
|
3989
|
+
get systemColumnsWidth() { return systemColumnsWidth; },
|
|
3990
|
+
get detailToggleOf() { return detailToggleOf; },
|
|
3818
3991
|
get rowNumberColumnWidth() { return rowNumberColumnWidth; },
|
|
3819
3992
|
get showRowNumbersEffective() { return showRowNumbersEffective; },
|
|
3820
3993
|
get filterOperatorOptions() { return filterOperatorOptions; },
|
|
@@ -4234,6 +4407,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
4234
4407
|
get frozenRowList() { return frozenRowList; },
|
|
4235
4408
|
get frozenBandHeight() { return frozenBandHeight; },
|
|
4236
4409
|
get frozenRowTop() { return frozenRowTop; },
|
|
4410
|
+
get stickyGroupRows() { return stickyGroupRows; },
|
|
4411
|
+
get stickyTopOfRow() { return stickyTopOfRow; },
|
|
4237
4412
|
/** Record the height the user dragged a row to. */
|
|
4238
4413
|
get setRowResizeHeight() { return setRowResizeHeight; },
|
|
4239
4414
|
get isRowCollapsed() { return isRowCollapsed; },
|
|
@@ -4246,6 +4421,12 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
4246
4421
|
rowResizeVersion;
|
|
4247
4422
|
return rowResizeHeightPx;
|
|
4248
4423
|
},
|
|
4424
|
+
/** The height row i is drawn at: dragged, measured, detail, declared or default. */
|
|
4425
|
+
get rowSizePxOf() {
|
|
4426
|
+
rowResizeVersion;
|
|
4427
|
+
autoRowHeightVersion;
|
|
4428
|
+
return rowSizePxOf;
|
|
4429
|
+
},
|
|
4249
4430
|
get virtualRowTotalSize() { return virtualRowTotalSize; },
|
|
4250
4431
|
get virtualRowStart() { return virtualRowStart; },
|
|
4251
4432
|
get virtualRowEnd() { return virtualRowEnd; },
|
package/dist/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 {
|
package/dist/SvGrid.helpers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseDateValue } from "./cell-formatting.js";
|
|
1
2
|
export function cfTextStyle(cf) {
|
|
2
3
|
let s = "";
|
|
3
4
|
if (cf.color)
|
|
@@ -233,10 +234,7 @@ export function getEditorClass(editorType) {
|
|
|
233
234
|
return "sv-grid-cell-editor";
|
|
234
235
|
}
|
|
235
236
|
export function asDate(value) {
|
|
236
|
-
|
|
237
|
-
return null;
|
|
238
|
-
const parsed = value instanceof Date ? value : new Date(value);
|
|
239
|
-
return Number.isNaN(parsed.getTime()) ? null : parsed;
|
|
237
|
+
return parseDateValue(value);
|
|
240
238
|
}
|
|
241
239
|
export function clampMenuX(x, width) {
|
|
242
240
|
return Math.max(8, Math.min(x, window.innerWidth - width - 8));
|