@freegridjs/core 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/freegrid.css +45 -1
- package/dist/cdn/freegrid.esm.js +7 -7
- package/dist/cdn/freegrid.min.js +8 -8
- package/dist/index.d.ts +14 -7
- package/dist/index.js +1339 -270
- 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.0";
|
|
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" };
|
|
@@ -4660,7 +5398,7 @@ var ColumnMenu = class {
|
|
|
4660
5398
|
popup.append(this.section(items));
|
|
4661
5399
|
const filter = this.filterSection(colId, () => this.close());
|
|
4662
5400
|
if (filter) popup.append(filter);
|
|
4663
|
-
|
|
5401
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
4664
5402
|
const r = anchor.getBoundingClientRect();
|
|
4665
5403
|
const w = popup.offsetWidth || 248;
|
|
4666
5404
|
popup.style.left = `${Math.max(4, Math.min(r.right - w, window.innerWidth - w - 4))}px`;
|
|
@@ -4670,13 +5408,12 @@ var ColumnMenu = class {
|
|
|
4670
5408
|
this.popup = popup;
|
|
4671
5409
|
this.colId = colId;
|
|
4672
5410
|
const onDown = (e) => {
|
|
4673
|
-
|
|
4674
|
-
if (!popup.contains(t) && !anchor.contains(t)) this.close();
|
|
5411
|
+
if (!eventInside(e, popup, anchor)) this.close();
|
|
4675
5412
|
};
|
|
4676
5413
|
const onKey = (e) => {
|
|
4677
5414
|
if (e.key !== "Escape") return;
|
|
4678
5415
|
this.close();
|
|
4679
|
-
this.grid.renderer.
|
|
5416
|
+
this.grid.renderer.gridEl.focus();
|
|
4680
5417
|
};
|
|
4681
5418
|
document.addEventListener("pointerdown", onDown, true);
|
|
4682
5419
|
document.addEventListener("keydown", onKey, true);
|
|
@@ -4940,19 +5677,26 @@ var RowModel = class {
|
|
|
4940
5677
|
if (!this.indexById) this.indexById = new Map(this.display.map((n, i) => [n.id, i]));
|
|
4941
5678
|
return this.indexById.get(id) ?? -1;
|
|
4942
5679
|
}
|
|
4943
|
-
/**
|
|
4944
|
-
|
|
5680
|
+
/**
|
|
5681
|
+
* Prefix-sum offsets of the display rows: detail rows use `detailHeight`, rows with a
|
|
5682
|
+
* measured height (auto row height) use it, everything else `rowHeight`.
|
|
5683
|
+
*/
|
|
5684
|
+
layout(rowHeight, detailHeight, heights, heightsVersion = 0) {
|
|
4945
5685
|
const c = this.layoutCache;
|
|
4946
|
-
if (c && c.rows === this.display && c.rh === rowHeight && c.dh === detailHeight) return c;
|
|
5686
|
+
if (c && c.rows === this.display && c.rh === rowHeight && c.dh === detailHeight && c.hv === heightsVersion) return c;
|
|
4947
5687
|
const n = this.display.length;
|
|
4948
5688
|
const offsets = new Float64Array(n + 1);
|
|
4949
5689
|
let uniform = true;
|
|
5690
|
+
const measured = heights && heights.size ? heights : null;
|
|
4950
5691
|
for (let i = 0; i < n; i++) {
|
|
4951
|
-
const
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
5692
|
+
const node = this.display[i];
|
|
5693
|
+
let h = rowHeight;
|
|
5694
|
+
if (node.kind === "detail") h = detailHeight;
|
|
5695
|
+
else if (measured && !node.kind) h = measured.get(node.id) ?? rowHeight;
|
|
5696
|
+
if (h !== rowHeight) uniform = false;
|
|
5697
|
+
offsets[i + 1] = offsets[i] + h;
|
|
5698
|
+
}
|
|
5699
|
+
this.layoutCache = { rows: this.display, rh: rowHeight, dh: detailHeight, hv: heightsVersion, offsets, uniform };
|
|
4956
5700
|
return this.layoutCache;
|
|
4957
5701
|
}
|
|
4958
5702
|
filter(input) {
|
|
@@ -4963,8 +5707,8 @@ var RowModel = class {
|
|
|
4963
5707
|
const matches = this.nodes.filter((n) => {
|
|
4964
5708
|
for (const p of preds) if (!p.pred(getCellValue(p.def, n.data))) return false;
|
|
4965
5709
|
if (tokens.length) {
|
|
4966
|
-
const
|
|
4967
|
-
if (!matchesQuick(tokens,
|
|
5710
|
+
const text2 = quickCols.map((d) => this.deps.format(d, getCellValue(d, n.data))).join("").toLowerCase();
|
|
5711
|
+
if (!matchesQuick(tokens, text2)) return false;
|
|
4968
5712
|
}
|
|
4969
5713
|
return true;
|
|
4970
5714
|
});
|
|
@@ -5059,8 +5803,10 @@ var RowModel = class {
|
|
|
5059
5803
|
for (const n of nodes) {
|
|
5060
5804
|
if (seen2.has(n.id)) continue;
|
|
5061
5805
|
const kids = children.get(n.id) ?? [];
|
|
5806
|
+
const lazy = !kids.length && !!d.lazyChildren?.(n.id, n.data);
|
|
5062
5807
|
const expanded = kids.length > 0 && this.isOpen(d, n.id, level);
|
|
5063
|
-
const node = { ...n, level, hasChildren: kids.length > 0, expanded };
|
|
5808
|
+
const node = { ...n, level, hasChildren: kids.length > 0 || lazy, expanded };
|
|
5809
|
+
if (lazy && d.loading?.has(n.id)) node.loading = true;
|
|
5064
5810
|
this.withDetails(d, [node], out);
|
|
5065
5811
|
if (expanded) visit(kids, level + 1, /* @__PURE__ */ new Set([...seen2, n.id]));
|
|
5066
5812
|
}
|
|
@@ -5328,6 +6074,17 @@ var Grid = class {
|
|
|
5328
6074
|
/** 0-based page and rows per page (pagination only). */
|
|
5329
6075
|
this.page = 0;
|
|
5330
6076
|
this.pageSize = DEFAULT_PAGE_SIZE;
|
|
6077
|
+
/** Tree rows whose children were loaded (or are loading) through treeData.loadChildren. */
|
|
6078
|
+
this.treeLoaded = /* @__PURE__ */ new Set();
|
|
6079
|
+
this.treeLoading = /* @__PURE__ */ new Set();
|
|
6080
|
+
this.lazyChildren = (id, row) => {
|
|
6081
|
+
if (this.treeLoaded.has(id)) return false;
|
|
6082
|
+
try {
|
|
6083
|
+
return !!this.options.treeData?.hasChildren?.(row);
|
|
6084
|
+
} catch {
|
|
6085
|
+
return false;
|
|
6086
|
+
}
|
|
6087
|
+
};
|
|
5331
6088
|
if (!Array.isArray(options.columns)) warn("options.columns must be an array");
|
|
5332
6089
|
if (options.data != null && !Array.isArray(options.data)) warn("options.data must be an array");
|
|
5333
6090
|
this.store = new Store({
|
|
@@ -5339,6 +6096,7 @@ var Grid = class {
|
|
|
5339
6096
|
anchorId: null,
|
|
5340
6097
|
focus: null,
|
|
5341
6098
|
range: null,
|
|
6099
|
+
extraRanges: [],
|
|
5342
6100
|
locale: resolveLocale(options.locale),
|
|
5343
6101
|
theme: options.theme ?? "light"
|
|
5344
6102
|
});
|
|
@@ -5415,6 +6173,7 @@ var Grid = class {
|
|
|
5415
6173
|
});
|
|
5416
6174
|
this.renderer.render();
|
|
5417
6175
|
this.api = this.createApi();
|
|
6176
|
+
liveGrids.set(this.renderer.root, this);
|
|
5418
6177
|
}
|
|
5419
6178
|
collator() {
|
|
5420
6179
|
const code = this.store.state.locale.code;
|
|
@@ -5425,6 +6184,7 @@ var Grid = class {
|
|
|
5425
6184
|
}
|
|
5426
6185
|
/** Column widths/order/pinning changed; rows are unaffected. */
|
|
5427
6186
|
layoutChanged() {
|
|
6187
|
+
this.renderer.invalidateHeights();
|
|
5428
6188
|
this.renderer.invalidate({ header: true, rows: true });
|
|
5429
6189
|
this.refreshChoosers();
|
|
5430
6190
|
}
|
|
@@ -5445,6 +6205,8 @@ var Grid = class {
|
|
|
5445
6205
|
defaultExpanded: this.defaultExpanded,
|
|
5446
6206
|
detailOpen: this.options.detailRenderer ? this.detailOpen : /* @__PURE__ */ new Set(),
|
|
5447
6207
|
isMaster: this.options.isRowMaster,
|
|
6208
|
+
lazyChildren: this.options.treeData?.hasChildren ? this.lazyChildren : void 0,
|
|
6209
|
+
loading: this.treeLoading,
|
|
5448
6210
|
version: this.displayVersion
|
|
5449
6211
|
};
|
|
5450
6212
|
}
|
|
@@ -5562,6 +6324,7 @@ var Grid = class {
|
|
|
5562
6324
|
if (!this.options.pagination || !Number.isFinite(page)) return;
|
|
5563
6325
|
const next = Math.max(0, Math.min(this.getPageCount() - 1, Math.floor(page)));
|
|
5564
6326
|
if (next === this.page) return;
|
|
6327
|
+
if (this.veto("beforePageChange", { page: next, pageSize: this.pageSize })) return;
|
|
5565
6328
|
this.page = next;
|
|
5566
6329
|
this.pageMoved();
|
|
5567
6330
|
}
|
|
@@ -5573,6 +6336,7 @@ var Grid = class {
|
|
|
5573
6336
|
}
|
|
5574
6337
|
if (size === this.pageSize) return;
|
|
5575
6338
|
const first = this.page * this.pageSize;
|
|
6339
|
+
if (this.veto("beforePageChange", { page: Math.floor(first / size), pageSize: size })) return;
|
|
5576
6340
|
this.pageSize = size;
|
|
5577
6341
|
this.page = Math.floor(first / size);
|
|
5578
6342
|
this.pageMoved();
|
|
@@ -5612,6 +6376,35 @@ var Grid = class {
|
|
|
5612
6376
|
this.renderer.invalidate({ rows: true });
|
|
5613
6377
|
this.events.emit("rowsLoadFailed", { start, end, error });
|
|
5614
6378
|
}
|
|
6379
|
+
/**
|
|
6380
|
+
* Scroll `other` along with this grid and back. The follower's echo of a position we set is not
|
|
6381
|
+
* sent back, so a shorter grid that stops at its end does not pull the longer one back.
|
|
6382
|
+
*/
|
|
6383
|
+
syncScroll(other, axis) {
|
|
6384
|
+
if (!other || typeof other.setScrollPosition !== "function" || other === this.api) return () => void 0;
|
|
6385
|
+
const written = /* @__PURE__ */ new Map();
|
|
6386
|
+
const key = (p) => `${p.top},${p.left}`;
|
|
6387
|
+
const link = (from, to) => from.on("scroll", (p) => {
|
|
6388
|
+
if (written.get(from) === key(p)) {
|
|
6389
|
+
written.delete(from);
|
|
6390
|
+
return;
|
|
6391
|
+
}
|
|
6392
|
+
to.setScrollPosition(axis === "vertical" ? { top: p.top } : axis === "horizontal" ? { left: p.left } : p);
|
|
6393
|
+
written.set(to, key(to.getScrollPosition()));
|
|
6394
|
+
});
|
|
6395
|
+
const offs = [link(this.api, other), link(other, this.api)];
|
|
6396
|
+
return () => {
|
|
6397
|
+
for (const off of offs) off();
|
|
6398
|
+
};
|
|
6399
|
+
}
|
|
6400
|
+
/** Emit a before-event; true when a listener called cancel(). */
|
|
6401
|
+
veto(type, payload) {
|
|
6402
|
+
let cancelled = false;
|
|
6403
|
+
this.events.emit(type, { ...payload, cancel: () => {
|
|
6404
|
+
cancelled = true;
|
|
6405
|
+
} });
|
|
6406
|
+
return cancelled;
|
|
6407
|
+
}
|
|
5615
6408
|
/** Client-only operations log once and do nothing with a datasource. */
|
|
5616
6409
|
clientOnly(what) {
|
|
5617
6410
|
if (!this.rowModel.server) return false;
|
|
@@ -5661,10 +6454,45 @@ var Grid = class {
|
|
|
5661
6454
|
expandKey(node) {
|
|
5662
6455
|
return node.group ? node.group.key : node.id;
|
|
5663
6456
|
}
|
|
6457
|
+
/** First expand of a lazy tree row: fetch its children, then show them expanded. */
|
|
6458
|
+
loadChildren(node) {
|
|
6459
|
+
const tree = this.options.treeData;
|
|
6460
|
+
const id = node.id;
|
|
6461
|
+
if (this.treeLoading.has(id)) return;
|
|
6462
|
+
if (typeof tree.loadChildren !== "function") {
|
|
6463
|
+
warn("treeData.hasChildren needs treeData.loadChildren");
|
|
6464
|
+
return;
|
|
6465
|
+
}
|
|
6466
|
+
this.treeLoading.add(id);
|
|
6467
|
+
this.displayChanged();
|
|
6468
|
+
const done = () => {
|
|
6469
|
+
this.treeLoading.delete(id);
|
|
6470
|
+
return !this.destroyed && this.store.state.data.includes(node.data);
|
|
6471
|
+
};
|
|
6472
|
+
Promise.resolve().then(() => tree.loadChildren(node.data)).then((rows) => {
|
|
6473
|
+
if (!done()) return;
|
|
6474
|
+
const kids = cleanRows(Array.isArray(rows) ? rows : []);
|
|
6475
|
+
this.treeLoaded.add(id);
|
|
6476
|
+
this.expanded.set(id, true);
|
|
6477
|
+
if (kids.length) this.replaceData([...this.store.state.data, ...kids]);
|
|
6478
|
+
this.displayChanged();
|
|
6479
|
+
this.events.emit("childrenLoaded", { row: node.data, rows: kids });
|
|
6480
|
+
this.events.emit("groupToggle", { key: id, expanded: true });
|
|
6481
|
+
}, (error) => {
|
|
6482
|
+
if (!done()) return;
|
|
6483
|
+
warn(`treeData.loadChildren failed for row "${id}"`);
|
|
6484
|
+
this.displayChanged();
|
|
6485
|
+
this.events.emit("childrenLoadFailed", { row: node.data, error });
|
|
6486
|
+
});
|
|
6487
|
+
}
|
|
5664
6488
|
setGroupExpanded(key, open) {
|
|
5665
6489
|
const node = this.rowModel.rows().find((n) => this.isExpandable(n) && this.expandKey(n) === key);
|
|
5666
6490
|
const cur = node ? !!(node.group?.expanded ?? node.expanded) : this.expanded.get(key);
|
|
5667
6491
|
if (cur === open) return;
|
|
6492
|
+
if (open && node && !node.group && this.options.treeData && this.lazyChildren(node.id, node.data)) {
|
|
6493
|
+
this.loadChildren(node);
|
|
6494
|
+
return;
|
|
6495
|
+
}
|
|
5668
6496
|
this.expanded.set(key, open);
|
|
5669
6497
|
this.displayChanged();
|
|
5670
6498
|
this.events.emit("groupToggle", { key, expanded: open });
|
|
@@ -5723,7 +6551,113 @@ var Grid = class {
|
|
|
5723
6551
|
/** Row order can only follow the source data when nothing reorders or hides rows. */
|
|
5724
6552
|
canMoveRows() {
|
|
5725
6553
|
const s = this.store.state;
|
|
5726
|
-
|
|
6554
|
+
const tree = this.options.treeData;
|
|
6555
|
+
return !this.rowModel.server && !s.sort.length && !Object.keys(s.filters).length && !s.quickFilter.trim() && !this.groupBy.length && (!tree || typeof tree.setParentId === "function");
|
|
6556
|
+
}
|
|
6557
|
+
get dragOptions() {
|
|
6558
|
+
const o = this.options.rowDrag;
|
|
6559
|
+
return o && typeof o === "object" ? o : {};
|
|
6560
|
+
}
|
|
6561
|
+
parentOf(row) {
|
|
6562
|
+
let p = null;
|
|
6563
|
+
try {
|
|
6564
|
+
p = this.options.treeData.getParentId(row);
|
|
6565
|
+
} catch {
|
|
6566
|
+
p = null;
|
|
6567
|
+
}
|
|
6568
|
+
return p == null ? null : String(p);
|
|
6569
|
+
}
|
|
6570
|
+
rawParent(row) {
|
|
6571
|
+
try {
|
|
6572
|
+
return this.options.treeData.getParentId(row) ?? null;
|
|
6573
|
+
} catch {
|
|
6574
|
+
return null;
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
/** The id of `row` as the data stores parent ids: a number when other rows hold numeric parent ids. */
|
|
6578
|
+
idValue(row) {
|
|
6579
|
+
const id = this.rowModel.idOf(row);
|
|
6580
|
+
for (const r of this.store.state.data) {
|
|
6581
|
+
const p = this.rawParent(r);
|
|
6582
|
+
if (p !== null && String(p) === id) return p;
|
|
6583
|
+
}
|
|
6584
|
+
const numeric = this.store.state.data.some((r) => typeof this.rawParent(r) === "number");
|
|
6585
|
+
return numeric && id.trim() !== "" && Number.isFinite(Number(id)) ? Number(id) : id;
|
|
6586
|
+
}
|
|
6587
|
+
/** Can `row` land here? Trees refuse the row's own subtree; then the user's canDrop rule decides. */
|
|
6588
|
+
canDropRow(row, target, position, from = this) {
|
|
6589
|
+
if (!this.canMoveRows() || target === row) return false;
|
|
6590
|
+
if (from === this && this.options.treeData && target) {
|
|
6591
|
+
const own = this.rowModel.idOf(row);
|
|
6592
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
6593
|
+
for (let id = this.rowModel.idOf(target); id !== null && !seen2.has(id); ) {
|
|
6594
|
+
if (id === own) return false;
|
|
6595
|
+
seen2.add(id);
|
|
6596
|
+
const node = this.rowModel.allNodes().find((n) => n.id === id);
|
|
6597
|
+
id = node ? this.parentOf(node.data) : null;
|
|
6598
|
+
}
|
|
6599
|
+
}
|
|
6600
|
+
const rule = this.dragOptions.canDrop;
|
|
6601
|
+
if (!rule) return true;
|
|
6602
|
+
try {
|
|
6603
|
+
return rule({ row, target, position, fromOtherGrid: from !== this }) !== false;
|
|
6604
|
+
} catch {
|
|
6605
|
+
warn("rowDrag.canDrop threw; the drop was refused");
|
|
6606
|
+
return false;
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
/**
|
|
6610
|
+
* Move `row` before, after or inside `target` (null = to the end). In tree data the row takes the
|
|
6611
|
+
* target's parent, or the target itself for 'inside'. `from` is the grid the row is dragged out of.
|
|
6612
|
+
*/
|
|
6613
|
+
dropRow(row, target, position = "after", from = this) {
|
|
6614
|
+
if (this.clientOnly("dropRow") || !row || typeof row !== "object") return false;
|
|
6615
|
+
const tree = this.options.treeData;
|
|
6616
|
+
if (!tree && position === "inside") position = "after";
|
|
6617
|
+
const data = this.store.state.data;
|
|
6618
|
+
const external = from !== this;
|
|
6619
|
+
const fromIndex = external ? -1 : data.indexOf(row);
|
|
6620
|
+
if (!external && fromIndex < 0) return false;
|
|
6621
|
+
if (target && !data.includes(target)) return false;
|
|
6622
|
+
if (external && (!from.canMoveRows() || data.includes(row))) return false;
|
|
6623
|
+
if (!this.canDropRow(row, target, position, from)) return false;
|
|
6624
|
+
const oldRaw = tree ? this.rawParent(row) : null;
|
|
6625
|
+
const parentRow = !tree || !target ? null : position === "inside" ? target : this.store.state.data.find((r) => this.rowModel.idOf(r) === this.parentOf(target)) ?? null;
|
|
6626
|
+
const newRaw = parentRow ? this.idValue(parentRow) : null;
|
|
6627
|
+
const next = data.filter((r) => r !== row);
|
|
6628
|
+
const at = !target || position === "inside" ? next.length : next.indexOf(target) + (position === "after" ? 1 : 0);
|
|
6629
|
+
next.splice(at, 0, row);
|
|
6630
|
+
const moved = next.some((r, i) => r !== data[i]);
|
|
6631
|
+
if (!external && !moved && String(oldRaw ?? "") === String(newRaw ?? "")) return false;
|
|
6632
|
+
const setParent = (raw, parent) => {
|
|
6633
|
+
try {
|
|
6634
|
+
tree.setParentId(row, raw, parent);
|
|
6635
|
+
} catch {
|
|
6636
|
+
warn("treeData.setParentId threw");
|
|
6637
|
+
}
|
|
6638
|
+
};
|
|
6639
|
+
const oldParentRow = tree ? this.store.state.data.find((r) => this.rowModel.idOf(r) === this.parentOf(row)) ?? null : null;
|
|
6640
|
+
if (external) {
|
|
6641
|
+
from.removeRows([row]);
|
|
6642
|
+
if (tree) setParent(newRaw, parentRow);
|
|
6643
|
+
this.addRows([row], at);
|
|
6644
|
+
} else {
|
|
6645
|
+
if (tree) setParent(newRaw, parentRow);
|
|
6646
|
+
this.replaceData(next);
|
|
6647
|
+
this.history.push({
|
|
6648
|
+
undo: () => {
|
|
6649
|
+
if (tree) setParent(oldRaw, oldParentRow);
|
|
6650
|
+
this.replaceData(data);
|
|
6651
|
+
},
|
|
6652
|
+
redo: () => {
|
|
6653
|
+
if (tree) setParent(newRaw, parentRow);
|
|
6654
|
+
this.replaceData(next);
|
|
6655
|
+
}
|
|
6656
|
+
});
|
|
6657
|
+
this.events.emit("rowDragEnd", { row, fromIndex, toIndex: at });
|
|
6658
|
+
}
|
|
6659
|
+
this.events.emit("rowDrop", { row, target, position, fromOtherGrid: external, fromGrid: external ? from.api : null });
|
|
6660
|
+
return true;
|
|
5727
6661
|
}
|
|
5728
6662
|
moveRow(row, toIndex, record = true) {
|
|
5729
6663
|
if (this.clientOnly("moveRow")) return false;
|
|
@@ -5753,6 +6687,7 @@ var Grid = class {
|
|
|
5753
6687
|
if (this.options.rowHeight == null) this.renderer.opts.rowHeight = autoRowHeight(effective);
|
|
5754
6688
|
if (this.options.headerHeight == null) this.renderer.opts.headerHeight = autoHeaderHeight(effective);
|
|
5755
6689
|
this.renderer.applyFont(size, family);
|
|
6690
|
+
this.renderer.invalidateHeights();
|
|
5756
6691
|
this.menu.close();
|
|
5757
6692
|
this.layoutChanged();
|
|
5758
6693
|
}
|
|
@@ -5788,30 +6723,55 @@ var Grid = class {
|
|
|
5788
6723
|
dataColumns() {
|
|
5789
6724
|
return this.columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
5790
6725
|
}
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
6726
|
+
validRange(range) {
|
|
6727
|
+
if (!range || this.options.rangeSelection === false) return null;
|
|
6728
|
+
const count = this.rowModel.getRowCount();
|
|
6729
|
+
const ok = (p) => !!p && !isSystemCol(p.colId) && !!this.columns.getColumn(p.colId) && p.rowIndex >= 0 && p.rowIndex < count;
|
|
6730
|
+
return ok(range.start) && ok(range.end) ? { start: { ...range.start }, end: { ...range.end } } : null;
|
|
6731
|
+
}
|
|
6732
|
+
/** Set the active range. Earlier Ctrl ranges are dropped unless `keepOthers`. */
|
|
6733
|
+
setRange(range, keepOthers = false) {
|
|
6734
|
+
const next = this.validRange(range);
|
|
6735
|
+
const extras = keepOthers || !this.store.state.extraRanges.length ? this.store.state.extraRanges : [];
|
|
5798
6736
|
const cur = this.store.state.range;
|
|
5799
|
-
if (JSON.stringify(cur) === JSON.stringify(next)) return;
|
|
5800
|
-
this.store.set({ range: next });
|
|
6737
|
+
if (JSON.stringify(cur) === JSON.stringify(next) && extras === this.store.state.extraRanges) return;
|
|
6738
|
+
this.store.set({ range: next, extraRanges: extras });
|
|
5801
6739
|
this.events.emit("rangeChange", { range: next && { start: { ...next.start }, end: { ...next.end } } });
|
|
5802
6740
|
}
|
|
5803
|
-
/**
|
|
5804
|
-
|
|
5805
|
-
const
|
|
6741
|
+
/** Ctrl+click / Ctrl+drag: keep what is selected (the focused cell when there is no range) and start a new range. */
|
|
6742
|
+
addRange(range) {
|
|
6743
|
+
const { range: active, focus } = this.store.state;
|
|
6744
|
+
const keep = this.validRange(active ?? (focus ? { start: focus, end: focus } : null));
|
|
6745
|
+
if (keep) this.store.set({ extraRanges: [...this.store.state.extraRanges, keep] });
|
|
6746
|
+
this.setRange(range, true);
|
|
6747
|
+
}
|
|
6748
|
+
getRanges() {
|
|
6749
|
+
const all2 = [...this.store.state.extraRanges];
|
|
6750
|
+
if (this.store.state.range) all2.push(this.store.state.range);
|
|
6751
|
+
return all2.map((r) => ({ start: { ...r.start }, end: { ...r.end } }));
|
|
6752
|
+
}
|
|
6753
|
+
setRanges(ranges) {
|
|
6754
|
+
const list = (Array.isArray(ranges) ? ranges : []).map((r) => this.validRange(r)).filter((r) => !!r);
|
|
6755
|
+
this.store.set({ extraRanges: list.slice(0, -1) });
|
|
6756
|
+
this.setRange(list[list.length - 1] ?? null, true);
|
|
6757
|
+
}
|
|
6758
|
+
boundsOf(r) {
|
|
5806
6759
|
if (!r) return null;
|
|
5807
6760
|
const a = this.columns.getColumn(r.start.colId), b = this.columns.getColumn(r.end.colId);
|
|
5808
6761
|
if (!a || !b) return null;
|
|
5809
6762
|
return normalizeRange({ start: { rowIndex: r.start.rowIndex, colIndex: a.index }, end: { rowIndex: r.end.rowIndex, colIndex: b.index } });
|
|
5810
6763
|
}
|
|
6764
|
+
/** Active range in displayed column indexes, or null. */
|
|
6765
|
+
rangeBounds() {
|
|
6766
|
+
return this.boundsOf(this.store.state.range);
|
|
6767
|
+
}
|
|
6768
|
+
/** Every range (Ctrl ranges first, the active one last). */
|
|
6769
|
+
allRangeBounds() {
|
|
6770
|
+
return [...this.store.state.extraRanges, this.store.state.range].map((r) => this.boundsOf(r)).filter((b) => !!b);
|
|
6771
|
+
}
|
|
5811
6772
|
rangeContains(rowIndex, colId) {
|
|
5812
|
-
const b = this.rangeBounds();
|
|
5813
6773
|
const col = this.columns.getColumn(colId);
|
|
5814
|
-
return !!
|
|
6774
|
+
return !!col && this.allRangeBounds().some((b) => rangeContains(b, rowIndex, col.index));
|
|
5815
6775
|
}
|
|
5816
6776
|
selectAllCells() {
|
|
5817
6777
|
const cols = this.dataColumns();
|
|
@@ -5829,6 +6789,18 @@ var Grid = class {
|
|
|
5829
6789
|
const checkedRows = rows;
|
|
5830
6790
|
let out;
|
|
5831
6791
|
let cols;
|
|
6792
|
+
const all2 = rows ? [] : this.allRangeBounds();
|
|
6793
|
+
if (all2.length > 1) {
|
|
6794
|
+
const sameCols = all2.every((x) => x.c1 === all2[0].c1 && x.c2 === all2[0].c2);
|
|
6795
|
+
const sameRows = all2.every((x) => x.r1 === all2[0].r1 && x.r2 === all2[0].r2);
|
|
6796
|
+
if (sameCols || sameRows) {
|
|
6797
|
+
const parts = [...all2].sort((x, y) => sameCols ? x.r1 - y.r1 : x.c1 - y.c1);
|
|
6798
|
+
const blocks = parts.map((p) => this.rangeMatrix(p));
|
|
6799
|
+
const matrix2 = sameCols ? blocks.flat() : blocks[0].map((_, ri) => blocks.flatMap((bl) => bl[ri] ?? []));
|
|
6800
|
+
if (headers) matrix2.unshift((sameCols ? parts.slice(0, 1) : parts).flatMap((p) => this.rangeHeaders(p)));
|
|
6801
|
+
return toTSV(matrix2);
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
5832
6804
|
if (b) {
|
|
5833
6805
|
out = [];
|
|
5834
6806
|
for (let i = b.r1; i <= b.r2; i++) {
|
|
@@ -5848,22 +6820,51 @@ var Grid = class {
|
|
|
5848
6820
|
out = [node.data];
|
|
5849
6821
|
cols = [col];
|
|
5850
6822
|
}
|
|
5851
|
-
const matrix = out.map((r) => cols.map((c) =>
|
|
6823
|
+
const matrix = out.map((r) => cols.map((c) => this.copyCell(c.def, r, code)));
|
|
5852
6824
|
if (headers) matrix.unshift(cols.map((c) => c.def.header ?? c.def.field ?? c.id));
|
|
5853
6825
|
return toTSV(matrix);
|
|
5854
6826
|
}
|
|
6827
|
+
rangeHeaders(b) {
|
|
6828
|
+
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);
|
|
6829
|
+
}
|
|
6830
|
+
/** Copy text of one range. */
|
|
6831
|
+
rangeMatrix(b) {
|
|
6832
|
+
const cols = this.columns.displayed().slice(b.c1, b.c2 + 1).filter((c) => !isSystemCol(c.id));
|
|
6833
|
+
const code = this.store.state.locale.code;
|
|
6834
|
+
const out = [];
|
|
6835
|
+
for (let i = b.r1; i <= b.r2; i++) {
|
|
6836
|
+
const node = this.rowModel.getRow(i);
|
|
6837
|
+
if (node && !node.kind) out.push(cols.map((c) => this.copyCell(c.def, node.data, code)));
|
|
6838
|
+
}
|
|
6839
|
+
return out;
|
|
6840
|
+
}
|
|
6841
|
+
copyCell(def, row, code) {
|
|
6842
|
+
const value = getCellValue(def, row);
|
|
6843
|
+
const rule = def.copyable;
|
|
6844
|
+
if (rule === false) return "";
|
|
6845
|
+
if (typeof rule === "function") {
|
|
6846
|
+
let ok = false;
|
|
6847
|
+
try {
|
|
6848
|
+
ok = rule({ value, row, column: def }) !== false;
|
|
6849
|
+
} catch {
|
|
6850
|
+
ok = false;
|
|
6851
|
+
}
|
|
6852
|
+
if (!ok) return "";
|
|
6853
|
+
}
|
|
6854
|
+
return copyValue(def, value, code);
|
|
6855
|
+
}
|
|
5855
6856
|
copyToClipboard(headers, rows = false) {
|
|
5856
|
-
const
|
|
5857
|
-
if (
|
|
6857
|
+
const text2 = this.selectionTSV(headers, rows);
|
|
6858
|
+
if (text2 && typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
5858
6859
|
try {
|
|
5859
|
-
navigator.clipboard.writeText(
|
|
6860
|
+
navigator.clipboard.writeText(text2)?.catch?.(() => void 0);
|
|
5860
6861
|
} catch {
|
|
5861
6862
|
}
|
|
5862
6863
|
}
|
|
5863
|
-
return
|
|
6864
|
+
return text2;
|
|
5864
6865
|
}
|
|
5865
|
-
pasteText(
|
|
5866
|
-
const matrix = parseTSV(
|
|
6866
|
+
pasteText(text2) {
|
|
6867
|
+
const matrix = parseTSV(text2);
|
|
5867
6868
|
const result = { changed: 0, skipped: 0 };
|
|
5868
6869
|
if (!matrix.length) return result;
|
|
5869
6870
|
const displayed = this.columns.displayed();
|
|
@@ -5895,7 +6896,7 @@ var Grid = class {
|
|
|
5895
6896
|
const raw = single && b ? matrix[0][0] : matrix[dr]?.[dc];
|
|
5896
6897
|
if (raw === void 0) continue;
|
|
5897
6898
|
const def = col.def;
|
|
5898
|
-
if (!def.
|
|
6899
|
+
if (!def.field || !cellEditable(def, node.data, ri)) {
|
|
5899
6900
|
result.skipped++;
|
|
5900
6901
|
continue;
|
|
5901
6902
|
}
|
|
@@ -5918,34 +6919,67 @@ var Grid = class {
|
|
|
5918
6919
|
const spec = def.editor;
|
|
5919
6920
|
if (spec && typeof spec === "object" && spec.type === "select") {
|
|
5920
6921
|
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 };
|
|
6922
|
+
const text2 = raw.trim().toLowerCase();
|
|
6923
|
+
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);
|
|
6924
|
+
if (!hit || hit.disabled) return { ok: false };
|
|
5924
6925
|
value = hit.value;
|
|
6926
|
+
} else if (def.formulas && isFormula(raw.trim())) {
|
|
6927
|
+
value = raw.trim();
|
|
5925
6928
|
} else {
|
|
5926
6929
|
const parsed = parseValue(def, raw, oldValue);
|
|
5927
6930
|
if (!parsed.ok) return parsed;
|
|
5928
6931
|
value = parsed.value;
|
|
5929
6932
|
}
|
|
5930
|
-
if (def
|
|
6933
|
+
if (this.cellError(def, row, value) !== null) return { ok: false };
|
|
6934
|
+
return { ok: true, value };
|
|
6935
|
+
}
|
|
6936
|
+
/** Why `value` cannot go into this cell (required, unique, validate), or null when it can. */
|
|
6937
|
+
cellError(def, row, value) {
|
|
6938
|
+
const loc = this.store.state.locale;
|
|
6939
|
+
if (def.required && isBlank(value)) return loc.required;
|
|
6940
|
+
if (def.unique && def.field && !isBlank(value)) {
|
|
6941
|
+
const key = value instanceof Date ? value.getTime() : String(value);
|
|
6942
|
+
const field = def.field;
|
|
6943
|
+
if (this.store.state.data.some((r) => {
|
|
6944
|
+
if (r === row) return false;
|
|
6945
|
+
const v = r[field];
|
|
6946
|
+
return !isBlank(v) && (v instanceof Date ? v.getTime() : String(v)) === key;
|
|
6947
|
+
})) return loc.duplicate;
|
|
6948
|
+
}
|
|
5931
6949
|
if (def.validate) {
|
|
6950
|
+
let res;
|
|
5932
6951
|
try {
|
|
5933
|
-
|
|
6952
|
+
res = def.validate(value, row);
|
|
5934
6953
|
} catch {
|
|
5935
|
-
|
|
6954
|
+
res = loc.invalidValue;
|
|
5936
6955
|
}
|
|
6956
|
+
if (res !== true) return typeof res === "string" && res ? res : loc.invalidValue;
|
|
5937
6957
|
}
|
|
5938
|
-
return
|
|
6958
|
+
return null;
|
|
6959
|
+
}
|
|
6960
|
+
validateAll() {
|
|
6961
|
+
const out = [];
|
|
6962
|
+
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));
|
|
6963
|
+
this.store.state.data.forEach((row, i) => {
|
|
6964
|
+
const id = this.rowModel.idOf(row);
|
|
6965
|
+
const rowIndex = this.rowModel.indexOfId(id);
|
|
6966
|
+
for (const c of cols) {
|
|
6967
|
+
if (!cellEditable(c.def, row, rowIndex < 0 ? i : rowIndex)) continue;
|
|
6968
|
+
const message = this.cellError(c.def, row, row[c.def.field]);
|
|
6969
|
+
if (message !== null) out.push({ row, rowIndex, colId: c.id, message });
|
|
6970
|
+
}
|
|
6971
|
+
});
|
|
6972
|
+
return out;
|
|
5939
6973
|
}
|
|
5940
6974
|
// ── merge, aggregates, fill, pinned rows ────────────────────
|
|
5941
6975
|
/** Merge blocks for every `merge` column over the displayed rows (memoized). */
|
|
5942
6976
|
mergeSpans() {
|
|
5943
|
-
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge)) return NO_SPANS;
|
|
6977
|
+
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan)) return NO_SPANS;
|
|
5944
6978
|
const rows = this.rowModel.rows();
|
|
5945
6979
|
const ver = this.editVersion * 1e6 + this.columns.displayed().length;
|
|
5946
6980
|
if (this.spanCache && this.spanCache.rows === rows && this.spanCache.ver === ver) return this.spanCache.spans;
|
|
5947
6981
|
const spans = /* @__PURE__ */ new Map();
|
|
5948
|
-
const merged = this.columns.displayed().filter((c) => c.def.merge);
|
|
6982
|
+
const merged = this.columns.displayed().filter((c) => c.def.merge || c.def.rowSpan);
|
|
5949
6983
|
if (merged.length && !rows.some((n) => n.kind)) {
|
|
5950
6984
|
const data = rows.map((n) => n.data);
|
|
5951
6985
|
for (const c of merged) spans.set(c.id, computeSpans(data, c.def));
|
|
@@ -5955,15 +6989,22 @@ var Grid = class {
|
|
|
5955
6989
|
}
|
|
5956
6990
|
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
5957
6991
|
rangeAggregates() {
|
|
5958
|
-
const
|
|
5959
|
-
if (!
|
|
5960
|
-
const
|
|
6992
|
+
const all2 = this.allRangeBounds();
|
|
6993
|
+
if (!all2.length) return null;
|
|
6994
|
+
const displayed = this.columns.displayed();
|
|
5961
6995
|
const out = { cells: 0, count: 0, numbers: 0, sum: 0, min: Infinity, max: -Infinity };
|
|
5962
|
-
|
|
6996
|
+
const seen2 = all2.length > 1 ? /* @__PURE__ */ new Set() : null;
|
|
6997
|
+
for (const b of all2) for (let r = b.r1; r <= b.r2; r++) {
|
|
5963
6998
|
const node = this.rowModel.getRow(r);
|
|
5964
6999
|
if (!node || node.kind) continue;
|
|
5965
7000
|
const row = node.data;
|
|
5966
|
-
for (const c of
|
|
7001
|
+
for (const c of displayed.slice(b.c1, b.c2 + 1)) {
|
|
7002
|
+
if (isSystemCol(c.id)) continue;
|
|
7003
|
+
if (seen2) {
|
|
7004
|
+
const k = `${r},${c.index}`;
|
|
7005
|
+
if (seen2.has(k)) continue;
|
|
7006
|
+
seen2.add(k);
|
|
7007
|
+
}
|
|
5967
7008
|
out.cells++;
|
|
5968
7009
|
const v = getCellValue(c.def, row);
|
|
5969
7010
|
if (isBlank(v)) continue;
|
|
@@ -5998,13 +7039,8 @@ var Grid = class {
|
|
|
5998
7039
|
const col = displayed[c];
|
|
5999
7040
|
const node = this.rowModel.getRow(r);
|
|
6000
7041
|
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
|
-
}
|
|
7042
|
+
if (!node || node.kind || !def?.field || isSystemCol(col.id) || !cellEditable(def, node.data, r)) return;
|
|
7043
|
+
if (this.cellError(def, node.data, value2) !== null) return;
|
|
6008
7044
|
this.writeCell(changes, node.data, def.field, col.id, r, value2);
|
|
6009
7045
|
};
|
|
6010
7046
|
const value = (r, c) => getCellValue(displayed[c].def, this.rowModel.getRow(r).data);
|
|
@@ -6038,6 +7074,7 @@ var Grid = class {
|
|
|
6038
7074
|
writeCell(changes, row, field, colId, rowIndex, value) {
|
|
6039
7075
|
const oldValue = row[field];
|
|
6040
7076
|
if (Object.is(oldValue, value)) return false;
|
|
7077
|
+
if (this.veto("beforeCellChange", { row, rowIndex, colId, oldValue, newValue: value })) return false;
|
|
6041
7078
|
this.tracker.beforeChange(row);
|
|
6042
7079
|
row[field] = value;
|
|
6043
7080
|
changes.push({ row, field, colId, oldValue, newValue: value });
|
|
@@ -6059,6 +7096,7 @@ var Grid = class {
|
|
|
6059
7096
|
this.afterDataEdit();
|
|
6060
7097
|
}
|
|
6061
7098
|
afterDataEdit() {
|
|
7099
|
+
this.renderer.invalidateHeights();
|
|
6062
7100
|
this.editVersion++;
|
|
6063
7101
|
this.refreshSummary();
|
|
6064
7102
|
this.renderer.invalidate({ rows: true });
|
|
@@ -6074,17 +7112,17 @@ var Grid = class {
|
|
|
6074
7112
|
/** Delete/Backspace: clear editable cells in the range, or the focused cell. */
|
|
6075
7113
|
clearCells() {
|
|
6076
7114
|
const displayed = this.columns.displayed();
|
|
6077
|
-
const
|
|
7115
|
+
const all2 = this.allRangeBounds();
|
|
6078
7116
|
const f = this.store.state.focus;
|
|
6079
7117
|
const fi = f ? this.columns.getColumn(f.colId)?.index : void 0;
|
|
6080
|
-
const
|
|
6081
|
-
if (!
|
|
7118
|
+
const boxes = all2.length ? all2 : f && fi !== void 0 ? [{ r1: f.rowIndex, r2: f.rowIndex, c1: fi, c2: fi }] : [];
|
|
7119
|
+
if (!boxes.length) return;
|
|
6082
7120
|
const changes = [];
|
|
6083
|
-
for (let r = box.r1; r <= box.r2; r++) {
|
|
7121
|
+
for (const box of boxes) for (let r = box.r1; r <= box.r2; r++) {
|
|
6084
7122
|
const node = this.rowModel.getRow(r);
|
|
6085
7123
|
for (let c = box.c1; c <= box.c2; c++) {
|
|
6086
7124
|
const def = displayed[c]?.def;
|
|
6087
|
-
if (node && !node.kind && def?.
|
|
7125
|
+
if (node && !node.kind && def?.field && cellEditable(def, node.data, r)) this.writeCell(changes, node.data, def.field, displayed[c].id, r, null);
|
|
6088
7126
|
}
|
|
6089
7127
|
}
|
|
6090
7128
|
this.endCellTx(changes);
|
|
@@ -6264,6 +7302,7 @@ var Grid = class {
|
|
|
6264
7302
|
}
|
|
6265
7303
|
return s.dir === "asc" || s.dir === "desc";
|
|
6266
7304
|
});
|
|
7305
|
+
if (this.veto("beforeSort", { sort: valid.map((s) => ({ ...s })) })) return;
|
|
6267
7306
|
this.store.set({ sort: valid });
|
|
6268
7307
|
this.events.emit("sortChange", { sort: [...valid] });
|
|
6269
7308
|
}
|
|
@@ -6285,6 +7324,7 @@ var Grid = class {
|
|
|
6285
7324
|
warn(`setFilter: unknown column "${colId}"`);
|
|
6286
7325
|
return;
|
|
6287
7326
|
}
|
|
7327
|
+
if (this.veto("beforeFilter", { colId, model })) return;
|
|
6288
7328
|
const filters = { ...this.store.state.filters };
|
|
6289
7329
|
if (model) filters[colId] = model;
|
|
6290
7330
|
else delete filters[colId];
|
|
@@ -6305,9 +7345,21 @@ var Grid = class {
|
|
|
6305
7345
|
this.store.set({ selected, anchorId });
|
|
6306
7346
|
this.events.emit("selectionChange", { selected: this.selectedRows() });
|
|
6307
7347
|
}
|
|
7348
|
+
/** Data rows that may be checked (isRowSelectable). */
|
|
7349
|
+
rowSelectable(row) {
|
|
7350
|
+
const f = this.options.isRowSelectable;
|
|
7351
|
+
if (!f) return true;
|
|
7352
|
+
try {
|
|
7353
|
+
return !!f(row);
|
|
7354
|
+
} catch {
|
|
7355
|
+
return false;
|
|
7356
|
+
}
|
|
7357
|
+
}
|
|
6308
7358
|
selectFromClick(index, toggle, range) {
|
|
6309
7359
|
const mode = this.options.rowSelection;
|
|
6310
|
-
if (!mode || index < 0 || index >= this.rowModel.getRowCount()
|
|
7360
|
+
if (!mode || index < 0 || index >= this.rowModel.getRowCount()) return;
|
|
7361
|
+
const target = this.rowModel.getRow(index);
|
|
7362
|
+
if (target.kind || !this.rowSelectable(target.data)) return;
|
|
6311
7363
|
const r = nextSelection({
|
|
6312
7364
|
selected: this.store.state.selected,
|
|
6313
7365
|
anchorIndex: this.anchorIndex(),
|
|
@@ -6317,11 +7369,12 @@ var Grid = class {
|
|
|
6317
7369
|
toggle,
|
|
6318
7370
|
range
|
|
6319
7371
|
});
|
|
6320
|
-
if (this.rowModel.rows() !== this.rowModel.dataRows()) {
|
|
7372
|
+
if (this.rowModel.rows() !== this.rowModel.dataRows() || this.options.isRowSelectable) {
|
|
6321
7373
|
for (const id of r.selected) {
|
|
6322
7374
|
if (this.store.state.selected.has(id)) continue;
|
|
6323
7375
|
const i = this.rowModel.indexOfId(id);
|
|
6324
|
-
|
|
7376
|
+
const n = i >= 0 ? this.rowModel.getRow(i) : null;
|
|
7377
|
+
if (n && (n.kind || !this.rowSelectable(n.data))) r.selected.delete(id);
|
|
6325
7378
|
}
|
|
6326
7379
|
}
|
|
6327
7380
|
this.setSelected(r.selected, this.rowModel.getRow(r.anchorIndex)?.id ?? null);
|
|
@@ -6333,8 +7386,13 @@ var Grid = class {
|
|
|
6333
7386
|
const i = this.rowModel.indexOfId(id);
|
|
6334
7387
|
return i < 0 ? null : i;
|
|
6335
7388
|
}
|
|
6336
|
-
|
|
7389
|
+
/** Rows select-all acts on: displayed data rows that are selectable. */
|
|
7390
|
+
selectableRows() {
|
|
6337
7391
|
const rows = this.rowModel.dataRows();
|
|
7392
|
+
return this.options.isRowSelectable ? rows.filter((n) => this.rowSelectable(n.data)) : rows;
|
|
7393
|
+
}
|
|
7394
|
+
toggleSelectAll() {
|
|
7395
|
+
const rows = this.selectableRows();
|
|
6338
7396
|
const sel = this.store.state.selected;
|
|
6339
7397
|
const all2 = rows.length > 0 && rows.every((n) => sel.has(n.id));
|
|
6340
7398
|
const next = new Set(sel);
|
|
@@ -6354,7 +7412,7 @@ var Grid = class {
|
|
|
6354
7412
|
if (this.options.rowSelection) sys.push(SELECT_COLUMN);
|
|
6355
7413
|
return [...sys, ...defs];
|
|
6356
7414
|
}
|
|
6357
|
-
setData(rows) {
|
|
7415
|
+
setData(rows, options) {
|
|
6358
7416
|
if (this.clientOnly("setData")) return;
|
|
6359
7417
|
if (!Array.isArray(rows)) {
|
|
6360
7418
|
warn("setData: rows must be an array");
|
|
@@ -6364,8 +7422,11 @@ var Grid = class {
|
|
|
6364
7422
|
this.tracker.reset();
|
|
6365
7423
|
this.history.clear();
|
|
6366
7424
|
this.detailOpen.clear();
|
|
7425
|
+
this.treeLoaded.clear();
|
|
7426
|
+
this.treeLoading.clear();
|
|
6367
7427
|
this.renderer.dropDetail();
|
|
6368
7428
|
this.store.set({ data: cleanRows(rows) });
|
|
7429
|
+
if (options?.scrollToTop) this.renderer.setScroll({ top: 0, left: 0 });
|
|
6369
7430
|
const sel = this.store.state.selected;
|
|
6370
7431
|
if (sel.size) {
|
|
6371
7432
|
if (this.options.getRowId) {
|
|
@@ -6439,7 +7500,7 @@ var Grid = class {
|
|
|
6439
7500
|
return fn(...a);
|
|
6440
7501
|
});
|
|
6441
7502
|
return {
|
|
6442
|
-
setData: guard((rows) => this.setData(rows)),
|
|
7503
|
+
setData: guard((rows, o) => this.setData(rows, o)),
|
|
6443
7504
|
getData: guard(() => this.store.state.data),
|
|
6444
7505
|
setColumns: guard((defs) => this.setColumns(defs)),
|
|
6445
7506
|
getColumnState: guard(() => this.columns.getState()),
|
|
@@ -6453,10 +7514,14 @@ var Grid = class {
|
|
|
6453
7514
|
getFilterModel: guard(() => ({ ...this.store.state.filters })),
|
|
6454
7515
|
setQuickFilter: guard((t) => this.store.set({ quickFilter: t ?? "" })),
|
|
6455
7516
|
getSelectedRows: guard(() => this.selectedRows()),
|
|
6456
|
-
selectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set([...this.store.state.selected, ...this.
|
|
7517
|
+
selectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set([...this.store.state.selected, ...this.selectableRows().map((n) => n.id)]))),
|
|
6457
7518
|
deselectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set(), null)),
|
|
6458
7519
|
getDisplayedRowCount: guard(() => this.rowModel.getRowCount()),
|
|
6459
7520
|
scrollToRow: guard((i) => this.renderer.scrollToRow(i)),
|
|
7521
|
+
validate: guard(() => this.validateAll()),
|
|
7522
|
+
getScrollPosition: guard(() => this.renderer.getScroll()),
|
|
7523
|
+
setScrollPosition: guard((p) => this.renderer.setScroll(p ?? {})),
|
|
7524
|
+
syncScroll: guard((other, axis = "both") => this.syncScroll(other, axis)),
|
|
6460
7525
|
setTheme: guard((t) => this.store.set({ theme: t })),
|
|
6461
7526
|
setLocale: guard((l) => this.store.set({ locale: resolveLocale(l) })),
|
|
6462
7527
|
setFont: guard((f) => this.setFont(f)),
|
|
@@ -6473,8 +7538,10 @@ var Grid = class {
|
|
|
6473
7538
|
return r && { start: { ...r.start }, end: { ...r.end } };
|
|
6474
7539
|
}),
|
|
6475
7540
|
setRange: guard((r) => this.setRange(r)),
|
|
7541
|
+
getRanges: guard(() => this.getRanges()),
|
|
7542
|
+
setRanges: guard((list) => this.setRanges(list)),
|
|
6476
7543
|
copySelection: guard((o) => this.copyToClipboard(!!o?.headers, !!o?.rows)),
|
|
6477
|
-
pasteText: guard((
|
|
7544
|
+
pasteText: guard((text2) => this.pasteText(text2)),
|
|
6478
7545
|
startEdit: guard((rowIndex, colId) => this.editor.start(rowIndex, colId)),
|
|
6479
7546
|
stopEdit: guard((cancel) => cancel ? this.editor.cancel() : void this.editor.commit(null)),
|
|
6480
7547
|
addRows: guard((rows, index) => this.addRows(rows, index)),
|
|
@@ -6491,7 +7558,7 @@ var Grid = class {
|
|
|
6491
7558
|
canRedo: guard(() => this.history.canRedo()),
|
|
6492
7559
|
fillRange: guard((t) => this.fillRange(t)),
|
|
6493
7560
|
setPinnedRows: guard((r) => this.setPinnedRows(r)),
|
|
6494
|
-
find: guard((
|
|
7561
|
+
find: guard((text2) => this.finder.find(text2)),
|
|
6495
7562
|
findNext: guard(() => this.finder.next()),
|
|
6496
7563
|
findPrevious: guard(() => this.finder.previous()),
|
|
6497
7564
|
clearFind: guard(() => this.finder.clear()),
|
|
@@ -6503,6 +7570,7 @@ var Grid = class {
|
|
|
6503
7570
|
setDetailExpanded: guard((row, open) => this.setDetailExpanded(row, open)),
|
|
6504
7571
|
isDetailExpanded: guard((row) => this.isDetailExpanded(row)),
|
|
6505
7572
|
moveRow: guard((row, toIndex) => this.moveRow(row, toIndex)),
|
|
7573
|
+
dropRow: guard((row, target, position) => this.dropRow(row, target, position)),
|
|
6506
7574
|
setPage: guard((p) => this.setPage(p)),
|
|
6507
7575
|
getPage: guard(() => this.getPage()),
|
|
6508
7576
|
getPageCount: guard(() => this.getPageCount()),
|
|
@@ -6524,6 +7592,7 @@ var Grid = class {
|
|
|
6524
7592
|
destroy() {
|
|
6525
7593
|
if (this.destroyed) return;
|
|
6526
7594
|
this.destroyed = true;
|
|
7595
|
+
liveGrids.delete(this.renderer.root);
|
|
6527
7596
|
for (const d of this.disposers) d();
|
|
6528
7597
|
this.renderer.destroy();
|
|
6529
7598
|
this.events.clear();
|