@freegridjs/core 1.10.0 → 1.11.0
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 +2 -2
- package/dist/cdn/freegrid.css +9 -18
- package/dist/cdn/freegrid.esm.js +19 -20
- package/dist/cdn/freegrid.min.js +24 -25
- package/dist/index.d.ts +3 -3
- package/dist/index.js +120 -472
- package/dist/locales/de.d.ts +1 -1
- package/dist/locales/de.js +0 -1
- package/dist/locales/en.d.ts +1 -1
- package/dist/locales/en.js +0 -1
- package/dist/locales/es.d.ts +1 -1
- package/dist/locales/es.js +0 -1
- package/dist/locales/fr.d.ts +1 -1
- package/dist/locales/fr.js +0 -1
- package/dist/locales/id.d.ts +1 -1
- package/dist/locales/id.js +0 -1
- package/dist/locales/it.d.ts +1 -1
- package/dist/locales/it.js +0 -1
- package/dist/locales/ja.d.ts +1 -1
- package/dist/locales/ja.js +0 -1
- package/dist/locales/ko.d.ts +1 -1
- package/dist/locales/ko.js +0 -1
- package/dist/locales/pt-BR.d.ts +1 -1
- package/dist/locales/pt-BR.js +0 -1
- package/dist/locales/ru.d.ts +1 -1
- package/dist/locales/ru.js +0 -1
- package/dist/locales/th.d.ts +1 -1
- package/dist/locales/th.js +0 -1
- package/dist/locales/vi.d.ts +1 -1
- package/dist/locales/vi.js +0 -1
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +0 -1
- package/dist/locales/zh-TW.d.ts +1 -1
- package/dist/locales/zh-TW.js +0 -1
- package/dist/styles.css +9 -18
- package/dist/{types-D5xV-gww.d.ts → types-8dux3w2p.d.ts} +13 -148
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -654,6 +654,39 @@ var ColumnModel = class {
|
|
|
654
654
|
}
|
|
655
655
|
};
|
|
656
656
|
|
|
657
|
+
// src/keyboard.ts
|
|
658
|
+
var imeKey = (e) => e.isComposing || e.keyCode === 229;
|
|
659
|
+
var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "PageUp", "PageDown"]);
|
|
660
|
+
function nextFocus(key, mods, p, s) {
|
|
661
|
+
const r = (i) => Math.max(0, Math.min(s.rows - 1, i));
|
|
662
|
+
const c = (i) => Math.max(0, Math.min(s.cols - 1, i));
|
|
663
|
+
switch (key) {
|
|
664
|
+
case "ArrowUp":
|
|
665
|
+
return { rowIndex: r(p.rowIndex - 1), colIndex: p.colIndex };
|
|
666
|
+
case "ArrowDown":
|
|
667
|
+
return { rowIndex: r(p.rowIndex + 1), colIndex: p.colIndex };
|
|
668
|
+
case "ArrowLeft":
|
|
669
|
+
return { rowIndex: p.rowIndex, colIndex: c(p.colIndex - 1) };
|
|
670
|
+
case "ArrowRight":
|
|
671
|
+
return { rowIndex: p.rowIndex, colIndex: c(p.colIndex + 1) };
|
|
672
|
+
case "PageUp":
|
|
673
|
+
return { rowIndex: r(p.rowIndex - s.pageRows), colIndex: p.colIndex };
|
|
674
|
+
case "PageDown":
|
|
675
|
+
return { rowIndex: r(p.rowIndex + s.pageRows), colIndex: p.colIndex };
|
|
676
|
+
case "Home":
|
|
677
|
+
return { rowIndex: mods.ctrl ? 0 : p.rowIndex, colIndex: 0 };
|
|
678
|
+
case "End":
|
|
679
|
+
return { rowIndex: mods.ctrl ? s.rows - 1 : p.rowIndex, colIndex: s.cols - 1 };
|
|
680
|
+
case "Tab": {
|
|
681
|
+
const flat2 = p.rowIndex * s.cols + p.colIndex + (mods.shift ? -1 : 1);
|
|
682
|
+
if (flat2 < 0 || flat2 >= s.rows * s.cols) return null;
|
|
683
|
+
return { rowIndex: Math.floor(flat2 / s.cols), colIndex: flat2 % s.cols };
|
|
684
|
+
}
|
|
685
|
+
default:
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
657
690
|
// src/formula.ts
|
|
658
691
|
var FormulaError = class extends Error {
|
|
659
692
|
constructor(code) {
|
|
@@ -1336,25 +1369,6 @@ function columnFormulaValue(def, row) {
|
|
|
1336
1369
|
}
|
|
1337
1370
|
var formulasOff = (def) => !!scopes.get(def)?.off?.();
|
|
1338
1371
|
var isFormula = (v) => typeof v === "string" && v.length > 1 && v.startsWith("=");
|
|
1339
|
-
function formulaRefs(formula) {
|
|
1340
|
-
let n;
|
|
1341
|
-
try {
|
|
1342
|
-
n = compile(formula);
|
|
1343
|
-
} catch {
|
|
1344
|
-
return null;
|
|
1345
|
-
}
|
|
1346
|
-
const out = /* @__PURE__ */ new Set();
|
|
1347
|
-
const walk = (x) => {
|
|
1348
|
-
if (x.k === "ref") out.add(x.name);
|
|
1349
|
-
else if (x.k === "un") walk(x.a);
|
|
1350
|
-
else if (x.k === "bin") {
|
|
1351
|
-
walk(x.a);
|
|
1352
|
-
walk(x.b);
|
|
1353
|
-
} else if (x.k === "call") x.args.forEach(walk);
|
|
1354
|
-
};
|
|
1355
|
-
walk(n);
|
|
1356
|
-
return [...out];
|
|
1357
|
-
}
|
|
1358
1372
|
|
|
1359
1373
|
// src/rows/values.ts
|
|
1360
1374
|
function getCellValue(def, row) {
|
|
@@ -1563,6 +1577,7 @@ var FilterRow = class {
|
|
|
1563
1577
|
});
|
|
1564
1578
|
input.addEventListener("keydown", (e) => {
|
|
1565
1579
|
e.stopPropagation();
|
|
1580
|
+
if (imeKey(e)) return;
|
|
1566
1581
|
if (e.key === "Enter") {
|
|
1567
1582
|
clearTimeout(this.timers.get(c.id));
|
|
1568
1583
|
apply();
|
|
@@ -1852,8 +1867,10 @@ function extreme(values, pickMax) {
|
|
|
1852
1867
|
function aggregate(agg, values, rows) {
|
|
1853
1868
|
if (typeof agg === "function") return agg(values, rows);
|
|
1854
1869
|
switch (agg) {
|
|
1855
|
-
case "sum":
|
|
1856
|
-
|
|
1870
|
+
case "sum": {
|
|
1871
|
+
const nums = numbers(values);
|
|
1872
|
+
return nums.length || !values.length ? nums.reduce((s, n) => s + n, 0) : null;
|
|
1873
|
+
}
|
|
1857
1874
|
case "avg": {
|
|
1858
1875
|
const nums = numbers(values);
|
|
1859
1876
|
return nums.length ? nums.reduce((s, n) => s + n, 0) / nums.length : null;
|
|
@@ -2238,7 +2255,8 @@ var Renderer = class {
|
|
|
2238
2255
|
}
|
|
2239
2256
|
if (options.autoHeight && !this.useCards()) {
|
|
2240
2257
|
this.root.classList.add("fg-auto-height");
|
|
2241
|
-
|
|
2258
|
+
const rowsH = count > 0 ? this.rowsHeight(count) : Math.max(this.rowsHeight(count), 3 * rh);
|
|
2259
|
+
this.bodyEl.style.height = `${rowsH + this.topOffset + bottomExtra + Math.max(0, this.scroller.offsetHeight - this.scroller.clientHeight)}px`;
|
|
2242
2260
|
}
|
|
2243
2261
|
const sig = `${headerHeight}|${REGIONS3.map((r) => columns.regionWidth(r)).join(",")}|${this.rowsHeight(count) + this.topOffset + bottomExtra}|${summary && sumPinned ? sumPos : ""}|${sumRows}|${options.autoHeight ? 1 : 0}`;
|
|
2244
2262
|
if (sig !== this.layoutSig) {
|
|
@@ -3682,7 +3700,6 @@ var en = {
|
|
|
3682
3700
|
freeBadge: "Free version",
|
|
3683
3701
|
exportPdf: "Export PDF",
|
|
3684
3702
|
exportJson: "Export JSON",
|
|
3685
|
-
ai: { title: "AI assistant", placeholder: "Ask about the data or tell the grid what to do", send: "Send", thinking: "Working\u2026", undo: "Undo", undone: "Undone", failed: "The assistant did not answer", skipped: "Skipped", done: "Done" },
|
|
3686
3703
|
cf: {
|
|
3687
3704
|
title: "Conditional formatting",
|
|
3688
3705
|
add: "Add rule",
|
|
@@ -3779,38 +3796,6 @@ function eventInside(e, ...els) {
|
|
|
3779
3796
|
return els.some((el2) => !!el2 && el2.contains(e.target));
|
|
3780
3797
|
}
|
|
3781
3798
|
|
|
3782
|
-
// src/keyboard.ts
|
|
3783
|
-
var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "PageUp", "PageDown"]);
|
|
3784
|
-
function nextFocus(key, mods, p, s) {
|
|
3785
|
-
const r = (i) => Math.max(0, Math.min(s.rows - 1, i));
|
|
3786
|
-
const c = (i) => Math.max(0, Math.min(s.cols - 1, i));
|
|
3787
|
-
switch (key) {
|
|
3788
|
-
case "ArrowUp":
|
|
3789
|
-
return { rowIndex: r(p.rowIndex - 1), colIndex: p.colIndex };
|
|
3790
|
-
case "ArrowDown":
|
|
3791
|
-
return { rowIndex: r(p.rowIndex + 1), colIndex: p.colIndex };
|
|
3792
|
-
case "ArrowLeft":
|
|
3793
|
-
return { rowIndex: p.rowIndex, colIndex: c(p.colIndex - 1) };
|
|
3794
|
-
case "ArrowRight":
|
|
3795
|
-
return { rowIndex: p.rowIndex, colIndex: c(p.colIndex + 1) };
|
|
3796
|
-
case "PageUp":
|
|
3797
|
-
return { rowIndex: r(p.rowIndex - s.pageRows), colIndex: p.colIndex };
|
|
3798
|
-
case "PageDown":
|
|
3799
|
-
return { rowIndex: r(p.rowIndex + s.pageRows), colIndex: p.colIndex };
|
|
3800
|
-
case "Home":
|
|
3801
|
-
return { rowIndex: mods.ctrl ? 0 : p.rowIndex, colIndex: 0 };
|
|
3802
|
-
case "End":
|
|
3803
|
-
return { rowIndex: mods.ctrl ? s.rows - 1 : p.rowIndex, colIndex: s.cols - 1 };
|
|
3804
|
-
case "Tab": {
|
|
3805
|
-
const flat2 = p.rowIndex * s.cols + p.colIndex + (mods.shift ? -1 : 1);
|
|
3806
|
-
if (flat2 < 0 || flat2 >= s.rows * s.cols) return null;
|
|
3807
|
-
return { rowIndex: Math.floor(flat2 / s.cols), colIndex: flat2 % s.cols };
|
|
3808
|
-
}
|
|
3809
|
-
default:
|
|
3810
|
-
return null;
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
|
|
3814
3799
|
// src/registry.ts
|
|
3815
3800
|
var liveGrids = /* @__PURE__ */ new Map();
|
|
3816
3801
|
var apiGrids = /* @__PURE__ */ new WeakMap();
|
|
@@ -3993,6 +3978,13 @@ function attachBody(grid) {
|
|
|
3993
3978
|
}
|
|
3994
3979
|
return false;
|
|
3995
3980
|
};
|
|
3981
|
+
const onImeKey = (e) => {
|
|
3982
|
+
const f = grid.store.state.focus;
|
|
3983
|
+
if (e.isComposing || e.key !== "Process" || e.ctrlKey || e.metaKey || e.altKey) return;
|
|
3984
|
+
if (!f || !grid.editor.canEdit(f.rowIndex, f.colId)) return;
|
|
3985
|
+
if (grid.columns.getColumn(f.colId).def.type === "boolean") return;
|
|
3986
|
+
grid.editor.start(f.rowIndex, f.colId, "");
|
|
3987
|
+
};
|
|
3996
3988
|
const onExpandKey = (e) => {
|
|
3997
3989
|
const f = grid.store.state.focus;
|
|
3998
3990
|
if (!f || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey) return false;
|
|
@@ -4142,6 +4134,10 @@ function attachBody(grid) {
|
|
|
4142
4134
|
const cols = grid.columns.displayed();
|
|
4143
4135
|
const count = grid.rowModel.getRowCount();
|
|
4144
4136
|
if (!cols.length || !count) return;
|
|
4137
|
+
if (imeKey(e)) {
|
|
4138
|
+
onImeKey(e);
|
|
4139
|
+
return;
|
|
4140
|
+
}
|
|
4145
4141
|
if (onExpandKey(e)) {
|
|
4146
4142
|
e.preventDefault();
|
|
4147
4143
|
return;
|
|
@@ -4766,7 +4762,6 @@ var ContextMenu = class {
|
|
|
4766
4762
|
{ label: loc.exportCsv, action: () => grid.api.exportCsv() },
|
|
4767
4763
|
...grid.paid ? [{ label: loc.exportExcel, action: () => grid.api.exportXlsx() }] : [],
|
|
4768
4764
|
{ label: loc.exportJson, action: () => grid.api.exportJson() },
|
|
4769
|
-
...grid.options.assistant?.url && grid.paid ? [{ label: `${loc.ai.title}\u2026`, action: () => grid.api.openAssistant() }] : [],
|
|
4770
4765
|
...grid.paid ? [{ label: loc.exportPdf, action: () => grid.api.exportPdf() }] : []
|
|
4771
4766
|
);
|
|
4772
4767
|
if (grid.options.advancedFilter !== false && !grid.options.pivot && grid.paid) items.push("separator", { label: `${loc.advancedFilter}\u2026`, action: () => grid.filterBuilder.open() });
|
|
@@ -5116,6 +5111,7 @@ var Finder = class {
|
|
|
5116
5111
|
input.addEventListener("input", () => this.find(input.value));
|
|
5117
5112
|
bar.addEventListener("keydown", (e) => {
|
|
5118
5113
|
e.stopPropagation();
|
|
5114
|
+
if (imeKey(e)) return;
|
|
5119
5115
|
if (e.key === "Escape") {
|
|
5120
5116
|
e.preventDefault();
|
|
5121
5117
|
this.closeBar();
|
|
@@ -5472,6 +5468,7 @@ var CellEditor = class {
|
|
|
5472
5468
|
wrap2.append(msg);
|
|
5473
5469
|
const onKey = (e) => {
|
|
5474
5470
|
e.stopPropagation();
|
|
5471
|
+
if (imeKey(e)) return;
|
|
5475
5472
|
if (e.key === "Escape") {
|
|
5476
5473
|
e.preventDefault();
|
|
5477
5474
|
this.cancel();
|
|
@@ -7192,8 +7189,10 @@ function buildXlsxSheet(grid, opts = {}) {
|
|
|
7192
7189
|
return d ? { v: d, fmt } : String(v);
|
|
7193
7190
|
}
|
|
7194
7191
|
case "boolean":
|
|
7192
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
7195
7193
|
return typeof v === "boolean" ? v : String(v);
|
|
7196
7194
|
default:
|
|
7195
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
7197
7196
|
return formatters[i](v);
|
|
7198
7197
|
}
|
|
7199
7198
|
};
|
|
@@ -7832,9 +7831,12 @@ var ROLLUP = /* @__PURE__ */ new WeakMap();
|
|
|
7832
7831
|
function rollupSource(row) {
|
|
7833
7832
|
return ROLLUP.get(row);
|
|
7834
7833
|
}
|
|
7835
|
-
function buildRollup(source,
|
|
7836
|
-
const
|
|
7837
|
-
const
|
|
7834
|
+
function buildRollup(source, cols, by) {
|
|
7835
|
+
const isKey = (c) => by ? by.includes(c.id) : !c.def.aggFunc;
|
|
7836
|
+
const keys = cols.filter(isKey).map((c) => c.def);
|
|
7837
|
+
const rest = cols.filter((c) => !isKey(c) && c.def.field && !c.def.valueGetter).map((c) => c.def);
|
|
7838
|
+
const values = rest.filter((d) => d.aggFunc);
|
|
7839
|
+
const others = rest.filter((d) => !d.aggFunc);
|
|
7838
7840
|
const buckets = /* @__PURE__ */ new Map();
|
|
7839
7841
|
for (const r of source) {
|
|
7840
7842
|
const k = keys.map((d) => setKey(getCellValue(d, r))).join("");
|
|
@@ -7852,6 +7854,12 @@ function buildRollup(source, defs) {
|
|
|
7852
7854
|
row[d.field] = null;
|
|
7853
7855
|
}
|
|
7854
7856
|
}
|
|
7857
|
+
if (src.length > 1) {
|
|
7858
|
+
for (const d of others) {
|
|
7859
|
+
const first = setKey(getCellValue(d, src[0]));
|
|
7860
|
+
if (src.some((r) => setKey(getCellValue(d, r)) !== first)) row[d.field] = null;
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7855
7863
|
ROLLUP.set(row, src);
|
|
7856
7864
|
rows.push(row);
|
|
7857
7865
|
}
|
|
@@ -7876,13 +7884,12 @@ var PAID_FEATURES = {
|
|
|
7876
7884
|
formulas: { en: "Formulas", ko: "\uC218\uC2DD" },
|
|
7877
7885
|
find: { en: "Find", ko: "\uCC3E\uAE30" },
|
|
7878
7886
|
conditional: { en: "Conditional formatting", ko: "\uC870\uAC74\uBD80 \uC11C\uC2DD" },
|
|
7879
|
-
assistant: { en: "AI assistant", ko: "AI \uC5B4\uC2DC\uC2A4\uD134\uD2B8" },
|
|
7880
7887
|
merge: { en: "Merged cells", ko: "\uC140 \uBCD1\uD569" },
|
|
7881
7888
|
workbook: { en: "Workbook", ko: "\uC6CC\uD06C\uBD81" }
|
|
7882
7889
|
};
|
|
7883
7890
|
|
|
7884
7891
|
// src/version.ts
|
|
7885
|
-
var version = "1.
|
|
7892
|
+
var version = "1.11.0";
|
|
7886
7893
|
|
|
7887
7894
|
// src/license.ts
|
|
7888
7895
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -8181,7 +8188,7 @@ var ColumnMenu = class {
|
|
|
8181
8188
|
if (grouped.length) g.push([loc.expandAll, () => this.grid.expandAll(true)], [loc.collapseAll, () => this.grid.expandAll(false)]);
|
|
8182
8189
|
popup.append(this.section(g));
|
|
8183
8190
|
}
|
|
8184
|
-
if (this.grid.getRollup()) {
|
|
8191
|
+
if (this.grid.getRollup() && !this.grid.isRollupKey(colId)) {
|
|
8185
8192
|
const cur = def.aggFunc ?? null;
|
|
8186
8193
|
const mark = (on, label) => `${on ? "\u2713 " : ""}${label}`;
|
|
8187
8194
|
const aggs = ["sum", "avg", "min", "max", "count"].map((a) => [mark(cur === a, loc[a]), () => this.grid.setColumnAggFunc(colId, a)]);
|
|
@@ -8318,7 +8325,7 @@ var ColumnMenu = class {
|
|
|
8318
8325
|
};
|
|
8319
8326
|
select.addEventListener("change", sync);
|
|
8320
8327
|
sync();
|
|
8321
|
-
for (const input of [v1, v2]) input.addEventListener("keydown", (e) => e.key === "Enter" && apply());
|
|
8328
|
+
for (const input of [v1, v2]) input.addEventListener("keydown", (e) => e.key === "Enter" && !imeKey(e) && apply());
|
|
8322
8329
|
row.append(select, v1, v2);
|
|
8323
8330
|
const read = () => {
|
|
8324
8331
|
const op = select.value;
|
|
@@ -8430,7 +8437,7 @@ var ColumnMenu = class {
|
|
|
8430
8437
|
grid.setFilter(colId, checked.size === distinct.size ? null : { type: "set", values: entries.filter(([k]) => checked.has(k)).map(([, v]) => v) });
|
|
8431
8438
|
done();
|
|
8432
8439
|
};
|
|
8433
|
-
search.addEventListener("keydown", (e) => e.key === "Enter" && apply());
|
|
8440
|
+
search.addEventListener("keydown", (e) => e.key === "Enter" && !imeKey(e) && apply());
|
|
8434
8441
|
draw();
|
|
8435
8442
|
box.append(this.title(loc), search, allLabel, list, this.actions(colId, loc, apply, done));
|
|
8436
8443
|
return box;
|
|
@@ -9767,394 +9774,6 @@ var FormatDialog = class {
|
|
|
9767
9774
|
}
|
|
9768
9775
|
};
|
|
9769
9776
|
|
|
9770
|
-
// src/assistant.ts
|
|
9771
|
-
var FILTER_TYPES = ["text", "number", "date", "set", "combined"];
|
|
9772
|
-
var CHART_TYPES = ["bar", "stackedBar", "line", "area", "pie", "donut", "combo", "scatter"];
|
|
9773
|
-
var MAX_ACTIONS = 30;
|
|
9774
|
-
function describeColumn(grid, id, def, rows) {
|
|
9775
|
-
const col = { id, header: def.header ?? def.field ?? id };
|
|
9776
|
-
if (def.type) col.type = def.type;
|
|
9777
|
-
if (def.formula) col.formula = def.formula;
|
|
9778
|
-
if (def.aggFunc && typeof def.aggFunc === "string") col.aggFunc = def.aggFunc;
|
|
9779
|
-
let blanks = 0;
|
|
9780
|
-
const counts = /* @__PURE__ */ new Map();
|
|
9781
|
-
let min = Infinity, max = -Infinity, sum = 0, nums = 0;
|
|
9782
|
-
let minText = "", maxText = "";
|
|
9783
|
-
for (const r of rows) {
|
|
9784
|
-
const v = getCellValue(def, r);
|
|
9785
|
-
if (isBlank(v)) {
|
|
9786
|
-
blanks++;
|
|
9787
|
-
continue;
|
|
9788
|
-
}
|
|
9789
|
-
if (typeof v === "number" && Number.isFinite(v)) {
|
|
9790
|
-
nums++;
|
|
9791
|
-
sum += v;
|
|
9792
|
-
if (v < min) min = v;
|
|
9793
|
-
if (v > max) max = v;
|
|
9794
|
-
} else if (def.type === "date") {
|
|
9795
|
-
const t = v instanceof Date ? v.toISOString().slice(0, 10) : String(v);
|
|
9796
|
-
if (!minText || t < minText) minText = t;
|
|
9797
|
-
if (!maxText || t > maxText) maxText = t;
|
|
9798
|
-
}
|
|
9799
|
-
if (counts.size < 5e3) {
|
|
9800
|
-
const k = setKey(v);
|
|
9801
|
-
const c = counts.get(k);
|
|
9802
|
-
if (c) c.n++;
|
|
9803
|
-
else counts.set(k, { value: v, n: 1 });
|
|
9804
|
-
}
|
|
9805
|
-
}
|
|
9806
|
-
col.blanks = blanks;
|
|
9807
|
-
col.distinct = counts.size;
|
|
9808
|
-
if (nums) Object.assign(col, { min, max, avg: Math.round(sum / nums * 1e3) / 1e3 });
|
|
9809
|
-
if (minText) Object.assign(col, { min: minText, max: maxText });
|
|
9810
|
-
if (!nums && counts.size) {
|
|
9811
|
-
col.top = [...counts.values()].sort((a, b) => b.n - a.n).slice(0, 12).map((c) => ({ value: String(c.value).slice(0, 80), count: c.n }));
|
|
9812
|
-
}
|
|
9813
|
-
return col;
|
|
9814
|
-
}
|
|
9815
|
-
function assistantRequest(grid, prompt, options) {
|
|
9816
|
-
const rows = grid.rowModel.dataRows().map((n) => n.data);
|
|
9817
|
-
const all2 = grid.columns.getState().map((s2) => grid.columns.getNode(s2.colId)).filter((n) => n && !n.children && !isSystemCol(n.id));
|
|
9818
|
-
const columns = all2.map((n) => ({ ...describeColumn(grid, n.id, n.def, rows), hidden: grid.columns.getState().find((s2) => s2.colId === n.id)?.hide || void 0 }));
|
|
9819
|
-
const sampleN = typeof options.sampleRows === "number" && options.sampleRows >= 0 ? Math.min(50, options.sampleRows) : 5;
|
|
9820
|
-
const sample = rows.slice(0, sampleN).map((r) => Object.fromEntries(all2.map((n) => [n.id, getCellValue(n.def, r)])));
|
|
9821
|
-
const s = grid.store.state;
|
|
9822
|
-
return {
|
|
9823
|
-
prompt: String(prompt).slice(0, 2e3),
|
|
9824
|
-
locale: s.locale.code,
|
|
9825
|
-
rowCount: rows.length,
|
|
9826
|
-
totalRows: grid.rowModel.server ? rows.length : grid.store.state.data.length,
|
|
9827
|
-
columns,
|
|
9828
|
-
sample,
|
|
9829
|
-
state: {
|
|
9830
|
-
sort: s.sort,
|
|
9831
|
-
filters: s.filters,
|
|
9832
|
-
quickFilter: s.quickFilter,
|
|
9833
|
-
advancedFilter: s.advancedFilter,
|
|
9834
|
-
groupBy: grid.getGroupBy(),
|
|
9835
|
-
pivot: grid.getPivot(),
|
|
9836
|
-
conditionalFormats: grid.getConditionalFormats()
|
|
9837
|
-
},
|
|
9838
|
-
...options.context ? { context: options.context() } : {}
|
|
9839
|
-
};
|
|
9840
|
-
}
|
|
9841
|
-
function applyAssistantActions(grid, actions) {
|
|
9842
|
-
const before = { state: grid.api.getState(), defs: grid.columnDefs(), summary: grid.summaryOptions() };
|
|
9843
|
-
const applied = [];
|
|
9844
|
-
const skipped = [];
|
|
9845
|
-
const charts = [];
|
|
9846
|
-
const list = Array.isArray(actions) ? actions.slice(0, MAX_ACTIONS) : [];
|
|
9847
|
-
const known = (id) => typeof id === "string" && !!grid.columns.getNode(id) && !grid.columns.getNode(id).children && !isSystemCol(id);
|
|
9848
|
-
const ok = (a) => applied.push(a);
|
|
9849
|
-
const skip = (a, reason) => skipped.push({ action: a, reason });
|
|
9850
|
-
for (const raw of list) {
|
|
9851
|
-
const a = raw;
|
|
9852
|
-
if (!a || typeof a !== "object" || typeof a.type !== "string") {
|
|
9853
|
-
skip(raw, "not an action");
|
|
9854
|
-
continue;
|
|
9855
|
-
}
|
|
9856
|
-
try {
|
|
9857
|
-
switch (a.type) {
|
|
9858
|
-
case "sort": {
|
|
9859
|
-
const sort = (Array.isArray(a.sort) ? a.sort : []).filter((x) => !!x && known(x.colId) && (x.dir === "asc" || x.dir === "desc"));
|
|
9860
|
-
if (Array.isArray(a.sort) && sort.length !== a.sort.length) skip(a, "unknown column in sort");
|
|
9861
|
-
grid.setSort(sort.map((x) => ({ colId: x.colId, dir: x.dir })));
|
|
9862
|
-
ok(a);
|
|
9863
|
-
break;
|
|
9864
|
-
}
|
|
9865
|
-
case "filter": {
|
|
9866
|
-
if (!known(a.colId)) {
|
|
9867
|
-
skip(a, "unknown column");
|
|
9868
|
-
break;
|
|
9869
|
-
}
|
|
9870
|
-
const m = a.model;
|
|
9871
|
-
if (m !== null && (!m || typeof m !== "object" || !FILTER_TYPES.includes(m.type))) {
|
|
9872
|
-
skip(a, "bad filter model");
|
|
9873
|
-
break;
|
|
9874
|
-
}
|
|
9875
|
-
grid.setFilter(a.colId, m);
|
|
9876
|
-
ok(a);
|
|
9877
|
-
break;
|
|
9878
|
-
}
|
|
9879
|
-
case "clearFilters":
|
|
9880
|
-
for (const id of Object.keys(grid.store.state.filters)) grid.setFilter(id, null);
|
|
9881
|
-
grid.setAdvancedFilter(null);
|
|
9882
|
-
grid.api.setQuickFilter("");
|
|
9883
|
-
ok(a);
|
|
9884
|
-
break;
|
|
9885
|
-
case "quickFilter":
|
|
9886
|
-
grid.api.setQuickFilter(typeof a.text === "string" ? a.text.slice(0, 200) : "");
|
|
9887
|
-
ok(a);
|
|
9888
|
-
break;
|
|
9889
|
-
case "advancedFilter": {
|
|
9890
|
-
const f = a.filter;
|
|
9891
|
-
const ids = [];
|
|
9892
|
-
const walk = (x) => {
|
|
9893
|
-
const o = x;
|
|
9894
|
-
if (Array.isArray(o?.conditions)) o.conditions.forEach(walk);
|
|
9895
|
-
else ids.push(o?.colId);
|
|
9896
|
-
};
|
|
9897
|
-
if (f) walk(f);
|
|
9898
|
-
if (f && (!Array.isArray(f.conditions) || !ids.every(known))) {
|
|
9899
|
-
skip(a, "unknown column in advanced filter");
|
|
9900
|
-
break;
|
|
9901
|
-
}
|
|
9902
|
-
grid.setAdvancedFilter(f ?? null);
|
|
9903
|
-
ok(a);
|
|
9904
|
-
break;
|
|
9905
|
-
}
|
|
9906
|
-
case "groupBy": {
|
|
9907
|
-
const ids = Array.isArray(a.colIds) ? a.colIds.filter(known) : [];
|
|
9908
|
-
grid.setGroupBy(ids);
|
|
9909
|
-
if (a.expand) grid.expandAll(true);
|
|
9910
|
-
ok(a);
|
|
9911
|
-
break;
|
|
9912
|
-
}
|
|
9913
|
-
case "pivot": {
|
|
9914
|
-
const p = a.pivot;
|
|
9915
|
-
if (p && (!Array.isArray(p.rows) || !Array.isArray(p.values) || !p.rows.every(known) || !(p.columns ?? []).every(known) || !p.values.every((v) => v && known(v.colId)))) {
|
|
9916
|
-
skip(a, "unknown column in pivot");
|
|
9917
|
-
break;
|
|
9918
|
-
}
|
|
9919
|
-
grid.setPivot(p ?? null);
|
|
9920
|
-
ok(a);
|
|
9921
|
-
break;
|
|
9922
|
-
}
|
|
9923
|
-
case "showColumns":
|
|
9924
|
-
case "hideColumns": {
|
|
9925
|
-
const ids = Array.isArray(a.colIds) ? a.colIds.filter(known) : [];
|
|
9926
|
-
if (ids.length) grid.setColumnsVisible(ids, a.type === "showColumns");
|
|
9927
|
-
ok(a);
|
|
9928
|
-
break;
|
|
9929
|
-
}
|
|
9930
|
-
case "conditionalFormat": {
|
|
9931
|
-
const f = a.format;
|
|
9932
|
-
if (!f || !Array.isArray(f.columns) || !f.columns.length || !f.columns.every(known)) {
|
|
9933
|
-
skip(a, "unknown column in format");
|
|
9934
|
-
break;
|
|
9935
|
-
}
|
|
9936
|
-
grid.setConditionalFormats([...grid.getConditionalFormats(), f]);
|
|
9937
|
-
ok(a);
|
|
9938
|
-
break;
|
|
9939
|
-
}
|
|
9940
|
-
case "clearConditionalFormats":
|
|
9941
|
-
grid.setConditionalFormats([]);
|
|
9942
|
-
ok(a);
|
|
9943
|
-
break;
|
|
9944
|
-
case "addColumn": {
|
|
9945
|
-
const c = a.column;
|
|
9946
|
-
if (!c || typeof c.id !== "string" || !/^[\w.-]{1,40}$/.test(c.id) || grid.columns.getNode(c.id)) {
|
|
9947
|
-
skip(a, "bad or taken column id");
|
|
9948
|
-
break;
|
|
9949
|
-
}
|
|
9950
|
-
const f = typeof c.formula === "string" ? c.formula.startsWith("=") ? c.formula : `=${c.formula}` : "";
|
|
9951
|
-
const refs = f ? formulaRefs(f) : null;
|
|
9952
|
-
if (!refs) {
|
|
9953
|
-
skip(a, "formula does not parse");
|
|
9954
|
-
break;
|
|
9955
|
-
}
|
|
9956
|
-
const def = { id: c.id, header: typeof c.header === "string" ? c.header.slice(0, 80) : c.id, formula: f };
|
|
9957
|
-
if (c.type === "number" || c.type === "date" || c.type === "text") def.type = c.type;
|
|
9958
|
-
grid.api.setColumns([...grid.columnDefs(), def]);
|
|
9959
|
-
ok(a);
|
|
9960
|
-
break;
|
|
9961
|
-
}
|
|
9962
|
-
case "summary": {
|
|
9963
|
-
const s = a.summary;
|
|
9964
|
-
if (s && (!Array.isArray(s.rows) || !s.rows.every((r) => r && Object.keys(r.columns ?? {}).every(known)))) {
|
|
9965
|
-
skip(a, "unknown column in summary");
|
|
9966
|
-
break;
|
|
9967
|
-
}
|
|
9968
|
-
grid.setSummary(s ?? null);
|
|
9969
|
-
ok(a);
|
|
9970
|
-
break;
|
|
9971
|
-
}
|
|
9972
|
-
case "chart": {
|
|
9973
|
-
const ids = Array.isArray(a.colIds) ? a.colIds.filter(known) : [];
|
|
9974
|
-
const displayed = grid.columns.displayed().map((c) => c.id);
|
|
9975
|
-
const idx = ids.map((id) => displayed.indexOf(id)).filter((i) => i >= 0);
|
|
9976
|
-
const count = grid.rowModel.getRowCount();
|
|
9977
|
-
if (!idx.length || !count) {
|
|
9978
|
-
skip(a, "nothing to chart");
|
|
9979
|
-
break;
|
|
9980
|
-
}
|
|
9981
|
-
const range = { start: { rowIndex: 0, colId: displayed[Math.min(...idx)] }, end: { rowIndex: Math.min(count, 200) - 1, colId: displayed[Math.max(...idx)] } };
|
|
9982
|
-
charts.push(grid.api.createChart({ type: CHART_TYPES.includes(a.chartType) ? a.chartType : "bar", range, title: typeof a.title === "string" ? a.title.slice(0, 80) : void 0 }));
|
|
9983
|
-
ok(a);
|
|
9984
|
-
break;
|
|
9985
|
-
}
|
|
9986
|
-
case "message":
|
|
9987
|
-
ok(a);
|
|
9988
|
-
break;
|
|
9989
|
-
default:
|
|
9990
|
-
skip(a, "unknown action");
|
|
9991
|
-
}
|
|
9992
|
-
} catch {
|
|
9993
|
-
skip(a, "failed");
|
|
9994
|
-
}
|
|
9995
|
-
}
|
|
9996
|
-
const undo = () => {
|
|
9997
|
-
for (const c of charts) c.destroy();
|
|
9998
|
-
if (grid.columnDefs().length !== before.defs.length || grid.columnDefs().some((d, i) => d !== before.defs[i])) grid.api.setColumns(before.defs);
|
|
9999
|
-
grid.setSummary(before.summary);
|
|
10000
|
-
grid.api.setState(before.state);
|
|
10001
|
-
if (!before.state.conditionalFormats) grid.setConditionalFormats([]);
|
|
10002
|
-
};
|
|
10003
|
-
return { applied, skipped, undo };
|
|
10004
|
-
}
|
|
10005
|
-
async function askAssistant(grid, prompt, options, signal) {
|
|
10006
|
-
if (!options?.url) throw new Error("assistant.url is not set");
|
|
10007
|
-
const headers = typeof options.headers === "function" ? options.headers() : options.headers ?? {};
|
|
10008
|
-
const res = await fetch(options.url, {
|
|
10009
|
-
method: "POST",
|
|
10010
|
-
credentials: options.credentials ?? "same-origin",
|
|
10011
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", ...headers },
|
|
10012
|
-
body: JSON.stringify(assistantRequest(grid, prompt, options)),
|
|
10013
|
-
signal
|
|
10014
|
-
});
|
|
10015
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} from the assistant`);
|
|
10016
|
-
const json = await res.json();
|
|
10017
|
-
const result = applyAssistantActions(grid, Array.isArray(json?.actions) ? json.actions : []);
|
|
10018
|
-
const say = typeof json?.message === "string" ? json.message : result.applied.filter((a) => a.type === "message").map((a) => a.text ?? "").join("\n");
|
|
10019
|
-
return { ...result, message: say.slice(0, 4e3) };
|
|
10020
|
-
}
|
|
10021
|
-
|
|
10022
|
-
// src/interactions/assistantPanel.ts
|
|
10023
|
-
var AssistantPanel = class {
|
|
10024
|
-
constructor(grid) {
|
|
10025
|
-
this.grid = grid;
|
|
10026
|
-
this.popup = null;
|
|
10027
|
-
this.log = null;
|
|
10028
|
-
this.off = [];
|
|
10029
|
-
this.busy = null;
|
|
10030
|
-
}
|
|
10031
|
-
get isOpen() {
|
|
10032
|
-
return !!this.popup;
|
|
10033
|
-
}
|
|
10034
|
-
open() {
|
|
10035
|
-
if (this.popup) {
|
|
10036
|
-
this.popup.querySelector(".fg-ai-input")?.focus();
|
|
10037
|
-
return;
|
|
10038
|
-
}
|
|
10039
|
-
const { grid } = this;
|
|
10040
|
-
const loc = grid.store.state.locale;
|
|
10041
|
-
const opts = grid.options.assistant;
|
|
10042
|
-
const popup = el("div", `fg-menu fg-ai-panel fg-theme-${grid.store.state.theme}`, { role: "dialog", "aria-label": loc.ai.title });
|
|
10043
|
-
grid.applyFontTo(popup);
|
|
10044
|
-
const head = el("div", "fg-chooser-head");
|
|
10045
|
-
const title = el("span", "fg-menu-title");
|
|
10046
|
-
title.textContent = loc.ai.title;
|
|
10047
|
-
const x = el("button", "fg-chooser-close", { type: "button", "aria-label": loc.close });
|
|
10048
|
-
x.textContent = "\xD7";
|
|
10049
|
-
x.addEventListener("click", () => this.close());
|
|
10050
|
-
head.append(title, x);
|
|
10051
|
-
const log = el("div", "fg-ai-log", { role: "log", "aria-live": "polite" });
|
|
10052
|
-
this.log = log;
|
|
10053
|
-
const form = el("form", "fg-ai-form");
|
|
10054
|
-
const input = el("textarea", "fg-input fg-ai-input", { rows: "2", placeholder: loc.ai.placeholder, "aria-label": loc.ai.placeholder, maxlength: "2000" });
|
|
10055
|
-
const send2 = el("button", "fg-btn fg-btn-primary fg-ai-send", { type: "submit" });
|
|
10056
|
-
send2.textContent = loc.ai.send;
|
|
10057
|
-
form.append(input, send2);
|
|
10058
|
-
const tips = el("div", "fg-ai-tips");
|
|
10059
|
-
for (const s of (opts?.suggestions ?? []).slice(0, 6)) {
|
|
10060
|
-
const b = el("button", "fg-ai-tip", { type: "button" });
|
|
10061
|
-
b.textContent = s;
|
|
10062
|
-
b.addEventListener("click", () => {
|
|
10063
|
-
input.value = s;
|
|
10064
|
-
input.focus();
|
|
10065
|
-
});
|
|
10066
|
-
tips.append(b);
|
|
10067
|
-
}
|
|
10068
|
-
input.addEventListener("keydown", (e) => {
|
|
10069
|
-
if (e.key === "Enter" && !e.shiftKey && !e.isComposing) {
|
|
10070
|
-
e.preventDefault();
|
|
10071
|
-
form.requestSubmit?.() ?? form.dispatchEvent(new Event("submit", { cancelable: true }));
|
|
10072
|
-
}
|
|
10073
|
-
});
|
|
10074
|
-
form.addEventListener("submit", (e) => {
|
|
10075
|
-
e.preventDefault();
|
|
10076
|
-
const prompt = input.value.trim();
|
|
10077
|
-
if (!prompt || this.busy) return;
|
|
10078
|
-
input.value = "";
|
|
10079
|
-
void this.ask(prompt, send2);
|
|
10080
|
-
});
|
|
10081
|
-
popup.append(head, log, form);
|
|
10082
|
-
if (tips.childElementCount) popup.append(tips);
|
|
10083
|
-
popupParent(grid.renderer.root).append(popup);
|
|
10084
|
-
const r = grid.renderer.root.getBoundingClientRect();
|
|
10085
|
-
const w = popup.offsetWidth || 360;
|
|
10086
|
-
popup.style.left = `${Math.max(4, Math.min(r.right - w - 16, (window.innerWidth || 1024) - w - 4))}px`;
|
|
10087
|
-
popup.style.top = `${Math.max(4, r.top + 16)}px`;
|
|
10088
|
-
this.popup = popup;
|
|
10089
|
-
input.focus();
|
|
10090
|
-
const onDown = (e) => {
|
|
10091
|
-
if (!eventInside(e, popup) && !this.busy) this.close();
|
|
10092
|
-
};
|
|
10093
|
-
const onKey = (e) => {
|
|
10094
|
-
if (e.key === "Escape") this.close();
|
|
10095
|
-
};
|
|
10096
|
-
document.addEventListener("pointerdown", onDown, true);
|
|
10097
|
-
document.addEventListener("keydown", onKey, true);
|
|
10098
|
-
this.off.push(
|
|
10099
|
-
() => document.removeEventListener("pointerdown", onDown, true),
|
|
10100
|
-
() => document.removeEventListener("keydown", onKey, true)
|
|
10101
|
-
);
|
|
10102
|
-
}
|
|
10103
|
-
say(who, text2) {
|
|
10104
|
-
const msg = el("div", `fg-ai-msg fg-ai-${who}`);
|
|
10105
|
-
msg.textContent = text2;
|
|
10106
|
-
this.log?.append(msg);
|
|
10107
|
-
if (this.log) this.log.scrollTop = this.log.scrollHeight;
|
|
10108
|
-
return msg;
|
|
10109
|
-
}
|
|
10110
|
-
async ask(prompt, send2) {
|
|
10111
|
-
const loc = this.grid.store.state.locale;
|
|
10112
|
-
this.say("user", prompt);
|
|
10113
|
-
const wait = this.say("ai", loc.ai.thinking);
|
|
10114
|
-
wait.classList.add("fg-ai-wait");
|
|
10115
|
-
this.busy = new AbortController();
|
|
10116
|
-
send2.disabled = true;
|
|
10117
|
-
try {
|
|
10118
|
-
const res = await askAssistant(this.grid, prompt, this.grid.options.assistant, this.busy.signal);
|
|
10119
|
-
wait.classList.remove("fg-ai-wait");
|
|
10120
|
-
const changed = res.applied.filter((a) => a.type !== "message").length;
|
|
10121
|
-
wait.textContent = res.message || (changed ? loc.ai.done : "");
|
|
10122
|
-
if (res.skipped.length) {
|
|
10123
|
-
const note = el("div", "fg-ai-note");
|
|
10124
|
-
note.textContent = `${loc.ai.skipped}: ${res.skipped.map((s) => s.reason).join(", ")}`;
|
|
10125
|
-
wait.append(note);
|
|
10126
|
-
}
|
|
10127
|
-
if (changed) {
|
|
10128
|
-
const undo = el("button", "fg-btn fg-ai-undo", { type: "button" });
|
|
10129
|
-
undo.textContent = loc.ai.undo;
|
|
10130
|
-
undo.addEventListener("click", () => {
|
|
10131
|
-
res.undo();
|
|
10132
|
-
undo.disabled = true;
|
|
10133
|
-
undo.textContent = loc.ai.undone;
|
|
10134
|
-
});
|
|
10135
|
-
wait.append(undo);
|
|
10136
|
-
}
|
|
10137
|
-
this.grid.events.emit("assistantAnswer", { prompt, message: res.message, applied: res.applied, skipped: res.skipped });
|
|
10138
|
-
} catch (e) {
|
|
10139
|
-
if (e?.name === "AbortError") return;
|
|
10140
|
-
wait.remove();
|
|
10141
|
-
this.say("error", loc.ai.failed);
|
|
10142
|
-
} finally {
|
|
10143
|
-
this.busy = null;
|
|
10144
|
-
send2.disabled = false;
|
|
10145
|
-
}
|
|
10146
|
-
}
|
|
10147
|
-
close() {
|
|
10148
|
-
this.busy?.abort();
|
|
10149
|
-
this.busy = null;
|
|
10150
|
-
for (const f of this.off) f();
|
|
10151
|
-
this.off = [];
|
|
10152
|
-
this.popup?.remove();
|
|
10153
|
-
this.popup = null;
|
|
10154
|
-
this.log = null;
|
|
10155
|
-
}
|
|
10156
|
-
};
|
|
10157
|
-
|
|
10158
9777
|
// src/grid.ts
|
|
10159
9778
|
var autoRowHeight = (size) => Math.round(size * 2.75);
|
|
10160
9779
|
var autoHeaderHeight = (size) => Math.round(size * 3.1);
|
|
@@ -10196,6 +9815,8 @@ var Grid = class {
|
|
|
10196
9815
|
this.pivotData = null;
|
|
10197
9816
|
/** rollup option / setRollup: rows merged by the shown columns without an aggFunc. */
|
|
10198
9817
|
this.rollupOn = false;
|
|
9818
|
+
/** rollup { by }: the columns the rows merge by (only the shown ones count); null: every shown column without an aggFunc. */
|
|
9819
|
+
this.rollupBy = null;
|
|
10199
9820
|
/** The merged rows while rollup applies (not while pivoting), else null. */
|
|
10200
9821
|
this.rollupData = null;
|
|
10201
9822
|
/** Grouping state from before pivot mode, restored when it ends. */
|
|
@@ -10238,7 +9859,8 @@ var Grid = class {
|
|
|
10238
9859
|
formula: (f, row) => evaluateFormula(f, row, /* @__PURE__ */ new Set(), 0, /* @__PURE__ */ new Map(), this.formulaScope)
|
|
10239
9860
|
});
|
|
10240
9861
|
this.formatDialog = new FormatDialog(this);
|
|
10241
|
-
|
|
9862
|
+
/** Selection waiting for the row model to hold the newly merged rows (set by updateRows). */
|
|
9863
|
+
this.rollupSelection = null;
|
|
10242
9864
|
// ── pagination and server rows ──────────────────────────────
|
|
10243
9865
|
/** Cells flashing after a transaction: `${rowId}\u0000${colId}` → time the flash ends. */
|
|
10244
9866
|
/** showOverlay: an overlay the app asked for, shown whatever the row count. */
|
|
@@ -10373,7 +9995,6 @@ var Grid = class {
|
|
|
10373
9995
|
() => this.charts.destroy(),
|
|
10374
9996
|
() => this.filterBuilder.close(),
|
|
10375
9997
|
() => this.formatDialog.close(),
|
|
10376
|
-
() => this.assistantPanel.close(),
|
|
10377
9998
|
attachTooltips(this)
|
|
10378
9999
|
);
|
|
10379
10000
|
if (typeof ResizeObserver !== "undefined") {
|
|
@@ -10382,7 +10003,7 @@ var Grid = class {
|
|
|
10382
10003
|
this.disposers.push(() => ro.disconnect());
|
|
10383
10004
|
}
|
|
10384
10005
|
if (options.pivot) this.setPivot(options.pivot);
|
|
10385
|
-
if (options.rollup) this.setRollup(
|
|
10006
|
+
if (options.rollup) this.setRollup(options.rollup);
|
|
10386
10007
|
void needsFreeBadge(options.licenseKey).then((show) => {
|
|
10387
10008
|
if (show && !this.destroyed) this.renderer.showFreeBadge();
|
|
10388
10009
|
});
|
|
@@ -10486,6 +10107,11 @@ var Grid = class {
|
|
|
10486
10107
|
const paged = !!this.options.pagination;
|
|
10487
10108
|
const data = this.viewRows();
|
|
10488
10109
|
this.rowModel.update({ ...this.store.state, data, display: this.display(), page: paged ? { index: this.page, size: this.pageSize } : null });
|
|
10110
|
+
const remapped = this.rollupSelection;
|
|
10111
|
+
if (remapped) {
|
|
10112
|
+
this.rollupSelection = null;
|
|
10113
|
+
this.setSelected(remapped);
|
|
10114
|
+
}
|
|
10489
10115
|
if (paged && this.page >= this.getPageCount()) {
|
|
10490
10116
|
this.page = this.getPageCount() - 1;
|
|
10491
10117
|
this.rowModel.update({ ...this.store.state, data, display: this.display(), page: { index: this.page, size: this.pageSize } });
|
|
@@ -10498,25 +10124,51 @@ var Grid = class {
|
|
|
10498
10124
|
}
|
|
10499
10125
|
// ── rollup ──────────────────────────────────────────────────
|
|
10500
10126
|
getRollup() {
|
|
10501
|
-
return this.rollupOn;
|
|
10127
|
+
return this.rollupBy ? { by: [...this.rollupBy] } : this.rollupOn;
|
|
10502
10128
|
}
|
|
10503
|
-
|
|
10504
|
-
|
|
10129
|
+
/** true: merge by every shown column without an aggFunc; { by }: merge by the shown `by` columns; false: off. */
|
|
10130
|
+
setRollup(cfg) {
|
|
10131
|
+
const by = cfg && typeof cfg === "object" ? Array.isArray(cfg.by) ? cfg.by.filter((id) => typeof id === "string") : null : null;
|
|
10132
|
+
if (cfg && typeof cfg === "object" && !by) {
|
|
10133
|
+
warn("setRollup: expected true, false or { by: string[] }");
|
|
10134
|
+
return;
|
|
10135
|
+
}
|
|
10136
|
+
const on = !!cfg;
|
|
10505
10137
|
if (on && !this.allow("pivot")) return;
|
|
10506
10138
|
if (on && this.clientOnly("rollup")) return;
|
|
10507
10139
|
if (on && this.options.treeData) {
|
|
10508
10140
|
warn("rollup is not available with treeData");
|
|
10509
10141
|
return;
|
|
10510
10142
|
}
|
|
10511
|
-
if (on === this.rollupOn) return;
|
|
10143
|
+
if (on === this.rollupOn && (by ?? []).join("") === (this.rollupBy ?? []).join("") && !by === !this.rollupBy) return;
|
|
10512
10144
|
this.editor.cancel();
|
|
10513
10145
|
this.rollupOn = on;
|
|
10146
|
+
this.rollupBy = on ? by : null;
|
|
10514
10147
|
this.store.set({ range: null, focus: null });
|
|
10515
10148
|
this.columnsChanged();
|
|
10516
10149
|
}
|
|
10517
10150
|
/** Merge the source rows again (rollup on, not pivoting); the view is updated by the caller. */
|
|
10518
10151
|
rebuildRollup() {
|
|
10519
|
-
|
|
10152
|
+
const old = this.rollupData;
|
|
10153
|
+
this.rollupData = this.rollupOn && !this.pivotCfg ? buildRollup(this.store.state.data, this.dataColumns().map((c) => ({ id: c.id, def: c.def })), this.rollupBy) : null;
|
|
10154
|
+
if (old || this.rollupData) this.remapRollupSelection(old);
|
|
10155
|
+
}
|
|
10156
|
+
/**
|
|
10157
|
+
* Keep the selection across a re-merge: a selected merged row stands for its source rows, and a newly merged
|
|
10158
|
+
* row is selected when every one of its source rows was (so a delete never reaches a row nobody checked).
|
|
10159
|
+
*/
|
|
10160
|
+
remapRollupSelection(old) {
|
|
10161
|
+
const sel = this.store.state.selected;
|
|
10162
|
+
if (!sel.size) return;
|
|
10163
|
+
const idOf = (r) => this.rowModel.idOf(r);
|
|
10164
|
+
const picked = old ? new Set(old.filter((r) => sel.has(idOf(r))).flatMap((r) => rollupSource(r) ?? [r]).map(idOf)) : sel;
|
|
10165
|
+
const now = this.rollupData;
|
|
10166
|
+
const next = now ? now.filter((r) => (rollupSource(r) ?? [r]).every((s) => picked.has(idOf(s)))).map(idOf) : this.store.state.data.map(idOf).filter((id) => picked.has(id));
|
|
10167
|
+
if (next.length !== sel.size || next.some((id) => !sel.has(id))) this.rollupSelection = new Set(next);
|
|
10168
|
+
}
|
|
10169
|
+
/** With rollup { by }: whether the rows merge by this column (it is one of `by`). */
|
|
10170
|
+
isRollupKey(colId) {
|
|
10171
|
+
return !!this.rollupBy?.includes(colId);
|
|
10520
10172
|
}
|
|
10521
10173
|
/** The source rows behind `rows`: merged rollup rows stand for the rows they merge (for group and summary values). */
|
|
10522
10174
|
sourceRowsOf(rows) {
|
|
@@ -12084,7 +11736,7 @@ var Grid = class {
|
|
|
12084
11736
|
removeRows(target, record = true) {
|
|
12085
11737
|
if (this.clientOnly("removeRows")) return [];
|
|
12086
11738
|
const data = this.store.state.data;
|
|
12087
|
-
const match = typeof target === "function" ? target : /* @__PURE__ */ ((set) => (r) => set.has(r))(new Set(target));
|
|
11739
|
+
const match = typeof target === "function" ? target : /* @__PURE__ */ ((set) => (r) => set.has(r))(new Set(Array.isArray(target) ? target.flatMap((r) => rollupSource(r) ?? [r]) : []));
|
|
12088
11740
|
const removed = [];
|
|
12089
11741
|
data.forEach((row, index) => {
|
|
12090
11742
|
let hit = false;
|
|
@@ -12208,10 +11860,12 @@ var Grid = class {
|
|
|
12208
11860
|
const { first, last } = this.renderer.renderedRange();
|
|
12209
11861
|
const code = this.store.state.locale.code;
|
|
12210
11862
|
const pad4 = this.getCellPadding() * 2;
|
|
12211
|
-
const HEADER_EXTRA = pad4 +
|
|
11863
|
+
const HEADER_EXTRA = pad4 + 6 + 14;
|
|
11864
|
+
const FILTER_BTN = 6 + 22;
|
|
11865
|
+
const filters = this.store.state.filters;
|
|
12212
11866
|
const CELL_EXTRA = pad4;
|
|
12213
11867
|
for (const col of cols) {
|
|
12214
|
-
let w = this.renderer.measureText(col.def.header ?? col.def.field ?? "", true) + HEADER_EXTRA;
|
|
11868
|
+
let w = this.renderer.measureText(col.def.header ?? col.def.field ?? "", true) + HEADER_EXTRA + (filters[col.id] ? FILTER_BTN : 0);
|
|
12215
11869
|
const fmt = getFormatter(col.def, code);
|
|
12216
11870
|
for (let i = first; i <= last; i++) {
|
|
12217
11871
|
const node = this.rowModel.getRow(i);
|
|
@@ -12408,9 +12062,10 @@ var Grid = class {
|
|
|
12408
12062
|
}
|
|
12409
12063
|
this.setSelected(next);
|
|
12410
12064
|
}
|
|
12065
|
+
/** The checked rows' data; a merged rollup row stands for its source rows, so these are always rows of the data. */
|
|
12411
12066
|
selectedRows() {
|
|
12412
12067
|
const sel = this.store.state.selected;
|
|
12413
|
-
return sel.size ? this.rowModel.allNodes().filter((n) => sel.has(n.id)).map((n) => n.data) : [];
|
|
12068
|
+
return sel.size ? this.sourceRowsOf(this.rowModel.allNodes().filter((n) => sel.has(n.id)).map((n) => n.data)) : [];
|
|
12414
12069
|
}
|
|
12415
12070
|
/** Filtered data rows in display order (group buckets, parents before children), collapsed ones included. */
|
|
12416
12071
|
orderedDataRows() {
|
|
@@ -12501,7 +12156,6 @@ var Grid = class {
|
|
|
12501
12156
|
this.contextMenu.close();
|
|
12502
12157
|
this.filterBuilder.close();
|
|
12503
12158
|
this.formatDialog.close();
|
|
12504
|
-
this.assistantPanel.close();
|
|
12505
12159
|
if (inPopup) this.renderer.gridEl.focus({ preventScroll: true });
|
|
12506
12160
|
}
|
|
12507
12161
|
onChange(keys) {
|
|
@@ -12602,12 +12256,6 @@ var Grid = class {
|
|
|
12602
12256
|
if (!this.allow("conditional")) return;
|
|
12603
12257
|
this.formatDialog.open(colId);
|
|
12604
12258
|
}),
|
|
12605
|
-
// closed: as if `assistant` were not set
|
|
12606
|
-
askAssistant: guard((prompt) => askAssistant(this, prompt, this.allow("assistant") ? this.options.assistant : void 0)),
|
|
12607
|
-
openAssistant: guard(() => {
|
|
12608
|
-
if (!this.allow("assistant")) return;
|
|
12609
|
-
this.assistantPanel.open();
|
|
12610
|
-
}),
|
|
12611
12259
|
getState: guard(() => this.getState()),
|
|
12612
12260
|
setState: guard((s, o) => this.setState(s, o)),
|
|
12613
12261
|
getScrollPosition: guard(() => this.renderer.getScroll()),
|
|
@@ -12618,7 +12266,7 @@ var Grid = class {
|
|
|
12618
12266
|
setFont: guard((f) => this.setFont(f)),
|
|
12619
12267
|
setCellPadding: guard((px) => this.setCellPadding(px)),
|
|
12620
12268
|
getCellPadding: guard(() => this.getCellPadding()),
|
|
12621
|
-
setRollup: guard((
|
|
12269
|
+
setRollup: guard((cfg) => this.setRollup(cfg)),
|
|
12622
12270
|
getRollup: guard(() => this.getRollup()),
|
|
12623
12271
|
setColumnAggFunc: guard((id, agg) => this.setColumnAggFunc(id, agg)),
|
|
12624
12272
|
setColumnVisible: guard((id, v) => this.setColumnsVisible([id], v)),
|