@svgrid/grid 2.2.31 → 2.2.33
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 +1 -1
- package/dist/DockNodeView.svelte +1 -1
- package/dist/GridFooter.svelte +0 -40
- package/dist/GridMenus.svelte +0 -77
- package/dist/SvAvatar.svelte +1 -1
- package/dist/SvCheckBox.svelte +2 -1
- package/dist/SvCircularProgress.svelte +1 -1
- package/dist/SvDateRangeInput.svelte +1 -1
- package/dist/SvGrid.controller.svelte.d.ts +20 -0
- package/dist/SvGrid.controller.svelte.js +358 -18
- package/dist/SvGrid.css +103 -3
- package/dist/SvGrid.svelte +164 -35
- package/dist/SvGrid.types.d.ts +81 -0
- package/dist/SvGridChart.svelte +2 -10
- package/dist/SvGridDropdown.svelte +38 -6
- package/dist/SvGridDropdown.svelte.d.ts +4 -0
- package/dist/SvGridSelect.svelte +12 -5
- package/dist/SvGridSelect.svelte.d.ts +6 -0
- package/dist/SvGroupCell.svelte +9 -2
- package/dist/SvKbd.svelte +1 -1
- package/dist/SvList.svelte +1 -1
- package/dist/SvMenubar.svelte +1 -1
- package/dist/SvMultiSelect.svelte +12 -4
- package/dist/SvMultiSelect.svelte.d.ts +6 -0
- package/dist/SvOtpInput.svelte +1 -1
- package/dist/SvPasswordInput.svelte +1 -1
- package/dist/SvProgress.svelte +1 -1
- package/dist/SvRating.svelte +1 -1
- package/dist/SvResult.svelte +1 -1
- package/dist/SvRichText.svelte +35 -16
- package/dist/SvRichText.svelte.d.ts +9 -0
- package/dist/SvSwitchButton.svelte +2 -1
- package/dist/SvToaster.svelte +1 -1
- package/dist/SvTour.svelte +4 -0
- package/dist/SvTree.svelte +9 -2
- package/dist/SvTree.svelte.d.ts +5 -0
- package/dist/SvTreeSelect.svelte +6 -4
- package/dist/build-api.js +16 -0
- package/dist/cdn/{GridMenus-Ds3OpzZT.js → GridMenus-B7XhhqdN.js} +34 -36
- package/dist/cdn/{GridMenus-Da_p7SKS.js → GridMenus-BxmIvCJh.js} +35 -37
- package/dist/cdn/{SvDateTimePicker-DB0aIEk8.js → SvDateTimePicker-ClqtVIj3.js} +2 -1
- package/dist/cdn/{SvDateTimePicker-B8GopjhC.js → SvDateTimePicker-o94wfSUa.js} +3 -2
- package/dist/cdn/{SvGridChart-BcLCYvw9.js → SvGridChart-Cr_90pPD.js} +1 -1
- package/dist/cdn/{SvGridChartPanel-DvjwxLvK.js → SvGridChartPanel-ICbxiUvK.js} +4 -4
- package/dist/cdn/{SvGridChartView-PwqI4s02.js → SvGridChartView-Bx42PMvf.js} +2 -2
- package/dist/cdn/{SvGridDropdown-hpnHvOGt.js → SvGridDropdown-CUOdm7YA.js} +93 -88
- package/dist/cdn/{SvGridDropdown-DjALc_4f.js → SvGridDropdown-D0VdjeR8.js} +129 -123
- package/dist/cdn/{disclose-version-DoD9AFD_.js → disclose-version-DljhZohK.js} +45 -45
- package/dist/cdn/{src-DYDCiC0D.js → src-erjBRYfX.js} +5952 -5615
- package/dist/cdn/{src-BLJfdyL0.js → src-ktbQVOk-.js} +4190 -3851
- package/dist/cdn/svgrid.js +9 -9
- package/dist/cdn/svgrid.svelte-external.js +8 -8
- package/dist/cell-render.d.ts +1 -0
- package/dist/cell-render.js +115 -1
- package/dist/cell-values.js +5 -0
- package/dist/chart.d.ts +2 -0
- package/dist/chart.js +1 -4
- package/dist/core.d.ts +80 -9
- package/dist/core.js +89 -0
- package/dist/createMenu.svelte.js +0 -1
- package/dist/createPopoverSelect.svelte.d.ts +2 -2
- package/dist/editing.js +1 -1
- package/dist/editor-contract.d.ts +12 -5
- package/dist/editor-registry.d.ts +18 -7
- package/dist/editor-registry.js +10 -1
- package/dist/export-format.js +3 -0
- package/dist/group-display.d.ts +39 -0
- package/dist/group-display.js +52 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/keyboard-handlers.js +16 -0
- package/dist/recurrence.js +3 -2
- package/dist/selection.js +0 -1
- package/dist/svgrid-wrapper.types.d.ts +8 -2
- package/dist/themes/index.d.ts +10 -0
- package/dist/themes/index.js +26 -0
- package/package.json +3 -2
- package/src/DockNodeView.svelte +1 -1
- package/src/GridFooter.svelte +0 -40
- package/src/GridMenus.svelte +0 -77
- package/src/SvAccordion.test.ts +5 -5
- package/src/SvAvatar.svelte +1 -1
- package/src/SvButtonGroup.test.ts +6 -6
- package/src/SvCalendar.test.ts +1 -1
- package/src/SvCheckBox.svelte +2 -1
- package/src/SvCircularProgress.svelte +1 -1
- package/src/SvDateRangeInput.svelte +1 -1
- package/src/SvFileUpload.test.ts +1 -1
- package/src/SvGrid.controller.svelte.ts +367 -22
- package/src/SvGrid.css +103 -3
- package/src/SvGrid.svelte +164 -35
- package/src/SvGrid.types.ts +81 -1
- package/src/SvGridChart.svelte +2 -10
- package/src/SvGridDropdown.svelte +38 -6
- package/src/SvGridSelect.svelte +12 -5
- package/src/SvGroupCell.svelte +9 -2
- package/src/SvKbd.svelte +1 -1
- package/src/SvList.svelte +1 -1
- package/src/SvMenubar.svelte +1 -1
- package/src/SvMultiSelect.svelte +12 -4
- package/src/SvOtpInput.svelte +1 -1
- package/src/SvPasswordInput.svelte +1 -1
- package/src/SvProgress.svelte +1 -1
- package/src/SvRating.svelte +1 -1
- package/src/SvResult.svelte +1 -1
- package/src/SvRichText.svelte +35 -16
- package/src/SvSwitchButton.svelte +2 -1
- package/src/SvToaster.svelte +1 -1
- package/src/SvTour.svelte +4 -0
- package/src/SvTree.svelte +9 -2
- package/src/SvTreeSelect.svelte +6 -4
- package/src/ai.test.ts +1 -1
- package/src/build-api.ts +15 -105
- package/src/builtin-editors.test.ts +3 -1
- package/src/cell-formatting.test.ts +1 -1
- package/src/cell-render.test.ts +27 -3
- package/src/cell-render.ts +122 -95
- package/src/cell-values.ts +4 -0
- package/src/chart-export.test.ts +2 -2
- package/src/chart.ts +1 -2
- package/src/clipboard.test.ts +0 -2
- package/src/clipboard.ts +0 -102
- package/src/columns.ts +0 -115
- package/src/core.ts +157 -8
- package/src/createMenu.svelte.ts +0 -1
- package/src/editing.test.ts +1 -1
- package/src/editing.ts +2 -105
- package/src/editor-contract.ts +12 -5
- package/src/editor-registry.grid.test.ts +48 -2
- package/src/editor-registry.test.ts +3 -1
- package/src/editor-registry.ts +23 -7
- package/src/export-format.ts +3 -0
- package/src/facet-buckets.test.ts +33 -33
- package/src/features.ts +0 -90
- package/src/group-display.test.ts +91 -1
- package/src/group-display.ts +83 -0
- package/src/index.ts +9 -0
- package/src/keyboard-handlers.coverage.test.ts +1 -1
- package/src/keyboard-handlers.ts +17 -120
- package/src/list-power.test.ts +2 -2
- package/src/menus.test.ts +4 -5
- package/src/menus.ts +0 -112
- package/src/named-views.coverage.test.ts +3 -3
- package/src/new-features.test.ts +3 -1
- package/src/recurrence.ts +3 -2
- package/src/row-drag.test.ts +1 -1
- package/src/row-resize.test.ts +41 -42
- package/src/scheduler-model.ts +1 -1
- package/src/scroll-sync.test.ts +4 -7
- package/src/scroll-sync.ts +0 -122
- package/src/selection.ts +0 -109
- package/src/server-data-source.coverage.test.ts +1 -1
- package/src/server-group-model.test.ts +8 -8
- package/src/spreadsheet.test.ts +0 -1
- package/src/summaries.ts +0 -112
- package/src/svgrid-wrapper.types.ts +11 -9
- package/src/svgrid.async-editor-options.test.ts +271 -0
- package/src/svgrid.auto-row-height.test.ts +204 -0
- package/src/svgrid.behavior.test.ts +0 -1
- package/src/svgrid.charting.test.ts +4 -4
- package/src/svgrid.column-reorder.test.ts +0 -8
- package/src/svgrid.comments-autocomplete.test.ts +8 -2
- package/src/svgrid.context-menu.test.ts +25 -9
- package/src/svgrid.filter-depth.test.ts +163 -0
- package/src/svgrid.filter-menu-listbox.svelte.test.ts +16 -2
- package/src/svgrid.filter-menu-scroll.test.ts +10 -2
- package/src/svgrid.grand-total.test.ts +188 -0
- package/src/svgrid.group-display-mode.test.ts +171 -0
- package/src/svgrid.group-footers.test.ts +121 -0
- package/src/svgrid.group-pagination.test.ts +153 -0
- package/src/svgrid.tree-data.test.ts +186 -0
- package/src/svgriddropdown.async-panel.svelte.test.ts +195 -0
- package/src/themes/index.ts +42 -0
- package/src/tree-edit.test.ts +4 -4
- package/src/tree-row-model.test.ts +168 -0
- package/src/ui-localization.test.ts +113 -0
- package/themes/ag-alpine.css +22 -0
- package/themes/antd.css +22 -0
- package/themes/atlassian.css +22 -0
- package/themes/bootstrap.css +22 -0
- package/themes/carbon.css +22 -0
- package/themes/catppuccin.css +22 -0
- package/themes/dracula.css +22 -0
- package/themes/ember.css +22 -0
- package/themes/excel.css +22 -0
- package/themes/fluent.css +22 -0
- package/themes/github.css +22 -0
- package/themes/linear.css +22 -0
- package/themes/material.css +22 -0
- package/themes/nord.css +22 -0
- package/themes/notion.css +22 -0
- package/themes/salesforce.css +22 -0
- package/themes/sap.css +22 -0
- package/themes/shadcn.css +22 -0
- package/themes/tailwind.css +22 -0
- package/themes/vercel.css +22 -0
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
applyExcelFilter,
|
|
3
|
+
applyGroupAggregate,
|
|
3
4
|
normalizeForFilter,
|
|
4
5
|
createColumnVirtualizer,
|
|
5
6
|
createCoreRowModel,
|
|
6
7
|
createExpandedRowModel,
|
|
7
8
|
createFilteredRowModel,
|
|
8
9
|
createGroupedRowModel,
|
|
10
|
+
createTreeRowModel,
|
|
9
11
|
createSvelteVirtualizer,
|
|
10
12
|
createSortedRowModel,
|
|
11
13
|
createSvGrid,
|
|
12
14
|
getGridCellDomId,
|
|
13
15
|
sortFns,
|
|
14
|
-
tableFeatures,
|
|
15
|
-
rowSortingFeature,
|
|
16
|
-
columnFilteringFeature,
|
|
17
|
-
columnGroupingFeature,
|
|
18
16
|
type CellEditorOption,
|
|
19
17
|
type Column,
|
|
20
18
|
type ColumnDef,
|
|
@@ -36,7 +34,6 @@ import {
|
|
|
36
34
|
formatNeedsStats,
|
|
37
35
|
type ColumnStat,
|
|
38
36
|
} from "./conditional-formatting";
|
|
39
|
-
import SvGridDropdown from "./SvGridDropdown.svelte";
|
|
40
37
|
import type {
|
|
41
38
|
Props,
|
|
42
39
|
SelectionRange,
|
|
@@ -88,6 +85,7 @@ import {
|
|
|
88
85
|
hiddenLeavesForCollapse,
|
|
89
86
|
} from "./column-groups";
|
|
90
87
|
import { resolveColumnId } from "./column-id";
|
|
88
|
+
import { buildAutoGroupColumns, insertGroupFooters, paginateGroupedRows } from "./group-display";
|
|
91
89
|
import {
|
|
92
90
|
createGridApi,
|
|
93
91
|
} from "./build-api";
|
|
@@ -667,6 +665,10 @@ export function createSvGridController<
|
|
|
667
665
|
const externalPaginationEnabled = $derived(props.externalPagination === true);
|
|
668
666
|
const passthroughSortedRowModel = ({ rows }: { rows: Array<Row<TData>> }) =>
|
|
669
667
|
rows;
|
|
668
|
+
// Client tree data. Structural like external sort, so captured once at mount -
|
|
669
|
+
// the row-model pipeline is read before any later prop change could apply.
|
|
670
|
+
// svelte-ignore state_referenced_locally
|
|
671
|
+
const treeDataConfig = props.treeData;
|
|
670
672
|
|
|
671
673
|
|
|
672
674
|
const grid = createSvGrid({
|
|
@@ -687,7 +689,14 @@ export function createSvGridController<
|
|
|
687
689
|
sortedRowModel: externalSortEnabled
|
|
688
690
|
? passthroughSortedRowModel
|
|
689
691
|
: createSortedRowModel<TData>(sortFns),
|
|
690
|
-
|
|
692
|
+
// Tree nesting replaces grouping when `treeData` is set - a row cannot
|
|
693
|
+
// be both a hierarchy node and bucketed under a group banner.
|
|
694
|
+
groupedRowModel: treeDataConfig
|
|
695
|
+
? createTreeRowModel<TData>({
|
|
696
|
+
parentField: treeDataConfig.parentField,
|
|
697
|
+
idField: treeDataConfig.idField,
|
|
698
|
+
})
|
|
699
|
+
: createGroupedRowModel<TData>(),
|
|
691
700
|
expandedRowModel: createExpandedRowModel<TData>(),
|
|
692
701
|
};
|
|
693
702
|
},
|
|
@@ -771,6 +780,103 @@ export function createSvGridController<
|
|
|
771
780
|
userColumnOrder = props.columnOrder ? [...props.columnOrder] : [];
|
|
772
781
|
});
|
|
773
782
|
|
|
783
|
+
// Declared here rather than beside the other pagination/state derivations:
|
|
784
|
+
// the auto-group column pipeline below reads it, and a `const` used above its
|
|
785
|
+
// declaration is a type error even though `$derived` is lazy enough at runtime.
|
|
786
|
+
const groupingColumns = $derived.by(() => {
|
|
787
|
+
gridStateVersion;
|
|
788
|
+
return grid.getState().grouping ?? [];
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Row-grouping display mode. `groupRows` (the default) renders a full-width
|
|
793
|
+
* banner per group - the historical behaviour. `singleColumn` folds every
|
|
794
|
+
* grouping level into one synthetic column; `multipleColumns` gives each
|
|
795
|
+
* grouped field its own. Both hide the grouped SOURCE columns, since their
|
|
796
|
+
* values now live in the auto column.
|
|
797
|
+
*/
|
|
798
|
+
const groupDisplayMode = $derived(props.groupDisplayMode ?? "groupRows");
|
|
799
|
+
const autoGroupSpec = $derived(
|
|
800
|
+
buildAutoGroupColumns(groupingColumns, groupDisplayMode),
|
|
801
|
+
);
|
|
802
|
+
/** True when group state is shown in columns rather than banner rows. */
|
|
803
|
+
const groupColumnMode = $derived(autoGroupSpec.autoColumns.length > 0);
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* The synthetic auto-group columns as real `Column` objects, so the rest of
|
|
807
|
+
* the pipeline (widths, pinning, virtualization, headers) treats them like
|
|
808
|
+
* any other column. They carry no `field` - their cell content is resolved
|
|
809
|
+
* from the row's group state at render time.
|
|
810
|
+
*/
|
|
811
|
+
const autoGroupColumns = $derived.by(() => {
|
|
812
|
+
if (!groupColumnMode) return [] as Column<TData>[];
|
|
813
|
+
const sourceById = new Map(grid.getAllColumns().map((c) => [c.id, c]));
|
|
814
|
+
return autoGroupSpec.autoColumns.map((spec) => {
|
|
815
|
+
const source = spec.field ? sourceById.get(spec.field) : undefined;
|
|
816
|
+
const sourceHeader = source?.columnDef.header;
|
|
817
|
+
const header =
|
|
818
|
+
spec.field == null
|
|
819
|
+
? (props.autoGroupColumnHeader ?? "Group")
|
|
820
|
+
: typeof sourceHeader === "string"
|
|
821
|
+
? sourceHeader
|
|
822
|
+
: spec.field;
|
|
823
|
+
const column: Column<TData> = {
|
|
824
|
+
id: spec.id,
|
|
825
|
+
depth: 0,
|
|
826
|
+
columnDef: {
|
|
827
|
+
header,
|
|
828
|
+
width: props.autoGroupColumnWidth ?? 220,
|
|
829
|
+
sortable: false,
|
|
830
|
+
filterable: false,
|
|
831
|
+
} as never,
|
|
832
|
+
getCanSort: () => false,
|
|
833
|
+
getCanFilter: () => false,
|
|
834
|
+
getIsSorted: () => false,
|
|
835
|
+
getToggleSortingHandler: () => () => {},
|
|
836
|
+
};
|
|
837
|
+
return column;
|
|
838
|
+
});
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* What an auto-group column shows for a given row.
|
|
843
|
+
*
|
|
844
|
+
* `singleColumn` puts every level in one column, so a group row renders its
|
|
845
|
+
* own level (indented by depth) and leaf rows stay blank - the ancestry is
|
|
846
|
+
* already visible in the banner rows above.
|
|
847
|
+
*
|
|
848
|
+
* `multipleColumns` gives each grouped field a column, so a group row only
|
|
849
|
+
* writes into the column matching ITS level and leaves the others blank.
|
|
850
|
+
*
|
|
851
|
+
* Returns null when the cell should render nothing.
|
|
852
|
+
*/
|
|
853
|
+
function autoGroupCell(
|
|
854
|
+
row: Row<TData>,
|
|
855
|
+
columnId: string,
|
|
856
|
+
): { label: string; count: number; depth: number } | null {
|
|
857
|
+
if (!isGroupRow(row)) return null;
|
|
858
|
+
const depth = row.depth ?? 0;
|
|
859
|
+
if (columnId !== "__autoGroup") {
|
|
860
|
+
// multipleColumns: only the column for this row's own level renders.
|
|
861
|
+
const field = groupingColumns[depth];
|
|
862
|
+
if (!field || `__group_${field}` !== columnId) return null;
|
|
863
|
+
}
|
|
864
|
+
const groupingColumnId = groupingColumns[depth] ?? "";
|
|
865
|
+
const sourceColumn = grid
|
|
866
|
+
.getAllColumns()
|
|
867
|
+
.find((c) => c.id === groupingColumnId);
|
|
868
|
+
const raw = row.getCellValueByColumnId(groupingColumnId);
|
|
869
|
+
const label = sourceColumn
|
|
870
|
+
? formatCellValue(sourceColumn, raw, row)
|
|
871
|
+
: String(raw ?? "");
|
|
872
|
+
return {
|
|
873
|
+
label,
|
|
874
|
+
count: row.leafCount ?? row.subRows?.length ?? 0,
|
|
875
|
+
// singleColumn indents by level; multipleColumns already separates them.
|
|
876
|
+
depth: columnId === "__autoGroup" ? depth : 0,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
|
|
774
880
|
const allColumns = $derived.by(() => {
|
|
775
881
|
let raw = grid
|
|
776
882
|
.getAllColumns()
|
|
@@ -778,8 +884,13 @@ export function createSvGridController<
|
|
|
778
884
|
(column) =>
|
|
779
885
|
!hiddenColumns[column.id] &&
|
|
780
886
|
!hiddenByGroupCollapse[column.id] &&
|
|
781
|
-
!isHiddenByResponsive(column)
|
|
887
|
+
!isHiddenByResponsive(column) &&
|
|
888
|
+
// In a column display mode the grouped columns are folded into the
|
|
889
|
+
// auto column(s), so showing them too would just duplicate the value.
|
|
890
|
+
!autoGroupSpec.hiddenSourceIds.has(column.id),
|
|
782
891
|
);
|
|
892
|
+
// Auto-group columns lead, like a row header.
|
|
893
|
+
if (autoGroupColumns.length) raw = [...autoGroupColumns, ...raw];
|
|
783
894
|
// Apply user reorder (if any). Unknown ids in userColumnOrder are
|
|
784
895
|
// skipped; columns not in userColumnOrder keep their original
|
|
785
896
|
// relative order after the user-ordered ones.
|
|
@@ -821,7 +932,21 @@ export function createSvGridController<
|
|
|
821
932
|
const headers: (typeof base)[number]["headers"] = [];
|
|
822
933
|
for (const column of allColumns) {
|
|
823
934
|
const header = byId.get(column.id);
|
|
824
|
-
if (header)
|
|
935
|
+
if (header) {
|
|
936
|
+
headers.push(header);
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
// Auto-group columns have no engine header. Synthesize one rather than
|
|
940
|
+
// skipping: the header row indexes `headers` BY POSITION against the
|
|
941
|
+
// rendered columns, so a gap would shift every later header by one.
|
|
942
|
+
const synthetic = {
|
|
943
|
+
id: column.id,
|
|
944
|
+
isPlaceholder: false,
|
|
945
|
+
colSpan: 1,
|
|
946
|
+
column,
|
|
947
|
+
getContext: () => ({ header: synthetic, column, table: grid }),
|
|
948
|
+
} as (typeof base)[number]["headers"][number];
|
|
949
|
+
headers.push(synthetic);
|
|
825
950
|
}
|
|
826
951
|
return [{ id: base[0]!.id, headers }];
|
|
827
952
|
});
|
|
@@ -1098,10 +1223,6 @@ export function createSvGridController<
|
|
|
1098
1223
|
return directions;
|
|
1099
1224
|
});
|
|
1100
1225
|
|
|
1101
|
-
const groupingColumns = $derived.by(() => {
|
|
1102
|
-
gridStateVersion;
|
|
1103
|
-
return grid.getState().grouping ?? [];
|
|
1104
|
-
});
|
|
1105
1226
|
|
|
1106
1227
|
const paginationState = $derived.by(() => {
|
|
1107
1228
|
gridStateVersion;
|
|
@@ -1221,22 +1342,137 @@ export function createSvGridController<
|
|
|
1221
1342
|
* the full dataset rather than the current page (see the comment above
|
|
1222
1343
|
* `_rowModels`).
|
|
1223
1344
|
*/
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1345
|
+
/**
|
|
1346
|
+
* Grouped pagination. `allRowsBeforePagination` interleaves group banners with
|
|
1347
|
+
* data rows, so a plain slice spends the page budget on banners - ten groups of
|
|
1348
|
+
* two at `pageSize: 10` used to show about three data rows (#73). When grouping
|
|
1349
|
+
* is active we page by DATA rows and reprint each page's ancestor banners.
|
|
1350
|
+
* Null when grouping is off, so the flat path stays a cheap slice.
|
|
1351
|
+
*/
|
|
1352
|
+
const groupedPage = $derived.by(() => {
|
|
1353
|
+
if (!paginationEnabled || externalPaginationEnabled) return null;
|
|
1354
|
+
if (!groupingColumns.length) return null;
|
|
1228
1355
|
const { pageIndex, pageSize } = paginationState;
|
|
1229
|
-
|
|
1230
|
-
|
|
1356
|
+
return paginateGroupedRows(allRowsBeforePagination, {
|
|
1357
|
+
getDepth: (row: any) => row.depth ?? 0,
|
|
1358
|
+
isGroup: (row: any) => isGroupRow(row),
|
|
1359
|
+
// A collapsed group is the visible unit and takes a page slot; an expanded
|
|
1360
|
+
// one is a banner reprinted above its children.
|
|
1361
|
+
isExpanded: (row: any) => row.getIsExpanded?.() === true,
|
|
1362
|
+
pageIndex,
|
|
1363
|
+
pageSize,
|
|
1364
|
+
});
|
|
1231
1365
|
});
|
|
1232
1366
|
|
|
1367
|
+
/**
|
|
1368
|
+
* The grand-total row: one synthetic row carrying each column's `aggregate`
|
|
1369
|
+
* over the whole filtered set. Shaped like a group footer so it renders
|
|
1370
|
+
* through the normal cell path - totals land under the columns they belong
|
|
1371
|
+
* to, and `getCanExpand`/`getIsExpanded` are off so nothing treats it as a
|
|
1372
|
+
* banner. Returns null when no column aggregates (nothing to total).
|
|
1373
|
+
*
|
|
1374
|
+
* Aggregates the LEAF rows: `allRowsBeforePagination` interleaves group
|
|
1375
|
+
* banners once grouping is on, and those carry per-group subtotals that
|
|
1376
|
+
* would otherwise be summed a second time.
|
|
1377
|
+
*/
|
|
1378
|
+
const buildGrandTotalRow = () => {
|
|
1379
|
+
const leaves = allRowsBeforePagination.filter((row: any) => !isGroupRow(row));
|
|
1380
|
+
const columns = grid.getAllColumns();
|
|
1381
|
+
const original: Record<string, unknown> = {};
|
|
1382
|
+
let hasAggregate = false;
|
|
1383
|
+
for (const column of columns) {
|
|
1384
|
+
const field = column.columnDef.field;
|
|
1385
|
+
const agg = column.columnDef.aggregate;
|
|
1386
|
+
if (!field || !agg) continue;
|
|
1387
|
+
hasAggregate = true;
|
|
1388
|
+
original[field] = applyGroupAggregate(agg, column.id, leaves as any);
|
|
1389
|
+
}
|
|
1390
|
+
if (!hasAggregate) return null;
|
|
1391
|
+
return {
|
|
1392
|
+
id: "__grand_total__",
|
|
1393
|
+
index: -1,
|
|
1394
|
+
original,
|
|
1395
|
+
depth: 0,
|
|
1396
|
+
subRows: [],
|
|
1397
|
+
leafCount: leaves.length,
|
|
1398
|
+
__groupFooter: true,
|
|
1399
|
+
__grandTotal: true,
|
|
1400
|
+
getCanExpand: () => false,
|
|
1401
|
+
getIsExpanded: () => false,
|
|
1402
|
+
toggleExpanded: () => {},
|
|
1403
|
+
getIsSelected: () => false,
|
|
1404
|
+
toggleSelected: () => {},
|
|
1405
|
+
getAllCells: () => [],
|
|
1406
|
+
getCellValueByColumnId: (columnId: string) => {
|
|
1407
|
+
const col = columns.find((c) => c.id === columnId);
|
|
1408
|
+
const field = col?.columnDef.field;
|
|
1409
|
+
return field && field in original ? original[field] : undefined;
|
|
1410
|
+
},
|
|
1411
|
+
} as any;
|
|
1412
|
+
};
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Visible rows for the current page. Applied last so filters operate on
|
|
1416
|
+
* the full dataset rather than the current page (see the comment above
|
|
1417
|
+
* `_rowModels`).
|
|
1418
|
+
*/
|
|
1419
|
+
const allRows = $derived.by(() => {
|
|
1420
|
+
const paged = (() => {
|
|
1421
|
+
const rows = allRowsBeforePagination;
|
|
1422
|
+
// External pagination: `data` already IS the current page - never slice.
|
|
1423
|
+
if (!paginationEnabled || externalPaginationEnabled) return rows;
|
|
1424
|
+
if (groupedPage) return groupedPage.rows;
|
|
1425
|
+
const { pageIndex, pageSize } = paginationState;
|
|
1426
|
+
const start = pageIndex * pageSize;
|
|
1427
|
+
return rows.slice(start, start + pageSize);
|
|
1428
|
+
})();
|
|
1429
|
+
const wantGroupFooters = Boolean(props.groupFooters) && groupingColumns.length > 0;
|
|
1430
|
+
// The grand total belongs at the END of the dataset, so it is appended only
|
|
1431
|
+
// on the last page - otherwise every page would end in a "total" that isn't
|
|
1432
|
+
// one. With pagination off (or external, where the consumer owns paging and
|
|
1433
|
+
// `data` is already the page) there is only one page to be last.
|
|
1434
|
+
const isLastPage = (() => {
|
|
1435
|
+
if (!paginationEnabled || externalPaginationEnabled) return true;
|
|
1436
|
+
const { pageIndex, pageSize } = paginationState;
|
|
1437
|
+
const total = groupedPage ? groupedPage.dataRowCount : allRowsBeforePagination.length;
|
|
1438
|
+
return pageIndex >= Math.ceil(total / pageSize) - 1;
|
|
1439
|
+
})();
|
|
1440
|
+
const wantGrandTotal = Boolean(props.grandTotalRow) && isLastPage;
|
|
1441
|
+
if (!wantGroupFooters && !wantGrandTotal) return paged;
|
|
1442
|
+
// Footers are inserted AFTER paging so they never eat page budget. A footer
|
|
1443
|
+
// is a clone of its group row, which already carries the group's aggregate
|
|
1444
|
+
// values - so it renders through the normal cell path and the totals land
|
|
1445
|
+
// under the columns they belong to, with `getCanExpand` off so nothing
|
|
1446
|
+
// treats it as a banner.
|
|
1447
|
+
return insertGroupFooters(paged, {
|
|
1448
|
+
getDepth: (row: any) => row.depth ?? 0,
|
|
1449
|
+
isGroup: (row: any) => isGroupRow(row),
|
|
1450
|
+
includeGroupFooter: wantGroupFooters,
|
|
1451
|
+
includeGrandTotalFooter: wantGrandTotal,
|
|
1452
|
+
makeGrandTotal: buildGrandTotalRow,
|
|
1453
|
+
makeFooter: (group: any) => ({
|
|
1454
|
+
...group,
|
|
1455
|
+
id: `${group.id}__footer`,
|
|
1456
|
+
subRows: [],
|
|
1457
|
+
__groupFooter: true,
|
|
1458
|
+
getCanExpand: () => false,
|
|
1459
|
+
getIsExpanded: () => false,
|
|
1460
|
+
}),
|
|
1461
|
+
}) as typeof paged;
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
/** Rows the pager divides into pages: data rows only once grouping is on. */
|
|
1465
|
+
const paginatedRowTotal = $derived(
|
|
1466
|
+
groupedPage ? groupedPage.dataRowCount : allRowsBeforePagination.length,
|
|
1467
|
+
);
|
|
1468
|
+
|
|
1233
1469
|
// When a filter reduces the dataset, the stored pageIndex can point beyond
|
|
1234
1470
|
// the last valid page. Reset to page 0 so the grid never shows a blank body.
|
|
1235
1471
|
// Skipped for external pagination, where the consumer owns pageIndex.
|
|
1236
1472
|
$effect(() => {
|
|
1237
1473
|
if (!paginationEnabled || externalPaginationEnabled) return;
|
|
1238
1474
|
const { pageIndex, pageSize } = paginationState;
|
|
1239
|
-
const pageCount = Math.ceil(
|
|
1475
|
+
const pageCount = Math.ceil(paginatedRowTotal / pageSize);
|
|
1240
1476
|
if (pageCount > 0 && pageIndex >= pageCount) {
|
|
1241
1477
|
grid.setPagination({ pageIndex: 0, pageSize });
|
|
1242
1478
|
}
|
|
@@ -1245,7 +1481,7 @@ export function createSvGridController<
|
|
|
1245
1481
|
// Footer-facing pagination values. In external mode they come from the
|
|
1246
1482
|
// consumer-controlled props; otherwise from the local row model + state.
|
|
1247
1483
|
const paginationTotalRows = $derived(
|
|
1248
|
-
externalPaginationEnabled ? (props.rowCount ?? 0) :
|
|
1484
|
+
externalPaginationEnabled ? (props.rowCount ?? 0) : paginatedRowTotal,
|
|
1249
1485
|
);
|
|
1250
1486
|
const paginationPageIndex = $derived(
|
|
1251
1487
|
externalPaginationEnabled ? (props.pageIndex ?? 0) : paginationState.pageIndex,
|
|
@@ -2223,6 +2459,75 @@ export function createSvGridController<
|
|
|
2223
2459
|
el.removeEventListener("scroll-change", onHorizontal as EventListener);
|
|
2224
2460
|
});
|
|
2225
2461
|
|
|
2462
|
+
// --- Auto row height -------------------------------------------------
|
|
2463
|
+
// Measured natural heights, keyed by row index. A plain Map (not $state):
|
|
2464
|
+
// it is written from a ResizeObserver during layout, and making it reactive
|
|
2465
|
+
// would re-run the render that is doing the measuring. `autoRowHeightVersion`
|
|
2466
|
+
// is the reactive signal instead, bumped only when a height actually changes.
|
|
2467
|
+
const measuredRowHeights = new Map<number, number>();
|
|
2468
|
+
let autoRowHeightVersion = $state(0);
|
|
2469
|
+
// A per-row `rowHeight` function already supplies heights, so auto-measuring
|
|
2470
|
+
// would fight it. Fixed numbers are fine - they become the pre-measure estimate.
|
|
2471
|
+
const autoRowHeightOn = $derived(
|
|
2472
|
+
props.autoRowHeight === true && typeof props.rowHeight !== "function",
|
|
2473
|
+
);
|
|
2474
|
+
const autoRowHeightFallback = $derived(
|
|
2475
|
+
typeof props.rowHeight === "number" ? props.rowHeight : 30,
|
|
2476
|
+
);
|
|
2477
|
+
|
|
2478
|
+
/** Report a row's measured height. Called by the view's measuring action.
|
|
2479
|
+
* Sub-pixel churn is ignored so a fractional layout can't loop. */
|
|
2480
|
+
function reportRowHeight(index: number, height: number): void {
|
|
2481
|
+
if (!autoRowHeightOn || height <= 0) return;
|
|
2482
|
+
const prev = measuredRowHeights.get(index);
|
|
2483
|
+
if (prev !== undefined && Math.abs(prev - height) < 1) return;
|
|
2484
|
+
measuredRowHeights.set(index, height);
|
|
2485
|
+
autoRowHeightVersion += 1;
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
/**
|
|
2489
|
+
* Svelte action for a `<tr>` under `autoRowHeight`. Measures the row once on
|
|
2490
|
+
* mount and again whenever its content reflows (a resized column rewraps
|
|
2491
|
+
* text), reporting the natural height back to the virtualizer.
|
|
2492
|
+
*
|
|
2493
|
+
* Uses the shared rAF-deferred observer: the callback bumps state that
|
|
2494
|
+
* re-lays-out the observed element, which is exactly the pattern that trips
|
|
2495
|
+
* "ResizeObserver loop completed with undelivered notifications" when handled
|
|
2496
|
+
* synchronously.
|
|
2497
|
+
*/
|
|
2498
|
+
function measureRowHeight(node: HTMLElement, index: number) {
|
|
2499
|
+
let current = index;
|
|
2500
|
+
let stop: (() => void) | null = null;
|
|
2501
|
+
const measure = () => reportRowHeight(current, node.getBoundingClientRect().height);
|
|
2502
|
+
const attach = () => {
|
|
2503
|
+
stop?.();
|
|
2504
|
+
stop = autoRowHeightOn ? observeSizeRaf(node, measure) : null;
|
|
2505
|
+
if (autoRowHeightOn) measure();
|
|
2506
|
+
};
|
|
2507
|
+
attach();
|
|
2508
|
+
return {
|
|
2509
|
+
update(next: number) {
|
|
2510
|
+
if (next === current) return;
|
|
2511
|
+
current = next;
|
|
2512
|
+
// A recycled row now shows different data - re-measure under its new index.
|
|
2513
|
+
measure();
|
|
2514
|
+
},
|
|
2515
|
+
destroy() {
|
|
2516
|
+
stop?.();
|
|
2517
|
+
},
|
|
2518
|
+
};
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// Row index -> row identity changes when the data does, so stale measurements
|
|
2522
|
+
// would size new rows by the old ones. Drop them when the row set changes.
|
|
2523
|
+
$effect(() => {
|
|
2524
|
+
void allRows.length;
|
|
2525
|
+
void internalData;
|
|
2526
|
+
if (!autoRowHeightOn || measuredRowHeights.size === 0) return;
|
|
2527
|
+
measuredRowHeights.clear();
|
|
2528
|
+
autoRowHeightVersion += 1;
|
|
2529
|
+
});
|
|
2530
|
+
|
|
2226
2531
|
$effect(() => {
|
|
2227
2532
|
// When containerHeight is a string (e.g. "100%") the actual pixel height
|
|
2228
2533
|
// depends on the parent layout - read it from the live scroll container.
|
|
@@ -2236,9 +2541,18 @@ export function createSvGridController<
|
|
|
2236
2541
|
? (scrollContainer?.clientHeight ?? 520)
|
|
2237
2542
|
: (props.containerHeight ?? 520);
|
|
2238
2543
|
const rh = props.rowHeight;
|
|
2544
|
+
// Auto height: read measured sizes, falling back to the fixed rowHeight as
|
|
2545
|
+
// the estimate for rows that have not rendered yet. Touch the version so a
|
|
2546
|
+
// new measurement re-runs this effect (the Map itself is not reactive).
|
|
2547
|
+
autoRowHeightVersion;
|
|
2548
|
+
const estimateSize = autoRowHeightOn
|
|
2549
|
+
? (index: number) => measuredRowHeights.get(index) ?? autoRowHeightFallback
|
|
2550
|
+
: typeof rh === "function"
|
|
2551
|
+
? rh
|
|
2552
|
+
: (rh ?? 30);
|
|
2239
2553
|
virtualizer.setOptions({
|
|
2240
2554
|
count: allRows.length,
|
|
2241
|
-
estimateSize
|
|
2555
|
+
estimateSize,
|
|
2242
2556
|
overscan: props.overscan ?? 8,
|
|
2243
2557
|
viewportHeight,
|
|
2244
2558
|
});
|
|
@@ -2313,6 +2627,20 @@ export function createSvGridController<
|
|
|
2313
2627
|
* are resolved on every call because they can change as other cells in
|
|
2314
2628
|
* the same row change (the whole point of cascading editors). */
|
|
2315
2629
|
const editorOptionsCache: Record<string, CellEditorOption[]> = {};
|
|
2630
|
+
// Resolved async `editorOptions`, keyed by column id (static source) or
|
|
2631
|
+
// `columnId::rowId` (per-row cascade). Reactive so a fetch landing re-renders
|
|
2632
|
+
// the open editor; `...Pending` both drives the loading state and dedupes the
|
|
2633
|
+
// request across the many synchronous render calls.
|
|
2634
|
+
let asyncEditorOptions = $state<Record<string, CellEditorOption[]>>({});
|
|
2635
|
+
// Deliberately NOT reactive: this is written during render (the resolver runs
|
|
2636
|
+
// inside the template), and mutating $state there is a Svelte 5 error that
|
|
2637
|
+
// takes the whole editor down with it. Only the resolved result needs to be
|
|
2638
|
+
// reactive; the loading flag is derived from "thenable source, no result yet".
|
|
2639
|
+
const asyncEditorOptionsPending = new Set<string>();
|
|
2640
|
+
// Columns whose per-row `editorOptions` has been observed returning a Promise.
|
|
2641
|
+
// Lets the resolver answer from cache without re-invoking the source, so an
|
|
2642
|
+
// async source fires one request per row instead of one per render.
|
|
2643
|
+
const asyncEditorColumns = new Set<string>();
|
|
2316
2644
|
|
|
2317
2645
|
|
|
2318
2646
|
|
|
@@ -2950,6 +3278,9 @@ export function createSvGridController<
|
|
|
2950
3278
|
get isCellEditableAt() { return isCellEditableAt; },
|
|
2951
3279
|
get sortDirectionByColumn() { return sortDirectionByColumn; },
|
|
2952
3280
|
get groupingColumns() { return groupingColumns; },
|
|
3281
|
+
get groupDisplayMode() { return groupDisplayMode; },
|
|
3282
|
+
get groupColumnMode() { return groupColumnMode; },
|
|
3283
|
+
get autoGroupCell() { return autoGroupCell; },
|
|
2953
3284
|
get paginationState() { return paginationState; },
|
|
2954
3285
|
get externalPaginationEnabled() { return externalPaginationEnabled; },
|
|
2955
3286
|
get paginationTotalRows() { return paginationTotalRows; },
|
|
@@ -3120,6 +3451,15 @@ export function createSvGridController<
|
|
|
3120
3451
|
get rowVirtualizationEnabled() { return rowVirtualizationEnabled; },
|
|
3121
3452
|
get columnVirtualizationEnabled() { return columnVirtualizationEnabled; },
|
|
3122
3453
|
get virtualRows() { return virtualRows; },
|
|
3454
|
+
get autoRowHeightOn() { return autoRowHeightOn; },
|
|
3455
|
+
/** Svelte action: measure this row and report its natural height. */
|
|
3456
|
+
get measureRowHeight() { return measureRowHeight; },
|
|
3457
|
+
/** A row's measured height, or undefined before it has been measured.
|
|
3458
|
+
* The non-virtualized body reads this directly (it has no virtual item). */
|
|
3459
|
+
get measuredRowHeightPx() {
|
|
3460
|
+
autoRowHeightVersion;
|
|
3461
|
+
return (index: number): number | undefined => measuredRowHeights.get(index);
|
|
3462
|
+
},
|
|
3123
3463
|
get virtualRowTotalSize() { return virtualRowTotalSize; },
|
|
3124
3464
|
get virtualRowStart() { return virtualRowStart; },
|
|
3125
3465
|
get virtualRowEnd() { return virtualRowEnd; },
|
|
@@ -3161,7 +3501,12 @@ export function createSvGridController<
|
|
|
3161
3501
|
get getCellDisplayValue() { return getCellDisplayValue; },
|
|
3162
3502
|
get getColumnAlign() { return getColumnAlign; },
|
|
3163
3503
|
get editorOptionsCache() { return editorOptionsCache; },
|
|
3504
|
+
get asyncEditorOptions() { return asyncEditorOptions; },
|
|
3505
|
+
set asyncEditorOptions(v) { asyncEditorOptions = v as never; },
|
|
3506
|
+
get asyncEditorOptionsPending() { return asyncEditorOptionsPending; },
|
|
3507
|
+
get asyncEditorColumns() { return asyncEditorColumns; },
|
|
3164
3508
|
get getColumnEditorOptions() { return getColumnEditorOptions; },
|
|
3509
|
+
get areEditorOptionsLoading() { return areEditorOptionsLoading; },
|
|
3165
3510
|
get formatListCellValue() { return formatListCellValue; },
|
|
3166
3511
|
get formatCellValue() { return formatCellValue; },
|
|
3167
3512
|
get getPinnedCellValue() { return getPinnedCellValue; },
|
|
@@ -3297,7 +3642,7 @@ export function createSvGridController<
|
|
|
3297
3642
|
const { onGridKeyDown, onWindowKeydown, onHeaderSortClick } = createKeyboard<TFeatures, TData>(ctx);
|
|
3298
3643
|
const { computeSummaries, hasRenderedColumn } = createSummaries<TFeatures, TData>(ctx);
|
|
3299
3644
|
const { updateFilterRow, updateFilterOperator, updateFilterMenuValue, updateFilterMenuValueTo, addFilterToken, removeFilterToken, toggleFilterToken, toggleCheckboxWithKeyboard, isColumnFiltered, closeMenus, openInSuggest, closeInSuggest, openChooseColumns, openColumnMenu, openFilterMenu, openOperatorMenu, sortColumnFromMenu, clearColumnSort, groupByColumnFromMenu, clearGroupingFromMenu, isFacetChecked, toggleFacetValue, setFacetSelection, setFilterTokens, isAllFacetsChecked, toggleAllFacets, clearColumnFilter, changePage, goToPage, setPageSize, openContextMenu, closeContextMenu, contextMenuItems, saveComment, removeComment, closeCommentEditor } = createMenus<TFeatures, TData>(ctx);
|
|
3300
|
-
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
3645
|
+
const { cellConditionalFormat, computeRowClass, computeCellClass, computeCellTooltip, computeCellValidity, computeCellNote, getColumnEditorOptions, areEditorOptionsLoading, formatListCellValue, formatCellValue, formatPinnedValue, computePinnedCellClass } = createCellRender<TFeatures, TData>(ctx);
|
|
3301
3646
|
const { isCellEditable, isCellEditableAt, getRowColumnValue, getCellDisplayValue, startEditingWithChar, startEditing, stopEditing, startFullRowEdit, setFullRowDraft, commitFullRowEdit, cancelFullRowEdit, saveEditingCell, applyHistoryStep, updateEditingCellValue, onEditorKeyDown, commitAndMoveByTab, focusOnMount, onCellDoubleClick, pasteFromClipboard, onGridPaste } = createEditing<TFeatures, TData>(ctx);
|
|
3302
3647
|
const { isRowSelected, toggleRowSelectionById, toggleSelectAllRows, setActiveCell, scrollActiveCellIntoView, setSelection, extendSelection, isCellInSelectedRange, getCellRangeEdges, getSelectionRects, isInFillPreview, fillMarqueeEdges, findColumnById, onCellPointerDown, onCellPointerEnter, endDragSelection, onWindowPointerMove, onCellClick, emitCellDoubleClick } = createSelection<TFeatures, TData>(ctx);
|
|
3303
3648
|
const { cellPinStyle, isColumnPinned, getCurrentColumnOrder, emitColumnOrder, setColumnOrderInternal, applyColumnDrop, onColumnHeaderDragStart, onColumnHeaderDragOver, onColumnHeaderDragLeave, onColumnHeaderDrop, onColumnHeaderDragEnd, pinColumnLeft, pinColumnRight, unpinColumn, toggleColumnVisibleInPanel, moveColumnInPanel, toggleGroupInPanel, getColumnBaseWidth, getColumnWidth, startColumnResize, onColumnResizeMove, endColumnResize, measureText, autosizeColumn, autosizeAllColumns, resetColumns } = createColumns<TFeatures, TData>(ctx);
|
package/src/SvGrid.css
CHANGED
|
@@ -902,8 +902,8 @@
|
|
|
902
902
|
pattern isn't being applied (it matches nothing until it compiles). */
|
|
903
903
|
.sv-grid-filter-value.sv-grid-filter-value-invalid,
|
|
904
904
|
.sv-grid-tp-filter-input.sv-grid-filter-value-invalid {
|
|
905
|
-
border-color: var(--sg-danger, #
|
|
906
|
-
box-shadow: 0 0 0 1px color-mix(in srgb, var(--sg-danger, #
|
|
905
|
+
border-color: var(--sg-danger, #dc2626) !important;
|
|
906
|
+
box-shadow: 0 0 0 1px color-mix(in srgb, var(--sg-danger, #dc2626) 45%, transparent);
|
|
907
907
|
}
|
|
908
908
|
|
|
909
909
|
/* In / Not-in chip (token) input: chips + a flex-growing text box share one
|
|
@@ -2130,6 +2130,104 @@ select.sv-grid-fr-editor {
|
|
|
2130
2130
|
background: var(--sg-selection-bg, #eaf2ff);
|
|
2131
2131
|
}
|
|
2132
2132
|
|
|
2133
|
+
/* Group subtotal row (`groupFooters`). Renders through the normal cell path so
|
|
2134
|
+
its totals sit under their own columns; a top rule + weight is what reads as
|
|
2135
|
+
"this closes the group above". Lighter than the banner on purpose - the
|
|
2136
|
+
banner opens a group, the footer just totals it. */
|
|
2137
|
+
.sv-grid-group-footer-row > .sv-grid-cell {
|
|
2138
|
+
background: var(--sg-header-bg, #eef2f8);
|
|
2139
|
+
border-top: 1px solid var(--sg-border, #d7dce5);
|
|
2140
|
+
font-weight: 600;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
/* Auto row height (`autoRowHeight`). The default cell truncates to one line
|
|
2144
|
+
(nowrap + ellipsis + overflow hidden), which would make a measured row height
|
|
2145
|
+
pointless - every row would measure the same. Let the text wrap so the row
|
|
2146
|
+
has something to grow to, and pad vertically since there is no fixed height
|
|
2147
|
+
centring the single line any more. */
|
|
2148
|
+
.sv-grid-row-auto-height > .sv-grid-cell {
|
|
2149
|
+
white-space: normal;
|
|
2150
|
+
overflow-wrap: anywhere;
|
|
2151
|
+
overflow: visible;
|
|
2152
|
+
text-overflow: clip;
|
|
2153
|
+
padding-top: 6px;
|
|
2154
|
+
padding-bottom: 6px;
|
|
2155
|
+
/* Cells stretch to the row's height rather than centring on one line. */
|
|
2156
|
+
vertical-align: top;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
/* Grand total (`grandTotalRow`). One step heavier than a group subtotal: it
|
|
2160
|
+
closes the whole dataset, not one group, so it takes a double top rule. */
|
|
2161
|
+
.sv-grid-grand-total-row > .sv-grid-cell {
|
|
2162
|
+
border-top: 3px double var(--sg-border, #d7dce5);
|
|
2163
|
+
font-weight: 700;
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
/* Client tree data (`treeData`). The indent is a sized spacer rather than
|
|
2167
|
+
padding on the cell so conditional-format backgrounds and data bars still
|
|
2168
|
+
span the full cell width. */
|
|
2169
|
+
.sv-grid-tree-indent {
|
|
2170
|
+
display: inline-block;
|
|
2171
|
+
flex: none;
|
|
2172
|
+
}
|
|
2173
|
+
.sv-grid-tree-toggle {
|
|
2174
|
+
display: inline-flex;
|
|
2175
|
+
align-items: center;
|
|
2176
|
+
justify-content: center;
|
|
2177
|
+
width: 14px;
|
|
2178
|
+
height: 14px;
|
|
2179
|
+
flex: none;
|
|
2180
|
+
margin-right: 3px;
|
|
2181
|
+
padding: 0;
|
|
2182
|
+
border: 0;
|
|
2183
|
+
border-radius: 3px;
|
|
2184
|
+
background: transparent;
|
|
2185
|
+
color: var(--sg-muted, #64748b);
|
|
2186
|
+
font-size: 10px;
|
|
2187
|
+
line-height: 1;
|
|
2188
|
+
cursor: pointer;
|
|
2189
|
+
}
|
|
2190
|
+
.sv-grid-tree-toggle:hover {
|
|
2191
|
+
background: var(--sg-header-bg, #eef2f8);
|
|
2192
|
+
color: var(--sg-fg, #0f172a);
|
|
2193
|
+
}
|
|
2194
|
+
/* One chevron for every expander in the library - the grid's tree rows, its
|
|
2195
|
+
group banners and auto-group columns, SvGroupCell and SvTree all draw the
|
|
2196
|
+
same `chevron-right` and rotate it open, rather than swapping between two
|
|
2197
|
+
different glyphs. */
|
|
2198
|
+
.sv-grid-tree-toggle .sv-grid-icon,
|
|
2199
|
+
.sv-grid-group-toggle .sv-grid-icon {
|
|
2200
|
+
width: 12px;
|
|
2201
|
+
height: 12px;
|
|
2202
|
+
transition: transform 0.15s ease;
|
|
2203
|
+
}
|
|
2204
|
+
.sv-grid-tree-toggle[aria-expanded="true"] .sv-grid-icon,
|
|
2205
|
+
.sv-grid-group-toggle[aria-expanded="true"] .sv-grid-icon {
|
|
2206
|
+
transform: rotate(90deg);
|
|
2207
|
+
}
|
|
2208
|
+
.sv-grid-tree-toggle:focus-visible {
|
|
2209
|
+
outline: 2px solid var(--sg-accent, #2563eb);
|
|
2210
|
+
outline-offset: 1px;
|
|
2211
|
+
}
|
|
2212
|
+
/* Auto-group column cell (`groupDisplayMode: singleColumn | multipleColumns`).
|
|
2213
|
+
The group row is an ordinary row here, so only the label needs weight - the
|
|
2214
|
+
full-width banner styling does not apply. */
|
|
2215
|
+
.sv-grid-autogroup-label {
|
|
2216
|
+
font-weight: 600;
|
|
2217
|
+
color: var(--sg-fg, #0f172a);
|
|
2218
|
+
overflow: hidden;
|
|
2219
|
+
text-overflow: ellipsis;
|
|
2220
|
+
white-space: nowrap;
|
|
2221
|
+
}
|
|
2222
|
+
|
|
2223
|
+
/* Keeps a childless row's value on the same x as its expandable siblings. */
|
|
2224
|
+
.sv-grid-tree-spacer {
|
|
2225
|
+
display: inline-block;
|
|
2226
|
+
width: 14px;
|
|
2227
|
+
flex: none;
|
|
2228
|
+
margin-right: 3px;
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2133
2231
|
.sv-grid-group-cell {
|
|
2134
2232
|
padding: 0 12px;
|
|
2135
2233
|
}
|
|
@@ -2209,7 +2307,9 @@ select.sv-grid-fr-editor {
|
|
|
2209
2307
|
line-height: 1.4;
|
|
2210
2308
|
resize: vertical;
|
|
2211
2309
|
outline: none;
|
|
2212
|
-
|
|
2310
|
+
/* --sg-focus-ring is a COLOR everywhere else (`outline: 2px solid ...`).
|
|
2311
|
+
Consume it as one here too, rather than as a whole box-shadow value. */
|
|
2312
|
+
box-shadow: 0 0 0 2px var(--sg-focus-ring, rgba(99, 102, 241, 0.40));
|
|
2213
2313
|
}
|
|
2214
2314
|
|
|
2215
2315
|
.sv-grid-autocomplete {
|