@svgrid/grid 3.0.1 → 3.0.3
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/GridFooter.svelte +14 -5
- package/dist/GridFooter.svelte.d.ts +8 -0
- package/dist/GridMenus.svelte +18 -10
- package/dist/GridMenus.svelte.d.ts +2 -1
- package/dist/SvGrid.controller.svelte.d.ts +17 -0
- package/dist/SvGrid.controller.svelte.js +128 -14
- package/dist/SvGrid.css +79 -10
- package/dist/SvGrid.svelte +191 -186
- package/dist/SvGrid.types.d.ts +42 -1
- package/dist/SvGridChart.svelte +571 -41
- package/dist/SvGridChart.svelte.d.ts +63 -1
- package/dist/SvGridChartPanel.svelte +169 -21
- package/dist/SvGridChartView.svelte +9 -0
- package/dist/SvGridChartView.svelte.d.ts +7 -0
- package/dist/SvGroupCell.svelte +28 -3
- package/dist/SvGroupCell.svelte.d.ts +8 -0
- package/dist/SvRowGroupPanel.svelte +20 -4
- package/dist/SvRowGroupPanel.svelte.d.ts +8 -0
- package/dist/ai.d.ts +11 -2
- package/dist/ai.js +51 -7
- package/dist/cdn/GridMenus-CHOMnW-c.js +602 -0
- package/dist/cdn/{GridMenus-DFB6iwF8.js → GridMenus-DhdPF700.js} +152 -150
- package/dist/cdn/SvGridChart-1PMSp2LZ.js +1481 -0
- package/dist/cdn/SvGridChart-D02avhyr.js +1480 -0
- package/dist/cdn/SvGridChartPanel-DJnViOv4.js +834 -0
- package/dist/cdn/SvGridChartPanel-vPTNerFZ.js +810 -0
- package/dist/cdn/{SvGridChartView-BhUEJ5ki.js → SvGridChartView-C--HhdUo.js} +5 -3
- package/dist/cdn/{SvGridChartView-00LPUHL1.js → SvGridChartView-KyyFG-wY.js} +5 -3
- package/dist/cdn/cell-formatting-C2Hf5gqd.js +86 -0
- package/dist/cdn/chart-DLg3_zTQ.js +1652 -0
- package/dist/cdn/{export-format-Cv1Dll6k.js → export-format-CUDLy2yn.js} +84 -168
- package/dist/cdn/{src-C8QjSaq-.js → src-Bugf5XlI.js} +3608 -3414
- package/dist/cdn/{src-D0hO2eYN.js → src-D6XvA1Ij.js} +8617 -8423
- package/dist/cdn/svgrid.js +16 -15
- package/dist/cdn/svgrid.svelte-external.js +16 -15
- package/dist/cell-formatting.d.ts +7 -0
- package/dist/cell-formatting.js +7 -1
- package/dist/chart-export.d.ts +23 -2
- package/dist/chart-export.js +80 -6
- package/dist/chart.d.ts +410 -18
- package/dist/chart.js +824 -24
- package/dist/grid-icons.d.ts +49 -0
- package/dist/grid-icons.js +86 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/package.json +11 -11
- package/src/GridFooter.svelte +14 -5
- package/src/GridMenus.svelte +18 -10
- package/src/SvGrid.controller.svelte.ts +146 -13
- package/src/SvGrid.css +79 -10
- package/src/SvGrid.svelte +191 -186
- package/src/SvGrid.types.ts +42 -1
- package/src/SvGridChart.svelte +571 -41
- package/src/SvGridChart.test.ts +243 -1
- package/src/SvGridChartPanel.svelte +169 -21
- package/src/SvGridChartView.svelte +9 -0
- package/src/SvGroupCell.svelte +28 -3
- package/src/SvRowGroupPanel.svelte +20 -4
- package/src/ai.test.ts +60 -1
- package/src/ai.ts +59 -6
- package/src/cell-formatting.ts +7 -1
- package/src/chart-export.test.ts +112 -0
- package/src/chart-export.ts +76 -8
- package/src/chart.test.ts +830 -1
- package/src/chart.ts +1053 -32
- package/src/grid-icons.test.ts +46 -0
- package/src/grid-icons.ts +184 -0
- package/src/icon-overrides.grid.test.ts +188 -0
- package/src/icon-seam.test.ts +100 -0
- package/src/index.ts +19 -0
- package/src/svgrid.charting.test.ts +120 -0
- package/dist/cdn/GridMenus-CYzOvmNQ.js +0 -600
- package/dist/cdn/SvGridChart-BEJmNNx9.js +0 -1203
- package/dist/cdn/SvGridChart-BwVos976.js +0 -1202
- package/dist/cdn/SvGridChartPanel-BOknOkrP.js +0 -561
- package/dist/cdn/SvGridChartPanel-D2PjII4O.js +0 -585
- package/dist/cdn/chart-i4XcXyHJ.js +0 -1214
package/dist/GridFooter.svelte
CHANGED
|
@@ -7,16 +7,25 @@
|
|
|
7
7
|
import {
|
|
8
8
|
fmtStat,
|
|
9
9
|
} from "./SvGrid.helpers";
|
|
10
|
+
import type { Snippet } from "svelte";
|
|
11
|
+
import type { GridIconName } from "./grid-icons";
|
|
10
12
|
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
11
13
|
|
|
12
14
|
let {
|
|
13
15
|
ctrl,
|
|
16
|
+
icon,
|
|
14
17
|
pager = true,
|
|
15
18
|
showStatus = true,
|
|
16
19
|
top = false,
|
|
17
20
|
pageSizeOptions,
|
|
18
21
|
}: {
|
|
19
22
|
ctrl: SvGridController<TFeatures, TData>;
|
|
23
|
+
/**
|
|
24
|
+
* The grid's icon resolver, so the pager arrows honour the `icons` prop.
|
|
25
|
+
* Required rather than optional: SvGrid is the only thing that mounts this
|
|
26
|
+
* component, so a missing icon would be a bug, not a supported call.
|
|
27
|
+
*/
|
|
28
|
+
icon: Snippet<[GridIconName]>;
|
|
20
29
|
/** Render the pagination controls in this instance. */
|
|
21
30
|
pager?: boolean;
|
|
22
31
|
/** Render the status bar in this instance. */
|
|
@@ -132,7 +141,7 @@
|
|
|
132
141
|
onclick={openPageSize}
|
|
133
142
|
>
|
|
134
143
|
<span class="sv-grid-dropdown-label">{pageSize}</span>
|
|
135
|
-
<span class="sv-grid-dropdown-caret" aria-hidden="true"
|
|
144
|
+
<span class="sv-grid-dropdown-caret" aria-hidden="true">{@render icon("page-size-caret")}</span>
|
|
136
145
|
</button>
|
|
137
146
|
</div>
|
|
138
147
|
{/if}
|
|
@@ -149,14 +158,14 @@
|
|
|
149
158
|
class="sv-grid-pagination-btn"
|
|
150
159
|
disabled={onFirst}
|
|
151
160
|
onclick={() => goToPage(0)}
|
|
152
|
-
aria-label={messages.firstPage}
|
|
161
|
+
aria-label={messages.firstPage}>{@render icon("page-first")}</button
|
|
153
162
|
>
|
|
154
163
|
<button
|
|
155
164
|
type="button"
|
|
156
165
|
class="sv-grid-pagination-btn"
|
|
157
166
|
disabled={onFirst}
|
|
158
167
|
onclick={() => changePage(-1)}
|
|
159
|
-
aria-label={messages.prevPage}
|
|
168
|
+
aria-label={messages.prevPage}>{@render icon("page-prev")}</button
|
|
160
169
|
>
|
|
161
170
|
<span class="sv-grid-pagination-label">
|
|
162
171
|
{messages.page} <strong>{currentPage.toLocaleString()}</strong> {messages.of}
|
|
@@ -167,14 +176,14 @@
|
|
|
167
176
|
class="sv-grid-pagination-btn"
|
|
168
177
|
disabled={onLast}
|
|
169
178
|
onclick={() => changePage(1)}
|
|
170
|
-
aria-label={messages.nextPage}
|
|
179
|
+
aria-label={messages.nextPage}>{@render icon("page-next")}</button
|
|
171
180
|
>
|
|
172
181
|
<button
|
|
173
182
|
type="button"
|
|
174
183
|
class="sv-grid-pagination-btn"
|
|
175
184
|
disabled={onLast}
|
|
176
185
|
onclick={() => goToPage(pageCount - 1)}
|
|
177
|
-
aria-label={messages.lastPage}
|
|
186
|
+
aria-label={messages.lastPage}>{@render icon("page-last")}</button
|
|
178
187
|
>
|
|
179
188
|
</div>
|
|
180
189
|
</div>
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { type RowData, type TableFeatures } from "./index.js";
|
|
2
2
|
import "./sv-grid-scrollbar.js";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { GridIconName } from "./grid-icons.js";
|
|
3
5
|
import type { SvGridController } from "./SvGrid.controller.svelte.js";
|
|
4
6
|
declare function $$render<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(): {
|
|
5
7
|
props: {
|
|
6
8
|
ctrl: SvGridController<TFeatures, TData>;
|
|
9
|
+
/**
|
|
10
|
+
* The grid's icon resolver, so the pager arrows honour the `icons` prop.
|
|
11
|
+
* Required rather than optional: SvGrid is the only thing that mounts this
|
|
12
|
+
* component, so a missing icon would be a bug, not a supported call.
|
|
13
|
+
*/
|
|
14
|
+
icon: Snippet<[GridIconName]>;
|
|
7
15
|
/** Render the pagination controls in this instance. */
|
|
8
16
|
pager?: boolean;
|
|
9
17
|
/** Render the status bar in this instance. */
|
package/dist/GridMenus.svelte
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import "./sv-grid-scrollbar";
|
|
11
11
|
import { onScrollOutside } from "./a11y/dismissable";
|
|
12
12
|
import type { Snippet } from "svelte";
|
|
13
|
+
import type { GridIconName } from "./grid-icons";
|
|
13
14
|
import SvListBox from "./SvListBox.svelte";
|
|
14
15
|
import type {
|
|
15
16
|
FilterOperator,
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
|
|
24
25
|
import type { SvGridController } from "./SvGrid.controller.svelte";
|
|
25
26
|
|
|
26
|
-
let { ctrl, icon }: { ctrl: SvGridController<TFeatures, TData>; icon: Snippet<[
|
|
27
|
+
let { ctrl, icon }: { ctrl: SvGridController<TFeatures, TData>; icon: Snippet<[GridIconName]> } = $props();
|
|
27
28
|
|
|
28
29
|
// View facade: re-bind the controller's reactive members so the markup
|
|
29
30
|
// (moved verbatim from SvGrid.svelte) stays identical.
|
|
@@ -403,7 +404,7 @@
|
|
|
403
404
|
onclick={() => patchFilter(colId, { join: "OR" })}>OR</button>
|
|
404
405
|
<button type="button" class="sv-grid-menu-join-x" title="Remove second condition"
|
|
405
406
|
aria-label="Remove second condition"
|
|
406
|
-
onclick={() => patchFilter(colId, { operator2: undefined, value2: undefined, valueTo2: undefined, join: undefined })}
|
|
407
|
+
onclick={() => patchFilter(colId, { operator2: undefined, value2: undefined, valueTo2: undefined, join: undefined })}>{@render icon("remove")}</button>
|
|
407
408
|
</div>
|
|
408
409
|
<select
|
|
409
410
|
class="sv-grid-menu-operator-select"
|
|
@@ -452,11 +453,18 @@
|
|
|
452
453
|
<span class="sv-grid-menu-head-note">{pickedCount} of {totalCount}</span>
|
|
453
454
|
{/if}
|
|
454
455
|
</div>
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
456
|
+
<!-- The magnifier used to be a background-image data URI in SvGrid.css
|
|
457
|
+
with a hard-coded stroke colour, so it ignored every theme and could
|
|
458
|
+
not be overridden. It is markup now, and the input keeps its own
|
|
459
|
+
class: focusFilterPanel() and a test both resolve `.sv-grid-menu-search`. -->
|
|
460
|
+
<span class="sv-grid-menu-search-wrap">
|
|
461
|
+
{@render icon("search")}
|
|
462
|
+
<input
|
|
463
|
+
class="sv-grid-menu-search"
|
|
464
|
+
placeholder="Search values..."
|
|
465
|
+
bind:value={ctrl.columnMenuSearch}
|
|
466
|
+
/>
|
|
467
|
+
</span>
|
|
460
468
|
<label class="sv-grid-facet sv-grid-facet-all">
|
|
461
469
|
<input type="checkbox" checked={isAllFacetsChecked(colId)} onchange={() => toggleAllFacets(colId)} />
|
|
462
470
|
<!-- Says what it will actually do: with a query typed it acts on the
|
|
@@ -593,7 +601,7 @@
|
|
|
593
601
|
onclick={() => pinColumnLeft(menuColumnId)}
|
|
594
602
|
>
|
|
595
603
|
<span class="sv-grid-header-icon"
|
|
596
|
-
>{@render icon("
|
|
604
|
+
>{@render icon("pin-left")}</span
|
|
597
605
|
> Pin to left
|
|
598
606
|
</button>
|
|
599
607
|
<button
|
|
@@ -604,7 +612,7 @@
|
|
|
604
612
|
onclick={() => pinColumnRight(menuColumnId)}
|
|
605
613
|
>
|
|
606
614
|
<span class="sv-grid-header-icon"
|
|
607
|
-
>{@render icon("
|
|
615
|
+
>{@render icon("pin-right")}</span
|
|
608
616
|
> Pin to right
|
|
609
617
|
</button>
|
|
610
618
|
<button
|
|
@@ -614,7 +622,7 @@
|
|
|
614
622
|
disabled={!menuPinSide}
|
|
615
623
|
onclick={() => unpinColumn(menuColumnId)}
|
|
616
624
|
>
|
|
617
|
-
<span class="sv-grid-header-icon">{@render icon("
|
|
625
|
+
<span class="sv-grid-header-icon">{@render icon("unpin")}</span> Unpin column
|
|
618
626
|
</button>
|
|
619
627
|
{/if}
|
|
620
628
|
<div class="sv-grid-menu-sep"></div>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { type RowData, type TableFeatures } from "./index.js";
|
|
2
2
|
import "./sv-grid-scrollbar.js";
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
|
+
import type { GridIconName } from "./grid-icons.js";
|
|
4
5
|
import type { SvGridController } from "./SvGrid.controller.svelte.js";
|
|
5
6
|
declare function $$render<TFeatures extends TableFeatures = TableFeatures, TData extends RowData = RowData>(): {
|
|
6
7
|
props: {
|
|
7
8
|
ctrl: SvGridController<TFeatures, TData>;
|
|
8
|
-
icon: Snippet<[
|
|
9
|
+
icon: Snippet<[GridIconName]>;
|
|
9
10
|
};
|
|
10
11
|
exports: {};
|
|
11
12
|
bindings: "";
|
|
@@ -371,6 +371,11 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
|
|
|
371
371
|
field: string;
|
|
372
372
|
label: string;
|
|
373
373
|
}[];
|
|
374
|
+
dates: {
|
|
375
|
+
id: string;
|
|
376
|
+
field: string;
|
|
377
|
+
label: string;
|
|
378
|
+
}[];
|
|
374
379
|
};
|
|
375
380
|
readonly chartSpec: ChartSpec | null;
|
|
376
381
|
applyChartCrossFilter: (category: string) => void;
|
|
@@ -389,6 +394,11 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
|
|
|
389
394
|
dimension: string | null;
|
|
390
395
|
series: string | null;
|
|
391
396
|
measure: string | null;
|
|
397
|
+
measure2: string | null;
|
|
398
|
+
orientation: "horizontal" | "vertical" | null;
|
|
399
|
+
stacked100: boolean | null;
|
|
400
|
+
donut: boolean | null;
|
|
401
|
+
palette: string[] | null;
|
|
392
402
|
reduce: "sum" | "avg" | "count";
|
|
393
403
|
stacked: boolean | null;
|
|
394
404
|
dataLabels: boolean | null;
|
|
@@ -397,6 +407,13 @@ export declare function createSvGridController<TFeatures extends TableFeatures =
|
|
|
397
407
|
valueFormat: ChartValueFormat | null;
|
|
398
408
|
}[];
|
|
399
409
|
applyChartsState(list: ReadonlyArray<Record<string, unknown>>, active?: number): void;
|
|
410
|
+
readonly chartCrossFilterable: boolean;
|
|
411
|
+
chartOrientation: "horizontal" | "vertical";
|
|
412
|
+
chartStacked100: boolean;
|
|
413
|
+
chartDonut: boolean;
|
|
414
|
+
chartPalette: string[] | null;
|
|
415
|
+
chartMeasure2Id: string | null;
|
|
416
|
+
readonly effectiveChartMeasure2Id: string | null;
|
|
400
417
|
chartAiHandler: ((prompt: string) => Promise<Record<string, unknown> | null>) | null;
|
|
401
418
|
applyChartConfig(config: {
|
|
402
419
|
open?: boolean;
|
|
@@ -1661,6 +1661,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1661
1661
|
dimensionId: null,
|
|
1662
1662
|
measureId: null,
|
|
1663
1663
|
seriesId: undefined,
|
|
1664
|
+
measure2Id: null,
|
|
1665
|
+
orientation: null,
|
|
1666
|
+
stacked100: null,
|
|
1667
|
+
donut: null,
|
|
1668
|
+
palette: null,
|
|
1664
1669
|
stacked: null,
|
|
1665
1670
|
dataLabels: null,
|
|
1666
1671
|
logScale: null,
|
|
@@ -1703,6 +1708,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1703
1708
|
const chartableColumns = $derived.by(function chartableColumns_d() {
|
|
1704
1709
|
const dims = [];
|
|
1705
1710
|
const measures = [];
|
|
1711
|
+
// Dates are a subset of dims, tracked separately so the panel can tell
|
|
1712
|
+
// whether a calendar chart is possible at all before the user picks
|
|
1713
|
+
// anything. Offering a type that can only render blank is worse than not
|
|
1714
|
+
// offering it.
|
|
1715
|
+
const dates = [];
|
|
1706
1716
|
for (const col of allColumns) {
|
|
1707
1717
|
const field = col.columnDef.field;
|
|
1708
1718
|
if (!field)
|
|
@@ -1710,10 +1720,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1710
1720
|
const entry = { id: col.id, field, label: columnLabel(col) };
|
|
1711
1721
|
if (isNumericColumn(col))
|
|
1712
1722
|
measures.push(entry);
|
|
1713
|
-
else
|
|
1723
|
+
else {
|
|
1714
1724
|
dims.push(entry);
|
|
1725
|
+
if (columnIsDate(col))
|
|
1726
|
+
dates.push(entry);
|
|
1727
|
+
}
|
|
1715
1728
|
}
|
|
1716
|
-
return { dims, measures };
|
|
1729
|
+
return { dims, measures, dates };
|
|
1717
1730
|
});
|
|
1718
1731
|
const chartColumnDefaults = $derived.by(function chartColumnDefaults_d() {
|
|
1719
1732
|
const { dims, measures } = chartableColumns;
|
|
@@ -1759,6 +1772,15 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1759
1772
|
return configured;
|
|
1760
1773
|
return chartColumnDefaults.measureIds;
|
|
1761
1774
|
});
|
|
1775
|
+
/** Scatter's Y measure. Defaults to the second numeric column, so picking
|
|
1776
|
+
* scatter plots something immediately rather than nothing. */
|
|
1777
|
+
const effectiveChartMeasure2Id = $derived(activeChart.measure2Id ??
|
|
1778
|
+
chartableColumns.measures.find((m) => m.id !== effectiveChartMeasureId)?.id ??
|
|
1779
|
+
null);
|
|
1780
|
+
const effectiveChartOrientation = $derived(activeChart.orientation ?? chartCfg?.orientation ?? "vertical");
|
|
1781
|
+
const effectiveChartStacked100 = $derived(activeChart.stacked100 ?? chartCfg?.stacked100 === true);
|
|
1782
|
+
const effectiveChartDonut = $derived(activeChart.donut ?? !!chartCfg?.donut);
|
|
1783
|
+
const effectiveChartPalette = $derived(activeChart.palette ?? chartCfg?.palette ?? null);
|
|
1762
1784
|
const effectiveChartStacked = $derived(chartStacked ?? chartCfg?.stacked ?? false);
|
|
1763
1785
|
const effectiveChartDataLabels = $derived(chartDataLabels ?? chartCfg?.dataLabels ?? false);
|
|
1764
1786
|
const effectiveChartLogScale = $derived(chartLogScale ?? chartCfg?.yScale === "log");
|
|
@@ -1784,8 +1806,13 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1784
1806
|
? "Count"
|
|
1785
1807
|
: `${chartReduce === "avg" ? "Average" : "Sum"} of ${label}`;
|
|
1786
1808
|
});
|
|
1787
|
-
|
|
1788
|
-
|
|
1809
|
+
/**
|
|
1810
|
+
* Does this column hold dates? Declared type first, then a probe of the
|
|
1811
|
+
* first non-empty value. Lifted out of `chartDimensionIsDate` so the chart
|
|
1812
|
+
* panel can also ask it of columns the user has NOT picked yet, which is
|
|
1813
|
+
* what decides whether a calendar chart is offerable at all.
|
|
1814
|
+
*/
|
|
1815
|
+
function columnIsDate(col) {
|
|
1789
1816
|
if (!col)
|
|
1790
1817
|
return false;
|
|
1791
1818
|
if (col.columnDef.cellDataType === "date")
|
|
@@ -1801,7 +1828,8 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1801
1828
|
return typeof v === "string" && /^\d{4}-\d{2}(-\d{2})?/.test(v);
|
|
1802
1829
|
}
|
|
1803
1830
|
return false;
|
|
1804
|
-
}
|
|
1831
|
+
}
|
|
1832
|
+
const chartDimensionIsDate = $derived.by(() => columnIsDate(allColumns.find((c) => c.id === effectiveChartDimensionId)));
|
|
1805
1833
|
const chartRows = $derived.by(() => {
|
|
1806
1834
|
const rects = getSelectionRects();
|
|
1807
1835
|
const pushLeaf = (out, row) => {
|
|
@@ -1893,11 +1921,27 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1893
1921
|
// null until it resolves, a tick after the panel itself starts loading (which it
|
|
1894
1922
|
// already does). The `getAggregate` server path uses the local bucketsToSpec and
|
|
1895
1923
|
// does not need the engine.
|
|
1924
|
+
// The whole module, not just `rowsToChartSpec`: the panel can now reach
|
|
1925
|
+
// every chart type, and the ones that do not read a categories-by-series
|
|
1926
|
+
// grid (scatter, gauge, treemap, calendar, sankey) need their own builders
|
|
1927
|
+
// from the same lazy chunk. Same `import()`, same boundary.
|
|
1896
1928
|
let chartEngine = $state(null);
|
|
1897
1929
|
$effect(() => {
|
|
1898
1930
|
if (chartingEnabled && !chartEngine)
|
|
1899
|
-
import("./chart.js").then((m) => (chartEngine = m
|
|
1900
|
-
});
|
|
1931
|
+
import("./chart.js").then((m) => (chartEngine = m));
|
|
1932
|
+
});
|
|
1933
|
+
/** Put the chart's numbers in the same locale as the grid's. A grid that set
|
|
1934
|
+
* `localization.locale` has already said which locale its data is in, and a
|
|
1935
|
+
* chart of that data reading in a different one is a bug nobody would think
|
|
1936
|
+
* to look for; `charting.locale` overrides when they really should differ.
|
|
1937
|
+
* Currency has no such default - only the app knows what the numbers are. */
|
|
1938
|
+
const applyChartLocale = (spec) => {
|
|
1939
|
+
const locale = chartCfg?.locale ?? props.localization?.locale;
|
|
1940
|
+
if (locale)
|
|
1941
|
+
spec.locale = locale;
|
|
1942
|
+
if (chartCfg?.currency)
|
|
1943
|
+
spec.currency = chartCfg.currency;
|
|
1944
|
+
};
|
|
1901
1945
|
const chartSpec = $derived.by(() => {
|
|
1902
1946
|
if (!chartingEnabled || !chartCfg)
|
|
1903
1947
|
return null;
|
|
@@ -1917,6 +1961,7 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1917
1961
|
spec.yScale = "log";
|
|
1918
1962
|
if (effectiveChartValueFormat)
|
|
1919
1963
|
spec.valueFormat = effectiveChartValueFormat;
|
|
1964
|
+
applyChartLocale(spec);
|
|
1920
1965
|
return spec;
|
|
1921
1966
|
}
|
|
1922
1967
|
if (chartCfg.buildSpec)
|
|
@@ -1929,33 +1974,69 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1929
1974
|
if (!chartEngine)
|
|
1930
1975
|
return null;
|
|
1931
1976
|
const seriesField = fieldOf(effectiveChartSeriesId);
|
|
1932
|
-
const
|
|
1977
|
+
const rowsForChart = chartRows;
|
|
1978
|
+
// Some types read the rows directly rather than a grouped grid: a scatter
|
|
1979
|
+
// point is one row, a gauge has no category axis at all, and a box plot
|
|
1980
|
+
// needs the whole sample per group rather than one reduced number. The
|
|
1981
|
+
// engine owns that dispatch - naming each type here would put them in the
|
|
1982
|
+
// base bundle, which every grid pays for whether or not it ever charts.
|
|
1983
|
+
// Everything else falls through to the aggregation below and is reshaped
|
|
1984
|
+
// afterwards, so reduce / sort / topN / "Other" keep working for all of it.
|
|
1985
|
+
const direct = chartEngine.rowsToDirectSpec(chartType, rowsForChart, {
|
|
1986
|
+
category: category,
|
|
1987
|
+
...(values[0] ? { value: values[0] } : {}),
|
|
1988
|
+
...(fieldOf(effectiveChartMeasure2Id) ? { value2: fieldOf(effectiveChartMeasure2Id) } : {}),
|
|
1989
|
+
...(seriesField ? { series: seriesField } : {}),
|
|
1990
|
+
reduce: chartReduce,
|
|
1991
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
1992
|
+
});
|
|
1993
|
+
if (direct)
|
|
1994
|
+
return direct;
|
|
1995
|
+
// A direct type that could not build (scatter with no Y picked yet, for
|
|
1996
|
+
// instance) must not fall through to the bar-chart path and draw the wrong
|
|
1997
|
+
// chart - it renders the panel's empty state instead.
|
|
1998
|
+
if (chartType === "scatter" || chartType === "gauge" || chartType === "boxplot")
|
|
1999
|
+
return null;
|
|
2000
|
+
const spec = chartEngine.rowsToChartSpec(rowsForChart, {
|
|
1933
2001
|
type: chartType,
|
|
1934
2002
|
category: category,
|
|
1935
2003
|
value: values.length === 1 ? values[0] : values,
|
|
1936
2004
|
...(seriesField ? { series: seriesField } : {}),
|
|
1937
2005
|
reduce: chartReduce,
|
|
1938
|
-
stacked: effectiveChartStacked ||
|
|
1939
|
-
stacked100:
|
|
1940
|
-
...(
|
|
2006
|
+
stacked: effectiveChartStacked || effectiveChartStacked100,
|
|
2007
|
+
stacked100: effectiveChartStacked100,
|
|
2008
|
+
...(effectiveChartPalette ? { palette: effectiveChartPalette } : {}),
|
|
1941
2009
|
...(chartCfg.topN ? { topN: chartCfg.topN } : {}),
|
|
1942
2010
|
...(chartCfg.otherLabel ? { otherLabel: chartCfg.otherLabel } : {}),
|
|
1943
2011
|
...(chartCfg.sort ? { sort: chartCfg.sort } : {}),
|
|
1944
2012
|
});
|
|
1945
|
-
|
|
1946
|
-
|
|
2013
|
+
// Reshape the aggregated grid into whatever this type actually reads.
|
|
2014
|
+
// These run AFTER the aggregation so grouping, reduce, sort, topN and the
|
|
2015
|
+
// "Other" bucket apply to them exactly as they do to a bar chart.
|
|
2016
|
+
if (chartType === "treemap")
|
|
2017
|
+
spec.treemap = chartEngine.specToTreemap(spec);
|
|
2018
|
+
else if (chartType === "calendar")
|
|
2019
|
+
spec.calendarValues = chartEngine.specToCalendar(spec);
|
|
2020
|
+
else if (chartType === "sankey") {
|
|
2021
|
+
const flow = chartEngine.specToSankey(spec);
|
|
2022
|
+
spec.sankeyNodes = flow.nodes;
|
|
2023
|
+
spec.sankeyLinks = flow.links;
|
|
2024
|
+
}
|
|
2025
|
+
if (effectiveChartOrientation === "horizontal")
|
|
2026
|
+
spec.orientation = "horizontal";
|
|
1947
2027
|
if (effectiveChartTimeAxis)
|
|
1948
2028
|
spec.xType = "time";
|
|
1949
2029
|
if (effectiveChartLogScale)
|
|
1950
2030
|
spec.yScale = "log";
|
|
1951
2031
|
if (effectiveChartValueFormat)
|
|
1952
2032
|
spec.valueFormat = effectiveChartValueFormat;
|
|
2033
|
+
applyChartLocale(spec);
|
|
1953
2034
|
if (chartType !== "pie" && spec.orientation !== "horizontal" && !spec.yAxisTitle && chartAutoYAxisTitle) {
|
|
1954
2035
|
spec.yAxisTitle = chartAutoYAxisTitle;
|
|
1955
2036
|
}
|
|
1956
2037
|
if (effectiveChartValueFormat && activeChart.valueFormat === null) { /* inherited default */ }
|
|
1957
2038
|
if (chartType === "pie") {
|
|
1958
|
-
if (
|
|
2039
|
+
if (effectiveChartDonut)
|
|
1959
2040
|
spec.innerRadius = typeof chartCfg.donut === "number" ? chartCfg.donut : 0.6;
|
|
1960
2041
|
}
|
|
1961
2042
|
if (chartCfg.annotations)
|
|
@@ -1982,7 +2063,18 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
1982
2063
|
}
|
|
1983
2064
|
return spec;
|
|
1984
2065
|
});
|
|
2066
|
+
/**
|
|
2067
|
+
* Can a click on this chart be turned back into a grid filter?
|
|
2068
|
+
*
|
|
2069
|
+
* Only where a clicked mark maps to exactly one value of the charted
|
|
2070
|
+
* dimension. It does not for a sankey (clicking a TARGET would filter the
|
|
2071
|
+
* SOURCE column by a target value and empty the grid), a gauge (no category
|
|
2072
|
+
* at all), or a scatter (the mark carries a point label, not a group).
|
|
2073
|
+
*/
|
|
2074
|
+
const chartCrossFilterable = $derived(chartType !== "sankey" && chartType !== "gauge" && chartType !== "scatter");
|
|
1985
2075
|
function applyChartCrossFilter(category) {
|
|
2076
|
+
if (!chartCrossFilterable)
|
|
2077
|
+
return;
|
|
1986
2078
|
const dimId = effectiveChartDimensionId;
|
|
1987
2079
|
if (!dimId)
|
|
1988
2080
|
return;
|
|
@@ -3418,6 +3510,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3418
3510
|
dimension: c.dimensionId,
|
|
3419
3511
|
series: c.seriesId ?? null,
|
|
3420
3512
|
measure: c.measureId,
|
|
3513
|
+
measure2: c.measure2Id,
|
|
3514
|
+
orientation: c.orientation,
|
|
3515
|
+
stacked100: c.stacked100,
|
|
3516
|
+
donut: c.donut,
|
|
3517
|
+
palette: c.palette,
|
|
3421
3518
|
reduce: c.reduce,
|
|
3422
3519
|
stacked: c.stacked,
|
|
3423
3520
|
dataLabels: c.dataLabels,
|
|
@@ -3435,6 +3532,11 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3435
3532
|
dimensionId: c.dimension ?? null,
|
|
3436
3533
|
measureId: c.measure ?? null,
|
|
3437
3534
|
seriesId: c.series,
|
|
3535
|
+
measure2Id: c.measure2 ?? null,
|
|
3536
|
+
orientation: c.orientation ?? null,
|
|
3537
|
+
stacked100: c.stacked100 ?? null,
|
|
3538
|
+
donut: c.donut ?? null,
|
|
3539
|
+
palette: c.palette ?? null,
|
|
3438
3540
|
stacked: c.stacked ?? null,
|
|
3439
3541
|
dataLabels: c.dataLabels ?? null,
|
|
3440
3542
|
logScale: c.logScale ?? null,
|
|
@@ -3444,6 +3546,18 @@ export function createSvGridController(rawProps, domIdBase) {
|
|
|
3444
3546
|
charts = restored.length ? restored : [makeChart("Chart 1")];
|
|
3445
3547
|
activeChartIndex = Math.max(0, Math.min(charts.length - 1, active ?? 0));
|
|
3446
3548
|
},
|
|
3549
|
+
get chartCrossFilterable() { return chartCrossFilterable; },
|
|
3550
|
+
get chartOrientation() { return effectiveChartOrientation; },
|
|
3551
|
+
set chartOrientation(v) { activeChart.orientation = v; },
|
|
3552
|
+
get chartStacked100() { return effectiveChartStacked100; },
|
|
3553
|
+
set chartStacked100(v) { activeChart.stacked100 = v; },
|
|
3554
|
+
get chartDonut() { return effectiveChartDonut; },
|
|
3555
|
+
set chartDonut(v) { activeChart.donut = v; },
|
|
3556
|
+
get chartPalette() { return effectiveChartPalette; },
|
|
3557
|
+
set chartPalette(v) { activeChart.palette = v; },
|
|
3558
|
+
get chartMeasure2Id() { return activeChart.measure2Id; },
|
|
3559
|
+
set chartMeasure2Id(v) { activeChart.measure2Id = v; },
|
|
3560
|
+
get effectiveChartMeasure2Id() { return effectiveChartMeasure2Id; },
|
|
3447
3561
|
get chartAiHandler() { return chartAiHandler; },
|
|
3448
3562
|
set chartAiHandler(v) { chartAiHandler = v; },
|
|
3449
3563
|
applyChartConfig(config) {
|
package/dist/SvGrid.css
CHANGED
|
@@ -153,6 +153,10 @@
|
|
|
153
153
|
/* Advanced-filter indicator. Reads as state rather than as another action, so
|
|
154
154
|
it uses the accent fill the toolbar buttons reserve for `is-active` instead
|
|
155
155
|
of the neutral button shell. */
|
|
156
|
+
.sv-grid-advf-chip > .sv-grid-icon {
|
|
157
|
+
width: 13px;
|
|
158
|
+
height: 13px;
|
|
159
|
+
}
|
|
156
160
|
.sv-grid-advf-chip {
|
|
157
161
|
display: inline-flex;
|
|
158
162
|
align-items: center;
|
|
@@ -261,6 +265,12 @@
|
|
|
261
265
|
flex-shrink: 0;
|
|
262
266
|
}
|
|
263
267
|
.sv-grid-tool-panel-btn {
|
|
268
|
+
/* Centred as flex because these draw an icon now, not a bare character: a
|
|
269
|
+
glyph default still centres, and an SVG override no longer sits on the
|
|
270
|
+
text baseline. */
|
|
271
|
+
display: inline-flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
justify-content: center;
|
|
264
274
|
width: 22px;
|
|
265
275
|
height: 22px;
|
|
266
276
|
border: 0;
|
|
@@ -807,9 +817,18 @@
|
|
|
807
817
|
color: var(--sg-accent, #2563eb);
|
|
808
818
|
}
|
|
809
819
|
.sv-grid-group-caret {
|
|
820
|
+
display: inline-flex;
|
|
821
|
+
align-items: center;
|
|
822
|
+
justify-content: center;
|
|
823
|
+
width: 10px;
|
|
824
|
+
height: 10px;
|
|
810
825
|
flex-shrink: 0;
|
|
811
826
|
transition: transform 140ms ease;
|
|
812
827
|
}
|
|
828
|
+
.sv-grid-group-caret > .sv-grid-icon {
|
|
829
|
+
width: 100%;
|
|
830
|
+
height: 100%;
|
|
831
|
+
}
|
|
813
832
|
/* Collapsed: point the caret right (like a closed disclosure). */
|
|
814
833
|
.sv-grid-group-toggle.is-collapsed .sv-grid-group-caret {
|
|
815
834
|
transform: rotate(-90deg);
|
|
@@ -1855,6 +1874,23 @@ select.sv-grid-fr-editor {
|
|
|
1855
1874
|
display: block;
|
|
1856
1875
|
flex: none;
|
|
1857
1876
|
}
|
|
1877
|
+
/* A consumer icon from the `icons` prop. SvGrid wraps the override in this box
|
|
1878
|
+
rather than asking the consumer to add `.sv-grid-icon` themselves, so it
|
|
1879
|
+
inherits every per-site size rule below and the expander's rotate-on-open
|
|
1880
|
+
without needing to know those rules exist. Must stay AFTER .sv-grid-icon:
|
|
1881
|
+
equal specificity, and this display has to win. */
|
|
1882
|
+
.sv-grid-icon-custom {
|
|
1883
|
+
display: inline-flex;
|
|
1884
|
+
align-items: center;
|
|
1885
|
+
justify-content: center;
|
|
1886
|
+
line-height: 0;
|
|
1887
|
+
}
|
|
1888
|
+
.sv-grid-icon-custom > svg,
|
|
1889
|
+
.sv-grid-icon-custom > img {
|
|
1890
|
+
width: 100%;
|
|
1891
|
+
height: 100%;
|
|
1892
|
+
display: block;
|
|
1893
|
+
}
|
|
1858
1894
|
|
|
1859
1895
|
.sv-grid-header-cell {
|
|
1860
1896
|
display: flex;
|
|
@@ -2453,14 +2489,19 @@ select.sv-grid-fr-editor {
|
|
|
2453
2489
|
group banners and auto-group columns, SvGroupCell and SvTree all draw the
|
|
2454
2490
|
same `chevron-right` and rotate it open, rather than swapping between two
|
|
2455
2491
|
different glyphs. */
|
|
2456
|
-
.
|
|
2457
|
-
|
|
2492
|
+
/* Direct child, not descendant. The column-group header button also carries
|
|
2493
|
+
`sv-grid-group-toggle` + `aria-expanded`, and its caret is wrapped one level
|
|
2494
|
+
deeper - a descendant selector would rotate that caret 90deg too, so an
|
|
2495
|
+
expanded down-caret would point left. The three row/tree expanders render
|
|
2496
|
+
their icon as an immediate child, so `>` changes nothing for them. */
|
|
2497
|
+
.sv-grid-tree-toggle > .sv-grid-icon,
|
|
2498
|
+
.sv-grid-group-toggle > .sv-grid-icon {
|
|
2458
2499
|
width: 12px;
|
|
2459
2500
|
height: 12px;
|
|
2460
2501
|
transition: transform 0.15s ease;
|
|
2461
2502
|
}
|
|
2462
|
-
.sv-grid-tree-toggle[aria-expanded="true"] .sv-grid-icon,
|
|
2463
|
-
.sv-grid-group-toggle[aria-expanded="true"] .sv-grid-icon {
|
|
2503
|
+
.sv-grid-tree-toggle[aria-expanded="true"] > .sv-grid-icon,
|
|
2504
|
+
.sv-grid-group-toggle[aria-expanded="true"] > .sv-grid-icon {
|
|
2464
2505
|
transform: rotate(90deg);
|
|
2465
2506
|
}
|
|
2466
2507
|
.sv-grid-tree-toggle:focus-visible {
|
|
@@ -2624,7 +2665,7 @@ select.sv-grid-fr-editor {
|
|
|
2624
2665
|
box-shadow: 0 10px 25px rgba(15, 23, 42, 0.18);
|
|
2625
2666
|
min-width: 280px;
|
|
2626
2667
|
}
|
|
2627
|
-
.sv-grid-find-icon {
|
|
2668
|
+
.sv-grid-find > .sv-grid-icon {
|
|
2628
2669
|
width: 14px; height: 14px;
|
|
2629
2670
|
color: var(--sg-muted, #94a3b8);
|
|
2630
2671
|
flex: none;
|
|
@@ -2868,13 +2909,26 @@ select.sv-grid-fr-editor {
|
|
|
2868
2909
|
opacity: 1;
|
|
2869
2910
|
}
|
|
2870
2911
|
|
|
2871
|
-
/* Magnifier prefix on the menu's search input.
|
|
2912
|
+
/* Magnifier prefix on the menu's search input. Real markup rather than a
|
|
2913
|
+
background-image, so it follows the theme and the `icons` prop can replace
|
|
2914
|
+
it; the input keeps its own class because focusFilterPanel() and a test both
|
|
2915
|
+
resolve `.sv-grid-menu-search`. */
|
|
2872
2916
|
.sv-grid-menu-search {
|
|
2873
2917
|
padding-left: 28px;
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2918
|
+
}
|
|
2919
|
+
.sv-grid-menu-search-wrap {
|
|
2920
|
+
position: relative;
|
|
2921
|
+
display: block;
|
|
2922
|
+
}
|
|
2923
|
+
.sv-grid-menu-search-wrap > .sv-grid-icon {
|
|
2924
|
+
position: absolute;
|
|
2925
|
+
left: 8px;
|
|
2926
|
+
top: 50%;
|
|
2927
|
+
transform: translateY(-50%);
|
|
2928
|
+
width: 14px;
|
|
2929
|
+
height: 14px;
|
|
2930
|
+
color: var(--sg-muted, #94a3b8);
|
|
2931
|
+
pointer-events: none;
|
|
2878
2932
|
}
|
|
2879
2933
|
|
|
2880
2934
|
/* Filter menu vertical rhythm. */
|
|
@@ -2910,6 +2964,11 @@ select.sv-grid-fr-editor {
|
|
|
2910
2964
|
opacity: 1;
|
|
2911
2965
|
}
|
|
2912
2966
|
|
|
2967
|
+
.sv-grid-board-search > .sv-grid-icon {
|
|
2968
|
+
width: 14px;
|
|
2969
|
+
height: 14px;
|
|
2970
|
+
}
|
|
2971
|
+
|
|
2913
2972
|
/* Kanban board mode search box (board.searchable) */
|
|
2914
2973
|
.sv-grid-board-search {
|
|
2915
2974
|
display: flex;
|
|
@@ -3048,6 +3107,16 @@ select.sv-grid-fr-editor {
|
|
|
3048
3107
|
.sv-grid-chart-actions { display: inline-flex; align-items: center; gap: 6px; }
|
|
3049
3108
|
.sv-grid-chart-controls { display: flex; align-items: flex-end; gap: 10px; flex-wrap: wrap; }
|
|
3050
3109
|
.sv-grid-chart-controls:empty { display: none; }
|
|
3110
|
+
/* Says why the chosen chart type cannot draw yet - a missing Split by, too few
|
|
3111
|
+
groups. The picker deliberately does NOT hide such a type: options that come
|
|
3112
|
+
and go as the user filters are worse than one sentence of explanation. */
|
|
3113
|
+
.sv-grid-chart-hint {
|
|
3114
|
+
flex-basis: 100%;
|
|
3115
|
+
margin: 2px 0 0;
|
|
3116
|
+
font-size: 11.5px;
|
|
3117
|
+
font-weight: 400;
|
|
3118
|
+
color: var(--sg-muted, #64748b);
|
|
3119
|
+
}
|
|
3051
3120
|
.sv-grid-chart-ctl { display: flex; flex-direction: column; gap: 2px; font-size: 11px; font-weight: 600; color: var(--sg-muted, #64748b); }
|
|
3052
3121
|
.sv-grid-chart-ctl select {
|
|
3053
3122
|
font: inherit; font-size: 12.5px; font-weight: 400; color: var(--sg-fg, #0f172a);
|