@svgrid/grid 2.1.6 → 2.1.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/dist/SvCarousel.svelte +35 -6
- package/dist/SvCarousel.svelte.d.ts +3 -0
- package/dist/SvColorInput.svelte +0 -1
- package/dist/SvContextMenu.svelte +26 -6
- package/dist/SvDrawer.svelte +5 -3
- package/dist/SvGrid.controller.svelte.d.ts +85 -2
- package/dist/SvGrid.controller.svelte.js +494 -1
- package/dist/SvGrid.css +142 -0
- package/dist/SvGrid.svelte +49 -27
- package/dist/SvGrid.types.d.ts +78 -0
- package/dist/SvGridBoard.svelte +71 -13
- package/dist/SvGridChart.svelte +41 -7
- package/dist/SvGridChart.svelte.d.ts +4 -0
- package/dist/SvGridChartPanel.svelte +485 -0
- package/dist/SvGridChartPanel.svelte.d.ts +28 -0
- package/dist/SvGridDropdown.svelte +18 -2
- package/dist/SvGroupCell.svelte +5 -5
- package/dist/SvMenu.svelte +25 -2
- package/dist/SvMenu.svelte.d.ts +3 -0
- package/dist/SvMenuList.svelte +31 -9
- package/dist/SvMenuList.svelte.d.ts +9 -1
- package/dist/SvNumberInput.svelte +1 -1
- package/dist/SvOtpInput.svelte +3 -2
- package/dist/SvPopover.svelte +13 -1
- package/dist/SvRowGroupPanel.svelte +23 -0
- package/dist/SvScrollArea.svelte +13 -2
- package/dist/SvScrollArea.svelte.d.ts +2 -0
- package/dist/SvTooltip.svelte +27 -2
- package/dist/SvTooltip.svelte.d.ts +3 -2
- package/dist/SvTour.svelte +30 -5
- package/dist/SvTour.svelte.d.ts +3 -0
- package/dist/SvTree.svelte +7 -3
- package/dist/SvTreeSelect.svelte +16 -9
- package/dist/build-api.js +51 -0
- package/dist/cdn/svgrid.js +17394 -16052
- package/dist/cdn/svgrid.svelte-external.js +16784 -15644
- package/dist/chart-export.d.ts +21 -0
- package/dist/chart-export.js +52 -11
- package/dist/chart.d.ts +30 -0
- package/dist/chart.js +53 -13
- package/dist/createAutocomplete.svelte.d.ts +6 -0
- package/dist/createAutocomplete.svelte.js +10 -0
- package/dist/createCombobox.svelte.d.ts +1 -0
- package/dist/createCombobox.svelte.js +1 -0
- package/dist/createCountryInput.svelte.d.ts +5 -0
- package/dist/createCountryInput.svelte.js +11 -0
- package/dist/createDropdownList.svelte.d.ts +4 -0
- package/dist/createDropdownList.svelte.js +7 -0
- package/dist/createPopoverSelect.svelte.d.ts +5 -3
- package/dist/createPopoverSelect.svelte.js +5 -3
- package/dist/createTree.svelte.d.ts +1 -0
- package/dist/createTree.svelte.js +15 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -2
- package/dist/svgrid-wrapper.types.d.ts +58 -0
- package/package.json +1 -1
- package/src/SvCarousel.svelte +35 -6
- package/src/SvColorInput.svelte +0 -1
- package/src/SvContextMenu.svelte +26 -6
- package/src/SvDrawer.svelte +5 -3
- package/src/SvGrid.controller.svelte.ts +481 -0
- package/src/SvGrid.css +142 -0
- package/src/SvGrid.svelte +49 -27
- package/src/SvGrid.types.ts +83 -0
- package/src/SvGridBoard.svelte +71 -13
- package/src/SvGridChart.svelte +41 -7
- package/src/SvGridChartPanel.svelte +485 -0
- package/src/SvGridDropdown.svelte +18 -2
- package/src/SvGroupCell.svelte +5 -5
- package/src/SvMenu.svelte +25 -2
- package/src/SvMenuList.svelte +31 -9
- package/src/SvNumberInput.svelte +1 -1
- package/src/SvOtpInput.svelte +3 -2
- package/src/SvPopover.svelte +13 -1
- package/src/SvRowGroupPanel.svelte +23 -0
- package/src/SvScrollArea.svelte +13 -2
- package/src/SvTooltip.svelte +27 -2
- package/src/SvTour.svelte +30 -5
- package/src/SvTree.svelte +7 -3
- package/src/SvTreeSelect.svelte +16 -9
- package/src/build-api.ts +49 -0
- package/src/chart-export.ts +57 -0
- package/src/chart.ts +70 -13
- package/src/createAutocomplete.svelte.ts +11 -0
- package/src/createCombobox.svelte.ts +1 -0
- package/src/createCountryInput.svelte.ts +12 -0
- package/src/createDropdownList.svelte.ts +8 -0
- package/src/createPopoverSelect.svelte.ts +5 -3
- package/src/createTree.svelte.ts +15 -2
- package/src/index.ts +6 -0
- package/src/overlays.test.ts +5 -0
- package/src/svgrid-wrapper.types.ts +62 -0
- package/src/svgrid.charting.test.ts +527 -0
- package/src/test-setup.ts +22 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { applyExcelFilter, normalizeForFilter, createColumnVirtualizer, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createSvelteVirtualizer, createSortedRowModel, createSvGrid, getGridCellDomId, sortFns, } from "./index";
|
|
1
|
+
import { applyExcelFilter, normalizeForFilter, createColumnVirtualizer, createCoreRowModel, createExpandedRowModel, createFilteredRowModel, createGroupedRowModel, createSvelteVirtualizer, createSortedRowModel, createSvGrid, getGridCellDomId, rowsToChartSpec, sortFns, } from "./index";
|
|
2
2
|
import { createRowScrollScaling, resolveMaxDomHeight, } from "./virtualization/scroll-scaling";
|
|
3
3
|
import "./sv-grid-scrollbar";
|
|
4
4
|
import { computeColumnStat, formatsNeedingStats, } from "./conditional-formatting";
|
|
@@ -1003,6 +1003,369 @@ export function createSvGridController(props) {
|
|
|
1003
1003
|
ordered.push(c);
|
|
1004
1004
|
return ordered;
|
|
1005
1005
|
});
|
|
1006
|
+
// ===================================================================
|
|
1007
|
+
// Integrated charting (the `charting` prop)
|
|
1008
|
+
// ===================================================================
|
|
1009
|
+
const chartCfg = $derived.by(() => {
|
|
1010
|
+
const c = props.charting;
|
|
1011
|
+
if (!c)
|
|
1012
|
+
return null;
|
|
1013
|
+
return c === true ? {} : c;
|
|
1014
|
+
});
|
|
1015
|
+
const chartingEnabled = $derived(!!chartCfg);
|
|
1016
|
+
const chartIsCustom = $derived(typeof chartCfg?.buildSpec === "function");
|
|
1017
|
+
const chartingConfig = $derived(chartCfg
|
|
1018
|
+
? {
|
|
1019
|
+
position: chartCfg.position ?? "right",
|
|
1020
|
+
defaultOpen: chartCfg.defaultOpen === true,
|
|
1021
|
+
defaultType: chartCfg.defaultType ?? "bar",
|
|
1022
|
+
height: chartCfg.height ?? 300,
|
|
1023
|
+
width: chartCfg.width ?? 460,
|
|
1024
|
+
crossFilter: chartCfg.crossFilter !== false,
|
|
1025
|
+
}
|
|
1026
|
+
: null);
|
|
1027
|
+
let chartPanelOpen = $state(!!props.charting && props.charting !== true && props.charting.defaultOpen === true);
|
|
1028
|
+
let chartSize = $state(null);
|
|
1029
|
+
let chartFloating = $state(false);
|
|
1030
|
+
let chartMaximized = $state(false);
|
|
1031
|
+
let chartFloatRect = $state.raw(null);
|
|
1032
|
+
let chartAiHandler = $state(null);
|
|
1033
|
+
let chartSeq = 0;
|
|
1034
|
+
const makeChart = (title) => ({
|
|
1035
|
+
id: `chart-${chartSeq++}`,
|
|
1036
|
+
title,
|
|
1037
|
+
type: (props.charting && props.charting !== true && props.charting.defaultType) || "bar",
|
|
1038
|
+
reduce: (props.charting && props.charting !== true && props.charting.reduce) || "sum",
|
|
1039
|
+
dimensionId: null,
|
|
1040
|
+
measureId: null,
|
|
1041
|
+
seriesId: undefined,
|
|
1042
|
+
stacked: null,
|
|
1043
|
+
dataLabels: null,
|
|
1044
|
+
logScale: null,
|
|
1045
|
+
timeAxis: null,
|
|
1046
|
+
valueFormat: null,
|
|
1047
|
+
});
|
|
1048
|
+
// svelte-ignore state_referenced_locally
|
|
1049
|
+
let charts = $state([makeChart("Chart 1")]);
|
|
1050
|
+
let activeChartIndex = $state(0);
|
|
1051
|
+
const activeChart = $derived((charts[Math.min(activeChartIndex, charts.length - 1)] ?? charts[0]));
|
|
1052
|
+
const chartType = $derived(activeChart.type);
|
|
1053
|
+
const chartReduce = $derived(activeChart.reduce);
|
|
1054
|
+
const chartDimensionId = $derived(activeChart.dimensionId);
|
|
1055
|
+
const chartMeasureId = $derived(activeChart.measureId);
|
|
1056
|
+
const chartSeriesId = $derived(activeChart.seriesId);
|
|
1057
|
+
const chartStacked = $derived(activeChart.stacked);
|
|
1058
|
+
const chartDataLabels = $derived(activeChart.dataLabels);
|
|
1059
|
+
const chartLogScale = $derived(activeChart.logScale);
|
|
1060
|
+
const chartTimeAxis = $derived(activeChart.timeAxis);
|
|
1061
|
+
const chartValueFormat = $derived(activeChart.valueFormat);
|
|
1062
|
+
const asMeasureList = (m) => m == null ? [] : Array.isArray(m) ? m : [m];
|
|
1063
|
+
const isNumericColumn = (col) => {
|
|
1064
|
+
const dt = col.columnDef.cellDataType;
|
|
1065
|
+
if (dt === "number")
|
|
1066
|
+
return true;
|
|
1067
|
+
if (dt)
|
|
1068
|
+
return false;
|
|
1069
|
+
for (const row of allRows) {
|
|
1070
|
+
const v = row.getCellValueByColumnId(col.id);
|
|
1071
|
+
if (v == null || v === "")
|
|
1072
|
+
continue;
|
|
1073
|
+
return typeof v === "number" || (typeof v === "string" && v.trim() !== "" && !Number.isNaN(Number(v)));
|
|
1074
|
+
}
|
|
1075
|
+
return false;
|
|
1076
|
+
};
|
|
1077
|
+
const columnLabel = (col) => {
|
|
1078
|
+
const h = col.columnDef.header;
|
|
1079
|
+
return typeof h === "string" && h ? h : (col.columnDef.field ?? col.id);
|
|
1080
|
+
};
|
|
1081
|
+
const chartableColumns = $derived.by(() => {
|
|
1082
|
+
const dims = [];
|
|
1083
|
+
const measures = [];
|
|
1084
|
+
for (const col of allColumns) {
|
|
1085
|
+
const field = col.columnDef.field;
|
|
1086
|
+
if (!field)
|
|
1087
|
+
continue;
|
|
1088
|
+
const entry = { id: col.id, field, label: columnLabel(col) };
|
|
1089
|
+
if (isNumericColumn(col))
|
|
1090
|
+
measures.push(entry);
|
|
1091
|
+
else
|
|
1092
|
+
dims.push(entry);
|
|
1093
|
+
}
|
|
1094
|
+
return { dims, measures };
|
|
1095
|
+
});
|
|
1096
|
+
const chartColumnDefaults = $derived.by(() => {
|
|
1097
|
+
const { dims, measures } = chartableColumns;
|
|
1098
|
+
let dimId = dims[0]?.id ?? null;
|
|
1099
|
+
let seriesId = null;
|
|
1100
|
+
let measureIds = measures[0] ? [measures[0].id] : [];
|
|
1101
|
+
const rects = getSelectionRects();
|
|
1102
|
+
if (rects.length) {
|
|
1103
|
+
let minCol = Infinity;
|
|
1104
|
+
let maxCol = -Infinity;
|
|
1105
|
+
for (const r of rects) {
|
|
1106
|
+
minCol = Math.min(minCol, r.minCol);
|
|
1107
|
+
maxCol = Math.max(maxCol, r.maxCol);
|
|
1108
|
+
}
|
|
1109
|
+
const span = allColumns.slice(minCol, maxCol + 1);
|
|
1110
|
+
const spanDims = span.filter((c) => dims.some((d) => d.id === c.id));
|
|
1111
|
+
const spanMeasures = span.filter((c) => measures.some((m) => m.id === c.id));
|
|
1112
|
+
if (maxCol > minCol) {
|
|
1113
|
+
if (spanDims[0])
|
|
1114
|
+
dimId = spanDims[0].id;
|
|
1115
|
+
if (spanDims[1])
|
|
1116
|
+
seriesId = spanDims[1].id;
|
|
1117
|
+
if (spanMeasures.length)
|
|
1118
|
+
measureIds = spanMeasures.map((c) => c.id);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
if (spanDims[0])
|
|
1122
|
+
dimId = spanDims[0].id;
|
|
1123
|
+
if (spanMeasures[0])
|
|
1124
|
+
measureIds = [spanMeasures[0].id];
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return { dimId, seriesId, measureIds, measureId: measureIds[0] ?? null };
|
|
1128
|
+
});
|
|
1129
|
+
const effectiveChartDimensionId = $derived(chartDimensionId ?? chartCfg?.dimension ?? chartColumnDefaults.dimId);
|
|
1130
|
+
const effectiveChartMeasureId = $derived(chartMeasureId ?? asMeasureList(chartCfg?.measures)[0] ?? chartColumnDefaults.measureId);
|
|
1131
|
+
const effectiveChartSeriesId = $derived(chartSeriesId !== undefined ? chartSeriesId : (chartCfg?.series ?? chartColumnDefaults.seriesId));
|
|
1132
|
+
const effectiveChartMeasureIds = $derived.by(() => {
|
|
1133
|
+
if (chartMeasureId)
|
|
1134
|
+
return [chartMeasureId];
|
|
1135
|
+
const configured = asMeasureList(chartCfg?.measures);
|
|
1136
|
+
if (configured.length)
|
|
1137
|
+
return configured;
|
|
1138
|
+
return chartColumnDefaults.measureIds;
|
|
1139
|
+
});
|
|
1140
|
+
const effectiveChartStacked = $derived(chartStacked ?? chartCfg?.stacked ?? false);
|
|
1141
|
+
const effectiveChartDataLabels = $derived(chartDataLabels ?? chartCfg?.dataLabels ?? false);
|
|
1142
|
+
const effectiveChartLogScale = $derived(chartLogScale ?? chartCfg?.yScale === "log");
|
|
1143
|
+
const effectiveChartTimeAxis = $derived(chartTimeAxis ?? chartCfg?.timeAxis === true);
|
|
1144
|
+
const chartMeasureFormatDefault = $derived.by(() => {
|
|
1145
|
+
const col = allColumns.find((c) => c.id === effectiveChartMeasureId);
|
|
1146
|
+
const t = col?.columnDef.format?.type;
|
|
1147
|
+
if (t === "currency")
|
|
1148
|
+
return "currency";
|
|
1149
|
+
if (t === "percent")
|
|
1150
|
+
return "percent";
|
|
1151
|
+
return undefined;
|
|
1152
|
+
});
|
|
1153
|
+
const effectiveChartValueFormat = $derived(chartValueFormat ?? chartCfg?.valueFormat ?? chartMeasureFormatDefault);
|
|
1154
|
+
const chartAutoYAxisTitle = $derived.by(() => {
|
|
1155
|
+
if (effectiveChartMeasureIds.length !== 1)
|
|
1156
|
+
return undefined;
|
|
1157
|
+
const col = allColumns.find((c) => c.id === effectiveChartMeasureId);
|
|
1158
|
+
const label = col ? columnLabel(col) : "";
|
|
1159
|
+
if (!label)
|
|
1160
|
+
return undefined;
|
|
1161
|
+
return chartReduce === "count"
|
|
1162
|
+
? "Count"
|
|
1163
|
+
: `${chartReduce === "avg" ? "Average" : "Sum"} of ${label}`;
|
|
1164
|
+
});
|
|
1165
|
+
const chartDimensionIsDate = $derived.by(() => {
|
|
1166
|
+
const col = allColumns.find((c) => c.id === effectiveChartDimensionId);
|
|
1167
|
+
if (!col)
|
|
1168
|
+
return false;
|
|
1169
|
+
if (col.columnDef.cellDataType === "date")
|
|
1170
|
+
return true;
|
|
1171
|
+
if (col.columnDef.cellDataType)
|
|
1172
|
+
return false;
|
|
1173
|
+
for (const row of allRows) {
|
|
1174
|
+
const v = row.getCellValueByColumnId(col.id);
|
|
1175
|
+
if (v == null || v === "")
|
|
1176
|
+
continue;
|
|
1177
|
+
if (v instanceof Date)
|
|
1178
|
+
return true;
|
|
1179
|
+
return typeof v === "string" && /^\d{4}-\d{2}(-\d{2})?/.test(v);
|
|
1180
|
+
}
|
|
1181
|
+
return false;
|
|
1182
|
+
});
|
|
1183
|
+
const chartRows = $derived.by(() => {
|
|
1184
|
+
const rects = getSelectionRects();
|
|
1185
|
+
const pushLeaf = (out, row) => {
|
|
1186
|
+
if (!row || (row.subRows && row.subRows.length > 0))
|
|
1187
|
+
return;
|
|
1188
|
+
out.push(row.original);
|
|
1189
|
+
};
|
|
1190
|
+
if (!rects.length) {
|
|
1191
|
+
const out = [];
|
|
1192
|
+
for (const row of allRows)
|
|
1193
|
+
pushLeaf(out, row);
|
|
1194
|
+
return out;
|
|
1195
|
+
}
|
|
1196
|
+
const idx = new Set();
|
|
1197
|
+
for (const r of rects)
|
|
1198
|
+
for (let i = r.minRow; i <= r.maxRow; i += 1)
|
|
1199
|
+
idx.add(i);
|
|
1200
|
+
const out = [];
|
|
1201
|
+
for (const i of Array.from(idx).sort((a, b) => a - b))
|
|
1202
|
+
pushLeaf(out, allRows[i]);
|
|
1203
|
+
return out;
|
|
1204
|
+
});
|
|
1205
|
+
const fieldOf = (id) => id ? allColumns.find((c) => c.id === id)?.columnDef.field : undefined;
|
|
1206
|
+
// ---- Server-side aggregation (charting.getAggregate) ----
|
|
1207
|
+
let chartServerBuckets = $state.raw(null);
|
|
1208
|
+
let chartReqSeq = 0;
|
|
1209
|
+
$effect(() => {
|
|
1210
|
+
const fn = chartCfg?.getAggregate;
|
|
1211
|
+
if (!fn)
|
|
1212
|
+
return;
|
|
1213
|
+
const category = fieldOf(effectiveChartDimensionId);
|
|
1214
|
+
const measure = fieldOf(effectiveChartMeasureIds[0]) ?? null;
|
|
1215
|
+
const series = fieldOf(effectiveChartSeriesId);
|
|
1216
|
+
// Read valueFilters + filterMenuValues so the fetch re-runs when either
|
|
1217
|
+
// changes; hand the model the facet checklists + operator filters + global.
|
|
1218
|
+
const facets = Object.fromEntries(Object.entries(valueFilters).map(([k, set]) => [k, Array.from(set)]));
|
|
1219
|
+
const filterModel = {
|
|
1220
|
+
facets,
|
|
1221
|
+
columns: { ...filterMenuValues },
|
|
1222
|
+
global: globalFilter,
|
|
1223
|
+
};
|
|
1224
|
+
void chartCfg?.refreshKey;
|
|
1225
|
+
const seq = ++chartReqSeq;
|
|
1226
|
+
Promise.resolve(fn({ dimension: category, measure, series, reduce: chartReduce, filterModel }))
|
|
1227
|
+
.then((buckets) => {
|
|
1228
|
+
if (seq === chartReqSeq)
|
|
1229
|
+
chartServerBuckets = buckets;
|
|
1230
|
+
})
|
|
1231
|
+
.catch(() => {
|
|
1232
|
+
if (seq === chartReqSeq)
|
|
1233
|
+
chartServerBuckets = [];
|
|
1234
|
+
});
|
|
1235
|
+
});
|
|
1236
|
+
const bucketsToSpec = (buckets, type, hasSeries) => {
|
|
1237
|
+
const cats = [];
|
|
1238
|
+
const catIdx = new Map();
|
|
1239
|
+
const ensureCat = (c) => {
|
|
1240
|
+
let i = catIdx.get(c);
|
|
1241
|
+
if (i === undefined) {
|
|
1242
|
+
i = cats.length;
|
|
1243
|
+
catIdx.set(c, i);
|
|
1244
|
+
cats.push(c);
|
|
1245
|
+
}
|
|
1246
|
+
return i;
|
|
1247
|
+
};
|
|
1248
|
+
const seriesMap = new Map();
|
|
1249
|
+
for (const b of buckets) {
|
|
1250
|
+
const ci = ensureCat(b.category);
|
|
1251
|
+
const key = hasSeries ? (b.series ?? "") : "value";
|
|
1252
|
+
let arr = seriesMap.get(key);
|
|
1253
|
+
if (!arr) {
|
|
1254
|
+
arr = [];
|
|
1255
|
+
seriesMap.set(key, arr);
|
|
1256
|
+
}
|
|
1257
|
+
arr[ci] = (arr[ci] ?? 0) + b.value;
|
|
1258
|
+
}
|
|
1259
|
+
return {
|
|
1260
|
+
type,
|
|
1261
|
+
categories: cats,
|
|
1262
|
+
series: [...seriesMap.entries()].map(([label, values]) => ({
|
|
1263
|
+
label,
|
|
1264
|
+
values: cats.map((_, i) => values[i] ?? 0),
|
|
1265
|
+
})),
|
|
1266
|
+
};
|
|
1267
|
+
};
|
|
1268
|
+
const chartSpec = $derived.by(() => {
|
|
1269
|
+
if (!chartingEnabled || !chartCfg)
|
|
1270
|
+
return null;
|
|
1271
|
+
if (chartCfg.getAggregate) {
|
|
1272
|
+
if (!chartServerBuckets)
|
|
1273
|
+
return null;
|
|
1274
|
+
const spec = bucketsToSpec(chartServerBuckets, chartType, !!effectiveChartSeriesId);
|
|
1275
|
+
if (effectiveChartStacked || chartCfg.stacked100)
|
|
1276
|
+
spec.stacked = true;
|
|
1277
|
+
if (chartCfg.stacked100)
|
|
1278
|
+
spec.stacked100 = true;
|
|
1279
|
+
if (chartCfg.palette)
|
|
1280
|
+
spec.palette = chartCfg.palette;
|
|
1281
|
+
if (effectiveChartTimeAxis)
|
|
1282
|
+
spec.xType = "time";
|
|
1283
|
+
if (effectiveChartLogScale)
|
|
1284
|
+
spec.yScale = "log";
|
|
1285
|
+
if (effectiveChartValueFormat)
|
|
1286
|
+
spec.valueFormat = effectiveChartValueFormat;
|
|
1287
|
+
return spec;
|
|
1288
|
+
}
|
|
1289
|
+
if (chartCfg.buildSpec)
|
|
1290
|
+
return chartCfg.buildSpec(chartRows) ?? null;
|
|
1291
|
+
const category = fieldOf(effectiveChartDimensionId);
|
|
1292
|
+
const values = effectiveChartMeasureIds.map((id) => fieldOf(id)).filter((f) => !!f);
|
|
1293
|
+
if (!category || !values.length)
|
|
1294
|
+
return null;
|
|
1295
|
+
const seriesField = fieldOf(effectiveChartSeriesId);
|
|
1296
|
+
const spec = rowsToChartSpec(chartRows, {
|
|
1297
|
+
type: chartType,
|
|
1298
|
+
category: category,
|
|
1299
|
+
value: values.length === 1 ? values[0] : values,
|
|
1300
|
+
...(seriesField ? { series: seriesField } : {}),
|
|
1301
|
+
reduce: chartReduce,
|
|
1302
|
+
stacked: effectiveChartStacked || chartCfg.stacked100 === true,
|
|
1303
|
+
stacked100: chartCfg.stacked100 === true,
|
|
1304
|
+
...(chartCfg.palette ? { palette: chartCfg.palette } : {}),
|
|
1305
|
+
...(chartCfg.topN ? { topN: chartCfg.topN } : {}),
|
|
1306
|
+
...(chartCfg.otherLabel ? { otherLabel: chartCfg.otherLabel } : {}),
|
|
1307
|
+
...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
|
|
1308
|
+
});
|
|
1309
|
+
if (chartCfg.orientation)
|
|
1310
|
+
spec.orientation = chartCfg.orientation;
|
|
1311
|
+
if (effectiveChartTimeAxis)
|
|
1312
|
+
spec.xType = "time";
|
|
1313
|
+
if (effectiveChartLogScale)
|
|
1314
|
+
spec.yScale = "log";
|
|
1315
|
+
if (effectiveChartValueFormat)
|
|
1316
|
+
spec.valueFormat = effectiveChartValueFormat;
|
|
1317
|
+
if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
|
|
1318
|
+
spec.yAxisTitle = chartAutoYAxisTitle;
|
|
1319
|
+
}
|
|
1320
|
+
if (effectiveChartValueFormat && activeChart.valueFormat === null) { /* inherited default */ }
|
|
1321
|
+
if (chartType === "pie") {
|
|
1322
|
+
if (chartCfg.donut)
|
|
1323
|
+
spec.innerRadius = typeof chartCfg.donut === "number" ? chartCfg.donut : 0.6;
|
|
1324
|
+
}
|
|
1325
|
+
if (chartCfg.annotations)
|
|
1326
|
+
spec.annotations = chartCfg.annotations;
|
|
1327
|
+
if (chartCfg.patternFallback)
|
|
1328
|
+
spec.patternFallback = true;
|
|
1329
|
+
const refLines = [...(chartCfg.referenceLines ?? [])];
|
|
1330
|
+
if (chartCfg.averageLine && spec.series[0]?.values.length) {
|
|
1331
|
+
const vals = spec.series[0].values;
|
|
1332
|
+
const avg = vals.reduce((a, b) => a + b, 0) / vals.length;
|
|
1333
|
+
refLines.push({ value: avg, label: "Average", dashed: true });
|
|
1334
|
+
}
|
|
1335
|
+
if (refLines.length)
|
|
1336
|
+
spec.referenceLines = refLines;
|
|
1337
|
+
for (const s of spec.series) {
|
|
1338
|
+
if (chartCfg.trend)
|
|
1339
|
+
s.overlay = chartCfg.trend === "linear" ? "linear" : `${chartCfg.trend}:7`;
|
|
1340
|
+
if (chartCfg.smooth)
|
|
1341
|
+
s.smooth = true;
|
|
1342
|
+
if (chartCfg.seriesTypes?.[s.label])
|
|
1343
|
+
s.type = chartCfg.seriesTypes[s.label];
|
|
1344
|
+
if (chartCfg.seriesAxes?.[s.label])
|
|
1345
|
+
s.axis = chartCfg.seriesAxes[s.label];
|
|
1346
|
+
}
|
|
1347
|
+
return spec;
|
|
1348
|
+
});
|
|
1349
|
+
function applyChartCrossFilter(category) {
|
|
1350
|
+
const dimId = effectiveChartDimensionId;
|
|
1351
|
+
if (!dimId)
|
|
1352
|
+
return;
|
|
1353
|
+
const existing = valueFilters[dimId];
|
|
1354
|
+
const next = new Set(existing && existing.has(category) ? existing : []);
|
|
1355
|
+
if (next.has(category))
|
|
1356
|
+
next.delete(category);
|
|
1357
|
+
else
|
|
1358
|
+
next.add(category);
|
|
1359
|
+
valueFilters = { ...valueFilters, [dimId]: next };
|
|
1360
|
+
}
|
|
1361
|
+
function clearChartCrossFilter() {
|
|
1362
|
+
const dimId = effectiveChartDimensionId;
|
|
1363
|
+
if (!dimId)
|
|
1364
|
+
return;
|
|
1365
|
+
const next = { ...valueFilters };
|
|
1366
|
+
delete next[dimId];
|
|
1367
|
+
valueFilters = next;
|
|
1368
|
+
}
|
|
1006
1369
|
// Forward sort-clause changes to the consumer. Same dedupe pattern as the
|
|
1007
1370
|
// selection callback above - fires only when the serialized clauses change.
|
|
1008
1371
|
let lastSortingSerialized = "";
|
|
@@ -1903,6 +2266,136 @@ export function createSvGridController(props) {
|
|
|
1903
2266
|
get toolPanelHeaderLabel() { return toolPanelHeaderLabel; },
|
|
1904
2267
|
get toggleColumnVisibleInPanel() { return toggleColumnVisibleInPanel; },
|
|
1905
2268
|
get moveColumnInPanel() { return moveColumnInPanel; },
|
|
2269
|
+
// ---- Integrated charting ----
|
|
2270
|
+
get chartingEnabled() { return chartingEnabled; },
|
|
2271
|
+
get chartingConfig() { return chartingConfig; },
|
|
2272
|
+
get chartCfg() { return chartCfg; },
|
|
2273
|
+
get chartIsCustom() { return chartIsCustom; },
|
|
2274
|
+
get chartPanelOpen() { return chartPanelOpen; },
|
|
2275
|
+
set chartPanelOpen(v) { chartPanelOpen = v; },
|
|
2276
|
+
get chartType() { return chartType; },
|
|
2277
|
+
set chartType(v) { activeChart.type = v; },
|
|
2278
|
+
get chartReduce() { return chartReduce; },
|
|
2279
|
+
set chartReduce(v) { activeChart.reduce = v; },
|
|
2280
|
+
get chartDimensionId() { return effectiveChartDimensionId; },
|
|
2281
|
+
set chartDimensionId(v) { activeChart.dimensionId = v; },
|
|
2282
|
+
get chartMeasureId() { return effectiveChartMeasureId; },
|
|
2283
|
+
set chartMeasureId(v) { activeChart.measureId = v; },
|
|
2284
|
+
get chartSeriesId() { return effectiveChartSeriesId; },
|
|
2285
|
+
set chartSeriesId(v) { activeChart.seriesId = v; },
|
|
2286
|
+
get chartStacked() { return effectiveChartStacked; },
|
|
2287
|
+
set chartStacked(v) { activeChart.stacked = v; },
|
|
2288
|
+
get chartDataLabels() { return effectiveChartDataLabels; },
|
|
2289
|
+
set chartDataLabels(v) { activeChart.dataLabels = v; },
|
|
2290
|
+
get chartLogScale() { return effectiveChartLogScale; },
|
|
2291
|
+
set chartLogScale(v) { activeChart.logScale = v; },
|
|
2292
|
+
get chartTimeAxis() { return effectiveChartTimeAxis; },
|
|
2293
|
+
set chartTimeAxis(v) { activeChart.timeAxis = v; },
|
|
2294
|
+
get chartValueFormat() { return effectiveChartValueFormat ?? "number"; },
|
|
2295
|
+
set chartValueFormat(v) { activeChart.valueFormat = v; },
|
|
2296
|
+
get chartDimensionIsDate() { return chartDimensionIsDate; },
|
|
2297
|
+
get chartSize() { return chartSize; },
|
|
2298
|
+
set chartSize(v) { chartSize = v; },
|
|
2299
|
+
get chartFloating() { return chartFloating; },
|
|
2300
|
+
set chartFloating(v) { chartFloating = v; },
|
|
2301
|
+
get chartMaximized() { return chartMaximized; },
|
|
2302
|
+
set chartMaximized(v) { chartMaximized = v; },
|
|
2303
|
+
get chartFloatRect() { return chartFloatRect; },
|
|
2304
|
+
set chartFloatRect(v) { chartFloatRect = v; },
|
|
2305
|
+
get chartableColumns() { return chartableColumns; },
|
|
2306
|
+
get chartSpec() { return chartSpec; },
|
|
2307
|
+
applyChartCrossFilter,
|
|
2308
|
+
clearChartCrossFilter,
|
|
2309
|
+
// Multiple charts (tab strip)
|
|
2310
|
+
get charts() { return charts.map((c) => ({ id: c.id, title: c.title })); },
|
|
2311
|
+
get activeChartIndex() { return Math.min(activeChartIndex, charts.length - 1); },
|
|
2312
|
+
set activeChartIndex(v) { activeChartIndex = Math.max(0, Math.min(charts.length - 1, v)); },
|
|
2313
|
+
addChart() {
|
|
2314
|
+
charts = [...charts, makeChart(`Chart ${charts.length + 1}`)];
|
|
2315
|
+
activeChartIndex = charts.length - 1;
|
|
2316
|
+
},
|
|
2317
|
+
removeChart(index) {
|
|
2318
|
+
if (charts.length <= 1)
|
|
2319
|
+
return;
|
|
2320
|
+
const idx = index ?? activeChartIndex;
|
|
2321
|
+
charts = charts.filter((_, k) => k !== idx);
|
|
2322
|
+
if (activeChartIndex >= charts.length)
|
|
2323
|
+
activeChartIndex = charts.length - 1;
|
|
2324
|
+
},
|
|
2325
|
+
renameChart(index, title) {
|
|
2326
|
+
const c = charts[index];
|
|
2327
|
+
if (c)
|
|
2328
|
+
c.title = title;
|
|
2329
|
+
},
|
|
2330
|
+
getChartsState() {
|
|
2331
|
+
return charts.map((c) => ({
|
|
2332
|
+
title: c.title,
|
|
2333
|
+
type: c.type,
|
|
2334
|
+
dimension: c.dimensionId,
|
|
2335
|
+
series: c.seriesId ?? null,
|
|
2336
|
+
measure: c.measureId,
|
|
2337
|
+
reduce: c.reduce,
|
|
2338
|
+
stacked: c.stacked,
|
|
2339
|
+
dataLabels: c.dataLabels,
|
|
2340
|
+
logScale: c.logScale,
|
|
2341
|
+
timeAxis: c.timeAxis,
|
|
2342
|
+
valueFormat: c.valueFormat,
|
|
2343
|
+
}));
|
|
2344
|
+
},
|
|
2345
|
+
applyChartsState(list, active) {
|
|
2346
|
+
const restored = list.map((c, i) => ({
|
|
2347
|
+
id: `chart-${chartSeq++}`,
|
|
2348
|
+
title: typeof c.title === "string" ? c.title : `Chart ${i + 1}`,
|
|
2349
|
+
type: c.type ?? "bar",
|
|
2350
|
+
reduce: c.reduce ?? "sum",
|
|
2351
|
+
dimensionId: c.dimension ?? null,
|
|
2352
|
+
measureId: c.measure ?? null,
|
|
2353
|
+
seriesId: c.series,
|
|
2354
|
+
stacked: c.stacked ?? null,
|
|
2355
|
+
dataLabels: c.dataLabels ?? null,
|
|
2356
|
+
logScale: c.logScale ?? null,
|
|
2357
|
+
timeAxis: c.timeAxis ?? null,
|
|
2358
|
+
valueFormat: c.valueFormat ?? null,
|
|
2359
|
+
}));
|
|
2360
|
+
charts = restored.length ? restored : [makeChart("Chart 1")];
|
|
2361
|
+
activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
|
|
2362
|
+
},
|
|
2363
|
+
get chartAiHandler() { return chartAiHandler; },
|
|
2364
|
+
set chartAiHandler(v) { chartAiHandler = v; },
|
|
2365
|
+
applyChartConfig(config) {
|
|
2366
|
+
const cols = [...chartableColumns.dims, ...chartableColumns.measures];
|
|
2367
|
+
const resolve = (v) => {
|
|
2368
|
+
if (v == null)
|
|
2369
|
+
return null;
|
|
2370
|
+
const byId = cols.find((c) => c.id === v);
|
|
2371
|
+
if (byId)
|
|
2372
|
+
return byId.id;
|
|
2373
|
+
const byField = cols.find((c) => c.field === v);
|
|
2374
|
+
return byField ? byField.id : v;
|
|
2375
|
+
};
|
|
2376
|
+
if (config.open !== false)
|
|
2377
|
+
chartPanelOpen = true;
|
|
2378
|
+
if (config.type)
|
|
2379
|
+
activeChart.type = config.type;
|
|
2380
|
+
if ("dimension" in config)
|
|
2381
|
+
activeChart.dimensionId = resolve(config.dimension);
|
|
2382
|
+
if ("series" in config)
|
|
2383
|
+
activeChart.seriesId = config.series == null ? null : resolve(config.series);
|
|
2384
|
+
if ("measure" in config)
|
|
2385
|
+
activeChart.measureId = resolve(config.measure);
|
|
2386
|
+
if (config.reduce)
|
|
2387
|
+
activeChart.reduce = config.reduce;
|
|
2388
|
+
if (typeof config.stacked === "boolean")
|
|
2389
|
+
activeChart.stacked = config.stacked;
|
|
2390
|
+
if (typeof config.dataLabels === "boolean")
|
|
2391
|
+
activeChart.dataLabels = config.dataLabels;
|
|
2392
|
+
if (typeof config.logScale === "boolean")
|
|
2393
|
+
activeChart.logScale = config.logScale;
|
|
2394
|
+
if (typeof config.timeAxis === "boolean")
|
|
2395
|
+
activeChart.timeAxis = config.timeAxis;
|
|
2396
|
+
if (config.valueFormat)
|
|
2397
|
+
activeChart.valueFormat = config.valueFormat;
|
|
2398
|
+
},
|
|
1906
2399
|
get toggleGroupInPanel() { return toggleGroupInPanel; },
|
|
1907
2400
|
get lastSortingSerialized() { return lastSortingSerialized; },
|
|
1908
2401
|
set lastSortingSerialized(v) { lastSortingSerialized = v; },
|
package/dist/SvGrid.css
CHANGED
|
@@ -2375,3 +2375,145 @@ select.sv-grid-fr-editor {
|
|
|
2375
2375
|
font-size: 0.82rem;
|
|
2376
2376
|
color: var(--sg-fg, #101828);
|
|
2377
2377
|
}
|
|
2378
|
+
|
|
2379
|
+
/* ===================================================================
|
|
2380
|
+
Integrated chart panel (the `charting` prop)
|
|
2381
|
+
=================================================================== */
|
|
2382
|
+
.sv-grid-chart-toggle svg { flex: none; }
|
|
2383
|
+
.sv-grid-chart-panel {
|
|
2384
|
+
position: absolute;
|
|
2385
|
+
z-index: 900;
|
|
2386
|
+
display: flex;
|
|
2387
|
+
flex-direction: column;
|
|
2388
|
+
background: var(--sg-bg, #fff);
|
|
2389
|
+
color: var(--sg-fg, #0f172a);
|
|
2390
|
+
}
|
|
2391
|
+
.sv-grid-chart-panel.is-bottom {
|
|
2392
|
+
left: 0;
|
|
2393
|
+
right: 0;
|
|
2394
|
+
bottom: 0;
|
|
2395
|
+
border-top: 1px solid var(--sg-border, #e2e8f0);
|
|
2396
|
+
box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.12);
|
|
2397
|
+
}
|
|
2398
|
+
.sv-grid-chart-panel.is-right {
|
|
2399
|
+
top: 0;
|
|
2400
|
+
right: 0;
|
|
2401
|
+
bottom: 0;
|
|
2402
|
+
border-left: 1px solid var(--sg-border, #e2e8f0);
|
|
2403
|
+
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.12);
|
|
2404
|
+
}
|
|
2405
|
+
.sv-grid-chart-panel.is-floating {
|
|
2406
|
+
top: auto;
|
|
2407
|
+
bottom: auto;
|
|
2408
|
+
left: auto;
|
|
2409
|
+
right: auto;
|
|
2410
|
+
border: 1px solid var(--sg-border, #e2e8f0);
|
|
2411
|
+
border-radius: calc(var(--sg-radius, 6px) + 2px);
|
|
2412
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
|
|
2413
|
+
overflow: hidden;
|
|
2414
|
+
}
|
|
2415
|
+
.sv-grid-chart-resizer {
|
|
2416
|
+
position: absolute;
|
|
2417
|
+
z-index: 1;
|
|
2418
|
+
background: transparent;
|
|
2419
|
+
touch-action: none;
|
|
2420
|
+
}
|
|
2421
|
+
.sv-grid-chart-panel.is-bottom > .sv-grid-chart-resizer { top: -3px; left: 0; right: 0; height: 7px; cursor: ns-resize; }
|
|
2422
|
+
.sv-grid-chart-panel.is-right > .sv-grid-chart-resizer { left: -3px; top: 0; bottom: 0; width: 7px; cursor: ew-resize; }
|
|
2423
|
+
.sv-grid-chart-resizer:hover,
|
|
2424
|
+
.sv-grid-chart-resizer.is-dragging { background: color-mix(in srgb, var(--sg-accent, #2563eb) 30%, transparent); }
|
|
2425
|
+
.sv-grid-chart-float-resizer {
|
|
2426
|
+
position: absolute;
|
|
2427
|
+
right: 0;
|
|
2428
|
+
bottom: 0;
|
|
2429
|
+
width: 16px;
|
|
2430
|
+
height: 16px;
|
|
2431
|
+
cursor: nwse-resize;
|
|
2432
|
+
touch-action: none;
|
|
2433
|
+
background: linear-gradient(135deg, transparent 50%, color-mix(in srgb, var(--sg-muted, #64748b) 55%, transparent) 50%);
|
|
2434
|
+
border-bottom-right-radius: calc(var(--sg-radius, 6px) + 2px);
|
|
2435
|
+
}
|
|
2436
|
+
.sv-grid-chart-panel-head {
|
|
2437
|
+
display: flex;
|
|
2438
|
+
flex-direction: column;
|
|
2439
|
+
align-items: stretch;
|
|
2440
|
+
gap: 8px;
|
|
2441
|
+
padding: 8px 12px 10px;
|
|
2442
|
+
border-bottom: 1px solid var(--sg-border, #e2e8f0);
|
|
2443
|
+
background: var(--sg-header-bg, #f8fafc);
|
|
2444
|
+
}
|
|
2445
|
+
.sv-grid-chart-drawer-title { display: flex; align-items: center; gap: 7px; }
|
|
2446
|
+
.sv-grid-chart-drawer-title.is-drag-handle { cursor: move; user-select: none; }
|
|
2447
|
+
.sv-grid-chart-title-icon { color: var(--sg-accent, #2563eb); flex: none; }
|
|
2448
|
+
.sv-grid-chart-title { font-size: 13px; font-weight: 700; letter-spacing: 0.01em; color: var(--sg-fg, #0f172a); }
|
|
2449
|
+
.sv-grid-chart-drawer-title .sv-grid-chart-actions { margin-left: auto; }
|
|
2450
|
+
.sv-grid-chart-actions { display: inline-flex; align-items: center; gap: 6px; }
|
|
2451
|
+
.sv-grid-chart-controls { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
|
|
2452
|
+
.sv-grid-chart-controls:empty { display: none; }
|
|
2453
|
+
.sv-grid-chart-ctl { display: flex; flex-direction: column; gap: 2px; font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
|
2454
|
+
.sv-grid-chart-ctl select {
|
|
2455
|
+
font: inherit; font-size: 12.5px; font-weight: 400; color: var(--sg-fg, #0f172a);
|
|
2456
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff));
|
|
2457
|
+
border: 1px solid var(--sg-input-border, var(--sg-border, #e2e8f0));
|
|
2458
|
+
border-radius: var(--sg-radius, 6px); padding: 4px 8px;
|
|
2459
|
+
}
|
|
2460
|
+
.sv-grid-chart-toggle {
|
|
2461
|
+
display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600;
|
|
2462
|
+
color: var(--sg-muted, #64748b); cursor: pointer; white-space: nowrap;
|
|
2463
|
+
}
|
|
2464
|
+
.sv-grid-chart-btn {
|
|
2465
|
+
font: inherit; font-size: 12px; font-weight: 550; padding: 5px 10px;
|
|
2466
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
2467
|
+
background: var(--sg-bg, #fff); color: var(--sg-fg, #0f172a); cursor: pointer;
|
|
2468
|
+
}
|
|
2469
|
+
.sv-grid-chart-btn:hover:not(:disabled) { border-color: var(--sg-accent, #2563eb); color: var(--sg-accent, #2563eb); }
|
|
2470
|
+
.sv-grid-chart-btn:disabled { opacity: 0.5; cursor: default; }
|
|
2471
|
+
.sv-grid-chart-icon-btn {
|
|
2472
|
+
display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px;
|
|
2473
|
+
border: 1px solid transparent; border-radius: var(--sg-radius, 6px); background: transparent;
|
|
2474
|
+
color: var(--sg-muted, #64748b); cursor: pointer;
|
|
2475
|
+
}
|
|
2476
|
+
.sv-grid-chart-icon-btn:hover { border-color: var(--sg-border, #e2e8f0); color: var(--sg-accent, #2563eb); background: var(--sg-bg, #fff); }
|
|
2477
|
+
.sv-grid-chart-close { border: 0; background: transparent; color: var(--sg-muted, #64748b); font-size: 18px; line-height: 1; cursor: pointer; padding: 0 4px; }
|
|
2478
|
+
.sv-grid-chart-close:hover { color: var(--sg-fg, #0f172a); }
|
|
2479
|
+
.sv-grid-chart-panel-body {
|
|
2480
|
+
flex: 1; min-height: 0; padding: 4px 10px 8px; overflow: hidden;
|
|
2481
|
+
display: flex; align-items: stretch; justify-content: center;
|
|
2482
|
+
}
|
|
2483
|
+
.sv-grid-chart-panel-body > .sv-grid-chart { width: 100%; justify-content: center; }
|
|
2484
|
+
.sv-grid-chart-empty { margin: 0; font-size: 13px; color: var(--sg-muted, #64748b); text-align: center; max-width: 42ch; }
|
|
2485
|
+
/* Export menu */
|
|
2486
|
+
.sv-grid-chart-export { position: relative; display: inline-flex; }
|
|
2487
|
+
.sv-grid-chart-export-menu {
|
|
2488
|
+
position: absolute; top: calc(100% + 4px); right: 0; z-index: 5; min-width: 150px; padding: 4px;
|
|
2489
|
+
display: flex; flex-direction: column; background: var(--sg-bg, #fff);
|
|
2490
|
+
border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px);
|
|
2491
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
|
|
2492
|
+
}
|
|
2493
|
+
.sv-grid-chart-export-menu button {
|
|
2494
|
+
font: inherit; font-size: 12.5px; text-align: left; padding: 6px 10px; border: 0;
|
|
2495
|
+
border-radius: calc(var(--sg-radius, 6px) - 2px); background: transparent; color: var(--sg-fg, #0f172a); cursor: pointer; white-space: nowrap;
|
|
2496
|
+
}
|
|
2497
|
+
.sv-grid-chart-export-menu button:hover:not(:disabled) { background: var(--sg-hover-bg, color-mix(in srgb, var(--sg-accent, #2563eb) 10%, transparent)); }
|
|
2498
|
+
.sv-grid-chart-export-menu button:disabled { opacity: 0.45; cursor: default; }
|
|
2499
|
+
/* AI "chart this" */
|
|
2500
|
+
.sv-grid-chart-ai-btn { display: inline-flex; align-items: center; gap: 4px; }
|
|
2501
|
+
.sv-grid-chart-ai-btn.is-active { border-color: var(--sg-accent, #2563eb); color: var(--sg-accent, #2563eb); }
|
|
2502
|
+
.sv-grid-chart-ai { display: flex; flex-direction: column; gap: 4px; }
|
|
2503
|
+
.sv-grid-chart-ai-row { display: flex; gap: 6px; align-items: center; }
|
|
2504
|
+
.sv-grid-chart-ai-input {
|
|
2505
|
+
flex: 1; min-width: 0; font: inherit; font-size: 12.5px; color: var(--sg-fg, #0f172a);
|
|
2506
|
+
background: var(--sg-input-bg, var(--sg-bg, #fff)); border: 1px solid var(--sg-input-border, var(--sg-border, #e2e8f0));
|
|
2507
|
+
border-radius: var(--sg-radius, 6px); padding: 5px 9px;
|
|
2508
|
+
}
|
|
2509
|
+
.sv-grid-chart-ai-input:focus { outline: none; border-color: var(--sg-accent, #2563eb); }
|
|
2510
|
+
.sv-grid-chart-ai-msg { margin: 0; font-size: 12px; color: var(--sg-muted, #64748b); }
|
|
2511
|
+
.sv-grid-chart-ai-msg.is-error { color: var(--sg-danger, #dc2626); }
|
|
2512
|
+
/* Multi-chart tab strip */
|
|
2513
|
+
.sv-grid-chart-tabs { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
|
|
2514
|
+
.sv-grid-chart-tab { display: inline-flex; align-items: center; border: 1px solid var(--sg-border, #e2e8f0); border-radius: var(--sg-radius, 6px); background: var(--sg-bg, #fff); overflow: hidden; }
|
|
2515
|
+
.sv-grid-chart-tab.is-active { border-color: var(--sg-accent, #2563eb); background: color-mix(in srgb, var(--sg-accent, #2563eb) 10%, transparent); }
|
|
2516
|
+
.sv-grid-chart-tab-label { font: inherit; font-size: 12px; font-weight: 600; color: var(--sg-fg, #0f172a); background: transparent; border: 0; padding: 3px 8px; cursor: pointer; max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2517
|
+
.sv-grid-chart-tab.is-active .sv-grid-chart-tab-label { color: var(--sg-accent, #2563eb); }
|
|
2518
|
+
.sv-grid-chart-tab-x { border: 0; background: transparent; color: var(--sg-muted, #64748b); font-size: 14px; line-height: 1; padding: 0 6px 0 2px; cursor: pointer; }
|
|
2519
|
+
.sv-grid-chart-tab-x:hover { color: var(--sg-fg, #0f172a); }
|