@freegridjs/core 1.0.0 → 1.1.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/dist/cdn/freegrid.esm.js +27 -14
- package/dist/cdn/freegrid.min.js +27 -14
- package/dist/index.d.ts +8 -6
- package/dist/index.js +576 -195
- package/dist/locales/de.d.ts +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/en.d.ts +1 -1
- package/dist/locales/en.js +1 -0
- package/dist/locales/es.d.ts +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/fr.d.ts +1 -1
- package/dist/locales/fr.js +1 -0
- package/dist/locales/id.d.ts +1 -1
- package/dist/locales/id.js +1 -0
- package/dist/locales/it.d.ts +1 -1
- package/dist/locales/it.js +1 -0
- package/dist/locales/ja.d.ts +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ko.d.ts +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/pt-BR.d.ts +1 -1
- package/dist/locales/pt-BR.js +1 -0
- package/dist/locales/ru.d.ts +1 -1
- package/dist/locales/ru.js +1 -0
- package/dist/locales/th.d.ts +1 -1
- package/dist/locales/th.js +1 -0
- package/dist/locales/vi.d.ts +1 -1
- package/dist/locales/vi.js +1 -0
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +1 -0
- package/dist/locales/zh-TW.d.ts +1 -1
- package/dist/locales/zh-TW.js +1 -0
- package/dist/{types-D9gm8TyF.d.ts → types-BDvXVuCe.d.ts} +40 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -512,14 +512,19 @@ var Parser = class {
|
|
|
512
512
|
}
|
|
513
513
|
};
|
|
514
514
|
var MAX_DEPTH = 32;
|
|
515
|
-
function evaluateFormula(formula, row, seen2 = /* @__PURE__ */ new Set(), depth = 0) {
|
|
515
|
+
function evaluateFormula(formula, row, seen2 = /* @__PURE__ */ new Set(), depth = 0, memo = /* @__PURE__ */ new Map()) {
|
|
516
516
|
try {
|
|
517
517
|
const resolve = (name) => {
|
|
518
518
|
if (!row || !Object.hasOwn(row, name)) throw new FormulaError("#REF!");
|
|
519
519
|
const v2 = row[name];
|
|
520
520
|
if (typeof v2 === "string" && v2.startsWith("=")) {
|
|
521
521
|
if (seen2.has(name) || depth >= MAX_DEPTH) throw new FormulaError("#CYCLE!");
|
|
522
|
-
|
|
522
|
+
let r;
|
|
523
|
+
if (memo.has(name)) r = memo.get(name);
|
|
524
|
+
else {
|
|
525
|
+
r = evaluateFormula(v2, row, /* @__PURE__ */ new Set([...seen2, name]), depth + 1, memo);
|
|
526
|
+
memo.set(name, r);
|
|
527
|
+
}
|
|
523
528
|
if (typeof r === "string" && /^#[A-Z/0]+[!?]$/.test(r)) throw new FormulaError(r);
|
|
524
529
|
return r;
|
|
525
530
|
}
|
|
@@ -560,7 +565,10 @@ var toNumber = (s) => {
|
|
|
560
565
|
};
|
|
561
566
|
var toDateKey = (s) => {
|
|
562
567
|
const m = /^(\d{4})[-./]\s*(\d{1,2})[-./]\s*(\d{1,2})\.?$/.exec(s.trim());
|
|
563
|
-
|
|
568
|
+
if (!m) return null;
|
|
569
|
+
const y = Number(m[1]), mo = Number(m[2]), d = Number(m[3]);
|
|
570
|
+
if (mo < 1 || mo > 12 || d < 1 || d > new Date(Date.UTC(y, mo, 0)).getUTCDate()) return null;
|
|
571
|
+
return `${m[1]}-${m[2].padStart(2, "0")}-${m[3].padStart(2, "0")}`;
|
|
564
572
|
};
|
|
565
573
|
function parseFilterText(type, text2) {
|
|
566
574
|
const t = text2.trim();
|
|
@@ -620,10 +628,6 @@ var FilterRow = class {
|
|
|
620
628
|
const { columns, store } = this.grid;
|
|
621
629
|
const h = this.height;
|
|
622
630
|
const cols = columns.displayed();
|
|
623
|
-
if (store.state.data !== this.data) {
|
|
624
|
-
this.data = store.state.data;
|
|
625
|
-
this.layout = "";
|
|
626
|
-
}
|
|
627
631
|
const key = `${h}|${store.state.locale.code}|${cols.map((c) => `${c.id}:${c.left}:${c.width}:${c.region}`).join(",")}`;
|
|
628
632
|
if (key !== this.layout) {
|
|
629
633
|
this.layout = key;
|
|
@@ -633,7 +637,11 @@ var FilterRow = class {
|
|
|
633
637
|
inner.style.height = `${h}px`;
|
|
634
638
|
inner.replaceChildren(...columns.region(r).map((c) => this.cell(c, h)));
|
|
635
639
|
}
|
|
640
|
+
this.data = store.state.data;
|
|
636
641
|
this.rebuilt = true;
|
|
642
|
+
} else if (store.state.data !== this.data) {
|
|
643
|
+
this.data = store.state.data;
|
|
644
|
+
this.refreshLists();
|
|
637
645
|
}
|
|
638
646
|
this.sync();
|
|
639
647
|
}
|
|
@@ -657,6 +665,33 @@ var FilterRow = class {
|
|
|
657
665
|
}
|
|
658
666
|
}
|
|
659
667
|
}
|
|
668
|
+
/** Refill the set lists from the current rows, keeping each list's choice. */
|
|
669
|
+
refreshLists() {
|
|
670
|
+
for (const r of REGIONS2) {
|
|
671
|
+
for (const select of this.inners[r].querySelectorAll("select[data-col]")) {
|
|
672
|
+
const def = this.grid.columns.getNode(select.dataset.col)?.def;
|
|
673
|
+
if (!def) continue;
|
|
674
|
+
const v = select.value;
|
|
675
|
+
this.fillOptions(select, def);
|
|
676
|
+
if (v && ![...select.options].some((o) => o.value === v)) {
|
|
677
|
+
const o = el("option", "", { value: v });
|
|
678
|
+
o.textContent = String(JSON.parse(v));
|
|
679
|
+
select.append(o);
|
|
680
|
+
}
|
|
681
|
+
select.value = v;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
fillOptions(select, def) {
|
|
686
|
+
const all2 = el("option", "", { value: "" });
|
|
687
|
+
all2.textContent = this.grid.store.state.locale.selectAll;
|
|
688
|
+
const opts = this.distinct(def).map((v) => {
|
|
689
|
+
const o = el("option", "", { value: JSON.stringify(v) });
|
|
690
|
+
o.textContent = v === true ? "\u2713" : v === false ? "\u2717" : String(v);
|
|
691
|
+
return o;
|
|
692
|
+
});
|
|
693
|
+
select.replaceChildren(all2, ...opts);
|
|
694
|
+
}
|
|
660
695
|
cell(c, h) {
|
|
661
696
|
const cell = el("div", "fg-filter-cell");
|
|
662
697
|
cell.style.left = `${c.left}px`;
|
|
@@ -669,14 +704,7 @@ var FilterRow = class {
|
|
|
669
704
|
if (type === "set") {
|
|
670
705
|
const select = el("select", "fg-filter-input", { "aria-label": label });
|
|
671
706
|
select.dataset.col = c.id;
|
|
672
|
-
|
|
673
|
-
all2.textContent = loc.selectAll;
|
|
674
|
-
select.append(all2);
|
|
675
|
-
for (const v of this.distinct(c.def)) {
|
|
676
|
-
const o = el("option", "", { value: JSON.stringify(v) });
|
|
677
|
-
o.textContent = v === true ? "\u2713" : v === false ? "\u2717" : String(v);
|
|
678
|
-
select.append(o);
|
|
679
|
-
}
|
|
707
|
+
this.fillOptions(select, c.def);
|
|
680
708
|
select.addEventListener("change", () => {
|
|
681
709
|
this.grid.setFilter(c.id, select.value ? { type: "set", values: [JSON.parse(select.value)] } : null);
|
|
682
710
|
});
|
|
@@ -2554,6 +2582,7 @@ var en = {
|
|
|
2554
2582
|
clearFilters: "Clear all filters",
|
|
2555
2583
|
exportCsv: "Export CSV",
|
|
2556
2584
|
exportExcel: "Export Excel",
|
|
2585
|
+
print: "Print\u2026",
|
|
2557
2586
|
pivot: "Pivot",
|
|
2558
2587
|
pivotMode: "Pivot mode",
|
|
2559
2588
|
pivotRows: "Row groups",
|
|
@@ -3075,7 +3104,7 @@ function attachBody(grid) {
|
|
|
3075
3104
|
if (!text2 || !e.clipboardData) return;
|
|
3076
3105
|
e.clipboardData.setData("text/plain", text2);
|
|
3077
3106
|
e.preventDefault();
|
|
3078
|
-
grid.clearCells();
|
|
3107
|
+
grid.clearCells(true);
|
|
3079
3108
|
};
|
|
3080
3109
|
const onPaste = (e) => {
|
|
3081
3110
|
if (fromField(e) || nested(e)) return;
|
|
@@ -3615,6 +3644,7 @@ var ContextMenu = class {
|
|
|
3615
3644
|
{ label: loc.exportExcel, action: () => grid.api.exportXlsx() }
|
|
3616
3645
|
);
|
|
3617
3646
|
if (grid.options.advancedFilter !== false && !grid.options.pivot) items.push("separator", { label: `${loc.advancedFilter}\u2026`, action: () => grid.filterBuilder.open() });
|
|
3647
|
+
items.push("separator", { label: loc.print, action: () => grid.api.print() });
|
|
3618
3648
|
return items;
|
|
3619
3649
|
}
|
|
3620
3650
|
pasteFromClipboard() {
|
|
@@ -4144,7 +4174,7 @@ var CellEditor = class {
|
|
|
4144
4174
|
beginRow(row, rowIndex) {
|
|
4145
4175
|
if (this.session?.row === row) return;
|
|
4146
4176
|
if (this.session) this.endRow(true);
|
|
4147
|
-
this.session = { row, id: this.grid.rowModel.idOf(row), before: { ...row } };
|
|
4177
|
+
this.session = { row, id: this.grid.rowModel.idOf(row), before: { ...row }, undoMark: this.grid.history.mark() };
|
|
4148
4178
|
this.grid.renderer.invalidate({ rows: true });
|
|
4149
4179
|
this.grid.events.emit("rowEditStart", { row, rowIndex });
|
|
4150
4180
|
}
|
|
@@ -4153,8 +4183,8 @@ var CellEditor = class {
|
|
|
4153
4183
|
const s = this.session;
|
|
4154
4184
|
if (!s) return;
|
|
4155
4185
|
if (this.cur) {
|
|
4156
|
-
if (commit
|
|
4157
|
-
|
|
4186
|
+
if (commit && !this.commit(null)) return;
|
|
4187
|
+
if (!commit) this.close(false);
|
|
4158
4188
|
}
|
|
4159
4189
|
this.session = null;
|
|
4160
4190
|
const { grid } = this;
|
|
@@ -4167,6 +4197,7 @@ var CellEditor = class {
|
|
|
4167
4197
|
if (!Object.is(s.row[f], s.before[f])) grid.writeCell(changes2, s.row, f, c.id, rowIndex, s.before[f]);
|
|
4168
4198
|
}
|
|
4169
4199
|
grid.endCellTx(changes2);
|
|
4200
|
+
grid.history.rollback(s.undoMark);
|
|
4170
4201
|
}
|
|
4171
4202
|
const changes = cols.filter((c) => !Object.is(s.row[c.def.field], s.before[c.def.field])).map((c) => ({ colId: c.id, oldValue: s.before[c.def.field], newValue: s.row[c.def.field] }));
|
|
4172
4203
|
grid.renderer.invalidate({ rows: true });
|
|
@@ -4183,7 +4214,7 @@ var CellEditor = class {
|
|
|
4183
4214
|
return !node.kind && cellEditable(col.def, node.data, rowIndex);
|
|
4184
4215
|
}
|
|
4185
4216
|
start(rowIndex, colId, initialText) {
|
|
4186
|
-
if (this.cur
|
|
4217
|
+
if (this.cur && !this.commit(null)) return false;
|
|
4187
4218
|
if (!this.canEdit(rowIndex, colId)) return false;
|
|
4188
4219
|
const { grid } = this;
|
|
4189
4220
|
if (grid.veto("beforeEdit", { row: grid.rowModel.getRow(rowIndex).data, rowIndex, colId })) return false;
|
|
@@ -4196,14 +4227,8 @@ var CellEditor = class {
|
|
|
4196
4227
|
grid.setFocus(rowIndex, colId);
|
|
4197
4228
|
renderer.ensureVisible(rowIndex, colId);
|
|
4198
4229
|
renderer.render();
|
|
4199
|
-
const cellEl = renderer.root.querySelector(`.fg-row[data-row="${rowIndex}"] .fg-cell[data-col="${CSS.escape(colId)}"]`);
|
|
4200
|
-
const rootBox = renderer.root.getBoundingClientRect();
|
|
4201
|
-
const box = cellEl?.getBoundingClientRect();
|
|
4202
4230
|
const wrap = el("div", "fg-editor");
|
|
4203
|
-
|
|
4204
|
-
wrap.style.top = `${(box?.top ?? rootBox.top) - rootBox.top}px`;
|
|
4205
|
-
wrap.style.width = `${box?.width || col.width}px`;
|
|
4206
|
-
wrap.style.height = `${box?.height || renderer.opts.rowHeight}px`;
|
|
4231
|
+
this.place(wrap, rowIndex, colId);
|
|
4207
4232
|
let control;
|
|
4208
4233
|
let read;
|
|
4209
4234
|
let dispose = () => {
|
|
@@ -4301,6 +4326,36 @@ var CellEditor = class {
|
|
|
4301
4326
|
grid.events.emit("editStart", { rowIndex, colId });
|
|
4302
4327
|
return true;
|
|
4303
4328
|
}
|
|
4329
|
+
/** Put the editor over its cell; false when the cell is not rendered. */
|
|
4330
|
+
place(wrap, rowIndex, colId) {
|
|
4331
|
+
const { renderer } = this.grid;
|
|
4332
|
+
const cellEl = renderer.root.querySelector(`.fg-row[data-row="${rowIndex}"] .fg-cell[data-col="${CSS.escape(colId)}"]`);
|
|
4333
|
+
const rootBox = renderer.root.getBoundingClientRect();
|
|
4334
|
+
const box = cellEl?.getBoundingClientRect();
|
|
4335
|
+
wrap.style.left = `${(box?.left ?? rootBox.left) - rootBox.left}px`;
|
|
4336
|
+
wrap.style.top = `${(box?.top ?? rootBox.top) - rootBox.top}px`;
|
|
4337
|
+
wrap.style.width = `${box?.width || this.grid.columns.getColumn(colId)?.width}px`;
|
|
4338
|
+
wrap.style.height = `${box?.height || renderer.opts.rowHeight}px`;
|
|
4339
|
+
return !!cellEl;
|
|
4340
|
+
}
|
|
4341
|
+
/** Display index of a row object, or -1 when it is no longer shown. */
|
|
4342
|
+
indexOf(row) {
|
|
4343
|
+
const m = this.grid.rowModel;
|
|
4344
|
+
const i = m.indexOfId(m.idOf(row));
|
|
4345
|
+
return i >= 0 && m.getRow(i)?.data === row ? i : -1;
|
|
4346
|
+
}
|
|
4347
|
+
/** The rows changed underneath (transactions): follow the edited row, or cancel when it is gone. */
|
|
4348
|
+
sync() {
|
|
4349
|
+
const cur = this.cur;
|
|
4350
|
+
if (!cur) return;
|
|
4351
|
+
const i = this.indexOf(cur.row);
|
|
4352
|
+
if (i < 0) return this.cancel();
|
|
4353
|
+
if (i === cur.rowIndex) return;
|
|
4354
|
+
cur.rowIndex = i;
|
|
4355
|
+
this.grid.setFocus(i, cur.colId);
|
|
4356
|
+
this.grid.renderer.render();
|
|
4357
|
+
if (!this.place(cur.wrap, i, cur.colId)) this.commit(null);
|
|
4358
|
+
}
|
|
4304
4359
|
/** Validate and write the value. Returns false (editor stays open) when invalid. */
|
|
4305
4360
|
commit(move) {
|
|
4306
4361
|
const cur = this.cur;
|
|
@@ -4320,18 +4375,30 @@ var CellEditor = class {
|
|
|
4320
4375
|
} else value = raw.value;
|
|
4321
4376
|
const error = grid.cellError(cur.def, cur.row, value);
|
|
4322
4377
|
if (error !== null) return this.fail(value, error);
|
|
4378
|
+
const rowIndex = this.indexOf(cur.row);
|
|
4379
|
+
if (rowIndex < 0) {
|
|
4380
|
+
this.cancel();
|
|
4381
|
+
return false;
|
|
4382
|
+
}
|
|
4383
|
+
cur.rowIndex = rowIndex;
|
|
4323
4384
|
this.close(true);
|
|
4324
|
-
grid.applyEdit(
|
|
4385
|
+
grid.applyEdit(rowIndex, cur.colId, value);
|
|
4325
4386
|
if (this.session && (move === "down" || move === "up")) {
|
|
4326
4387
|
this.endRow(true);
|
|
4327
4388
|
}
|
|
4328
|
-
this.move(
|
|
4389
|
+
this.move(rowIndex, cur.colId, move);
|
|
4329
4390
|
return true;
|
|
4330
4391
|
}
|
|
4331
4392
|
cancel() {
|
|
4332
4393
|
this.close(false);
|
|
4333
4394
|
if (this.session) this.endRow(false);
|
|
4334
4395
|
}
|
|
4396
|
+
/** Teardown: remove the editor without writing values or emitting events. */
|
|
4397
|
+
destroy() {
|
|
4398
|
+
this.cur?.dispose();
|
|
4399
|
+
this.cur = null;
|
|
4400
|
+
this.session = null;
|
|
4401
|
+
}
|
|
4335
4402
|
fail(value, message) {
|
|
4336
4403
|
const cur = this.cur;
|
|
4337
4404
|
cur.wrap.classList.add("fg-editor-invalid");
|
|
@@ -4404,6 +4471,16 @@ var History = class {
|
|
|
4404
4471
|
this.entries[this.index++].redo();
|
|
4405
4472
|
return true;
|
|
4406
4473
|
}
|
|
4474
|
+
/** The current position, for rollback(). */
|
|
4475
|
+
mark() {
|
|
4476
|
+
return this.entries[this.index - 1] ?? null;
|
|
4477
|
+
}
|
|
4478
|
+
/** Forget every step pushed after `mark` (a cancelled edit session). */
|
|
4479
|
+
rollback(mark) {
|
|
4480
|
+
const i = mark ? this.entries.indexOf(mark) + 1 : 0;
|
|
4481
|
+
this.entries.splice(i);
|
|
4482
|
+
this.index = Math.min(this.index, i);
|
|
4483
|
+
}
|
|
4407
4484
|
clear() {
|
|
4408
4485
|
this.entries = [];
|
|
4409
4486
|
this.index = 0;
|
|
@@ -4432,6 +4509,14 @@ var ChangeTracker = class {
|
|
|
4432
4509
|
const o = this.originals.get(row);
|
|
4433
4510
|
return o ? fields.filter((f) => !same(o[f], row[f])) : [];
|
|
4434
4511
|
}
|
|
4512
|
+
/** Baseline `rows` (row → the values that were saved); `deleted` rows are no longer pending. */
|
|
4513
|
+
commit(rows, deleted) {
|
|
4514
|
+
for (const r of deleted) this.deleted.delete(r);
|
|
4515
|
+
for (const [row, values] of rows) {
|
|
4516
|
+
this.created.delete(row);
|
|
4517
|
+
this.originals.set(row, { ...values });
|
|
4518
|
+
}
|
|
4519
|
+
}
|
|
4435
4520
|
get size() {
|
|
4436
4521
|
return this.originals.size + this.created.size + this.deleted.size;
|
|
4437
4522
|
}
|
|
@@ -4594,6 +4679,58 @@ function attachHeader(grid) {
|
|
|
4594
4679
|
};
|
|
4595
4680
|
}
|
|
4596
4681
|
|
|
4682
|
+
// src/rows/sort.ts
|
|
4683
|
+
function compareNonBlank(a, b, collator) {
|
|
4684
|
+
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
4685
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
|
4686
|
+
if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
|
|
4687
|
+
return collator.compare(String(a), String(b));
|
|
4688
|
+
}
|
|
4689
|
+
function normalize(def, v) {
|
|
4690
|
+
if (isBlank(v)) return null;
|
|
4691
|
+
if (def.type === "number") {
|
|
4692
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
4693
|
+
return Number.isNaN(n) ? null : n;
|
|
4694
|
+
}
|
|
4695
|
+
if (def.type === "date") return toDate(v)?.getTime() ?? null;
|
|
4696
|
+
return v;
|
|
4697
|
+
}
|
|
4698
|
+
function compareValues(a, b, collator) {
|
|
4699
|
+
const ba = isBlank(a), bb = isBlank(b);
|
|
4700
|
+
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
4701
|
+
return compareNonBlank(a, b, collator);
|
|
4702
|
+
}
|
|
4703
|
+
function sortNodes(nodes, sort, resolve, collator) {
|
|
4704
|
+
const keys = sort.map((s) => ({ def: resolve(s.colId), mul: s.dir === "desc" ? -1 : 1 })).filter((k) => !!k.def);
|
|
4705
|
+
if (!keys.length) return nodes;
|
|
4706
|
+
const values = keys.map((k) => nodes.map((n) => {
|
|
4707
|
+
const v = getCellValue(k.def, n.data);
|
|
4708
|
+
return k.def.comparator ? v : normalize(k.def, v);
|
|
4709
|
+
}));
|
|
4710
|
+
const order = nodes.map((_, i) => i);
|
|
4711
|
+
order.sort((i, j) => {
|
|
4712
|
+
for (let k = 0; k < keys.length; k++) {
|
|
4713
|
+
const { def, mul } = keys[k];
|
|
4714
|
+
const a = values[k][i], b = values[k][j];
|
|
4715
|
+
let c;
|
|
4716
|
+
if (def.comparator) {
|
|
4717
|
+
try {
|
|
4718
|
+
c = (Number(def.comparator(a, b, nodes[i].data, nodes[j].data)) || 0) * mul;
|
|
4719
|
+
} catch {
|
|
4720
|
+
warn(`comparator of column "${def.id ?? def.field}" threw; treating values as equal`);
|
|
4721
|
+
c = 0;
|
|
4722
|
+
}
|
|
4723
|
+
} else {
|
|
4724
|
+
const ba = isBlank(a), bb = isBlank(b);
|
|
4725
|
+
c = ba || bb ? ba === bb ? 0 : ba ? 1 : -1 : compareNonBlank(a, b, collator) * mul;
|
|
4726
|
+
}
|
|
4727
|
+
if (c) return c;
|
|
4728
|
+
}
|
|
4729
|
+
return i - j;
|
|
4730
|
+
});
|
|
4731
|
+
return order.map((i) => nodes[i]);
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4597
4734
|
// src/export/csv.ts
|
|
4598
4735
|
var FORMULA = /^[=+\-@\t\r]/;
|
|
4599
4736
|
function toCsv(rows, sep = ",") {
|
|
@@ -4692,12 +4829,34 @@ function zipStore(files) {
|
|
|
4692
4829
|
return out;
|
|
4693
4830
|
}
|
|
4694
4831
|
var isZip = (data) => data.length > 4 && data[0] === 80 && data[1] === 75 && data[2] === 3 && data[3] === 4;
|
|
4695
|
-
|
|
4832
|
+
var tooLarge = (max) => new Error(`Zip file is too large: it unpacks to more than ${max >= 1048576 ? `${Math.round(max / 1048576)} MB` : `${max} bytes`}`);
|
|
4833
|
+
async function inflateRaw(data, max) {
|
|
4696
4834
|
if (typeof DecompressionStream === "undefined") throw new Error("This browser cannot read compressed xlsx files (no DecompressionStream)");
|
|
4697
4835
|
const stream = new Response(data).body.pipeThrough(new DecompressionStream("deflate-raw"));
|
|
4698
|
-
|
|
4836
|
+
const reader = stream.getReader();
|
|
4837
|
+
const chunks = [];
|
|
4838
|
+
let size = 0;
|
|
4839
|
+
for (; ; ) {
|
|
4840
|
+
const { done, value } = await reader.read();
|
|
4841
|
+
if (done) break;
|
|
4842
|
+
size += value.length;
|
|
4843
|
+
if (size > max) {
|
|
4844
|
+
await reader.cancel().catch(() => void 0);
|
|
4845
|
+
throw tooLarge(max);
|
|
4846
|
+
}
|
|
4847
|
+
chunks.push(value);
|
|
4848
|
+
}
|
|
4849
|
+
const out = new Uint8Array(size);
|
|
4850
|
+
let at = 0;
|
|
4851
|
+
for (const c of chunks) {
|
|
4852
|
+
out.set(c, at);
|
|
4853
|
+
at += c.length;
|
|
4854
|
+
}
|
|
4855
|
+
return out;
|
|
4699
4856
|
}
|
|
4700
|
-
async function unzip(data) {
|
|
4857
|
+
async function unzip(data, limits = {}) {
|
|
4858
|
+
const maxBytes = limits.maxBytes ?? 200 * 1048576;
|
|
4859
|
+
const maxEntries = limits.maxEntries ?? 1e4;
|
|
4701
4860
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
4702
4861
|
let eocd = -1;
|
|
4703
4862
|
for (let i = data.length - 22; i >= Math.max(0, data.length - 65557); i--) {
|
|
@@ -4708,9 +4867,11 @@ async function unzip(data) {
|
|
|
4708
4867
|
}
|
|
4709
4868
|
if (eocd < 0) throw new Error("Not a zip file");
|
|
4710
4869
|
const count = view.getUint16(eocd + 10, true);
|
|
4870
|
+
if (count > maxEntries) throw new Error(`Zip file has too many entries (${count}, at most ${maxEntries})`);
|
|
4711
4871
|
let p = view.getUint32(eocd + 16, true);
|
|
4712
4872
|
const decoder = new TextDecoder();
|
|
4713
4873
|
const out = /* @__PURE__ */ new Map();
|
|
4874
|
+
let total = 0;
|
|
4714
4875
|
for (let i = 0; i < count; i++) {
|
|
4715
4876
|
if (view.getUint32(p, true) !== 33639248) throw new Error("Broken zip directory");
|
|
4716
4877
|
const method = view.getUint16(p + 10, true);
|
|
@@ -4721,9 +4882,13 @@ async function unzip(data) {
|
|
|
4721
4882
|
const lName = view.getUint16(offset + 26, true), lExtra = view.getUint16(offset + 28, true);
|
|
4722
4883
|
const start = offset + 30 + lName + lExtra;
|
|
4723
4884
|
const raw = data.subarray(start, start + size);
|
|
4724
|
-
|
|
4725
|
-
|
|
4885
|
+
let bytes;
|
|
4886
|
+
if (method === 0) bytes = raw;
|
|
4887
|
+
else if (method === 8) bytes = await inflateRaw(raw, maxBytes - total);
|
|
4726
4888
|
else throw new Error(`Unsupported zip compression ${method}`);
|
|
4889
|
+
total += bytes.length;
|
|
4890
|
+
if (total > maxBytes) throw tooLarge(maxBytes);
|
|
4891
|
+
out.set(name, bytes);
|
|
4727
4892
|
p += 46 + nameLen + extraLen + commentLen;
|
|
4728
4893
|
}
|
|
4729
4894
|
return out;
|
|
@@ -4844,7 +5009,8 @@ function writeXlsx(sheets) {
|
|
|
4844
5009
|
const views = freeze ? `<sheetViews><sheetView workbookViewId="0"><pane ySplit="${freeze}" topLeftCell="A${freeze + 1}" activePane="bottomLeft" state="frozen"/></sheetView></sheetViews>` : '<sheetViews><sheetView workbookViewId="0"/></sheetViews>';
|
|
4845
5010
|
const cols = sheet.widths?.length ? `<cols>${sheet.widths.map((w, i) => `<col min="${i + 1}" max="${i + 1}" width="${Math.max(4, Math.min(120, w)).toFixed(1)}" customWidth="1"/>`).join("")}</cols>` : "";
|
|
4846
5011
|
const filter = sheet.autoFilter && width && freeze && sheet.rows.length >= freeze ? `<autoFilter ref="A${freeze}:${colName(width - 1)}${sheet.rows.length}"/>` : "";
|
|
4847
|
-
const
|
|
5012
|
+
const mergeRefs = (sheet.merges ?? []).filter((m) => typeof m === "string" && /^[A-Z]{1,3}\d{1,7}:[A-Z]{1,3}\d{1,7}$/.test(m));
|
|
5013
|
+
const merges = mergeRefs.length ? `<mergeCells count="${mergeRefs.length}">${mergeRefs.map((m) => `<mergeCell ref="${m}"/>`).join("")}</mergeCells>` : "";
|
|
4848
5014
|
return `${XML}<worksheet xmlns="${NS}" xmlns:r="${REL_NS}">${views}<sheetFormatPr defaultRowHeight="15"/>${cols}<sheetData>${rowsXml}</sheetData>${filter}${merges}</worksheet>`;
|
|
4849
5015
|
});
|
|
4850
5016
|
const styles = `${XML}<styleSheet xmlns="${NS}">` + (customFmts.length ? `<numFmts count="${customFmts.length}">${customFmts.map((f, i) => `<numFmt numFmtId="${164 + i}" formatCode="${esc(f)}"/>`).join("")}</numFmts>` : "") + `<fonts count="${fonts.length}">${fonts.join("")}</fonts><fills count="${fills.length}">${fills.join("")}</fills><borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders><cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs><cellXfs count="${xfs.length}">${xfs.join("")}</cellXfs><cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles></styleSheet>`;
|
|
@@ -4892,6 +5058,8 @@ function colIndex(ref) {
|
|
|
4892
5058
|
}
|
|
4893
5059
|
return n - 1;
|
|
4894
5060
|
}
|
|
5061
|
+
var MAX_ROWS = 1048576;
|
|
5062
|
+
var MAX_COLS = 16384;
|
|
4895
5063
|
async function readXlsx(input) {
|
|
4896
5064
|
const data = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
4897
5065
|
if (!isZip(data)) throw new Error("Not an xlsx file");
|
|
@@ -4919,11 +5087,13 @@ async function readXlsx(input) {
|
|
|
4919
5087
|
if (doc) {
|
|
4920
5088
|
for (const row of tags(doc, "row")) {
|
|
4921
5089
|
const r = Number(row.getAttribute("r") ?? rows.length + 1) - 1;
|
|
5090
|
+
if (!Number.isInteger(r) || r < 0 || r >= MAX_ROWS) continue;
|
|
4922
5091
|
const out = [];
|
|
4923
5092
|
let next = 0;
|
|
4924
5093
|
for (const c of [...row.children].filter((x) => x.localName === "c")) {
|
|
4925
5094
|
const ref = c.getAttribute("r");
|
|
4926
5095
|
const ci = ref ? colIndex(ref) : next;
|
|
5096
|
+
if (ci < 0 || ci >= MAX_COLS) continue;
|
|
4927
5097
|
next = ci + 1;
|
|
4928
5098
|
const t = c.getAttribute("t");
|
|
4929
5099
|
const v = tags(c, "v")[0]?.textContent ?? null;
|
|
@@ -4968,11 +5138,71 @@ function exportRows(grid, mode) {
|
|
|
4968
5138
|
return grid.rowModel.dataRows().map((n) => n.data);
|
|
4969
5139
|
}
|
|
4970
5140
|
var header = (def, id) => def.header ?? def.field ?? id;
|
|
5141
|
+
var ALIGNS = ["left", "center", "right"];
|
|
5142
|
+
var PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
4971
5143
|
var escHtml = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
5144
|
+
var PAPERS = ["A4", "A3", "A5", "B4", "B5", "letter", "legal"];
|
|
5145
|
+
var PORTRAIT_WIDTH = 760;
|
|
5146
|
+
var cssStr = (s) => `"${s.replace(/[\\"<\n\r]/g, (c) => c === "\n" ? "\\A " : c === "\r" ? "" : `\\${c.charCodeAt(0).toString(16)} `)}"`;
|
|
5147
|
+
var hexOf = (argb) => argb ? `#${argb.slice(2)}` : "";
|
|
5148
|
+
function printLines(grid, rows, groupBy) {
|
|
5149
|
+
const out = [];
|
|
5150
|
+
let n = 0;
|
|
5151
|
+
const sort = grid.store.state.sort;
|
|
5152
|
+
const collator = grid.collator();
|
|
5153
|
+
const walk = (list, level) => {
|
|
5154
|
+
if (level >= groupBy.length) {
|
|
5155
|
+
for (const row of list) out.push({ kind: "row", row, n: ++n });
|
|
5156
|
+
return;
|
|
5157
|
+
}
|
|
5158
|
+
const colId = groupBy[level];
|
|
5159
|
+
const def = grid.columns.getNode(colId)?.def;
|
|
5160
|
+
if (!def) return walk(list, level + 1);
|
|
5161
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
5162
|
+
for (const r of list) {
|
|
5163
|
+
const v = getCellValue(def, r);
|
|
5164
|
+
const k = isBlank(v) ? "" : setKey(v);
|
|
5165
|
+
let b = buckets.get(k);
|
|
5166
|
+
if (!b) buckets.set(k, b = { value: v, rows: [] });
|
|
5167
|
+
b.rows.push(r);
|
|
5168
|
+
}
|
|
5169
|
+
const dir = sort.find((x) => x.colId === colId)?.dir === "desc" ? -1 : 1;
|
|
5170
|
+
for (const b of [...buckets.values()].sort((a, c) => compareValues(a.value, c.value, collator) * dir)) {
|
|
5171
|
+
out.push({ kind: "group", colId, value: b.value, rows: b.rows, level });
|
|
5172
|
+
walk(b.rows, level + 1);
|
|
5173
|
+
}
|
|
5174
|
+
};
|
|
5175
|
+
walk(rows, 0);
|
|
5176
|
+
return out;
|
|
5177
|
+
}
|
|
5178
|
+
var PRINT_CSS = `
|
|
5179
|
+
* { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
|
5180
|
+
body { font: 10.5px/1.45 system-ui, 'Pretendard', 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif; color: #111827; margin: 0; }
|
|
5181
|
+
header { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; margin: 0 0 10px; padding-bottom: 8px; border-bottom: 2px solid #111827; }
|
|
5182
|
+
h1 { font-size: 17px; margin: 0; letter-spacing: -.01em; }
|
|
5183
|
+
.sub { margin: 3px 0 0; color: #4b5563; font-size: 11px; }
|
|
5184
|
+
.printed { margin: 0; color: #6b7280; font-size: 9.5px; white-space: nowrap; }
|
|
5185
|
+
table { border-collapse: collapse; width: 100%; }
|
|
5186
|
+
table.fit { table-layout: fixed; }
|
|
5187
|
+
th, td { padding: 4px 7px; border-bottom: 1px solid #e5e7eb; vertical-align: top; overflow-wrap: anywhere; font-variant-numeric: tabular-nums; }
|
|
5188
|
+
th { background: #f3f4f6; color: #374151; font-weight: 600; text-align: left; vertical-align: bottom; border-bottom: 1.5px solid #9ca3af; border-left: 1px solid #e5e7eb; }
|
|
5189
|
+
th.span { text-align: center; }
|
|
5190
|
+
th:first-child { border-left: 0; }
|
|
5191
|
+
thead { display: table-header-group; }
|
|
5192
|
+
tr { break-inside: avoid; page-break-inside: avoid; }
|
|
5193
|
+
.num { text-align: right; color: #9ca3af; }
|
|
5194
|
+
tr.group td { background: #eef2f7; font-weight: 600; border-top: 1px solid #cbd5e1; break-after: avoid; page-break-after: avoid; }
|
|
5195
|
+
tr.group.level-0 td { background: #e2e8f0; }
|
|
5196
|
+
tr.total td { font-weight: 700; background: #f3f4f6; border-top: 1.5px solid #111827; border-bottom: 0; }
|
|
5197
|
+
`;
|
|
4972
5198
|
function buildPrintHtml(grid, opts = {}) {
|
|
4973
5199
|
const cols = exportColumns(grid, opts.columns);
|
|
4974
|
-
const
|
|
5200
|
+
const loc = grid.store.state.locale;
|
|
5201
|
+
const code = loc.code;
|
|
4975
5202
|
const fmts = cols.map((c) => getFormatter(c.def, code));
|
|
5203
|
+
const nf = new Intl.NumberFormat(code);
|
|
5204
|
+
const numbered = opts.rowNumbers ?? !!grid.options.rowNumbers;
|
|
5205
|
+
const align = (def) => ALIGNS.includes(def.align) ? def.align : def.type === "number" ? "right" : def.type === "boolean" ? "center" : "left";
|
|
4976
5206
|
const chains = cols.map((c) => {
|
|
4977
5207
|
const chain = [];
|
|
4978
5208
|
for (let n = c.node; n; n = n.parent) chain.unshift(n);
|
|
@@ -4981,7 +5211,7 @@ function buildPrintHtml(grid, opts = {}) {
|
|
|
4981
5211
|
const depth = Math.max(1, ...chains.map((ch) => ch.length));
|
|
4982
5212
|
const head = [];
|
|
4983
5213
|
for (let level = 0; level < depth; level++) {
|
|
4984
|
-
let tr = "";
|
|
5214
|
+
let tr = level === 0 && numbered ? `<th class="num" rowspan="${depth}">#</th>` : "";
|
|
4985
5215
|
for (let ci = 0; ci < cols.length; ci++) {
|
|
4986
5216
|
const chain = chains[ci];
|
|
4987
5217
|
const leaf = chain.length - 1;
|
|
@@ -4990,29 +5220,108 @@ function buildPrintHtml(grid, opts = {}) {
|
|
|
4990
5220
|
if (ci > 0 && chains[ci - 1][level] === g) continue;
|
|
4991
5221
|
let span = 1;
|
|
4992
5222
|
while (ci + span < cols.length && chains[ci + span][level] === g) span++;
|
|
4993
|
-
tr += `<th colspan="${span}">${escHtml(header(g.def, g.id))}</th>`;
|
|
5223
|
+
tr += `<th colspan="${span}"${span > 1 ? ' class="span"' : ""}>${escHtml(header(g.def, g.id))}</th>`;
|
|
4994
5224
|
} else if (level === leaf) {
|
|
4995
|
-
|
|
5225
|
+
const a = align(cols[ci].def);
|
|
5226
|
+
tr += `<th colspan="1" rowspan="${depth - level}"${a === "left" ? "" : ` style="text-align:${a}"`}>${escHtml(header(cols[ci].def, cols[ci].id))}</th>`;
|
|
4996
5227
|
}
|
|
4997
5228
|
}
|
|
4998
5229
|
head.push(`<tr>${tr}</tr>`);
|
|
4999
5230
|
}
|
|
5000
|
-
const
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5231
|
+
const aggText = (c, i, v, agg) => {
|
|
5232
|
+
if (v == null || v === "") return "";
|
|
5233
|
+
if (agg === "count") return nf.format(v);
|
|
5234
|
+
const type = c.def.type ?? (typeof v === "number" ? "number" : v instanceof Date ? "date" : void 0);
|
|
5235
|
+
return type === c.def.type ? fmts[i](v) : getFormatter({ ...c.def, type }, code)(v);
|
|
5236
|
+
};
|
|
5237
|
+
const labelled = (cls, label, values, indent = 0) => {
|
|
5238
|
+
let lead = 0;
|
|
5239
|
+
while (lead < cols.length - 1 && !values[lead]) lead++;
|
|
5240
|
+
lead = Math.max(1, lead);
|
|
5241
|
+
let tr = numbered ? '<td class="num"></td>' : "";
|
|
5242
|
+
tr += `<td colspan="${lead}"${indent ? ` style="padding-left:${7 + indent * 14}px"` : ""}>${escHtml(label)}</td>`;
|
|
5243
|
+
for (let i = lead; i < cols.length; i++) tr += `<td style="text-align:${align(cols[i].def)}">${escHtml(values[i] ?? "")}</td>`;
|
|
5244
|
+
return `<tr class="${cls}">${tr}</tr>`;
|
|
5245
|
+
};
|
|
5246
|
+
const rows = exportRows(grid, opts.rows);
|
|
5247
|
+
const groupBy = opts.groups !== false && !grid.rowModel.server && !grid.options.treeData ? grid.getGroupBy() : [];
|
|
5248
|
+
const probe = opts.styles !== false && cols.some((c) => c.def.cellClass) ? classStyles(grid) : null;
|
|
5249
|
+
let body = "";
|
|
5250
|
+
try {
|
|
5251
|
+
for (const line of printLines(grid, rows, groupBy)) {
|
|
5252
|
+
if (line.kind === "group") {
|
|
5253
|
+
const def = grid.columns.getNode(line.colId)?.def;
|
|
5254
|
+
const value = isBlank(line.value) ? loc.blanks : def ? getFormatter(def, code)(line.value) : String(line.value);
|
|
5255
|
+
const label = `${def ? header(def, line.colId) : line.colId}: ${value} (${nf.format(line.rows.length)})`;
|
|
5256
|
+
const values = cols.map((c, i) => {
|
|
5257
|
+
if (!c.def.aggFunc || groupBy.includes(c.id)) return void 0;
|
|
5258
|
+
try {
|
|
5259
|
+
return aggText(c, i, aggregate(c.def.aggFunc, line.rows.map((r2) => getCellValue(c.def, r2)), line.rows), c.def.aggFunc) || void 0;
|
|
5260
|
+
} catch {
|
|
5261
|
+
return void 0;
|
|
5262
|
+
}
|
|
5263
|
+
});
|
|
5264
|
+
body += labelled(`group level-${Math.min(line.level, 5)}`, label, values, line.level);
|
|
5265
|
+
continue;
|
|
5266
|
+
}
|
|
5267
|
+
const r = line.row;
|
|
5268
|
+
let tr = numbered ? `<td class="num">${line.n}</td>` : "";
|
|
5269
|
+
cols.forEach((c, i) => {
|
|
5270
|
+
const v = getCellValue(c.def, r);
|
|
5271
|
+
const text2 = isBlank(v) ? "" : fmts[i](v);
|
|
5272
|
+
let style = `text-align:${align(c.def)}`;
|
|
5273
|
+
if (probe && c.def.cellClass) {
|
|
5274
|
+
let cls;
|
|
5275
|
+
try {
|
|
5276
|
+
cls = typeof c.def.cellClass === "function" ? c.def.cellClass({ value: v, formatted: text2, row: r, rowIndex: line.n - 1, column: c.def }) : c.def.cellClass;
|
|
5277
|
+
} catch {
|
|
5278
|
+
cls = void 0;
|
|
5279
|
+
}
|
|
5280
|
+
if (cls) {
|
|
5281
|
+
const st = probe.of(cls);
|
|
5282
|
+
if (st.color) style += `;color:${hexOf(st.color)}`;
|
|
5283
|
+
if (st.fill) style += `;background:${hexOf(st.fill)}`;
|
|
5284
|
+
if (st.bold) style += ";font-weight:600";
|
|
5285
|
+
}
|
|
5286
|
+
}
|
|
5287
|
+
tr += `<td style="${style}">${escHtml(text2)}</td>`;
|
|
5288
|
+
});
|
|
5289
|
+
body += `<tr>${tr}</tr>`;
|
|
5290
|
+
}
|
|
5291
|
+
} finally {
|
|
5292
|
+
probe?.dispose();
|
|
5293
|
+
}
|
|
5294
|
+
let foot = "";
|
|
5295
|
+
const summary = opts.summary !== false ? grid.summary() : null;
|
|
5296
|
+
if (summary) {
|
|
5297
|
+
summary.opts.rows.forEach((def, ri) => {
|
|
5298
|
+
const values = cols.map((c, i) => {
|
|
5299
|
+
const spec = def.columns?.[c.id];
|
|
5300
|
+
return spec === void 0 ? void 0 : aggText(c, i, summary.values[ri]?.[c.id], cellSpec(spec).agg) || void 0;
|
|
5301
|
+
});
|
|
5302
|
+
foot += labelled("total", def.label ?? "", values);
|
|
5303
|
+
});
|
|
5304
|
+
}
|
|
5305
|
+
const widths = cols.map((c) => Math.max(1, c.width));
|
|
5306
|
+
const sum = widths.reduce((a, b) => a + b, 0);
|
|
5307
|
+
const paper = PAPERS.includes(opts.paper) ? opts.paper : "A4";
|
|
5308
|
+
const orientation = opts.orientation === "portrait" || opts.orientation === "landscape" ? opts.orientation : sum + (numbered ? 40 : 0) > PORTRAIT_WIDTH ? "landscape" : "portrait";
|
|
5309
|
+
const fit = opts.fitToWidth !== false;
|
|
5310
|
+
const colgroup = fit ? `<colgroup>${numbered ? '<col style="width:40px">' : ""}${widths.map((w) => `<col style="width:${Math.round(w / sum * 1e3) / 10}%">`).join("")}</colgroup>` : "";
|
|
5005
5311
|
const title = escHtml(opts.title ?? document.title ?? "");
|
|
5312
|
+
const printed = opts.printedAt !== false ? new Intl.DateTimeFormat(code, { dateStyle: "medium", timeStyle: "short" }).format(/* @__PURE__ */ new Date()) : "";
|
|
5313
|
+
const heading = opts.title || opts.subtitle ? `<div>${opts.title ? `<h1>${title}</h1>` : ""}${opts.subtitle ? `<p class="sub">${escHtml(opts.subtitle)}</p>` : ""}</div>` : "<div></div>";
|
|
5314
|
+
const top = opts.title || opts.subtitle || printed ? `<header>${heading}${printed ? `<p class="printed">${escHtml(printed)}</p>` : ""}</header>` : "";
|
|
5315
|
+
const small = "font: 8.5px system-ui, sans-serif; color: #6b7280;";
|
|
5316
|
+
const page = [
|
|
5317
|
+
`size: ${paper} ${orientation}; margin: 12mm 12mm 14mm;`,
|
|
5318
|
+
opts.footer ? `@bottom-left { content: ${cssStr(opts.footer)}; ${small} }` : "",
|
|
5319
|
+
opts.pageNumbers !== false ? `@bottom-right { content: counter(page) " / " counter(pages); ${small} }` : ""
|
|
5320
|
+
].join(" ");
|
|
5321
|
+
const stripes = opts.striped !== false ? "tbody tr:not(.group):nth-child(even) td { background: #f9fafb; }" : "";
|
|
5006
5322
|
return `<!doctype html><html lang="${escHtml(code)}"><head><meta charset="utf-8"><title>${title}</title><style>
|
|
5007
|
-
@page {
|
|
5008
|
-
body
|
|
5009
|
-
h1 { font-size: 15px; margin: 0 0 8px; }
|
|
5010
|
-
table { border-collapse: collapse; width: 100%; }
|
|
5011
|
-
th, td { border: 1px solid #999; padding: 3px 6px; }
|
|
5012
|
-
th { background: #eee; font-weight: 600; }
|
|
5013
|
-
thead { display: table-header-group; }
|
|
5014
|
-
tr { page-break-inside: avoid; }
|
|
5015
|
-
</style></head><body>${opts.title ? `<h1>${title}</h1>` : ""}<table><thead>${opts.headers === false ? "" : head.join("")}</thead><tbody>${body}</tbody></table></body></html>`;
|
|
5323
|
+
@page { ${page} }${PRINT_CSS}${stripes}
|
|
5324
|
+
</style></head><body>${top}<table${fit ? ' class="fit"' : ""}>${colgroup}<thead>${opts.headers === false ? "" : head.join("")}</thead><tbody>${body}</tbody>${foot ? `<tfoot>${foot}</tfoot>` : ""}</table></body></html>`;
|
|
5016
5325
|
}
|
|
5017
5326
|
function printHtml(html) {
|
|
5018
5327
|
if (typeof document === "undefined") return;
|
|
@@ -5048,7 +5357,8 @@ function buildCsv(grid, opts = {}) {
|
|
|
5048
5357
|
const matrix = exportRows(grid, opts.rows).map((r) => cols.map((c) => {
|
|
5049
5358
|
const v = getCellValue(c.def, r);
|
|
5050
5359
|
const t = c.def.type;
|
|
5051
|
-
|
|
5360
|
+
const value = copyValue(c.def, v, code);
|
|
5361
|
+
return { value, text: !(t === "number" || t === "date" || t === "boolean") || typeof v === "string" && !PLAIN_NUMBER.test(value) };
|
|
5052
5362
|
}));
|
|
5053
5363
|
if (opts.headers !== false) matrix.unshift(cols.map((c) => ({ value: header(c.def, c.id), text: true })));
|
|
5054
5364
|
return toCsv(matrix, sep);
|
|
@@ -5559,7 +5869,7 @@ function drawScatter(root, spec, series, top, bottom, nf, short) {
|
|
|
5559
5869
|
|
|
5560
5870
|
// src/interactions/charts.ts
|
|
5561
5871
|
var MAX_SERIES = 12;
|
|
5562
|
-
var
|
|
5872
|
+
var MAX_ROWS2 = 500;
|
|
5563
5873
|
var TYPES = ["bar", "stackedBar", "line", "area", "combo", "pie", "donut", "scatter"];
|
|
5564
5874
|
var GridCharts = class {
|
|
5565
5875
|
constructor(grid) {
|
|
@@ -5577,7 +5887,7 @@ var GridCharts = class {
|
|
|
5577
5887
|
if (a && b) {
|
|
5578
5888
|
const box = normalizeRange({ start: { rowIndex: r.start.rowIndex, colIndex: a.index }, end: { rowIndex: r.end.rowIndex, colIndex: b.index } });
|
|
5579
5889
|
const rows = [];
|
|
5580
|
-
for (let i = box.r1; i <= box.r2 && rows.length <
|
|
5890
|
+
for (let i = box.r1; i <= box.r2 && rows.length < MAX_ROWS2; i++) {
|
|
5581
5891
|
const n = grid.rowModel.getRow(i);
|
|
5582
5892
|
if (n && !n.kind) rows.push(n.data);
|
|
5583
5893
|
}
|
|
@@ -5585,7 +5895,7 @@ var GridCharts = class {
|
|
|
5585
5895
|
}
|
|
5586
5896
|
}
|
|
5587
5897
|
const data = () => {
|
|
5588
|
-
const rows = fixed ? fixed.rows : grid.rowModel.dataRows().slice(0,
|
|
5898
|
+
const rows = fixed ? fixed.rows : grid.rowModel.dataRows().slice(0, MAX_ROWS2).map((n) => n.data);
|
|
5589
5899
|
const cols = (fixed ? fixed.colIds.map((id) => grid.columns.getColumn(id)) : grid.columns.displayed().filter((c) => !isSystemCol(c.id))).filter((c) => !!c);
|
|
5590
5900
|
const cat = cols[0] && cols[0].def.type !== "number" ? cols[0] : fixed ? null : cols.find((c) => c.def.type !== "number") ?? null;
|
|
5591
5901
|
const isTotal = (c) => !!c.node.parent && c.node.parent.id === "pivotgroup|total";
|
|
@@ -5659,58 +5969,6 @@ var GridCharts = class {
|
|
|
5659
5969
|
}
|
|
5660
5970
|
};
|
|
5661
5971
|
|
|
5662
|
-
// src/rows/sort.ts
|
|
5663
|
-
function compareNonBlank(a, b, collator) {
|
|
5664
|
-
if (typeof a === "number" && typeof b === "number") return a - b;
|
|
5665
|
-
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
|
5666
|
-
if (typeof a === "boolean" && typeof b === "boolean") return Number(a) - Number(b);
|
|
5667
|
-
return collator.compare(String(a), String(b));
|
|
5668
|
-
}
|
|
5669
|
-
function normalize(def, v) {
|
|
5670
|
-
if (isBlank(v)) return null;
|
|
5671
|
-
if (def.type === "number") {
|
|
5672
|
-
const n = typeof v === "number" ? v : Number(v);
|
|
5673
|
-
return Number.isNaN(n) ? null : n;
|
|
5674
|
-
}
|
|
5675
|
-
if (def.type === "date") return toDate(v)?.getTime() ?? null;
|
|
5676
|
-
return v;
|
|
5677
|
-
}
|
|
5678
|
-
function compareValues(a, b, collator) {
|
|
5679
|
-
const ba = isBlank(a), bb = isBlank(b);
|
|
5680
|
-
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
5681
|
-
return compareNonBlank(a, b, collator);
|
|
5682
|
-
}
|
|
5683
|
-
function sortNodes(nodes, sort, resolve, collator) {
|
|
5684
|
-
const keys = sort.map((s) => ({ def: resolve(s.colId), mul: s.dir === "desc" ? -1 : 1 })).filter((k) => !!k.def);
|
|
5685
|
-
if (!keys.length) return nodes;
|
|
5686
|
-
const values = keys.map((k) => nodes.map((n) => {
|
|
5687
|
-
const v = getCellValue(k.def, n.data);
|
|
5688
|
-
return k.def.comparator ? v : normalize(k.def, v);
|
|
5689
|
-
}));
|
|
5690
|
-
const order = nodes.map((_, i) => i);
|
|
5691
|
-
order.sort((i, j) => {
|
|
5692
|
-
for (let k = 0; k < keys.length; k++) {
|
|
5693
|
-
const { def, mul } = keys[k];
|
|
5694
|
-
const a = values[k][i], b = values[k][j];
|
|
5695
|
-
let c;
|
|
5696
|
-
if (def.comparator) {
|
|
5697
|
-
try {
|
|
5698
|
-
c = (Number(def.comparator(a, b, nodes[i].data, nodes[j].data)) || 0) * mul;
|
|
5699
|
-
} catch {
|
|
5700
|
-
warn(`comparator of column "${def.id ?? def.field}" threw; treating values as equal`);
|
|
5701
|
-
c = 0;
|
|
5702
|
-
}
|
|
5703
|
-
} else {
|
|
5704
|
-
const ba = isBlank(a), bb = isBlank(b);
|
|
5705
|
-
c = ba || bb ? ba === bb ? 0 : ba ? 1 : -1 : compareNonBlank(a, b, collator) * mul;
|
|
5706
|
-
}
|
|
5707
|
-
if (c) return c;
|
|
5708
|
-
}
|
|
5709
|
-
return i - j;
|
|
5710
|
-
});
|
|
5711
|
-
return order.map((i) => nodes[i]);
|
|
5712
|
-
}
|
|
5713
|
-
|
|
5714
5972
|
// src/pivot.ts
|
|
5715
5973
|
var SOURCE = /* @__PURE__ */ new WeakMap();
|
|
5716
5974
|
var aggName = (a) => typeof a === "function" ? "custom" : a;
|
|
@@ -5836,7 +6094,7 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
5836
6094
|
}
|
|
5837
6095
|
|
|
5838
6096
|
// src/version.ts
|
|
5839
|
-
var version = "1.
|
|
6097
|
+
var version = "1.1.0";
|
|
5840
6098
|
|
|
5841
6099
|
// src/license.ts
|
|
5842
6100
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -5862,6 +6120,12 @@ function domainMatches(host, domains) {
|
|
|
5862
6120
|
return wildcard && h.endsWith(`.${dom}`);
|
|
5863
6121
|
});
|
|
5864
6122
|
}
|
|
6123
|
+
var isDay = (s) => {
|
|
6124
|
+
const m = typeof s === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(s) : null;
|
|
6125
|
+
if (!m) return false;
|
|
6126
|
+
const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));
|
|
6127
|
+
return d.getUTCMonth() === Number(m[2]) - 1 && d.getUTCDate() === Number(m[3]);
|
|
6128
|
+
};
|
|
5865
6129
|
var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
5866
6130
|
async function verifyLicense(key, host, opts = {}) {
|
|
5867
6131
|
const parts = typeof key === "string" ? key.trim().split(".") : [];
|
|
@@ -5869,7 +6133,7 @@ async function verifyLicense(key, host, opts = {}) {
|
|
|
5869
6133
|
let info;
|
|
5870
6134
|
try {
|
|
5871
6135
|
info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
|
|
5872
|
-
if (!Array.isArray(info.domains) ||
|
|
6136
|
+
if (!Array.isArray(info.domains) || !isDay(info.expires)) return { ok: false, reason: "format" };
|
|
5873
6137
|
} catch {
|
|
5874
6138
|
return { ok: false, reason: "format" };
|
|
5875
6139
|
}
|
|
@@ -6479,17 +6743,25 @@ var RowModel = class {
|
|
|
6479
6743
|
|
|
6480
6744
|
// src/rows/serverGroups.ts
|
|
6481
6745
|
var LEVEL_ROWS = 1e4;
|
|
6746
|
+
var MAX_LEVEL_ROWS = 1e5;
|
|
6747
|
+
var MAX_IN_FLIGHT = 4;
|
|
6748
|
+
var RETRY_MS = 3e3;
|
|
6482
6749
|
var ServerGroups = class {
|
|
6483
|
-
constructor(datasource, getRowId, groupBy, query, onChange) {
|
|
6750
|
+
constructor(datasource, getRowId, groupBy, query, onChange, onFailed) {
|
|
6484
6751
|
this.datasource = datasource;
|
|
6485
6752
|
this.getRowId = getRowId;
|
|
6486
6753
|
this.groupBy = groupBy;
|
|
6487
6754
|
this.query = query;
|
|
6488
6755
|
this.onChange = onChange;
|
|
6756
|
+
this.onFailed = onFailed;
|
|
6489
6757
|
this.levels = /* @__PURE__ */ new Map();
|
|
6490
6758
|
this.gen = 0;
|
|
6491
6759
|
this.display = null;
|
|
6492
6760
|
this.ids = null;
|
|
6761
|
+
this.queue = [];
|
|
6762
|
+
this.inFlight = 0;
|
|
6763
|
+
/** Earliest time a failed level in the display may be asked again (0: none). */
|
|
6764
|
+
this.retryAt = 0;
|
|
6493
6765
|
this.version = 0;
|
|
6494
6766
|
this.builtFor = -1;
|
|
6495
6767
|
}
|
|
@@ -6498,9 +6770,15 @@ var ServerGroups = class {
|
|
|
6498
6770
|
}
|
|
6499
6771
|
purge() {
|
|
6500
6772
|
this.gen++;
|
|
6773
|
+
this.queue = [];
|
|
6501
6774
|
this.levels.clear();
|
|
6502
6775
|
this.touch();
|
|
6503
6776
|
}
|
|
6777
|
+
/** Replaced by another grouping: answers still on their way are dropped, no more change calls. */
|
|
6778
|
+
dispose() {
|
|
6779
|
+
this.gen++;
|
|
6780
|
+
this.queue = [];
|
|
6781
|
+
}
|
|
6504
6782
|
touch() {
|
|
6505
6783
|
this.version++;
|
|
6506
6784
|
this.display = null;
|
|
@@ -6508,15 +6786,33 @@ var ServerGroups = class {
|
|
|
6508
6786
|
}
|
|
6509
6787
|
load(path, keys, level) {
|
|
6510
6788
|
if (this.levels.has(path)) return;
|
|
6511
|
-
const gen = this.gen;
|
|
6512
6789
|
this.levels.set(path, { state: "loading", nodes: [] });
|
|
6790
|
+
this.fetch({ path, keys, level, start: 0, gen: this.gen });
|
|
6791
|
+
}
|
|
6792
|
+
fetch(job) {
|
|
6793
|
+
this.queue.push(job);
|
|
6794
|
+
this.pump();
|
|
6795
|
+
}
|
|
6796
|
+
pump() {
|
|
6797
|
+
while (this.inFlight < MAX_IN_FLIGHT && this.queue.length) {
|
|
6798
|
+
const job = this.queue.shift();
|
|
6799
|
+
if (job.gen !== this.gen) continue;
|
|
6800
|
+
this.inFlight++;
|
|
6801
|
+
void this.request(job).finally(() => {
|
|
6802
|
+
this.inFlight--;
|
|
6803
|
+
this.pump();
|
|
6804
|
+
});
|
|
6805
|
+
}
|
|
6806
|
+
}
|
|
6807
|
+
request({ path, keys, level, start, gen }) {
|
|
6513
6808
|
const last = level >= this.groupBy.length;
|
|
6514
6809
|
const q = this.query;
|
|
6810
|
+
const end = start + LEVEL_ROWS;
|
|
6515
6811
|
let result;
|
|
6516
6812
|
try {
|
|
6517
6813
|
result = Promise.resolve(this.datasource.getRows({
|
|
6518
|
-
start
|
|
6519
|
-
end
|
|
6814
|
+
start,
|
|
6815
|
+
end,
|
|
6520
6816
|
sort: [...q.sort],
|
|
6521
6817
|
filters: { ...q.filters },
|
|
6522
6818
|
quickFilter: q.quickFilter,
|
|
@@ -6527,11 +6823,11 @@ var ServerGroups = class {
|
|
|
6527
6823
|
} catch (e) {
|
|
6528
6824
|
result = Promise.reject(e);
|
|
6529
6825
|
}
|
|
6530
|
-
result.then((res) => {
|
|
6826
|
+
return result.then((res) => {
|
|
6531
6827
|
if (gen !== this.gen) return;
|
|
6532
6828
|
const rows = Array.isArray(res?.rows) ? res.rows.filter((r) => r !== null && typeof r === "object") : [];
|
|
6533
6829
|
if (!Array.isArray(res?.rows)) warn("datasource.getRows must resolve to { rows: Row[] }");
|
|
6534
|
-
const
|
|
6830
|
+
const added = last ? rows.map((data, i) => ({ id: this.getRowId ? String(this.getRowId(data)) : autoId(data), data, sourceIndex: start + i, level })) : rows.map((data) => {
|
|
6535
6831
|
const colId = this.groupBy[level];
|
|
6536
6832
|
const value = data[colId];
|
|
6537
6833
|
const key = `${path ? `${path}|` : ""}${colId}:${setKey(value)}`;
|
|
@@ -6545,14 +6841,23 @@ var ServerGroups = class {
|
|
|
6545
6841
|
group: { key, colId, value, rows: [], expanded: false, count, aggregates: data, keys: [...keys, value] }
|
|
6546
6842
|
};
|
|
6547
6843
|
});
|
|
6548
|
-
this.levels.
|
|
6844
|
+
const nodes = start ? [...this.levels.get(path)?.nodes ?? [], ...added] : added;
|
|
6845
|
+
const total = typeof res?.total === "number" && res.total >= 0 ? res.total : null;
|
|
6846
|
+
const more = rows.length > 0 && (total !== null ? nodes.length < total : rows.length >= end - start);
|
|
6847
|
+
if (more && nodes.length < MAX_LEVEL_ROWS) {
|
|
6848
|
+
this.levels.set(path, { state: "loading", nodes });
|
|
6849
|
+
this.fetch({ path, keys, level, start: nodes.length, gen });
|
|
6850
|
+
} else {
|
|
6851
|
+
if (more) warn(`a server group level has more than ${MAX_LEVEL_ROWS} rows; only the first ${MAX_LEVEL_ROWS} are shown`);
|
|
6852
|
+
this.levels.set(path, { state: "loaded", nodes });
|
|
6853
|
+
}
|
|
6549
6854
|
this.touch();
|
|
6550
6855
|
this.onChange();
|
|
6551
|
-
}, () => {
|
|
6856
|
+
}, (error) => {
|
|
6552
6857
|
if (gen !== this.gen) return;
|
|
6553
|
-
this.levels.set(path, { state: "failed", nodes: [] });
|
|
6858
|
+
this.levels.set(path, { state: "failed", nodes: [], retryAt: Date.now() + RETRY_MS });
|
|
6554
6859
|
this.touch();
|
|
6555
|
-
this.
|
|
6860
|
+
this.onFailed(start, end, error);
|
|
6556
6861
|
});
|
|
6557
6862
|
}
|
|
6558
6863
|
isOpen(d, key, level) {
|
|
@@ -6562,17 +6867,24 @@ var ServerGroups = class {
|
|
|
6562
6867
|
}
|
|
6563
6868
|
/** Display rows: open groups show their loaded children, or a loading row while they arrive. */
|
|
6564
6869
|
rows(d, displayVersion) {
|
|
6565
|
-
|
|
6870
|
+
const now = Date.now();
|
|
6871
|
+
if (this.display && this.builtFor === displayVersion && !(this.retryAt && now >= this.retryAt)) return this.display;
|
|
6566
6872
|
const out = [];
|
|
6873
|
+
let retryAt = 0;
|
|
6567
6874
|
const walk = (path, keys, level) => {
|
|
6568
|
-
|
|
6875
|
+
let lv = this.levels.get(path);
|
|
6876
|
+
if (lv?.state === "failed" && now >= (lv.retryAt ?? 0)) {
|
|
6877
|
+
this.levels.delete(path);
|
|
6878
|
+
lv = void 0;
|
|
6879
|
+
}
|
|
6569
6880
|
if (!lv) {
|
|
6570
6881
|
this.load(path, keys, level);
|
|
6571
6882
|
out.push({ id: `loading:${path}`, data: {}, sourceIndex: -1, kind: "loading", level });
|
|
6572
6883
|
return;
|
|
6573
6884
|
}
|
|
6574
|
-
if (lv.state
|
|
6575
|
-
|
|
6885
|
+
if (lv.state === "failed") {
|
|
6886
|
+
const at = lv.retryAt ?? 0;
|
|
6887
|
+
retryAt = retryAt ? Math.min(retryAt, at) : at;
|
|
6576
6888
|
return;
|
|
6577
6889
|
}
|
|
6578
6890
|
for (const n of lv.nodes) {
|
|
@@ -6586,8 +6898,10 @@ var ServerGroups = class {
|
|
|
6586
6898
|
out.push(node);
|
|
6587
6899
|
if (open) walk(g.key, g.keys ?? [], level + 1);
|
|
6588
6900
|
}
|
|
6901
|
+
if (lv.state === "loading") out.push({ id: `loading:${path}`, data: {}, sourceIndex: -1, kind: "loading", level });
|
|
6589
6902
|
};
|
|
6590
6903
|
walk("", [], 0);
|
|
6904
|
+
this.retryAt = retryAt;
|
|
6591
6905
|
this.display = out;
|
|
6592
6906
|
this.builtFor = displayVersion;
|
|
6593
6907
|
this.ids = null;
|
|
@@ -6621,7 +6935,6 @@ var ServerGroups = class {
|
|
|
6621
6935
|
};
|
|
6622
6936
|
|
|
6623
6937
|
// src/rows/serverRowModel.ts
|
|
6624
|
-
var RETRY_MS = 3e3;
|
|
6625
6938
|
var NO_OFFSETS = new Float64Array(0);
|
|
6626
6939
|
var ServerRowModel = class {
|
|
6627
6940
|
constructor(deps) {
|
|
@@ -6655,7 +6968,15 @@ var ServerRowModel = class {
|
|
|
6655
6968
|
if (groupBy.length && !input.display?.getParentId) {
|
|
6656
6969
|
const q2 = { sort: input.sort, filters: input.filters, quickFilter: input.quickFilter, advancedFilter: input.advancedFilter ?? null };
|
|
6657
6970
|
if (!this.groups || !this.groups.matches(groupBy, q2)) {
|
|
6658
|
-
this.groups
|
|
6971
|
+
this.groups?.dispose();
|
|
6972
|
+
this.groups = new ServerGroups(
|
|
6973
|
+
this.deps.datasource,
|
|
6974
|
+
this.deps.getRowId,
|
|
6975
|
+
[...groupBy],
|
|
6976
|
+
q2,
|
|
6977
|
+
() => this.deps.onLoaded(0, 0, null),
|
|
6978
|
+
(start, end, error) => this.deps.onFailed(start, end, error)
|
|
6979
|
+
);
|
|
6659
6980
|
this.touch();
|
|
6660
6981
|
}
|
|
6661
6982
|
this.displayIn = input.display ?? null;
|
|
@@ -6663,6 +6984,7 @@ var ServerRowModel = class {
|
|
|
6663
6984
|
return;
|
|
6664
6985
|
}
|
|
6665
6986
|
if (this.groups) {
|
|
6987
|
+
this.groups.dispose();
|
|
6666
6988
|
this.groups = null;
|
|
6667
6989
|
this.query = null;
|
|
6668
6990
|
this.touch();
|
|
@@ -7066,22 +7388,30 @@ var FilterBuilder = class {
|
|
|
7066
7388
|
del.addEventListener("click", () => remove(state));
|
|
7067
7389
|
return state;
|
|
7068
7390
|
};
|
|
7069
|
-
const
|
|
7391
|
+
const keptRow = (kept, label) => {
|
|
7070
7392
|
const rowEl = el("div", "fg-fb-row fg-fb-group");
|
|
7071
7393
|
const text2 = el("span", "fg-fb-group-label");
|
|
7072
|
-
text2.textContent =
|
|
7394
|
+
text2.textContent = label;
|
|
7073
7395
|
const del = el("button", "fg-chooser-close fg-fb-remove", { type: "button", "aria-label": loc.close });
|
|
7074
7396
|
del.textContent = "\xD7";
|
|
7075
7397
|
rowEl.append(text2, del);
|
|
7076
|
-
const state = { el: rowEl, read: () =>
|
|
7398
|
+
const state = { el: rowEl, read: () => kept };
|
|
7077
7399
|
del.addEventListener("click", () => remove(state));
|
|
7078
7400
|
return state;
|
|
7079
7401
|
};
|
|
7402
|
+
const rowFor = (c) => {
|
|
7403
|
+
if ("conditions" in c) return keptRow(c, `( ${c.conditions.length} \xD7 ${loc[c.operator]} )`);
|
|
7404
|
+
const col = cols.find((x2) => x2.id === c.colId);
|
|
7405
|
+
const m = c.model;
|
|
7406
|
+
if (col && m && (m.type === "text" || m.type === "number" || m.type === "date")) return conditionRow(c);
|
|
7407
|
+
const what = m?.type === "set" ? m.values.map(String).join(", ") : m?.type === "combined" ? `( ${m.conditions.length} \xD7 ${loc[m.operator]} )` : "";
|
|
7408
|
+
return keptRow(c, `${col?.label ?? c.colId}: ${what}`);
|
|
7409
|
+
};
|
|
7080
7410
|
const add = (row) => {
|
|
7081
7411
|
rows.push(row);
|
|
7082
7412
|
list.append(row.el);
|
|
7083
7413
|
};
|
|
7084
|
-
for (const c of cur?.conditions ?? []) add(
|
|
7414
|
+
for (const c of cur?.conditions ?? []) add(rowFor(c));
|
|
7085
7415
|
const addBtn = el("button", "fg-btn fg-fb-add", { type: "button" });
|
|
7086
7416
|
addBtn.textContent = `+ ${loc.addCondition}`;
|
|
7087
7417
|
addBtn.addEventListener("click", () => {
|
|
@@ -7279,7 +7609,7 @@ var Grid = class {
|
|
|
7279
7609
|
() => this.menu.close(),
|
|
7280
7610
|
() => this.chooser.close(),
|
|
7281
7611
|
() => this.contextMenu.destroy(),
|
|
7282
|
-
() => this.editor.
|
|
7612
|
+
() => this.editor.destroy(),
|
|
7283
7613
|
() => this.fillHandle.destroy(),
|
|
7284
7614
|
() => this.groupPanel?.destroy(),
|
|
7285
7615
|
() => this.charts.destroy(),
|
|
@@ -7449,35 +7779,68 @@ var Grid = class {
|
|
|
7449
7779
|
this.flashTimer = window.setTimeout(tick, 1e3);
|
|
7450
7780
|
}
|
|
7451
7781
|
applyTransaction(tx) {
|
|
7782
|
+
if (!tx || typeof tx !== "object") return { added: [], updated: [], removed: [], notFound: [] };
|
|
7783
|
+
return this.applyTransactions([tx]);
|
|
7784
|
+
}
|
|
7785
|
+
/** Apply transactions one after another, then render and emit once. */
|
|
7786
|
+
applyTransactions(list) {
|
|
7452
7787
|
const result = { added: [], updated: [], removed: [], notFound: [] };
|
|
7453
|
-
if (this.clientOnly("applyTransaction")
|
|
7454
|
-
const
|
|
7788
|
+
if (this.clientOnly("applyTransaction")) return result;
|
|
7789
|
+
const colsByField = /* @__PURE__ */ new Map();
|
|
7790
|
+
for (const st of this.columns.getState()) {
|
|
7791
|
+
const f = this.columns.getNode(st.colId)?.def.field;
|
|
7792
|
+
if (f) colsByField.set(f, [...colsByField.get(f) ?? [], st.colId]);
|
|
7793
|
+
}
|
|
7794
|
+
const flashKeys = [];
|
|
7795
|
+
let next = this.store.state.data;
|
|
7796
|
+
const present = new Set(next);
|
|
7797
|
+
const byId = new Map(next.map((r) => [this.rowModel.idOf(r), r]));
|
|
7798
|
+
for (const tx of list) {
|
|
7799
|
+
if (tx && typeof tx === "object") next = this.txStep(tx, next, { present, byId }, result, flashKeys, colsByField);
|
|
7800
|
+
}
|
|
7801
|
+
if (!result.added.length && !result.updated.length && !result.removed.length) {
|
|
7802
|
+
if (result.notFound.length) this.events.emit("transactionApplied", result);
|
|
7803
|
+
return result;
|
|
7804
|
+
}
|
|
7805
|
+
this.editVersion++;
|
|
7806
|
+
this.renderer.invalidateHeights();
|
|
7807
|
+
this.keepRange = true;
|
|
7808
|
+
try {
|
|
7809
|
+
this.replaceData(next === this.store.state.data ? [...next] : next);
|
|
7810
|
+
} finally {
|
|
7811
|
+
this.keepRange = false;
|
|
7812
|
+
}
|
|
7813
|
+
this.editor.sync();
|
|
7814
|
+
if (flashKeys.length && this.options.flashCells !== false) this.flash(flashKeys);
|
|
7815
|
+
this.events.emit("transactionApplied", result);
|
|
7816
|
+
return result;
|
|
7817
|
+
}
|
|
7818
|
+
/** One transaction against `data` (remove → update → add); returns the new array. */
|
|
7819
|
+
txStep(tx, data, index, result, flashKeys, colsByField) {
|
|
7455
7820
|
const getId = this.options.getRowId;
|
|
7821
|
+
const { present, byId } = index;
|
|
7456
7822
|
const keyOf = (r) => {
|
|
7457
7823
|
if (!r || typeof r !== "object") return null;
|
|
7458
|
-
if (
|
|
7824
|
+
if (present.has(r)) return this.rowModel.idOf(r);
|
|
7459
7825
|
try {
|
|
7460
7826
|
return getId ? String(getId(r)) : null;
|
|
7461
7827
|
} catch {
|
|
7462
7828
|
return null;
|
|
7463
7829
|
}
|
|
7464
7830
|
};
|
|
7465
|
-
const byId = new Map(data.map((r) => [this.rowModel.idOf(r), r]));
|
|
7466
7831
|
const gone = /* @__PURE__ */ new Set();
|
|
7467
7832
|
for (const r of tx.remove ?? []) {
|
|
7468
7833
|
const k = keyOf(r);
|
|
7469
7834
|
const row = k === null ? void 0 : byId.get(k);
|
|
7470
7835
|
if (row && !gone.has(row)) {
|
|
7471
7836
|
gone.add(row);
|
|
7837
|
+
present.delete(row);
|
|
7838
|
+
byId.delete(k);
|
|
7472
7839
|
result.removed.push(row);
|
|
7840
|
+
this.tracker.created.delete(row);
|
|
7841
|
+
this.tracker.originals.delete(row);
|
|
7473
7842
|
} else if (!row) result.notFound.push(r);
|
|
7474
7843
|
}
|
|
7475
|
-
const colsByField = /* @__PURE__ */ new Map();
|
|
7476
|
-
for (const st of this.columns.getState()) {
|
|
7477
|
-
const f = this.columns.getNode(st.colId)?.def.field;
|
|
7478
|
-
if (f) colsByField.set(f, [...colsByField.get(f) ?? [], st.colId]);
|
|
7479
|
-
}
|
|
7480
|
-
const flashKeys = [];
|
|
7481
7844
|
for (const r of tx.update ?? []) {
|
|
7482
7845
|
const k = keyOf(r);
|
|
7483
7846
|
const row = k === null ? void 0 : byId.get(k);
|
|
@@ -7485,7 +7848,9 @@ var Grid = class {
|
|
|
7485
7848
|
result.notFound.push(r);
|
|
7486
7849
|
continue;
|
|
7487
7850
|
}
|
|
7851
|
+
const orig = this.tracker.originals.get(row);
|
|
7488
7852
|
for (const [field, value] of Object.entries(r)) {
|
|
7853
|
+
if (orig) orig[field] = value;
|
|
7489
7854
|
if (Object.is(row[field], value)) continue;
|
|
7490
7855
|
row[field] = value;
|
|
7491
7856
|
for (const colId of colsByField.get(field) ?? []) flashKeys.push(`${k}\0${colId}`);
|
|
@@ -7494,24 +7859,15 @@ var Grid = class {
|
|
|
7494
7859
|
}
|
|
7495
7860
|
const added = cleanRows(Array.isArray(tx.add) ? tx.add : []);
|
|
7496
7861
|
result.added.push(...added);
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7862
|
+
for (const r of added) {
|
|
7863
|
+
present.add(r);
|
|
7864
|
+
byId.set(this.rowModel.idOf(r), r);
|
|
7500
7865
|
}
|
|
7866
|
+
if (!gone.size && !added.length) return data;
|
|
7501
7867
|
const next = gone.size ? data.filter((r) => !gone.has(r)) : [...data];
|
|
7502
7868
|
const at = typeof tx.addIndex === "number" ? Math.max(0, Math.min(next.length, Math.floor(tx.addIndex))) : next.length;
|
|
7503
7869
|
next.splice(at, 0, ...added);
|
|
7504
|
-
|
|
7505
|
-
this.renderer.invalidateHeights();
|
|
7506
|
-
this.keepRange = true;
|
|
7507
|
-
try {
|
|
7508
|
-
this.replaceData(next);
|
|
7509
|
-
} finally {
|
|
7510
|
-
this.keepRange = false;
|
|
7511
|
-
}
|
|
7512
|
-
if (flashKeys.length && this.options.flashCells !== false) this.flash(flashKeys);
|
|
7513
|
-
this.events.emit("transactionApplied", result);
|
|
7514
|
-
return result;
|
|
7870
|
+
return next;
|
|
7515
7871
|
}
|
|
7516
7872
|
applyTransactionAsync(tx) {
|
|
7517
7873
|
if (!tx || typeof tx !== "object") return;
|
|
@@ -7522,12 +7878,7 @@ var Grid = class {
|
|
|
7522
7878
|
const queue = this.txQueue;
|
|
7523
7879
|
this.txQueue = [];
|
|
7524
7880
|
if (this.destroyed || !queue.length) return;
|
|
7525
|
-
this.
|
|
7526
|
-
add: queue.flatMap((t) => t.add ?? []),
|
|
7527
|
-
addIndex: queue.find((t) => typeof t.addIndex === "number")?.addIndex,
|
|
7528
|
-
update: queue.flatMap((t) => t.update ?? []),
|
|
7529
|
-
remove: queue.flatMap((t) => t.remove ?? [])
|
|
7530
|
-
});
|
|
7881
|
+
this.applyTransactions(queue);
|
|
7531
7882
|
}, 50);
|
|
7532
7883
|
}
|
|
7533
7884
|
setAdvancedFilter(filter) {
|
|
@@ -7543,6 +7894,8 @@ var Grid = class {
|
|
|
7543
7894
|
}
|
|
7544
7895
|
getState() {
|
|
7545
7896
|
const s = this.store.state;
|
|
7897
|
+
const groupBy = this.savedGroup?.groupBy ?? this.groupBy;
|
|
7898
|
+
const expanded = this.savedGroup?.expanded ?? this.expanded;
|
|
7546
7899
|
const state = {
|
|
7547
7900
|
columns: this.columns.getState(),
|
|
7548
7901
|
columnGroups: this.columns.groupOpenState(),
|
|
@@ -7550,9 +7903,9 @@ var Grid = class {
|
|
|
7550
7903
|
sort: s.sort.map((x) => ({ ...x })),
|
|
7551
7904
|
filters: JSON.parse(JSON.stringify(s.filters)),
|
|
7552
7905
|
quickFilter: s.quickFilter,
|
|
7553
|
-
groupBy: [...
|
|
7906
|
+
groupBy: [...groupBy],
|
|
7554
7907
|
pivot: this.pivotCfg ? JSON.parse(JSON.stringify(this.pivotCfg)) : null,
|
|
7555
|
-
expanded: Object.fromEntries(
|
|
7908
|
+
expanded: Object.fromEntries(expanded),
|
|
7556
7909
|
selected: [...s.selected]
|
|
7557
7910
|
};
|
|
7558
7911
|
if (this.options.pagination) {
|
|
@@ -7583,8 +7936,12 @@ var Grid = class {
|
|
|
7583
7936
|
if (want("advancedFilter")) this.setAdvancedFilter(state.advancedFilter ?? null);
|
|
7584
7937
|
if (want("quickFilter") && typeof state.quickFilter === "string") this.store.set({ quickFilter: state.quickFilter });
|
|
7585
7938
|
if (want("expanded") && state.expanded && typeof state.expanded === "object") {
|
|
7586
|
-
|
|
7587
|
-
this.
|
|
7939
|
+
const expanded = new Map(Object.entries(state.expanded).filter(([, v]) => typeof v === "boolean"));
|
|
7940
|
+
if (this.savedGroup) this.savedGroup.expanded = expanded;
|
|
7941
|
+
else {
|
|
7942
|
+
this.expanded = expanded;
|
|
7943
|
+
this.displayChanged();
|
|
7944
|
+
}
|
|
7588
7945
|
}
|
|
7589
7946
|
if (want("pageSize") && typeof state.pageSize === "number") this.setPageSize(state.pageSize);
|
|
7590
7947
|
if (want("page") && typeof state.page === "number") this.setPage(state.page);
|
|
@@ -8108,7 +8465,7 @@ var Grid = class {
|
|
|
8108
8465
|
if (all2.length > 1) {
|
|
8109
8466
|
const sameCols = all2.every((x) => x.c1 === all2[0].c1 && x.c2 === all2[0].c2);
|
|
8110
8467
|
const sameRows = all2.every((x) => x.r1 === all2[0].r1 && x.r2 === all2[0].r2);
|
|
8111
|
-
if (
|
|
8468
|
+
if (this.rangesStack(all2)) {
|
|
8112
8469
|
const parts = [...all2].sort((x, y) => sameCols ? x.r1 - y.r1 : x.c1 - y.c1);
|
|
8113
8470
|
const blocks = parts.map((p) => this.rangeMatrix(p));
|
|
8114
8471
|
const matrix2 = sameCols ? blocks.flat() : blocks[0].map((_, ri) => blocks.flatMap((bl) => bl[ri] ?? []));
|
|
@@ -8139,6 +8496,10 @@ var Grid = class {
|
|
|
8139
8496
|
if (headers) matrix.unshift(cols.map((c) => c.def.header ?? c.def.field ?? c.id));
|
|
8140
8497
|
return toTSV(matrix);
|
|
8141
8498
|
}
|
|
8499
|
+
/** Like Excel: ranges stack when they share columns or rows; otherwise only the active one is copied. */
|
|
8500
|
+
rangesStack(all2) {
|
|
8501
|
+
return all2.length > 1 && (all2.every((x) => x.c1 === all2[0].c1 && x.c2 === all2[0].c2) || all2.every((x) => x.r1 === all2[0].r1 && x.r2 === all2[0].r2));
|
|
8502
|
+
}
|
|
8142
8503
|
rangeHeaders(b) {
|
|
8143
8504
|
return this.columns.displayed().slice(b.c1, b.c2 + 1).filter((c) => !isSystemCol(c.id)).map((c) => c.def.header ?? c.def.field ?? c.id);
|
|
8144
8505
|
}
|
|
@@ -8154,24 +8515,21 @@ var Grid = class {
|
|
|
8154
8515
|
return out;
|
|
8155
8516
|
}
|
|
8156
8517
|
copyCell(def, row, code) {
|
|
8157
|
-
|
|
8518
|
+
return this.copyAllowed(def, row) ? copyValue(def, getCellValue(def, row), code) : "";
|
|
8519
|
+
}
|
|
8520
|
+
copyAllowed(def, row) {
|
|
8158
8521
|
const rule = def.copyable;
|
|
8159
|
-
if (rule
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
} catch {
|
|
8165
|
-
ok = false;
|
|
8166
|
-
}
|
|
8167
|
-
if (!ok) return "";
|
|
8522
|
+
if (typeof rule !== "function") return rule !== false;
|
|
8523
|
+
try {
|
|
8524
|
+
return rule({ value: getCellValue(def, row), row, column: def }) !== false;
|
|
8525
|
+
} catch {
|
|
8526
|
+
return false;
|
|
8168
8527
|
}
|
|
8169
|
-
return copyValue(def, value, code);
|
|
8170
8528
|
}
|
|
8171
8529
|
/** Copy, then clear the editable cells of the selection. */
|
|
8172
8530
|
cutToClipboard() {
|
|
8173
8531
|
const text2 = this.copyToClipboard(false);
|
|
8174
|
-
if (text2) this.clearCells();
|
|
8532
|
+
if (text2) this.clearCells(true);
|
|
8175
8533
|
return text2;
|
|
8176
8534
|
}
|
|
8177
8535
|
copyToClipboard(headers, rows = false) {
|
|
@@ -8430,10 +8788,14 @@ var Grid = class {
|
|
|
8430
8788
|
this.writeCell(changes, node.data, def.field, colId, rowIndex, value);
|
|
8431
8789
|
this.endCellTx(changes);
|
|
8432
8790
|
}
|
|
8433
|
-
/** Delete/Backspace: clear editable cells in the range, or the focused cell. */
|
|
8434
|
-
clearCells() {
|
|
8791
|
+
/** Delete/Backspace: clear editable cells in the range, or the focused cell. `cut` clears only what a copy takes. */
|
|
8792
|
+
clearCells(cut = false) {
|
|
8435
8793
|
const displayed = this.columns.displayed();
|
|
8436
|
-
|
|
8794
|
+
let all2 = this.allRangeBounds();
|
|
8795
|
+
if (cut && !this.rangesStack(all2)) {
|
|
8796
|
+
const active = this.rangeBounds();
|
|
8797
|
+
all2 = active ? [active] : [];
|
|
8798
|
+
}
|
|
8437
8799
|
const f = this.store.state.focus;
|
|
8438
8800
|
const fi = f ? this.columns.getColumn(f.colId)?.index : void 0;
|
|
8439
8801
|
const boxes = all2.length ? all2 : f && fi !== void 0 ? [{ r1: f.rowIndex, r2: f.rowIndex, c1: fi, c2: fi }] : [];
|
|
@@ -8443,7 +8805,7 @@ var Grid = class {
|
|
|
8443
8805
|
const node = this.rowModel.getRow(r);
|
|
8444
8806
|
for (let c = box.c1; c <= box.c2; c++) {
|
|
8445
8807
|
const def = displayed[c]?.def;
|
|
8446
|
-
if (node && !node.kind && def?.field && cellEditable(def, node.data, r)) this.writeCell(changes, node.data, def.field, displayed[c].id, r, null);
|
|
8808
|
+
if (node && !node.kind && def?.field && cellEditable(def, node.data, r) && (!cut || this.copyAllowed(def, node.data))) this.writeCell(changes, node.data, def.field, displayed[c].id, r, null);
|
|
8447
8809
|
}
|
|
8448
8810
|
}
|
|
8449
8811
|
this.endCellTx(changes);
|
|
@@ -8545,8 +8907,9 @@ var Grid = class {
|
|
|
8545
8907
|
const m = this.getModifiedRows();
|
|
8546
8908
|
return m.created.length + m.updated.length + m.deleted.length > 0;
|
|
8547
8909
|
}
|
|
8548
|
-
commitChanges() {
|
|
8549
|
-
this.tracker.
|
|
8910
|
+
commitChanges(saved) {
|
|
8911
|
+
if (saved && saved.rows instanceof Map) this.tracker.commit(saved.rows, Array.isArray(saved.deleted) ? saved.deleted : []);
|
|
8912
|
+
else this.tracker.reset();
|
|
8550
8913
|
this.renderer.invalidate({ rows: true });
|
|
8551
8914
|
}
|
|
8552
8915
|
revertChanges() {
|
|
@@ -8950,7 +9313,7 @@ var Grid = class {
|
|
|
8950
9313
|
getModifiedRows: guard(() => this.getModifiedRows()),
|
|
8951
9314
|
getChanges: guard(() => this.getChanges()),
|
|
8952
9315
|
hasChanges: guard(() => this.hasChanges()),
|
|
8953
|
-
commitChanges: guard(() => this.commitChanges()),
|
|
9316
|
+
commitChanges: guard((saved) => this.commitChanges(saved)),
|
|
8954
9317
|
revertChanges: guard(() => this.revertChanges()),
|
|
8955
9318
|
undo: guard(() => this.undo()),
|
|
8956
9319
|
redo: guard(() => this.redo()),
|
|
@@ -9021,26 +9384,32 @@ function exportWorkbook(filename, sheets) {
|
|
|
9021
9384
|
var headersOf = (h) => typeof h === "function" ? h() : h ?? {};
|
|
9022
9385
|
async function send(url, method, body, opts) {
|
|
9023
9386
|
let target = url;
|
|
9387
|
+
const where = url.split(/[?#]/)[0];
|
|
9024
9388
|
const init = { method, credentials: opts.credentials ?? "same-origin", headers: { Accept: "application/json", ...headersOf(opts.headers) } };
|
|
9025
9389
|
if (method === "GET") {
|
|
9026
9390
|
const q = new URLSearchParams();
|
|
9027
|
-
for (const [k, v] of Object.entries(body)) q.set(k, typeof v === "string" ? v : JSON.stringify(v));
|
|
9391
|
+
for (const [k, v] of Object.entries(body)) if (v !== void 0) q.set(k, typeof v === "string" ? v : JSON.stringify(v));
|
|
9028
9392
|
target += (url.includes("?") ? "&" : "?") + q.toString();
|
|
9029
9393
|
} else {
|
|
9030
9394
|
init.headers["Content-Type"] = "application/json";
|
|
9031
9395
|
init.body = JSON.stringify(body);
|
|
9032
9396
|
}
|
|
9033
9397
|
const res = await fetch(target, init);
|
|
9034
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} from ${
|
|
9398
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${where}`);
|
|
9035
9399
|
const text2 = await res.text();
|
|
9036
|
-
|
|
9400
|
+
if (!text2) return null;
|
|
9401
|
+
try {
|
|
9402
|
+
return JSON.parse(text2);
|
|
9403
|
+
} catch {
|
|
9404
|
+
throw new Error(`HTTP ${res.status} from ${where}: the response is not JSON`);
|
|
9405
|
+
}
|
|
9037
9406
|
}
|
|
9038
9407
|
function readRows(json) {
|
|
9039
9408
|
if (Array.isArray(json)) return { rows: json };
|
|
9040
9409
|
const o = json ?? {};
|
|
9041
9410
|
const rows = [o.rows, o.list, o.content, o.data, o.items, o.resultList].find(Array.isArray);
|
|
9042
9411
|
const total = [o.total, o.totalCount, o.totalElements, o.totCnt, o.count].find((v) => typeof v === "number");
|
|
9043
|
-
return { rows: rows ?? [], total };
|
|
9412
|
+
return { rows: (rows ?? []).filter((r) => r !== null && typeof r === "object"), total };
|
|
9044
9413
|
}
|
|
9045
9414
|
function restDatasource(url, options = {}) {
|
|
9046
9415
|
return {
|
|
@@ -9054,6 +9423,10 @@ function restDatasource(url, options = {}) {
|
|
|
9054
9423
|
sort: p.sort,
|
|
9055
9424
|
filters: p.filters,
|
|
9056
9425
|
quickFilter: p.quickFilter,
|
|
9426
|
+
// only when set (undefined is left out of the request)
|
|
9427
|
+
advancedFilter: p.advancedFilter ?? void 0,
|
|
9428
|
+
groupBy: p.groupBy,
|
|
9429
|
+
groupKeys: p.groupKeys,
|
|
9057
9430
|
...options.params?.() ?? {}
|
|
9058
9431
|
};
|
|
9059
9432
|
const json = await send(url, options.method ?? "POST", body, options);
|
|
@@ -9061,12 +9434,20 @@ function restDatasource(url, options = {}) {
|
|
|
9061
9434
|
}
|
|
9062
9435
|
};
|
|
9063
9436
|
}
|
|
9064
|
-
|
|
9065
|
-
|
|
9437
|
+
var saving = /* @__PURE__ */ new WeakMap();
|
|
9438
|
+
function saveChanges(grid, url, options = {}) {
|
|
9439
|
+
const pending = saving.get(grid);
|
|
9440
|
+
if (pending) return pending;
|
|
9441
|
+
if (!grid.hasChanges()) return Promise.resolve(null);
|
|
9066
9442
|
const { created, updated, deleted } = grid.getModifiedRows();
|
|
9067
|
-
const
|
|
9068
|
-
|
|
9069
|
-
|
|
9443
|
+
const rows = new Map([...created, ...updated].map((r) => [r, { ...r }]));
|
|
9444
|
+
const run = (async () => {
|
|
9445
|
+
const json = await send(url, "POST", { created, updated, deleted, ...options.params?.() ?? {} }, options);
|
|
9446
|
+
grid.commitChanges({ rows, deleted });
|
|
9447
|
+
return json;
|
|
9448
|
+
})().finally(() => saving.delete(grid));
|
|
9449
|
+
saving.set(grid, run);
|
|
9450
|
+
return run;
|
|
9070
9451
|
}
|
|
9071
9452
|
async function loadData(grid, url, options = {}) {
|
|
9072
9453
|
const { rows } = await restDatasource(url, options).getRows({ start: 0, end: options.maxRows ?? 1e4, sort: [], filters: {}, quickFilter: "" });
|