@freegridjs/core 0.7.0 → 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/LICENSE +0 -7
- package/dist/cdn/freegrid.css +54 -1
- package/dist/cdn/freegrid.esm.js +8 -8
- package/dist/cdn/freegrid.min.js +8 -8
- package/dist/index.d.ts +14 -7
- package/dist/index.js +1393 -279
- package/dist/locales/de.d.ts +1 -1
- package/dist/locales/de.js +2 -0
- package/dist/locales/en.d.ts +1 -1
- package/dist/locales/en.js +2 -0
- package/dist/locales/es.d.ts +1 -1
- package/dist/locales/es.js +2 -0
- package/dist/locales/fr.d.ts +1 -1
- package/dist/locales/fr.js +2 -0
- package/dist/locales/ja.d.ts +1 -1
- package/dist/locales/ja.js +2 -0
- package/dist/locales/ko.d.ts +1 -1
- package/dist/locales/ko.js +2 -0
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +2 -0
- package/dist/styles.css +54 -1
- package/dist/{types-DZ66BpLd.d.ts → types-e45suTSB.d.ts} +152 -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
|
};
|
|
@@ -1374,6 +1814,103 @@ var Renderer = class {
|
|
|
1374
1814
|
);
|
|
1375
1815
|
this.pagerEl.replaceChildren(...parts);
|
|
1376
1816
|
}
|
|
1817
|
+
/** Small "free version" mark in the bottom-right corner (unlicensed company domains flagged on freegrid.app). */
|
|
1818
|
+
showFreeBadge() {
|
|
1819
|
+
if (this.root.querySelector(".fg-free-badge")) return;
|
|
1820
|
+
const a = el("a", "fg-free-badge", { href: "https://freegrid.app/pricing/", target: "_blank", rel: "noopener" });
|
|
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>';
|
|
1822
|
+
const text2 = el("span");
|
|
1823
|
+
text2.textContent = `FreeGrid \xB7 ${this.grid.store.state.locale.freeBadge}`;
|
|
1824
|
+
a.append(text2);
|
|
1825
|
+
this.root.append(a);
|
|
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
|
+
}
|
|
1377
1914
|
/** Forget cached detail panels (one master, or all). */
|
|
1378
1915
|
dropDetail(id) {
|
|
1379
1916
|
if (id === void 0) this.details.clear();
|
|
@@ -1383,14 +1920,14 @@ var Renderer = class {
|
|
|
1383
1920
|
return this.lastRange;
|
|
1384
1921
|
}
|
|
1385
1922
|
/** Text width in px using the grid font; falls back to 0.6em per char without canvas. */
|
|
1386
|
-
measureText(
|
|
1923
|
+
measureText(text2, bold) {
|
|
1387
1924
|
const cs = getComputedStyle(this.main);
|
|
1388
1925
|
const size = parseFloat(getComputedStyle(this.root).getPropertyValue("--fg-font-size")) || 13;
|
|
1389
1926
|
if (this.measureCtx === void 0) this.measureCtx = document.createElement("canvas").getContext("2d");
|
|
1390
1927
|
const ctx = this.measureCtx;
|
|
1391
|
-
if (!ctx) return
|
|
1928
|
+
if (!ctx) return text2.length * size * 0.6;
|
|
1392
1929
|
ctx.font = `${bold ? 600 : 400} ${size}px ${cs.fontFamily || "sans-serif"}`;
|
|
1393
|
-
return ctx.measureText(
|
|
1930
|
+
return ctx.measureText(text2).width;
|
|
1394
1931
|
}
|
|
1395
1932
|
scrollToRow(index) {
|
|
1396
1933
|
this.scroller.scrollTop = this.rowTop(Math.max(0, Math.min(index, this.grid.rowModel.getRowCount())));
|
|
@@ -1476,7 +2013,7 @@ var GroupPanel = class {
|
|
|
1476
2013
|
window.addEventListener("pointerup", up);
|
|
1477
2014
|
};
|
|
1478
2015
|
this.el = el("div", "fg-group-panel", { role: "toolbar" });
|
|
1479
|
-
grid.renderer.main.insertBefore(this.el, grid.renderer.
|
|
2016
|
+
grid.renderer.main.insertBefore(this.el, grid.renderer.gridEl);
|
|
1480
2017
|
this.el.addEventListener("click", this.onClick);
|
|
1481
2018
|
this.el.addEventListener("pointerdown", this.onPointerDown);
|
|
1482
2019
|
this.refresh();
|
|
@@ -1587,6 +2124,7 @@ var en = {
|
|
|
1587
2124
|
pasteHint: "Clipboard access was blocked. Press Ctrl+V to paste.",
|
|
1588
2125
|
required: "This field is required",
|
|
1589
2126
|
invalidValue: "Invalid value",
|
|
2127
|
+
duplicate: "This value is already used",
|
|
1590
2128
|
undo: "Undo",
|
|
1591
2129
|
redo: "Redo",
|
|
1592
2130
|
deleteRows: "Delete selected rows",
|
|
@@ -1633,6 +2171,7 @@ var en = {
|
|
|
1633
2171
|
chartLine: "Line chart",
|
|
1634
2172
|
chartArea: "Area chart",
|
|
1635
2173
|
chartPie: "Pie chart",
|
|
2174
|
+
freeBadge: "Free version",
|
|
1636
2175
|
ops: {
|
|
1637
2176
|
contains: "Contains",
|
|
1638
2177
|
equals: "Equals",
|
|
@@ -1691,6 +2230,25 @@ function resolveLocale(input, navLangs = navigatorLanguages()) {
|
|
|
1691
2230
|
return en_default;
|
|
1692
2231
|
}
|
|
1693
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
|
+
|
|
1694
2252
|
// src/keyboard.ts
|
|
1695
2253
|
var NAV_KEYS = /* @__PURE__ */ new Set(["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "Tab", "Home", "End", "PageUp", "PageDown"]);
|
|
1696
2254
|
function nextFocus(key, mods, p, s) {
|
|
@@ -1723,6 +2281,10 @@ function nextFocus(key, mods, p, s) {
|
|
|
1723
2281
|
}
|
|
1724
2282
|
}
|
|
1725
2283
|
|
|
2284
|
+
// src/registry.ts
|
|
2285
|
+
var liveGrids = /* @__PURE__ */ new Map();
|
|
2286
|
+
var gridOfRoot = (root) => root ? liveGrids.get(root) : void 0;
|
|
2287
|
+
|
|
1726
2288
|
// src/interactions/body.ts
|
|
1727
2289
|
var DEFAULT_KEYMAP = {
|
|
1728
2290
|
Enter: "edit",
|
|
@@ -1744,10 +2306,17 @@ function comboOf(e) {
|
|
|
1744
2306
|
return parts.join("+");
|
|
1745
2307
|
}
|
|
1746
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
|
+
}
|
|
1747
2313
|
function attachBody(grid) {
|
|
1748
2314
|
const { renderer } = grid;
|
|
1749
2315
|
const body = renderer.bodyEl;
|
|
1750
2316
|
const root = renderer.root;
|
|
2317
|
+
const keyOf = (e) => mirrored(grid, e);
|
|
2318
|
+
let lastPointer = "mouse";
|
|
2319
|
+
let pressTimer = 0;
|
|
1751
2320
|
let suppressClick = false;
|
|
1752
2321
|
const toEvent = (e) => {
|
|
1753
2322
|
const hit = renderer.cellAt(e.target);
|
|
@@ -1762,7 +2331,7 @@ function attachBody(grid) {
|
|
|
1762
2331
|
const btn = e.target.closest?.(".fg-group-toggle, .fg-tree-toggle, .fg-detail-toggle");
|
|
1763
2332
|
const hit = btn && renderer.cellAt(e.target);
|
|
1764
2333
|
if (!hit) return false;
|
|
1765
|
-
|
|
2334
|
+
renderer.gridEl.focus({ preventScroll: true });
|
|
1766
2335
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
1767
2336
|
grid.setRange(null);
|
|
1768
2337
|
const node = grid.rowModel.getRow(hit.rowIndex);
|
|
@@ -1772,23 +2341,28 @@ function attachBody(grid) {
|
|
|
1772
2341
|
};
|
|
1773
2342
|
const onClick = (e) => {
|
|
1774
2343
|
if (onToggle(e)) return;
|
|
2344
|
+
const before = grid.store.state.focus;
|
|
1775
2345
|
const hit = renderer.cellAt(e.target);
|
|
1776
2346
|
if (hit && grid.rowModel.getRow(hit.rowIndex)?.kind) {
|
|
1777
|
-
|
|
2347
|
+
renderer.gridEl.focus({ preventScroll: true });
|
|
1778
2348
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
1779
2349
|
grid.setRange(null);
|
|
1780
2350
|
return;
|
|
1781
2351
|
}
|
|
1782
2352
|
const ev = toEvent(e);
|
|
1783
2353
|
if (!ev) return;
|
|
1784
|
-
|
|
2354
|
+
renderer.gridEl.focus({ preventScroll: true });
|
|
1785
2355
|
const dragged = suppressClick;
|
|
1786
2356
|
suppressClick = false;
|
|
1787
2357
|
const prev = grid.store.state.focus;
|
|
1788
2358
|
const onBox = ev.colId === SELECT_COL_ID;
|
|
1789
2359
|
if (dragged) {
|
|
1790
2360
|
} else if (e.shiftKey && prev && !onBox && !isSystemCol(prev.colId) && grid.options.rangeSelection !== false) {
|
|
1791
|
-
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);
|
|
1792
2366
|
} else {
|
|
1793
2367
|
grid.setFocus(ev.rowIndex, ev.colId);
|
|
1794
2368
|
grid.setRange(null);
|
|
@@ -1797,6 +2371,11 @@ function attachBody(grid) {
|
|
|
1797
2371
|
grid.selectFromClick(ev.rowIndex, onBox || e.ctrlKey || e.metaKey, e.shiftKey);
|
|
1798
2372
|
}
|
|
1799
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
|
+
}
|
|
1800
2379
|
};
|
|
1801
2380
|
const onDblClick = (e) => {
|
|
1802
2381
|
if (e.target.closest?.("button")) return;
|
|
@@ -1874,8 +2453,8 @@ function attachBody(grid) {
|
|
|
1874
2453
|
const open = !!(node.group?.expanded ?? node.expanded);
|
|
1875
2454
|
const key = grid.expandKey(node);
|
|
1876
2455
|
if (group && (e.key === "Enter" || e.key === " ")) grid.setGroupExpanded(key, !open);
|
|
1877
|
-
else if (e
|
|
1878
|
-
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);
|
|
1879
2458
|
else return false;
|
|
1880
2459
|
return true;
|
|
1881
2460
|
};
|
|
@@ -1887,16 +2466,42 @@ function attachBody(grid) {
|
|
|
1887
2466
|
if (!node || node.kind || !grid.canMoveRows()) return true;
|
|
1888
2467
|
const row = node.data;
|
|
1889
2468
|
const marker = el("div", "fg-row-drop-marker");
|
|
1890
|
-
let
|
|
2469
|
+
let drop = null;
|
|
1891
2470
|
root.classList.add("fg-row-dragging");
|
|
1892
2471
|
const move = (ev) => {
|
|
1893
|
-
const
|
|
1894
|
-
|
|
1895
|
-
const
|
|
1896
|
-
const
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
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);
|
|
1900
2505
|
};
|
|
1901
2506
|
const up = () => {
|
|
1902
2507
|
window.removeEventListener("pointermove", move);
|
|
@@ -1904,39 +2509,66 @@ function attachBody(grid) {
|
|
|
1904
2509
|
window.removeEventListener("pointercancel", up);
|
|
1905
2510
|
root.classList.remove("fg-row-dragging");
|
|
1906
2511
|
marker.remove();
|
|
1907
|
-
if (
|
|
1908
|
-
const over = grid.rowModel.getRow(target.index);
|
|
1909
|
-
const overRow = over?.detailOf?.data ?? over?.data;
|
|
1910
|
-
const data = grid.store.state.data;
|
|
1911
|
-
const from = data.indexOf(row);
|
|
1912
|
-
let to = data.indexOf(overRow) + (target.after || over?.kind === "detail" ? 1 : 0);
|
|
1913
|
-
if (from < 0 || to < 0) return;
|
|
1914
|
-
if (to > from) to--;
|
|
1915
|
-
grid.moveRow(row, to);
|
|
2512
|
+
if (drop) drop.into.dropRow(row, drop.target, drop.position, grid);
|
|
1916
2513
|
};
|
|
1917
2514
|
window.addEventListener("pointermove", move);
|
|
1918
2515
|
window.addEventListener("pointerup", up);
|
|
1919
2516
|
window.addEventListener("pointercancel", up);
|
|
1920
2517
|
return true;
|
|
1921
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
|
+
};
|
|
1922
2547
|
const onPointerDown = (e) => {
|
|
2548
|
+
lastPointer = e.pointerType || "mouse";
|
|
2549
|
+
if (lastPointer === "touch") {
|
|
2550
|
+
onTouchDown(e);
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
1923
2553
|
if (e.button === 0 && onHandleDown(e)) return;
|
|
1924
2554
|
if (grid.options.rangeSelection === false) return;
|
|
1925
2555
|
if (e.button !== 0 || e.shiftKey || e.target.closest("input, a, button")) return;
|
|
1926
2556
|
const start = renderer.cellAt(e.target);
|
|
1927
2557
|
if (!start || isSystemCol(start.colId)) return;
|
|
1928
2558
|
const x0 = e.clientX, y0 = e.clientY;
|
|
2559
|
+
const adding = e.ctrlKey || e.metaKey;
|
|
1929
2560
|
let dragging = false;
|
|
1930
2561
|
const move = (ev) => {
|
|
1931
2562
|
if (!dragging && Math.hypot(ev.clientX - x0, ev.clientY - y0) < DRAG_THRESHOLD2) return;
|
|
1932
|
-
const hit = renderer.cellAt(
|
|
2563
|
+
const hit = renderer.cellAt(elementAt(root, ev.clientX, ev.clientY));
|
|
1933
2564
|
if (!hit || isSystemCol(hit.colId)) return;
|
|
1934
2565
|
if (!dragging) {
|
|
1935
2566
|
dragging = true;
|
|
1936
2567
|
root.classList.add("fg-range-dragging");
|
|
2568
|
+
if (adding) grid.addRange({ start, end: start });
|
|
1937
2569
|
grid.setFocus(start.rowIndex, start.colId);
|
|
1938
2570
|
}
|
|
1939
|
-
grid.setRange({ start, end: hit });
|
|
2571
|
+
grid.setRange({ start, end: hit }, adding);
|
|
1940
2572
|
};
|
|
1941
2573
|
const up = () => {
|
|
1942
2574
|
window.removeEventListener("pointermove", move);
|
|
@@ -1948,7 +2580,7 @@ function attachBody(grid) {
|
|
|
1948
2580
|
window.addEventListener("pointerup", up);
|
|
1949
2581
|
};
|
|
1950
2582
|
const onKeyDown = (e) => {
|
|
1951
|
-
if (e.target !== root) return;
|
|
2583
|
+
if (e.target !== root && e.target !== renderer.gridEl) return;
|
|
1952
2584
|
const cols = grid.columns.displayed();
|
|
1953
2585
|
const count = grid.rowModel.getRowCount();
|
|
1954
2586
|
if (!cols.length || !count) return;
|
|
@@ -1990,7 +2622,7 @@ function attachBody(grid) {
|
|
|
1990
2622
|
const range = grid.store.state.range;
|
|
1991
2623
|
const end = range?.end ?? anchor;
|
|
1992
2624
|
const pos = { rowIndex: Math.min(end.rowIndex, count - 1), colIndex: Math.max(0, cols.findIndex((c) => c.id === end.colId)) };
|
|
1993
|
-
const next2 = nextFocus(e
|
|
2625
|
+
const next2 = nextFocus(keyOf(e), { ctrl, shift: false }, pos, size);
|
|
1994
2626
|
if (!next2) return;
|
|
1995
2627
|
e.preventDefault();
|
|
1996
2628
|
const colId2 = cols[Math.max(firstData, next2.colIndex)].id;
|
|
@@ -2003,7 +2635,7 @@ function attachBody(grid) {
|
|
|
2003
2635
|
if (!f) next = NAV_KEYS.has(e.key) && e.key !== "Tab" ? { rowIndex: 0, colIndex: 0 } : null;
|
|
2004
2636
|
else {
|
|
2005
2637
|
const colIndex2 = Math.max(0, cols.findIndex((c) => c.id === f.colId));
|
|
2006
|
-
next = nextFocus(e
|
|
2638
|
+
next = nextFocus(keyOf(e), { ctrl, shift: e.shiftKey }, { rowIndex: Math.min(f.rowIndex, count - 1), colIndex: colIndex2 }, size);
|
|
2007
2639
|
}
|
|
2008
2640
|
if (!next) return;
|
|
2009
2641
|
e.preventDefault();
|
|
@@ -2016,17 +2648,17 @@ function attachBody(grid) {
|
|
|
2016
2648
|
const nested = (e) => e.target?.closest?.(".fg-root") !== root;
|
|
2017
2649
|
const onCopy = (e) => {
|
|
2018
2650
|
if (fromField(e) || nested(e)) return;
|
|
2019
|
-
const
|
|
2020
|
-
if (!
|
|
2021
|
-
e.clipboardData.setData("text/plain",
|
|
2651
|
+
const text2 = grid.selectionTSV(false);
|
|
2652
|
+
if (!text2 || !e.clipboardData) return;
|
|
2653
|
+
e.clipboardData.setData("text/plain", text2);
|
|
2022
2654
|
e.preventDefault();
|
|
2023
2655
|
};
|
|
2024
2656
|
const onPaste = (e) => {
|
|
2025
2657
|
if (fromField(e) || nested(e)) return;
|
|
2026
|
-
const
|
|
2027
|
-
if (!
|
|
2658
|
+
const text2 = e.clipboardData?.getData("text/plain");
|
|
2659
|
+
if (!text2) return;
|
|
2028
2660
|
e.preventDefault();
|
|
2029
|
-
grid.pasteText(
|
|
2661
|
+
grid.pasteText(text2);
|
|
2030
2662
|
};
|
|
2031
2663
|
body.addEventListener("click", onClick);
|
|
2032
2664
|
body.addEventListener("dblclick", onDblClick);
|
|
@@ -2288,9 +2920,9 @@ var ColumnChooserPanel = class {
|
|
|
2288
2920
|
box.checked = leaves.length > 0 && shown === leaves.length;
|
|
2289
2921
|
box.indeterminate = shown > 0 && shown < leaves.length;
|
|
2290
2922
|
box.disabled = leaves.every((l) => l.def.lockVisible);
|
|
2291
|
-
const
|
|
2292
|
-
|
|
2293
|
-
row.append(box,
|
|
2923
|
+
const text2 = el("span", "fg-chooser-label");
|
|
2924
|
+
text2.textContent = labelOf(n);
|
|
2925
|
+
row.append(box, text2);
|
|
2294
2926
|
if (!n.children && n.pinned) {
|
|
2295
2927
|
const pin = el("span", "fg-chooser-pin");
|
|
2296
2928
|
pin.textContent = n.pinned === "left" ? "\u21E4" : "\u21E5";
|
|
@@ -2368,14 +3000,14 @@ var ColumnChooserDialog = class {
|
|
|
2368
3000
|
head.append(title, close);
|
|
2369
3001
|
this.panel = new ColumnChooserPanel(this.grid);
|
|
2370
3002
|
popup.append(head, this.panel.el);
|
|
2371
|
-
|
|
3003
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
2372
3004
|
const r = this.grid.renderer.root.getBoundingClientRect();
|
|
2373
3005
|
const w = popup.offsetWidth || 280;
|
|
2374
3006
|
popup.style.left = `${Math.max(4, Math.min(r.right - w - 8, window.innerWidth - w - 4))}px`;
|
|
2375
3007
|
popup.style.top = `${Math.max(4, r.top + 8)}px`;
|
|
2376
3008
|
this.popup = popup;
|
|
2377
3009
|
const onDown = (e) => {
|
|
2378
|
-
if (!
|
|
3010
|
+
if (!eventInside(e, popup)) this.close();
|
|
2379
3011
|
};
|
|
2380
3012
|
const onKey = (e) => {
|
|
2381
3013
|
if (e.key === "Escape") this.close();
|
|
@@ -2490,7 +3122,7 @@ var ContextMenu = class {
|
|
|
2490
3122
|
const hit = grid.renderer.cellAt(target);
|
|
2491
3123
|
if (!hit) return;
|
|
2492
3124
|
e.preventDefault();
|
|
2493
|
-
grid.renderer.
|
|
3125
|
+
grid.renderer.gridEl.focus({ preventScroll: true });
|
|
2494
3126
|
if (!isSystemCol(hit.colId) && !grid.rangeContains(hit.rowIndex, hit.colId)) {
|
|
2495
3127
|
grid.setFocus(hit.rowIndex, hit.colId);
|
|
2496
3128
|
grid.setRange({ start: hit, end: hit });
|
|
@@ -2560,7 +3192,7 @@ var ContextMenu = class {
|
|
|
2560
3192
|
const clip = typeof navigator !== "undefined" ? navigator.clipboard : void 0;
|
|
2561
3193
|
const fail = () => this.grid.toast(this.grid.store.state.locale.pasteHint.replace("Ctrl+V", shortcut("V")));
|
|
2562
3194
|
if (!clip?.readText) return fail();
|
|
2563
|
-
clip.readText().then((
|
|
3195
|
+
clip.readText().then((text2) => this.grid.pasteText(text2), fail);
|
|
2564
3196
|
}
|
|
2565
3197
|
open(x, y, rowIndex, colId) {
|
|
2566
3198
|
this.close();
|
|
@@ -2613,23 +3245,23 @@ var ContextMenu = class {
|
|
|
2613
3245
|
prevSep = false;
|
|
2614
3246
|
}
|
|
2615
3247
|
if (popup.lastElementChild?.classList.contains("fg-cm-sep")) popup.lastElementChild.remove();
|
|
2616
|
-
|
|
3248
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
2617
3249
|
const w = popup.offsetWidth || 220, h = popup.offsetHeight || 300;
|
|
2618
3250
|
popup.style.left = `${Math.max(4, Math.min(x, window.innerWidth - w - 4))}px`;
|
|
2619
3251
|
popup.style.top = `${Math.max(4, Math.min(y, window.innerHeight - h - 4))}px`;
|
|
2620
3252
|
this.popup = popup;
|
|
2621
3253
|
const onDown = (e) => {
|
|
2622
|
-
if (!
|
|
3254
|
+
if (!eventInside(e, popup)) this.close();
|
|
2623
3255
|
};
|
|
2624
3256
|
const onKey = (e) => {
|
|
2625
3257
|
if (e.key === "Escape") {
|
|
2626
3258
|
this.close();
|
|
2627
|
-
grid.renderer.
|
|
3259
|
+
grid.renderer.gridEl.focus();
|
|
2628
3260
|
return;
|
|
2629
3261
|
}
|
|
2630
3262
|
if (e.key !== "ArrowDown" && e.key !== "ArrowUp") return;
|
|
2631
3263
|
const buttons = [...popup.querySelectorAll("button:not(:disabled)")];
|
|
2632
|
-
const i = buttons.indexOf(
|
|
3264
|
+
const i = buttons.indexOf(activeElement(popup));
|
|
2633
3265
|
const next = buttons[(i + (e.key === "ArrowDown" ? 1 : buttons.length - 1) + buttons.length) % buttons.length];
|
|
2634
3266
|
next?.focus();
|
|
2635
3267
|
e.preventDefault();
|
|
@@ -2667,7 +3299,7 @@ var FillHandle = class {
|
|
|
2667
3299
|
let target = null;
|
|
2668
3300
|
const rootEl = grid.renderer.root;
|
|
2669
3301
|
const move = (ev) => {
|
|
2670
|
-
const hit = grid.renderer.cellAt(
|
|
3302
|
+
const hit = grid.renderer.cellAt(elementAt(grid.renderer.root, ev.clientX, ev.clientY));
|
|
2671
3303
|
const col = hit && grid.columns.getColumn(hit.colId);
|
|
2672
3304
|
if (!hit || !col || isSystemCol(col.id)) return;
|
|
2673
3305
|
const dr = hit.rowIndex > src.r2 ? hit.rowIndex - src.r2 : hit.rowIndex < src.r1 ? hit.rowIndex - src.r1 : 0;
|
|
@@ -2724,7 +3356,7 @@ var FillHandle = class {
|
|
|
2724
3356
|
}
|
|
2725
3357
|
const root = this.grid.renderer.root.getBoundingClientRect();
|
|
2726
3358
|
const r = cell.getBoundingClientRect();
|
|
2727
|
-
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`;
|
|
2728
3360
|
this.handle.style.top = `${r.bottom - root.top - 4}px`;
|
|
2729
3361
|
if (!this.handle.isConnected) this.grid.renderer.root.append(this.handle);
|
|
2730
3362
|
}
|
|
@@ -2773,8 +3405,8 @@ var Finder = class {
|
|
|
2773
3405
|
if (i === void 0) return "";
|
|
2774
3406
|
return i === this.index ? " fg-cell-match fg-cell-match-current" : " fg-cell-match";
|
|
2775
3407
|
}
|
|
2776
|
-
find(
|
|
2777
|
-
this.query =
|
|
3408
|
+
find(text2) {
|
|
3409
|
+
this.query = text2.trim().toLowerCase();
|
|
2778
3410
|
this.matches = [];
|
|
2779
3411
|
this.lookup.clear();
|
|
2780
3412
|
this.index = -1;
|
|
@@ -2787,8 +3419,8 @@ var Finder = class {
|
|
|
2787
3419
|
outer: for (let r = 0; r < rows.length; r++) {
|
|
2788
3420
|
if (rows[r].kind) continue;
|
|
2789
3421
|
for (let c = 0; c < cols.length; c++) {
|
|
2790
|
-
const
|
|
2791
|
-
if (!
|
|
3422
|
+
const text3 = fmts[c](getCellValue(cols[c].def, rows[r].data)).toLowerCase();
|
|
3423
|
+
if (!text3.includes(this.query)) continue;
|
|
2792
3424
|
this.lookup.set(`${r}|${cols[c].id}`, this.matches.length);
|
|
2793
3425
|
this.matches.push({ rowIndex: r, colId: cols[c].id });
|
|
2794
3426
|
if (this.matches.length >= MAX_MATCHES) break outer;
|
|
@@ -2840,9 +3472,9 @@ var Finder = class {
|
|
|
2840
3472
|
const bar = el("div", "fg-findbar", { role: "search" });
|
|
2841
3473
|
const input = el("input", "fg-input", { type: "search", placeholder: loc.findPlaceholder, "aria-label": loc.findPlaceholder });
|
|
2842
3474
|
this.countEl = el("span", "fg-findbar-count", { "aria-live": "polite" });
|
|
2843
|
-
const btn = (label,
|
|
3475
|
+
const btn = (label, text2, run) => {
|
|
2844
3476
|
const b = el("button", "fg-findbar-btn", { type: "button", "aria-label": label, title: label });
|
|
2845
|
-
b.textContent =
|
|
3477
|
+
b.textContent = text2;
|
|
2846
3478
|
b.addEventListener("click", run);
|
|
2847
3479
|
return b;
|
|
2848
3480
|
};
|
|
@@ -2874,40 +3506,10 @@ var Finder = class {
|
|
|
2874
3506
|
this.bar = null;
|
|
2875
3507
|
this.countEl = null;
|
|
2876
3508
|
this.clear();
|
|
2877
|
-
this.grid.renderer.
|
|
3509
|
+
this.grid.renderer.gridEl.focus({ preventScroll: true });
|
|
2878
3510
|
}
|
|
2879
3511
|
};
|
|
2880
3512
|
|
|
2881
|
-
// src/merge.ts
|
|
2882
|
-
function computeSpans(rows, def) {
|
|
2883
|
-
const n = rows.length;
|
|
2884
|
-
const start = new Int32Array(n);
|
|
2885
|
-
const end = new Int32Array(n);
|
|
2886
|
-
const same2 = typeof def.merge === "function" ? (a, b) => {
|
|
2887
|
-
try {
|
|
2888
|
-
return !!def.merge(getCellValue(def, rows[a]), getCellValue(def, rows[b]), rows[a], rows[b]);
|
|
2889
|
-
} catch {
|
|
2890
|
-
return false;
|
|
2891
|
-
}
|
|
2892
|
-
} : (() => {
|
|
2893
|
-
const keys = rows.map((r) => {
|
|
2894
|
-
const v = getCellValue(def, r);
|
|
2895
|
-
return isBlank(v) ? null : setKey(v);
|
|
2896
|
-
});
|
|
2897
|
-
return (a, b) => keys[a] !== null && keys[a] === keys[b];
|
|
2898
|
-
})();
|
|
2899
|
-
let s = 0;
|
|
2900
|
-
for (let i = 1; i <= n; i++) {
|
|
2901
|
-
if (i < n && same2(i - 1, i)) continue;
|
|
2902
|
-
for (let k = s; k < i; k++) {
|
|
2903
|
-
start[k] = s;
|
|
2904
|
-
end[k] = i - 1;
|
|
2905
|
-
}
|
|
2906
|
-
s = i;
|
|
2907
|
-
}
|
|
2908
|
-
return { start, end };
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
3513
|
// src/fill.ts
|
|
2912
3514
|
var DAY = 864e5;
|
|
2913
3515
|
function fillSeries(source, count) {
|
|
@@ -2952,22 +3554,22 @@ function toTSV(rows) {
|
|
|
2952
3554
|
const cell = (s) => /[\t\n\r"]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
2953
3555
|
return rows.map((r) => r.map(cell).join(" ")).join("\r\n");
|
|
2954
3556
|
}
|
|
2955
|
-
function parseTSV(
|
|
2956
|
-
return parseDelimited(
|
|
3557
|
+
function parseTSV(text2) {
|
|
3558
|
+
return parseDelimited(text2, " ");
|
|
2957
3559
|
}
|
|
2958
|
-
function parseDelimited(
|
|
2959
|
-
if (!
|
|
3560
|
+
function parseDelimited(text2, sep) {
|
|
3561
|
+
if (!text2) return [];
|
|
2960
3562
|
const rows = [];
|
|
2961
3563
|
let row = [];
|
|
2962
3564
|
let cell = "";
|
|
2963
3565
|
let i = 0;
|
|
2964
3566
|
let quoted = false;
|
|
2965
3567
|
let atCellStart = true;
|
|
2966
|
-
while (i <
|
|
2967
|
-
const ch =
|
|
3568
|
+
while (i < text2.length) {
|
|
3569
|
+
const ch = text2[i];
|
|
2968
3570
|
if (quoted) {
|
|
2969
3571
|
if (ch === '"') {
|
|
2970
|
-
if (
|
|
3572
|
+
if (text2[i + 1] === '"') {
|
|
2971
3573
|
cell += '"';
|
|
2972
3574
|
i += 2;
|
|
2973
3575
|
continue;
|
|
@@ -2999,7 +3601,7 @@ function parseDelimited(text, sep) {
|
|
|
2999
3601
|
row = [];
|
|
3000
3602
|
cell = "";
|
|
3001
3603
|
atCellStart = true;
|
|
3002
|
-
i += ch === "\r" &&
|
|
3604
|
+
i += ch === "\r" && text2[i + 1] === "\n" ? 2 : 1;
|
|
3003
3605
|
continue;
|
|
3004
3606
|
}
|
|
3005
3607
|
cell += ch;
|
|
@@ -3029,8 +3631,8 @@ function copyValue(def, value, locale) {
|
|
|
3029
3631
|
}
|
|
3030
3632
|
var TRUE_WORDS = /* @__PURE__ */ new Set(["true", "1", "y", "yes", "\uC608", "o", "on"]);
|
|
3031
3633
|
var FALSE_WORDS = /* @__PURE__ */ new Set(["false", "0", "n", "no", "\uC544\uB2C8\uC624", "x", "off"]);
|
|
3032
|
-
function parseNumber(
|
|
3033
|
-
let s =
|
|
3634
|
+
function parseNumber(text2) {
|
|
3635
|
+
let s = text2.trim();
|
|
3034
3636
|
if (!s) return { ok: true, value: null };
|
|
3035
3637
|
let negative = false;
|
|
3036
3638
|
if (/^\(.*\)$/.test(s)) {
|
|
@@ -3050,8 +3652,8 @@ function validDate(y, m, d) {
|
|
|
3050
3652
|
const date = new Date(y, m - 1, d);
|
|
3051
3653
|
return date.getFullYear() === y && date.getMonth() === m - 1 && date.getDate() === d ? date : null;
|
|
3052
3654
|
}
|
|
3053
|
-
function parseDate(
|
|
3054
|
-
const s =
|
|
3655
|
+
function parseDate(text2, previous) {
|
|
3656
|
+
const s = text2.trim();
|
|
3055
3657
|
if (!s) return { ok: true, value: null };
|
|
3056
3658
|
let date = null;
|
|
3057
3659
|
let m = /^(\d{4})\s*[-./년]\s*(\d{1,2})\s*[-./월]\s*(\d{1,2})\s*[.일]?$/.exec(s);
|
|
@@ -3061,26 +3663,37 @@ function parseDate(text, previous) {
|
|
|
3061
3663
|
if (!date) return { ok: false };
|
|
3062
3664
|
return { ok: true, value: previous instanceof Date ? date : toDateKey(date) };
|
|
3063
3665
|
}
|
|
3064
|
-
function parseValue(def,
|
|
3666
|
+
function parseValue(def, text2, previous) {
|
|
3065
3667
|
switch (def.type) {
|
|
3066
3668
|
case "number":
|
|
3067
|
-
return parseNumber(
|
|
3669
|
+
return parseNumber(text2);
|
|
3068
3670
|
case "date":
|
|
3069
|
-
return parseDate(
|
|
3671
|
+
return parseDate(text2, previous);
|
|
3070
3672
|
case "boolean": {
|
|
3071
|
-
const t =
|
|
3673
|
+
const t = text2.trim().toLowerCase();
|
|
3072
3674
|
if (!t) return { ok: true, value: null };
|
|
3073
3675
|
if (TRUE_WORDS.has(t)) return { ok: true, value: true };
|
|
3074
3676
|
if (FALSE_WORDS.has(t)) return { ok: true, value: false };
|
|
3075
3677
|
return { ok: false };
|
|
3076
3678
|
}
|
|
3077
3679
|
default:
|
|
3078
|
-
return { ok: true, value:
|
|
3680
|
+
return { ok: true, value: text2 };
|
|
3079
3681
|
}
|
|
3080
3682
|
}
|
|
3081
3683
|
|
|
3082
3684
|
// src/editing/editor.ts
|
|
3083
|
-
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
|
+
}
|
|
3084
3697
|
var CellEditor = class {
|
|
3085
3698
|
constructor(grid) {
|
|
3086
3699
|
this.grid = grid;
|
|
@@ -3092,12 +3705,15 @@ var CellEditor = class {
|
|
|
3092
3705
|
canEdit(rowIndex, colId) {
|
|
3093
3706
|
if (isSystemCol(colId)) return false;
|
|
3094
3707
|
const col = this.grid.columns.getColumn(colId);
|
|
3095
|
-
|
|
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);
|
|
3096
3711
|
}
|
|
3097
3712
|
start(rowIndex, colId, initialText) {
|
|
3098
3713
|
if (this.cur) this.commit(null);
|
|
3099
3714
|
if (!this.canEdit(rowIndex, colId)) return false;
|
|
3100
3715
|
const { grid } = this;
|
|
3716
|
+
if (grid.veto("beforeEdit", { row: grid.rowModel.getRow(rowIndex).data, rowIndex, colId })) return false;
|
|
3101
3717
|
const { renderer } = grid;
|
|
3102
3718
|
const col = grid.columns.getColumn(colId);
|
|
3103
3719
|
const def = col.def;
|
|
@@ -3133,12 +3749,24 @@ var CellEditor = class {
|
|
|
3133
3749
|
const opt = el("option");
|
|
3134
3750
|
opt.value = String(i);
|
|
3135
3751
|
opt.textContent = o.label ?? String(o.value);
|
|
3752
|
+
opt.disabled = !!o.disabled;
|
|
3136
3753
|
select.append(opt);
|
|
3137
3754
|
});
|
|
3138
3755
|
select.selectedIndex = Math.max(0, options.findIndex((o) => Object.is(o.value, value)));
|
|
3139
3756
|
control = select;
|
|
3140
3757
|
read = () => ({ value: options[select.selectedIndex]?.value ?? null });
|
|
3141
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);
|
|
3142
3770
|
} else {
|
|
3143
3771
|
const textarea = spec?.type === "textarea";
|
|
3144
3772
|
const input = textarea ? el("textarea", "fg-editor-control") : el("input", "fg-editor-control");
|
|
@@ -3209,21 +3837,15 @@ var CellEditor = class {
|
|
|
3209
3837
|
const old = cur.row[field];
|
|
3210
3838
|
const raw = cur.read();
|
|
3211
3839
|
let value;
|
|
3212
|
-
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) {
|
|
3213
3843
|
const parsed = parseValue(cur.def, raw.text, old);
|
|
3214
3844
|
if (!parsed.ok) return this.fail(raw.text, loc.invalidValue);
|
|
3215
3845
|
value = parsed.value;
|
|
3216
3846
|
} else value = raw.value;
|
|
3217
|
-
|
|
3218
|
-
if (
|
|
3219
|
-
let res;
|
|
3220
|
-
try {
|
|
3221
|
-
res = cur.def.validate(value, cur.row);
|
|
3222
|
-
} catch {
|
|
3223
|
-
res = loc.invalidValue;
|
|
3224
|
-
}
|
|
3225
|
-
if (res !== true) return this.fail(value, typeof res === "string" && res ? res : loc.invalidValue);
|
|
3226
|
-
}
|
|
3847
|
+
const error = grid.cellError(cur.def, cur.row, value);
|
|
3848
|
+
if (error !== null) return this.fail(value, error);
|
|
3227
3849
|
this.close(true);
|
|
3228
3850
|
grid.applyEdit(cur.rowIndex, cur.colId, value);
|
|
3229
3851
|
this.move(cur.rowIndex, cur.colId, move);
|
|
@@ -3244,7 +3866,7 @@ var CellEditor = class {
|
|
|
3244
3866
|
if (!cur) return;
|
|
3245
3867
|
this.cur = null;
|
|
3246
3868
|
cur.dispose();
|
|
3247
|
-
this.grid.renderer.
|
|
3869
|
+
this.grid.renderer.gridEl.focus({ preventScroll: true });
|
|
3248
3870
|
this.grid.events.emit("editStop", { rowIndex: cur.rowIndex, colId: cur.colId, committed });
|
|
3249
3871
|
}
|
|
3250
3872
|
move(rowIndex, colId, move) {
|
|
@@ -3377,7 +3999,7 @@ function startResize(grid, e, colId) {
|
|
|
3377
3999
|
grid.renderer.root.classList.add("fg-resizing");
|
|
3378
4000
|
track(
|
|
3379
4001
|
(ev) => {
|
|
3380
|
-
grid.columns.setWidth(colId, startW + ev.clientX - startX);
|
|
4002
|
+
grid.columns.setWidth(colId, startW + (ev.clientX - startX) * (grid.renderer.rtl ? -1 : 1));
|
|
3381
4003
|
grid.layoutChanged();
|
|
3382
4004
|
},
|
|
3383
4005
|
() => {
|
|
@@ -3397,7 +4019,7 @@ function beginDrag(grid, colId, cell, pane) {
|
|
|
3397
4019
|
const ghost = el("div", `fg-drag-ghost fg-theme-${grid.store.state.theme}`);
|
|
3398
4020
|
ghost.textContent = cell.querySelector(".fg-header-label")?.textContent ?? "";
|
|
3399
4021
|
grid.applyFontTo(ghost);
|
|
3400
|
-
|
|
4022
|
+
popupParent(grid.renderer.root).append(ghost);
|
|
3401
4023
|
const marker = el("div", "fg-drop-marker");
|
|
3402
4024
|
marker.style.height = `${grid.renderer.headerEl.offsetHeight}px`;
|
|
3403
4025
|
grid.renderer.root.append(marker);
|
|
@@ -3412,11 +4034,13 @@ function updateDrag(grid, colId, d, x, y) {
|
|
|
3412
4034
|
let t = 0;
|
|
3413
4035
|
for (const s of d.sibs) {
|
|
3414
4036
|
const r = s.el.getBoundingClientRect();
|
|
3415
|
-
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++;
|
|
3416
4038
|
}
|
|
3417
4039
|
d.target = t;
|
|
3418
4040
|
const rootLeft = grid.renderer.root.getBoundingClientRect().left;
|
|
3419
|
-
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);
|
|
3420
4044
|
d.marker.style.left = `${edge - rootLeft - 1}px`;
|
|
3421
4045
|
}
|
|
3422
4046
|
function finishDrag(grid, colId, d) {
|
|
@@ -3467,6 +4091,7 @@ function attachHeader(grid) {
|
|
|
3467
4091
|
const cell = target.closest(".fg-header-cell");
|
|
3468
4092
|
const colId = cell?.dataset.col;
|
|
3469
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;
|
|
3470
4095
|
e.preventDefault();
|
|
3471
4096
|
if (target.closest(".fg-resize")) startResize(grid, e, colId);
|
|
3472
4097
|
else startPress(grid, e, colId, cell);
|
|
@@ -3502,16 +4127,16 @@ function toCsv(rows, sep = ",") {
|
|
|
3502
4127
|
};
|
|
3503
4128
|
return rows.map((r) => r.map(cell).join(sep)).join("\r\n");
|
|
3504
4129
|
}
|
|
3505
|
-
function detectSeparator(
|
|
3506
|
-
const line =
|
|
4130
|
+
function detectSeparator(text2) {
|
|
4131
|
+
const line = text2.slice(0, text2.search(/\r?\n|$/));
|
|
3507
4132
|
const count = (ch) => line.split(ch).length - 1;
|
|
3508
4133
|
const tabs = count(" "), semis = count(";"), commas = count(",");
|
|
3509
4134
|
if (tabs && tabs >= commas && tabs >= semis) return " ";
|
|
3510
4135
|
if (semis > commas) return ";";
|
|
3511
4136
|
return ",";
|
|
3512
4137
|
}
|
|
3513
|
-
function parseCsv(
|
|
3514
|
-
return parseDelimited(
|
|
4138
|
+
function parseCsv(text2, sep = detectSeparator(text2)) {
|
|
4139
|
+
return parseDelimited(text2.replace(/^\uFEFF/, ""), sep);
|
|
3515
4140
|
}
|
|
3516
4141
|
|
|
3517
4142
|
// src/export/zip.ts
|
|
@@ -3660,9 +4285,42 @@ function writeXlsx(sheets) {
|
|
|
3660
4285
|
const fmtIds = /* @__PURE__ */ new Map([["General", 0]]);
|
|
3661
4286
|
const customFmts = [];
|
|
3662
4287
|
const xfs = ['<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>'];
|
|
3663
|
-
const xfIndex = /* @__PURE__ */ new Map([["General
|
|
3664
|
-
const
|
|
3665
|
-
|
|
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}`;
|
|
3666
4324
|
let i = xfIndex.get(key);
|
|
3667
4325
|
if (i !== void 0) return i;
|
|
3668
4326
|
let id = fmtIds.get(fmt);
|
|
@@ -3671,8 +4329,11 @@ function writeXlsx(sheets) {
|
|
|
3671
4329
|
customFmts.push(fmt);
|
|
3672
4330
|
fmtIds.set(fmt, id);
|
|
3673
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"' : "");
|
|
3674
4335
|
i = xfs.length;
|
|
3675
|
-
xfs.push(`<xf
|
|
4336
|
+
xfs.push(align ? `<xf ${attrs}><alignment horizontal="${align}"/></xf>` : `<xf ${attrs}/>`);
|
|
3676
4337
|
xfIndex.set(key, i);
|
|
3677
4338
|
return i;
|
|
3678
4339
|
};
|
|
@@ -3691,7 +4352,7 @@ function writeXlsx(sheets) {
|
|
|
3691
4352
|
v = toSerial(v);
|
|
3692
4353
|
}
|
|
3693
4354
|
}
|
|
3694
|
-
const s = styleOf(fmt,
|
|
4355
|
+
const s = styleOf(fmt, o);
|
|
3695
4356
|
const sa = s ? ` s="${s}"` : "";
|
|
3696
4357
|
if (v === null || v === void 0 || v === "") return sa ? `<c r="${ref}"${sa}/>` : "";
|
|
3697
4358
|
if (typeof v === "number") return Number.isFinite(v) ? `<c r="${ref}"${sa}><v>${v}</v></c>` : "";
|
|
@@ -3708,7 +4369,7 @@ function writeXlsx(sheets) {
|
|
|
3708
4369
|
const merges = sheet.merges?.length ? `<mergeCells count="${sheet.merges.length}">${sheet.merges.map((m) => `<mergeCell ref="${m}"/>`).join("")}</mergeCells>` : "";
|
|
3709
4370
|
return `${XML}<worksheet xmlns="${NS}" xmlns:r="${REL_NS}">${views}<sheetFormatPr defaultRowHeight="15"/>${cols}<sheetData>${rowsXml}</sheetData>${filter}${merges}</worksheet>`;
|
|
3710
4371
|
});
|
|
3711
|
-
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>`;
|
|
3712
4373
|
const files = [
|
|
3713
4374
|
{
|
|
3714
4375
|
name: "[Content_Types].xml",
|
|
@@ -3862,6 +4523,51 @@ function excelFormat(def) {
|
|
|
3862
4523
|
if (o.style === "percent") return `0${decimals}%`;
|
|
3863
4524
|
return `${o.useGrouping === false ? "0" : "#,##0"}${decimals}`;
|
|
3864
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
|
+
}
|
|
3865
4571
|
function buildXlsx(grid, opts = {}) {
|
|
3866
4572
|
const cols = exportColumns(grid, opts.columns);
|
|
3867
4573
|
const code = grid.store.state.locale.code;
|
|
@@ -3888,43 +4594,86 @@ function buildXlsx(grid, opts = {}) {
|
|
|
3888
4594
|
if (level < leafLevel) {
|
|
3889
4595
|
const g = chain[level];
|
|
3890
4596
|
const prev = ci > 0 ? chains[ci - 1][level] : null;
|
|
3891
|
-
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 });
|
|
3892
4598
|
if (prev !== g) {
|
|
3893
4599
|
let end = ci;
|
|
3894
4600
|
while (end + 1 < cols.length && chains[end + 1][level] === g) end++;
|
|
3895
4601
|
if (end > ci) merges.push(`${name(ci)}${level + 1}:${name(end)}${level + 1}`);
|
|
3896
4602
|
}
|
|
3897
4603
|
} else if (level === leafLevel) {
|
|
3898
|
-
row.push({ v: header(cols[ci].def, cols[ci].id),
|
|
4604
|
+
row.push({ v: header(cols[ci].def, cols[ci].id), header: true });
|
|
3899
4605
|
if (headRows - 1 > level) merges.push(`${name(ci)}${level + 1}:${name(ci)}${headRows}`);
|
|
3900
|
-
} else row.push({ v: null,
|
|
4606
|
+
} else row.push({ v: null, header: true });
|
|
3901
4607
|
}
|
|
3902
4608
|
rows.push(row);
|
|
3903
4609
|
}
|
|
3904
4610
|
}
|
|
3905
4611
|
const fmts = cols.map((c) => excelFormat(c.def));
|
|
3906
4612
|
const formatters = cols.map((c) => getFormatter(c.def, code));
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
}
|
|
3917
|
-
case "date": {
|
|
3918
|
-
const d = toDate(v);
|
|
3919
|
-
return d ? { v: d, fmt } : String(v);
|
|
3920
|
-
}
|
|
3921
|
-
case "boolean":
|
|
3922
|
-
return typeof v === "boolean" ? v : String(v);
|
|
3923
|
-
default:
|
|
3924
|
-
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);
|
|
3925
4623
|
}
|
|
3926
|
-
|
|
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();
|
|
3927
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
|
+
});
|
|
3928
4677
|
return writeXlsx([{
|
|
3929
4678
|
name: opts.sheetName ?? "Sheet1",
|
|
3930
4679
|
rows,
|
|
@@ -3967,8 +4716,8 @@ async function importInto(grid, file, opts = {}) {
|
|
|
3967
4716
|
if (!s) throw new Error(`Sheet ${String(opts.sheet)} not found`);
|
|
3968
4717
|
matrix = s.rows;
|
|
3969
4718
|
} else {
|
|
3970
|
-
const
|
|
3971
|
-
matrix = parseCsv(
|
|
4719
|
+
const text2 = new TextDecoder().decode(bytes);
|
|
4720
|
+
matrix = parseCsv(text2, detectSeparator(text2));
|
|
3972
4721
|
}
|
|
3973
4722
|
if (!matrix.length) return { rows: [], skipped: 0 };
|
|
3974
4723
|
const leaves = [];
|
|
@@ -4030,9 +4779,9 @@ function svg(tag, attrs = {}, cls = "") {
|
|
|
4030
4779
|
for (const [k, v] of Object.entries(attrs)) node.setAttribute(k, String(v));
|
|
4031
4780
|
return node;
|
|
4032
4781
|
}
|
|
4033
|
-
function tip(node,
|
|
4782
|
+
function tip(node, text2) {
|
|
4034
4783
|
const t = svg("title");
|
|
4035
|
-
t.textContent =
|
|
4784
|
+
t.textContent = text2;
|
|
4036
4785
|
node.append(t);
|
|
4037
4786
|
}
|
|
4038
4787
|
function niceTicks(min, max, count = 5) {
|
|
@@ -4468,12 +5217,13 @@ function buildPivot(source, config, defOf, labels, locale) {
|
|
|
4468
5217
|
}
|
|
4469
5218
|
|
|
4470
5219
|
// src/version.ts
|
|
4471
|
-
var version = "0.
|
|
5220
|
+
var version = "0.8.0";
|
|
4472
5221
|
|
|
4473
5222
|
// src/license.ts
|
|
4474
5223
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
4475
5224
|
var PRICING_URL = "https://freegrid.app/pricing/";
|
|
4476
5225
|
var USAGE_URL = "https://freegrid.app/api/usage";
|
|
5226
|
+
var STATUS_URL = "https://freegrid.app/api/domain-status";
|
|
4477
5227
|
var b64urlDecode = (s) => {
|
|
4478
5228
|
const bin = atob(s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4));
|
|
4479
5229
|
return Uint8Array.from(bin, (c) => c.charCodeAt(0));
|
|
@@ -4515,7 +5265,8 @@ async function verifyLicense(key, host, opts = {}) {
|
|
|
4515
5265
|
return { ok: true, info };
|
|
4516
5266
|
}
|
|
4517
5267
|
var globalKey;
|
|
4518
|
-
var
|
|
5268
|
+
var statuses = /* @__PURE__ */ new Map();
|
|
5269
|
+
var badgeCheck = null;
|
|
4519
5270
|
function setLicenseKey(key) {
|
|
4520
5271
|
globalKey = key;
|
|
4521
5272
|
}
|
|
@@ -4542,22 +5293,52 @@ function reportUsage(info, host = typeof location !== "undefined" ? location.hos
|
|
|
4542
5293
|
}
|
|
4543
5294
|
}
|
|
4544
5295
|
function checkLicense(key = globalKey, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
5296
|
+
if (isDevHost(host)) return Promise.resolve(true);
|
|
4545
5297
|
const k = key ?? globalKey;
|
|
4546
5298
|
const id = `${k ?? ""}@${host}`;
|
|
4547
|
-
|
|
4548
|
-
|
|
5299
|
+
let status = statuses.get(id);
|
|
5300
|
+
if (status) return status;
|
|
4549
5301
|
if (!k) {
|
|
4550
5302
|
console.info(NOTICE.missing);
|
|
4551
5303
|
reportUsage({ version, licensed: false }, host);
|
|
4552
|
-
|
|
4553
|
-
}
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
5304
|
+
status = Promise.resolve(false);
|
|
5305
|
+
} else {
|
|
5306
|
+
status = verifyLicense(k, host).then((res) => {
|
|
5307
|
+
reportUsage({ version, licensed: res.ok, company: res.ok ? res.info.company : void 0 }, host);
|
|
5308
|
+
if (res.ok || res.reason === "unsupported") return res.ok || res.reason === "unsupported";
|
|
5309
|
+
const extra = res.info ? ` (${res.info.domains.join(", ")}, ${res.info.expires})` : "";
|
|
5310
|
+
console.warn(`${NOTICE[res.reason]}${extra}
|
|
4559
5311
|
${PRICING_URL}`);
|
|
4560
|
-
|
|
5312
|
+
return false;
|
|
5313
|
+
}, () => false);
|
|
5314
|
+
}
|
|
5315
|
+
statuses.set(id, status);
|
|
5316
|
+
return status;
|
|
5317
|
+
}
|
|
5318
|
+
function badgeRequested(host = typeof location !== "undefined" ? location.hostname : "") {
|
|
5319
|
+
if (isDevHost(host) || typeof fetch !== "function") return Promise.resolve(false);
|
|
5320
|
+
if (badgeCheck) return badgeCheck;
|
|
5321
|
+
const day = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5322
|
+
let store = null;
|
|
5323
|
+
try {
|
|
5324
|
+
store = typeof localStorage !== "undefined" ? localStorage : null;
|
|
5325
|
+
const cached = JSON.parse(store?.getItem("freegrid:badge") ?? "null");
|
|
5326
|
+
if (cached && cached.host === host && cached.day === day) return badgeCheck = Promise.resolve(cached.show);
|
|
5327
|
+
} catch {
|
|
5328
|
+
}
|
|
5329
|
+
badgeCheck = fetch(STATUS_URL, { mode: "cors", credentials: "omit" }).then((r) => r.ok ? r.json() : { badge: false }).then((d) => {
|
|
5330
|
+
const show = d.badge === true;
|
|
5331
|
+
try {
|
|
5332
|
+
store?.setItem("freegrid:badge", JSON.stringify({ host, day, show }));
|
|
5333
|
+
} catch {
|
|
5334
|
+
}
|
|
5335
|
+
return show;
|
|
5336
|
+
}, () => false);
|
|
5337
|
+
return badgeCheck;
|
|
5338
|
+
}
|
|
5339
|
+
async function needsFreeBadge(key, host = typeof location !== "undefined" ? location.hostname : "") {
|
|
5340
|
+
if (await checkLicense(key, host)) return false;
|
|
5341
|
+
return badgeRequested(host);
|
|
4561
5342
|
}
|
|
4562
5343
|
|
|
4563
5344
|
// src/interactions/menu.ts
|
|
@@ -4617,7 +5398,7 @@ var ColumnMenu = class {
|
|
|
4617
5398
|
popup.append(this.section(items));
|
|
4618
5399
|
const filter = this.filterSection(colId, () => this.close());
|
|
4619
5400
|
if (filter) popup.append(filter);
|
|
4620
|
-
|
|
5401
|
+
popupParent(this.grid.renderer.root).append(popup);
|
|
4621
5402
|
const r = anchor.getBoundingClientRect();
|
|
4622
5403
|
const w = popup.offsetWidth || 248;
|
|
4623
5404
|
popup.style.left = `${Math.max(4, Math.min(r.right - w, window.innerWidth - w - 4))}px`;
|
|
@@ -4627,13 +5408,12 @@ var ColumnMenu = class {
|
|
|
4627
5408
|
this.popup = popup;
|
|
4628
5409
|
this.colId = colId;
|
|
4629
5410
|
const onDown = (e) => {
|
|
4630
|
-
|
|
4631
|
-
if (!popup.contains(t) && !anchor.contains(t)) this.close();
|
|
5411
|
+
if (!eventInside(e, popup, anchor)) this.close();
|
|
4632
5412
|
};
|
|
4633
5413
|
const onKey = (e) => {
|
|
4634
5414
|
if (e.key !== "Escape") return;
|
|
4635
5415
|
this.close();
|
|
4636
|
-
this.grid.renderer.
|
|
5416
|
+
this.grid.renderer.gridEl.focus();
|
|
4637
5417
|
};
|
|
4638
5418
|
document.addEventListener("pointerdown", onDown, true);
|
|
4639
5419
|
document.addEventListener("keydown", onKey, true);
|
|
@@ -4897,19 +5677,26 @@ var RowModel = class {
|
|
|
4897
5677
|
if (!this.indexById) this.indexById = new Map(this.display.map((n, i) => [n.id, i]));
|
|
4898
5678
|
return this.indexById.get(id) ?? -1;
|
|
4899
5679
|
}
|
|
4900
|
-
/**
|
|
4901
|
-
|
|
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) {
|
|
4902
5685
|
const c = this.layoutCache;
|
|
4903
|
-
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;
|
|
4904
5687
|
const n = this.display.length;
|
|
4905
5688
|
const offsets = new Float64Array(n + 1);
|
|
4906
5689
|
let uniform = true;
|
|
5690
|
+
const measured = heights && heights.size ? heights : null;
|
|
4907
5691
|
for (let i = 0; i < n; i++) {
|
|
4908
|
-
const
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
|
|
4912
|
-
|
|
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 };
|
|
4913
5700
|
return this.layoutCache;
|
|
4914
5701
|
}
|
|
4915
5702
|
filter(input) {
|
|
@@ -4920,8 +5707,8 @@ var RowModel = class {
|
|
|
4920
5707
|
const matches = this.nodes.filter((n) => {
|
|
4921
5708
|
for (const p of preds) if (!p.pred(getCellValue(p.def, n.data))) return false;
|
|
4922
5709
|
if (tokens.length) {
|
|
4923
|
-
const
|
|
4924
|
-
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;
|
|
4925
5712
|
}
|
|
4926
5713
|
return true;
|
|
4927
5714
|
});
|
|
@@ -5016,8 +5803,10 @@ var RowModel = class {
|
|
|
5016
5803
|
for (const n of nodes) {
|
|
5017
5804
|
if (seen2.has(n.id)) continue;
|
|
5018
5805
|
const kids = children.get(n.id) ?? [];
|
|
5806
|
+
const lazy = !kids.length && !!d.lazyChildren?.(n.id, n.data);
|
|
5019
5807
|
const expanded = kids.length > 0 && this.isOpen(d, n.id, level);
|
|
5020
|
-
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;
|
|
5021
5810
|
this.withDetails(d, [node], out);
|
|
5022
5811
|
if (expanded) visit(kids, level + 1, /* @__PURE__ */ new Set([...seen2, n.id]));
|
|
5023
5812
|
}
|
|
@@ -5285,6 +6074,17 @@ var Grid = class {
|
|
|
5285
6074
|
/** 0-based page and rows per page (pagination only). */
|
|
5286
6075
|
this.page = 0;
|
|
5287
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
|
+
};
|
|
5288
6088
|
if (!Array.isArray(options.columns)) warn("options.columns must be an array");
|
|
5289
6089
|
if (options.data != null && !Array.isArray(options.data)) warn("options.data must be an array");
|
|
5290
6090
|
this.store = new Store({
|
|
@@ -5296,6 +6096,7 @@ var Grid = class {
|
|
|
5296
6096
|
anchorId: null,
|
|
5297
6097
|
focus: null,
|
|
5298
6098
|
range: null,
|
|
6099
|
+
extraRanges: [],
|
|
5299
6100
|
locale: resolveLocale(options.locale),
|
|
5300
6101
|
theme: options.theme ?? "light"
|
|
5301
6102
|
});
|
|
@@ -5367,9 +6168,12 @@ var Grid = class {
|
|
|
5367
6168
|
this.disposers.push(() => ro.disconnect());
|
|
5368
6169
|
}
|
|
5369
6170
|
if (options.pivot) this.setPivot(options.pivot);
|
|
5370
|
-
void
|
|
6171
|
+
void needsFreeBadge(options.licenseKey).then((show) => {
|
|
6172
|
+
if (show && !this.destroyed) this.renderer.showFreeBadge();
|
|
6173
|
+
});
|
|
5371
6174
|
this.renderer.render();
|
|
5372
6175
|
this.api = this.createApi();
|
|
6176
|
+
liveGrids.set(this.renderer.root, this);
|
|
5373
6177
|
}
|
|
5374
6178
|
collator() {
|
|
5375
6179
|
const code = this.store.state.locale.code;
|
|
@@ -5380,6 +6184,7 @@ var Grid = class {
|
|
|
5380
6184
|
}
|
|
5381
6185
|
/** Column widths/order/pinning changed; rows are unaffected. */
|
|
5382
6186
|
layoutChanged() {
|
|
6187
|
+
this.renderer.invalidateHeights();
|
|
5383
6188
|
this.renderer.invalidate({ header: true, rows: true });
|
|
5384
6189
|
this.refreshChoosers();
|
|
5385
6190
|
}
|
|
@@ -5400,6 +6205,8 @@ var Grid = class {
|
|
|
5400
6205
|
defaultExpanded: this.defaultExpanded,
|
|
5401
6206
|
detailOpen: this.options.detailRenderer ? this.detailOpen : /* @__PURE__ */ new Set(),
|
|
5402
6207
|
isMaster: this.options.isRowMaster,
|
|
6208
|
+
lazyChildren: this.options.treeData?.hasChildren ? this.lazyChildren : void 0,
|
|
6209
|
+
loading: this.treeLoading,
|
|
5403
6210
|
version: this.displayVersion
|
|
5404
6211
|
};
|
|
5405
6212
|
}
|
|
@@ -5517,6 +6324,7 @@ var Grid = class {
|
|
|
5517
6324
|
if (!this.options.pagination || !Number.isFinite(page)) return;
|
|
5518
6325
|
const next = Math.max(0, Math.min(this.getPageCount() - 1, Math.floor(page)));
|
|
5519
6326
|
if (next === this.page) return;
|
|
6327
|
+
if (this.veto("beforePageChange", { page: next, pageSize: this.pageSize })) return;
|
|
5520
6328
|
this.page = next;
|
|
5521
6329
|
this.pageMoved();
|
|
5522
6330
|
}
|
|
@@ -5528,6 +6336,7 @@ var Grid = class {
|
|
|
5528
6336
|
}
|
|
5529
6337
|
if (size === this.pageSize) return;
|
|
5530
6338
|
const first = this.page * this.pageSize;
|
|
6339
|
+
if (this.veto("beforePageChange", { page: Math.floor(first / size), pageSize: size })) return;
|
|
5531
6340
|
this.pageSize = size;
|
|
5532
6341
|
this.page = Math.floor(first / size);
|
|
5533
6342
|
this.pageMoved();
|
|
@@ -5567,6 +6376,35 @@ var Grid = class {
|
|
|
5567
6376
|
this.renderer.invalidate({ rows: true });
|
|
5568
6377
|
this.events.emit("rowsLoadFailed", { start, end, error });
|
|
5569
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
|
+
}
|
|
5570
6408
|
/** Client-only operations log once and do nothing with a datasource. */
|
|
5571
6409
|
clientOnly(what) {
|
|
5572
6410
|
if (!this.rowModel.server) return false;
|
|
@@ -5616,10 +6454,45 @@ var Grid = class {
|
|
|
5616
6454
|
expandKey(node) {
|
|
5617
6455
|
return node.group ? node.group.key : node.id;
|
|
5618
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
|
+
}
|
|
5619
6488
|
setGroupExpanded(key, open) {
|
|
5620
6489
|
const node = this.rowModel.rows().find((n) => this.isExpandable(n) && this.expandKey(n) === key);
|
|
5621
6490
|
const cur = node ? !!(node.group?.expanded ?? node.expanded) : this.expanded.get(key);
|
|
5622
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
|
+
}
|
|
5623
6496
|
this.expanded.set(key, open);
|
|
5624
6497
|
this.displayChanged();
|
|
5625
6498
|
this.events.emit("groupToggle", { key, expanded: open });
|
|
@@ -5678,7 +6551,113 @@ var Grid = class {
|
|
|
5678
6551
|
/** Row order can only follow the source data when nothing reorders or hides rows. */
|
|
5679
6552
|
canMoveRows() {
|
|
5680
6553
|
const s = this.store.state;
|
|
5681
|
-
|
|
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;
|
|
5682
6661
|
}
|
|
5683
6662
|
moveRow(row, toIndex, record = true) {
|
|
5684
6663
|
if (this.clientOnly("moveRow")) return false;
|
|
@@ -5708,6 +6687,7 @@ var Grid = class {
|
|
|
5708
6687
|
if (this.options.rowHeight == null) this.renderer.opts.rowHeight = autoRowHeight(effective);
|
|
5709
6688
|
if (this.options.headerHeight == null) this.renderer.opts.headerHeight = autoHeaderHeight(effective);
|
|
5710
6689
|
this.renderer.applyFont(size, family);
|
|
6690
|
+
this.renderer.invalidateHeights();
|
|
5711
6691
|
this.menu.close();
|
|
5712
6692
|
this.layoutChanged();
|
|
5713
6693
|
}
|
|
@@ -5743,30 +6723,55 @@ var Grid = class {
|
|
|
5743
6723
|
dataColumns() {
|
|
5744
6724
|
return this.columns.displayed().filter((c) => !isSystemCol(c.id));
|
|
5745
6725
|
}
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
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 : [];
|
|
5753
6736
|
const cur = this.store.state.range;
|
|
5754
|
-
if (JSON.stringify(cur) === JSON.stringify(next)) return;
|
|
5755
|
-
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 });
|
|
5756
6739
|
this.events.emit("rangeChange", { range: next && { start: { ...next.start }, end: { ...next.end } } });
|
|
5757
6740
|
}
|
|
5758
|
-
/**
|
|
5759
|
-
|
|
5760
|
-
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) {
|
|
5761
6759
|
if (!r) return null;
|
|
5762
6760
|
const a = this.columns.getColumn(r.start.colId), b = this.columns.getColumn(r.end.colId);
|
|
5763
6761
|
if (!a || !b) return null;
|
|
5764
6762
|
return normalizeRange({ start: { rowIndex: r.start.rowIndex, colIndex: a.index }, end: { rowIndex: r.end.rowIndex, colIndex: b.index } });
|
|
5765
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
|
+
}
|
|
5766
6772
|
rangeContains(rowIndex, colId) {
|
|
5767
|
-
const b = this.rangeBounds();
|
|
5768
6773
|
const col = this.columns.getColumn(colId);
|
|
5769
|
-
return !!
|
|
6774
|
+
return !!col && this.allRangeBounds().some((b) => rangeContains(b, rowIndex, col.index));
|
|
5770
6775
|
}
|
|
5771
6776
|
selectAllCells() {
|
|
5772
6777
|
const cols = this.dataColumns();
|
|
@@ -5784,6 +6789,18 @@ var Grid = class {
|
|
|
5784
6789
|
const checkedRows = rows;
|
|
5785
6790
|
let out;
|
|
5786
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
|
+
}
|
|
5787
6804
|
if (b) {
|
|
5788
6805
|
out = [];
|
|
5789
6806
|
for (let i = b.r1; i <= b.r2; i++) {
|
|
@@ -5803,22 +6820,51 @@ var Grid = class {
|
|
|
5803
6820
|
out = [node.data];
|
|
5804
6821
|
cols = [col];
|
|
5805
6822
|
}
|
|
5806
|
-
const matrix = out.map((r) => cols.map((c) =>
|
|
6823
|
+
const matrix = out.map((r) => cols.map((c) => this.copyCell(c.def, r, code)));
|
|
5807
6824
|
if (headers) matrix.unshift(cols.map((c) => c.def.header ?? c.def.field ?? c.id));
|
|
5808
6825
|
return toTSV(matrix);
|
|
5809
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
|
+
}
|
|
5810
6856
|
copyToClipboard(headers, rows = false) {
|
|
5811
|
-
const
|
|
5812
|
-
if (
|
|
6857
|
+
const text2 = this.selectionTSV(headers, rows);
|
|
6858
|
+
if (text2 && typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
|
|
5813
6859
|
try {
|
|
5814
|
-
navigator.clipboard.writeText(
|
|
6860
|
+
navigator.clipboard.writeText(text2)?.catch?.(() => void 0);
|
|
5815
6861
|
} catch {
|
|
5816
6862
|
}
|
|
5817
6863
|
}
|
|
5818
|
-
return
|
|
6864
|
+
return text2;
|
|
5819
6865
|
}
|
|
5820
|
-
pasteText(
|
|
5821
|
-
const matrix = parseTSV(
|
|
6866
|
+
pasteText(text2) {
|
|
6867
|
+
const matrix = parseTSV(text2);
|
|
5822
6868
|
const result = { changed: 0, skipped: 0 };
|
|
5823
6869
|
if (!matrix.length) return result;
|
|
5824
6870
|
const displayed = this.columns.displayed();
|
|
@@ -5850,7 +6896,7 @@ var Grid = class {
|
|
|
5850
6896
|
const raw = single && b ? matrix[0][0] : matrix[dr]?.[dc];
|
|
5851
6897
|
if (raw === void 0) continue;
|
|
5852
6898
|
const def = col.def;
|
|
5853
|
-
if (!def.
|
|
6899
|
+
if (!def.field || !cellEditable(def, node.data, ri)) {
|
|
5854
6900
|
result.skipped++;
|
|
5855
6901
|
continue;
|
|
5856
6902
|
}
|
|
@@ -5873,34 +6919,67 @@ var Grid = class {
|
|
|
5873
6919
|
const spec = def.editor;
|
|
5874
6920
|
if (spec && typeof spec === "object" && spec.type === "select") {
|
|
5875
6921
|
const opts = (typeof spec.options === "function" ? spec.options(row) : spec.options) ?? [];
|
|
5876
|
-
const
|
|
5877
|
-
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() ===
|
|
5878
|
-
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 };
|
|
5879
6925
|
value = hit.value;
|
|
6926
|
+
} else if (def.formulas && isFormula(raw.trim())) {
|
|
6927
|
+
value = raw.trim();
|
|
5880
6928
|
} else {
|
|
5881
6929
|
const parsed = parseValue(def, raw, oldValue);
|
|
5882
6930
|
if (!parsed.ok) return parsed;
|
|
5883
6931
|
value = parsed.value;
|
|
5884
6932
|
}
|
|
5885
|
-
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
|
+
}
|
|
5886
6949
|
if (def.validate) {
|
|
6950
|
+
let res;
|
|
5887
6951
|
try {
|
|
5888
|
-
|
|
6952
|
+
res = def.validate(value, row);
|
|
5889
6953
|
} catch {
|
|
5890
|
-
|
|
6954
|
+
res = loc.invalidValue;
|
|
5891
6955
|
}
|
|
6956
|
+
if (res !== true) return typeof res === "string" && res ? res : loc.invalidValue;
|
|
5892
6957
|
}
|
|
5893
|
-
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;
|
|
5894
6973
|
}
|
|
5895
6974
|
// ── merge, aggregates, fill, pinned rows ────────────────────
|
|
5896
6975
|
/** Merge blocks for every `merge` column over the displayed rows (memoized). */
|
|
5897
6976
|
mergeSpans() {
|
|
5898
|
-
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;
|
|
5899
6978
|
const rows = this.rowModel.rows();
|
|
5900
6979
|
const ver = this.editVersion * 1e6 + this.columns.displayed().length;
|
|
5901
6980
|
if (this.spanCache && this.spanCache.rows === rows && this.spanCache.ver === ver) return this.spanCache.spans;
|
|
5902
6981
|
const spans = /* @__PURE__ */ new Map();
|
|
5903
|
-
const merged = this.columns.displayed().filter((c) => c.def.merge);
|
|
6982
|
+
const merged = this.columns.displayed().filter((c) => c.def.merge || c.def.rowSpan);
|
|
5904
6983
|
if (merged.length && !rows.some((n) => n.kind)) {
|
|
5905
6984
|
const data = rows.map((n) => n.data);
|
|
5906
6985
|
for (const c of merged) spans.set(c.id, computeSpans(data, c.def));
|
|
@@ -5910,15 +6989,22 @@ var Grid = class {
|
|
|
5910
6989
|
}
|
|
5911
6990
|
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
5912
6991
|
rangeAggregates() {
|
|
5913
|
-
const
|
|
5914
|
-
if (!
|
|
5915
|
-
const
|
|
6992
|
+
const all2 = this.allRangeBounds();
|
|
6993
|
+
if (!all2.length) return null;
|
|
6994
|
+
const displayed = this.columns.displayed();
|
|
5916
6995
|
const out = { cells: 0, count: 0, numbers: 0, sum: 0, min: Infinity, max: -Infinity };
|
|
5917
|
-
|
|
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++) {
|
|
5918
6998
|
const node = this.rowModel.getRow(r);
|
|
5919
6999
|
if (!node || node.kind) continue;
|
|
5920
7000
|
const row = node.data;
|
|
5921
|
-
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
|
+
}
|
|
5922
7008
|
out.cells++;
|
|
5923
7009
|
const v = getCellValue(c.def, row);
|
|
5924
7010
|
if (isBlank(v)) continue;
|
|
@@ -5953,13 +7039,8 @@ var Grid = class {
|
|
|
5953
7039
|
const col = displayed[c];
|
|
5954
7040
|
const node = this.rowModel.getRow(r);
|
|
5955
7041
|
const def = col?.def;
|
|
5956
|
-
if (!node || node.kind || !def?.
|
|
5957
|
-
if (def.
|
|
5958
|
-
try {
|
|
5959
|
-
if (def.validate && def.validate(value2, node.data) !== true) return;
|
|
5960
|
-
} catch {
|
|
5961
|
-
return;
|
|
5962
|
-
}
|
|
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;
|
|
5963
7044
|
this.writeCell(changes, node.data, def.field, col.id, r, value2);
|
|
5964
7045
|
};
|
|
5965
7046
|
const value = (r, c) => getCellValue(displayed[c].def, this.rowModel.getRow(r).data);
|
|
@@ -5993,6 +7074,7 @@ var Grid = class {
|
|
|
5993
7074
|
writeCell(changes, row, field, colId, rowIndex, value) {
|
|
5994
7075
|
const oldValue = row[field];
|
|
5995
7076
|
if (Object.is(oldValue, value)) return false;
|
|
7077
|
+
if (this.veto("beforeCellChange", { row, rowIndex, colId, oldValue, newValue: value })) return false;
|
|
5996
7078
|
this.tracker.beforeChange(row);
|
|
5997
7079
|
row[field] = value;
|
|
5998
7080
|
changes.push({ row, field, colId, oldValue, newValue: value });
|
|
@@ -6014,6 +7096,7 @@ var Grid = class {
|
|
|
6014
7096
|
this.afterDataEdit();
|
|
6015
7097
|
}
|
|
6016
7098
|
afterDataEdit() {
|
|
7099
|
+
this.renderer.invalidateHeights();
|
|
6017
7100
|
this.editVersion++;
|
|
6018
7101
|
this.refreshSummary();
|
|
6019
7102
|
this.renderer.invalidate({ rows: true });
|
|
@@ -6029,17 +7112,17 @@ var Grid = class {
|
|
|
6029
7112
|
/** Delete/Backspace: clear editable cells in the range, or the focused cell. */
|
|
6030
7113
|
clearCells() {
|
|
6031
7114
|
const displayed = this.columns.displayed();
|
|
6032
|
-
const
|
|
7115
|
+
const all2 = this.allRangeBounds();
|
|
6033
7116
|
const f = this.store.state.focus;
|
|
6034
7117
|
const fi = f ? this.columns.getColumn(f.colId)?.index : void 0;
|
|
6035
|
-
const
|
|
6036
|
-
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;
|
|
6037
7120
|
const changes = [];
|
|
6038
|
-
for (let r = box.r1; r <= box.r2; r++) {
|
|
7121
|
+
for (const box of boxes) for (let r = box.r1; r <= box.r2; r++) {
|
|
6039
7122
|
const node = this.rowModel.getRow(r);
|
|
6040
7123
|
for (let c = box.c1; c <= box.c2; c++) {
|
|
6041
7124
|
const def = displayed[c]?.def;
|
|
6042
|
-
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);
|
|
6043
7126
|
}
|
|
6044
7127
|
}
|
|
6045
7128
|
this.endCellTx(changes);
|
|
@@ -6219,6 +7302,7 @@ var Grid = class {
|
|
|
6219
7302
|
}
|
|
6220
7303
|
return s.dir === "asc" || s.dir === "desc";
|
|
6221
7304
|
});
|
|
7305
|
+
if (this.veto("beforeSort", { sort: valid.map((s) => ({ ...s })) })) return;
|
|
6222
7306
|
this.store.set({ sort: valid });
|
|
6223
7307
|
this.events.emit("sortChange", { sort: [...valid] });
|
|
6224
7308
|
}
|
|
@@ -6240,6 +7324,7 @@ var Grid = class {
|
|
|
6240
7324
|
warn(`setFilter: unknown column "${colId}"`);
|
|
6241
7325
|
return;
|
|
6242
7326
|
}
|
|
7327
|
+
if (this.veto("beforeFilter", { colId, model })) return;
|
|
6243
7328
|
const filters = { ...this.store.state.filters };
|
|
6244
7329
|
if (model) filters[colId] = model;
|
|
6245
7330
|
else delete filters[colId];
|
|
@@ -6260,9 +7345,21 @@ var Grid = class {
|
|
|
6260
7345
|
this.store.set({ selected, anchorId });
|
|
6261
7346
|
this.events.emit("selectionChange", { selected: this.selectedRows() });
|
|
6262
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
|
+
}
|
|
6263
7358
|
selectFromClick(index, toggle, range) {
|
|
6264
7359
|
const mode = this.options.rowSelection;
|
|
6265
|
-
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;
|
|
6266
7363
|
const r = nextSelection({
|
|
6267
7364
|
selected: this.store.state.selected,
|
|
6268
7365
|
anchorIndex: this.anchorIndex(),
|
|
@@ -6272,11 +7369,12 @@ var Grid = class {
|
|
|
6272
7369
|
toggle,
|
|
6273
7370
|
range
|
|
6274
7371
|
});
|
|
6275
|
-
if (this.rowModel.rows() !== this.rowModel.dataRows()) {
|
|
7372
|
+
if (this.rowModel.rows() !== this.rowModel.dataRows() || this.options.isRowSelectable) {
|
|
6276
7373
|
for (const id of r.selected) {
|
|
6277
7374
|
if (this.store.state.selected.has(id)) continue;
|
|
6278
7375
|
const i = this.rowModel.indexOfId(id);
|
|
6279
|
-
|
|
7376
|
+
const n = i >= 0 ? this.rowModel.getRow(i) : null;
|
|
7377
|
+
if (n && (n.kind || !this.rowSelectable(n.data))) r.selected.delete(id);
|
|
6280
7378
|
}
|
|
6281
7379
|
}
|
|
6282
7380
|
this.setSelected(r.selected, this.rowModel.getRow(r.anchorIndex)?.id ?? null);
|
|
@@ -6288,8 +7386,13 @@ var Grid = class {
|
|
|
6288
7386
|
const i = this.rowModel.indexOfId(id);
|
|
6289
7387
|
return i < 0 ? null : i;
|
|
6290
7388
|
}
|
|
6291
|
-
|
|
7389
|
+
/** Rows select-all acts on: displayed data rows that are selectable. */
|
|
7390
|
+
selectableRows() {
|
|
6292
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();
|
|
6293
7396
|
const sel = this.store.state.selected;
|
|
6294
7397
|
const all2 = rows.length > 0 && rows.every((n) => sel.has(n.id));
|
|
6295
7398
|
const next = new Set(sel);
|
|
@@ -6309,7 +7412,7 @@ var Grid = class {
|
|
|
6309
7412
|
if (this.options.rowSelection) sys.push(SELECT_COLUMN);
|
|
6310
7413
|
return [...sys, ...defs];
|
|
6311
7414
|
}
|
|
6312
|
-
setData(rows) {
|
|
7415
|
+
setData(rows, options) {
|
|
6313
7416
|
if (this.clientOnly("setData")) return;
|
|
6314
7417
|
if (!Array.isArray(rows)) {
|
|
6315
7418
|
warn("setData: rows must be an array");
|
|
@@ -6319,8 +7422,11 @@ var Grid = class {
|
|
|
6319
7422
|
this.tracker.reset();
|
|
6320
7423
|
this.history.clear();
|
|
6321
7424
|
this.detailOpen.clear();
|
|
7425
|
+
this.treeLoaded.clear();
|
|
7426
|
+
this.treeLoading.clear();
|
|
6322
7427
|
this.renderer.dropDetail();
|
|
6323
7428
|
this.store.set({ data: cleanRows(rows) });
|
|
7429
|
+
if (options?.scrollToTop) this.renderer.setScroll({ top: 0, left: 0 });
|
|
6324
7430
|
const sel = this.store.state.selected;
|
|
6325
7431
|
if (sel.size) {
|
|
6326
7432
|
if (this.options.getRowId) {
|
|
@@ -6394,7 +7500,7 @@ var Grid = class {
|
|
|
6394
7500
|
return fn(...a);
|
|
6395
7501
|
});
|
|
6396
7502
|
return {
|
|
6397
|
-
setData: guard((rows) => this.setData(rows)),
|
|
7503
|
+
setData: guard((rows, o) => this.setData(rows, o)),
|
|
6398
7504
|
getData: guard(() => this.store.state.data),
|
|
6399
7505
|
setColumns: guard((defs) => this.setColumns(defs)),
|
|
6400
7506
|
getColumnState: guard(() => this.columns.getState()),
|
|
@@ -6408,10 +7514,14 @@ var Grid = class {
|
|
|
6408
7514
|
getFilterModel: guard(() => ({ ...this.store.state.filters })),
|
|
6409
7515
|
setQuickFilter: guard((t) => this.store.set({ quickFilter: t ?? "" })),
|
|
6410
7516
|
getSelectedRows: guard(() => this.selectedRows()),
|
|
6411
|
-
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)]))),
|
|
6412
7518
|
deselectAll: guard(() => this.setSelected(/* @__PURE__ */ new Set(), null)),
|
|
6413
7519
|
getDisplayedRowCount: guard(() => this.rowModel.getRowCount()),
|
|
6414
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)),
|
|
6415
7525
|
setTheme: guard((t) => this.store.set({ theme: t })),
|
|
6416
7526
|
setLocale: guard((l) => this.store.set({ locale: resolveLocale(l) })),
|
|
6417
7527
|
setFont: guard((f) => this.setFont(f)),
|
|
@@ -6428,8 +7538,10 @@ var Grid = class {
|
|
|
6428
7538
|
return r && { start: { ...r.start }, end: { ...r.end } };
|
|
6429
7539
|
}),
|
|
6430
7540
|
setRange: guard((r) => this.setRange(r)),
|
|
7541
|
+
getRanges: guard(() => this.getRanges()),
|
|
7542
|
+
setRanges: guard((list) => this.setRanges(list)),
|
|
6431
7543
|
copySelection: guard((o) => this.copyToClipboard(!!o?.headers, !!o?.rows)),
|
|
6432
|
-
pasteText: guard((
|
|
7544
|
+
pasteText: guard((text2) => this.pasteText(text2)),
|
|
6433
7545
|
startEdit: guard((rowIndex, colId) => this.editor.start(rowIndex, colId)),
|
|
6434
7546
|
stopEdit: guard((cancel) => cancel ? this.editor.cancel() : void this.editor.commit(null)),
|
|
6435
7547
|
addRows: guard((rows, index) => this.addRows(rows, index)),
|
|
@@ -6446,7 +7558,7 @@ var Grid = class {
|
|
|
6446
7558
|
canRedo: guard(() => this.history.canRedo()),
|
|
6447
7559
|
fillRange: guard((t) => this.fillRange(t)),
|
|
6448
7560
|
setPinnedRows: guard((r) => this.setPinnedRows(r)),
|
|
6449
|
-
find: guard((
|
|
7561
|
+
find: guard((text2) => this.finder.find(text2)),
|
|
6450
7562
|
findNext: guard(() => this.finder.next()),
|
|
6451
7563
|
findPrevious: guard(() => this.finder.previous()),
|
|
6452
7564
|
clearFind: guard(() => this.finder.clear()),
|
|
@@ -6458,6 +7570,7 @@ var Grid = class {
|
|
|
6458
7570
|
setDetailExpanded: guard((row, open) => this.setDetailExpanded(row, open)),
|
|
6459
7571
|
isDetailExpanded: guard((row) => this.isDetailExpanded(row)),
|
|
6460
7572
|
moveRow: guard((row, toIndex) => this.moveRow(row, toIndex)),
|
|
7573
|
+
dropRow: guard((row, target, position) => this.dropRow(row, target, position)),
|
|
6461
7574
|
setPage: guard((p) => this.setPage(p)),
|
|
6462
7575
|
getPage: guard(() => this.getPage()),
|
|
6463
7576
|
getPageCount: guard(() => this.getPageCount()),
|
|
@@ -6479,6 +7592,7 @@ var Grid = class {
|
|
|
6479
7592
|
destroy() {
|
|
6480
7593
|
if (this.destroyed) return;
|
|
6481
7594
|
this.destroyed = true;
|
|
7595
|
+
liveGrids.delete(this.renderer.root);
|
|
6482
7596
|
for (const d of this.disposers) d();
|
|
6483
7597
|
this.renderer.destroy();
|
|
6484
7598
|
this.events.clear();
|