@freegridjs/core 1.9.2 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/freegrid.css +542 -525
- package/dist/cdn/freegrid.esm.js +23 -23
- package/dist/cdn/freegrid.min.js +26 -26
- package/dist/index.d.ts +3 -3
- package/dist/index.js +583 -245
- 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/id.d.ts +1 -1
- package/dist/locales/id.js +2 -0
- package/dist/locales/it.d.ts +1 -1
- package/dist/locales/it.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/pt-BR.d.ts +1 -1
- package/dist/locales/pt-BR.js +2 -0
- package/dist/locales/ru.d.ts +1 -1
- package/dist/locales/ru.js +2 -0
- package/dist/locales/th.d.ts +1 -1
- package/dist/locales/th.js +2 -0
- package/dist/locales/vi.d.ts +1 -1
- package/dist/locales/vi.js +2 -0
- package/dist/locales/zh-CN.d.ts +1 -1
- package/dist/locales/zh-CN.js +2 -0
- package/dist/locales/zh-TW.d.ts +1 -1
- package/dist/locales/zh-TW.js +2 -0
- package/dist/styles.css +542 -525
- package/dist/{types-Du3F8mqD.d.ts → types-CIOv3MoN.d.ts} +50 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1540,6 +1540,238 @@ var FilterRow = class {
|
|
|
1540
1540
|
}
|
|
1541
1541
|
};
|
|
1542
1542
|
|
|
1543
|
+
// src/conditional.ts
|
|
1544
|
+
var NUMERIC = (v) => {
|
|
1545
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
1546
|
+
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
1547
|
+
if (v instanceof Date) return v.getTime();
|
|
1548
|
+
return null;
|
|
1549
|
+
};
|
|
1550
|
+
var STAT_TYPES = /* @__PURE__ */ new Set(["top", "bottom", "aboveAverage", "belowAverage", "duplicate", "unique", "dataBar", "colorScale", "iconSet"]);
|
|
1551
|
+
var ICON_SETS = {
|
|
1552
|
+
// high, middle, low
|
|
1553
|
+
arrows: [{ char: "\u25B2", color: "#16a34a", label: "up" }, { char: "\u25B6", color: "#d97706", label: "level" }, { char: "\u25BC", color: "#dc2626", label: "down" }],
|
|
1554
|
+
traffic: [{ char: "\u25CF", color: "#16a34a", label: "good" }, { char: "\u25CF", color: "#f59e0b", label: "fair" }, { char: "\u25CF", color: "#dc2626", label: "poor" }],
|
|
1555
|
+
signs: [{ char: "\u2714", color: "#16a34a", label: "good" }, { char: "!", color: "#d97706", label: "fair" }, { char: "\u2716", color: "#dc2626", label: "poor" }]
|
|
1556
|
+
};
|
|
1557
|
+
function hex(c) {
|
|
1558
|
+
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(c.trim());
|
|
1559
|
+
if (!m) return null;
|
|
1560
|
+
const h = m[1].length === 3 ? [...m[1]].map((x) => x + x).join("") : m[1];
|
|
1561
|
+
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
1562
|
+
}
|
|
1563
|
+
function mixColor(a, b, t) {
|
|
1564
|
+
const x = hex(a), y = hex(b);
|
|
1565
|
+
if (!x || !y) return t < 0.5 ? a : b;
|
|
1566
|
+
const c = x.map((v, i) => Math.round(v + (y[i] - v) * Math.max(0, Math.min(1, t))));
|
|
1567
|
+
return `#${c.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
1568
|
+
}
|
|
1569
|
+
function textOf(v) {
|
|
1570
|
+
return v == null ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
1571
|
+
}
|
|
1572
|
+
function valueMatches(rule, value) {
|
|
1573
|
+
const op = rule.op ?? "eq";
|
|
1574
|
+
if (op === "blank") return isBlank(value);
|
|
1575
|
+
if (op === "notBlank") return !isBlank(value);
|
|
1576
|
+
if (op === "contains" || op === "notContains" || op === "startsWith" || op === "endsWith") {
|
|
1577
|
+
const s2 = textOf(value).toLowerCase(), q = textOf(rule.value).toLowerCase();
|
|
1578
|
+
const hit = op === "startsWith" ? s2.startsWith(q) : op === "endsWith" ? s2.endsWith(q) : s2.includes(q);
|
|
1579
|
+
return op === "notContains" ? !hit : hit;
|
|
1580
|
+
}
|
|
1581
|
+
if (isBlank(value)) return op === "ne";
|
|
1582
|
+
const a = NUMERIC(value), b = NUMERIC(rule.value), c = NUMERIC(rule.valueTo);
|
|
1583
|
+
if (a !== null && b !== null) {
|
|
1584
|
+
switch (op) {
|
|
1585
|
+
case "eq":
|
|
1586
|
+
return a === b;
|
|
1587
|
+
case "ne":
|
|
1588
|
+
return a !== b;
|
|
1589
|
+
case "gt":
|
|
1590
|
+
return a > b;
|
|
1591
|
+
case "gte":
|
|
1592
|
+
return a >= b;
|
|
1593
|
+
case "lt":
|
|
1594
|
+
return a < b;
|
|
1595
|
+
case "lte":
|
|
1596
|
+
return a <= b;
|
|
1597
|
+
case "between":
|
|
1598
|
+
case "notBetween": {
|
|
1599
|
+
if (c === null) return false;
|
|
1600
|
+
const inside = a >= Math.min(b, c) && a <= Math.max(b, c);
|
|
1601
|
+
return op === "between" ? inside : !inside;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
const s = textOf(value).toLowerCase(), t = textOf(rule.value).toLowerCase(), u = textOf(rule.valueTo).toLowerCase();
|
|
1606
|
+
switch (op) {
|
|
1607
|
+
case "eq":
|
|
1608
|
+
return s === t;
|
|
1609
|
+
case "ne":
|
|
1610
|
+
return s !== t;
|
|
1611
|
+
case "gt":
|
|
1612
|
+
return s > t;
|
|
1613
|
+
case "gte":
|
|
1614
|
+
return s >= t;
|
|
1615
|
+
case "lt":
|
|
1616
|
+
return s < t;
|
|
1617
|
+
case "lte":
|
|
1618
|
+
return s <= t;
|
|
1619
|
+
case "between":
|
|
1620
|
+
return s >= t && s <= u;
|
|
1621
|
+
case "notBetween":
|
|
1622
|
+
return s < t || s > u;
|
|
1623
|
+
}
|
|
1624
|
+
return false;
|
|
1625
|
+
}
|
|
1626
|
+
var ConditionalFormatter = class {
|
|
1627
|
+
constructor(deps) {
|
|
1628
|
+
this.deps = deps;
|
|
1629
|
+
this.stats = /* @__PURE__ */ new Map();
|
|
1630
|
+
this.statsKey = [];
|
|
1631
|
+
this.rowCache = /* @__PURE__ */ new WeakMap();
|
|
1632
|
+
}
|
|
1633
|
+
/** True when any rule looks at other rows (an update to one row can restyle others). */
|
|
1634
|
+
dependsOnOtherRows() {
|
|
1635
|
+
return this.deps.rules().some((r) => r && (STAT_TYPES.has(r.type ?? "value") || r.type === "formula"));
|
|
1636
|
+
}
|
|
1637
|
+
fresh() {
|
|
1638
|
+
const key = [this.deps.rows(), this.deps.version(), this.deps.rules()];
|
|
1639
|
+
if (key.every((k, i) => k === this.statsKey[i])) return;
|
|
1640
|
+
this.statsKey = key;
|
|
1641
|
+
this.stats.clear();
|
|
1642
|
+
this.rowCache = /* @__PURE__ */ new WeakMap();
|
|
1643
|
+
}
|
|
1644
|
+
statsOf(colId) {
|
|
1645
|
+
let s = this.stats.get(colId);
|
|
1646
|
+
if (!s) {
|
|
1647
|
+
const nums = [];
|
|
1648
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1649
|
+
for (const row of this.deps.rows()) {
|
|
1650
|
+
const v = this.deps.value(colId, row);
|
|
1651
|
+
const n = NUMERIC(v);
|
|
1652
|
+
if (n !== null) nums.push(n);
|
|
1653
|
+
if (!isBlank(v)) {
|
|
1654
|
+
const k = setKey(v);
|
|
1655
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
nums.sort((a, b) => a - b);
|
|
1659
|
+
const sum = nums.reduce((a, b) => a + b, 0);
|
|
1660
|
+
s = { min: nums[0] ?? 0, max: nums[nums.length - 1] ?? 0, avg: nums.length ? sum / nums.length : 0, sorted: nums, counts };
|
|
1661
|
+
this.stats.set(colId, s);
|
|
1662
|
+
}
|
|
1663
|
+
return s;
|
|
1664
|
+
}
|
|
1665
|
+
test(rule, colId, value, row) {
|
|
1666
|
+
const type = rule.type ?? "value";
|
|
1667
|
+
switch (type) {
|
|
1668
|
+
case "value":
|
|
1669
|
+
return valueMatches(rule, value);
|
|
1670
|
+
case "formula": {
|
|
1671
|
+
if (typeof rule.formula !== "string") return false;
|
|
1672
|
+
const r = this.deps.formula(rule.formula.startsWith("=") ? rule.formula : `=${rule.formula}`, row);
|
|
1673
|
+
return r === true || typeof r === "number" && r !== 0;
|
|
1674
|
+
}
|
|
1675
|
+
case "top":
|
|
1676
|
+
case "bottom": {
|
|
1677
|
+
const n = NUMERIC(value);
|
|
1678
|
+
if (n === null) return false;
|
|
1679
|
+
const s = this.statsOf(colId);
|
|
1680
|
+
if (!s.sorted.length) return false;
|
|
1681
|
+
const k = Math.max(1, rule.percent ? Math.ceil(s.sorted.length * (rule.rank ?? 10) / 100) : Math.floor(rule.rank ?? 10));
|
|
1682
|
+
return type === "top" ? n >= s.sorted[Math.max(0, s.sorted.length - k)] : n <= s.sorted[Math.min(s.sorted.length - 1, k - 1)];
|
|
1683
|
+
}
|
|
1684
|
+
case "aboveAverage":
|
|
1685
|
+
case "belowAverage": {
|
|
1686
|
+
const n = NUMERIC(value);
|
|
1687
|
+
if (n === null) return false;
|
|
1688
|
+
const avg = this.statsOf(colId).avg;
|
|
1689
|
+
return type === "aboveAverage" ? n > avg : n < avg;
|
|
1690
|
+
}
|
|
1691
|
+
case "duplicate":
|
|
1692
|
+
case "unique": {
|
|
1693
|
+
if (isBlank(value)) return false;
|
|
1694
|
+
const c = this.statsOf(colId).counts.get(setKey(value)) ?? 0;
|
|
1695
|
+
return type === "duplicate" ? c > 1 : c === 1;
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
return false;
|
|
1699
|
+
}
|
|
1700
|
+
/** Style from rules with `row: true`, for every cell of the row. */
|
|
1701
|
+
rowStyle(row) {
|
|
1702
|
+
if (this.rowCache.has(row)) return this.rowCache.get(row);
|
|
1703
|
+
let style = null;
|
|
1704
|
+
for (const rule of this.deps.rules()) {
|
|
1705
|
+
if (!rule?.row || !Array.isArray(rule.columns) || !rule.columns.length) continue;
|
|
1706
|
+
const colId = rule.columns[0];
|
|
1707
|
+
if (this.test(rule, colId, this.deps.value(colId, row), row)) {
|
|
1708
|
+
style = mergeStyle(style ?? {}, rule.style);
|
|
1709
|
+
if (rule.stopIfTrue) break;
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
this.rowCache.set(row, style);
|
|
1713
|
+
return style;
|
|
1714
|
+
}
|
|
1715
|
+
/** Format of one cell; null when no rule applies. */
|
|
1716
|
+
cell(colId, row, value) {
|
|
1717
|
+
const rules = this.deps.rules();
|
|
1718
|
+
if (!rules.length) return null;
|
|
1719
|
+
this.fresh();
|
|
1720
|
+
let out = null;
|
|
1721
|
+
const rs = this.rowStyle(row);
|
|
1722
|
+
if (rs) out = { style: { ...rs } };
|
|
1723
|
+
for (const rule of rules) {
|
|
1724
|
+
if (!rule || rule.row || !Array.isArray(rule.columns) || !rule.columns.includes(colId)) continue;
|
|
1725
|
+
const type = rule.type ?? "value";
|
|
1726
|
+
if (type === "dataBar" || type === "colorScale" || type === "iconSet") {
|
|
1727
|
+
const n = NUMERIC(value);
|
|
1728
|
+
if (n === null) continue;
|
|
1729
|
+
const s = this.statsOf(colId);
|
|
1730
|
+
const lo = typeof rule.min === "number" ? rule.min : s.min;
|
|
1731
|
+
const hi = typeof rule.max === "number" ? rule.max : s.max;
|
|
1732
|
+
const t = hi === lo ? 1 : Math.max(0, Math.min(1, (n - lo) / (hi - lo)));
|
|
1733
|
+
out ?? (out = { style: {} });
|
|
1734
|
+
if (type === "dataBar") {
|
|
1735
|
+
const zero = lo < 0 && hi > 0 ? -lo / (hi - lo) : lo >= 0 ? 0 : 1;
|
|
1736
|
+
const at = hi === lo ? 1 : t;
|
|
1737
|
+
out.bar = { from: Math.min(zero, at), to: Math.max(zero, at), color: rule.color || "var(--fg-accent)" };
|
|
1738
|
+
} else if (type === "colorScale") {
|
|
1739
|
+
const cs = Array.isArray(rule.colors) && rule.colors.length >= 2 ? rule.colors : ["#f8696b", "#ffeb84", "#63be7b"];
|
|
1740
|
+
const bg = cs.length >= 3 ? t < 0.5 ? mixColor(cs[0], cs[1], t * 2) : mixColor(cs[1], cs[2], (t - 0.5) * 2) : mixColor(cs[0], cs[1], t);
|
|
1741
|
+
out.style = { ...out.style, background: bg };
|
|
1742
|
+
} else {
|
|
1743
|
+
const set = ICON_SETS[rule.icons ?? "arrows"] ?? ICON_SETS.arrows;
|
|
1744
|
+
out.icon = t >= 2 / 3 ? set[0] : t >= 1 / 3 ? set[1] : set[2];
|
|
1745
|
+
}
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
if (this.test(rule, colId, value, row)) {
|
|
1749
|
+
out ?? (out = { style: {} });
|
|
1750
|
+
out.style = mergeStyle(out.style, rule.style);
|
|
1751
|
+
if (rule.stopIfTrue) break;
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return out;
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1757
|
+
function styleAlign(s) {
|
|
1758
|
+
const a = s?.align;
|
|
1759
|
+
return a === "left" || a === "center" || a === "right" ? a : void 0;
|
|
1760
|
+
}
|
|
1761
|
+
function mergeStyle(base, add) {
|
|
1762
|
+
if (!add || typeof add !== "object") return { ...base };
|
|
1763
|
+
const out = { ...base, ...add };
|
|
1764
|
+
if (!styleAlign(add)) {
|
|
1765
|
+
if (styleAlign(base)) out.align = base.align;
|
|
1766
|
+
else delete out.align;
|
|
1767
|
+
}
|
|
1768
|
+
return out;
|
|
1769
|
+
}
|
|
1770
|
+
function argbOf(c) {
|
|
1771
|
+
const x = c ? hex(c) : null;
|
|
1772
|
+
return x ? `FF${x.map((v) => v.toString(16).padStart(2, "0")).join("").toUpperCase()}` : void 0;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1543
1775
|
// src/summary.ts
|
|
1544
1776
|
function numbers(values) {
|
|
1545
1777
|
const out = [];
|
|
@@ -1650,6 +1882,28 @@ function computeSpans(rows, def) {
|
|
|
1650
1882
|
}
|
|
1651
1883
|
return { start, end };
|
|
1652
1884
|
}
|
|
1885
|
+
function nestedSpans(list, n) {
|
|
1886
|
+
const start = new Int32Array(n);
|
|
1887
|
+
const end = new Int32Array(n);
|
|
1888
|
+
let s = 0;
|
|
1889
|
+
for (let i = 1; i <= n; i++) {
|
|
1890
|
+
if (i < n && !list.some((sp) => sp.start[i] === i)) continue;
|
|
1891
|
+
for (let k = s; k < i; k++) {
|
|
1892
|
+
start[k] = s;
|
|
1893
|
+
end[k] = i - 1;
|
|
1894
|
+
}
|
|
1895
|
+
s = i;
|
|
1896
|
+
}
|
|
1897
|
+
return { start, end };
|
|
1898
|
+
}
|
|
1899
|
+
function mergeBlocks(rows, defs) {
|
|
1900
|
+
const n = rows.length;
|
|
1901
|
+
const block = new Int32Array(n);
|
|
1902
|
+
if (!n || !defs.length) return block;
|
|
1903
|
+
const { start } = nestedSpans(defs.map((def) => computeSpans(rows, def)), n);
|
|
1904
|
+
for (let i = 1; i < n; i++) block[i] = block[i - 1] + (start[i] === i ? 1 : 0);
|
|
1905
|
+
return block;
|
|
1906
|
+
}
|
|
1653
1907
|
|
|
1654
1908
|
// src/viewport.ts
|
|
1655
1909
|
function rowRange(scrollTop, height, rowHeight, count, overscan = 4) {
|
|
@@ -2166,7 +2420,9 @@ var Renderer = class {
|
|
|
2166
2420
|
const def = c.node.def;
|
|
2167
2421
|
if (def.wrapHeaderText ?? this.grid.options.wrapHeaderText) cell.classList.add("fg-header-wrap");
|
|
2168
2422
|
cell.setAttribute("aria-colindex", String(c.column.index + 1));
|
|
2169
|
-
|
|
2423
|
+
const headAlign = def.align ?? (def.type === "number" ? "right" : "left");
|
|
2424
|
+
if (headAlign === "right") cell.classList.add("fg-align-right");
|
|
2425
|
+
else if (headAlign === "center") cell.classList.add("fg-align-center");
|
|
2170
2426
|
const si = sort.findIndex((s) => s.colId === c.id);
|
|
2171
2427
|
cell.setAttribute("aria-sort", si < 0 ? "none" : sort[si].dir === "asc" ? "ascending" : "descending");
|
|
2172
2428
|
if (isSortable(def)) cell.classList.add("fg-sortable");
|
|
@@ -2213,6 +2469,7 @@ var Renderer = class {
|
|
|
2213
2469
|
const { selected, focus } = store.state;
|
|
2214
2470
|
const allBounds = this.grid.allRangeBounds();
|
|
2215
2471
|
const spans = this.grid.mergeSpans();
|
|
2472
|
+
const blocks = spans.has(SELECT_COL_ID);
|
|
2216
2473
|
const { geo } = this;
|
|
2217
2474
|
const top = Math.max(0, geo.top - this.topOffset);
|
|
2218
2475
|
const range = this.grid.options.autoHeight && !rowModel.server ? { first: 0, last: count - 1 } : this.offsets ? rowRangeByOffsets(this.offsets, top, geo.ch, count, ROW_OVERSCAN) : rowRange(top, geo.ch, this.opts.rowHeight, count, ROW_OVERSCAN);
|
|
@@ -2241,8 +2498,9 @@ var Renderer = class {
|
|
|
2241
2498
|
const hits = allBounds.filter((b) => i >= b.r1 && i <= b.r2);
|
|
2242
2499
|
const inRange = hits.map((b) => `${b.c1}-${b.c2}`).join(",");
|
|
2243
2500
|
const merge2 = this.mergeState(spans, cols, i, range.first);
|
|
2501
|
+
const block = blocks && !node.kind ? this.grid.blockSelection(i) ?? "" : "";
|
|
2244
2502
|
const pos = node.kind === "detail" ? `${geo.left}:${geo.cw}` : "";
|
|
2245
|
-
const base = `${node.id}|${this.version}.${this.rowVersions.get(node.id) ?? 0}|${isSel ? 1 : 0}${node.kind === "group" ? `:${this.grid.selectionVersion}` : ""}|${focusCol}|${inRange}|${merge2}|${pos}`;
|
|
2503
|
+
const base = `${node.id}|${this.version}.${this.rowVersions.get(node.id) ?? 0}|${isSel ? 1 : 0}${node.kind === "group" ? `:${this.grid.selectionVersion}` : ""}${block}|${focusCol}|${inRange}|${merge2}|${pos}`;
|
|
2246
2504
|
const key = `${colKey}|${base}`;
|
|
2247
2505
|
let row = map.get(i);
|
|
2248
2506
|
if (!row) {
|
|
@@ -2271,21 +2529,47 @@ var Renderer = class {
|
|
|
2271
2529
|
else this.paintRow(row.el, node, i, cols, isSel, focusCol, hits, spans, range.first);
|
|
2272
2530
|
}
|
|
2273
2531
|
}
|
|
2274
|
-
if (spans.size) this.stickMergedText(top);
|
|
2532
|
+
if (spans.size) this.stickMergedText(geo.top, geo.ch || Number.MAX_SAFE_INTEGER / 4);
|
|
2275
2533
|
}
|
|
2276
2534
|
/**
|
|
2277
2535
|
* A vertically merged cell taller than the view keeps its text in sight: the text moves down to the top of the
|
|
2278
2536
|
* visible part of the block and stops in the block's last row. Runs on every scroll, also for rows not repainted.
|
|
2279
2537
|
*/
|
|
2280
|
-
stickMergedText(viewTop) {
|
|
2538
|
+
stickMergedText(viewTop, viewHeight) {
|
|
2281
2539
|
for (const r of REGIONS3) {
|
|
2540
|
+
const ends = /* @__PURE__ */ new Map();
|
|
2541
|
+
for (const [i, row] of this.painted[r]) {
|
|
2542
|
+
let end = -1;
|
|
2543
|
+
for (const c of row.el.querySelectorAll(".fg-cell-merged")) end = Math.max(end, Number(c.dataset.mergeEnd));
|
|
2544
|
+
if (end > i) ends.set(i, end);
|
|
2545
|
+
}
|
|
2546
|
+
const z = /* @__PURE__ */ new Map();
|
|
2547
|
+
for (const i of [...ends.keys()].sort((a, b) => b - a)) {
|
|
2548
|
+
let top = 0;
|
|
2549
|
+
for (const [j, zj] of z) if (j > i && j <= ends.get(i)) top = Math.max(top, zj);
|
|
2550
|
+
z.set(i, top + 1);
|
|
2551
|
+
}
|
|
2552
|
+
for (const [i, row] of this.painted[r]) {
|
|
2553
|
+
const want = z.has(i) ? String(z.get(i)) : "";
|
|
2554
|
+
if (row.el.style.zIndex !== want && (want || row.el.querySelector(".fg-cell-merged, .fg-cell-merge-hidden"))) row.el.style.zIndex = want;
|
|
2555
|
+
}
|
|
2282
2556
|
for (const row of this.painted[r].values()) {
|
|
2283
2557
|
for (const c of row.el.querySelectorAll(".fg-cell-merged")) {
|
|
2284
2558
|
const blockTop = Number(c.dataset.mergeTop);
|
|
2285
2559
|
const last = Number(c.dataset.mergeLast);
|
|
2286
2560
|
if (!Number.isFinite(blockTop) || !Number.isFinite(last)) continue;
|
|
2287
|
-
|
|
2561
|
+
let top = Math.min(last, Math.max(0, viewTop - blockTop));
|
|
2562
|
+
let bottom = 0;
|
|
2563
|
+
if (c.classList.contains("fg-cell-merged-middle") || c.classList.contains("fg-cell-merged-bottom")) {
|
|
2564
|
+
const height = Number(c.dataset.mergeHeight);
|
|
2565
|
+
const room = height - last;
|
|
2566
|
+
bottom = Math.max(0, Math.min(height - top - room, blockTop + height - (viewTop + viewHeight)));
|
|
2567
|
+
top = Math.min(top, height - bottom - room);
|
|
2568
|
+
}
|
|
2569
|
+
const shift = `${Math.round(top)}px`;
|
|
2288
2570
|
if (c.style.getPropertyValue("--fg-merge-shift") !== shift) c.style.setProperty("--fg-merge-shift", shift);
|
|
2571
|
+
const shiftBottom = `${Math.round(bottom)}px`;
|
|
2572
|
+
if (c.style.getPropertyValue("--fg-merge-shift-bottom") !== shiftBottom) c.style.setProperty("--fg-merge-shift-bottom", shiftBottom);
|
|
2289
2573
|
}
|
|
2290
2574
|
}
|
|
2291
2575
|
}
|
|
@@ -2369,13 +2653,21 @@ var Renderer = class {
|
|
|
2369
2653
|
cellEl.style.bottom = "auto";
|
|
2370
2654
|
cellEl.dataset.mergeTop = String(this.rowTop(s.start[i]));
|
|
2371
2655
|
cellEl.dataset.mergeLast = String(this.rowTop(s.end[i]) - this.rowTop(s.start[i]));
|
|
2656
|
+
cellEl.dataset.mergeEnd = String(s.end[i]);
|
|
2657
|
+
cellEl.dataset.mergeHeight = String(this.rowTop(s.end[i]) + this.rowH(s.end[i]) - this.rowTop(s.start[i]));
|
|
2658
|
+
cellEl.classList.toggle("fg-cell-merged-middle", col.def.mergeAlign === "middle");
|
|
2659
|
+
cellEl.classList.toggle("fg-cell-merged-bottom", col.def.mergeAlign === "bottom");
|
|
2372
2660
|
drawsMerge = true;
|
|
2373
2661
|
} else cellEl.classList.add("fg-cell-merge-hidden");
|
|
2374
2662
|
}
|
|
2375
2663
|
if (!cellEl.classList.contains("fg-cell-merged") && cellEl.dataset.mergeTop !== void 0) {
|
|
2376
2664
|
delete cellEl.dataset.mergeTop;
|
|
2377
2665
|
delete cellEl.dataset.mergeLast;
|
|
2666
|
+
delete cellEl.dataset.mergeEnd;
|
|
2667
|
+
delete cellEl.dataset.mergeHeight;
|
|
2668
|
+
cellEl.classList.remove("fg-cell-merged-middle", "fg-cell-merged-bottom");
|
|
2378
2669
|
cellEl.style.removeProperty("--fg-merge-shift");
|
|
2670
|
+
cellEl.style.removeProperty("--fg-merge-shift-bottom");
|
|
2379
2671
|
}
|
|
2380
2672
|
if (!isSystemCol(col.id) && bounds.some((b) => col.index >= b.c1 && col.index <= b.c2)) cellEl.classList.add("fg-cell-range");
|
|
2381
2673
|
});
|
|
@@ -2405,7 +2697,15 @@ var Renderer = class {
|
|
|
2405
2697
|
cell.className = `${cls} fg-cell-select`;
|
|
2406
2698
|
const box = el("input", "fg-checkbox", { type: "checkbox", tabindex: "-1", "aria-label": locale.selectRow });
|
|
2407
2699
|
box.checked = selected;
|
|
2408
|
-
|
|
2700
|
+
const block = i >= 0 && !node.kind ? this.grid.blockSelection(i) : null;
|
|
2701
|
+
if (block) {
|
|
2702
|
+
box.checked = block === "all";
|
|
2703
|
+
box.indeterminate = block === "some";
|
|
2704
|
+
if (block === "off") {
|
|
2705
|
+
box.disabled = true;
|
|
2706
|
+
cell.classList.add("fg-cell-select-locked");
|
|
2707
|
+
}
|
|
2708
|
+
} else if (i >= 0 && !node.kind && !this.grid.rowSelectable(node.data)) {
|
|
2409
2709
|
box.disabled = true;
|
|
2410
2710
|
cell.classList.add("fg-cell-select-locked");
|
|
2411
2711
|
}
|
|
@@ -2415,7 +2715,8 @@ var Renderer = class {
|
|
|
2415
2715
|
const def = col.def;
|
|
2416
2716
|
const value = getCellValue(def, node.data);
|
|
2417
2717
|
const formatted = getFormatter(def, locale.code)(value);
|
|
2418
|
-
|
|
2718
|
+
const cf = i >= 0 && !node.kind ? this.grid.cellFormat(node.data, col.id, value) : null;
|
|
2719
|
+
cls += ` fg-align-${styleAlign(cf?.style) ?? def.align ?? (def.type === "number" ? "right" : def.type === "boolean" ? "center" : "left")}`;
|
|
2419
2720
|
const params = { value, formatted, row: node.data, rowIndex: i, column: def };
|
|
2420
2721
|
if (def.cellClass) {
|
|
2421
2722
|
try {
|
|
@@ -2430,7 +2731,6 @@ var Renderer = class {
|
|
|
2430
2731
|
if (typeof def.editable === "function" && i >= 0 && !cellEditable(def, node.data, i)) cls += " fg-cell-readonly";
|
|
2431
2732
|
if (i >= 0) cls += this.grid.finder.classFor(i, col.id);
|
|
2432
2733
|
cls += this.grid.flashClass(node.id, col.id);
|
|
2433
|
-
const cf = i >= 0 && !node.kind ? this.grid.cellFormat(node.data, col.id, value) : null;
|
|
2434
2734
|
if (cf?.bar) cls += " fg-cf-bar";
|
|
2435
2735
|
cell.className = cls;
|
|
2436
2736
|
this.applyFormat(cell, cf);
|
|
@@ -3323,6 +3623,8 @@ var en = {
|
|
|
3323
3623
|
style: "Format",
|
|
3324
3624
|
wholeRow: "Whole row",
|
|
3325
3625
|
count: "Count",
|
|
3626
|
+
align: "Align",
|
|
3627
|
+
aligns: { none: "Default", left: "Left", center: "Center", right: "Right" },
|
|
3326
3628
|
types: { value: "Cell value", formula: "Formula", top: "Top", bottom: "Bottom", aboveAverage: "Above average", belowAverage: "Below average", duplicate: "Duplicate values", unique: "Unique values", dataBar: "Data bars", colorScale: "Color scale", iconSet: "Icon set" },
|
|
3327
3629
|
ops: { notBetween: "Not between", notContains: "Does not contain" },
|
|
3328
3630
|
styles: { red: "Light red fill", yellow: "Yellow fill", green: "Green fill", blue: "Blue fill", redText: "Red text", bold: "Bold" }
|
|
@@ -5855,225 +6157,6 @@ ${xref}
|
|
|
5855
6157
|
return out;
|
|
5856
6158
|
}
|
|
5857
6159
|
|
|
5858
|
-
// src/conditional.ts
|
|
5859
|
-
var NUMERIC = (v) => {
|
|
5860
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
5861
|
-
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
5862
|
-
if (v instanceof Date) return v.getTime();
|
|
5863
|
-
return null;
|
|
5864
|
-
};
|
|
5865
|
-
var STAT_TYPES = /* @__PURE__ */ new Set(["top", "bottom", "aboveAverage", "belowAverage", "duplicate", "unique", "dataBar", "colorScale", "iconSet"]);
|
|
5866
|
-
var ICON_SETS = {
|
|
5867
|
-
// high, middle, low
|
|
5868
|
-
arrows: [{ char: "\u25B2", color: "#16a34a", label: "up" }, { char: "\u25B6", color: "#d97706", label: "level" }, { char: "\u25BC", color: "#dc2626", label: "down" }],
|
|
5869
|
-
traffic: [{ char: "\u25CF", color: "#16a34a", label: "good" }, { char: "\u25CF", color: "#f59e0b", label: "fair" }, { char: "\u25CF", color: "#dc2626", label: "poor" }],
|
|
5870
|
-
signs: [{ char: "\u2714", color: "#16a34a", label: "good" }, { char: "!", color: "#d97706", label: "fair" }, { char: "\u2716", color: "#dc2626", label: "poor" }]
|
|
5871
|
-
};
|
|
5872
|
-
function hex(c) {
|
|
5873
|
-
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(c.trim());
|
|
5874
|
-
if (!m) return null;
|
|
5875
|
-
const h = m[1].length === 3 ? [...m[1]].map((x) => x + x).join("") : m[1];
|
|
5876
|
-
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
5877
|
-
}
|
|
5878
|
-
function mixColor(a, b, t) {
|
|
5879
|
-
const x = hex(a), y = hex(b);
|
|
5880
|
-
if (!x || !y) return t < 0.5 ? a : b;
|
|
5881
|
-
const c = x.map((v, i) => Math.round(v + (y[i] - v) * Math.max(0, Math.min(1, t))));
|
|
5882
|
-
return `#${c.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
5883
|
-
}
|
|
5884
|
-
function textOf(v) {
|
|
5885
|
-
return v == null ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
5886
|
-
}
|
|
5887
|
-
function valueMatches(rule, value) {
|
|
5888
|
-
const op = rule.op ?? "eq";
|
|
5889
|
-
if (op === "blank") return isBlank(value);
|
|
5890
|
-
if (op === "notBlank") return !isBlank(value);
|
|
5891
|
-
if (op === "contains" || op === "notContains" || op === "startsWith" || op === "endsWith") {
|
|
5892
|
-
const s2 = textOf(value).toLowerCase(), q = textOf(rule.value).toLowerCase();
|
|
5893
|
-
const hit = op === "startsWith" ? s2.startsWith(q) : op === "endsWith" ? s2.endsWith(q) : s2.includes(q);
|
|
5894
|
-
return op === "notContains" ? !hit : hit;
|
|
5895
|
-
}
|
|
5896
|
-
if (isBlank(value)) return op === "ne";
|
|
5897
|
-
const a = NUMERIC(value), b = NUMERIC(rule.value), c = NUMERIC(rule.valueTo);
|
|
5898
|
-
if (a !== null && b !== null) {
|
|
5899
|
-
switch (op) {
|
|
5900
|
-
case "eq":
|
|
5901
|
-
return a === b;
|
|
5902
|
-
case "ne":
|
|
5903
|
-
return a !== b;
|
|
5904
|
-
case "gt":
|
|
5905
|
-
return a > b;
|
|
5906
|
-
case "gte":
|
|
5907
|
-
return a >= b;
|
|
5908
|
-
case "lt":
|
|
5909
|
-
return a < b;
|
|
5910
|
-
case "lte":
|
|
5911
|
-
return a <= b;
|
|
5912
|
-
case "between":
|
|
5913
|
-
case "notBetween": {
|
|
5914
|
-
if (c === null) return false;
|
|
5915
|
-
const inside = a >= Math.min(b, c) && a <= Math.max(b, c);
|
|
5916
|
-
return op === "between" ? inside : !inside;
|
|
5917
|
-
}
|
|
5918
|
-
}
|
|
5919
|
-
}
|
|
5920
|
-
const s = textOf(value).toLowerCase(), t = textOf(rule.value).toLowerCase(), u = textOf(rule.valueTo).toLowerCase();
|
|
5921
|
-
switch (op) {
|
|
5922
|
-
case "eq":
|
|
5923
|
-
return s === t;
|
|
5924
|
-
case "ne":
|
|
5925
|
-
return s !== t;
|
|
5926
|
-
case "gt":
|
|
5927
|
-
return s > t;
|
|
5928
|
-
case "gte":
|
|
5929
|
-
return s >= t;
|
|
5930
|
-
case "lt":
|
|
5931
|
-
return s < t;
|
|
5932
|
-
case "lte":
|
|
5933
|
-
return s <= t;
|
|
5934
|
-
case "between":
|
|
5935
|
-
return s >= t && s <= u;
|
|
5936
|
-
case "notBetween":
|
|
5937
|
-
return s < t || s > u;
|
|
5938
|
-
}
|
|
5939
|
-
return false;
|
|
5940
|
-
}
|
|
5941
|
-
var ConditionalFormatter = class {
|
|
5942
|
-
constructor(deps) {
|
|
5943
|
-
this.deps = deps;
|
|
5944
|
-
this.stats = /* @__PURE__ */ new Map();
|
|
5945
|
-
this.statsKey = [];
|
|
5946
|
-
this.rowCache = /* @__PURE__ */ new WeakMap();
|
|
5947
|
-
}
|
|
5948
|
-
/** True when any rule looks at other rows (an update to one row can restyle others). */
|
|
5949
|
-
dependsOnOtherRows() {
|
|
5950
|
-
return this.deps.rules().some((r) => r && (STAT_TYPES.has(r.type ?? "value") || r.type === "formula"));
|
|
5951
|
-
}
|
|
5952
|
-
fresh() {
|
|
5953
|
-
const key = [this.deps.rows(), this.deps.version(), this.deps.rules()];
|
|
5954
|
-
if (key.every((k, i) => k === this.statsKey[i])) return;
|
|
5955
|
-
this.statsKey = key;
|
|
5956
|
-
this.stats.clear();
|
|
5957
|
-
this.rowCache = /* @__PURE__ */ new WeakMap();
|
|
5958
|
-
}
|
|
5959
|
-
statsOf(colId) {
|
|
5960
|
-
let s = this.stats.get(colId);
|
|
5961
|
-
if (!s) {
|
|
5962
|
-
const nums = [];
|
|
5963
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5964
|
-
for (const row of this.deps.rows()) {
|
|
5965
|
-
const v = this.deps.value(colId, row);
|
|
5966
|
-
const n = NUMERIC(v);
|
|
5967
|
-
if (n !== null) nums.push(n);
|
|
5968
|
-
if (!isBlank(v)) {
|
|
5969
|
-
const k = setKey(v);
|
|
5970
|
-
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
5971
|
-
}
|
|
5972
|
-
}
|
|
5973
|
-
nums.sort((a, b) => a - b);
|
|
5974
|
-
const sum = nums.reduce((a, b) => a + b, 0);
|
|
5975
|
-
s = { min: nums[0] ?? 0, max: nums[nums.length - 1] ?? 0, avg: nums.length ? sum / nums.length : 0, sorted: nums, counts };
|
|
5976
|
-
this.stats.set(colId, s);
|
|
5977
|
-
}
|
|
5978
|
-
return s;
|
|
5979
|
-
}
|
|
5980
|
-
test(rule, colId, value, row) {
|
|
5981
|
-
const type = rule.type ?? "value";
|
|
5982
|
-
switch (type) {
|
|
5983
|
-
case "value":
|
|
5984
|
-
return valueMatches(rule, value);
|
|
5985
|
-
case "formula": {
|
|
5986
|
-
if (typeof rule.formula !== "string") return false;
|
|
5987
|
-
const r = this.deps.formula(rule.formula.startsWith("=") ? rule.formula : `=${rule.formula}`, row);
|
|
5988
|
-
return r === true || typeof r === "number" && r !== 0;
|
|
5989
|
-
}
|
|
5990
|
-
case "top":
|
|
5991
|
-
case "bottom": {
|
|
5992
|
-
const n = NUMERIC(value);
|
|
5993
|
-
if (n === null) return false;
|
|
5994
|
-
const s = this.statsOf(colId);
|
|
5995
|
-
if (!s.sorted.length) return false;
|
|
5996
|
-
const k = Math.max(1, rule.percent ? Math.ceil(s.sorted.length * (rule.rank ?? 10) / 100) : Math.floor(rule.rank ?? 10));
|
|
5997
|
-
return type === "top" ? n >= s.sorted[Math.max(0, s.sorted.length - k)] : n <= s.sorted[Math.min(s.sorted.length - 1, k - 1)];
|
|
5998
|
-
}
|
|
5999
|
-
case "aboveAverage":
|
|
6000
|
-
case "belowAverage": {
|
|
6001
|
-
const n = NUMERIC(value);
|
|
6002
|
-
if (n === null) return false;
|
|
6003
|
-
const avg = this.statsOf(colId).avg;
|
|
6004
|
-
return type === "aboveAverage" ? n > avg : n < avg;
|
|
6005
|
-
}
|
|
6006
|
-
case "duplicate":
|
|
6007
|
-
case "unique": {
|
|
6008
|
-
if (isBlank(value)) return false;
|
|
6009
|
-
const c = this.statsOf(colId).counts.get(setKey(value)) ?? 0;
|
|
6010
|
-
return type === "duplicate" ? c > 1 : c === 1;
|
|
6011
|
-
}
|
|
6012
|
-
}
|
|
6013
|
-
return false;
|
|
6014
|
-
}
|
|
6015
|
-
/** Style from rules with `row: true`, for every cell of the row. */
|
|
6016
|
-
rowStyle(row) {
|
|
6017
|
-
if (this.rowCache.has(row)) return this.rowCache.get(row);
|
|
6018
|
-
let style = null;
|
|
6019
|
-
for (const rule of this.deps.rules()) {
|
|
6020
|
-
if (!rule?.row || !Array.isArray(rule.columns) || !rule.columns.length) continue;
|
|
6021
|
-
const colId = rule.columns[0];
|
|
6022
|
-
if (this.test(rule, colId, this.deps.value(colId, row), row)) {
|
|
6023
|
-
style = { ...style ?? {}, ...rule.style };
|
|
6024
|
-
if (rule.stopIfTrue) break;
|
|
6025
|
-
}
|
|
6026
|
-
}
|
|
6027
|
-
this.rowCache.set(row, style);
|
|
6028
|
-
return style;
|
|
6029
|
-
}
|
|
6030
|
-
/** Format of one cell; null when no rule applies. */
|
|
6031
|
-
cell(colId, row, value) {
|
|
6032
|
-
const rules = this.deps.rules();
|
|
6033
|
-
if (!rules.length) return null;
|
|
6034
|
-
this.fresh();
|
|
6035
|
-
let out = null;
|
|
6036
|
-
const rs = this.rowStyle(row);
|
|
6037
|
-
if (rs) out = { style: { ...rs } };
|
|
6038
|
-
for (const rule of rules) {
|
|
6039
|
-
if (!rule || rule.row || !Array.isArray(rule.columns) || !rule.columns.includes(colId)) continue;
|
|
6040
|
-
const type = rule.type ?? "value";
|
|
6041
|
-
if (type === "dataBar" || type === "colorScale" || type === "iconSet") {
|
|
6042
|
-
const n = NUMERIC(value);
|
|
6043
|
-
if (n === null) continue;
|
|
6044
|
-
const s = this.statsOf(colId);
|
|
6045
|
-
const lo = typeof rule.min === "number" ? rule.min : s.min;
|
|
6046
|
-
const hi = typeof rule.max === "number" ? rule.max : s.max;
|
|
6047
|
-
const t = hi === lo ? 1 : Math.max(0, Math.min(1, (n - lo) / (hi - lo)));
|
|
6048
|
-
out ?? (out = { style: {} });
|
|
6049
|
-
if (type === "dataBar") {
|
|
6050
|
-
const zero = lo < 0 && hi > 0 ? -lo / (hi - lo) : lo >= 0 ? 0 : 1;
|
|
6051
|
-
const at = hi === lo ? 1 : t;
|
|
6052
|
-
out.bar = { from: Math.min(zero, at), to: Math.max(zero, at), color: rule.color || "var(--fg-accent)" };
|
|
6053
|
-
} else if (type === "colorScale") {
|
|
6054
|
-
const cs = Array.isArray(rule.colors) && rule.colors.length >= 2 ? rule.colors : ["#f8696b", "#ffeb84", "#63be7b"];
|
|
6055
|
-
const bg = cs.length >= 3 ? t < 0.5 ? mixColor(cs[0], cs[1], t * 2) : mixColor(cs[1], cs[2], (t - 0.5) * 2) : mixColor(cs[0], cs[1], t);
|
|
6056
|
-
out.style = { ...out.style, background: bg };
|
|
6057
|
-
} else {
|
|
6058
|
-
const set = ICON_SETS[rule.icons ?? "arrows"] ?? ICON_SETS.arrows;
|
|
6059
|
-
out.icon = t >= 2 / 3 ? set[0] : t >= 1 / 3 ? set[1] : set[2];
|
|
6060
|
-
}
|
|
6061
|
-
continue;
|
|
6062
|
-
}
|
|
6063
|
-
if (this.test(rule, colId, value, row)) {
|
|
6064
|
-
out ?? (out = { style: {} });
|
|
6065
|
-
out.style = { ...out.style, ...rule.style };
|
|
6066
|
-
if (rule.stopIfTrue) break;
|
|
6067
|
-
}
|
|
6068
|
-
}
|
|
6069
|
-
return out;
|
|
6070
|
-
}
|
|
6071
|
-
};
|
|
6072
|
-
function argbOf(c) {
|
|
6073
|
-
const x = c ? hex(c) : null;
|
|
6074
|
-
return x ? `FF${x.map((v) => v.toString(16).padStart(2, "0")).join("").toUpperCase()}` : void 0;
|
|
6075
|
-
}
|
|
6076
|
-
|
|
6077
6160
|
// src/rows/sort.ts
|
|
6078
6161
|
var PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6079
6162
|
function numeric(v) {
|
|
@@ -6118,7 +6201,7 @@ function compareGroupValues(a, b, collator, dir) {
|
|
|
6118
6201
|
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
6119
6202
|
return compareNonBlank(a, b, collator) * dir;
|
|
6120
6203
|
}
|
|
6121
|
-
function sortNodes(nodes, sort, resolve, collator) {
|
|
6204
|
+
function sortNodes(nodes, sort, resolve, collator, blocks) {
|
|
6122
6205
|
const keys = sort.map((s) => ({ def: resolve(s.colId), mul: s.dir === "desc" ? -1 : 1 })).filter((k) => !!k.def);
|
|
6123
6206
|
if (!keys.length) return nodes;
|
|
6124
6207
|
const values = keys.map((k) => nodes.map((n) => {
|
|
@@ -6127,6 +6210,7 @@ function sortNodes(nodes, sort, resolve, collator) {
|
|
|
6127
6210
|
}));
|
|
6128
6211
|
const order = nodes.map((_, i) => i);
|
|
6129
6212
|
order.sort((i, j) => {
|
|
6213
|
+
if (blocks && blocks[i] !== blocks[j]) return blocks[i] - blocks[j];
|
|
6130
6214
|
for (let k = 0; k < keys.length; k++) {
|
|
6131
6215
|
const { def, mul } = keys[k];
|
|
6132
6216
|
const a = values[k][i], b = values[k][j];
|
|
@@ -6721,6 +6805,8 @@ function printModel(grid, opts = {}) {
|
|
|
6721
6805
|
if (s.bold) cell.bold = true;
|
|
6722
6806
|
if (s.italic) cell.italic = true;
|
|
6723
6807
|
if (s.strike) cell.strike = true;
|
|
6808
|
+
const align2 = styleAlign(s);
|
|
6809
|
+
if (align2) cell.align = align2;
|
|
6724
6810
|
if (cf.bar) cell.bar = { from: cf.bar.from, to: cf.bar.to, color: /^#[0-9a-f]{3,8}$/i.test(cf.bar.color) ? cf.bar.color : "#93c5fd" };
|
|
6725
6811
|
if (cf.icon) cell.icon = { char: cf.icon.char, color: cf.icon.color };
|
|
6726
6812
|
}
|
|
@@ -7037,6 +7123,8 @@ function buildXlsxSheet(grid, opts = {}) {
|
|
|
7037
7123
|
if (color) style.color = color;
|
|
7038
7124
|
if (fill) style.fill = fill;
|
|
7039
7125
|
if (cf.style.bold) style.bold = true;
|
|
7126
|
+
const align = styleAlign(cf.style);
|
|
7127
|
+
if (align) style.align = align;
|
|
7040
7128
|
}
|
|
7041
7129
|
if (!Object.keys(style).length) return cell;
|
|
7042
7130
|
return cell !== null && typeof cell === "object" && !(cell instanceof Date) ? { ...cell, ...style } : { v: cell, ...style };
|
|
@@ -7669,7 +7757,7 @@ var PAID_FEATURES = {
|
|
|
7669
7757
|
};
|
|
7670
7758
|
|
|
7671
7759
|
// src/version.ts
|
|
7672
|
-
var version = "1.9.
|
|
7760
|
+
var version = "1.9.4";
|
|
7673
7761
|
|
|
7674
7762
|
// src/license.ts
|
|
7675
7763
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -8217,6 +8305,8 @@ var RowModel = class {
|
|
|
8217
8305
|
this.full = [];
|
|
8218
8306
|
this.last = {};
|
|
8219
8307
|
this.nodes = [];
|
|
8308
|
+
/** Ids given to the current rows (repeats of a getRowId value made unique). */
|
|
8309
|
+
this.assignedIds = /* @__PURE__ */ new WeakMap();
|
|
8220
8310
|
this.filtered = [];
|
|
8221
8311
|
this.sorted = [];
|
|
8222
8312
|
this.display = [];
|
|
@@ -8235,14 +8325,32 @@ var RowModel = class {
|
|
|
8235
8325
|
const displayChanged = sortChanged || d?.version !== this.last.displayVersion || d?.groupBy !== this.last.display?.groupBy;
|
|
8236
8326
|
if (dataChanged) {
|
|
8237
8327
|
const getId = this.deps.getRowId;
|
|
8238
|
-
|
|
8328
|
+
if (getId) {
|
|
8329
|
+
const seen2 = /* @__PURE__ */ new Set();
|
|
8330
|
+
let dup = null;
|
|
8331
|
+
this.assignedIds = /* @__PURE__ */ new WeakMap();
|
|
8332
|
+
this.nodes = input.data.map((data, i) => {
|
|
8333
|
+
const raw = String(getId(data));
|
|
8334
|
+
let id = raw;
|
|
8335
|
+
if (seen2.has(id)) {
|
|
8336
|
+
dup ?? (dup = raw);
|
|
8337
|
+
for (let k = 2; seen2.has(id); k++) id = `${raw}#${k}`;
|
|
8338
|
+
}
|
|
8339
|
+
seen2.add(id);
|
|
8340
|
+
this.assignedIds.set(data, id);
|
|
8341
|
+
return { id, data, sourceIndex: i };
|
|
8342
|
+
});
|
|
8343
|
+
if (dup !== null) warn(`getRowId returned "${dup}" for more than one row; inside the grid the repeats are told apart as "${dup}#2" and so on, and updates by id reach the first of them. Return a unique id per row.`);
|
|
8344
|
+
} else this.nodes = input.data.map((data, i) => ({ id: autoId(data), data, sourceIndex: i }));
|
|
8239
8345
|
}
|
|
8240
8346
|
if (filterChanged) {
|
|
8241
8347
|
this.filtered = this.filter(input);
|
|
8242
8348
|
this.stats.filterRuns++;
|
|
8243
8349
|
}
|
|
8244
8350
|
if (sortChanged) {
|
|
8245
|
-
|
|
8351
|
+
const blockDefs = input.sort.length ? this.deps.sortBlocks?.(input.sort) : null;
|
|
8352
|
+
const blocks = blockDefs?.length ? mergeBlocks(this.filtered.map((n) => n.data), blockDefs) : void 0;
|
|
8353
|
+
this.sorted = sortNodes(this.filtered, input.sort, (id) => this.deps.columnDef(id), collator, blocks);
|
|
8246
8354
|
this.stats.sortRuns++;
|
|
8247
8355
|
}
|
|
8248
8356
|
if (displayChanged) this.full = d ? this.buildDisplay(d, input.sort, collator) : this.sorted;
|
|
@@ -8286,7 +8394,8 @@ var RowModel = class {
|
|
|
8286
8394
|
}
|
|
8287
8395
|
/** Id of a row object as currently assigned. */
|
|
8288
8396
|
idOf(row) {
|
|
8289
|
-
|
|
8397
|
+
if (!this.deps.getRowId) return autoId(row);
|
|
8398
|
+
return this.assignedIds.get(row) ?? String(this.deps.getRowId(row));
|
|
8290
8399
|
}
|
|
8291
8400
|
indexOfId(id) {
|
|
8292
8401
|
if (!this.indexById) this.indexById = new Map(this.display.map((n, i) => [n.id, i]));
|
|
@@ -9259,7 +9368,7 @@ var FormatDialog = class {
|
|
|
9259
9368
|
const node = grid.columns.getNode(colId);
|
|
9260
9369
|
if (!node || node.children || isSystemCol(colId)) return;
|
|
9261
9370
|
const loc = grid.store.state.locale;
|
|
9262
|
-
const cf = loc.cf;
|
|
9371
|
+
const cf = { ...en_default.cf, ...loc.cf, aligns: { ...en_default.cf.aligns, ...loc.cf?.aligns } };
|
|
9263
9372
|
const label = node.def.header ?? node.def.field ?? colId;
|
|
9264
9373
|
const popup = el("div", `fg-menu fg-cf-dialog fg-theme-${grid.store.state.theme}`, { role: "dialog", "aria-label": `${cf.title}: ${label}` });
|
|
9265
9374
|
grid.applyFontTo(popup);
|
|
@@ -9301,9 +9410,14 @@ var FormatDialog = class {
|
|
|
9301
9410
|
if (r2.style?.background) sample.style.background = r2.style.background;
|
|
9302
9411
|
if (r2.style?.color) sample.style.color = r2.style.color;
|
|
9303
9412
|
if (r2.style?.bold) sample.style.fontWeight = "700";
|
|
9413
|
+
const align = styleAlign(r2.style);
|
|
9414
|
+
if (align && align !== "center") {
|
|
9415
|
+
sample.style.justifyItems = align === "left" ? "start" : "end";
|
|
9416
|
+
sample.style.padding = "0 3px";
|
|
9417
|
+
}
|
|
9304
9418
|
if (r2.type === "colorScale") sample.style.background = `linear-gradient(to right, ${(r2.colors ?? ["#f8696b", "#ffeb84", "#63be7b"]).join(",")})`;
|
|
9305
9419
|
const text2 = el("span", "fg-cf-rule-text");
|
|
9306
|
-
text2.textContent = `${describe(r2)}${r2.row ? ` \xB7 ${cf.wholeRow}` : ""}`;
|
|
9420
|
+
text2.textContent = `${describe(r2)}${r2.row ? ` \xB7 ${cf.wholeRow}` : ""}${align ? ` \xB7 ${cf.align}: ${cf.aligns[align]}` : ""}`;
|
|
9307
9421
|
text2.title = text2.textContent;
|
|
9308
9422
|
const del = el("button", "fg-chooser-close", { type: "button", "aria-label": `${loc.close}: ${text2.textContent}` });
|
|
9309
9423
|
del.textContent = "\xD7";
|
|
@@ -9355,10 +9469,17 @@ var FormatDialog = class {
|
|
|
9355
9469
|
});
|
|
9356
9470
|
swatches.append(b);
|
|
9357
9471
|
});
|
|
9472
|
+
const alignSel = el("select", "fg-filter-op fg-cf-wide fg-cf-align", { "aria-label": cf.align, title: cf.align });
|
|
9473
|
+
for (const a of ["", "left", "center", "right"]) {
|
|
9474
|
+
const o = el("option");
|
|
9475
|
+
o.value = a;
|
|
9476
|
+
o.textContent = a ? `${cf.align}: ${cf.aligns[a]}` : `${cf.align}: ${cf.aligns.none}`;
|
|
9477
|
+
alignSel.append(o);
|
|
9478
|
+
}
|
|
9358
9479
|
const rowLabel = el("label", "fg-cf-wide fg-cf-row");
|
|
9359
9480
|
const rowBox = el("input", "", { type: "checkbox" });
|
|
9360
9481
|
rowLabel.append(rowBox, ` ${cf.wholeRow}`);
|
|
9361
|
-
form.append(typeSel, opSel, v1, v2, formula, rank, pctLabel, swatches, rowLabel);
|
|
9482
|
+
form.append(typeSel, opSel, v1, v2, formula, rank, pctLabel, swatches, alignSel, rowLabel);
|
|
9362
9483
|
const sync = () => {
|
|
9363
9484
|
const t = typeSel.value;
|
|
9364
9485
|
const op = opSel.value;
|
|
@@ -9367,7 +9488,7 @@ var FormatDialog = class {
|
|
|
9367
9488
|
v2.hidden = t !== "value" || op !== "between" && op !== "notBetween";
|
|
9368
9489
|
formula.hidden = t !== "formula";
|
|
9369
9490
|
rank.hidden = pctLabel.hidden = t !== "top" && t !== "bottom";
|
|
9370
|
-
swatches.hidden = SCALES.includes(t);
|
|
9491
|
+
swatches.hidden = alignSel.hidden = SCALES.includes(t);
|
|
9371
9492
|
rowLabel.hidden = t !== "value" && t !== "formula";
|
|
9372
9493
|
};
|
|
9373
9494
|
typeSel.addEventListener("change", sync);
|
|
@@ -9395,7 +9516,11 @@ var FormatDialog = class {
|
|
|
9395
9516
|
rule.rank = Math.max(1, Math.floor(Number(rank.value) || 10));
|
|
9396
9517
|
if (pct.checked) rule.percent = true;
|
|
9397
9518
|
}
|
|
9398
|
-
if (!SCALES.includes(type))
|
|
9519
|
+
if (!SCALES.includes(type)) {
|
|
9520
|
+
rule.style = { ...CF_PRESETS[preset].style };
|
|
9521
|
+
const a = styleAlign({ align: alignSel.value });
|
|
9522
|
+
if (a) rule.style.align = a;
|
|
9523
|
+
}
|
|
9399
9524
|
if (rowBox.checked && (type === "value" || type === "formula")) rule.row = true;
|
|
9400
9525
|
return rule;
|
|
9401
9526
|
};
|
|
@@ -9424,7 +9549,12 @@ var FormatDialog = class {
|
|
|
9424
9549
|
const r = grid.renderer.root.getBoundingClientRect();
|
|
9425
9550
|
const w = popup.offsetWidth || 340;
|
|
9426
9551
|
popup.style.left = `${Math.max(4, Math.min(r.left + 24, (window.innerWidth || 1024) - w - 4))}px`;
|
|
9427
|
-
|
|
9552
|
+
const vh = window.innerHeight || 768;
|
|
9553
|
+
const h = popup.offsetHeight || 0;
|
|
9554
|
+
const top = Math.max(4, Math.min(r.top + 24, vh - h - 4));
|
|
9555
|
+
popup.style.top = `${top}px`;
|
|
9556
|
+
popup.style.maxHeight = `${Math.max(160, vh - top - 8)}px`;
|
|
9557
|
+
popup.style.overflowY = "auto";
|
|
9428
9558
|
this.popup = popup;
|
|
9429
9559
|
typeSel.focus();
|
|
9430
9560
|
const onDown = (e) => {
|
|
@@ -9941,6 +10071,14 @@ var Grid = class {
|
|
|
9941
10071
|
}
|
|
9942
10072
|
};
|
|
9943
10073
|
this.density = "normal";
|
|
10074
|
+
// ── merge, aggregates, fill, pinned rows ────────────────────
|
|
10075
|
+
/** `sortWithinMerges` is on. */
|
|
10076
|
+
this.sortWithin = false;
|
|
10077
|
+
// ── mergeSelection: one checkbox per merged block ───────────
|
|
10078
|
+
/** 'rows' or the merge level whose blocks get one checkbox each. */
|
|
10079
|
+
this.mergeSel = "rows";
|
|
10080
|
+
this.selBlockCache = null;
|
|
10081
|
+
this.blockStateCache = null;
|
|
9944
10082
|
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
9945
10083
|
this.aggMemo = null;
|
|
9946
10084
|
/** Bumped on every selection change (group row checkboxes depend on their rows). */
|
|
@@ -9987,8 +10125,12 @@ var Grid = class {
|
|
|
9987
10125
|
quickColumns: () => this.columns.displayed().filter((c) => !isSystemCol(c.id)).map((c) => c.def),
|
|
9988
10126
|
format: (def, v) => getFormatter(def, this.store.state.locale.code)(v),
|
|
9989
10127
|
collator: () => this.collator(),
|
|
9990
|
-
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}
|
|
10128
|
+
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}`,
|
|
10129
|
+
sortBlocks: (sort) => this.sortBlocks(sort)
|
|
9991
10130
|
});
|
|
10131
|
+
this.sortWithin = !!options.sortWithinMerges;
|
|
10132
|
+
this.mergeSel = this.validMergeSelection(options.mergeSelection);
|
|
10133
|
+
if (this.sortWithin && this.rowModel.server) warn("sortWithinMerges is ignored with a datasource: the server sorts the rows");
|
|
9992
10134
|
if (options.groupBy?.length && this.allow(this.rowModel.server ? "serverGroups" : "grouping")) this.groupBy = this.validGroupBy(options.groupBy);
|
|
9993
10135
|
if (Array.isArray(options.conditionalFormats) && options.conditionalFormats.length && this.allow("conditional")) this.cfRules = options.conditionalFormats.filter((r) => r && typeof r === "object" && Array.isArray(r.columns)).map((r) => ({ ...r, columns: [...r.columns] }));
|
|
9994
10136
|
this.defaultExpanded = options.treeData || this.paid ? options.groupDefaultExpanded ?? 0 : 0;
|
|
@@ -10333,8 +10475,9 @@ var Grid = class {
|
|
|
10333
10475
|
const st = this.store.state;
|
|
10334
10476
|
if (this.pivotCfg || this.options.treeData || st.quickFilter || st.advancedFilter || this.groupBy.length) return true;
|
|
10335
10477
|
const ids = [...st.sort.map((x) => x.colId), ...Object.keys(st.filters)];
|
|
10336
|
-
|
|
10337
|
-
|
|
10478
|
+
const defs = ids.map((id) => this.columns.getNode(id)?.def);
|
|
10479
|
+
if (st.sort.length) defs.push(...this.sortBlocks(st.sort) ?? []);
|
|
10480
|
+
return defs.some((def) => {
|
|
10338
10481
|
if (!def) return false;
|
|
10339
10482
|
if (def.valueGetter || def.formulas || def.formula) return true;
|
|
10340
10483
|
return def.field != null && fields.has(def.field);
|
|
@@ -10651,6 +10794,7 @@ var Grid = class {
|
|
|
10651
10794
|
this.groupBy = next;
|
|
10652
10795
|
this.page = 0;
|
|
10653
10796
|
this.groupPanel?.refresh();
|
|
10797
|
+
if (this.sortWithin) this.rowModel.invalidate();
|
|
10654
10798
|
this.displayChanged();
|
|
10655
10799
|
this.events.emit("groupChange", { groupBy: [...next] });
|
|
10656
10800
|
}
|
|
@@ -11221,23 +11365,205 @@ var Grid = class {
|
|
|
11221
11365
|
});
|
|
11222
11366
|
return out;
|
|
11223
11367
|
}
|
|
11224
|
-
|
|
11368
|
+
/**
|
|
11369
|
+
* Merge columns the client sort must stay inside: the displayed `merge` columns that are
|
|
11370
|
+
* not sort keys. Nothing (a plain sort) when the option is off, merging is not allowed,
|
|
11371
|
+
* or rows are grouped, pivoted or shown as a tree.
|
|
11372
|
+
*/
|
|
11373
|
+
sortBlocks(sort) {
|
|
11374
|
+
if (!this.sortWithin || !this.paid) return null;
|
|
11375
|
+
if (this.pivotCfg || this.options.treeData || this.groupBy.length) {
|
|
11376
|
+
warn("sortWithinMerges is ignored while rows are grouped, pivoted or shown as a tree");
|
|
11377
|
+
return null;
|
|
11378
|
+
}
|
|
11379
|
+
const keys = new Set(sort.map((s) => s.colId));
|
|
11380
|
+
return this.columns.displayed().filter((c) => c.def.merge && !c.def.rowSpan && !isSystemCol(c.id) && !keys.has(c.id)).map((c) => c.def);
|
|
11381
|
+
}
|
|
11382
|
+
setSortWithinMerges(on) {
|
|
11383
|
+
const next = !!on;
|
|
11384
|
+
if (next === this.sortWithin) return;
|
|
11385
|
+
this.sortWithin = next;
|
|
11386
|
+
if (this.rowModel.server) {
|
|
11387
|
+
if (next) warn("sortWithinMerges is ignored with a datasource: the server sorts the rows");
|
|
11388
|
+
return;
|
|
11389
|
+
}
|
|
11390
|
+
this.rowModel.invalidate();
|
|
11391
|
+
this.updateRows();
|
|
11392
|
+
this.renderer.invalidate({ rows: true });
|
|
11393
|
+
}
|
|
11225
11394
|
/** Merge blocks for every `merge` column over the displayed rows (memoized). */
|
|
11226
11395
|
mergeSpans() {
|
|
11227
11396
|
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan)) return NO_SPANS;
|
|
11228
11397
|
if (!this.allow("merge")) return NO_SPANS;
|
|
11229
11398
|
const rows = this.rowModel.rows();
|
|
11230
11399
|
const ver = this.editVersion * 1e6 + this.columns.displayed().length;
|
|
11231
|
-
|
|
11400
|
+
const sel = this.columns.getColumn(SELECT_COL_ID) ? this.selectionBlocks() : null;
|
|
11401
|
+
if (this.spanCache && this.spanCache.rows === rows && this.spanCache.ver === ver && this.spanCache.sel === sel) return this.spanCache.spans;
|
|
11232
11402
|
const spans = /* @__PURE__ */ new Map();
|
|
11233
11403
|
const merged = this.columns.displayed().filter((c) => c.def.merge || c.def.rowSpan);
|
|
11234
11404
|
if (merged.length && !rows.some((n) => n.kind)) {
|
|
11235
11405
|
const data = rows.map((n) => n.data);
|
|
11236
11406
|
for (const c of merged) spans.set(c.id, computeSpans(data, c.def));
|
|
11407
|
+
if (sel) {
|
|
11408
|
+
const n = rows.length, start = new Int32Array(n), end = new Int32Array(n);
|
|
11409
|
+
for (let i = 0; i < n; i++) {
|
|
11410
|
+
const g = sel.index.get(rows[i].id) ?? -1;
|
|
11411
|
+
start[i] = g < 0 ? i : Math.max(0, i - (g - sel.spans.start[g]));
|
|
11412
|
+
end[i] = g < 0 ? i : Math.min(n - 1, i + (sel.spans.end[g] - g));
|
|
11413
|
+
}
|
|
11414
|
+
spans.set(SELECT_COL_ID, { start, end });
|
|
11415
|
+
}
|
|
11237
11416
|
}
|
|
11238
|
-
this.spanCache = { rows, ver, spans };
|
|
11417
|
+
this.spanCache = { rows, ver, sel, spans };
|
|
11239
11418
|
return spans;
|
|
11240
11419
|
}
|
|
11420
|
+
validMergeSelection(v) {
|
|
11421
|
+
if (v === void 0 || v === null || v === "rows") return "rows";
|
|
11422
|
+
if (typeof v === "number" && Number.isInteger(v) && v >= 1) return v;
|
|
11423
|
+
warn("mergeSelection must be 'rows' or a whole number from 1; one checkbox per row is used");
|
|
11424
|
+
return "rows";
|
|
11425
|
+
}
|
|
11426
|
+
setMergeSelection(level) {
|
|
11427
|
+
const next = this.validMergeSelection(level);
|
|
11428
|
+
if (next === this.mergeSel) return;
|
|
11429
|
+
this.mergeSel = next;
|
|
11430
|
+
this.renderer.invalidate({ rows: true });
|
|
11431
|
+
}
|
|
11432
|
+
getMergeSelection() {
|
|
11433
|
+
return this.mergeSel;
|
|
11434
|
+
}
|
|
11435
|
+
/** The displayed merge columns that make the levels of `mergeSelection`, left to right. */
|
|
11436
|
+
mergeLevelColumns() {
|
|
11437
|
+
return this.columns.displayed().filter((c) => c.def.merge && !c.def.rowSpan && !isSystemCol(c.id));
|
|
11438
|
+
}
|
|
11439
|
+
/** Merge level whose blocks are checked together; 0 = one checkbox per row. */
|
|
11440
|
+
mergeSelectionLevel() {
|
|
11441
|
+
if (this.mergeSel === "rows") return 0;
|
|
11442
|
+
if (!this.options.rowSelection) {
|
|
11443
|
+
warn("mergeSelection needs rowSelection 'multiple' or 'single'");
|
|
11444
|
+
return 0;
|
|
11445
|
+
}
|
|
11446
|
+
if (!this.paid) return 0;
|
|
11447
|
+
if (this.rowModel.server || this.pivotCfg || this.options.treeData || this.groupBy.length) {
|
|
11448
|
+
warn("mergeSelection is ignored while rows are grouped, pivoted, shown as a tree or loaded from a datasource: one checkbox per row");
|
|
11449
|
+
return 0;
|
|
11450
|
+
}
|
|
11451
|
+
const m = this.mergeLevelColumns().length;
|
|
11452
|
+
if (!m) return 0;
|
|
11453
|
+
if (this.mergeSel > m) {
|
|
11454
|
+
warn(`mergeSelection: level ${this.mergeSel} asked for, but only ${m} merge column${m > 1 ? "s are" : " is"} shown; level ${m} is used`);
|
|
11455
|
+
return m;
|
|
11456
|
+
}
|
|
11457
|
+
return this.mergeSel;
|
|
11458
|
+
}
|
|
11459
|
+
/** Blocks of the selection level over every filtered, sorted row (not only the page); null for one checkbox per row. */
|
|
11460
|
+
selectionBlocks() {
|
|
11461
|
+
const level = this.mergeSelectionLevel();
|
|
11462
|
+
if (!level) return null;
|
|
11463
|
+
const rows = this.rowModel.dataRows(), disp = this.rowModel.rows();
|
|
11464
|
+
const cols = this.mergeLevelColumns().slice(0, level);
|
|
11465
|
+
const key = `${level}|${this.editVersion}|${cols.map((c2) => c2.id).join("")}`;
|
|
11466
|
+
const c = this.selBlockCache;
|
|
11467
|
+
if (c && c.rows === rows && c.disp === disp && c.key === key) return c.blocks;
|
|
11468
|
+
let blocks = null;
|
|
11469
|
+
if (!disp.some((n) => n.kind)) {
|
|
11470
|
+
const data = rows.map((n) => n.data);
|
|
11471
|
+
blocks = {
|
|
11472
|
+
level,
|
|
11473
|
+
cols,
|
|
11474
|
+
spans: nestedSpans(cols.map((col) => computeSpans(data, col.def)), rows.length),
|
|
11475
|
+
index: new Map(rows.map((n, i) => [n.id, i]))
|
|
11476
|
+
};
|
|
11477
|
+
}
|
|
11478
|
+
this.selBlockCache = { rows, disp, key, blocks };
|
|
11479
|
+
return blocks;
|
|
11480
|
+
}
|
|
11481
|
+
/** Checkbox of the block holding display row i: all, some or none of its selectable rows selected, or 'off' (none selectable). Null without block checkboxes. */
|
|
11482
|
+
blockSelection(i) {
|
|
11483
|
+
const sb = this.selectionBlocks();
|
|
11484
|
+
const node = sb && this.rowModel.getRow(i);
|
|
11485
|
+
const g = node ? sb.index.get(node.id) : void 0;
|
|
11486
|
+
if (!sb || g === void 0) return null;
|
|
11487
|
+
const sel = this.store.state.selected;
|
|
11488
|
+
let c = this.blockStateCache;
|
|
11489
|
+
if (!c || c.sel !== sel || c.blocks !== sb || c.ver !== this.editVersion) c = this.blockStateCache = { sel, blocks: sb, ver: this.editVersion, map: /* @__PURE__ */ new Map() };
|
|
11490
|
+
const a = sb.spans.start[g];
|
|
11491
|
+
let state = c.map.get(a);
|
|
11492
|
+
if (state === void 0) {
|
|
11493
|
+
const rows = this.rowModel.dataRows();
|
|
11494
|
+
let able = 0, on = 0;
|
|
11495
|
+
for (let k = a; k <= sb.spans.end[a]; k++) {
|
|
11496
|
+
if (!this.rowSelectable(rows[k].data)) continue;
|
|
11497
|
+
able++;
|
|
11498
|
+
if (sel.has(rows[k].id)) on++;
|
|
11499
|
+
}
|
|
11500
|
+
state = !able ? "off" : on === 0 ? "none" : on === able ? "all" : "some";
|
|
11501
|
+
c.map.set(a, state);
|
|
11502
|
+
}
|
|
11503
|
+
return state;
|
|
11504
|
+
}
|
|
11505
|
+
/** Selectable row ids from position a to b (inclusive) of the filtered rows. */
|
|
11506
|
+
blockIds(a, b) {
|
|
11507
|
+
const rows = this.rowModel.dataRows(), out = [];
|
|
11508
|
+
for (let k = a; k <= b; k++) if (this.rowSelectable(rows[k].data)) out.push(rows[k].id);
|
|
11509
|
+
return out;
|
|
11510
|
+
}
|
|
11511
|
+
/** selectFromClick with block checkboxes: the clicked row's whole block (Shift: every block from the anchor's). */
|
|
11512
|
+
selectBlockFromClick(sb, index, toggle, range) {
|
|
11513
|
+
const mode = this.options.rowSelection;
|
|
11514
|
+
const g = sb.index.get(this.rowModel.getRow(index).id);
|
|
11515
|
+
if (g === void 0) return;
|
|
11516
|
+
const { start, end } = sb.spans;
|
|
11517
|
+
const a = start[g], b = end[g];
|
|
11518
|
+
const ids = this.blockIds(a, b);
|
|
11519
|
+
if (!ids.length) return;
|
|
11520
|
+
const cur = this.store.state.selected;
|
|
11521
|
+
const anchorId = this.rowModel.dataRows()[a].id;
|
|
11522
|
+
if (mode === "single") {
|
|
11523
|
+
const all2 = ids.every((id) => cur.has(id));
|
|
11524
|
+
this.setSelected(toggle && all2 ? /* @__PURE__ */ new Set() : new Set(ids), anchorId);
|
|
11525
|
+
return;
|
|
11526
|
+
}
|
|
11527
|
+
const ag = this.store.state.anchorId == null ? void 0 : sb.index.get(this.store.state.anchorId);
|
|
11528
|
+
if (range && ag !== void 0) {
|
|
11529
|
+
const next = toggle ? new Set(cur) : /* @__PURE__ */ new Set();
|
|
11530
|
+
for (const id of this.blockIds(Math.min(start[ag], a), Math.max(end[ag], b))) next.add(id);
|
|
11531
|
+
this.setSelected(next);
|
|
11532
|
+
return;
|
|
11533
|
+
}
|
|
11534
|
+
if (toggle) {
|
|
11535
|
+
const next = new Set(cur);
|
|
11536
|
+
const all2 = ids.every((id) => next.has(id));
|
|
11537
|
+
for (const id of ids) all2 ? next.delete(id) : next.add(id);
|
|
11538
|
+
this.setSelected(next, anchorId);
|
|
11539
|
+
return;
|
|
11540
|
+
}
|
|
11541
|
+
this.setSelected(new Set(ids), anchorId);
|
|
11542
|
+
}
|
|
11543
|
+
/**
|
|
11544
|
+
* getSelectedGroups: blocks holding selected rows, in display order, with their merge values and selected rows.
|
|
11545
|
+
* allRows: every row of those blocks that passes the filters, selected or not (getSelectedGroupsAllRows).
|
|
11546
|
+
*/
|
|
11547
|
+
selectedGroups(allRows = false) {
|
|
11548
|
+
const sb = this.selectionBlocks();
|
|
11549
|
+
const sel = this.store.state.selected;
|
|
11550
|
+
if (!sb || !sel.size) return [];
|
|
11551
|
+
const rows = this.rowModel.dataRows(), out = [];
|
|
11552
|
+
for (let a = 0; a < rows.length; a = sb.spans.end[a] + 1) {
|
|
11553
|
+
const picked = [];
|
|
11554
|
+
let any = false;
|
|
11555
|
+
for (let k = a; k <= sb.spans.end[a]; k++) {
|
|
11556
|
+
const on = sel.has(rows[k].id);
|
|
11557
|
+
any || (any = on);
|
|
11558
|
+
if (on || allRows) picked.push(rows[k].data);
|
|
11559
|
+
}
|
|
11560
|
+
if (!any) continue;
|
|
11561
|
+
const values = {};
|
|
11562
|
+
for (const c of sb.cols) values[c.def.field ?? c.id] = getCellValue(c.def, rows[a].data);
|
|
11563
|
+
out.push({ level: sb.level, values, rows: picked });
|
|
11564
|
+
}
|
|
11565
|
+
return out;
|
|
11566
|
+
}
|
|
11241
11567
|
rangeAggregates() {
|
|
11242
11568
|
const all2 = this.allRangeBounds();
|
|
11243
11569
|
if (!all2.length || !this.paid) return null;
|
|
@@ -11673,7 +11999,13 @@ var Grid = class {
|
|
|
11673
11999
|
const mode = this.options.rowSelection;
|
|
11674
12000
|
if (!mode || index < 0 || index >= this.rowModel.getRowCount()) return;
|
|
11675
12001
|
const target = this.rowModel.getRow(index);
|
|
11676
|
-
if (target.kind
|
|
12002
|
+
if (target.kind) return;
|
|
12003
|
+
const blocks = this.selectionBlocks();
|
|
12004
|
+
if (blocks) {
|
|
12005
|
+
this.selectBlockFromClick(blocks, index, toggle, range);
|
|
12006
|
+
return;
|
|
12007
|
+
}
|
|
12008
|
+
if (!this.rowSelectable(target.data)) return;
|
|
11677
12009
|
const r = nextSelection({
|
|
11678
12010
|
selected: this.store.state.selected,
|
|
11679
12011
|
anchorIndex: this.anchorIndex(),
|
|
@@ -11861,6 +12193,12 @@ var Grid = class {
|
|
|
11861
12193
|
validate: guard(() => this.validateAll()),
|
|
11862
12194
|
setDensity: guard((d) => this.setDensity(d)),
|
|
11863
12195
|
getDensity: guard(() => this.density),
|
|
12196
|
+
setSortWithinMerges: guard((on) => this.setSortWithinMerges(on)),
|
|
12197
|
+
getSortWithinMerges: guard(() => this.sortWithin),
|
|
12198
|
+
setMergeSelection: guard((level) => this.setMergeSelection(level)),
|
|
12199
|
+
getMergeSelection: guard(() => this.getMergeSelection()),
|
|
12200
|
+
getSelectedGroups: guard(() => this.selectedGroups()),
|
|
12201
|
+
getSelectedGroupsAllRows: guard(() => this.selectedGroups(true)),
|
|
11864
12202
|
showOverlay: guard((kind) => {
|
|
11865
12203
|
this.forcedOverlay = kind === "loading" || kind === "noRows" ? kind : null;
|
|
11866
12204
|
this.renderer.invalidate();
|