@freegridjs/core 1.0.0 → 1.0.1
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 +8 -8
- package/dist/cdn/freegrid.min.js +8 -8
- package/dist/index.d.ts +8 -6
- package/dist/index.js +368 -126
- package/dist/locales/de.d.ts +1 -1
- package/dist/locales/en.d.ts +1 -1
- package/dist/locales/es.d.ts +1 -1
- package/dist/locales/fr.d.ts +1 -1
- package/dist/locales/id.d.ts +1 -1
- package/dist/locales/it.d.ts +1 -1
- package/dist/locales/ja.d.ts +1 -1
- package/dist/locales/ko.d.ts +1 -1
- package/dist/locales/pt-BR.d.ts +1 -1
- package/dist/locales/ru.d.ts +1 -1
- package/dist/locales/th.d.ts +1 -1
- package/dist/locales/vi.d.ts +1 -1
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-TW.d.ts +1 -1
- package/dist/{types-D9gm8TyF.d.ts → types-bFz-Q6ac.d.ts} +8 -1
- 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
|
});
|
|
@@ -3075,7 +3103,7 @@ function attachBody(grid) {
|
|
|
3075
3103
|
if (!text2 || !e.clipboardData) return;
|
|
3076
3104
|
e.clipboardData.setData("text/plain", text2);
|
|
3077
3105
|
e.preventDefault();
|
|
3078
|
-
grid.clearCells();
|
|
3106
|
+
grid.clearCells(true);
|
|
3079
3107
|
};
|
|
3080
3108
|
const onPaste = (e) => {
|
|
3081
3109
|
if (fromField(e) || nested(e)) return;
|
|
@@ -4144,7 +4172,7 @@ var CellEditor = class {
|
|
|
4144
4172
|
beginRow(row, rowIndex) {
|
|
4145
4173
|
if (this.session?.row === row) return;
|
|
4146
4174
|
if (this.session) this.endRow(true);
|
|
4147
|
-
this.session = { row, id: this.grid.rowModel.idOf(row), before: { ...row } };
|
|
4175
|
+
this.session = { row, id: this.grid.rowModel.idOf(row), before: { ...row }, undoMark: this.grid.history.mark() };
|
|
4148
4176
|
this.grid.renderer.invalidate({ rows: true });
|
|
4149
4177
|
this.grid.events.emit("rowEditStart", { row, rowIndex });
|
|
4150
4178
|
}
|
|
@@ -4153,8 +4181,8 @@ var CellEditor = class {
|
|
|
4153
4181
|
const s = this.session;
|
|
4154
4182
|
if (!s) return;
|
|
4155
4183
|
if (this.cur) {
|
|
4156
|
-
if (commit
|
|
4157
|
-
|
|
4184
|
+
if (commit && !this.commit(null)) return;
|
|
4185
|
+
if (!commit) this.close(false);
|
|
4158
4186
|
}
|
|
4159
4187
|
this.session = null;
|
|
4160
4188
|
const { grid } = this;
|
|
@@ -4167,6 +4195,7 @@ var CellEditor = class {
|
|
|
4167
4195
|
if (!Object.is(s.row[f], s.before[f])) grid.writeCell(changes2, s.row, f, c.id, rowIndex, s.before[f]);
|
|
4168
4196
|
}
|
|
4169
4197
|
grid.endCellTx(changes2);
|
|
4198
|
+
grid.history.rollback(s.undoMark);
|
|
4170
4199
|
}
|
|
4171
4200
|
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
4201
|
grid.renderer.invalidate({ rows: true });
|
|
@@ -4183,7 +4212,7 @@ var CellEditor = class {
|
|
|
4183
4212
|
return !node.kind && cellEditable(col.def, node.data, rowIndex);
|
|
4184
4213
|
}
|
|
4185
4214
|
start(rowIndex, colId, initialText) {
|
|
4186
|
-
if (this.cur
|
|
4215
|
+
if (this.cur && !this.commit(null)) return false;
|
|
4187
4216
|
if (!this.canEdit(rowIndex, colId)) return false;
|
|
4188
4217
|
const { grid } = this;
|
|
4189
4218
|
if (grid.veto("beforeEdit", { row: grid.rowModel.getRow(rowIndex).data, rowIndex, colId })) return false;
|
|
@@ -4196,14 +4225,8 @@ var CellEditor = class {
|
|
|
4196
4225
|
grid.setFocus(rowIndex, colId);
|
|
4197
4226
|
renderer.ensureVisible(rowIndex, colId);
|
|
4198
4227
|
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
4228
|
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`;
|
|
4229
|
+
this.place(wrap, rowIndex, colId);
|
|
4207
4230
|
let control;
|
|
4208
4231
|
let read;
|
|
4209
4232
|
let dispose = () => {
|
|
@@ -4301,6 +4324,36 @@ var CellEditor = class {
|
|
|
4301
4324
|
grid.events.emit("editStart", { rowIndex, colId });
|
|
4302
4325
|
return true;
|
|
4303
4326
|
}
|
|
4327
|
+
/** Put the editor over its cell; false when the cell is not rendered. */
|
|
4328
|
+
place(wrap, rowIndex, colId) {
|
|
4329
|
+
const { renderer } = this.grid;
|
|
4330
|
+
const cellEl = renderer.root.querySelector(`.fg-row[data-row="${rowIndex}"] .fg-cell[data-col="${CSS.escape(colId)}"]`);
|
|
4331
|
+
const rootBox = renderer.root.getBoundingClientRect();
|
|
4332
|
+
const box = cellEl?.getBoundingClientRect();
|
|
4333
|
+
wrap.style.left = `${(box?.left ?? rootBox.left) - rootBox.left}px`;
|
|
4334
|
+
wrap.style.top = `${(box?.top ?? rootBox.top) - rootBox.top}px`;
|
|
4335
|
+
wrap.style.width = `${box?.width || this.grid.columns.getColumn(colId)?.width}px`;
|
|
4336
|
+
wrap.style.height = `${box?.height || renderer.opts.rowHeight}px`;
|
|
4337
|
+
return !!cellEl;
|
|
4338
|
+
}
|
|
4339
|
+
/** Display index of a row object, or -1 when it is no longer shown. */
|
|
4340
|
+
indexOf(row) {
|
|
4341
|
+
const m = this.grid.rowModel;
|
|
4342
|
+
const i = m.indexOfId(m.idOf(row));
|
|
4343
|
+
return i >= 0 && m.getRow(i)?.data === row ? i : -1;
|
|
4344
|
+
}
|
|
4345
|
+
/** The rows changed underneath (transactions): follow the edited row, or cancel when it is gone. */
|
|
4346
|
+
sync() {
|
|
4347
|
+
const cur = this.cur;
|
|
4348
|
+
if (!cur) return;
|
|
4349
|
+
const i = this.indexOf(cur.row);
|
|
4350
|
+
if (i < 0) return this.cancel();
|
|
4351
|
+
if (i === cur.rowIndex) return;
|
|
4352
|
+
cur.rowIndex = i;
|
|
4353
|
+
this.grid.setFocus(i, cur.colId);
|
|
4354
|
+
this.grid.renderer.render();
|
|
4355
|
+
if (!this.place(cur.wrap, i, cur.colId)) this.commit(null);
|
|
4356
|
+
}
|
|
4304
4357
|
/** Validate and write the value. Returns false (editor stays open) when invalid. */
|
|
4305
4358
|
commit(move) {
|
|
4306
4359
|
const cur = this.cur;
|
|
@@ -4320,18 +4373,30 @@ var CellEditor = class {
|
|
|
4320
4373
|
} else value = raw.value;
|
|
4321
4374
|
const error = grid.cellError(cur.def, cur.row, value);
|
|
4322
4375
|
if (error !== null) return this.fail(value, error);
|
|
4376
|
+
const rowIndex = this.indexOf(cur.row);
|
|
4377
|
+
if (rowIndex < 0) {
|
|
4378
|
+
this.cancel();
|
|
4379
|
+
return false;
|
|
4380
|
+
}
|
|
4381
|
+
cur.rowIndex = rowIndex;
|
|
4323
4382
|
this.close(true);
|
|
4324
|
-
grid.applyEdit(
|
|
4383
|
+
grid.applyEdit(rowIndex, cur.colId, value);
|
|
4325
4384
|
if (this.session && (move === "down" || move === "up")) {
|
|
4326
4385
|
this.endRow(true);
|
|
4327
4386
|
}
|
|
4328
|
-
this.move(
|
|
4387
|
+
this.move(rowIndex, cur.colId, move);
|
|
4329
4388
|
return true;
|
|
4330
4389
|
}
|
|
4331
4390
|
cancel() {
|
|
4332
4391
|
this.close(false);
|
|
4333
4392
|
if (this.session) this.endRow(false);
|
|
4334
4393
|
}
|
|
4394
|
+
/** Teardown: remove the editor without writing values or emitting events. */
|
|
4395
|
+
destroy() {
|
|
4396
|
+
this.cur?.dispose();
|
|
4397
|
+
this.cur = null;
|
|
4398
|
+
this.session = null;
|
|
4399
|
+
}
|
|
4335
4400
|
fail(value, message) {
|
|
4336
4401
|
const cur = this.cur;
|
|
4337
4402
|
cur.wrap.classList.add("fg-editor-invalid");
|
|
@@ -4404,6 +4469,16 @@ var History = class {
|
|
|
4404
4469
|
this.entries[this.index++].redo();
|
|
4405
4470
|
return true;
|
|
4406
4471
|
}
|
|
4472
|
+
/** The current position, for rollback(). */
|
|
4473
|
+
mark() {
|
|
4474
|
+
return this.entries[this.index - 1] ?? null;
|
|
4475
|
+
}
|
|
4476
|
+
/** Forget every step pushed after `mark` (a cancelled edit session). */
|
|
4477
|
+
rollback(mark) {
|
|
4478
|
+
const i = mark ? this.entries.indexOf(mark) + 1 : 0;
|
|
4479
|
+
this.entries.splice(i);
|
|
4480
|
+
this.index = Math.min(this.index, i);
|
|
4481
|
+
}
|
|
4407
4482
|
clear() {
|
|
4408
4483
|
this.entries = [];
|
|
4409
4484
|
this.index = 0;
|
|
@@ -4432,6 +4507,14 @@ var ChangeTracker = class {
|
|
|
4432
4507
|
const o = this.originals.get(row);
|
|
4433
4508
|
return o ? fields.filter((f) => !same(o[f], row[f])) : [];
|
|
4434
4509
|
}
|
|
4510
|
+
/** Baseline `rows` (row → the values that were saved); `deleted` rows are no longer pending. */
|
|
4511
|
+
commit(rows, deleted) {
|
|
4512
|
+
for (const r of deleted) this.deleted.delete(r);
|
|
4513
|
+
for (const [row, values] of rows) {
|
|
4514
|
+
this.created.delete(row);
|
|
4515
|
+
this.originals.set(row, { ...values });
|
|
4516
|
+
}
|
|
4517
|
+
}
|
|
4435
4518
|
get size() {
|
|
4436
4519
|
return this.originals.size + this.created.size + this.deleted.size;
|
|
4437
4520
|
}
|
|
@@ -4692,12 +4775,34 @@ function zipStore(files) {
|
|
|
4692
4775
|
return out;
|
|
4693
4776
|
}
|
|
4694
4777
|
var isZip = (data) => data.length > 4 && data[0] === 80 && data[1] === 75 && data[2] === 3 && data[3] === 4;
|
|
4695
|
-
|
|
4778
|
+
var tooLarge = (max) => new Error(`Zip file is too large: it unpacks to more than ${max >= 1048576 ? `${Math.round(max / 1048576)} MB` : `${max} bytes`}`);
|
|
4779
|
+
async function inflateRaw(data, max) {
|
|
4696
4780
|
if (typeof DecompressionStream === "undefined") throw new Error("This browser cannot read compressed xlsx files (no DecompressionStream)");
|
|
4697
4781
|
const stream = new Response(data).body.pipeThrough(new DecompressionStream("deflate-raw"));
|
|
4698
|
-
|
|
4782
|
+
const reader = stream.getReader();
|
|
4783
|
+
const chunks = [];
|
|
4784
|
+
let size = 0;
|
|
4785
|
+
for (; ; ) {
|
|
4786
|
+
const { done, value } = await reader.read();
|
|
4787
|
+
if (done) break;
|
|
4788
|
+
size += value.length;
|
|
4789
|
+
if (size > max) {
|
|
4790
|
+
await reader.cancel().catch(() => void 0);
|
|
4791
|
+
throw tooLarge(max);
|
|
4792
|
+
}
|
|
4793
|
+
chunks.push(value);
|
|
4794
|
+
}
|
|
4795
|
+
const out = new Uint8Array(size);
|
|
4796
|
+
let at = 0;
|
|
4797
|
+
for (const c of chunks) {
|
|
4798
|
+
out.set(c, at);
|
|
4799
|
+
at += c.length;
|
|
4800
|
+
}
|
|
4801
|
+
return out;
|
|
4699
4802
|
}
|
|
4700
|
-
async function unzip(data) {
|
|
4803
|
+
async function unzip(data, limits = {}) {
|
|
4804
|
+
const maxBytes = limits.maxBytes ?? 200 * 1048576;
|
|
4805
|
+
const maxEntries = limits.maxEntries ?? 1e4;
|
|
4701
4806
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
4702
4807
|
let eocd = -1;
|
|
4703
4808
|
for (let i = data.length - 22; i >= Math.max(0, data.length - 65557); i--) {
|
|
@@ -4708,9 +4813,11 @@ async function unzip(data) {
|
|
|
4708
4813
|
}
|
|
4709
4814
|
if (eocd < 0) throw new Error("Not a zip file");
|
|
4710
4815
|
const count = view.getUint16(eocd + 10, true);
|
|
4816
|
+
if (count > maxEntries) throw new Error(`Zip file has too many entries (${count}, at most ${maxEntries})`);
|
|
4711
4817
|
let p = view.getUint32(eocd + 16, true);
|
|
4712
4818
|
const decoder = new TextDecoder();
|
|
4713
4819
|
const out = /* @__PURE__ */ new Map();
|
|
4820
|
+
let total = 0;
|
|
4714
4821
|
for (let i = 0; i < count; i++) {
|
|
4715
4822
|
if (view.getUint32(p, true) !== 33639248) throw new Error("Broken zip directory");
|
|
4716
4823
|
const method = view.getUint16(p + 10, true);
|
|
@@ -4721,9 +4828,13 @@ async function unzip(data) {
|
|
|
4721
4828
|
const lName = view.getUint16(offset + 26, true), lExtra = view.getUint16(offset + 28, true);
|
|
4722
4829
|
const start = offset + 30 + lName + lExtra;
|
|
4723
4830
|
const raw = data.subarray(start, start + size);
|
|
4724
|
-
|
|
4725
|
-
|
|
4831
|
+
let bytes;
|
|
4832
|
+
if (method === 0) bytes = raw;
|
|
4833
|
+
else if (method === 8) bytes = await inflateRaw(raw, maxBytes - total);
|
|
4726
4834
|
else throw new Error(`Unsupported zip compression ${method}`);
|
|
4835
|
+
total += bytes.length;
|
|
4836
|
+
if (total > maxBytes) throw tooLarge(maxBytes);
|
|
4837
|
+
out.set(name, bytes);
|
|
4727
4838
|
p += 46 + nameLen + extraLen + commentLen;
|
|
4728
4839
|
}
|
|
4729
4840
|
return out;
|
|
@@ -4844,7 +4955,8 @@ function writeXlsx(sheets) {
|
|
|
4844
4955
|
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
4956
|
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
4957
|
const filter = sheet.autoFilter && width && freeze && sheet.rows.length >= freeze ? `<autoFilter ref="A${freeze}:${colName(width - 1)}${sheet.rows.length}"/>` : "";
|
|
4847
|
-
const
|
|
4958
|
+
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));
|
|
4959
|
+
const merges = mergeRefs.length ? `<mergeCells count="${mergeRefs.length}">${mergeRefs.map((m) => `<mergeCell ref="${m}"/>`).join("")}</mergeCells>` : "";
|
|
4848
4960
|
return `${XML}<worksheet xmlns="${NS}" xmlns:r="${REL_NS}">${views}<sheetFormatPr defaultRowHeight="15"/>${cols}<sheetData>${rowsXml}</sheetData>${filter}${merges}</worksheet>`;
|
|
4849
4961
|
});
|
|
4850
4962
|
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 +5004,8 @@ function colIndex(ref) {
|
|
|
4892
5004
|
}
|
|
4893
5005
|
return n - 1;
|
|
4894
5006
|
}
|
|
5007
|
+
var MAX_ROWS = 1048576;
|
|
5008
|
+
var MAX_COLS = 16384;
|
|
4895
5009
|
async function readXlsx(input) {
|
|
4896
5010
|
const data = input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
4897
5011
|
if (!isZip(data)) throw new Error("Not an xlsx file");
|
|
@@ -4919,11 +5033,13 @@ async function readXlsx(input) {
|
|
|
4919
5033
|
if (doc) {
|
|
4920
5034
|
for (const row of tags(doc, "row")) {
|
|
4921
5035
|
const r = Number(row.getAttribute("r") ?? rows.length + 1) - 1;
|
|
5036
|
+
if (!Number.isInteger(r) || r < 0 || r >= MAX_ROWS) continue;
|
|
4922
5037
|
const out = [];
|
|
4923
5038
|
let next = 0;
|
|
4924
5039
|
for (const c of [...row.children].filter((x) => x.localName === "c")) {
|
|
4925
5040
|
const ref = c.getAttribute("r");
|
|
4926
5041
|
const ci = ref ? colIndex(ref) : next;
|
|
5042
|
+
if (ci < 0 || ci >= MAX_COLS) continue;
|
|
4927
5043
|
next = ci + 1;
|
|
4928
5044
|
const t = c.getAttribute("t");
|
|
4929
5045
|
const v = tags(c, "v")[0]?.textContent ?? null;
|
|
@@ -4968,6 +5084,8 @@ function exportRows(grid, mode) {
|
|
|
4968
5084
|
return grid.rowModel.dataRows().map((n) => n.data);
|
|
4969
5085
|
}
|
|
4970
5086
|
var header = (def, id) => def.header ?? def.field ?? id;
|
|
5087
|
+
var ALIGNS = ["left", "center", "right"];
|
|
5088
|
+
var PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
4971
5089
|
var escHtml = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
4972
5090
|
function buildPrintHtml(grid, opts = {}) {
|
|
4973
5091
|
const cols = exportColumns(grid, opts.columns);
|
|
@@ -4997,7 +5115,7 @@ function buildPrintHtml(grid, opts = {}) {
|
|
|
4997
5115
|
}
|
|
4998
5116
|
head.push(`<tr>${tr}</tr>`);
|
|
4999
5117
|
}
|
|
5000
|
-
const align = (def) => def.align
|
|
5118
|
+
const align = (def) => ALIGNS.includes(def.align) ? def.align : def.type === "number" ? "right" : def.type === "boolean" ? "center" : "left";
|
|
5001
5119
|
const body = exportRows(grid, opts.rows).map((r) => `<tr>${cols.map((c, i) => {
|
|
5002
5120
|
const v = getCellValue(c.def, r);
|
|
5003
5121
|
return `<td style="text-align:${align(c.def)}">${escHtml(isBlank(v) ? "" : fmts[i](v))}</td>`;
|
|
@@ -5048,7 +5166,8 @@ function buildCsv(grid, opts = {}) {
|
|
|
5048
5166
|
const matrix = exportRows(grid, opts.rows).map((r) => cols.map((c) => {
|
|
5049
5167
|
const v = getCellValue(c.def, r);
|
|
5050
5168
|
const t = c.def.type;
|
|
5051
|
-
|
|
5169
|
+
const value = copyValue(c.def, v, code);
|
|
5170
|
+
return { value, text: !(t === "number" || t === "date" || t === "boolean") || typeof v === "string" && !PLAIN_NUMBER.test(value) };
|
|
5052
5171
|
}));
|
|
5053
5172
|
if (opts.headers !== false) matrix.unshift(cols.map((c) => ({ value: header(c.def, c.id), text: true })));
|
|
5054
5173
|
return toCsv(matrix, sep);
|
|
@@ -5559,7 +5678,7 @@ function drawScatter(root, spec, series, top, bottom, nf, short) {
|
|
|
5559
5678
|
|
|
5560
5679
|
// src/interactions/charts.ts
|
|
5561
5680
|
var MAX_SERIES = 12;
|
|
5562
|
-
var
|
|
5681
|
+
var MAX_ROWS2 = 500;
|
|
5563
5682
|
var TYPES = ["bar", "stackedBar", "line", "area", "combo", "pie", "donut", "scatter"];
|
|
5564
5683
|
var GridCharts = class {
|
|
5565
5684
|
constructor(grid) {
|
|
@@ -5577,7 +5696,7 @@ var GridCharts = class {
|
|
|
5577
5696
|
if (a && b) {
|
|
5578
5697
|
const box = normalizeRange({ start: { rowIndex: r.start.rowIndex, colIndex: a.index }, end: { rowIndex: r.end.rowIndex, colIndex: b.index } });
|
|
5579
5698
|
const rows = [];
|
|
5580
|
-
for (let i = box.r1; i <= box.r2 && rows.length <
|
|
5699
|
+
for (let i = box.r1; i <= box.r2 && rows.length < MAX_ROWS2; i++) {
|
|
5581
5700
|
const n = grid.rowModel.getRow(i);
|
|
5582
5701
|
if (n && !n.kind) rows.push(n.data);
|
|
5583
5702
|
}
|
|
@@ -5585,7 +5704,7 @@ var GridCharts = class {
|
|
|
5585
5704
|
}
|
|
5586
5705
|
}
|
|
5587
5706
|
const data = () => {
|
|
5588
|
-
const rows = fixed ? fixed.rows : grid.rowModel.dataRows().slice(0,
|
|
5707
|
+
const rows = fixed ? fixed.rows : grid.rowModel.dataRows().slice(0, MAX_ROWS2).map((n) => n.data);
|
|
5589
5708
|
const cols = (fixed ? fixed.colIds.map((id) => grid.columns.getColumn(id)) : grid.columns.displayed().filter((c) => !isSystemCol(c.id))).filter((c) => !!c);
|
|
5590
5709
|
const cat = cols[0] && cols[0].def.type !== "number" ? cols[0] : fixed ? null : cols.find((c) => c.def.type !== "number") ?? null;
|
|
5591
5710
|
const isTotal = (c) => !!c.node.parent && c.node.parent.id === "pivotgroup|total";
|
|
@@ -5836,7 +5955,7 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
5836
5955
|
}
|
|
5837
5956
|
|
|
5838
5957
|
// src/version.ts
|
|
5839
|
-
var version = "1.0.
|
|
5958
|
+
var version = "1.0.1";
|
|
5840
5959
|
|
|
5841
5960
|
// src/license.ts
|
|
5842
5961
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -5862,6 +5981,12 @@ function domainMatches(host, domains) {
|
|
|
5862
5981
|
return wildcard && h.endsWith(`.${dom}`);
|
|
5863
5982
|
});
|
|
5864
5983
|
}
|
|
5984
|
+
var isDay = (s) => {
|
|
5985
|
+
const m = typeof s === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(s) : null;
|
|
5986
|
+
if (!m) return false;
|
|
5987
|
+
const d = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3])));
|
|
5988
|
+
return d.getUTCMonth() === Number(m[2]) - 1 && d.getUTCDate() === Number(m[3]);
|
|
5989
|
+
};
|
|
5865
5990
|
var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
5866
5991
|
async function verifyLicense(key, host, opts = {}) {
|
|
5867
5992
|
const parts = typeof key === "string" ? key.trim().split(".") : [];
|
|
@@ -5869,7 +5994,7 @@ async function verifyLicense(key, host, opts = {}) {
|
|
|
5869
5994
|
let info;
|
|
5870
5995
|
try {
|
|
5871
5996
|
info = JSON.parse(new TextDecoder().decode(b64urlDecode(parts[1])));
|
|
5872
|
-
if (!Array.isArray(info.domains) ||
|
|
5997
|
+
if (!Array.isArray(info.domains) || !isDay(info.expires)) return { ok: false, reason: "format" };
|
|
5873
5998
|
} catch {
|
|
5874
5999
|
return { ok: false, reason: "format" };
|
|
5875
6000
|
}
|
|
@@ -6479,17 +6604,25 @@ var RowModel = class {
|
|
|
6479
6604
|
|
|
6480
6605
|
// src/rows/serverGroups.ts
|
|
6481
6606
|
var LEVEL_ROWS = 1e4;
|
|
6607
|
+
var MAX_LEVEL_ROWS = 1e5;
|
|
6608
|
+
var MAX_IN_FLIGHT = 4;
|
|
6609
|
+
var RETRY_MS = 3e3;
|
|
6482
6610
|
var ServerGroups = class {
|
|
6483
|
-
constructor(datasource, getRowId, groupBy, query, onChange) {
|
|
6611
|
+
constructor(datasource, getRowId, groupBy, query, onChange, onFailed) {
|
|
6484
6612
|
this.datasource = datasource;
|
|
6485
6613
|
this.getRowId = getRowId;
|
|
6486
6614
|
this.groupBy = groupBy;
|
|
6487
6615
|
this.query = query;
|
|
6488
6616
|
this.onChange = onChange;
|
|
6617
|
+
this.onFailed = onFailed;
|
|
6489
6618
|
this.levels = /* @__PURE__ */ new Map();
|
|
6490
6619
|
this.gen = 0;
|
|
6491
6620
|
this.display = null;
|
|
6492
6621
|
this.ids = null;
|
|
6622
|
+
this.queue = [];
|
|
6623
|
+
this.inFlight = 0;
|
|
6624
|
+
/** Earliest time a failed level in the display may be asked again (0: none). */
|
|
6625
|
+
this.retryAt = 0;
|
|
6493
6626
|
this.version = 0;
|
|
6494
6627
|
this.builtFor = -1;
|
|
6495
6628
|
}
|
|
@@ -6498,9 +6631,15 @@ var ServerGroups = class {
|
|
|
6498
6631
|
}
|
|
6499
6632
|
purge() {
|
|
6500
6633
|
this.gen++;
|
|
6634
|
+
this.queue = [];
|
|
6501
6635
|
this.levels.clear();
|
|
6502
6636
|
this.touch();
|
|
6503
6637
|
}
|
|
6638
|
+
/** Replaced by another grouping: answers still on their way are dropped, no more change calls. */
|
|
6639
|
+
dispose() {
|
|
6640
|
+
this.gen++;
|
|
6641
|
+
this.queue = [];
|
|
6642
|
+
}
|
|
6504
6643
|
touch() {
|
|
6505
6644
|
this.version++;
|
|
6506
6645
|
this.display = null;
|
|
@@ -6508,15 +6647,33 @@ var ServerGroups = class {
|
|
|
6508
6647
|
}
|
|
6509
6648
|
load(path, keys, level) {
|
|
6510
6649
|
if (this.levels.has(path)) return;
|
|
6511
|
-
const gen = this.gen;
|
|
6512
6650
|
this.levels.set(path, { state: "loading", nodes: [] });
|
|
6651
|
+
this.fetch({ path, keys, level, start: 0, gen: this.gen });
|
|
6652
|
+
}
|
|
6653
|
+
fetch(job) {
|
|
6654
|
+
this.queue.push(job);
|
|
6655
|
+
this.pump();
|
|
6656
|
+
}
|
|
6657
|
+
pump() {
|
|
6658
|
+
while (this.inFlight < MAX_IN_FLIGHT && this.queue.length) {
|
|
6659
|
+
const job = this.queue.shift();
|
|
6660
|
+
if (job.gen !== this.gen) continue;
|
|
6661
|
+
this.inFlight++;
|
|
6662
|
+
void this.request(job).finally(() => {
|
|
6663
|
+
this.inFlight--;
|
|
6664
|
+
this.pump();
|
|
6665
|
+
});
|
|
6666
|
+
}
|
|
6667
|
+
}
|
|
6668
|
+
request({ path, keys, level, start, gen }) {
|
|
6513
6669
|
const last = level >= this.groupBy.length;
|
|
6514
6670
|
const q = this.query;
|
|
6671
|
+
const end = start + LEVEL_ROWS;
|
|
6515
6672
|
let result;
|
|
6516
6673
|
try {
|
|
6517
6674
|
result = Promise.resolve(this.datasource.getRows({
|
|
6518
|
-
start
|
|
6519
|
-
end
|
|
6675
|
+
start,
|
|
6676
|
+
end,
|
|
6520
6677
|
sort: [...q.sort],
|
|
6521
6678
|
filters: { ...q.filters },
|
|
6522
6679
|
quickFilter: q.quickFilter,
|
|
@@ -6527,11 +6684,11 @@ var ServerGroups = class {
|
|
|
6527
6684
|
} catch (e) {
|
|
6528
6685
|
result = Promise.reject(e);
|
|
6529
6686
|
}
|
|
6530
|
-
result.then((res) => {
|
|
6687
|
+
return result.then((res) => {
|
|
6531
6688
|
if (gen !== this.gen) return;
|
|
6532
6689
|
const rows = Array.isArray(res?.rows) ? res.rows.filter((r) => r !== null && typeof r === "object") : [];
|
|
6533
6690
|
if (!Array.isArray(res?.rows)) warn("datasource.getRows must resolve to { rows: Row[] }");
|
|
6534
|
-
const
|
|
6691
|
+
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
6692
|
const colId = this.groupBy[level];
|
|
6536
6693
|
const value = data[colId];
|
|
6537
6694
|
const key = `${path ? `${path}|` : ""}${colId}:${setKey(value)}`;
|
|
@@ -6545,14 +6702,23 @@ var ServerGroups = class {
|
|
|
6545
6702
|
group: { key, colId, value, rows: [], expanded: false, count, aggregates: data, keys: [...keys, value] }
|
|
6546
6703
|
};
|
|
6547
6704
|
});
|
|
6548
|
-
this.levels.
|
|
6705
|
+
const nodes = start ? [...this.levels.get(path)?.nodes ?? [], ...added] : added;
|
|
6706
|
+
const total = typeof res?.total === "number" && res.total >= 0 ? res.total : null;
|
|
6707
|
+
const more = rows.length > 0 && (total !== null ? nodes.length < total : rows.length >= end - start);
|
|
6708
|
+
if (more && nodes.length < MAX_LEVEL_ROWS) {
|
|
6709
|
+
this.levels.set(path, { state: "loading", nodes });
|
|
6710
|
+
this.fetch({ path, keys, level, start: nodes.length, gen });
|
|
6711
|
+
} else {
|
|
6712
|
+
if (more) warn(`a server group level has more than ${MAX_LEVEL_ROWS} rows; only the first ${MAX_LEVEL_ROWS} are shown`);
|
|
6713
|
+
this.levels.set(path, { state: "loaded", nodes });
|
|
6714
|
+
}
|
|
6549
6715
|
this.touch();
|
|
6550
6716
|
this.onChange();
|
|
6551
|
-
}, () => {
|
|
6717
|
+
}, (error) => {
|
|
6552
6718
|
if (gen !== this.gen) return;
|
|
6553
|
-
this.levels.set(path, { state: "failed", nodes: [] });
|
|
6719
|
+
this.levels.set(path, { state: "failed", nodes: [], retryAt: Date.now() + RETRY_MS });
|
|
6554
6720
|
this.touch();
|
|
6555
|
-
this.
|
|
6721
|
+
this.onFailed(start, end, error);
|
|
6556
6722
|
});
|
|
6557
6723
|
}
|
|
6558
6724
|
isOpen(d, key, level) {
|
|
@@ -6562,17 +6728,24 @@ var ServerGroups = class {
|
|
|
6562
6728
|
}
|
|
6563
6729
|
/** Display rows: open groups show their loaded children, or a loading row while they arrive. */
|
|
6564
6730
|
rows(d, displayVersion) {
|
|
6565
|
-
|
|
6731
|
+
const now = Date.now();
|
|
6732
|
+
if (this.display && this.builtFor === displayVersion && !(this.retryAt && now >= this.retryAt)) return this.display;
|
|
6566
6733
|
const out = [];
|
|
6734
|
+
let retryAt = 0;
|
|
6567
6735
|
const walk = (path, keys, level) => {
|
|
6568
|
-
|
|
6736
|
+
let lv = this.levels.get(path);
|
|
6737
|
+
if (lv?.state === "failed" && now >= (lv.retryAt ?? 0)) {
|
|
6738
|
+
this.levels.delete(path);
|
|
6739
|
+
lv = void 0;
|
|
6740
|
+
}
|
|
6569
6741
|
if (!lv) {
|
|
6570
6742
|
this.load(path, keys, level);
|
|
6571
6743
|
out.push({ id: `loading:${path}`, data: {}, sourceIndex: -1, kind: "loading", level });
|
|
6572
6744
|
return;
|
|
6573
6745
|
}
|
|
6574
|
-
if (lv.state
|
|
6575
|
-
|
|
6746
|
+
if (lv.state === "failed") {
|
|
6747
|
+
const at = lv.retryAt ?? 0;
|
|
6748
|
+
retryAt = retryAt ? Math.min(retryAt, at) : at;
|
|
6576
6749
|
return;
|
|
6577
6750
|
}
|
|
6578
6751
|
for (const n of lv.nodes) {
|
|
@@ -6586,8 +6759,10 @@ var ServerGroups = class {
|
|
|
6586
6759
|
out.push(node);
|
|
6587
6760
|
if (open) walk(g.key, g.keys ?? [], level + 1);
|
|
6588
6761
|
}
|
|
6762
|
+
if (lv.state === "loading") out.push({ id: `loading:${path}`, data: {}, sourceIndex: -1, kind: "loading", level });
|
|
6589
6763
|
};
|
|
6590
6764
|
walk("", [], 0);
|
|
6765
|
+
this.retryAt = retryAt;
|
|
6591
6766
|
this.display = out;
|
|
6592
6767
|
this.builtFor = displayVersion;
|
|
6593
6768
|
this.ids = null;
|
|
@@ -6621,7 +6796,6 @@ var ServerGroups = class {
|
|
|
6621
6796
|
};
|
|
6622
6797
|
|
|
6623
6798
|
// src/rows/serverRowModel.ts
|
|
6624
|
-
var RETRY_MS = 3e3;
|
|
6625
6799
|
var NO_OFFSETS = new Float64Array(0);
|
|
6626
6800
|
var ServerRowModel = class {
|
|
6627
6801
|
constructor(deps) {
|
|
@@ -6655,7 +6829,15 @@ var ServerRowModel = class {
|
|
|
6655
6829
|
if (groupBy.length && !input.display?.getParentId) {
|
|
6656
6830
|
const q2 = { sort: input.sort, filters: input.filters, quickFilter: input.quickFilter, advancedFilter: input.advancedFilter ?? null };
|
|
6657
6831
|
if (!this.groups || !this.groups.matches(groupBy, q2)) {
|
|
6658
|
-
this.groups
|
|
6832
|
+
this.groups?.dispose();
|
|
6833
|
+
this.groups = new ServerGroups(
|
|
6834
|
+
this.deps.datasource,
|
|
6835
|
+
this.deps.getRowId,
|
|
6836
|
+
[...groupBy],
|
|
6837
|
+
q2,
|
|
6838
|
+
() => this.deps.onLoaded(0, 0, null),
|
|
6839
|
+
(start, end, error) => this.deps.onFailed(start, end, error)
|
|
6840
|
+
);
|
|
6659
6841
|
this.touch();
|
|
6660
6842
|
}
|
|
6661
6843
|
this.displayIn = input.display ?? null;
|
|
@@ -6663,6 +6845,7 @@ var ServerRowModel = class {
|
|
|
6663
6845
|
return;
|
|
6664
6846
|
}
|
|
6665
6847
|
if (this.groups) {
|
|
6848
|
+
this.groups.dispose();
|
|
6666
6849
|
this.groups = null;
|
|
6667
6850
|
this.query = null;
|
|
6668
6851
|
this.touch();
|
|
@@ -7066,22 +7249,30 @@ var FilterBuilder = class {
|
|
|
7066
7249
|
del.addEventListener("click", () => remove(state));
|
|
7067
7250
|
return state;
|
|
7068
7251
|
};
|
|
7069
|
-
const
|
|
7252
|
+
const keptRow = (kept, label) => {
|
|
7070
7253
|
const rowEl = el("div", "fg-fb-row fg-fb-group");
|
|
7071
7254
|
const text2 = el("span", "fg-fb-group-label");
|
|
7072
|
-
text2.textContent =
|
|
7255
|
+
text2.textContent = label;
|
|
7073
7256
|
const del = el("button", "fg-chooser-close fg-fb-remove", { type: "button", "aria-label": loc.close });
|
|
7074
7257
|
del.textContent = "\xD7";
|
|
7075
7258
|
rowEl.append(text2, del);
|
|
7076
|
-
const state = { el: rowEl, read: () =>
|
|
7259
|
+
const state = { el: rowEl, read: () => kept };
|
|
7077
7260
|
del.addEventListener("click", () => remove(state));
|
|
7078
7261
|
return state;
|
|
7079
7262
|
};
|
|
7263
|
+
const rowFor = (c) => {
|
|
7264
|
+
if ("conditions" in c) return keptRow(c, `( ${c.conditions.length} \xD7 ${loc[c.operator]} )`);
|
|
7265
|
+
const col = cols.find((x2) => x2.id === c.colId);
|
|
7266
|
+
const m = c.model;
|
|
7267
|
+
if (col && m && (m.type === "text" || m.type === "number" || m.type === "date")) return conditionRow(c);
|
|
7268
|
+
const what = m?.type === "set" ? m.values.map(String).join(", ") : m?.type === "combined" ? `( ${m.conditions.length} \xD7 ${loc[m.operator]} )` : "";
|
|
7269
|
+
return keptRow(c, `${col?.label ?? c.colId}: ${what}`);
|
|
7270
|
+
};
|
|
7080
7271
|
const add = (row) => {
|
|
7081
7272
|
rows.push(row);
|
|
7082
7273
|
list.append(row.el);
|
|
7083
7274
|
};
|
|
7084
|
-
for (const c of cur?.conditions ?? []) add(
|
|
7275
|
+
for (const c of cur?.conditions ?? []) add(rowFor(c));
|
|
7085
7276
|
const addBtn = el("button", "fg-btn fg-fb-add", { type: "button" });
|
|
7086
7277
|
addBtn.textContent = `+ ${loc.addCondition}`;
|
|
7087
7278
|
addBtn.addEventListener("click", () => {
|
|
@@ -7279,7 +7470,7 @@ var Grid = class {
|
|
|
7279
7470
|
() => this.menu.close(),
|
|
7280
7471
|
() => this.chooser.close(),
|
|
7281
7472
|
() => this.contextMenu.destroy(),
|
|
7282
|
-
() => this.editor.
|
|
7473
|
+
() => this.editor.destroy(),
|
|
7283
7474
|
() => this.fillHandle.destroy(),
|
|
7284
7475
|
() => this.groupPanel?.destroy(),
|
|
7285
7476
|
() => this.charts.destroy(),
|
|
@@ -7449,35 +7640,68 @@ var Grid = class {
|
|
|
7449
7640
|
this.flashTimer = window.setTimeout(tick, 1e3);
|
|
7450
7641
|
}
|
|
7451
7642
|
applyTransaction(tx) {
|
|
7643
|
+
if (!tx || typeof tx !== "object") return { added: [], updated: [], removed: [], notFound: [] };
|
|
7644
|
+
return this.applyTransactions([tx]);
|
|
7645
|
+
}
|
|
7646
|
+
/** Apply transactions one after another, then render and emit once. */
|
|
7647
|
+
applyTransactions(list) {
|
|
7452
7648
|
const result = { added: [], updated: [], removed: [], notFound: [] };
|
|
7453
|
-
if (this.clientOnly("applyTransaction")
|
|
7454
|
-
const
|
|
7649
|
+
if (this.clientOnly("applyTransaction")) return result;
|
|
7650
|
+
const colsByField = /* @__PURE__ */ new Map();
|
|
7651
|
+
for (const st of this.columns.getState()) {
|
|
7652
|
+
const f = this.columns.getNode(st.colId)?.def.field;
|
|
7653
|
+
if (f) colsByField.set(f, [...colsByField.get(f) ?? [], st.colId]);
|
|
7654
|
+
}
|
|
7655
|
+
const flashKeys = [];
|
|
7656
|
+
let next = this.store.state.data;
|
|
7657
|
+
const present = new Set(next);
|
|
7658
|
+
const byId = new Map(next.map((r) => [this.rowModel.idOf(r), r]));
|
|
7659
|
+
for (const tx of list) {
|
|
7660
|
+
if (tx && typeof tx === "object") next = this.txStep(tx, next, { present, byId }, result, flashKeys, colsByField);
|
|
7661
|
+
}
|
|
7662
|
+
if (!result.added.length && !result.updated.length && !result.removed.length) {
|
|
7663
|
+
if (result.notFound.length) this.events.emit("transactionApplied", result);
|
|
7664
|
+
return result;
|
|
7665
|
+
}
|
|
7666
|
+
this.editVersion++;
|
|
7667
|
+
this.renderer.invalidateHeights();
|
|
7668
|
+
this.keepRange = true;
|
|
7669
|
+
try {
|
|
7670
|
+
this.replaceData(next === this.store.state.data ? [...next] : next);
|
|
7671
|
+
} finally {
|
|
7672
|
+
this.keepRange = false;
|
|
7673
|
+
}
|
|
7674
|
+
this.editor.sync();
|
|
7675
|
+
if (flashKeys.length && this.options.flashCells !== false) this.flash(flashKeys);
|
|
7676
|
+
this.events.emit("transactionApplied", result);
|
|
7677
|
+
return result;
|
|
7678
|
+
}
|
|
7679
|
+
/** One transaction against `data` (remove → update → add); returns the new array. */
|
|
7680
|
+
txStep(tx, data, index, result, flashKeys, colsByField) {
|
|
7455
7681
|
const getId = this.options.getRowId;
|
|
7682
|
+
const { present, byId } = index;
|
|
7456
7683
|
const keyOf = (r) => {
|
|
7457
7684
|
if (!r || typeof r !== "object") return null;
|
|
7458
|
-
if (
|
|
7685
|
+
if (present.has(r)) return this.rowModel.idOf(r);
|
|
7459
7686
|
try {
|
|
7460
7687
|
return getId ? String(getId(r)) : null;
|
|
7461
7688
|
} catch {
|
|
7462
7689
|
return null;
|
|
7463
7690
|
}
|
|
7464
7691
|
};
|
|
7465
|
-
const byId = new Map(data.map((r) => [this.rowModel.idOf(r), r]));
|
|
7466
7692
|
const gone = /* @__PURE__ */ new Set();
|
|
7467
7693
|
for (const r of tx.remove ?? []) {
|
|
7468
7694
|
const k = keyOf(r);
|
|
7469
7695
|
const row = k === null ? void 0 : byId.get(k);
|
|
7470
7696
|
if (row && !gone.has(row)) {
|
|
7471
7697
|
gone.add(row);
|
|
7698
|
+
present.delete(row);
|
|
7699
|
+
byId.delete(k);
|
|
7472
7700
|
result.removed.push(row);
|
|
7701
|
+
this.tracker.created.delete(row);
|
|
7702
|
+
this.tracker.originals.delete(row);
|
|
7473
7703
|
} else if (!row) result.notFound.push(r);
|
|
7474
7704
|
}
|
|
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
7705
|
for (const r of tx.update ?? []) {
|
|
7482
7706
|
const k = keyOf(r);
|
|
7483
7707
|
const row = k === null ? void 0 : byId.get(k);
|
|
@@ -7485,7 +7709,9 @@ var Grid = class {
|
|
|
7485
7709
|
result.notFound.push(r);
|
|
7486
7710
|
continue;
|
|
7487
7711
|
}
|
|
7712
|
+
const orig = this.tracker.originals.get(row);
|
|
7488
7713
|
for (const [field, value] of Object.entries(r)) {
|
|
7714
|
+
if (orig) orig[field] = value;
|
|
7489
7715
|
if (Object.is(row[field], value)) continue;
|
|
7490
7716
|
row[field] = value;
|
|
7491
7717
|
for (const colId of colsByField.get(field) ?? []) flashKeys.push(`${k}\0${colId}`);
|
|
@@ -7494,24 +7720,15 @@ var Grid = class {
|
|
|
7494
7720
|
}
|
|
7495
7721
|
const added = cleanRows(Array.isArray(tx.add) ? tx.add : []);
|
|
7496
7722
|
result.added.push(...added);
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7723
|
+
for (const r of added) {
|
|
7724
|
+
present.add(r);
|
|
7725
|
+
byId.set(this.rowModel.idOf(r), r);
|
|
7500
7726
|
}
|
|
7727
|
+
if (!gone.size && !added.length) return data;
|
|
7501
7728
|
const next = gone.size ? data.filter((r) => !gone.has(r)) : [...data];
|
|
7502
7729
|
const at = typeof tx.addIndex === "number" ? Math.max(0, Math.min(next.length, Math.floor(tx.addIndex))) : next.length;
|
|
7503
7730
|
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;
|
|
7731
|
+
return next;
|
|
7515
7732
|
}
|
|
7516
7733
|
applyTransactionAsync(tx) {
|
|
7517
7734
|
if (!tx || typeof tx !== "object") return;
|
|
@@ -7522,12 +7739,7 @@ var Grid = class {
|
|
|
7522
7739
|
const queue = this.txQueue;
|
|
7523
7740
|
this.txQueue = [];
|
|
7524
7741
|
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
|
-
});
|
|
7742
|
+
this.applyTransactions(queue);
|
|
7531
7743
|
}, 50);
|
|
7532
7744
|
}
|
|
7533
7745
|
setAdvancedFilter(filter) {
|
|
@@ -7543,6 +7755,8 @@ var Grid = class {
|
|
|
7543
7755
|
}
|
|
7544
7756
|
getState() {
|
|
7545
7757
|
const s = this.store.state;
|
|
7758
|
+
const groupBy = this.savedGroup?.groupBy ?? this.groupBy;
|
|
7759
|
+
const expanded = this.savedGroup?.expanded ?? this.expanded;
|
|
7546
7760
|
const state = {
|
|
7547
7761
|
columns: this.columns.getState(),
|
|
7548
7762
|
columnGroups: this.columns.groupOpenState(),
|
|
@@ -7550,9 +7764,9 @@ var Grid = class {
|
|
|
7550
7764
|
sort: s.sort.map((x) => ({ ...x })),
|
|
7551
7765
|
filters: JSON.parse(JSON.stringify(s.filters)),
|
|
7552
7766
|
quickFilter: s.quickFilter,
|
|
7553
|
-
groupBy: [...
|
|
7767
|
+
groupBy: [...groupBy],
|
|
7554
7768
|
pivot: this.pivotCfg ? JSON.parse(JSON.stringify(this.pivotCfg)) : null,
|
|
7555
|
-
expanded: Object.fromEntries(
|
|
7769
|
+
expanded: Object.fromEntries(expanded),
|
|
7556
7770
|
selected: [...s.selected]
|
|
7557
7771
|
};
|
|
7558
7772
|
if (this.options.pagination) {
|
|
@@ -7583,8 +7797,12 @@ var Grid = class {
|
|
|
7583
7797
|
if (want("advancedFilter")) this.setAdvancedFilter(state.advancedFilter ?? null);
|
|
7584
7798
|
if (want("quickFilter") && typeof state.quickFilter === "string") this.store.set({ quickFilter: state.quickFilter });
|
|
7585
7799
|
if (want("expanded") && state.expanded && typeof state.expanded === "object") {
|
|
7586
|
-
|
|
7587
|
-
this.
|
|
7800
|
+
const expanded = new Map(Object.entries(state.expanded).filter(([, v]) => typeof v === "boolean"));
|
|
7801
|
+
if (this.savedGroup) this.savedGroup.expanded = expanded;
|
|
7802
|
+
else {
|
|
7803
|
+
this.expanded = expanded;
|
|
7804
|
+
this.displayChanged();
|
|
7805
|
+
}
|
|
7588
7806
|
}
|
|
7589
7807
|
if (want("pageSize") && typeof state.pageSize === "number") this.setPageSize(state.pageSize);
|
|
7590
7808
|
if (want("page") && typeof state.page === "number") this.setPage(state.page);
|
|
@@ -8108,7 +8326,7 @@ var Grid = class {
|
|
|
8108
8326
|
if (all2.length > 1) {
|
|
8109
8327
|
const sameCols = all2.every((x) => x.c1 === all2[0].c1 && x.c2 === all2[0].c2);
|
|
8110
8328
|
const sameRows = all2.every((x) => x.r1 === all2[0].r1 && x.r2 === all2[0].r2);
|
|
8111
|
-
if (
|
|
8329
|
+
if (this.rangesStack(all2)) {
|
|
8112
8330
|
const parts = [...all2].sort((x, y) => sameCols ? x.r1 - y.r1 : x.c1 - y.c1);
|
|
8113
8331
|
const blocks = parts.map((p) => this.rangeMatrix(p));
|
|
8114
8332
|
const matrix2 = sameCols ? blocks.flat() : blocks[0].map((_, ri) => blocks.flatMap((bl) => bl[ri] ?? []));
|
|
@@ -8139,6 +8357,10 @@ var Grid = class {
|
|
|
8139
8357
|
if (headers) matrix.unshift(cols.map((c) => c.def.header ?? c.def.field ?? c.id));
|
|
8140
8358
|
return toTSV(matrix);
|
|
8141
8359
|
}
|
|
8360
|
+
/** Like Excel: ranges stack when they share columns or rows; otherwise only the active one is copied. */
|
|
8361
|
+
rangesStack(all2) {
|
|
8362
|
+
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));
|
|
8363
|
+
}
|
|
8142
8364
|
rangeHeaders(b) {
|
|
8143
8365
|
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
8366
|
}
|
|
@@ -8154,24 +8376,21 @@ var Grid = class {
|
|
|
8154
8376
|
return out;
|
|
8155
8377
|
}
|
|
8156
8378
|
copyCell(def, row, code) {
|
|
8157
|
-
|
|
8379
|
+
return this.copyAllowed(def, row) ? copyValue(def, getCellValue(def, row), code) : "";
|
|
8380
|
+
}
|
|
8381
|
+
copyAllowed(def, row) {
|
|
8158
8382
|
const rule = def.copyable;
|
|
8159
|
-
if (rule
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
} catch {
|
|
8165
|
-
ok = false;
|
|
8166
|
-
}
|
|
8167
|
-
if (!ok) return "";
|
|
8383
|
+
if (typeof rule !== "function") return rule !== false;
|
|
8384
|
+
try {
|
|
8385
|
+
return rule({ value: getCellValue(def, row), row, column: def }) !== false;
|
|
8386
|
+
} catch {
|
|
8387
|
+
return false;
|
|
8168
8388
|
}
|
|
8169
|
-
return copyValue(def, value, code);
|
|
8170
8389
|
}
|
|
8171
8390
|
/** Copy, then clear the editable cells of the selection. */
|
|
8172
8391
|
cutToClipboard() {
|
|
8173
8392
|
const text2 = this.copyToClipboard(false);
|
|
8174
|
-
if (text2) this.clearCells();
|
|
8393
|
+
if (text2) this.clearCells(true);
|
|
8175
8394
|
return text2;
|
|
8176
8395
|
}
|
|
8177
8396
|
copyToClipboard(headers, rows = false) {
|
|
@@ -8430,10 +8649,14 @@ var Grid = class {
|
|
|
8430
8649
|
this.writeCell(changes, node.data, def.field, colId, rowIndex, value);
|
|
8431
8650
|
this.endCellTx(changes);
|
|
8432
8651
|
}
|
|
8433
|
-
/** Delete/Backspace: clear editable cells in the range, or the focused cell. */
|
|
8434
|
-
clearCells() {
|
|
8652
|
+
/** Delete/Backspace: clear editable cells in the range, or the focused cell. `cut` clears only what a copy takes. */
|
|
8653
|
+
clearCells(cut = false) {
|
|
8435
8654
|
const displayed = this.columns.displayed();
|
|
8436
|
-
|
|
8655
|
+
let all2 = this.allRangeBounds();
|
|
8656
|
+
if (cut && !this.rangesStack(all2)) {
|
|
8657
|
+
const active = this.rangeBounds();
|
|
8658
|
+
all2 = active ? [active] : [];
|
|
8659
|
+
}
|
|
8437
8660
|
const f = this.store.state.focus;
|
|
8438
8661
|
const fi = f ? this.columns.getColumn(f.colId)?.index : void 0;
|
|
8439
8662
|
const boxes = all2.length ? all2 : f && fi !== void 0 ? [{ r1: f.rowIndex, r2: f.rowIndex, c1: fi, c2: fi }] : [];
|
|
@@ -8443,7 +8666,7 @@ var Grid = class {
|
|
|
8443
8666
|
const node = this.rowModel.getRow(r);
|
|
8444
8667
|
for (let c = box.c1; c <= box.c2; c++) {
|
|
8445
8668
|
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);
|
|
8669
|
+
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
8670
|
}
|
|
8448
8671
|
}
|
|
8449
8672
|
this.endCellTx(changes);
|
|
@@ -8545,8 +8768,9 @@ var Grid = class {
|
|
|
8545
8768
|
const m = this.getModifiedRows();
|
|
8546
8769
|
return m.created.length + m.updated.length + m.deleted.length > 0;
|
|
8547
8770
|
}
|
|
8548
|
-
commitChanges() {
|
|
8549
|
-
this.tracker.
|
|
8771
|
+
commitChanges(saved) {
|
|
8772
|
+
if (saved && saved.rows instanceof Map) this.tracker.commit(saved.rows, Array.isArray(saved.deleted) ? saved.deleted : []);
|
|
8773
|
+
else this.tracker.reset();
|
|
8550
8774
|
this.renderer.invalidate({ rows: true });
|
|
8551
8775
|
}
|
|
8552
8776
|
revertChanges() {
|
|
@@ -8950,7 +9174,7 @@ var Grid = class {
|
|
|
8950
9174
|
getModifiedRows: guard(() => this.getModifiedRows()),
|
|
8951
9175
|
getChanges: guard(() => this.getChanges()),
|
|
8952
9176
|
hasChanges: guard(() => this.hasChanges()),
|
|
8953
|
-
commitChanges: guard(() => this.commitChanges()),
|
|
9177
|
+
commitChanges: guard((saved) => this.commitChanges(saved)),
|
|
8954
9178
|
revertChanges: guard(() => this.revertChanges()),
|
|
8955
9179
|
undo: guard(() => this.undo()),
|
|
8956
9180
|
redo: guard(() => this.redo()),
|
|
@@ -9021,26 +9245,32 @@ function exportWorkbook(filename, sheets) {
|
|
|
9021
9245
|
var headersOf = (h) => typeof h === "function" ? h() : h ?? {};
|
|
9022
9246
|
async function send(url, method, body, opts) {
|
|
9023
9247
|
let target = url;
|
|
9248
|
+
const where = url.split(/[?#]/)[0];
|
|
9024
9249
|
const init = { method, credentials: opts.credentials ?? "same-origin", headers: { Accept: "application/json", ...headersOf(opts.headers) } };
|
|
9025
9250
|
if (method === "GET") {
|
|
9026
9251
|
const q = new URLSearchParams();
|
|
9027
|
-
for (const [k, v] of Object.entries(body)) q.set(k, typeof v === "string" ? v : JSON.stringify(v));
|
|
9252
|
+
for (const [k, v] of Object.entries(body)) if (v !== void 0) q.set(k, typeof v === "string" ? v : JSON.stringify(v));
|
|
9028
9253
|
target += (url.includes("?") ? "&" : "?") + q.toString();
|
|
9029
9254
|
} else {
|
|
9030
9255
|
init.headers["Content-Type"] = "application/json";
|
|
9031
9256
|
init.body = JSON.stringify(body);
|
|
9032
9257
|
}
|
|
9033
9258
|
const res = await fetch(target, init);
|
|
9034
|
-
if (!res.ok) throw new Error(`HTTP ${res.status} from ${
|
|
9259
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} from ${where}`);
|
|
9035
9260
|
const text2 = await res.text();
|
|
9036
|
-
|
|
9261
|
+
if (!text2) return null;
|
|
9262
|
+
try {
|
|
9263
|
+
return JSON.parse(text2);
|
|
9264
|
+
} catch {
|
|
9265
|
+
throw new Error(`HTTP ${res.status} from ${where}: the response is not JSON`);
|
|
9266
|
+
}
|
|
9037
9267
|
}
|
|
9038
9268
|
function readRows(json) {
|
|
9039
9269
|
if (Array.isArray(json)) return { rows: json };
|
|
9040
9270
|
const o = json ?? {};
|
|
9041
9271
|
const rows = [o.rows, o.list, o.content, o.data, o.items, o.resultList].find(Array.isArray);
|
|
9042
9272
|
const total = [o.total, o.totalCount, o.totalElements, o.totCnt, o.count].find((v) => typeof v === "number");
|
|
9043
|
-
return { rows: rows ?? [], total };
|
|
9273
|
+
return { rows: (rows ?? []).filter((r) => r !== null && typeof r === "object"), total };
|
|
9044
9274
|
}
|
|
9045
9275
|
function restDatasource(url, options = {}) {
|
|
9046
9276
|
return {
|
|
@@ -9054,6 +9284,10 @@ function restDatasource(url, options = {}) {
|
|
|
9054
9284
|
sort: p.sort,
|
|
9055
9285
|
filters: p.filters,
|
|
9056
9286
|
quickFilter: p.quickFilter,
|
|
9287
|
+
// only when set (undefined is left out of the request)
|
|
9288
|
+
advancedFilter: p.advancedFilter ?? void 0,
|
|
9289
|
+
groupBy: p.groupBy,
|
|
9290
|
+
groupKeys: p.groupKeys,
|
|
9057
9291
|
...options.params?.() ?? {}
|
|
9058
9292
|
};
|
|
9059
9293
|
const json = await send(url, options.method ?? "POST", body, options);
|
|
@@ -9061,12 +9295,20 @@ function restDatasource(url, options = {}) {
|
|
|
9061
9295
|
}
|
|
9062
9296
|
};
|
|
9063
9297
|
}
|
|
9064
|
-
|
|
9065
|
-
|
|
9298
|
+
var saving = /* @__PURE__ */ new WeakMap();
|
|
9299
|
+
function saveChanges(grid, url, options = {}) {
|
|
9300
|
+
const pending = saving.get(grid);
|
|
9301
|
+
if (pending) return pending;
|
|
9302
|
+
if (!grid.hasChanges()) return Promise.resolve(null);
|
|
9066
9303
|
const { created, updated, deleted } = grid.getModifiedRows();
|
|
9067
|
-
const
|
|
9068
|
-
|
|
9069
|
-
|
|
9304
|
+
const rows = new Map([...created, ...updated].map((r) => [r, { ...r }]));
|
|
9305
|
+
const run = (async () => {
|
|
9306
|
+
const json = await send(url, "POST", { created, updated, deleted, ...options.params?.() ?? {} }, options);
|
|
9307
|
+
grid.commitChanges({ rows, deleted });
|
|
9308
|
+
return json;
|
|
9309
|
+
})().finally(() => saving.delete(grid));
|
|
9310
|
+
saving.set(grid, run);
|
|
9311
|
+
return run;
|
|
9070
9312
|
}
|
|
9071
9313
|
async function loadData(grid, url, options = {}) {
|
|
9072
9314
|
const { rows } = await restDatasource(url, options).getRows({ start: 0, end: options.maxRows ?? 1e4, sort: [], filters: {}, quickFilter: "" });
|