@freegridjs/core 0.7.1 → 0.8.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/LICENSE +5 -4
- package/README.md +2 -2
- package/dist/cdn/freegrid.css +45 -1
- package/dist/cdn/freegrid.esm.js +7 -7
- package/dist/cdn/freegrid.min.js +8 -8
- package/dist/index.d.ts +15 -8
- package/dist/index.js +1345 -272
- 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/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/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +1 -0
- package/dist/styles.css +45 -1
- package/dist/{types-CusLVNhk.d.ts → types-e45suTSB.d.ts} +151 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -50,6 +50,15 @@ function filterTypeOf(def) {
|
|
|
50
50
|
return "text";
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
+
function cellEditable(def, row, rowIndex) {
|
|
54
|
+
const e = def.editable;
|
|
55
|
+
if (typeof e !== "function") return !!e;
|
|
56
|
+
try {
|
|
57
|
+
return !!e({ value: def.valueGetter ? def.valueGetter(row) : def.field ? row[def.field] : void 0, row, rowIndex, column: def });
|
|
58
|
+
} catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
53
62
|
var isSortable = (def) => def.id !== SELECT_COL_ID && def.sortable !== false;
|
|
54
63
|
var isResizable = (def) => def.id !== SELECT_COL_ID && def.resizable !== false;
|
|
55
64
|
function clampWidth(def, w) {
|
|
@@ -387,8 +396,8 @@ function quickTokens(q) {
|
|
|
387
396
|
const t = q.trim().toLowerCase();
|
|
388
397
|
return t ? t.split(/\s+/) : [];
|
|
389
398
|
}
|
|
390
|
-
function matchesQuick(tokens,
|
|
391
|
-
return tokens.every((t) =>
|
|
399
|
+
function matchesQuick(tokens, text2) {
|
|
400
|
+
return tokens.every((t) => text2.includes(t));
|
|
392
401
|
}
|
|
393
402
|
|
|
394
403
|
// src/i18n/format.ts
|
|
@@ -445,13 +454,13 @@ function create(type, format, locale) {
|
|
|
445
454
|
}
|
|
446
455
|
case "date": {
|
|
447
456
|
if (locale.toLowerCase().startsWith("ko") && (format === void 0 || format === "date" || format === "datetime")) {
|
|
448
|
-
const
|
|
457
|
+
const pad3 = (n) => String(n).padStart(2, "0");
|
|
449
458
|
const withTime = format === "datetime";
|
|
450
459
|
return (v) => {
|
|
451
460
|
const d = toDate(v);
|
|
452
461
|
if (!d) return isBlank(v) ? "" : String(v);
|
|
453
|
-
const day = `${d.getFullYear()}-${
|
|
454
|
-
return withTime ? `${day} ${
|
|
462
|
+
const day = `${d.getFullYear()}-${pad3(d.getMonth() + 1)}-${pad3(d.getDate())}`;
|
|
463
|
+
return withTime ? `${day} ${pad3(d.getHours())}:${pad3(d.getMinutes())}` : day;
|
|
455
464
|
};
|
|
456
465
|
}
|
|
457
466
|
let opts = DATE_PRESETS.date;
|
|
@@ -482,6 +491,216 @@ function getFormatter(def, locale) {
|
|
|
482
491
|
return fmt;
|
|
483
492
|
}
|
|
484
493
|
|
|
494
|
+
// src/formula.ts
|
|
495
|
+
var FormulaError = class extends Error {
|
|
496
|
+
constructor(code) {
|
|
497
|
+
super(code);
|
|
498
|
+
this.code = code;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
function tokenize(src) {
|
|
502
|
+
const out = [];
|
|
503
|
+
let i = 0;
|
|
504
|
+
while (i < src.length) {
|
|
505
|
+
const c = src[i];
|
|
506
|
+
if (/\s/.test(c)) {
|
|
507
|
+
i++;
|
|
508
|
+
} else if (/[0-9.]/.test(c)) {
|
|
509
|
+
const m = /^(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/i.exec(src.slice(i));
|
|
510
|
+
if (!m) throw new FormulaError("#ERROR!");
|
|
511
|
+
out.push({ t: "num", v: Number(m[0]) });
|
|
512
|
+
i += m[0].length;
|
|
513
|
+
} else if (c === '"') {
|
|
514
|
+
let s = "";
|
|
515
|
+
i++;
|
|
516
|
+
for (; ; ) {
|
|
517
|
+
if (i >= src.length) throw new FormulaError("#ERROR!");
|
|
518
|
+
if (src[i] === '"') {
|
|
519
|
+
if (src[i + 1] === '"') {
|
|
520
|
+
s += '"';
|
|
521
|
+
i += 2;
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
i++;
|
|
525
|
+
break;
|
|
526
|
+
}
|
|
527
|
+
s += src[i++];
|
|
528
|
+
}
|
|
529
|
+
out.push({ t: "str", v: s });
|
|
530
|
+
} else if (c === "[") {
|
|
531
|
+
const end = src.indexOf("]", i);
|
|
532
|
+
if (end < 0) throw new FormulaError("#ERROR!");
|
|
533
|
+
out.push({ t: "id", v: src.slice(i + 1, end) });
|
|
534
|
+
i = end + 1;
|
|
535
|
+
} else if (/[A-Za-z_À-]/.test(c)) {
|
|
536
|
+
const m = /^[A-Za-z_À-][\wÀ-]*/.exec(src.slice(i));
|
|
537
|
+
out.push({ t: "id", v: m[0] });
|
|
538
|
+
i += m[0].length;
|
|
539
|
+
} else {
|
|
540
|
+
const two = src.slice(i, i + 2);
|
|
541
|
+
if (two === "<>" || two === "<=" || two === ">=") {
|
|
542
|
+
out.push({ t: "op", v: two });
|
|
543
|
+
i += 2;
|
|
544
|
+
} else if ("+-*/%^&=<>(),".includes(c)) {
|
|
545
|
+
out.push({ t: "op", v: c });
|
|
546
|
+
i++;
|
|
547
|
+
} else throw new FormulaError("#ERROR!");
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return out;
|
|
551
|
+
}
|
|
552
|
+
var isBlankValue = (v) => v === null || v === void 0 || v === "";
|
|
553
|
+
function num(v) {
|
|
554
|
+
if (typeof v === "number") return v;
|
|
555
|
+
if (typeof v === "boolean") return v ? 1 : 0;
|
|
556
|
+
if (isBlankValue(v)) return 0;
|
|
557
|
+
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
558
|
+
throw new FormulaError("#VALUE!");
|
|
559
|
+
}
|
|
560
|
+
var text = (v) => isBlankValue(v) ? "" : typeof v === "boolean" ? v ? "TRUE" : "FALSE" : String(v);
|
|
561
|
+
function round(x, digits) {
|
|
562
|
+
const d = Math.trunc(digits);
|
|
563
|
+
const r = Number(`${Math.round(Number(`${Math.abs(x)}e${d}`))}e${-d}`);
|
|
564
|
+
return x < 0 ? -r : r;
|
|
565
|
+
}
|
|
566
|
+
var FUNCS = {
|
|
567
|
+
ROUND: (a) => round(num(a[0]), a.length > 1 ? num(a[1]) : 0),
|
|
568
|
+
ABS: (a) => Math.abs(num(a[0])),
|
|
569
|
+
MIN: (a) => a.length ? Math.min(...a.map(num)) : 0,
|
|
570
|
+
MAX: (a) => a.length ? Math.max(...a.map(num)) : 0,
|
|
571
|
+
SUM: (a) => a.reduce((s, v) => s + num(v), 0),
|
|
572
|
+
AVG: (a) => {
|
|
573
|
+
if (!a.length) throw new FormulaError("#DIV/0!");
|
|
574
|
+
return a.reduce((s, v) => s + num(v), 0) / a.length;
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
function compare(a, b) {
|
|
578
|
+
const bothNum = (typeof a === "number" || isBlankValue(a)) && (typeof b === "number" || isBlankValue(b));
|
|
579
|
+
if (bothNum) return num(a) - num(b);
|
|
580
|
+
return text(a).localeCompare(text(b), void 0, { sensitivity: "accent" });
|
|
581
|
+
}
|
|
582
|
+
var Parser = class {
|
|
583
|
+
constructor(tokens, resolve) {
|
|
584
|
+
this.tokens = tokens;
|
|
585
|
+
this.resolve = resolve;
|
|
586
|
+
this.i = 0;
|
|
587
|
+
}
|
|
588
|
+
parse() {
|
|
589
|
+
const v = this.comparison();
|
|
590
|
+
if (this.i < this.tokens.length) throw new FormulaError("#ERROR!");
|
|
591
|
+
return v;
|
|
592
|
+
}
|
|
593
|
+
peek(v) {
|
|
594
|
+
const t = this.tokens[this.i];
|
|
595
|
+
return !!t && t.t === "op" && (v === void 0 || t.v === v);
|
|
596
|
+
}
|
|
597
|
+
take(v) {
|
|
598
|
+
if (!this.peek(v)) return false;
|
|
599
|
+
this.i++;
|
|
600
|
+
return true;
|
|
601
|
+
}
|
|
602
|
+
comparison() {
|
|
603
|
+
const a = this.concat();
|
|
604
|
+
for (const op of ["=", "<>", "<=", ">=", "<", ">"]) {
|
|
605
|
+
if (this.take(op)) {
|
|
606
|
+
const c = compare(a, this.concat());
|
|
607
|
+
return op === "=" ? c === 0 : op === "<>" ? c !== 0 : op === "<" ? c < 0 : op === ">" ? c > 0 : op === "<=" ? c <= 0 : c >= 0;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return a;
|
|
611
|
+
}
|
|
612
|
+
concat() {
|
|
613
|
+
let v = this.additive();
|
|
614
|
+
while (this.take("&")) v = text(v) + text(this.additive());
|
|
615
|
+
return v;
|
|
616
|
+
}
|
|
617
|
+
additive() {
|
|
618
|
+
let v = this.multiplicative();
|
|
619
|
+
for (; ; ) {
|
|
620
|
+
if (this.take("+")) v = num(v) + num(this.multiplicative());
|
|
621
|
+
else if (this.take("-")) v = num(v) - num(this.multiplicative());
|
|
622
|
+
else return v;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
multiplicative() {
|
|
626
|
+
let v = this.unary();
|
|
627
|
+
for (; ; ) {
|
|
628
|
+
if (this.take("*")) v = num(v) * num(this.unary());
|
|
629
|
+
else if (this.take("/") || this.take("%")) {
|
|
630
|
+
const op = this.tokens[this.i - 1].v;
|
|
631
|
+
const d = num(this.unary());
|
|
632
|
+
if (d === 0) throw new FormulaError("#DIV/0!");
|
|
633
|
+
v = op === "/" ? num(v) / d : num(v) % d;
|
|
634
|
+
} else return v;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
unary() {
|
|
638
|
+
if (this.take("-")) return -num(this.unary());
|
|
639
|
+
if (this.take("+")) return num(this.unary());
|
|
640
|
+
return this.power();
|
|
641
|
+
}
|
|
642
|
+
power() {
|
|
643
|
+
const base = this.primary();
|
|
644
|
+
if (this.take("^")) return num(base) ** num(this.unary());
|
|
645
|
+
return base;
|
|
646
|
+
}
|
|
647
|
+
primary() {
|
|
648
|
+
const t = this.tokens[this.i++];
|
|
649
|
+
if (!t) throw new FormulaError("#ERROR!");
|
|
650
|
+
if (t.t === "num" || t.t === "str") return t.v;
|
|
651
|
+
if (t.t === "op") {
|
|
652
|
+
if (t.v !== "(") throw new FormulaError("#ERROR!");
|
|
653
|
+
const v = this.comparison();
|
|
654
|
+
if (!this.take(")")) throw new FormulaError("#ERROR!");
|
|
655
|
+
return v;
|
|
656
|
+
}
|
|
657
|
+
const name = t.v;
|
|
658
|
+
if (this.peek("(")) {
|
|
659
|
+
this.i++;
|
|
660
|
+
const args = [];
|
|
661
|
+
if (!this.take(")")) {
|
|
662
|
+
do
|
|
663
|
+
args.push(this.comparison());
|
|
664
|
+
while (this.take(","));
|
|
665
|
+
if (!this.take(")")) throw new FormulaError("#ERROR!");
|
|
666
|
+
}
|
|
667
|
+
const upper = name.toUpperCase();
|
|
668
|
+
if (upper === "IF") {
|
|
669
|
+
if (args.length < 2) throw new FormulaError("#ERROR!");
|
|
670
|
+
const cond = typeof args[0] === "boolean" ? args[0] : num(args[0]) !== 0;
|
|
671
|
+
return cond ? args[1] : args.length > 2 ? args[2] : false;
|
|
672
|
+
}
|
|
673
|
+
const fn = Object.hasOwn(FUNCS, upper) ? FUNCS[upper] : void 0;
|
|
674
|
+
if (!fn) throw new FormulaError("#NAME?");
|
|
675
|
+
return fn(args);
|
|
676
|
+
}
|
|
677
|
+
if (/^(true|false)$/i.test(name)) return name.toLowerCase() === "true";
|
|
678
|
+
return this.resolve(name);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
var MAX_DEPTH = 32;
|
|
682
|
+
function evaluateFormula(formula, row, seen2 = /* @__PURE__ */ new Set(), depth = 0) {
|
|
683
|
+
try {
|
|
684
|
+
const resolve = (name) => {
|
|
685
|
+
if (!row || !Object.hasOwn(row, name)) throw new FormulaError("#REF!");
|
|
686
|
+
const v2 = row[name];
|
|
687
|
+
if (typeof v2 === "string" && v2.startsWith("=")) {
|
|
688
|
+
if (seen2.has(name) || depth >= MAX_DEPTH) throw new FormulaError("#CYCLE!");
|
|
689
|
+
const r = evaluateFormula(v2, row, /* @__PURE__ */ new Set([...seen2, name]), depth + 1);
|
|
690
|
+
if (typeof r === "string" && /^#[A-Z/0]+[!?]$/.test(r)) throw new FormulaError(r);
|
|
691
|
+
return r;
|
|
692
|
+
}
|
|
693
|
+
return v2;
|
|
694
|
+
};
|
|
695
|
+
const v = new Parser(tokenize(formula.slice(1)), resolve).parse();
|
|
696
|
+
if (typeof v === "number" && !Number.isFinite(v)) return "#NUM!";
|
|
697
|
+
return v;
|
|
698
|
+
} catch (e) {
|
|
699
|
+
return e instanceof FormulaError ? e.code : "#ERROR!";
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
var isFormula = (v) => typeof v === "string" && v.length > 1 && v.startsWith("=");
|
|
703
|
+
|
|
485
704
|
// src/rows/values.ts
|
|
486
705
|
function getCellValue(def, row) {
|
|
487
706
|
if (def.valueGetter) {
|
|
@@ -492,7 +711,9 @@ function getCellValue(def, row) {
|
|
|
492
711
|
return void 0;
|
|
493
712
|
}
|
|
494
713
|
}
|
|
495
|
-
|
|
714
|
+
if (def.field == null) return void 0;
|
|
715
|
+
const raw = row[def.field];
|
|
716
|
+
return def.formulas && isFormula(raw) ? evaluateFormula(raw, row, /* @__PURE__ */ new Set([def.field])) : raw;
|
|
496
717
|
}
|
|
497
718
|
|
|
498
719
|
// src/summary.ts
|
|
@@ -553,6 +774,57 @@ function computeSummary(defs, rows, columnDef) {
|
|
|
553
774
|
});
|
|
554
775
|
}
|
|
555
776
|
|
|
777
|
+
// src/merge.ts
|
|
778
|
+
function spanOf(fn, def, row, rowIndex, max) {
|
|
779
|
+
if (!fn) return 1;
|
|
780
|
+
let n = 1;
|
|
781
|
+
try {
|
|
782
|
+
n = Math.floor(Number(fn({ value: getCellValue(def, row), row, rowIndex })));
|
|
783
|
+
} catch {
|
|
784
|
+
n = 1;
|
|
785
|
+
}
|
|
786
|
+
return Number.isFinite(n) ? Math.max(1, Math.min(max, n)) : 1;
|
|
787
|
+
}
|
|
788
|
+
function computeSpans(rows, def) {
|
|
789
|
+
const n = rows.length;
|
|
790
|
+
const start = new Int32Array(n);
|
|
791
|
+
const end = new Int32Array(n);
|
|
792
|
+
if (def.rowSpan) {
|
|
793
|
+
for (let i = 0; i < n; ) {
|
|
794
|
+
const len = spanOf(def.rowSpan, def, rows[i], i, n - i);
|
|
795
|
+
for (let k = i; k < i + len; k++) {
|
|
796
|
+
start[k] = i;
|
|
797
|
+
end[k] = i + len - 1;
|
|
798
|
+
}
|
|
799
|
+
i += len;
|
|
800
|
+
}
|
|
801
|
+
return { start, end };
|
|
802
|
+
}
|
|
803
|
+
const same2 = typeof def.merge === "function" ? (a, b) => {
|
|
804
|
+
try {
|
|
805
|
+
return !!def.merge(getCellValue(def, rows[a]), getCellValue(def, rows[b]), rows[a], rows[b]);
|
|
806
|
+
} catch {
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
} : (() => {
|
|
810
|
+
const keys = rows.map((r) => {
|
|
811
|
+
const v = getCellValue(def, r);
|
|
812
|
+
return isBlank(v) ? null : setKey(v);
|
|
813
|
+
});
|
|
814
|
+
return (a, b) => keys[a] !== null && keys[a] === keys[b];
|
|
815
|
+
})();
|
|
816
|
+
let s = 0;
|
|
817
|
+
for (let i = 1; i <= n; i++) {
|
|
818
|
+
if (i < n && same2(i - 1, i)) continue;
|
|
819
|
+
for (let k = s; k < i; k++) {
|
|
820
|
+
start[k] = s;
|
|
821
|
+
end[k] = i - 1;
|
|
822
|
+
}
|
|
823
|
+
s = i;
|
|
824
|
+
}
|
|
825
|
+
return { start, end };
|
|
826
|
+
}
|
|
827
|
+
|
|
556
828
|
// src/viewport.ts
|
|
557
829
|
function rowRange(scrollTop, height, rowHeight, count, overscan = 4) {
|
|
558
830
|
if (count <= 0 || rowHeight <= 0) return { first: 0, last: -1 };
|
|
@@ -601,6 +873,7 @@ function rowRangeByOffsets(offsets, scrollTop, height, count, overscan = 4) {
|
|
|
601
873
|
// src/render/renderer.ts
|
|
602
874
|
var REGIONS2 = ["left", "center", "right"];
|
|
603
875
|
var ROW_OVERSCAN = 6;
|
|
876
|
+
var MAX_COLSPAN_REACH = 8;
|
|
604
877
|
var COL_OVERSCAN = 2;
|
|
605
878
|
var MENU_ICON = '<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M3 4h10M3 8h10M3 12h10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>';
|
|
606
879
|
var CHEVRON = '<svg viewBox="0 0 16 16" width="12" height="12" aria-hidden="true"><path d="M6 3.5 10.5 8 6 12.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>';
|
|
@@ -615,6 +888,8 @@ var Renderer = class {
|
|
|
615
888
|
constructor(host, grid, opts) {
|
|
616
889
|
this.grid = grid;
|
|
617
890
|
this.opts = opts;
|
|
891
|
+
this.activeCellId = "";
|
|
892
|
+
this.idBase = `fg${Math.random().toString(36).slice(2, 8)}`;
|
|
618
893
|
this.painted = { left: /* @__PURE__ */ new Map(), center: /* @__PURE__ */ new Map(), right: /* @__PURE__ */ new Map() };
|
|
619
894
|
this.pool = { left: [], center: [], right: [] };
|
|
620
895
|
this.frame = 0;
|
|
@@ -634,6 +909,9 @@ var Renderer = class {
|
|
|
634
909
|
this.statusEl = null;
|
|
635
910
|
this.statusKey = "";
|
|
636
911
|
this.pagerEl = null;
|
|
912
|
+
/** Card view (narrow screens): scroller and canvas of absolutely placed cards. */
|
|
913
|
+
this.cards = null;
|
|
914
|
+
this.cardMode = false;
|
|
637
915
|
this.pagerKey = "";
|
|
638
916
|
/** Prefix-sum row tops when rows differ in height (detail rows); null when every row is rowHeight. */
|
|
639
917
|
this.offsets = null;
|
|
@@ -641,19 +919,42 @@ var Renderer = class {
|
|
|
641
919
|
this.details = /* @__PURE__ */ new Map();
|
|
642
920
|
/** First non-system displayed column: holds group labels, tree chevrons and detail toggles. */
|
|
643
921
|
this.firstDataId = "";
|
|
922
|
+
/** Measured heights per row id (autoRowHeight); cleared when content or widths change. */
|
|
923
|
+
this.rowHeights = /* @__PURE__ */ new Map();
|
|
924
|
+
this.heightsVersion = 0;
|
|
644
925
|
/** Column holding group labels: the first scrolling data column, so long labels have room. */
|
|
645
926
|
this.groupLabelId = "";
|
|
646
927
|
this.onScroll = () => {
|
|
647
928
|
if (this.frame) cancelAnimationFrame(this.frame);
|
|
648
929
|
this.frame = 0;
|
|
649
930
|
this.render();
|
|
931
|
+
this.grid.events.emit("scroll", this.getScroll());
|
|
650
932
|
};
|
|
651
933
|
this.onSideWheel = (e) => {
|
|
652
934
|
if (!e.deltaY) return;
|
|
653
935
|
e.preventDefault();
|
|
654
936
|
this.scroller.scrollTop += e.deltaY;
|
|
655
937
|
};
|
|
656
|
-
this.
|
|
938
|
+
this.onCardClick = (e) => {
|
|
939
|
+
const target = e.target;
|
|
940
|
+
const card = target.closest(".fg-card");
|
|
941
|
+
if (!card) return;
|
|
942
|
+
const i = Number(card.dataset.row);
|
|
943
|
+
const node = this.grid.rowModel.getRow(i);
|
|
944
|
+
if (!node || node.kind) return;
|
|
945
|
+
if (target.closest(".fg-card-check")) {
|
|
946
|
+
this.grid.selectFromClick(i, true, false);
|
|
947
|
+
this.invalidate({ rows: true });
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
const colId = target.closest(".fg-card-value")?.dataset.col;
|
|
951
|
+
const col = colId ? this.grid.columns.getColumn(colId) : void 0;
|
|
952
|
+
if (!col) return;
|
|
953
|
+
this.grid.events.emit("cellClick", { row: node.data, rowIndex: i, colId: col.id, value: getCellValue(col.def, node.data), event: e });
|
|
954
|
+
};
|
|
955
|
+
this.root = el("div", "", { tabindex: "-1" });
|
|
956
|
+
this.root.addEventListener("focus", () => this.gridEl.focus({ preventScroll: true }));
|
|
957
|
+
this.gridEl = el("div", "fg-grid", { role: "grid", tabindex: "0" });
|
|
657
958
|
this.headerEl = el("div", "fg-header", { role: "rowgroup" });
|
|
658
959
|
this.bodyEl = el("div", "fg-body", { role: "rowgroup" });
|
|
659
960
|
const panes = (cls) => ({
|
|
@@ -663,6 +964,7 @@ var Renderer = class {
|
|
|
663
964
|
});
|
|
664
965
|
this.headerPanes = panes("fg-header-pane");
|
|
665
966
|
this.headerInners = panes("fg-header-inner");
|
|
967
|
+
for (const r of REGIONS2) this.headerInners[r].setAttribute("role", "row");
|
|
666
968
|
this.bodyPanes = panes("fg-body-pane");
|
|
667
969
|
this.canvases = panes("fg-canvas");
|
|
668
970
|
for (const r of REGIONS2) {
|
|
@@ -674,9 +976,10 @@ var Renderer = class {
|
|
|
674
976
|
this.bodyEl.append(this.bodyPanes[r]);
|
|
675
977
|
}
|
|
676
978
|
this.scroller = this.bodyPanes.center;
|
|
677
|
-
this.overlay = el("div", "fg-overlay", {
|
|
979
|
+
this.overlay = el("div", "fg-overlay", { "aria-live": "polite" });
|
|
678
980
|
this.main = el("div", "fg-main");
|
|
679
|
-
this.
|
|
981
|
+
this.gridEl.append(this.headerEl, this.bodyEl);
|
|
982
|
+
this.main.append(this.gridEl, this.overlay);
|
|
680
983
|
this.root.append(this.main);
|
|
681
984
|
host.append(this.root);
|
|
682
985
|
this.applyTheme();
|
|
@@ -684,6 +987,19 @@ var Renderer = class {
|
|
|
684
987
|
this.bodyPanes.left.addEventListener("wheel", this.onSideWheel, { passive: false });
|
|
685
988
|
this.bodyPanes.right.addEventListener("wheel", this.onSideWheel, { passive: false });
|
|
686
989
|
}
|
|
990
|
+
/** The element that scrolls the rows: the card list when cards are shown. */
|
|
991
|
+
get activeScroller() {
|
|
992
|
+
return this.cards?.scroller ?? this.scroller;
|
|
993
|
+
}
|
|
994
|
+
getScroll() {
|
|
995
|
+
const s = this.activeScroller;
|
|
996
|
+
return { top: s.scrollTop, left: s.scrollLeft };
|
|
997
|
+
}
|
|
998
|
+
setScroll(p) {
|
|
999
|
+
const s = this.activeScroller;
|
|
1000
|
+
if (typeof p?.top === "number" && Number.isFinite(p.top)) s.scrollTop = Math.max(0, p.top);
|
|
1001
|
+
if (typeof p?.left === "number" && Number.isFinite(p.left)) s.scrollLeft = Math.max(0, p.left);
|
|
1002
|
+
}
|
|
687
1003
|
applyFont(size, family) {
|
|
688
1004
|
if (size) this.root.style.setProperty("--fg-font-size", `${size}px`);
|
|
689
1005
|
else this.root.style.removeProperty("--fg-font-size");
|
|
@@ -691,7 +1007,13 @@ var Renderer = class {
|
|
|
691
1007
|
else this.root.style.removeProperty("--fg-font");
|
|
692
1008
|
}
|
|
693
1009
|
applyTheme() {
|
|
694
|
-
|
|
1010
|
+
const rtl = this.grid.options.direction === "rtl";
|
|
1011
|
+
this.root.className = `fg-root fg-theme-${this.grid.store.state.theme}${rtl ? " fg-rtl" : ""}${this.cardMode ? " fg-card-mode" : ""}`;
|
|
1012
|
+
this.root.setAttribute("dir", rtl ? "rtl" : "ltr");
|
|
1013
|
+
}
|
|
1014
|
+
/** Right-to-left layout: the main area is mirrored, so screen deltas along x flip sign. */
|
|
1015
|
+
get rtl() {
|
|
1016
|
+
return this.grid.options.direction === "rtl";
|
|
695
1017
|
}
|
|
696
1018
|
invalidate(what = {}) {
|
|
697
1019
|
if (what.header) this.headerDirty = true;
|
|
@@ -714,10 +1036,20 @@ var Renderer = class {
|
|
|
714
1036
|
this.version++;
|
|
715
1037
|
}
|
|
716
1038
|
const count = rowModel.getRowCount();
|
|
717
|
-
const
|
|
1039
|
+
const auto = !!this.grid.options.autoRowHeight && !rowModel.server;
|
|
1040
|
+
const lay = rowModel.layout(rh, this.detailHeight(), auto ? this.rowHeights : void 0, this.heightsVersion);
|
|
718
1041
|
this.offsets = lay.uniform ? null : lay.offsets;
|
|
719
1042
|
this.firstDataId = columns.displayed().find((c) => !isSystemCol(c.id))?.id ?? "";
|
|
720
1043
|
this.groupLabelId = columns.region("center").find((c) => !isSystemCol(c.id))?.id ?? this.firstDataId;
|
|
1044
|
+
if (this.useCards()) {
|
|
1045
|
+
this.renderCards(count);
|
|
1046
|
+
this.renderStatusBar(rowModel.dataCount());
|
|
1047
|
+
this.renderPager();
|
|
1048
|
+
this.orderSections(null);
|
|
1049
|
+
this.overlay.hidden = count > 0;
|
|
1050
|
+
this.overlay.textContent = count === 0 && rowModel.loading() ? store.state.locale.loading : store.state.locale.noRows;
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
721
1053
|
const summary = this.grid.summary();
|
|
722
1054
|
const sumRows = summary ? summary.opts.rows.length : 0;
|
|
723
1055
|
const sumPos = summary?.opts.position ?? "bottom";
|
|
@@ -738,14 +1070,18 @@ var Renderer = class {
|
|
|
738
1070
|
}
|
|
739
1071
|
const hScrollbar = this.scroller.offsetHeight - this.scroller.clientHeight;
|
|
740
1072
|
this.bodyPanes.left.style.marginBottom = this.bodyPanes.right.style.marginBottom = `${hScrollbar}px`;
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1073
|
+
const g = this.gridEl;
|
|
1074
|
+
g.setAttribute("role", options.treeData || this.grid.getGroupBy().length ? "treegrid" : "grid");
|
|
1075
|
+
g.setAttribute("aria-rowcount", String(count + columns.depth));
|
|
1076
|
+
g.setAttribute("aria-colcount", String(columns.displayed().length));
|
|
1077
|
+
if (options.rowSelection === "multiple") g.setAttribute("aria-multiselectable", "true");
|
|
744
1078
|
if (this.headerDirty) {
|
|
745
1079
|
this.headerDirty = false;
|
|
746
1080
|
this.renderHeader();
|
|
747
1081
|
}
|
|
748
1082
|
this.renderRows(count);
|
|
1083
|
+
this.updateActiveCell();
|
|
1084
|
+
if (auto) this.measureRows();
|
|
749
1085
|
this.renderSummary(count, summary && summary.opts.rows, summary?.values ?? [], sumPinned, sumPos);
|
|
750
1086
|
this.pinnedTop = this.renderPinned(this.pinnedTop, "top", this.grid.pinnedRows.top);
|
|
751
1087
|
this.pinnedBottom = this.renderPinned(this.pinnedBottom, "bottom", this.grid.pinnedRows.bottom);
|
|
@@ -765,11 +1101,12 @@ var Renderer = class {
|
|
|
765
1101
|
/** Place optional sections around the body without ever moving the body (which would reset its scroll). */
|
|
766
1102
|
orderSections(summaryPos) {
|
|
767
1103
|
const tops = [summaryPos === "top" ? this.summaryEl : null, this.pinnedTop?.el ?? null].filter((x) => !!x);
|
|
768
|
-
const bottoms = [this.pinnedBottom?.el ?? null, summaryPos === "bottom" ? this.summaryEl : null
|
|
769
|
-
|
|
1104
|
+
const bottoms = [this.pinnedBottom?.el ?? null, summaryPos === "bottom" ? this.summaryEl : null].filter((x) => !!x);
|
|
1105
|
+
const after = [this.statusEl, this.pagerEl].filter((x) => !!x);
|
|
1106
|
+
for (const t of tops) if (t.nextElementSibling !== this.bodyEl && !tops.includes(t.nextElementSibling)) this.gridEl.insertBefore(t, this.bodyEl);
|
|
770
1107
|
let ref = this.bodyEl;
|
|
771
1108
|
for (let i = tops.length - 1; i >= 0; i--) {
|
|
772
|
-
if (ref.previousElementSibling !== tops[i]) this.
|
|
1109
|
+
if (ref.previousElementSibling !== tops[i]) this.gridEl.insertBefore(tops[i], ref);
|
|
773
1110
|
ref = tops[i];
|
|
774
1111
|
}
|
|
775
1112
|
ref = this.bodyEl;
|
|
@@ -777,6 +1114,25 @@ var Renderer = class {
|
|
|
777
1114
|
if (ref.nextElementSibling !== b) ref.after(b);
|
|
778
1115
|
ref = b;
|
|
779
1116
|
}
|
|
1117
|
+
ref = this.gridEl;
|
|
1118
|
+
for (const b of after) {
|
|
1119
|
+
if (ref.nextElementSibling !== b) ref.after(b);
|
|
1120
|
+
ref = b;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
/** Point aria-activedescendant at the focused cell so screen readers announce it. */
|
|
1124
|
+
updateActiveCell() {
|
|
1125
|
+
const prev = this.activeCellId ? this.root.getRootNode().getElementById?.(this.activeCellId) ?? null : null;
|
|
1126
|
+
const cell = this.gridEl.querySelector(".fg-body .fg-cell-focused, .fg-body .fg-row-group .fg-cell-focused");
|
|
1127
|
+
if (prev && prev !== cell) prev.removeAttribute("id");
|
|
1128
|
+
if (!cell) {
|
|
1129
|
+
this.gridEl.removeAttribute("aria-activedescendant");
|
|
1130
|
+
this.activeCellId = "";
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (!cell.id) cell.id = `${this.idBase}-cell`;
|
|
1134
|
+
this.activeCellId = cell.id;
|
|
1135
|
+
this.gridEl.setAttribute("aria-activedescendant", cell.id);
|
|
780
1136
|
}
|
|
781
1137
|
renderPinned(section, position, rows) {
|
|
782
1138
|
if (!rows.length) {
|
|
@@ -862,7 +1218,26 @@ var Renderer = class {
|
|
|
862
1218
|
return cell;
|
|
863
1219
|
}
|
|
864
1220
|
const label = el("span", "fg-header-label");
|
|
1221
|
+
const hdef = c.node.def;
|
|
865
1222
|
label.textContent = c.label;
|
|
1223
|
+
if (hdef.headerRenderer) {
|
|
1224
|
+
try {
|
|
1225
|
+
const out = hdef.headerRenderer({ header: c.label, column: hdef });
|
|
1226
|
+
if (typeof out === "string") label.innerHTML = out;
|
|
1227
|
+
else label.replaceChildren(out);
|
|
1228
|
+
} catch {
|
|
1229
|
+
warn(`headerRenderer of column "${c.id}" threw; showing the plain header`);
|
|
1230
|
+
label.textContent = c.label;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (hdef.headerClass) {
|
|
1234
|
+
try {
|
|
1235
|
+
const extra = typeof hdef.headerClass === "function" ? hdef.headerClass(hdef) : hdef.headerClass;
|
|
1236
|
+
if (extra) cell.classList.add(...extra.split(/\s+/).filter(Boolean));
|
|
1237
|
+
} catch {
|
|
1238
|
+
warn(`headerClass of column "${c.id}" threw`);
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
866
1241
|
cell.append(label);
|
|
867
1242
|
if (c.isGroup || !c.column) return cell;
|
|
868
1243
|
const def = c.node.def;
|
|
@@ -892,26 +1267,27 @@ var Renderer = class {
|
|
|
892
1267
|
return cell;
|
|
893
1268
|
}
|
|
894
1269
|
selectAllBox(locale) {
|
|
895
|
-
const rows = this.grid.
|
|
1270
|
+
const rows = this.grid.selectableRows();
|
|
896
1271
|
const sel = this.grid.store.state.selected;
|
|
897
1272
|
let n = 0;
|
|
898
1273
|
for (const r of rows) if (sel.has(r.id)) n++;
|
|
899
1274
|
const box = el("input", "fg-checkbox fg-select-all", { type: "checkbox", tabindex: "-1", "aria-label": locale.selectAllRows });
|
|
900
1275
|
box.checked = rows.length > 0 && n === rows.length;
|
|
901
1276
|
box.indeterminate = n > 0 && n < rows.length;
|
|
902
|
-
box.hidden = this.grid.options.rowSelection !== "multiple";
|
|
1277
|
+
box.hidden = this.grid.options.rowSelection !== "multiple" || this.grid.options.headerCheckbox === false;
|
|
903
1278
|
return box;
|
|
904
1279
|
}
|
|
905
1280
|
renderRows(count) {
|
|
906
1281
|
const { columns, rowModel, store } = this.grid;
|
|
907
1282
|
const { selected, focus } = store.state;
|
|
908
|
-
const
|
|
1283
|
+
const allBounds = this.grid.allRangeBounds();
|
|
909
1284
|
const spans = this.grid.mergeSpans();
|
|
910
1285
|
const top = Math.max(0, this.scroller.scrollTop - this.topOffset);
|
|
911
1286
|
const range = this.offsets ? rowRangeByOffsets(this.offsets, top, this.scroller.clientHeight, count, ROW_OVERSCAN) : rowRange(top, this.scroller.clientHeight, this.opts.rowHeight, count, ROW_OVERSCAN);
|
|
912
1287
|
this.lastRange = range;
|
|
913
1288
|
const center = columns.region("center");
|
|
914
1289
|
const cr = colRange(center.map((c) => c.left), center.map((c) => c.width), this.scroller.scrollLeft, this.scroller.clientWidth, COL_OVERSCAN);
|
|
1290
|
+
if (center.some((c) => c.def.colSpan)) cr.first = Math.max(0, cr.first - MAX_COLSPAN_REACH);
|
|
915
1291
|
for (const r of REGIONS2) {
|
|
916
1292
|
const cols = r === "center" ? center.slice(cr.first, cr.last + 1) : columns.region(r);
|
|
917
1293
|
const map = this.painted[r];
|
|
@@ -927,7 +1303,8 @@ var Renderer = class {
|
|
|
927
1303
|
const node = rowModel.getRow(i);
|
|
928
1304
|
const isSel = selected.has(node.id);
|
|
929
1305
|
const focusCol = focus && focus.rowIndex === i ? focus.colId : "";
|
|
930
|
-
const
|
|
1306
|
+
const hits = allBounds.filter((b) => i >= b.r1 && i <= b.r2);
|
|
1307
|
+
const inRange = hits.map((b) => `${b.c1}-${b.c2}`).join(",");
|
|
931
1308
|
const merge2 = this.mergeState(spans, cols, i, range.first);
|
|
932
1309
|
const pos = node.kind === "detail" ? `${this.scroller.scrollLeft}:${this.scroller.clientWidth}` : "";
|
|
933
1310
|
const key = `${node.id}|${this.version}|${colKey}|${isSel ? 1 : 0}|${focusCol}|${inRange}|${merge2}|${pos}`;
|
|
@@ -947,7 +1324,7 @@ var Renderer = class {
|
|
|
947
1324
|
if (node.kind === "group") this.paintGroupRow(row.el, node, i, cols, focusCol);
|
|
948
1325
|
else if (node.kind === "detail") this.paintDetailRow(row.el, node, i, r);
|
|
949
1326
|
else if (node.kind === "loading") this.paintLoadingRow(row.el, i, cols);
|
|
950
|
-
else this.paintRow(row.el, node, i, cols, isSel, focusCol,
|
|
1327
|
+
else this.paintRow(row.el, node, i, cols, isSel, focusCol, hits, spans, range.first);
|
|
951
1328
|
}
|
|
952
1329
|
}
|
|
953
1330
|
}
|
|
@@ -975,22 +1352,40 @@ var Renderer = class {
|
|
|
975
1352
|
while (rowEl.childElementCount > cols.length) rowEl.lastElementChild.remove();
|
|
976
1353
|
while (rowEl.childElementCount < cols.length) rowEl.append(el("div", "fg-cell", { role: "gridcell" }));
|
|
977
1354
|
let drawsMerge = false;
|
|
1355
|
+
const area = cols.length ? this.grid.columns.region(cols[0].region) : [];
|
|
1356
|
+
const spanning = area.some((c) => c.def.colSpan);
|
|
1357
|
+
let coveredUntil = -1;
|
|
1358
|
+
const posOf = spanning ? new Map(area.map((c, p) => [c.id, p])) : null;
|
|
978
1359
|
cols.forEach((col, k) => {
|
|
979
1360
|
const cellEl = rowEl.children[k];
|
|
980
1361
|
this.paintCell(cellEl, node, i, col, selected, focusCol === col.id);
|
|
1362
|
+
if (posOf) {
|
|
1363
|
+
const p = posOf.get(col.id);
|
|
1364
|
+
if (p <= coveredUntil) cellEl.classList.add("fg-cell-span-hidden");
|
|
1365
|
+
else if (col.def.colSpan && !isSystemCol(col.id)) {
|
|
1366
|
+
const n = spanOf(col.def.colSpan, col.def, node.data, i, area.length - p);
|
|
1367
|
+
if (n > 1) {
|
|
1368
|
+
coveredUntil = p + n - 1;
|
|
1369
|
+
let w = 0;
|
|
1370
|
+
for (let q = p; q <= coveredUntil; q++) w += area[q].width;
|
|
1371
|
+
cellEl.style.width = `${w}px`;
|
|
1372
|
+
cellEl.classList.add("fg-cell-colspan");
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
981
1376
|
if (col.id === this.firstDataId) this.decorate(cellEl, node);
|
|
982
1377
|
cellEl.style.top = cellEl.style.height = cellEl.style.bottom = "";
|
|
983
1378
|
const s = spans.get(col.id);
|
|
984
1379
|
if (s && s.start[i] !== s.end[i]) {
|
|
985
1380
|
if (i === Math.max(s.start[i], first)) {
|
|
986
1381
|
cellEl.classList.add("fg-cell-merged");
|
|
987
|
-
cellEl.style.top = `${-(i - s.start[i])
|
|
988
|
-
cellEl.style.height = `${(s.end[i] - s.start[i]
|
|
1382
|
+
cellEl.style.top = `${-(this.rowTop(i) - this.rowTop(s.start[i]))}px`;
|
|
1383
|
+
cellEl.style.height = `${this.rowTop(s.end[i]) + this.rowH(s.end[i]) - this.rowTop(s.start[i])}px`;
|
|
989
1384
|
cellEl.style.bottom = "auto";
|
|
990
1385
|
drawsMerge = true;
|
|
991
1386
|
} else cellEl.classList.add("fg-cell-merge-hidden");
|
|
992
1387
|
}
|
|
993
|
-
if (
|
|
1388
|
+
if (!isSystemCol(col.id) && bounds.some((b) => col.index >= b.c1 && col.index <= b.c2)) cellEl.classList.add("fg-cell-range");
|
|
994
1389
|
});
|
|
995
1390
|
rowEl.style.zIndex = drawsMerge ? "1" : "";
|
|
996
1391
|
}
|
|
@@ -1012,6 +1407,10 @@ var Renderer = class {
|
|
|
1012
1407
|
cell.className = `${cls} fg-cell-select`;
|
|
1013
1408
|
const box = el("input", "fg-checkbox", { type: "checkbox", tabindex: "-1", "aria-label": locale.selectRow });
|
|
1014
1409
|
box.checked = selected;
|
|
1410
|
+
if (i >= 0 && !node.kind && !this.grid.rowSelectable(node.data)) {
|
|
1411
|
+
box.disabled = true;
|
|
1412
|
+
cell.classList.add("fg-cell-select-locked");
|
|
1413
|
+
}
|
|
1015
1414
|
cell.replaceChildren(box);
|
|
1016
1415
|
return;
|
|
1017
1416
|
}
|
|
@@ -1029,6 +1428,8 @@ var Renderer = class {
|
|
|
1029
1428
|
}
|
|
1030
1429
|
}
|
|
1031
1430
|
if (this.grid.tracker.isDirty(node.data, def.field)) cls += " fg-cell-dirty";
|
|
1431
|
+
if (def.wrapText) cls += " fg-cell-wrap";
|
|
1432
|
+
if (typeof def.editable === "function" && i >= 0 && !cellEditable(def, node.data, i)) cls += " fg-cell-readonly";
|
|
1032
1433
|
if (i >= 0) cls += this.grid.finder.classFor(i, col.id);
|
|
1033
1434
|
cell.className = cls;
|
|
1034
1435
|
if (!def.renderer) {
|
|
@@ -1145,17 +1546,55 @@ var Renderer = class {
|
|
|
1145
1546
|
const value = values[col.id];
|
|
1146
1547
|
const s = cellSpec(spec);
|
|
1147
1548
|
const code = this.grid.store.state.locale.code;
|
|
1148
|
-
let
|
|
1149
|
-
if (value == null || value === "")
|
|
1150
|
-
else if (typeof value === "string")
|
|
1151
|
-
else if (s.agg === "count")
|
|
1549
|
+
let text2;
|
|
1550
|
+
if (value == null || value === "") text2 = "";
|
|
1551
|
+
else if (typeof value === "string") text2 = value;
|
|
1552
|
+
else if (s.agg === "count") text2 = new Intl.NumberFormat(code).format(value);
|
|
1152
1553
|
else {
|
|
1153
1554
|
const type = cd.type ?? (typeof value === "number" ? "number" : value instanceof Date ? "date" : void 0);
|
|
1154
|
-
|
|
1555
|
+
text2 = getFormatter({ ...cd, type, format: s.format ?? cd.format }, code)(value);
|
|
1155
1556
|
}
|
|
1156
|
-
cell.textContent = s.prefix &&
|
|
1557
|
+
cell.textContent = s.prefix && text2 ? `${s.prefix} ${text2}` : text2;
|
|
1157
1558
|
return cell;
|
|
1158
1559
|
}
|
|
1560
|
+
/** Forget measured row heights (data, widths or fonts changed); rows are measured again on screen. */
|
|
1561
|
+
invalidateHeights() {
|
|
1562
|
+
if (!this.rowHeights.size) return;
|
|
1563
|
+
this.rowHeights.clear();
|
|
1564
|
+
this.heightsVersion++;
|
|
1565
|
+
}
|
|
1566
|
+
/** Measure wrapped cells of the painted rows; when a height changes, lay out again next frame. */
|
|
1567
|
+
measureRows() {
|
|
1568
|
+
const base = this.opts.rowHeight;
|
|
1569
|
+
const need = /* @__PURE__ */ new Map();
|
|
1570
|
+
for (const r of REGIONS2) {
|
|
1571
|
+
for (const [i, row] of this.painted[r]) {
|
|
1572
|
+
const node = this.grid.rowModel.getRow(i);
|
|
1573
|
+
if (!node || node.kind) continue;
|
|
1574
|
+
let h = need.get(i) ?? base;
|
|
1575
|
+
for (const cell of row.el.querySelectorAll(".fg-cell-wrap")) {
|
|
1576
|
+
if (cell.classList.contains("fg-cell-merged") || cell.classList.contains("fg-cell-merge-hidden")) continue;
|
|
1577
|
+
h = Math.max(h, Math.ceil(cell.scrollHeight));
|
|
1578
|
+
}
|
|
1579
|
+
need.set(i, h);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
let changed = false;
|
|
1583
|
+
for (const [i, h] of need) {
|
|
1584
|
+
const id = this.grid.rowModel.getRow(i).id;
|
|
1585
|
+
const cur = this.rowHeights.get(id) ?? base;
|
|
1586
|
+
if (Math.abs(cur - h) > 1) {
|
|
1587
|
+
if (h === base) this.rowHeights.delete(id);
|
|
1588
|
+
else this.rowHeights.set(id, h);
|
|
1589
|
+
changed = true;
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
if (changed) {
|
|
1593
|
+
this.heightsVersion++;
|
|
1594
|
+
this.version++;
|
|
1595
|
+
this.schedule();
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1159
1598
|
detailHeight() {
|
|
1160
1599
|
const h = this.grid.options.detailHeight;
|
|
1161
1600
|
return typeof h === "number" && h > 0 ? h : 240;
|
|
@@ -1189,12 +1628,13 @@ var Renderer = class {
|
|
|
1189
1628
|
const lead = [];
|
|
1190
1629
|
if (tree) {
|
|
1191
1630
|
if (node.hasChildren) {
|
|
1192
|
-
const b = el("button", node.expanded ? "fg-tree-toggle fg-expanded" : "fg-tree-toggle", {
|
|
1631
|
+
const b = el("button", node.expanded ? "fg-tree-toggle fg-expanded" : node.loading ? "fg-tree-toggle fg-tree-loading" : "fg-tree-toggle", {
|
|
1193
1632
|
type: "button",
|
|
1194
1633
|
tabindex: "-1",
|
|
1195
1634
|
"aria-label": node.expanded ? loc.collapseAll : loc.expandAll,
|
|
1196
1635
|
"aria-expanded": String(!!node.expanded)
|
|
1197
1636
|
});
|
|
1637
|
+
if (node.loading) b.setAttribute("aria-busy", "true");
|
|
1198
1638
|
b.innerHTML = CHEVRON;
|
|
1199
1639
|
lead.push(b);
|
|
1200
1640
|
} else lead.push(el("span", "fg-tree-spacer", { "aria-hidden": "true" }));
|
|
@@ -1356,10 +1796,10 @@ var Renderer = class {
|
|
|
1356
1796
|
label.append(sel);
|
|
1357
1797
|
parts.push(label);
|
|
1358
1798
|
}
|
|
1359
|
-
const btn = (action,
|
|
1799
|
+
const btn = (action, text2, label, disabled) => {
|
|
1360
1800
|
const b = el("button", "fg-pager-btn", { type: "button", "aria-label": label, title: label });
|
|
1361
1801
|
b.dataset.action = action;
|
|
1362
|
-
b.textContent =
|
|
1802
|
+
b.textContent = text2;
|
|
1363
1803
|
b.disabled = disabled;
|
|
1364
1804
|
return b;
|
|
1365
1805
|
};
|
|
@@ -1379,11 +1819,98 @@ var Renderer = class {
|
|
|
1379
1819
|
if (this.root.querySelector(".fg-free-badge")) return;
|
|
1380
1820
|
const a = el("a", "fg-free-badge", { href: "https://freegrid.app/pricing/", target: "_blank", rel: "noopener" });
|
|
1381
1821
|
a.innerHTML = '<svg viewBox="0 0 32 32" width="14" height="14" aria-hidden="true"><rect width="32" height="32" rx="7" fill="#0E1726"/><path d="M7 11.5h18M7 16.5h18M7 21.5h18M13 7v18" stroke="#fff" stroke-width="2" stroke-linecap="round"/><rect x="14" y="12.5" width="11" height="3" rx="1" fill="#F2A900"/></svg>';
|
|
1382
|
-
const
|
|
1383
|
-
|
|
1384
|
-
a.append(
|
|
1822
|
+
const text2 = el("span");
|
|
1823
|
+
text2.textContent = `FreeGrid \xB7 ${this.grid.store.state.locale.freeBadge}`;
|
|
1824
|
+
a.append(text2);
|
|
1385
1825
|
this.root.append(a);
|
|
1386
1826
|
}
|
|
1827
|
+
/** Card view applies when enabled and the grid is narrower than the breakpoint. */
|
|
1828
|
+
useCards() {
|
|
1829
|
+
const opt = this.grid.options.cardView;
|
|
1830
|
+
const bp = opt === true ? 640 : typeof opt === "number" ? opt : -1;
|
|
1831
|
+
const on = bp > 0 && this.main.clientWidth < bp;
|
|
1832
|
+
if (on !== this.cardMode) {
|
|
1833
|
+
this.cardMode = on;
|
|
1834
|
+
this.root.classList.toggle("fg-card-mode", on);
|
|
1835
|
+
if (!on && this.cards) {
|
|
1836
|
+
this.cards.scroller.remove();
|
|
1837
|
+
this.cards = null;
|
|
1838
|
+
}
|
|
1839
|
+
this.version++;
|
|
1840
|
+
}
|
|
1841
|
+
return on;
|
|
1842
|
+
}
|
|
1843
|
+
/** Rows as cards: a check box (with row selection) and one "header: value" line per data column. */
|
|
1844
|
+
renderCards(count) {
|
|
1845
|
+
const { grid } = this;
|
|
1846
|
+
if (!this.cards) {
|
|
1847
|
+
const scroller2 = el("div", "fg-cards", { role: "list" });
|
|
1848
|
+
const canvas2 = el("div", "fg-cards-canvas");
|
|
1849
|
+
scroller2.append(canvas2);
|
|
1850
|
+
this.main.insertBefore(scroller2, this.overlay);
|
|
1851
|
+
scroller2.addEventListener("scroll", () => {
|
|
1852
|
+
this.schedule();
|
|
1853
|
+
this.grid.events.emit("scroll", this.getScroll());
|
|
1854
|
+
}, { passive: true });
|
|
1855
|
+
scroller2.addEventListener("click", this.onCardClick);
|
|
1856
|
+
this.cards = { scroller: scroller2, canvas: canvas2 };
|
|
1857
|
+
}
|
|
1858
|
+
const { scroller, canvas } = this.cards;
|
|
1859
|
+
const cols = grid.columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
1860
|
+
const line = Math.round(this.opts.rowHeight * 0.75);
|
|
1861
|
+
const select = !!grid.options.rowSelection;
|
|
1862
|
+
const cardH = 20 + cols.length * line + (select ? line : 0);
|
|
1863
|
+
const gap = 10;
|
|
1864
|
+
canvas.style.height = `${count * (cardH + gap)}px`;
|
|
1865
|
+
const first = Math.max(0, Math.floor(scroller.scrollTop / (cardH + gap)) - 2);
|
|
1866
|
+
const last = Math.min(count - 1, Math.ceil((scroller.scrollTop + (scroller.clientHeight || 800)) / (cardH + gap)) + 2);
|
|
1867
|
+
const { selected, locale } = grid.store.state;
|
|
1868
|
+
const out = [];
|
|
1869
|
+
for (let i = first; i <= last; i++) {
|
|
1870
|
+
const node = grid.rowModel.getRow(i);
|
|
1871
|
+
if (!node) continue;
|
|
1872
|
+
const card = el("div", node.kind ? "fg-card fg-card-other" : selected.has(node.id) ? "fg-card fg-card-selected" : "fg-card", { role: "listitem" });
|
|
1873
|
+
card.dataset.row = String(i);
|
|
1874
|
+
card.style.transform = `translateY(${i * (cardH + gap)}px)`;
|
|
1875
|
+
card.style.height = `${cardH}px`;
|
|
1876
|
+
card.style.setProperty("--fg-card-line", `${line}px`);
|
|
1877
|
+
if (node.kind === "group" && node.group) {
|
|
1878
|
+
const def = grid.columns.getNode(node.group.colId)?.def;
|
|
1879
|
+
card.textContent = `${def?.header ?? node.group.colId}: ${String(node.group.value ?? locale.blanks)} (${node.group.rows.length})`;
|
|
1880
|
+
} else if (!node.kind) {
|
|
1881
|
+
if (select) {
|
|
1882
|
+
const head = el("label", "fg-card-head");
|
|
1883
|
+
const box = el("input", "fg-card-check fg-checkbox", { type: "checkbox", "aria-label": locale.selectRow });
|
|
1884
|
+
box.checked = selected.has(node.id);
|
|
1885
|
+
box.disabled = !grid.rowSelectable(node.data);
|
|
1886
|
+
head.append(box, document.createTextNode(`#${i + 1}`));
|
|
1887
|
+
card.append(head);
|
|
1888
|
+
}
|
|
1889
|
+
for (const col of cols) {
|
|
1890
|
+
const lineEl = el("div", "fg-card-line");
|
|
1891
|
+
const label = el("span", "fg-card-label");
|
|
1892
|
+
label.textContent = col.def.header ?? col.def.field ?? col.id;
|
|
1893
|
+
const value = el("span", "fg-card-value");
|
|
1894
|
+
value.dataset.col = col.id;
|
|
1895
|
+
const v = getCellValue(col.def, node.data);
|
|
1896
|
+
const formatted = getFormatter(col.def, locale.code)(v);
|
|
1897
|
+
if (col.def.renderer) {
|
|
1898
|
+
try {
|
|
1899
|
+
const r = col.def.renderer({ value: v, formatted, row: node.data, rowIndex: i, column: col.def });
|
|
1900
|
+
if (typeof r === "string") value.innerHTML = r;
|
|
1901
|
+
else value.replaceChildren(r);
|
|
1902
|
+
} catch {
|
|
1903
|
+
value.textContent = formatted;
|
|
1904
|
+
}
|
|
1905
|
+
} else value.textContent = formatted;
|
|
1906
|
+
lineEl.append(label, value);
|
|
1907
|
+
card.append(lineEl);
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
out.push(card);
|
|
1911
|
+
}
|
|
1912
|
+
canvas.replaceChildren(...out);
|
|
1913
|
+
}
|
|
1387
1914
|
/** Forget cached detail panels (one master, or all). */
|
|
1388
1915
|
dropDetail(id) {
|
|
1389
1916
|
if (id === void 0) this.details.clear();
|
|
@@ -1393,14 +1920,14 @@ var Renderer = class {
|
|
|
1393
1920
|
return this.lastRange;
|
|
1394
1921
|
}
|
|
1395
1922
|
/** Text width in px using the grid font; falls back to 0.6em per char without canvas. */
|
|
1396
|
-
measureText(
|
|
1923
|
+
measureText(text2, bold) {
|
|
1397
1924
|
const cs = getComputedStyle(this.main);
|
|
1398
1925
|
const size = parseFloat(getComputedStyle(this.root).getPropertyValue("--fg-font-size")) || 13;
|
|
1399
1926
|
if (this.measureCtx === void 0) this.measureCtx = document.createElement("canvas").getContext("2d");
|
|
1400
1927
|
const ctx = this.measureCtx;
|
|
1401
|
-
if (!ctx) return
|
|
1928
|
+
if (!ctx) return text2.length * size * 0.6;
|
|
1402
1929
|
ctx.font = `${bold ? 600 : 400} ${size}px ${cs.fontFamily || "sans-serif"}`;
|
|
1403
|
-
return ctx.measureText(
|
|
1930
|
+
return ctx.measureText(text2).width;
|
|
1404
1931
|
}
|
|
1405
1932
|
scrollToRow(index) {
|
|
1406
1933
|
this.scroller.scrollTop = this.rowTop(Math.max(0, Math.min(index, this.grid.rowModel.getRowCount())));
|
|
@@ -1486,7 +2013,7 @@ var GroupPanel = class {
|
|
|
1486
2013
|
window.addEventListener("pointerup", up);
|
|
1487
2014
|
};
|
|
1488
2015
|
this.el = el("div", "fg-group-panel", { role: "toolbar" });
|
|
1489
|
-
grid.renderer.main.insertBefore(this.el, grid.renderer.
|
|
2016
|
+
grid.renderer.main.insertBefore(this.el, grid.renderer.gridEl);
|
|
1490
2017
|
this.el.addEventListener("click", this.onClick);
|
|
1491
2018
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1492
2019
|
this.refresh();
|
|
@@ -1597,6 +2124,7 @@ var en = {
|
|
|
1597
2124
|
pasteHint: "Clipboard access was blocked. Press Ctrl+V to paste.",
|
|
1598
2125
|
required: "This field is required",
|
|
1599
2126
|
invalidValue: "Invalid value",
|
|
2127
|
+
duplicate: "This value is already used",
|
|
1600
2128
|
undo: "Undo",
|
|
1601
2129
|
redo: "Redo",
|
|
1602
2130
|
deleteRows: "Delete selected rows",
|
|
@@ -1702,6 +2230,25 @@ function resolveLocale(input, navLangs = navigatorLanguages()) {
|
|
|
1702
2230
|
return en_default;
|
|
1703
2231
|
}
|
|
1704
2232
|
|
|
2233
|
+
// src/dom.ts
|
|
2234
|
+
function popupParent(root) {
|
|
2235
|
+
const r = root.getRootNode();
|
|
2236
|
+
return typeof ShadowRoot !== "undefined" && r instanceof ShadowRoot ? r : document.body;
|
|
2237
|
+
}
|
|
2238
|
+
function elementAt(root, x, y) {
|
|
2239
|
+
const r = root.getRootNode();
|
|
2240
|
+
return (typeof r.elementFromPoint === "function" ? r.elementFromPoint(x, y) : null) ?? document.elementFromPoint(x, y);
|
|
2241
|
+
}
|
|
2242
|
+
function activeElement(root) {
|
|
2243
|
+
const r = root.getRootNode();
|
|
2244
|
+
return r.activeElement ?? document.activeElement;
|
|
2245
|
+
}
|
|
2246
|
+
function eventInside(e, ...els) {
|
|
2247
|
+
const path = typeof e.composedPath === "function" ? e.composedPath() : [];
|
|
2248
|
+
if (path.length) return els.some((el2) => !!el2 && path.includes(el2));
|
|
2249
|
+
return els.some((el2) => !!el2 && el2.contains(e.target));
|
|
2250
|
+
}
|
|
2251
|
+
|
|
1705
2252
|
// src/keyboard.ts
|
|
1706
2253
|
var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "PageUp", "PageDown"]);
|
|
1707
2254
|
function nextFocus(key, mods, p, s) {
|
|
@@ -1734,6 +2281,10 @@ function nextFocus(key, mods, p, s) {
|
|
|
1734
2281
|
}
|
|
1735
2282
|
}
|
|
1736
2283
|
|
|
2284
|
+
// src/registry.ts
|
|
2285
|
+
var liveGrids = /* @__PURE__ */ new Map();
|
|
2286
|
+
var gridOfRoot = (root) => root ? liveGrids.get(root) : void 0;
|
|
2287
|
+
|
|
1737
2288
|
// src/interactions/body.ts
|
|
1738
2289
|
var DEFAULT_KEYMAP = {
|
|
1739
2290
|
Enter: "edit",
|
|
@@ -1755,10 +2306,17 @@ function comboOf(e) {
|
|
|
1755
2306
|
return parts.join("+");
|
|
1756
2307
|
}
|
|
1757
2308
|
var DRAG_THRESHOLD2 = 3;
|
|
2309
|
+
function mirrored(grid, e) {
|
|
2310
|
+
if (!grid.renderer.rtl) return e.key;
|
|
2311
|
+
return e.key === "ArrowLeft" ? "ArrowRight" : e.key === "ArrowRight" ? "ArrowLeft" : e.key;
|
|
2312
|
+
}
|
|
1758
2313
|
function attachBody(grid) {
|
|
1759
2314
|
const { renderer } = grid;
|
|
1760
2315
|
const body = renderer.bodyEl;
|
|
1761
2316
|
const root = renderer.root;
|
|
2317
|
+
const keyOf = (e) => mirrored(grid, e);
|
|
2318
|
+
let lastPointer = "mouse";
|
|
2319
|
+
let pressTimer = 0;
|
|
1762
2320
|
let suppressClick = false;
|
|
1763
2321
|
const toEvent = (e) => {
|
|
1764
2322
|
const hit = renderer.cellAt(e.target);
|
|
@@ -1773,7 +2331,7 @@ function attachBody(grid) {
|
|
|
1773
2331
|
const btn = e.target.closest?.(".fg-group-toggle, .fg-tree-toggle, .fg-detail-toggle");
|
|
1774
2332
|
const hit = btn && renderer.cellAt(e.target);
|
|
1775
2333
|
if (!hit) return false;
|
|
1776
|
-
|
|
2334
|
+
renderer.gridEl.focus({ preventScroll: true });
|
|
1777
2335
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
1778
2336
|
grid.setRange(null);
|
|
1779
2337
|
const node = grid.rowModel.getRow(hit.rowIndex);
|
|
@@ -1783,23 +2341,28 @@ function attachBody(grid) {
|
|
|
1783
2341
|
};
|
|
1784
2342
|
const onClick = (e) => {
|
|
1785
2343
|
if (onToggle(e)) return;
|
|
2344
|
+
const before = grid.store.state.focus;
|
|
1786
2345
|
const hit = renderer.cellAt(e.target);
|
|
1787
2346
|
if (hit && grid.rowModel.getRow(hit.rowIndex)?.kind) {
|
|
1788
|
-
|
|
2347
|
+
renderer.gridEl.focus({ preventScroll: true });
|
|
1789
2348
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
1790
2349
|
grid.setRange(null);
|
|
1791
2350
|
return;
|
|
1792
2351
|
}
|
|
1793
2352
|
const ev = toEvent(e);
|
|
1794
2353
|
if (!ev) return;
|
|
1795
|
-
|
|
2354
|
+
renderer.gridEl.focus({ preventScroll: true });
|
|
1796
2355
|
const dragged = suppressClick;
|
|
1797
2356
|
suppressClick = false;
|
|
1798
2357
|
const prev = grid.store.state.focus;
|
|
1799
2358
|
const onBox = ev.colId === SELECT_COL_ID;
|
|
1800
2359
|
if (dragged) {
|
|
1801
2360
|
} else if (e.shiftKey && prev && !onBox && !isSystemCol(prev.colId) && grid.options.rangeSelection !== false) {
|
|
1802
|
-
grid.setRange({ start: prev, end: { rowIndex: ev.rowIndex, colId: ev.colId } });
|
|
2361
|
+
grid.setRange({ start: prev, end: { rowIndex: ev.rowIndex, colId: ev.colId } }, true);
|
|
2362
|
+
} else if ((e.ctrlKey || e.metaKey) && !onBox && !isSystemCol(ev.colId) && grid.options.rangeSelection !== false) {
|
|
2363
|
+
const at = { rowIndex: ev.rowIndex, colId: ev.colId };
|
|
2364
|
+
grid.addRange({ start: at, end: at });
|
|
2365
|
+
grid.setFocus(ev.rowIndex, ev.colId);
|
|
1803
2366
|
} else {
|
|
1804
2367
|
grid.setFocus(ev.rowIndex, ev.colId);
|
|
1805
2368
|
grid.setRange(null);
|
|
@@ -1808,6 +2371,11 @@ function attachBody(grid) {
|
|
|
1808
2371
|
grid.selectFromClick(ev.rowIndex, onBox || e.ctrlKey || e.metaKey, e.shiftKey);
|
|
1809
2372
|
}
|
|
1810
2373
|
grid.events.emit("cellClick", ev);
|
|
2374
|
+
if (lastPointer === "touch" && !dragged && before && before.rowIndex === ev.rowIndex && before.colId === ev.colId && grid.editor.canEdit(ev.rowIndex, ev.colId)) {
|
|
2375
|
+
const def = grid.columns.getColumn(ev.colId).def;
|
|
2376
|
+
if (def.type === "boolean") grid.applyEdit(ev.rowIndex, ev.colId, ev.value !== true);
|
|
2377
|
+
else grid.editor.start(ev.rowIndex, ev.colId);
|
|
2378
|
+
}
|
|
1811
2379
|
};
|
|
1812
2380
|
const onDblClick = (e) => {
|
|
1813
2381
|
if (e.target.closest?.("button")) return;
|
|
@@ -1885,8 +2453,8 @@ function attachBody(grid) {
|
|
|
1885
2453
|
const open = !!(node.group?.expanded ?? node.expanded);
|
|
1886
2454
|
const key = grid.expandKey(node);
|
|
1887
2455
|
if (group && (e.key === "Enter" || e.key === " ")) grid.setGroupExpanded(key, !open);
|
|
1888
|
-
else if (e
|
|
1889
|
-
else if (e
|
|
2456
|
+
else if (keyOf(e) === "ArrowRight" && !open) grid.setGroupExpanded(key, true);
|
|
2457
|
+
else if (keyOf(e) === "ArrowLeft" && open) grid.setGroupExpanded(key, false);
|
|
1890
2458
|
else return false;
|
|
1891
2459
|
return true;
|
|
1892
2460
|
};
|
|
@@ -1898,16 +2466,42 @@ function attachBody(grid) {
|
|
|
1898
2466
|
if (!node || node.kind || !grid.canMoveRows()) return true;
|
|
1899
2467
|
const row = node.data;
|
|
1900
2468
|
const marker = el("div", "fg-row-drop-marker");
|
|
1901
|
-
let
|
|
2469
|
+
let drop = null;
|
|
1902
2470
|
root.classList.add("fg-row-dragging");
|
|
1903
2471
|
const move = (ev) => {
|
|
1904
|
-
const
|
|
1905
|
-
|
|
1906
|
-
const
|
|
1907
|
-
const
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
2472
|
+
const point = elementAt(root, ev.clientX, ev.clientY);
|
|
2473
|
+
const intoRoot = point?.closest(".fg-root");
|
|
2474
|
+
const into = intoRoot === root ? grid : gridOfRoot(intoRoot);
|
|
2475
|
+
const group = grid.dragOptions.group;
|
|
2476
|
+
drop = null;
|
|
2477
|
+
marker.remove();
|
|
2478
|
+
if (!into || !intoRoot || into !== grid && (!group || into.dragOptions.group !== group || !into.options.rowDrag)) return;
|
|
2479
|
+
const under = point.closest(".fg-row[data-row]");
|
|
2480
|
+
const box = under?.getBoundingClientRect();
|
|
2481
|
+
let target = null;
|
|
2482
|
+
let position = "after";
|
|
2483
|
+
let line;
|
|
2484
|
+
if (under && box) {
|
|
2485
|
+
const over = into.rowModel.getRow(Number(under.dataset.row));
|
|
2486
|
+
if (!over || over.kind === "group") return;
|
|
2487
|
+
target = over.detailOf?.data ?? over.data;
|
|
2488
|
+
const y = (ev.clientY - box.top) / box.height;
|
|
2489
|
+
if (over.kind === "detail") position = "after";
|
|
2490
|
+
else if (into.options.treeData) position = y < 0.25 ? "before" : y > 0.75 ? "after" : "inside";
|
|
2491
|
+
else position = y < 0.5 ? "before" : "after";
|
|
2492
|
+
line = position === "before" ? box.top : box.bottom;
|
|
2493
|
+
} else {
|
|
2494
|
+
if (!point.closest(".fg-body")) return;
|
|
2495
|
+
const rows = intoRoot.querySelectorAll(".fg-body-pane-center .fg-row[data-row]");
|
|
2496
|
+
line = rows.length ? Math.max(...[...rows].map((r) => r.getBoundingClientRect().bottom)) : intoRoot.querySelector(".fg-body").getBoundingClientRect().top;
|
|
2497
|
+
}
|
|
2498
|
+
if (!into.canDropRow(row, target, position, grid)) return;
|
|
2499
|
+
drop = { into, target, position };
|
|
2500
|
+
const top = intoRoot.getBoundingClientRect().top;
|
|
2501
|
+
marker.classList.toggle("fg-row-drop-inside", position === "inside");
|
|
2502
|
+
marker.style.top = `${position === "inside" ? box.top - top : line - top - 1}px`;
|
|
2503
|
+
marker.style.height = position === "inside" ? `${box.height}px` : "";
|
|
2504
|
+
intoRoot.append(marker);
|
|
1911
2505
|
};
|
|
1912
2506
|
const up = () => {
|
|
1913
2507
|
window.removeEventListener("pointermove", move);
|
|
@@ -1915,39 +2509,66 @@ function attachBody(grid) {
|
|
|
1915
2509
|
window.removeEventListener("pointercancel", up);
|
|
1916
2510
|
root.classList.remove("fg-row-dragging");
|
|
1917
2511
|
marker.remove();
|
|
1918
|
-
if (
|
|
1919
|
-
const over = grid.rowModel.getRow(target.index);
|
|
1920
|
-
const overRow = over?.detailOf?.data ?? over?.data;
|
|
1921
|
-
const data = grid.store.state.data;
|
|
1922
|
-
const from = data.indexOf(row);
|
|
1923
|
-
let to = data.indexOf(overRow) + (target.after || over?.kind === "detail" ? 1 : 0);
|
|
1924
|
-
if (from < 0 || to < 0) return;
|
|
1925
|
-
if (to > from) to--;
|
|
1926
|
-
grid.moveRow(row, to);
|
|
2512
|
+
if (drop) drop.into.dropRow(row, drop.target, drop.position, grid);
|
|
1927
2513
|
};
|
|
1928
2514
|
window.addEventListener("pointermove", move);
|
|
1929
2515
|
window.addEventListener("pointerup", up);
|
|
1930
2516
|
window.addEventListener("pointercancel", up);
|
|
1931
2517
|
return true;
|
|
1932
2518
|
};
|
|
2519
|
+
const onTouchDown = (e) => {
|
|
2520
|
+
const hit = renderer.cellAt(e.target);
|
|
2521
|
+
if (!hit) return;
|
|
2522
|
+
const x0 = e.clientX, y0 = e.clientY;
|
|
2523
|
+
const cancel = () => {
|
|
2524
|
+
clearTimeout(pressTimer);
|
|
2525
|
+
window.removeEventListener("pointermove", move);
|
|
2526
|
+
window.removeEventListener("pointerup", cancel);
|
|
2527
|
+
window.removeEventListener("pointercancel", cancel);
|
|
2528
|
+
};
|
|
2529
|
+
const move = (ev) => {
|
|
2530
|
+
if (Math.hypot(ev.clientX - x0, ev.clientY - y0) > 8) cancel();
|
|
2531
|
+
};
|
|
2532
|
+
clearTimeout(pressTimer);
|
|
2533
|
+
pressTimer = window.setTimeout(() => {
|
|
2534
|
+
cancel();
|
|
2535
|
+
if (grid.options.contextMenu === false) return;
|
|
2536
|
+
suppressClick = true;
|
|
2537
|
+
if (!isSystemCol(hit.colId)) {
|
|
2538
|
+
grid.setFocus(hit.rowIndex, hit.colId);
|
|
2539
|
+
if (!grid.rangeContains(hit.rowIndex, hit.colId)) grid.setRange({ start: hit, end: hit });
|
|
2540
|
+
}
|
|
2541
|
+
grid.contextMenu.open(x0, y0, hit.rowIndex, hit.colId);
|
|
2542
|
+
}, 550);
|
|
2543
|
+
window.addEventListener("pointermove", move);
|
|
2544
|
+
window.addEventListener("pointerup", cancel);
|
|
2545
|
+
window.addEventListener("pointercancel", cancel);
|
|
2546
|
+
};
|
|
1933
2547
|
const onPointerDown = (e) => {
|
|
2548
|
+
lastPointer = e.pointerType || "mouse";
|
|
2549
|
+
if (lastPointer === "touch") {
|
|
2550
|
+
onTouchDown(e);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
1934
2553
|
if (e.button === 0 && onHandleDown(e)) return;
|
|
1935
2554
|
if (grid.options.rangeSelection === false) return;
|
|
1936
2555
|
if (e.button !== 0 || e.shiftKey || e.target.closest("input, a, button")) return;
|
|
1937
2556
|
const start = renderer.cellAt(e.target);
|
|
1938
2557
|
if (!start || isSystemCol(start.colId)) return;
|
|
1939
2558
|
const x0 = e.clientX, y0 = e.clientY;
|
|
2559
|
+
const adding = e.ctrlKey || e.metaKey;
|
|
1940
2560
|
let dragging = false;
|
|
1941
2561
|
const move = (ev) => {
|
|
1942
2562
|
if (!dragging && Math.hypot(ev.clientX - x0, ev.clientY - y0) < DRAG_THRESHOLD2) return;
|
|
1943
|
-
const hit = renderer.cellAt(
|
|
2563
|
+
const hit = renderer.cellAt(elementAt(root, ev.clientX, ev.clientY));
|
|
1944
2564
|
if (!hit || isSystemCol(hit.colId)) return;
|
|
1945
2565
|
if (!dragging) {
|
|
1946
2566
|
dragging = true;
|
|
1947
2567
|
root.classList.add("fg-range-dragging");
|
|
2568
|
+
if (adding) grid.addRange({ start, end: start });
|
|
1948
2569
|
grid.setFocus(start.rowIndex, start.colId);
|
|
1949
2570
|
}
|
|
1950
|
-
grid.setRange({ start, end: hit });
|
|
2571
|
+
grid.setRange({ start, end: hit }, adding);
|
|
1951
2572
|
};
|
|
1952
2573
|
const up = () => {
|
|
1953
2574
|
window.removeEventListener("pointermove", move);
|
|
@@ -1959,7 +2580,7 @@ function attachBody(grid) {
|
|
|
1959
2580
|
window.addEventListener("pointerup", up);
|
|
1960
2581
|
};
|
|
1961
2582
|
const onKeyDown = (e) => {
|
|
1962
|
-
if (e.target !== root) return;
|
|
2583
|
+
if (e.target !== root && e.target !== renderer.gridEl) return;
|
|
1963
2584
|
const cols = grid.columns.displayed();
|
|
1964
2585
|
const count = grid.rowModel.getRowCount();
|
|
1965
2586
|
if (!cols.length || !count) return;
|
|
@@ -2001,7 +2622,7 @@ function attachBody(grid) {
|
|
|
2001
2622
|
const range = grid.store.state.range;
|
|
2002
2623
|
const end = range?.end ?? anchor;
|
|
2003
2624
|
const pos = { rowIndex: Math.min(end.rowIndex, count - 1), colIndex: Math.max(0, cols.findIndex((c) => c.id === end.colId)) };
|
|
2004
|
-
const next2 = nextFocus(e
|
|
2625
|
+
const next2 = nextFocus(keyOf(e), { ctrl, shift: false }, pos, size);
|
|
2005
2626
|
if (!next2) return;
|
|
2006
2627
|
e.preventDefault();
|
|
2007
2628
|
const colId2 = cols[Math.max(firstData, next2.colIndex)].id;
|
|
@@ -2014,7 +2635,7 @@ function attachBody(grid) {
|
|
|
2014
2635
|
if (!f) next = NAV_KEYS.has(e.key) && e.key !== "Tab" ? { rowIndex: 0, colIndex: 0 } : null;
|
|
2015
2636
|
else {
|
|
2016
2637
|
const colIndex2 = Math.max(0, cols.findIndex((c) => c.id === f.colId));
|
|
2017
|
-
next = nextFocus(e
|
|
2638
|
+
next = nextFocus(keyOf(e), { ctrl, shift: e.shiftKey }, { rowIndex: Math.min(f.rowIndex, count - 1), colIndex: colIndex2 }, size);
|
|
2018
2639
|
}
|
|
2019
2640
|
if (!next) return;
|
|
2020
2641
|
e.preventDefault();
|
|
@@ -2027,17 +2648,17 @@ function attachBody(grid) {
|
|
|
2027
2648
|
const nested = (e) => e.target?.closest?.(".fg-root") !== root;
|
|
2028
2649
|
const onCopy = (e) => {
|
|
2029
2650
|
if (fromField(e) || nested(e)) return;
|
|
2030
|
-
const
|
|
2031
|
-
if (!
|
|
2032
|
-
e.clipboardData.setData("text/plain",
|
|
2651
|
+
const text2 = grid.selectionTSV(false);
|
|
2652
|
+
if (!text2 || !e.clipboardData) return;
|
|
2653
|
+
e.clipboardData.setData("text/plain", text2);
|
|
2033
2654
|
e.preventDefault();
|
|
2034
2655
|
};
|
|
2035
2656
|
const onPaste = (e) => {
|
|
2036
2657
|
if (fromField(e) || nested(e)) return;
|
|
2037
|
-
const
|
|
2038
|
-
if (!
|
|
2658
|
+
const text2 = e.clipboardData?.getData("text/plain");
|
|
2659
|
+
if (!text2) return;
|
|
2039
2660
|
e.preventDefault();
|
|
2040
|
-
grid.pasteText(
|
|
2661
|
+
grid.pasteText(text2);
|
|
2041
2662
|
};
|
|
2042
2663
|
body.addEventListener("click", onClick);
|
|
2043
2664
|
body.addEventListener("dblclick", onDblClick);
|
|
@@ -2299,9 +2920,9 @@ var ColumnChooserPanel = class {
|
|
|
2299
2920
|
box.checked = leaves.length > 0 && shown === leaves.length;
|
|
2300
2921
|
box.indeterminate = shown > 0 && shown < leaves.length;
|
|
2301
2922
|
box.disabled = leaves.every((l) => l.def.lockVisible);
|
|
2302
|
-
const
|
|
2303
|
-
|
|
2304
|
-
row.append(box,
|
|
2923
|
+
const text2 = el("span", "fg-chooser-label");
|
|
2924
|
+
text2.textContent = labelOf(n);
|
|
2925
|
+
row.append(box, text2);
|
|
2305
2926
|
if (!n.children && n.pinned) {
|
|
2306
2927
|
const pin = el("span", "fg-chooser-pin");
|
|
2307
2928
|
pin.textContent = n.pinned === "left" ? "\u21E4" : "\u21E5";
|
|
@@ -2379,14 +3000,14 @@ var ColumnChooserDialog = class {
|
|
|
2379
3000
|
head.append(title, close);
|
|
2380
3001
|
this.panel = new ColumnChooserPanel(this.grid);
|
|
2381
3002
|
popup.append(head, this.panel.el);
|
|
2382
|
-
|
|
3003
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
2383
3004
|
const r = this.grid.renderer.root.getBoundingClientRect();
|
|
2384
3005
|
const w = popup.offsetWidth || 280;
|
|
2385
3006
|
popup.style.left = `${Math.max(4, Math.min(r.right - w - 8, window.innerWidth - w - 4))}px`;
|
|
2386
3007
|
popup.style.top = `${Math.max(4, r.top + 8)}px`;
|
|
2387
3008
|
this.popup = popup;
|
|
2388
3009
|
const onDown = (e) => {
|
|
2389
|
-
if (!
|
|
3010
|
+
if (!eventInside(e, popup)) this.close();
|
|
2390
3011
|
};
|
|
2391
3012
|
const onKey = (e) => {
|
|
2392
3013
|
if (e.key === "Escape") this.close();
|
|
@@ -2501,7 +3122,7 @@ var ContextMenu = class {
|
|
|
2501
3122
|
const hit = grid.renderer.cellAt(target);
|
|
2502
3123
|
if (!hit) return;
|
|
2503
3124
|
e.preventDefault();
|
|
2504
|
-
grid.renderer.
|
|
3125
|
+
grid.renderer.gridEl.focus({ preventScroll: true });
|
|
2505
3126
|
if (!isSystemCol(hit.colId) && !grid.rangeContains(hit.rowIndex, hit.colId)) {
|
|
2506
3127
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
2507
3128
|
grid.setRange({ start: hit, end: hit });
|
|
@@ -2571,7 +3192,7 @@ var ContextMenu = class {
|
|
|
2571
3192
|
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
2572
3193
|
const fail = () => this.grid.toast(this.grid.store.state.locale.pasteHint.replace("Ctrl+V", shortcut("V")));
|
|
2573
3194
|
if (!clip?.readText) return fail();
|
|
2574
|
-
clip.readText().then((
|
|
3195
|
+
clip.readText().then((text2) => this.grid.pasteText(text2), fail);
|
|
2575
3196
|
}
|
|
2576
3197
|
open(x, y, rowIndex, colId) {
|
|
2577
3198
|
this.close();
|
|
@@ -2624,23 +3245,23 @@ var ContextMenu = class {
|
|
|
2624
3245
|
prevSep = false;
|
|
2625
3246
|
}
|
|
2626
3247
|
if (popup.lastElementChild?.classList.contains("fg-cm-sep")) popup.lastElementChild.remove();
|
|
2627
|
-
|
|
3248
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
2628
3249
|
const w = popup.offsetWidth || 220, h = popup.offsetHeight || 300;
|
|
2629
3250
|
popup.style.left = `${Math.max(4, Math.min(x, window.innerWidth - w - 4))}px`;
|
|
2630
3251
|
popup.style.top = `${Math.max(4, Math.min(y, window.innerHeight - h - 4))}px`;
|
|
2631
3252
|
this.popup = popup;
|
|
2632
3253
|
const onDown = (e) => {
|
|
2633
|
-
if (!
|
|
3254
|
+
if (!eventInside(e, popup)) this.close();
|
|
2634
3255
|
};
|
|
2635
3256
|
const onKey = (e) => {
|
|
2636
3257
|
if (e.key === "Escape") {
|
|
2637
3258
|
this.close();
|
|
2638
|
-
grid.renderer.
|
|
3259
|
+
grid.renderer.gridEl.focus();
|
|
2639
3260
|
return;
|
|
2640
3261
|
}
|
|
2641
3262
|
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
2642
3263
|
const buttons = [...popup.querySelectorAll("button:not(:disabled)")];
|
|
2643
|
-
const i = buttons.indexOf(
|
|
3264
|
+
const i = buttons.indexOf(activeElement(popup));
|
|
2644
3265
|
const next = buttons[(i + (e.key === "ArrowDown" ? 1 : buttons.length - 1) + buttons.length) % buttons.length];
|
|
2645
3266
|
next?.focus();
|
|
2646
3267
|
e.preventDefault();
|
|
@@ -2678,7 +3299,7 @@ var FillHandle = class {
|
|
|
2678
3299
|
let target = null;
|
|
2679
3300
|
const rootEl = grid.renderer.root;
|
|
2680
3301
|
const move = (ev) => {
|
|
2681
|
-
const hit = grid.renderer.cellAt(
|
|
3302
|
+
const hit = grid.renderer.cellAt(elementAt(grid.renderer.root, ev.clientX, ev.clientY));
|
|
2682
3303
|
const col = hit && grid.columns.getColumn(hit.colId);
|
|
2683
3304
|
if (!hit || !col || isSystemCol(col.id)) return;
|
|
2684
3305
|
const dr = hit.rowIndex > src.r2 ? hit.rowIndex - src.r2 : hit.rowIndex < src.r1 ? hit.rowIndex - src.r1 : 0;
|
|
@@ -2735,7 +3356,7 @@ var FillHandle = class {
|
|
|
2735
3356
|
}
|
|
2736
3357
|
const root = this.grid.renderer.root.getBoundingClientRect();
|
|
2737
3358
|
const r = cell.getBoundingClientRect();
|
|
2738
|
-
this.handle.style.left = `${r.right - root.left - 4}px`;
|
|
3359
|
+
this.handle.style.left = `${(this.grid.renderer.rtl ? r.left : r.right) - root.left - 4}px`;
|
|
2739
3360
|
this.handle.style.top = `${r.bottom - root.top - 4}px`;
|
|
2740
3361
|
if (!this.handle.isConnected) this.grid.renderer.root.append(this.handle);
|
|
2741
3362
|
}
|
|
@@ -2784,8 +3405,8 @@ var Finder = class {
|
|
|
2784
3405
|
if (i === void 0) return "";
|
|
2785
3406
|
return i === this.index ? " fg-cell-match fg-cell-match-current" : " fg-cell-match";
|
|
2786
3407
|
}
|
|
2787
|
-
find(
|
|
2788
|
-
this.query =
|
|
3408
|
+
find(text2) {
|
|
3409
|
+
this.query = text2.trim().toLowerCase();
|
|
2789
3410
|
this.matches = [];
|
|
2790
3411
|
this.lookup.clear();
|
|
2791
3412
|
this.index = -1;
|
|
@@ -2798,8 +3419,8 @@ var Finder = class {
|
|
|
2798
3419
|
outer: for (let r = 0; r < rows.length; r++) {
|
|
2799
3420
|
if (rows[r].kind) continue;
|
|
2800
3421
|
for (let c = 0; c < cols.length; c++) {
|
|
2801
|
-
const
|
|
2802
|
-
if (!
|
|
3422
|
+
const text3 = fmts[c](getCellValue(cols[c].def, rows[r].data)).toLowerCase();
|
|
3423
|
+
if (!text3.includes(this.query)) continue;
|
|
2803
3424
|
this.lookup.set(`${r}|${cols[c].id}`, this.matches.length);
|
|
2804
3425
|
this.matches.push({ rowIndex: r, colId: cols[c].id });
|
|
2805
3426
|
if (this.matches.length >= MAX_MATCHES) break outer;
|
|
@@ -2851,9 +3472,9 @@ var Finder = class {
|
|
|
2851
3472
|
const bar = el("div", "fg-findbar", { role: "search" });
|
|
2852
3473
|
const input = el("input", "fg-input", { type: "search", placeholder: loc.findPlaceholder, "aria-label": loc.findPlaceholder });
|
|
2853
3474
|
this.countEl = el("span", "fg-findbar-count", { "aria-live": "polite" });
|
|
2854
|
-
const btn = (label,
|
|
3475
|
+
const btn = (label, text2, run) => {
|
|
2855
3476
|
const b = el("button", "fg-findbar-btn", { type: "button", "aria-label": label, title: label });
|
|
2856
|
-
b.textContent =
|
|
3477
|
+
b.textContent = text2;
|
|
2857
3478
|
b.addEventListener("click", run);
|
|
2858
3479
|
return b;
|
|
2859
3480
|
};
|
|
@@ -2885,40 +3506,10 @@ var Finder = class {
|
|
|
2885
3506
|
this.bar = null;
|
|
2886
3507
|
this.countEl = null;
|
|
2887
3508
|
this.clear();
|
|
2888
|
-
this.grid.renderer.
|
|
3509
|
+
this.grid.renderer.gridEl.focus({ preventScroll: true });
|
|
2889
3510
|
}
|
|
2890
3511
|
};
|
|
2891
3512
|
|
|
2892
|
-
// src/merge.ts
|
|
2893
|
-
function computeSpans(rows, def) {
|
|
2894
|
-
const n = rows.length;
|
|
2895
|
-
const start = new Int32Array(n);
|
|
2896
|
-
const end = new Int32Array(n);
|
|
2897
|
-
const same2 = typeof def.merge === "function" ? (a, b) => {
|
|
2898
|
-
try {
|
|
2899
|
-
return !!def.merge(getCellValue(def, rows[a]), getCellValue(def, rows[b]), rows[a], rows[b]);
|
|
2900
|
-
} catch {
|
|
2901
|
-
return false;
|
|
2902
|
-
}
|
|
2903
|
-
} : (() => {
|
|
2904
|
-
const keys = rows.map((r) => {
|
|
2905
|
-
const v = getCellValue(def, r);
|
|
2906
|
-
return isBlank(v) ? null : setKey(v);
|
|
2907
|
-
});
|
|
2908
|
-
return (a, b) => keys[a] !== null && keys[a] === keys[b];
|
|
2909
|
-
})();
|
|
2910
|
-
let s = 0;
|
|
2911
|
-
for (let i = 1; i <= n; i++) {
|
|
2912
|
-
if (i < n && same2(i - 1, i)) continue;
|
|
2913
|
-
for (let k = s; k < i; k++) {
|
|
2914
|
-
start[k] = s;
|
|
2915
|
-
end[k] = i - 1;
|
|
2916
|
-
}
|
|
2917
|
-
s = i;
|
|
2918
|
-
}
|
|
2919
|
-
return { start, end };
|
|
2920
|
-
}
|
|
2921
|
-
|
|
2922
3513
|
// src/fill.ts
|
|
2923
3514
|
var DAY = 864e5;
|
|
2924
3515
|
function fillSeries(source, count) {
|
|
@@ -2963,22 +3554,22 @@ function toTSV(rows) {
|
|
|
2963
3554
|
const cell = (s) => /[\t\n\r"]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
2964
3555
|
return rows.map((r) => r.map(cell).join(" ")).join("\r\n");
|
|
2965
3556
|
}
|
|
2966
|
-
function parseTSV(
|
|
2967
|
-
return parseDelimited(
|
|
3557
|
+
function parseTSV(text2) {
|
|
3558
|
+
return parseDelimited(text2, " ");
|
|
2968
3559
|
}
|
|
2969
|
-
function parseDelimited(
|
|
2970
|
-
if (!
|
|
3560
|
+
function parseDelimited(text2, sep) {
|
|
3561
|
+
if (!text2) return [];
|
|
2971
3562
|
const rows = [];
|
|
2972
3563
|
let row = [];
|
|
2973
3564
|
let cell = "";
|
|
2974
3565
|
let i = 0;
|
|
2975
3566
|
let quoted = false;
|
|
2976
3567
|
let atCellStart = true;
|
|
2977
|
-
while (i <
|
|
2978
|
-
const ch =
|
|
3568
|
+
while (i < text2.length) {
|
|
3569
|
+
const ch = text2[i];
|
|
2979
3570
|
if (quoted) {
|
|
2980
3571
|
if (ch === '"') {
|
|
2981
|
-
if (
|
|
3572
|
+
if (text2[i + 1] === '"') {
|
|
2982
3573
|
cell += '"';
|
|
2983
3574
|
i += 2;
|
|
2984
3575
|
continue;
|
|
@@ -3010,7 +3601,7 @@ function parseDelimited(text, sep) {
|
|
|
3010
3601
|
row = [];
|
|
3011
3602
|
cell = "";
|
|
3012
3603
|
atCellStart = true;
|
|
3013
|
-
i += ch === "\r" &&
|
|
3604
|
+
i += ch === "\r" && text2[i + 1] === "\n" ? 2 : 1;
|
|
3014
3605
|
continue;
|
|
3015
3606
|
}
|
|
3016
3607
|
cell += ch;
|
|
@@ -3040,8 +3631,8 @@ function copyValue(def, value, locale) {
|
|
|
3040
3631
|
}
|
|
3041
3632
|
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "1", "y", "yes", "\uC608", "o", "on"]);
|
|
3042
3633
|
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "0", "n", "no", "\uC544\uB2C8\uC624", "x", "off"]);
|
|
3043
|
-
function parseNumber(
|
|
3044
|
-
let s =
|
|
3634
|
+
function parseNumber(text2) {
|
|
3635
|
+
let s = text2.trim();
|
|
3045
3636
|
if (!s) return { ok: true, value: null };
|
|
3046
3637
|
let negative = false;
|
|
3047
3638
|
if (/^\(.*\)$/.test(s)) {
|
|
@@ -3061,8 +3652,8 @@ function validDate(y, m, d) {
|
|
|
3061
3652
|
const date = new Date(y, m - 1, d);
|
|
3062
3653
|
return date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d ? date : null;
|
|
3063
3654
|
}
|
|
3064
|
-
function parseDate(
|
|
3065
|
-
const s =
|
|
3655
|
+
function parseDate(text2, previous) {
|
|
3656
|
+
const s = text2.trim();
|
|
3066
3657
|
if (!s) return { ok: true, value: null };
|
|
3067
3658
|
let date = null;
|
|
3068
3659
|
let m = /^(\d{4})\s*[-./년]\s*(\d{1,2})\s*[-./월]\s*(\d{1,2})\s*[.일]?$/.exec(s);
|
|
@@ -3072,26 +3663,37 @@ function parseDate(text, previous) {
|
|
|
3072
3663
|
if (!date) return { ok: false };
|
|
3073
3664
|
return { ok: true, value: previous instanceof Date ? date : toDateKey(date) };
|
|
3074
3665
|
}
|
|
3075
|
-
function parseValue(def,
|
|
3666
|
+
function parseValue(def, text2, previous) {
|
|
3076
3667
|
switch (def.type) {
|
|
3077
3668
|
case "number":
|
|
3078
|
-
return parseNumber(
|
|
3669
|
+
return parseNumber(text2);
|
|
3079
3670
|
case "date":
|
|
3080
|
-
return parseDate(
|
|
3671
|
+
return parseDate(text2, previous);
|
|
3081
3672
|
case "boolean": {
|
|
3082
|
-
const t =
|
|
3673
|
+
const t = text2.trim().toLowerCase();
|
|
3083
3674
|
if (!t) return { ok: true, value: null };
|
|
3084
3675
|
if (TRUE_WORDS.has(t)) return { ok: true, value: true };
|
|
3085
3676
|
if (FALSE_WORDS.has(t)) return { ok: true, value: false };
|
|
3086
3677
|
return { ok: false };
|
|
3087
3678
|
}
|
|
3088
3679
|
default:
|
|
3089
|
-
return { ok: true, value:
|
|
3680
|
+
return { ok: true, value: text2 };
|
|
3090
3681
|
}
|
|
3091
3682
|
}
|
|
3092
3683
|
|
|
3093
3684
|
// src/editing/editor.ts
|
|
3094
|
-
var normalizeOptions = (opts) => opts.map((o) => o !== null && typeof o === "object" && "value" in o ? o : { value: o, label: String(o) });
|
|
3685
|
+
var normalizeOptions = (opts) => opts.map((o) => o !== null && typeof o === "object" && "value" in o ? o : { value: o, label: String(o), disabled: false });
|
|
3686
|
+
var pad2 = (n) => String(n).padStart(2, "0");
|
|
3687
|
+
function localDateTime(v) {
|
|
3688
|
+
const d = v instanceof Date ? v : typeof v === "string" && v ? new Date(v) : null;
|
|
3689
|
+
if (!d || Number.isNaN(d.getTime())) return "";
|
|
3690
|
+
return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}T${pad2(d.getHours())}:${pad2(d.getMinutes())}`;
|
|
3691
|
+
}
|
|
3692
|
+
function timeOf(v) {
|
|
3693
|
+
if (v instanceof Date) return `${pad2(v.getHours())}:${pad2(v.getMinutes())}`;
|
|
3694
|
+
const m = typeof v === "string" ? /(\d{1,2}):(\d{2})/.exec(v) : null;
|
|
3695
|
+
return m ? `${pad2(Number(m[1]))}:${m[2]}` : "";
|
|
3696
|
+
}
|
|
3095
3697
|
var CellEditor = class {
|
|
3096
3698
|
constructor(grid) {
|
|
3097
3699
|
this.grid = grid;
|
|
@@ -3103,12 +3705,15 @@ var CellEditor = class {
|
|
|
3103
3705
|
canEdit(rowIndex, colId) {
|
|
3104
3706
|
if (isSystemCol(colId)) return false;
|
|
3105
3707
|
const col = this.grid.columns.getColumn(colId);
|
|
3106
|
-
|
|
3708
|
+
if (!col?.def.field || rowIndex < 0 || rowIndex >= this.grid.rowModel.getRowCount()) return false;
|
|
3709
|
+
const node = this.grid.rowModel.getRow(rowIndex);
|
|
3710
|
+
return !node.kind && cellEditable(col.def, node.data, rowIndex);
|
|
3107
3711
|
}
|
|
3108
3712
|
start(rowIndex, colId, initialText) {
|
|
3109
3713
|
if (this.cur) this.commit(null);
|
|
3110
3714
|
if (!this.canEdit(rowIndex, colId)) return false;
|
|
3111
3715
|
const { grid } = this;
|
|
3716
|
+
if (grid.veto("beforeEdit", { row: grid.rowModel.getRow(rowIndex).data, rowIndex, colId })) return false;
|
|
3112
3717
|
const { renderer } = grid;
|
|
3113
3718
|
const col = grid.columns.getColumn(colId);
|
|
3114
3719
|
const def = col.def;
|
|
@@ -3144,12 +3749,24 @@ var CellEditor = class {
|
|
|
3144
3749
|
const opt = el("option");
|
|
3145
3750
|
opt.value = String(i);
|
|
3146
3751
|
opt.textContent = o.label ?? String(o.value);
|
|
3752
|
+
opt.disabled = !!o.disabled;
|
|
3147
3753
|
select.append(opt);
|
|
3148
3754
|
});
|
|
3149
3755
|
select.selectedIndex = Math.max(0, options.findIndex((o) => Object.is(o.value, value)));
|
|
3150
3756
|
control = select;
|
|
3151
3757
|
read = () => ({ value: options[select.selectedIndex]?.value ?? null });
|
|
3152
3758
|
wrap.append(select);
|
|
3759
|
+
} else if (spec?.type === "time" || spec?.type === "datetime" || !spec && def.type === "date" && def.format === "datetime") {
|
|
3760
|
+
const isTime = spec?.type === "time";
|
|
3761
|
+
const input = el("input", "fg-editor-control", { type: isTime ? "time" : "datetime-local" });
|
|
3762
|
+
input.value = isTime ? timeOf(value) : localDateTime(value);
|
|
3763
|
+
control = input;
|
|
3764
|
+
read = () => {
|
|
3765
|
+
if (!input.value) return { value: null };
|
|
3766
|
+
if (isTime) return { value: input.value };
|
|
3767
|
+
return { value: value instanceof Date ? new Date(input.value) : input.value };
|
|
3768
|
+
};
|
|
3769
|
+
wrap.append(input);
|
|
3153
3770
|
} else {
|
|
3154
3771
|
const textarea = spec?.type === "textarea";
|
|
3155
3772
|
const input = textarea ? el("textarea", "fg-editor-control") : el("input", "fg-editor-control");
|
|
@@ -3220,21 +3837,15 @@ var CellEditor = class {
|
|
|
3220
3837
|
const old = cur.row[field];
|
|
3221
3838
|
const raw = cur.read();
|
|
3222
3839
|
let value;
|
|
3223
|
-
if ("text" in raw) {
|
|
3840
|
+
if ("text" in raw && cur.def.formulas && isFormula(raw.text.trim())) {
|
|
3841
|
+
value = raw.text.trim();
|
|
3842
|
+
} else if ("text" in raw) {
|
|
3224
3843
|
const parsed = parseValue(cur.def, raw.text, old);
|
|
3225
3844
|
if (!parsed.ok) return this.fail(raw.text, loc.invalidValue);
|
|
3226
3845
|
value = parsed.value;
|
|
3227
3846
|
} else value = raw.value;
|
|
3228
|
-
|
|
3229
|
-
if (
|
|
3230
|
-
let res;
|
|
3231
|
-
try {
|
|
3232
|
-
res = cur.def.validate(value, cur.row);
|
|
3233
|
-
} catch {
|
|
3234
|
-
res = loc.invalidValue;
|
|
3235
|
-
}
|
|
3236
|
-
if (res !== true) return this.fail(value, typeof res === "string" && res ? res : loc.invalidValue);
|
|
3237
|
-
}
|
|
3847
|
+
const error = grid.cellError(cur.def, cur.row, value);
|
|
3848
|
+
if (error !== null) return this.fail(value, error);
|
|
3238
3849
|
this.close(true);
|
|
3239
3850
|
grid.applyEdit(cur.rowIndex, cur.colId, value);
|
|
3240
3851
|
this.move(cur.rowIndex, cur.colId, move);
|
|
@@ -3255,7 +3866,7 @@ var CellEditor = class {
|
|
|
3255
3866
|
if (!cur) return;
|
|
3256
3867
|
this.cur = null;
|
|
3257
3868
|
cur.dispose();
|
|
3258
|
-
this.grid.renderer.
|
|
3869
|
+
this.grid.renderer.gridEl.focus({ preventScroll: true });
|
|
3259
3870
|
this.grid.events.emit("editStop", { rowIndex: cur.rowIndex, colId: cur.colId, committed });
|
|
3260
3871
|
}
|
|
3261
3872
|
move(rowIndex, colId, move) {
|
|
@@ -3388,7 +3999,7 @@ function startResize(grid, e, colId) {
|
|
|
3388
3999
|
grid.renderer.root.classList.add("fg-resizing");
|
|
3389
4000
|
track(
|
|
3390
4001
|
(ev) => {
|
|
3391
|
-
grid.columns.setWidth(colId, startW + ev.clientX - startX);
|
|
4002
|
+
grid.columns.setWidth(colId, startW + (ev.clientX - startX) * (grid.renderer.rtl ? -1 : 1));
|
|
3392
4003
|
grid.layoutChanged();
|
|
3393
4004
|
},
|
|
3394
4005
|
() => {
|
|
@@ -3408,7 +4019,7 @@ function beginDrag(grid, colId, cell, pane) {
|
|
|
3408
4019
|
const ghost = el("div", `fg-drag-ghost fg-theme-${grid.store.state.theme}`);
|
|
3409
4020
|
ghost.textContent = cell.querySelector(".fg-header-label")?.textContent ?? "";
|
|
3410
4021
|
grid.applyFontTo(ghost);
|
|
3411
|
-
|
|
4022
|
+
popupParent(grid.renderer.root).append(ghost);
|
|
3412
4023
|
const marker = el("div", "fg-drop-marker");
|
|
3413
4024
|
marker.style.height = `${grid.renderer.headerEl.offsetHeight}px`;
|
|
3414
4025
|
grid.renderer.root.append(marker);
|
|
@@ -3423,11 +4034,13 @@ function updateDrag(grid, colId, d, x, y) {
|
|
|
3423
4034
|
let t = 0;
|
|
3424
4035
|
for (const s of d.sibs) {
|
|
3425
4036
|
const r = s.el.getBoundingClientRect();
|
|
3426
|
-
if (x > r.left + r.width / 2) t++;
|
|
4037
|
+
if (grid.renderer.rtl ? x < r.left + r.width / 2 : x > r.left + r.width / 2) t++;
|
|
3427
4038
|
}
|
|
3428
4039
|
d.target = t;
|
|
3429
4040
|
const rootLeft = grid.renderer.root.getBoundingClientRect().left;
|
|
3430
|
-
const
|
|
4041
|
+
const start = (el2) => grid.renderer.rtl ? el2.getBoundingClientRect().right : el2.getBoundingClientRect().left;
|
|
4042
|
+
const end = (el2) => grid.renderer.rtl ? el2.getBoundingClientRect().left : el2.getBoundingClientRect().right;
|
|
4043
|
+
const edge = t < d.sibs.length ? start(d.sibs[t].el) : d.sibs.length ? end(d.sibs[d.sibs.length - 1].el) : start(d.cell);
|
|
3431
4044
|
d.marker.style.left = `${edge - rootLeft - 1}px`;
|
|
3432
4045
|
}
|
|
3433
4046
|
function finishDrag(grid, colId, d) {
|
|
@@ -3478,6 +4091,7 @@ function attachHeader(grid) {
|
|
|
3478
4091
|
const cell = target.closest(".fg-header-cell");
|
|
3479
4092
|
const colId = cell?.dataset.col;
|
|
3480
4093
|
if (!cell || !colId || isSystemCol(colId) || target.closest(".fg-menu-btn")) return;
|
|
4094
|
+
if (target.closest("button, a, input, select, textarea, [data-fg-no-sort]") && target.closest(".fg-header-label")) return;
|
|
3481
4095
|
e.preventDefault();
|
|
3482
4096
|
if (target.closest(".fg-resize")) startResize(grid, e, colId);
|
|
3483
4097
|
else startPress(grid, e, colId, cell);
|
|
@@ -3513,16 +4127,16 @@ function toCsv(rows, sep = ",") {
|
|
|
3513
4127
|
};
|
|
3514
4128
|
return rows.map((r) => r.map(cell).join(sep)).join("\r\n");
|
|
3515
4129
|
}
|
|
3516
|
-
function detectSeparator(
|
|
3517
|
-
const line =
|
|
4130
|
+
function detectSeparator(text2) {
|
|
4131
|
+
const line = text2.slice(0, text2.search(/\r?\n|$/));
|
|
3518
4132
|
const count = (ch) => line.split(ch).length - 1;
|
|
3519
4133
|
const tabs = count(" "), semis = count(";"), commas = count(",");
|
|
3520
4134
|
if (tabs && tabs >= commas && tabs >= semis) return " ";
|
|
3521
4135
|
if (semis > commas) return ";";
|
|
3522
4136
|
return ",";
|
|
3523
4137
|
}
|
|
3524
|
-
function parseCsv(
|
|
3525
|
-
return parseDelimited(
|
|
4138
|
+
function parseCsv(text2, sep = detectSeparator(text2)) {
|
|
4139
|
+
return parseDelimited(text2.replace(/^\uFEFF/, ""), sep);
|
|
3526
4140
|
}
|
|
3527
4141
|
|
|
3528
4142
|
// src/export/zip.ts
|
|
@@ -3671,9 +4285,42 @@ function writeXlsx(sheets) {
|
|
|
3671
4285
|
const fmtIds = /* @__PURE__ */ new Map([["General", 0]]);
|
|
3672
4286
|
const customFmts = [];
|
|
3673
4287
|
const xfs = ['<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>'];
|
|
3674
|
-
const xfIndex = /* @__PURE__ */ new Map([["General
|
|
3675
|
-
const
|
|
3676
|
-
|
|
4288
|
+
const xfIndex = /* @__PURE__ */ new Map([["General||||", 0]]);
|
|
4289
|
+
const fonts = ['<font><sz val="11"/><name val="Calibri"/></font>', '<font><b/><sz val="11"/><name val="Calibri"/></font>'];
|
|
4290
|
+
const fontIndex = /* @__PURE__ */ new Map([["0|", 0], ["1|", 1]]);
|
|
4291
|
+
const fills = [
|
|
4292
|
+
'<fill><patternFill patternType="none"/></fill>',
|
|
4293
|
+
'<fill><patternFill patternType="gray125"/></fill>',
|
|
4294
|
+
'<fill><patternFill patternType="solid"><fgColor rgb="FFF2F4F7"/><bgColor indexed="64"/></patternFill></fill>'
|
|
4295
|
+
];
|
|
4296
|
+
const fillIndex = /* @__PURE__ */ new Map([["FFF2F4F7", 2]]);
|
|
4297
|
+
const argb = (c) => c && /^[0-9a-f]{8}$/i.test(c) ? c.toUpperCase() : c && /^[0-9a-f]{6}$/i.test(c) ? `FF${c.toUpperCase()}` : "";
|
|
4298
|
+
const fontOf = (bold, color) => {
|
|
4299
|
+
const key = `${bold ? 1 : 0}|${color}`;
|
|
4300
|
+
let i = fontIndex.get(key);
|
|
4301
|
+
if (i === void 0) {
|
|
4302
|
+
i = fonts.length;
|
|
4303
|
+
fonts.push(`<font>${bold ? "<b/>" : ""}<sz val="11"/>${color ? `<color rgb="${color}"/>` : ""}<name val="Calibri"/></font>`);
|
|
4304
|
+
fontIndex.set(key, i);
|
|
4305
|
+
}
|
|
4306
|
+
return i;
|
|
4307
|
+
};
|
|
4308
|
+
const fillOf = (color) => {
|
|
4309
|
+
if (!color) return 0;
|
|
4310
|
+
let i = fillIndex.get(color);
|
|
4311
|
+
if (i === void 0) {
|
|
4312
|
+
i = fills.length;
|
|
4313
|
+
fills.push(`<fill><patternFill patternType="solid"><fgColor rgb="${color}"/><bgColor indexed="64"/></patternFill></fill>`);
|
|
4314
|
+
fillIndex.set(color, i);
|
|
4315
|
+
}
|
|
4316
|
+
return i;
|
|
4317
|
+
};
|
|
4318
|
+
const styleOf = (fmt, st) => {
|
|
4319
|
+
const color = argb(st.color);
|
|
4320
|
+
const fill = st.header ? "FFF2F4F7" : argb(st.fill);
|
|
4321
|
+
const bold = !!(st.bold || st.header);
|
|
4322
|
+
const align = st.align === "left" || st.align === "center" || st.align === "right" ? st.align : "";
|
|
4323
|
+
const key = `${fmt}|${bold ? 1 : ""}|${color}|${fill}|${align}`;
|
|
3677
4324
|
let i = xfIndex.get(key);
|
|
3678
4325
|
if (i !== void 0) return i;
|
|
3679
4326
|
let id = fmtIds.get(fmt);
|
|
@@ -3682,8 +4329,11 @@ function writeXlsx(sheets) {
|
|
|
3682
4329
|
customFmts.push(fmt);
|
|
3683
4330
|
fmtIds.set(fmt, id);
|
|
3684
4331
|
}
|
|
4332
|
+
const fontId = fontOf(bold, color);
|
|
4333
|
+
const fillId = fillOf(fill);
|
|
4334
|
+
const attrs = `numFmtId="${id}" fontId="${fontId}" fillId="${fillId}" borderId="0" xfId="0"` + (id ? ' applyNumberFormat="1"' : "") + (fontId ? ' applyFont="1"' : "") + (fillId ? ' applyFill="1"' : "") + (align ? ' applyAlignment="1"' : "");
|
|
3685
4335
|
i = xfs.length;
|
|
3686
|
-
xfs.push(`<xf
|
|
4336
|
+
xfs.push(align ? `<xf ${attrs}><alignment horizontal="${align}"/></xf>` : `<xf ${attrs}/>`);
|
|
3687
4337
|
xfIndex.set(key, i);
|
|
3688
4338
|
return i;
|
|
3689
4339
|
};
|
|
@@ -3702,7 +4352,7 @@ function writeXlsx(sheets) {
|
|
|
3702
4352
|
v = toSerial(v);
|
|
3703
4353
|
}
|
|
3704
4354
|
}
|
|
3705
|
-
const s = styleOf(fmt,
|
|
4355
|
+
const s = styleOf(fmt, o);
|
|
3706
4356
|
const sa = s ? ` s="${s}"` : "";
|
|
3707
4357
|
if (v === null || v === void 0 || v === "") return sa ? `<c r="${ref}"${sa}/>` : "";
|
|
3708
4358
|
if (typeof v === "number") return Number.isFinite(v) ? `<c r="${ref}"${sa}><v>${v}</v></c>` : "";
|
|
@@ -3719,7 +4369,7 @@ function writeXlsx(sheets) {
|
|
|
3719
4369
|
const merges = sheet.merges?.length ? `<mergeCells count="${sheet.merges.length}">${sheet.merges.map((m) => `<mergeCell ref="${m}"/>`).join("")}</mergeCells>` : "";
|
|
3720
4370
|
return `${XML}<worksheet xmlns="${NS}" xmlns:r="${REL_NS}">${views}<sheetFormatPr defaultRowHeight="15"/>${cols}<sheetData>${rowsXml}</sheetData>${filter}${merges}</worksheet>`;
|
|
3721
4371
|
});
|
|
3722
|
-
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="
|
|
4372
|
+
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>`;
|
|
3723
4373
|
const files = [
|
|
3724
4374
|
{
|
|
3725
4375
|
name: "[Content_Types].xml",
|
|
@@ -3873,6 +4523,51 @@ function excelFormat(def) {
|
|
|
3873
4523
|
if (o.style === "percent") return `0${decimals}%`;
|
|
3874
4524
|
return `${o.useGrouping === false ? "0" : "#,##0"}${decimals}`;
|
|
3875
4525
|
}
|
|
4526
|
+
function toArgb(css) {
|
|
4527
|
+
const hex = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(css.trim());
|
|
4528
|
+
if (hex) {
|
|
4529
|
+
const h = hex[1].length === 3 ? [...hex[1]].map((c) => c + c).join("") : hex[1];
|
|
4530
|
+
return `FF${h.toUpperCase()}`;
|
|
4531
|
+
}
|
|
4532
|
+
const m = /^rgba?\(\s*(\d+)[\s,]+(\d+)[\s,]+(\d+)(?:[\s,/]+([\d.]+%?))?\s*\)$/i.exec(css.trim());
|
|
4533
|
+
if (!m) return "";
|
|
4534
|
+
const alpha = m[4] === void 0 ? 1 : m[4].endsWith("%") ? parseFloat(m[4]) / 100 : parseFloat(m[4]);
|
|
4535
|
+
if (!(alpha > 0)) return "";
|
|
4536
|
+
return `FF${[m[1], m[2], m[3]].map((n) => Math.min(255, Number(n)).toString(16).padStart(2, "0")).join("").toUpperCase()}`;
|
|
4537
|
+
}
|
|
4538
|
+
function classStyles(grid) {
|
|
4539
|
+
const root = grid.renderer.root;
|
|
4540
|
+
if (typeof getComputedStyle !== "function" || !root.isConnected) return { of: () => ({}), dispose: () => void 0 };
|
|
4541
|
+
const row = document.createElement("div");
|
|
4542
|
+
row.className = "fg-row";
|
|
4543
|
+
row.style.cssText = "position:absolute;left:-9999px;top:0;visibility:hidden;pointer-events:none";
|
|
4544
|
+
const cell = document.createElement("div");
|
|
4545
|
+
row.append(cell);
|
|
4546
|
+
root.append(row);
|
|
4547
|
+
const read = (cls) => {
|
|
4548
|
+
cell.className = cls ? `fg-cell ${cls}` : "fg-cell";
|
|
4549
|
+
const cs = getComputedStyle(cell);
|
|
4550
|
+
const w = cs.fontWeight;
|
|
4551
|
+
return { color: cs.color, fill: cs.backgroundColor, bold: w === "bold" || Number(w) >= 600 };
|
|
4552
|
+
};
|
|
4553
|
+
const base = read("");
|
|
4554
|
+
const cache2 = /* @__PURE__ */ new Map();
|
|
4555
|
+
return {
|
|
4556
|
+
of(classes) {
|
|
4557
|
+
let s = cache2.get(classes);
|
|
4558
|
+
if (!s) {
|
|
4559
|
+
const r = read(classes);
|
|
4560
|
+
s = {};
|
|
4561
|
+
if (r.color !== base.color && toArgb(r.color)) s.color = toArgb(r.color);
|
|
4562
|
+
if (r.fill !== base.fill && toArgb(r.fill)) s.fill = toArgb(r.fill);
|
|
4563
|
+
if (r.bold && !base.bold) s.bold = true;
|
|
4564
|
+
cache2.set(classes, s);
|
|
4565
|
+
}
|
|
4566
|
+
return s;
|
|
4567
|
+
},
|
|
4568
|
+
dispose: () => row.remove()
|
|
4569
|
+
};
|
|
4570
|
+
}
|
|
3876
4571
|
function buildXlsx(grid, opts = {}) {
|
|
3877
4572
|
const cols = exportColumns(grid, opts.columns);
|
|
3878
4573
|
const code = grid.store.state.locale.code;
|
|
@@ -3899,43 +4594,86 @@ function buildXlsx(grid, opts = {}) {
|
|
|
3899
4594
|
if (level < leafLevel) {
|
|
3900
4595
|
const g = chain[level];
|
|
3901
4596
|
const prev = ci > 0 ? chains[ci - 1][level] : null;
|
|
3902
|
-
row.push({ v: prev === g ? null : header(g.def, g.id),
|
|
4597
|
+
row.push({ v: prev === g ? null : header(g.def, g.id), header: true });
|
|
3903
4598
|
if (prev !== g) {
|
|
3904
4599
|
let end = ci;
|
|
3905
4600
|
while (end + 1 < cols.length && chains[end + 1][level] === g) end++;
|
|
3906
4601
|
if (end > ci) merges.push(`${name(ci)}${level + 1}:${name(end)}${level + 1}`);
|
|
3907
4602
|
}
|
|
3908
4603
|
} else if (level === leafLevel) {
|
|
3909
|
-
row.push({ v: header(cols[ci].def, cols[ci].id),
|
|
4604
|
+
row.push({ v: header(cols[ci].def, cols[ci].id), header: true });
|
|
3910
4605
|
if (headRows - 1 > level) merges.push(`${name(ci)}${level + 1}:${name(ci)}${headRows}`);
|
|
3911
|
-
} else row.push({ v: null,
|
|
4606
|
+
} else row.push({ v: null, header: true });
|
|
3912
4607
|
}
|
|
3913
4608
|
rows.push(row);
|
|
3914
4609
|
}
|
|
3915
4610
|
}
|
|
3916
4611
|
const fmts = cols.map((c) => excelFormat(c.def));
|
|
3917
4612
|
const formatters = cols.map((c) => getFormatter(c.def, code));
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
}
|
|
3928
|
-
case "date": {
|
|
3929
|
-
const d = toDate(v);
|
|
3930
|
-
return d ? { v: d, fmt } : String(v);
|
|
3931
|
-
}
|
|
3932
|
-
case "boolean":
|
|
3933
|
-
return typeof v === "boolean" ? v : String(v);
|
|
3934
|
-
default:
|
|
3935
|
-
return formatters[i](v);
|
|
4613
|
+
const data = exportRows(grid, opts.rows);
|
|
4614
|
+
const styled = opts.styles !== false;
|
|
4615
|
+
const probe = styled && cols.some((c) => c.def.cellClass) ? classStyles(grid) : null;
|
|
4616
|
+
const valueOf = (c, i, v) => {
|
|
4617
|
+
if (isBlank(v)) return null;
|
|
4618
|
+
const fmt = fmts[i] ?? void 0;
|
|
4619
|
+
switch (c.def.type) {
|
|
4620
|
+
case "number": {
|
|
4621
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
4622
|
+
return Number.isFinite(n) ? { v: n, fmt } : String(v);
|
|
3936
4623
|
}
|
|
3937
|
-
|
|
4624
|
+
case "date": {
|
|
4625
|
+
const d = toDate(v);
|
|
4626
|
+
return d ? { v: d, fmt } : String(v);
|
|
4627
|
+
}
|
|
4628
|
+
case "boolean":
|
|
4629
|
+
return typeof v === "boolean" ? v : String(v);
|
|
4630
|
+
default:
|
|
4631
|
+
return formatters[i](v);
|
|
4632
|
+
}
|
|
4633
|
+
};
|
|
4634
|
+
try {
|
|
4635
|
+
data.forEach((r, ri) => {
|
|
4636
|
+
rows.push(cols.map((c, i) => {
|
|
4637
|
+
const v = getCellValue(c.def, r);
|
|
4638
|
+
const cell = valueOf(c, i, v);
|
|
4639
|
+
if (!styled) return cell;
|
|
4640
|
+
let style = c.def.align ? { align: c.def.align } : {};
|
|
4641
|
+
if (probe && c.def.cellClass) {
|
|
4642
|
+
let cls;
|
|
4643
|
+
try {
|
|
4644
|
+
cls = typeof c.def.cellClass === "function" ? c.def.cellClass({ value: v, formatted: formatters[i](v), row: r, rowIndex: ri, column: c.def }) : c.def.cellClass;
|
|
4645
|
+
} catch {
|
|
4646
|
+
cls = void 0;
|
|
4647
|
+
}
|
|
4648
|
+
if (cls) style = { ...style, ...probe.of(cls) };
|
|
4649
|
+
}
|
|
4650
|
+
if (!Object.keys(style).length) return cell;
|
|
4651
|
+
return cell !== null && typeof cell === "object" && !(cell instanceof Date) ? { ...cell, ...style } : { v: cell, ...style };
|
|
4652
|
+
}));
|
|
4653
|
+
});
|
|
4654
|
+
} finally {
|
|
4655
|
+
probe?.dispose();
|
|
3938
4656
|
}
|
|
4657
|
+
const taken = /* @__PURE__ */ new Set();
|
|
4658
|
+
const letter = (i) => {
|
|
4659
|
+
let out = "";
|
|
4660
|
+
for (let n = i + 1; n > 0; n = Math.floor((n - 1) / 26)) out = String.fromCharCode(65 + (n - 1) % 26) + out;
|
|
4661
|
+
return out;
|
|
4662
|
+
};
|
|
4663
|
+
const mergeRange = (r1, c1, r2, c2) => {
|
|
4664
|
+
for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) if (taken.has(`${r},${c}`)) return;
|
|
4665
|
+
for (let r = r1; r <= r2; r++) for (let c = c1; c <= c2; c++) taken.add(`${r},${c}`);
|
|
4666
|
+
merges.push(`${letter(c1)}${headRows + r1 + 1}:${letter(c2)}${headRows + r2 + 1}`);
|
|
4667
|
+
};
|
|
4668
|
+
data.forEach((row, i) => cols.forEach((c, ci) => {
|
|
4669
|
+
const n = c.def.colSpan ? spanOf(c.def.colSpan, c.def, row, i, cols.length - ci) : 1;
|
|
4670
|
+
if (n > 1) mergeRange(i, ci, i, ci + n - 1);
|
|
4671
|
+
}));
|
|
4672
|
+
cols.forEach((c, ci) => {
|
|
4673
|
+
if (!c.def.merge && !c.def.rowSpan) return;
|
|
4674
|
+
const s = computeSpans(data, c.def);
|
|
4675
|
+
for (let i = 0; i < data.length; i++) if (s.start[i] === i && s.end[i] > i) mergeRange(i, ci, s.end[i], ci);
|
|
4676
|
+
});
|
|
3939
4677
|
return writeXlsx([{
|
|
3940
4678
|
name: opts.sheetName ?? "Sheet1",
|
|
3941
4679
|
rows,
|
|
@@ -3978,8 +4716,8 @@ async function importInto(grid, file, opts = {}) {
|
|
|
3978
4716
|
if (!s) throw new Error(`Sheet ${String(opts.sheet)} not found`);
|
|
3979
4717
|
matrix = s.rows;
|
|
3980
4718
|
} else {
|
|
3981
|
-
const
|
|
3982
|
-
matrix = parseCsv(
|
|
4719
|
+
const text2 = new TextDecoder().decode(bytes);
|
|
4720
|
+
matrix = parseCsv(text2, detectSeparator(text2));
|
|
3983
4721
|
}
|
|
3984
4722
|
if (!matrix.length) return { rows: [], skipped: 0 };
|
|
3985
4723
|
const leaves = [];
|
|
@@ -4041,9 +4779,9 @@ function svg(tag, attrs = {}, cls = "") {
|
|
|
4041
4779
|
for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, String(v));
|
|
4042
4780
|
return node;
|
|
4043
4781
|
}
|
|
4044
|
-
function tip(node,
|
|
4782
|
+
function tip(node, text2) {
|
|
4045
4783
|
const t = svg("title");
|
|
4046
|
-
t.textContent =
|
|
4784
|
+
t.textContent = text2;
|
|
4047
4785
|
node.append(t);
|
|
4048
4786
|
}
|
|
4049
4787
|
function niceTicks(min, max, count = 5) {
|
|
@@ -4479,7 +5217,7 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
4479
5217
|
}
|
|
4480
5218
|
|
|
4481
5219
|
// src/version.ts
|
|
4482
|
-
var version = "0.
|
|
5220
|
+
var version = "0.8.1";
|
|
4483
5221
|
|
|
4484
5222
|
// src/license.ts
|
|
4485
5223
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -4497,8 +5235,12 @@ function isDevHost(host) {
|
|
|
4497
5235
|
function domainMatches(host, domains) {
|
|
4498
5236
|
const h = host.toLowerCase().replace(/\.$/, "");
|
|
4499
5237
|
return domains.some((d) => {
|
|
4500
|
-
const
|
|
4501
|
-
|
|
5238
|
+
const raw = d.toLowerCase().replace(/\.$/, "");
|
|
5239
|
+
const wildcard = raw.startsWith("*.");
|
|
5240
|
+
const dom = wildcard ? raw.slice(2) : raw;
|
|
5241
|
+
if (!dom) return false;
|
|
5242
|
+
if (h === dom || h === `www.${dom}`) return true;
|
|
5243
|
+
return wildcard && h.endsWith(`.${dom}`);
|
|
4502
5244
|
});
|
|
4503
5245
|
}
|
|
4504
5246
|
var today = (now) => `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
|
|
@@ -4660,7 +5402,7 @@ var ColumnMenu = class {
|
|
|
4660
5402
|
popup.append(this.section(items));
|
|
4661
5403
|
const filter = this.filterSection(colId, () => this.close());
|
|
4662
5404
|
if (filter) popup.append(filter);
|
|
4663
|
-
|
|
5405
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
4664
5406
|
const r = anchor.getBoundingClientRect();
|
|
4665
5407
|
const w = popup.offsetWidth || 248;
|
|
4666
5408
|
popup.style.left = `${Math.max(4, Math.min(r.right - w, window.innerWidth - w - 4))}px`;
|
|
@@ -4670,13 +5412,12 @@ var ColumnMenu = class {
|
|
|
4670
5412
|
this.popup = popup;
|
|
4671
5413
|
this.colId = colId;
|
|
4672
5414
|
const onDown = (e) => {
|
|
4673
|
-
|
|
4674
|
-
if (!popup.contains(t) && !anchor.contains(t)) this.close();
|
|
5415
|
+
if (!eventInside(e, popup, anchor)) this.close();
|
|
4675
5416
|
};
|
|
4676
5417
|
const onKey = (e) => {
|
|
4677
5418
|
if (e.key !== "Escape") return;
|
|
4678
5419
|
this.close();
|
|
4679
|
-
this.grid.renderer.
|
|
5420
|
+
this.grid.renderer.gridEl.focus();
|
|
4680
5421
|
};
|
|
4681
5422
|
document.addEventListener("pointerdown", onDown, true);
|
|
4682
5423
|
document.addEventListener("keydown", onKey, true);
|
|
@@ -4940,19 +5681,26 @@ var RowModel = class {
|
|
|
4940
5681
|
if (!this.indexById) this.indexById = new Map(this.display.map((n, i) => [n.id, i]));
|
|
4941
5682
|
return this.indexById.get(id) ?? -1;
|
|
4942
5683
|
}
|
|
4943
|
-
/**
|
|
4944
|
-
|
|
5684
|
+
/**
|
|
5685
|
+
* Prefix-sum offsets of the display rows: detail rows use `detailHeight`, rows with a
|
|
5686
|
+
* measured height (auto row height) use it, everything else `rowHeight`.
|
|
5687
|
+
*/
|
|
5688
|
+
layout(rowHeight, detailHeight, heights, heightsVersion = 0) {
|
|
4945
5689
|
const c = this.layoutCache;
|
|
4946
|
-
if (c && c.rows === this.display && c.rh === rowHeight && c.dh === detailHeight) return c;
|
|
5690
|
+
if (c && c.rows === this.display && c.rh === rowHeight && c.dh === detailHeight && c.hv === heightsVersion) return c;
|
|
4947
5691
|
const n = this.display.length;
|
|
4948
5692
|
const offsets = new Float64Array(n + 1);
|
|
4949
5693
|
let uniform = true;
|
|
5694
|
+
const measured = heights && heights.size ? heights : null;
|
|
4950
5695
|
for (let i = 0; i < n; i++) {
|
|
4951
|
-
const
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
5696
|
+
const node = this.display[i];
|
|
5697
|
+
let h = rowHeight;
|
|
5698
|
+
if (node.kind === "detail") h = detailHeight;
|
|
5699
|
+
else if (measured && !node.kind) h = measured.get(node.id) ?? rowHeight;
|
|
5700
|
+
if (h !== rowHeight) uniform = false;
|
|
5701
|
+
offsets[i + 1] = offsets[i] + h;
|
|
5702
|
+
}
|
|
5703
|
+
this.layoutCache = { rows: this.display, rh: rowHeight, dh: detailHeight, hv: heightsVersion, offsets, uniform };
|
|
4956
5704
|
return this.layoutCache;
|
|
4957
5705
|
}
|
|
4958
5706
|
filter(input) {
|
|
@@ -4963,8 +5711,8 @@ var RowModel = class {
|
|
|
4963
5711
|
const matches = this.nodes.filter((n) => {
|
|
4964
5712
|
for (const p of preds) if (!p.pred(getCellValue(p.def, n.data))) return false;
|
|
4965
5713
|
if (tokens.length) {
|
|
4966
|
-
const
|
|
4967
|
-
if (!matchesQuick(tokens,
|
|
5714
|
+
const text2 = quickCols.map((d) => this.deps.format(d, getCellValue(d, n.data))).join("").toLowerCase();
|
|
5715
|
+
if (!matchesQuick(tokens, text2)) return false;
|
|
4968
5716
|
}
|
|
4969
5717
|
return true;
|
|
4970
5718
|
});
|
|
@@ -5059,8 +5807,10 @@ var RowModel = class {
|
|
|
5059
5807
|
for (const n of nodes) {
|
|
5060
5808
|
if (seen2.has(n.id)) continue;
|
|
5061
5809
|
const kids = children.get(n.id) ?? [];
|
|
5810
|
+
const lazy = !kids.length && !!d.lazyChildren?.(n.id, n.data);
|
|
5062
5811
|
const expanded = kids.length > 0 && this.isOpen(d, n.id, level);
|
|
5063
|
-
const node = { ...n, level, hasChildren: kids.length > 0, expanded };
|
|
5812
|
+
const node = { ...n, level, hasChildren: kids.length > 0 || lazy, expanded };
|
|
5813
|
+
if (lazy && d.loading?.has(n.id)) node.loading = true;
|
|
5064
5814
|
this.withDetails(d, [node], out);
|
|
5065
5815
|
if (expanded) visit(kids, level + 1, /* @__PURE__ */ new Set([...seen2, n.id]));
|
|
5066
5816
|
}
|
|
@@ -5328,6 +6078,17 @@ var Grid = class {
|
|
|
5328
6078
|
/** 0-based page and rows per page (pagination only). */
|
|
5329
6079
|
this.page = 0;
|
|
5330
6080
|
this.pageSize = DEFAULT_PAGE_SIZE;
|
|
6081
|
+
/** Tree rows whose children were loaded (or are loading) through treeData.loadChildren. */
|
|
6082
|
+
this.treeLoaded = /* @__PURE__ */ new Set();
|
|
6083
|
+
this.treeLoading = /* @__PURE__ */ new Set();
|
|
6084
|
+
this.lazyChildren = (id, row) => {
|
|
6085
|
+
if (this.treeLoaded.has(id)) return false;
|
|
6086
|
+
try {
|
|
6087
|
+
return !!this.options.treeData?.hasChildren?.(row);
|
|
6088
|
+
} catch {
|
|
6089
|
+
return false;
|
|
6090
|
+
}
|
|
6091
|
+
};
|
|
5331
6092
|
if (!Array.isArray(options.columns)) warn("options.columns must be an array");
|
|
5332
6093
|
if (options.data != null && !Array.isArray(options.data)) warn("options.data must be an array");
|
|
5333
6094
|
this.store = new Store({
|
|
@@ -5339,6 +6100,7 @@ var Grid = class {
|
|
|
5339
6100
|
anchorId: null,
|
|
5340
6101
|
focus: null,
|
|
5341
6102
|
range: null,
|
|
6103
|
+
extraRanges: [],
|
|
5342
6104
|
locale: resolveLocale(options.locale),
|
|
5343
6105
|
theme: options.theme ?? "light"
|
|
5344
6106
|
});
|
|
@@ -5415,6 +6177,7 @@ var Grid = class {
|
|
|
5415
6177
|
});
|
|
5416
6178
|
this.renderer.render();
|
|
5417
6179
|
this.api = this.createApi();
|
|
6180
|
+
liveGrids.set(this.renderer.root, this);
|
|
5418
6181
|
}
|
|
5419
6182
|
collator() {
|
|
5420
6183
|
const code = this.store.state.locale.code;
|
|
@@ -5425,6 +6188,7 @@ var Grid = class {
|
|
|
5425
6188
|
}
|
|
5426
6189
|
/** Column widths/order/pinning changed; rows are unaffected. */
|
|
5427
6190
|
layoutChanged() {
|
|
6191
|
+
this.renderer.invalidateHeights();
|
|
5428
6192
|
this.renderer.invalidate({ header: true, rows: true });
|
|
5429
6193
|
this.refreshChoosers();
|
|
5430
6194
|
}
|
|
@@ -5445,6 +6209,8 @@ var Grid = class {
|
|
|
5445
6209
|
defaultExpanded: this.defaultExpanded,
|
|
5446
6210
|
detailOpen: this.options.detailRenderer ? this.detailOpen : /* @__PURE__ */ new Set(),
|
|
5447
6211
|
isMaster: this.options.isRowMaster,
|
|
6212
|
+
lazyChildren: this.options.treeData?.hasChildren ? this.lazyChildren : void 0,
|
|
6213
|
+
loading: this.treeLoading,
|
|
5448
6214
|
version: this.displayVersion
|
|
5449
6215
|
};
|
|
5450
6216
|
}
|
|
@@ -5562,6 +6328,7 @@ var Grid = class {
|
|
|
5562
6328
|
if (!this.options.pagination || !Number.isFinite(page)) return;
|
|
5563
6329
|
const next = Math.max(0, Math.min(this.getPageCount() - 1, Math.floor(page)));
|
|
5564
6330
|
if (next === this.page) return;
|
|
6331
|
+
if (this.veto("beforePageChange", { page: next, pageSize: this.pageSize })) return;
|
|
5565
6332
|
this.page = next;
|
|
5566
6333
|
this.pageMoved();
|
|
5567
6334
|
}
|
|
@@ -5573,6 +6340,7 @@ var Grid = class {
|
|
|
5573
6340
|
}
|
|
5574
6341
|
if (size === this.pageSize) return;
|
|
5575
6342
|
const first = this.page * this.pageSize;
|
|
6343
|
+
if (this.veto("beforePageChange", { page: Math.floor(first / size), pageSize: size })) return;
|
|
5576
6344
|
this.pageSize = size;
|
|
5577
6345
|
this.page = Math.floor(first / size);
|
|
5578
6346
|
this.pageMoved();
|
|
@@ -5612,6 +6380,35 @@ var Grid = class {
|
|
|
5612
6380
|
this.renderer.invalidate({ rows: true });
|
|
5613
6381
|
this.events.emit("rowsLoadFailed", { start, end, error });
|
|
5614
6382
|
}
|
|
6383
|
+
/**
|
|
6384
|
+
* Scroll `other` along with this grid and back. The follower's echo of a position we set is not
|
|
6385
|
+
* sent back, so a shorter grid that stops at its end does not pull the longer one back.
|
|
6386
|
+
*/
|
|
6387
|
+
syncScroll(other, axis) {
|
|
6388
|
+
if (!other || typeof other.setScrollPosition !== "function" || other === this.api) return () => void 0;
|
|
6389
|
+
const written = /* @__PURE__ */ new Map();
|
|
6390
|
+
const key = (p) => `${p.top},${p.left}`;
|
|
6391
|
+
const link = (from, to) => from.on("scroll", (p) => {
|
|
6392
|
+
if (written.get(from) === key(p)) {
|
|
6393
|
+
written.delete(from);
|
|
6394
|
+
return;
|
|
6395
|
+
}
|
|
6396
|
+
to.setScrollPosition(axis === "vertical" ? { top: p.top } : axis === "horizontal" ? { left: p.left } : p);
|
|
6397
|
+
written.set(to, key(to.getScrollPosition()));
|
|
6398
|
+
});
|
|
6399
|
+
const offs = [link(this.api, other), link(other, this.api)];
|
|
6400
|
+
return () => {
|
|
6401
|
+
for (const off of offs) off();
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
/** Emit a before-event; true when a listener called cancel(). */
|
|
6405
|
+
veto(type, payload) {
|
|
6406
|
+
let cancelled = false;
|
|
6407
|
+
this.events.emit(type, { ...payload, cancel: () => {
|
|
6408
|
+
cancelled = true;
|
|
6409
|
+
} });
|
|
6410
|
+
return cancelled;
|
|
6411
|
+
}
|
|
5615
6412
|
/** Client-only operations log once and do nothing with a datasource. */
|
|
5616
6413
|
clientOnly(what) {
|
|
5617
6414
|
if (!this.rowModel.server) return false;
|
|
@@ -5661,10 +6458,45 @@ var Grid = class {
|
|
|
5661
6458
|
expandKey(node) {
|
|
5662
6459
|
return node.group ? node.group.key : node.id;
|
|
5663
6460
|
}
|
|
6461
|
+
/** First expand of a lazy tree row: fetch its children, then show them expanded. */
|
|
6462
|
+
loadChildren(node) {
|
|
6463
|
+
const tree = this.options.treeData;
|
|
6464
|
+
const id = node.id;
|
|
6465
|
+
if (this.treeLoading.has(id)) return;
|
|
6466
|
+
if (typeof tree.loadChildren !== "function") {
|
|
6467
|
+
warn("treeData.hasChildren needs treeData.loadChildren");
|
|
6468
|
+
return;
|
|
6469
|
+
}
|
|
6470
|
+
this.treeLoading.add(id);
|
|
6471
|
+
this.displayChanged();
|
|
6472
|
+
const done = () => {
|
|
6473
|
+
this.treeLoading.delete(id);
|
|
6474
|
+
return !this.destroyed && this.store.state.data.includes(node.data);
|
|
6475
|
+
};
|
|
6476
|
+
Promise.resolve().then(() => tree.loadChildren(node.data)).then((rows) => {
|
|
6477
|
+
if (!done()) return;
|
|
6478
|
+
const kids = cleanRows(Array.isArray(rows) ? rows : []);
|
|
6479
|
+
this.treeLoaded.add(id);
|
|
6480
|
+
this.expanded.set(id, true);
|
|
6481
|
+
if (kids.length) this.replaceData([...this.store.state.data, ...kids]);
|
|
6482
|
+
this.displayChanged();
|
|
6483
|
+
this.events.emit("childrenLoaded", { row: node.data, rows: kids });
|
|
6484
|
+
this.events.emit("groupToggle", { key: id, expanded: true });
|
|
6485
|
+
}, (error) => {
|
|
6486
|
+
if (!done()) return;
|
|
6487
|
+
warn(`treeData.loadChildren failed for row "${id}"`);
|
|
6488
|
+
this.displayChanged();
|
|
6489
|
+
this.events.emit("childrenLoadFailed", { row: node.data, error });
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
5664
6492
|
setGroupExpanded(key, open) {
|
|
5665
6493
|
const node = this.rowModel.rows().find((n) => this.isExpandable(n) && this.expandKey(n) === key);
|
|
5666
6494
|
const cur = node ? !!(node.group?.expanded ?? node.expanded) : this.expanded.get(key);
|
|
5667
6495
|
if (cur === open) return;
|
|
6496
|
+
if (open && node && !node.group && this.options.treeData && this.lazyChildren(node.id, node.data)) {
|
|
6497
|
+
this.loadChildren(node);
|
|
6498
|
+
return;
|
|
6499
|
+
}
|
|
5668
6500
|
this.expanded.set(key, open);
|
|
5669
6501
|
this.displayChanged();
|
|
5670
6502
|
this.events.emit("groupToggle", { key, expanded: open });
|
|
@@ -5723,7 +6555,113 @@ var Grid = class {
|
|
|
5723
6555
|
/** Row order can only follow the source data when nothing reorders or hides rows. */
|
|
5724
6556
|
canMoveRows() {
|
|
5725
6557
|
const s = this.store.state;
|
|
5726
|
-
|
|
6558
|
+
const tree = this.options.treeData;
|
|
6559
|
+
return !this.rowModel.server && !s.sort.length && !Object.keys(s.filters).length && !s.quickFilter.trim() && !this.groupBy.length && (!tree || typeof tree.setParentId === "function");
|
|
6560
|
+
}
|
|
6561
|
+
get dragOptions() {
|
|
6562
|
+
const o = this.options.rowDrag;
|
|
6563
|
+
return o && typeof o === "object" ? o : {};
|
|
6564
|
+
}
|
|
6565
|
+
parentOf(row) {
|
|
6566
|
+
let p = null;
|
|
6567
|
+
try {
|
|
6568
|
+
p = this.options.treeData.getParentId(row);
|
|
6569
|
+
} catch {
|
|
6570
|
+
p = null;
|
|
6571
|
+
}
|
|
6572
|
+
return p == null ? null : String(p);
|
|
6573
|
+
}
|
|
6574
|
+
rawParent(row) {
|
|
6575
|
+
try {
|
|
6576
|
+
return this.options.treeData.getParentId(row) ?? null;
|
|
6577
|
+
} catch {
|
|
6578
|
+
return null;
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
/** The id of `row` as the data stores parent ids: a number when other rows hold numeric parent ids. */
|
|
6582
|
+
idValue(row) {
|
|
6583
|
+
const id = this.rowModel.idOf(row);
|
|
6584
|
+
for (const r of this.store.state.data) {
|
|
6585
|
+
const p = this.rawParent(r);
|
|
6586
|
+
if (p !== null && String(p) === id) return p;
|
|
6587
|
+
}
|
|
6588
|
+
const numeric = this.store.state.data.some((r) => typeof this.rawParent(r) === "number");
|
|
6589
|
+
return numeric && id.trim() !== "" && Number.isFinite(Number(id)) ? Number(id) : id;
|
|
6590
|
+
}
|
|
6591
|
+
/** Can `row` land here? Trees refuse the row's own subtree; then the user's canDrop rule decides. */
|
|
6592
|
+
canDropRow(row, target, position, from = this) {
|
|
6593
|
+
if (!this.canMoveRows() || target === row) return false;
|
|
6594
|
+
if (from === this && this.options.treeData && target) {
|
|
6595
|
+
const own = this.rowModel.idOf(row);
|
|
6596
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
6597
|
+
for (let id = this.rowModel.idOf(target); id !== null && !seen2.has(id); ) {
|
|
6598
|
+
if (id === own) return false;
|
|
6599
|
+
seen2.add(id);
|
|
6600
|
+
const node = this.rowModel.allNodes().find((n) => n.id === id);
|
|
6601
|
+
id = node ? this.parentOf(node.data) : null;
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6604
|
+
const rule = this.dragOptions.canDrop;
|
|
6605
|
+
if (!rule) return true;
|
|
6606
|
+
try {
|
|
6607
|
+
return rule({ row, target, position, fromOtherGrid: from !== this }) !== false;
|
|
6608
|
+
} catch {
|
|
6609
|
+
warn("rowDrag.canDrop threw; the drop was refused");
|
|
6610
|
+
return false;
|
|
6611
|
+
}
|
|
6612
|
+
}
|
|
6613
|
+
/**
|
|
6614
|
+
* Move `row` before, after or inside `target` (null = to the end). In tree data the row takes the
|
|
6615
|
+
* target's parent, or the target itself for 'inside'. `from` is the grid the row is dragged out of.
|
|
6616
|
+
*/
|
|
6617
|
+
dropRow(row, target, position = "after", from = this) {
|
|
6618
|
+
if (this.clientOnly("dropRow") || !row || typeof row !== "object") return false;
|
|
6619
|
+
const tree = this.options.treeData;
|
|
6620
|
+
if (!tree && position === "inside") position = "after";
|
|
6621
|
+
const data = this.store.state.data;
|
|
6622
|
+
const external = from !== this;
|
|
6623
|
+
const fromIndex = external ? -1 : data.indexOf(row);
|
|
6624
|
+
if (!external && fromIndex < 0) return false;
|
|
6625
|
+
if (target && !data.includes(target)) return false;
|
|
6626
|
+
if (external && (!from.canMoveRows() || data.includes(row))) return false;
|
|
6627
|
+
if (!this.canDropRow(row, target, position, from)) return false;
|
|
6628
|
+
const oldRaw = tree ? this.rawParent(row) : null;
|
|
6629
|
+
const parentRow = !tree || !target ? null : position === "inside" ? target : this.store.state.data.find((r) => this.rowModel.idOf(r) === this.parentOf(target)) ?? null;
|
|
6630
|
+
const newRaw = parentRow ? this.idValue(parentRow) : null;
|
|
6631
|
+
const next = data.filter((r) => r !== row);
|
|
6632
|
+
const at = !target || position === "inside" ? next.length : next.indexOf(target) + (position === "after" ? 1 : 0);
|
|
6633
|
+
next.splice(at, 0, row);
|
|
6634
|
+
const moved = next.some((r, i) => r !== data[i]);
|
|
6635
|
+
if (!external && !moved && String(oldRaw ?? "") === String(newRaw ?? "")) return false;
|
|
6636
|
+
const setParent = (raw, parent) => {
|
|
6637
|
+
try {
|
|
6638
|
+
tree.setParentId(row, raw, parent);
|
|
6639
|
+
} catch {
|
|
6640
|
+
warn("treeData.setParentId threw");
|
|
6641
|
+
}
|
|
6642
|
+
};
|
|
6643
|
+
const oldParentRow = tree ? this.store.state.data.find((r) => this.rowModel.idOf(r) === this.parentOf(row)) ?? null : null;
|
|
6644
|
+
if (external) {
|
|
6645
|
+
from.removeRows([row]);
|
|
6646
|
+
if (tree) setParent(newRaw, parentRow);
|
|
6647
|
+
this.addRows([row], at);
|
|
6648
|
+
} else {
|
|
6649
|
+
if (tree) setParent(newRaw, parentRow);
|
|
6650
|
+
this.replaceData(next);
|
|
6651
|
+
this.history.push({
|
|
6652
|
+
undo: () => {
|
|
6653
|
+
if (tree) setParent(oldRaw, oldParentRow);
|
|
6654
|
+
this.replaceData(data);
|
|
6655
|
+
},
|
|
6656
|
+
redo: () => {
|
|
6657
|
+
if (tree) setParent(newRaw, parentRow);
|
|
6658
|
+
this.replaceData(next);
|
|
6659
|
+
}
|
|
6660
|
+
});
|
|
6661
|
+
this.events.emit("rowDragEnd", { row, fromIndex, toIndex: at });
|
|
6662
|
+
}
|
|
6663
|
+
this.events.emit("rowDrop", { row, target, position, fromOtherGrid: external, fromGrid: external ? from.api : null });
|
|
6664
|
+
return true;
|
|
5727
6665
|
}
|
|
5728
6666
|
moveRow(row, toIndex, record = true) {
|
|
5729
6667
|
if (this.clientOnly("moveRow")) return false;
|
|
@@ -5753,6 +6691,7 @@ var Grid = class {
|
|
|
5753
6691
|
if (this.options.rowHeight == null) this.renderer.opts.rowHeight = autoRowHeight(effective);
|
|
5754
6692
|
if (this.options.headerHeight == null) this.renderer.opts.headerHeight = autoHeaderHeight(effective);
|
|
5755
6693
|
this.renderer.applyFont(size, family);
|
|
6694
|
+
this.renderer.invalidateHeights();
|
|
5756
6695
|
this.menu.close();
|
|
5757
6696
|
this.layoutChanged();
|
|
5758
6697
|
}
|
|
@@ -5788,30 +6727,55 @@ var Grid = class {
|
|
|
5788
6727
|
dataColumns() {
|
|
5789
6728
|
return this.columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
5790
6729
|
}
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
6730
|
+
validRange(range) {
|
|
6731
|
+
if (!range || this.options.rangeSelection === false) return null;
|
|
6732
|
+
const count = this.rowModel.getRowCount();
|
|
6733
|
+
const ok = (p) => !!p && !isSystemCol(p.colId) && !!this.columns.getColumn(p.colId) && p.rowIndex >= 0 && p.rowIndex < count;
|
|
6734
|
+
return ok(range.start) && ok(range.end) ? { start: { ...range.start }, end: { ...range.end } } : null;
|
|
6735
|
+
}
|
|
6736
|
+
/** Set the active range. Earlier Ctrl ranges are dropped unless `keepOthers`. */
|
|
6737
|
+
setRange(range, keepOthers = false) {
|
|
6738
|
+
const next = this.validRange(range);
|
|
6739
|
+
const extras = keepOthers || !this.store.state.extraRanges.length ? this.store.state.extraRanges : [];
|
|
5798
6740
|
const cur = this.store.state.range;
|
|
5799
|
-
if (JSON.stringify(cur) === JSON.stringify(next)) return;
|
|
5800
|
-
this.store.set({ range: next });
|
|
6741
|
+
if (JSON.stringify(cur) === JSON.stringify(next) && extras === this.store.state.extraRanges) return;
|
|
6742
|
+
this.store.set({ range: next, extraRanges: extras });
|
|
5801
6743
|
this.events.emit("rangeChange", { range: next && { start: { ...next.start }, end: { ...next.end } } });
|
|
5802
6744
|
}
|
|
5803
|
-
/**
|
|
5804
|
-
|
|
5805
|
-
const
|
|
6745
|
+
/** Ctrl+click / Ctrl+drag: keep what is selected (the focused cell when there is no range) and start a new range. */
|
|
6746
|
+
addRange(range) {
|
|
6747
|
+
const { range: active, focus } = this.store.state;
|
|
6748
|
+
const keep = this.validRange(active ?? (focus ? { start: focus, end: focus } : null));
|
|
6749
|
+
if (keep) this.store.set({ extraRanges: [...this.store.state.extraRanges, keep] });
|
|
6750
|
+
this.setRange(range, true);
|
|
6751
|
+
}
|
|
6752
|
+
getRanges() {
|
|
6753
|
+
const all2 = [...this.store.state.extraRanges];
|
|
6754
|
+
if (this.store.state.range) all2.push(this.store.state.range);
|
|
6755
|
+
return all2.map((r) => ({ start: { ...r.start }, end: { ...r.end } }));
|
|
6756
|
+
}
|
|
6757
|
+
setRanges(ranges) {
|
|
6758
|
+
const list = (Array.isArray(ranges) ? ranges : []).map((r) => this.validRange(r)).filter((r) => !!r);
|
|
6759
|
+
this.store.set({ extraRanges: list.slice(0, -1) });
|
|
6760
|
+
this.setRange(list[list.length - 1] ?? null, true);
|
|
6761
|
+
}
|
|
6762
|
+
boundsOf(r) {
|
|
5806
6763
|
if (!r) return null;
|
|
5807
6764
|
const a = this.columns.getColumn(r.start.colId), b = this.columns.getColumn(r.end.colId);
|
|
5808
6765
|
if (!a || !b) return null;
|
|
5809
6766
|
return normalizeRange({ start: { rowIndex: r.start.rowIndex, colIndex: a.index }, end: { rowIndex: r.end.rowIndex, colIndex: b.index } });
|
|
5810
6767
|
}
|
|
6768
|
+
/** Active range in displayed column indexes, or null. */
|
|
6769
|
+
rangeBounds() {
|
|
6770
|
+
return this.boundsOf(this.store.state.range);
|
|
6771
|
+
}
|
|
6772
|
+
/** Every range (Ctrl ranges first, the active one last). */
|
|
6773
|
+
allRangeBounds() {
|
|
6774
|
+
return [...this.store.state.extraRanges, this.store.state.range].map((r) => this.boundsOf(r)).filter((b) => !!b);
|
|
6775
|
+
}
|
|
5811
6776
|
rangeContains(rowIndex, colId) {
|
|
5812
|
-
const b = this.rangeBounds();
|
|
5813
6777
|
const col = this.columns.getColumn(colId);
|
|
5814
|
-
return !!
|
|
6778
|
+
return !!col && this.allRangeBounds().some((b) => rangeContains(b, rowIndex, col.index));
|
|
5815
6779
|
}
|
|
5816
6780
|
selectAllCells() {
|
|
5817
6781
|
const cols = this.dataColumns();
|
|
@@ -5829,6 +6793,18 @@ var Grid = class {
|
|
|
5829
6793
|
const checkedRows = rows;
|
|
5830
6794
|
let out;
|
|
5831
6795
|
let cols;
|
|
6796
|
+
const all2 = rows ? [] : this.allRangeBounds();
|
|
6797
|
+
if (all2.length > 1) {
|
|
6798
|
+
const sameCols = all2.every((x) => x.c1 === all2[0].c1 && x.c2 === all2[0].c2);
|
|
6799
|
+
const sameRows = all2.every((x) => x.r1 === all2[0].r1 && x.r2 === all2[0].r2);
|
|
6800
|
+
if (sameCols || sameRows) {
|
|
6801
|
+
const parts = [...all2].sort((x, y) => sameCols ? x.r1 - y.r1 : x.c1 - y.c1);
|
|
6802
|
+
const blocks = parts.map((p) => this.rangeMatrix(p));
|
|
6803
|
+
const matrix2 = sameCols ? blocks.flat() : blocks[0].map((_, ri) => blocks.flatMap((bl) => bl[ri] ?? []));
|
|
6804
|
+
if (headers) matrix2.unshift((sameCols ? parts.slice(0, 1) : parts).flatMap((p) => this.rangeHeaders(p)));
|
|
6805
|
+
return toTSV(matrix2);
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
5832
6808
|
if (b) {
|
|
5833
6809
|
out = [];
|
|
5834
6810
|
for (let i = b.r1; i <= b.r2; i++) {
|
|
@@ -5848,22 +6824,51 @@ var Grid = class {
|
|
|
5848
6824
|
out = [node.data];
|
|
5849
6825
|
cols = [col];
|
|
5850
6826
|
}
|
|
5851
|
-
const matrix = out.map((r) => cols.map((c) =>
|
|
6827
|
+
const matrix = out.map((r) => cols.map((c) => this.copyCell(c.def, r, code)));
|
|
5852
6828
|
if (headers) matrix.unshift(cols.map((c) => c.def.header ?? c.def.field ?? c.id));
|
|
5853
6829
|
return toTSV(matrix);
|
|
5854
6830
|
}
|
|
6831
|
+
rangeHeaders(b) {
|
|
6832
|
+
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);
|
|
6833
|
+
}
|
|
6834
|
+
/** Copy text of one range. */
|
|
6835
|
+
rangeMatrix(b) {
|
|
6836
|
+
const cols = this.columns.displayed().slice(b.c1, b.c2 + 1).filter((c) => !isSystemCol(c.id));
|
|
6837
|
+
const code = this.store.state.locale.code;
|
|
6838
|
+
const out = [];
|
|
6839
|
+
for (let i = b.r1; i <= b.r2; i++) {
|
|
6840
|
+
const node = this.rowModel.getRow(i);
|
|
6841
|
+
if (node && !node.kind) out.push(cols.map((c) => this.copyCell(c.def, node.data, code)));
|
|
6842
|
+
}
|
|
6843
|
+
return out;
|
|
6844
|
+
}
|
|
6845
|
+
copyCell(def, row, code) {
|
|
6846
|
+
const value = getCellValue(def, row);
|
|
6847
|
+
const rule = def.copyable;
|
|
6848
|
+
if (rule === false) return "";
|
|
6849
|
+
if (typeof rule === "function") {
|
|
6850
|
+
let ok = false;
|
|
6851
|
+
try {
|
|
6852
|
+
ok = rule({ value, row, column: def }) !== false;
|
|
6853
|
+
} catch {
|
|
6854
|
+
ok = false;
|
|
6855
|
+
}
|
|
6856
|
+
if (!ok) return "";
|
|
6857
|
+
}
|
|
6858
|
+
return copyValue(def, value, code);
|
|
6859
|
+
}
|
|
5855
6860
|
copyToClipboard(headers, rows = false) {
|
|
5856
|
-
const
|
|
5857
|
-
if (
|
|
6861
|
+
const text2 = this.selectionTSV(headers, rows);
|
|
6862
|
+
if (text2 && typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
5858
6863
|
try {
|
|
5859
|
-
navigator.clipboard.writeText(
|
|
6864
|
+
navigator.clipboard.writeText(text2)?.catch?.(() => void 0);
|
|
5860
6865
|
} catch {
|
|
5861
6866
|
}
|
|
5862
6867
|
}
|
|
5863
|
-
return
|
|
6868
|
+
return text2;
|
|
5864
6869
|
}
|
|
5865
|
-
pasteText(
|
|
5866
|
-
const matrix = parseTSV(
|
|
6870
|
+
pasteText(text2) {
|
|
6871
|
+
const matrix = parseTSV(text2);
|
|
5867
6872
|
const result = { changed: 0, skipped: 0 };
|
|
5868
6873
|
if (!matrix.length) return result;
|
|
5869
6874
|
const displayed = this.columns.displayed();
|
|
@@ -5895,7 +6900,7 @@ var Grid = class {
|
|
|
5895
6900
|
const raw = single && b ? matrix[0][0] : matrix[dr]?.[dc];
|
|
5896
6901
|
if (raw === void 0) continue;
|
|
5897
6902
|
const def = col.def;
|
|
5898
|
-
if (!def.
|
|
6903
|
+
if (!def.field || !cellEditable(def, node.data, ri)) {
|
|
5899
6904
|
result.skipped++;
|
|
5900
6905
|
continue;
|
|
5901
6906
|
}
|
|
@@ -5918,34 +6923,67 @@ var Grid = class {
|
|
|
5918
6923
|
const spec = def.editor;
|
|
5919
6924
|
if (spec && typeof spec === "object" && spec.type === "select") {
|
|
5920
6925
|
const opts = (typeof spec.options === "function" ? spec.options(row) : spec.options) ?? [];
|
|
5921
|
-
const
|
|
5922
|
-
const hit = opts.map((o) => o !== null && typeof o === "object" && "value" in o ? o : { value: o, label: String(o) }).find((o) => String(o.value).toLowerCase() ===
|
|
5923
|
-
if (!hit) return { ok: false };
|
|
6926
|
+
const text2 = raw.trim().toLowerCase();
|
|
6927
|
+
const hit = opts.map((o) => o !== null && typeof o === "object" && "value" in o ? o : { value: o, label: String(o), disabled: false }).find((o) => String(o.value).toLowerCase() === text2 || (o.label ?? "").toLowerCase() === text2);
|
|
6928
|
+
if (!hit || hit.disabled) return { ok: false };
|
|
5924
6929
|
value = hit.value;
|
|
6930
|
+
} else if (def.formulas && isFormula(raw.trim())) {
|
|
6931
|
+
value = raw.trim();
|
|
5925
6932
|
} else {
|
|
5926
6933
|
const parsed = parseValue(def, raw, oldValue);
|
|
5927
6934
|
if (!parsed.ok) return parsed;
|
|
5928
6935
|
value = parsed.value;
|
|
5929
6936
|
}
|
|
5930
|
-
if (def
|
|
6937
|
+
if (this.cellError(def, row, value) !== null) return { ok: false };
|
|
6938
|
+
return { ok: true, value };
|
|
6939
|
+
}
|
|
6940
|
+
/** Why `value` cannot go into this cell (required, unique, validate), or null when it can. */
|
|
6941
|
+
cellError(def, row, value) {
|
|
6942
|
+
const loc = this.store.state.locale;
|
|
6943
|
+
if (def.required && isBlank(value)) return loc.required;
|
|
6944
|
+
if (def.unique && def.field && !isBlank(value)) {
|
|
6945
|
+
const key = value instanceof Date ? value.getTime() : String(value);
|
|
6946
|
+
const field = def.field;
|
|
6947
|
+
if (this.store.state.data.some((r) => {
|
|
6948
|
+
if (r === row) return false;
|
|
6949
|
+
const v = r[field];
|
|
6950
|
+
return !isBlank(v) && (v instanceof Date ? v.getTime() : String(v)) === key;
|
|
6951
|
+
})) return loc.duplicate;
|
|
6952
|
+
}
|
|
5931
6953
|
if (def.validate) {
|
|
6954
|
+
let res;
|
|
5932
6955
|
try {
|
|
5933
|
-
|
|
6956
|
+
res = def.validate(value, row);
|
|
5934
6957
|
} catch {
|
|
5935
|
-
|
|
6958
|
+
res = loc.invalidValue;
|
|
5936
6959
|
}
|
|
6960
|
+
if (res !== true) return typeof res === "string" && res ? res : loc.invalidValue;
|
|
5937
6961
|
}
|
|
5938
|
-
return
|
|
6962
|
+
return null;
|
|
6963
|
+
}
|
|
6964
|
+
validateAll() {
|
|
6965
|
+
const out = [];
|
|
6966
|
+
const cols = this.columns.getState().map((st) => ({ id: st.colId, def: this.columns.getNode(st.colId).def })).filter((c) => c.def.field && c.def.editable && (c.def.required || c.def.unique || c.def.validate));
|
|
6967
|
+
this.store.state.data.forEach((row, i) => {
|
|
6968
|
+
const id = this.rowModel.idOf(row);
|
|
6969
|
+
const rowIndex = this.rowModel.indexOfId(id);
|
|
6970
|
+
for (const c of cols) {
|
|
6971
|
+
if (!cellEditable(c.def, row, rowIndex < 0 ? i : rowIndex)) continue;
|
|
6972
|
+
const message = this.cellError(c.def, row, row[c.def.field]);
|
|
6973
|
+
if (message !== null) out.push({ row, rowIndex, colId: c.id, message });
|
|
6974
|
+
}
|
|
6975
|
+
});
|
|
6976
|
+
return out;
|
|
5939
6977
|
}
|
|
5940
6978
|
// ── merge, aggregates, fill, pinned rows ────────────────────
|
|
5941
6979
|
/** Merge blocks for every `merge` column over the displayed rows (memoized). */
|
|
5942
6980
|
mergeSpans() {
|
|
5943
|
-
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge)) return NO_SPANS;
|
|
6981
|
+
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan)) return NO_SPANS;
|
|
5944
6982
|
const rows = this.rowModel.rows();
|
|
5945
6983
|
const ver = this.editVersion * 1e6 + this.columns.displayed().length;
|
|
5946
6984
|
if (this.spanCache && this.spanCache.rows === rows && this.spanCache.ver === ver) return this.spanCache.spans;
|
|
5947
6985
|
const spans = /* @__PURE__ */ new Map();
|
|
5948
|
-
const merged = this.columns.displayed().filter((c) => c.def.merge);
|
|
6986
|
+
const merged = this.columns.displayed().filter((c) => c.def.merge || c.def.rowSpan);
|
|
5949
6987
|
if (merged.length && !rows.some((n) => n.kind)) {
|
|
5950
6988
|
const data = rows.map((n) => n.data);
|
|
5951
6989
|
for (const c of merged) spans.set(c.id, computeSpans(data, c.def));
|
|
@@ -5955,15 +6993,22 @@ var Grid = class {
|
|
|
5955
6993
|
}
|
|
5956
6994
|
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
5957
6995
|
rangeAggregates() {
|
|
5958
|
-
const
|
|
5959
|
-
if (!
|
|
5960
|
-
const
|
|
6996
|
+
const all2 = this.allRangeBounds();
|
|
6997
|
+
if (!all2.length) return null;
|
|
6998
|
+
const displayed = this.columns.displayed();
|
|
5961
6999
|
const out = { cells: 0, count: 0, numbers: 0, sum: 0, min: Infinity, max: -Infinity };
|
|
5962
|
-
|
|
7000
|
+
const seen2 = all2.length > 1 ? /* @__PURE__ */ new Set() : null;
|
|
7001
|
+
for (const b of all2) for (let r = b.r1; r <= b.r2; r++) {
|
|
5963
7002
|
const node = this.rowModel.getRow(r);
|
|
5964
7003
|
if (!node || node.kind) continue;
|
|
5965
7004
|
const row = node.data;
|
|
5966
|
-
for (const c of
|
|
7005
|
+
for (const c of displayed.slice(b.c1, b.c2 + 1)) {
|
|
7006
|
+
if (isSystemCol(c.id)) continue;
|
|
7007
|
+
if (seen2) {
|
|
7008
|
+
const k = `${r},${c.index}`;
|
|
7009
|
+
if (seen2.has(k)) continue;
|
|
7010
|
+
seen2.add(k);
|
|
7011
|
+
}
|
|
5967
7012
|
out.cells++;
|
|
5968
7013
|
const v = getCellValue(c.def, row);
|
|
5969
7014
|
if (isBlank(v)) continue;
|
|
@@ -5998,13 +7043,8 @@ var Grid = class {
|
|
|
5998
7043
|
const col = displayed[c];
|
|
5999
7044
|
const node = this.rowModel.getRow(r);
|
|
6000
7045
|
const def = col?.def;
|
|
6001
|
-
if (!node || node.kind || !def?.
|
|
6002
|
-
if (def.
|
|
6003
|
-
try {
|
|
6004
|
-
if (def.validate && def.validate(value2, node.data) !== true) return;
|
|
6005
|
-
} catch {
|
|
6006
|
-
return;
|
|
6007
|
-
}
|
|
7046
|
+
if (!node || node.kind || !def?.field || isSystemCol(col.id) || !cellEditable(def, node.data, r)) return;
|
|
7047
|
+
if (this.cellError(def, node.data, value2) !== null) return;
|
|
6008
7048
|
this.writeCell(changes, node.data, def.field, col.id, r, value2);
|
|
6009
7049
|
};
|
|
6010
7050
|
const value = (r, c) => getCellValue(displayed[c].def, this.rowModel.getRow(r).data);
|
|
@@ -6038,6 +7078,7 @@ var Grid = class {
|
|
|
6038
7078
|
writeCell(changes, row, field, colId, rowIndex, value) {
|
|
6039
7079
|
const oldValue = row[field];
|
|
6040
7080
|
if (Object.is(oldValue, value)) return false;
|
|
7081
|
+
if (this.veto("beforeCellChange", { row, rowIndex, colId, oldValue, newValue: value })) return false;
|
|
6041
7082
|
this.tracker.beforeChange(row);
|
|
6042
7083
|
row[field] = value;
|
|
6043
7084
|
changes.push({ row, field, colId, oldValue, newValue: value });
|
|
@@ -6059,6 +7100,7 @@ var Grid = class {
|
|
|
6059
7100
|
this.afterDataEdit();
|
|
6060
7101
|
}
|
|
6061
7102
|
afterDataEdit() {
|
|
7103
|
+
this.renderer.invalidateHeights();
|
|
6062
7104
|
this.editVersion++;
|
|
6063
7105
|
this.refreshSummary();
|
|
6064
7106
|
this.renderer.invalidate({ rows: true });
|
|
@@ -6074,17 +7116,17 @@ var Grid = class {
|
|
|
6074
7116
|
/** Delete/Backspace: clear editable cells in the range, or the focused cell. */
|
|
6075
7117
|
clearCells() {
|
|
6076
7118
|
const displayed = this.columns.displayed();
|
|
6077
|
-
const
|
|
7119
|
+
const all2 = this.allRangeBounds();
|
|
6078
7120
|
const f = this.store.state.focus;
|
|
6079
7121
|
const fi = f ? this.columns.getColumn(f.colId)?.index : void 0;
|
|
6080
|
-
const
|
|
6081
|
-
if (!
|
|
7122
|
+
const boxes = all2.length ? all2 : f && fi !== void 0 ? [{ r1: f.rowIndex, r2: f.rowIndex, c1: fi, c2: fi }] : [];
|
|
7123
|
+
if (!boxes.length) return;
|
|
6082
7124
|
const changes = [];
|
|
6083
|
-
for (let r = box.r1; r <= box.r2; r++) {
|
|
7125
|
+
for (const box of boxes) for (let r = box.r1; r <= box.r2; r++) {
|
|
6084
7126
|
const node = this.rowModel.getRow(r);
|
|
6085
7127
|
for (let c = box.c1; c <= box.c2; c++) {
|
|
6086
7128
|
const def = displayed[c]?.def;
|
|
6087
|
-
if (node && !node.kind && def?.
|
|
7129
|
+
if (node && !node.kind && def?.field && cellEditable(def, node.data, r)) this.writeCell(changes, node.data, def.field, displayed[c].id, r, null);
|
|
6088
7130
|
}
|
|
6089
7131
|
}
|
|
6090
7132
|
this.endCellTx(changes);
|
|
@@ -6264,6 +7306,7 @@ var Grid = class {
|
|
|
6264
7306
|
}
|
|
6265
7307
|
return s.dir === "asc" || s.dir === "desc";
|
|
6266
7308
|
});
|
|
7309
|
+
if (this.veto("beforeSort", { sort: valid.map((s) => ({ ...s })) })) return;
|
|
6267
7310
|
this.store.set({ sort: valid });
|
|
6268
7311
|
this.events.emit("sortChange", { sort: [...valid] });
|
|
6269
7312
|
}
|
|
@@ -6285,6 +7328,7 @@ var Grid = class {
|
|
|
6285
7328
|
warn(`setFilter: unknown column "${colId}"`);
|
|
6286
7329
|
return;
|
|
6287
7330
|
}
|
|
7331
|
+
if (this.veto("beforeFilter", { colId, model })) return;
|
|
6288
7332
|
const filters = { ...this.store.state.filters };
|
|
6289
7333
|
if (model) filters[colId] = model;
|
|
6290
7334
|
else delete filters[colId];
|
|
@@ -6305,9 +7349,21 @@ var Grid = class {
|
|
|
6305
7349
|
this.store.set({ selected, anchorId });
|
|
6306
7350
|
this.events.emit("selectionChange", { selected: this.selectedRows() });
|
|
6307
7351
|
}
|
|
7352
|
+
/** Data rows that may be checked (isRowSelectable). */
|
|
7353
|
+
rowSelectable(row) {
|
|
7354
|
+
const f = this.options.isRowSelectable;
|
|
7355
|
+
if (!f) return true;
|
|
7356
|
+
try {
|
|
7357
|
+
return !!f(row);
|
|
7358
|
+
} catch {
|
|
7359
|
+
return false;
|
|
7360
|
+
}
|
|
7361
|
+
}
|
|
6308
7362
|
selectFromClick(index, toggle, range) {
|
|
6309
7363
|
const mode = this.options.rowSelection;
|
|
6310
|
-
if (!mode || index < 0 || index >= this.rowModel.getRowCount()
|
|
7364
|
+
if (!mode || index < 0 || index >= this.rowModel.getRowCount()) return;
|
|
7365
|
+
const target = this.rowModel.getRow(index);
|
|
7366
|
+
if (target.kind || !this.rowSelectable(target.data)) return;
|
|
6311
7367
|
const r = nextSelection({
|
|
6312
7368
|
selected: this.store.state.selected,
|
|
6313
7369
|
anchorIndex: this.anchorIndex(),
|
|
@@ -6317,11 +7373,12 @@ var Grid = class {
|
|
|
6317
7373
|
toggle,
|
|
6318
7374
|
range
|
|
6319
7375
|
});
|
|
6320
|
-
if (this.rowModel.rows() !== this.rowModel.dataRows()) {
|
|
7376
|
+
if (this.rowModel.rows() !== this.rowModel.dataRows() || this.options.isRowSelectable) {
|
|
6321
7377
|
for (const id of r.selected) {
|
|
6322
7378
|
if (this.store.state.selected.has(id)) continue;
|
|
6323
7379
|
const i = this.rowModel.indexOfId(id);
|
|
6324
|
-
|
|
7380
|
+
const n = i >= 0 ? this.rowModel.getRow(i) : null;
|
|
7381
|
+
if (n && (n.kind || !this.rowSelectable(n.data))) r.selected.delete(id);
|
|
6325
7382
|
}
|
|
6326
7383
|
}
|
|
6327
7384
|
this.setSelected(r.selected, this.rowModel.getRow(r.anchorIndex)?.id ?? null);
|
|
@@ -6333,8 +7390,13 @@ var Grid = class {
|
|
|
6333
7390
|
const i = this.rowModel.indexOfId(id);
|
|
6334
7391
|
return i < 0 ? null : i;
|
|
6335
7392
|
}
|
|
6336
|
-
|
|
7393
|
+
/** Rows select-all acts on: displayed data rows that are selectable. */
|
|
7394
|
+
selectableRows() {
|
|
6337
7395
|
const rows = this.rowModel.dataRows();
|
|
7396
|
+
return this.options.isRowSelectable ? rows.filter((n) => this.rowSelectable(n.data)) : rows;
|
|
7397
|
+
}
|
|
7398
|
+
toggleSelectAll() {
|
|
7399
|
+
const rows = this.selectableRows();
|
|
6338
7400
|
const sel = this.store.state.selected;
|
|
6339
7401
|
const all2 = rows.length > 0 && rows.every((n) => sel.has(n.id));
|
|
6340
7402
|
const next = new Set(sel);
|
|
@@ -6354,7 +7416,7 @@ var Grid = class {
|
|
|
6354
7416
|
if (this.options.rowSelection) sys.push(SELECT_COLUMN);
|
|
6355
7417
|
return [...sys, ...defs];
|
|
6356
7418
|
}
|
|
6357
|
-
setData(rows) {
|
|
7419
|
+
setData(rows, options) {
|
|
6358
7420
|
if (this.clientOnly("setData")) return;
|
|
6359
7421
|
if (!Array.isArray(rows)) {
|
|
6360
7422
|
warn("setData: rows must be an array");
|
|
@@ -6364,8 +7426,11 @@ var Grid = class {
|
|
|
6364
7426
|
this.tracker.reset();
|
|
6365
7427
|
this.history.clear();
|
|
6366
7428
|
this.detailOpen.clear();
|
|
7429
|
+
this.treeLoaded.clear();
|
|
7430
|
+
this.treeLoading.clear();
|
|
6367
7431
|
this.renderer.dropDetail();
|
|
6368
7432
|
this.store.set({ data: cleanRows(rows) });
|
|
7433
|
+
if (options?.scrollToTop) this.renderer.setScroll({ top: 0, left: 0 });
|
|
6369
7434
|
const sel = this.store.state.selected;
|
|
6370
7435
|
if (sel.size) {
|
|
6371
7436
|
if (this.options.getRowId) {
|
|
@@ -6439,7 +7504,7 @@ var Grid = class {
|
|
|
6439
7504
|
return fn(...a);
|
|
6440
7505
|
});
|
|
6441
7506
|
return {
|
|
6442
|
-
setData: guard((rows) => this.setData(rows)),
|
|
7507
|
+
setData: guard((rows, o) => this.setData(rows, o)),
|
|
6443
7508
|
getData: guard(() => this.store.state.data),
|
|
6444
7509
|
setColumns: guard((defs) => this.setColumns(defs)),
|
|
6445
7510
|
getColumnState: guard(() => this.columns.getState()),
|
|
@@ -6453,10 +7518,14 @@ var Grid = class {
|
|
|
6453
7518
|
getFilterModel: guard(() => ({ ...this.store.state.filters })),
|
|
6454
7519
|
setQuickFilter: guard((t) => this.store.set({ quickFilter: t ?? "" })),
|
|
6455
7520
|
getSelectedRows: guard(() => this.selectedRows()),
|
|
6456
|
-
selectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set([...this.store.state.selected, ...this.
|
|
7521
|
+
selectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set([...this.store.state.selected, ...this.selectableRows().map((n) => n.id)]))),
|
|
6457
7522
|
deselectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set(), null)),
|
|
6458
7523
|
getDisplayedRowCount: guard(() => this.rowModel.getRowCount()),
|
|
6459
7524
|
scrollToRow: guard((i) => this.renderer.scrollToRow(i)),
|
|
7525
|
+
validate: guard(() => this.validateAll()),
|
|
7526
|
+
getScrollPosition: guard(() => this.renderer.getScroll()),
|
|
7527
|
+
setScrollPosition: guard((p) => this.renderer.setScroll(p ?? {})),
|
|
7528
|
+
syncScroll: guard((other, axis = "both") => this.syncScroll(other, axis)),
|
|
6460
7529
|
setTheme: guard((t) => this.store.set({ theme: t })),
|
|
6461
7530
|
setLocale: guard((l) => this.store.set({ locale: resolveLocale(l) })),
|
|
6462
7531
|
setFont: guard((f) => this.setFont(f)),
|
|
@@ -6473,8 +7542,10 @@ var Grid = class {
|
|
|
6473
7542
|
return r && { start: { ...r.start }, end: { ...r.end } };
|
|
6474
7543
|
}),
|
|
6475
7544
|
setRange: guard((r) => this.setRange(r)),
|
|
7545
|
+
getRanges: guard(() => this.getRanges()),
|
|
7546
|
+
setRanges: guard((list) => this.setRanges(list)),
|
|
6476
7547
|
copySelection: guard((o) => this.copyToClipboard(!!o?.headers, !!o?.rows)),
|
|
6477
|
-
pasteText: guard((
|
|
7548
|
+
pasteText: guard((text2) => this.pasteText(text2)),
|
|
6478
7549
|
startEdit: guard((rowIndex, colId) => this.editor.start(rowIndex, colId)),
|
|
6479
7550
|
stopEdit: guard((cancel) => cancel ? this.editor.cancel() : void this.editor.commit(null)),
|
|
6480
7551
|
addRows: guard((rows, index) => this.addRows(rows, index)),
|
|
@@ -6491,7 +7562,7 @@ var Grid = class {
|
|
|
6491
7562
|
canRedo: guard(() => this.history.canRedo()),
|
|
6492
7563
|
fillRange: guard((t) => this.fillRange(t)),
|
|
6493
7564
|
setPinnedRows: guard((r) => this.setPinnedRows(r)),
|
|
6494
|
-
find: guard((
|
|
7565
|
+
find: guard((text2) => this.finder.find(text2)),
|
|
6495
7566
|
findNext: guard(() => this.finder.next()),
|
|
6496
7567
|
findPrevious: guard(() => this.finder.previous()),
|
|
6497
7568
|
clearFind: guard(() => this.finder.clear()),
|
|
@@ -6503,6 +7574,7 @@ var Grid = class {
|
|
|
6503
7574
|
setDetailExpanded: guard((row, open) => this.setDetailExpanded(row, open)),
|
|
6504
7575
|
isDetailExpanded: guard((row) => this.isDetailExpanded(row)),
|
|
6505
7576
|
moveRow: guard((row, toIndex) => this.moveRow(row, toIndex)),
|
|
7577
|
+
dropRow: guard((row, target, position) => this.dropRow(row, target, position)),
|
|
6506
7578
|
setPage: guard((p) => this.setPage(p)),
|
|
6507
7579
|
getPage: guard(() => this.getPage()),
|
|
6508
7580
|
getPageCount: guard(() => this.getPageCount()),
|
|
@@ -6524,6 +7596,7 @@ var Grid = class {
|
|
|
6524
7596
|
destroy() {
|
|
6525
7597
|
if (this.destroyed) return;
|
|
6526
7598
|
this.destroyed = true;
|
|
7599
|
+
liveGrids.delete(this.renderer.root);
|
|
6527
7600
|
for (const d of this.disposers) d();
|
|
6528
7601
|
this.renderer.destroy();
|
|
6529
7602
|
this.events.clear();
|