@freegridjs/core 1.9.2 → 1.9.3
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 +17 -1
- package/dist/cdn/freegrid.esm.js +22 -22
- package/dist/cdn/freegrid.min.js +21 -21
- package/dist/index.d.ts +3 -3
- package/dist/index.js +369 -238
- 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 +17 -1
- package/dist/{types-Du3F8mqD.d.ts → types-DloQYCt_.d.ts} +16 -0
- 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,23 @@ function computeSpans(rows, def) {
|
|
|
1650
1882
|
}
|
|
1651
1883
|
return { start, end };
|
|
1652
1884
|
}
|
|
1885
|
+
function mergeBlocks(rows, defs) {
|
|
1886
|
+
const n = rows.length;
|
|
1887
|
+
const block = new Int32Array(n);
|
|
1888
|
+
if (!n || !defs.length) return block;
|
|
1889
|
+
const starts = defs.map((def) => computeSpans(rows, def).start);
|
|
1890
|
+
let b = 0;
|
|
1891
|
+
for (let i = 1; i < n; i++) {
|
|
1892
|
+
for (let k = 0; k < starts.length; k++) {
|
|
1893
|
+
if (starts[k][i] === i) {
|
|
1894
|
+
b++;
|
|
1895
|
+
break;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
block[i] = b;
|
|
1899
|
+
}
|
|
1900
|
+
return block;
|
|
1901
|
+
}
|
|
1653
1902
|
|
|
1654
1903
|
// src/viewport.ts
|
|
1655
1904
|
function rowRange(scrollTop, height, rowHeight, count, overscan = 4) {
|
|
@@ -2166,7 +2415,9 @@ var Renderer = class {
|
|
|
2166
2415
|
const def = c.node.def;
|
|
2167
2416
|
if (def.wrapHeaderText ?? this.grid.options.wrapHeaderText) cell.classList.add("fg-header-wrap");
|
|
2168
2417
|
cell.setAttribute("aria-colindex", String(c.column.index + 1));
|
|
2169
|
-
|
|
2418
|
+
const headAlign = def.align ?? (def.type === "number" ? "right" : "left");
|
|
2419
|
+
if (headAlign === "right") cell.classList.add("fg-align-right");
|
|
2420
|
+
else if (headAlign === "center") cell.classList.add("fg-align-center");
|
|
2170
2421
|
const si = sort.findIndex((s) => s.colId === c.id);
|
|
2171
2422
|
cell.setAttribute("aria-sort", si < 0 ? "none" : sort[si].dir === "asc" ? "ascending" : "descending");
|
|
2172
2423
|
if (isSortable(def)) cell.classList.add("fg-sortable");
|
|
@@ -2271,21 +2522,47 @@ var Renderer = class {
|
|
|
2271
2522
|
else this.paintRow(row.el, node, i, cols, isSel, focusCol, hits, spans, range.first);
|
|
2272
2523
|
}
|
|
2273
2524
|
}
|
|
2274
|
-
if (spans.size) this.stickMergedText(top);
|
|
2525
|
+
if (spans.size) this.stickMergedText(geo.top, geo.ch || Number.MAX_SAFE_INTEGER / 4);
|
|
2275
2526
|
}
|
|
2276
2527
|
/**
|
|
2277
2528
|
* A vertically merged cell taller than the view keeps its text in sight: the text moves down to the top of the
|
|
2278
2529
|
* visible part of the block and stops in the block's last row. Runs on every scroll, also for rows not repainted.
|
|
2279
2530
|
*/
|
|
2280
|
-
stickMergedText(viewTop) {
|
|
2531
|
+
stickMergedText(viewTop, viewHeight) {
|
|
2281
2532
|
for (const r of REGIONS3) {
|
|
2533
|
+
const ends = /* @__PURE__ */ new Map();
|
|
2534
|
+
for (const [i, row] of this.painted[r]) {
|
|
2535
|
+
let end = -1;
|
|
2536
|
+
for (const c of row.el.querySelectorAll(".fg-cell-merged")) end = Math.max(end, Number(c.dataset.mergeEnd));
|
|
2537
|
+
if (end > i) ends.set(i, end);
|
|
2538
|
+
}
|
|
2539
|
+
const z = /* @__PURE__ */ new Map();
|
|
2540
|
+
for (const i of [...ends.keys()].sort((a, b) => b - a)) {
|
|
2541
|
+
let top = 0;
|
|
2542
|
+
for (const [j, zj] of z) if (j > i && j <= ends.get(i)) top = Math.max(top, zj);
|
|
2543
|
+
z.set(i, top + 1);
|
|
2544
|
+
}
|
|
2545
|
+
for (const [i, row] of this.painted[r]) {
|
|
2546
|
+
const want = z.has(i) ? String(z.get(i)) : "";
|
|
2547
|
+
if (row.el.style.zIndex !== want && (want || row.el.querySelector(".fg-cell-merged, .fg-cell-merge-hidden"))) row.el.style.zIndex = want;
|
|
2548
|
+
}
|
|
2282
2549
|
for (const row of this.painted[r].values()) {
|
|
2283
2550
|
for (const c of row.el.querySelectorAll(".fg-cell-merged")) {
|
|
2284
2551
|
const blockTop = Number(c.dataset.mergeTop);
|
|
2285
2552
|
const last = Number(c.dataset.mergeLast);
|
|
2286
2553
|
if (!Number.isFinite(blockTop) || !Number.isFinite(last)) continue;
|
|
2287
|
-
|
|
2554
|
+
let top = Math.min(last, Math.max(0, viewTop - blockTop));
|
|
2555
|
+
let bottom = 0;
|
|
2556
|
+
if (c.classList.contains("fg-cell-merged-middle") || c.classList.contains("fg-cell-merged-bottom")) {
|
|
2557
|
+
const height = Number(c.dataset.mergeHeight);
|
|
2558
|
+
const room = height - last;
|
|
2559
|
+
bottom = Math.max(0, Math.min(height - top - room, blockTop + height - (viewTop + viewHeight)));
|
|
2560
|
+
top = Math.min(top, height - bottom - room);
|
|
2561
|
+
}
|
|
2562
|
+
const shift = `${Math.round(top)}px`;
|
|
2288
2563
|
if (c.style.getPropertyValue("--fg-merge-shift") !== shift) c.style.setProperty("--fg-merge-shift", shift);
|
|
2564
|
+
const shiftBottom = `${Math.round(bottom)}px`;
|
|
2565
|
+
if (c.style.getPropertyValue("--fg-merge-shift-bottom") !== shiftBottom) c.style.setProperty("--fg-merge-shift-bottom", shiftBottom);
|
|
2289
2566
|
}
|
|
2290
2567
|
}
|
|
2291
2568
|
}
|
|
@@ -2369,13 +2646,21 @@ var Renderer = class {
|
|
|
2369
2646
|
cellEl.style.bottom = "auto";
|
|
2370
2647
|
cellEl.dataset.mergeTop = String(this.rowTop(s.start[i]));
|
|
2371
2648
|
cellEl.dataset.mergeLast = String(this.rowTop(s.end[i]) - this.rowTop(s.start[i]));
|
|
2649
|
+
cellEl.dataset.mergeEnd = String(s.end[i]);
|
|
2650
|
+
cellEl.dataset.mergeHeight = String(this.rowTop(s.end[i]) + this.rowH(s.end[i]) - this.rowTop(s.start[i]));
|
|
2651
|
+
cellEl.classList.toggle("fg-cell-merged-middle", col.def.mergeAlign === "middle");
|
|
2652
|
+
cellEl.classList.toggle("fg-cell-merged-bottom", col.def.mergeAlign === "bottom");
|
|
2372
2653
|
drawsMerge = true;
|
|
2373
2654
|
} else cellEl.classList.add("fg-cell-merge-hidden");
|
|
2374
2655
|
}
|
|
2375
2656
|
if (!cellEl.classList.contains("fg-cell-merged") && cellEl.dataset.mergeTop !== void 0) {
|
|
2376
2657
|
delete cellEl.dataset.mergeTop;
|
|
2377
2658
|
delete cellEl.dataset.mergeLast;
|
|
2659
|
+
delete cellEl.dataset.mergeEnd;
|
|
2660
|
+
delete cellEl.dataset.mergeHeight;
|
|
2661
|
+
cellEl.classList.remove("fg-cell-merged-middle", "fg-cell-merged-bottom");
|
|
2378
2662
|
cellEl.style.removeProperty("--fg-merge-shift");
|
|
2663
|
+
cellEl.style.removeProperty("--fg-merge-shift-bottom");
|
|
2379
2664
|
}
|
|
2380
2665
|
if (!isSystemCol(col.id) && bounds.some((b) => col.index >= b.c1 && col.index <= b.c2)) cellEl.classList.add("fg-cell-range");
|
|
2381
2666
|
});
|
|
@@ -2415,7 +2700,8 @@ var Renderer = class {
|
|
|
2415
2700
|
const def = col.def;
|
|
2416
2701
|
const value = getCellValue(def, node.data);
|
|
2417
2702
|
const formatted = getFormatter(def, locale.code)(value);
|
|
2418
|
-
|
|
2703
|
+
const cf = i >= 0 && !node.kind ? this.grid.cellFormat(node.data, col.id, value) : null;
|
|
2704
|
+
cls += ` fg-align-${styleAlign(cf?.style) ?? def.align ?? (def.type === "number" ? "right" : def.type === "boolean" ? "center" : "left")}`;
|
|
2419
2705
|
const params = { value, formatted, row: node.data, rowIndex: i, column: def };
|
|
2420
2706
|
if (def.cellClass) {
|
|
2421
2707
|
try {
|
|
@@ -2430,7 +2716,6 @@ var Renderer = class {
|
|
|
2430
2716
|
if (typeof def.editable === "function" && i >= 0 && !cellEditable(def, node.data, i)) cls += " fg-cell-readonly";
|
|
2431
2717
|
if (i >= 0) cls += this.grid.finder.classFor(i, col.id);
|
|
2432
2718
|
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
2719
|
if (cf?.bar) cls += " fg-cf-bar";
|
|
2435
2720
|
cell.className = cls;
|
|
2436
2721
|
this.applyFormat(cell, cf);
|
|
@@ -3323,6 +3608,8 @@ var en = {
|
|
|
3323
3608
|
style: "Format",
|
|
3324
3609
|
wholeRow: "Whole row",
|
|
3325
3610
|
count: "Count",
|
|
3611
|
+
align: "Align",
|
|
3612
|
+
aligns: { none: "Default", left: "Left", center: "Center", right: "Right" },
|
|
3326
3613
|
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
3614
|
ops: { notBetween: "Not between", notContains: "Does not contain" },
|
|
3328
3615
|
styles: { red: "Light red fill", yellow: "Yellow fill", green: "Green fill", blue: "Blue fill", redText: "Red text", bold: "Bold" }
|
|
@@ -5855,225 +6142,6 @@ ${xref}
|
|
|
5855
6142
|
return out;
|
|
5856
6143
|
}
|
|
5857
6144
|
|
|
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
6145
|
// src/rows/sort.ts
|
|
6078
6146
|
var PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6079
6147
|
function numeric(v) {
|
|
@@ -6118,7 +6186,7 @@ function compareGroupValues(a, b, collator, dir) {
|
|
|
6118
6186
|
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
6119
6187
|
return compareNonBlank(a, b, collator) * dir;
|
|
6120
6188
|
}
|
|
6121
|
-
function sortNodes(nodes, sort, resolve, collator) {
|
|
6189
|
+
function sortNodes(nodes, sort, resolve, collator, blocks) {
|
|
6122
6190
|
const keys = sort.map((s) => ({ def: resolve(s.colId), mul: s.dir === "desc" ? -1 : 1 })).filter((k) => !!k.def);
|
|
6123
6191
|
if (!keys.length) return nodes;
|
|
6124
6192
|
const values = keys.map((k) => nodes.map((n) => {
|
|
@@ -6127,6 +6195,7 @@ function sortNodes(nodes, sort, resolve, collator) {
|
|
|
6127
6195
|
}));
|
|
6128
6196
|
const order = nodes.map((_, i) => i);
|
|
6129
6197
|
order.sort((i, j) => {
|
|
6198
|
+
if (blocks && blocks[i] !== blocks[j]) return blocks[i] - blocks[j];
|
|
6130
6199
|
for (let k = 0; k < keys.length; k++) {
|
|
6131
6200
|
const { def, mul } = keys[k];
|
|
6132
6201
|
const a = values[k][i], b = values[k][j];
|
|
@@ -6721,6 +6790,8 @@ function printModel(grid, opts = {}) {
|
|
|
6721
6790
|
if (s.bold) cell.bold = true;
|
|
6722
6791
|
if (s.italic) cell.italic = true;
|
|
6723
6792
|
if (s.strike) cell.strike = true;
|
|
6793
|
+
const align2 = styleAlign(s);
|
|
6794
|
+
if (align2) cell.align = align2;
|
|
6724
6795
|
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
6796
|
if (cf.icon) cell.icon = { char: cf.icon.char, color: cf.icon.color };
|
|
6726
6797
|
}
|
|
@@ -7037,6 +7108,8 @@ function buildXlsxSheet(grid, opts = {}) {
|
|
|
7037
7108
|
if (color) style.color = color;
|
|
7038
7109
|
if (fill) style.fill = fill;
|
|
7039
7110
|
if (cf.style.bold) style.bold = true;
|
|
7111
|
+
const align = styleAlign(cf.style);
|
|
7112
|
+
if (align) style.align = align;
|
|
7040
7113
|
}
|
|
7041
7114
|
if (!Object.keys(style).length) return cell;
|
|
7042
7115
|
return cell !== null && typeof cell === "object" && !(cell instanceof Date) ? { ...cell, ...style } : { v: cell, ...style };
|
|
@@ -7669,7 +7742,7 @@ var PAID_FEATURES = {
|
|
|
7669
7742
|
};
|
|
7670
7743
|
|
|
7671
7744
|
// src/version.ts
|
|
7672
|
-
var version = "1.9.
|
|
7745
|
+
var version = "1.9.3";
|
|
7673
7746
|
|
|
7674
7747
|
// src/license.ts
|
|
7675
7748
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -8242,7 +8315,9 @@ var RowModel = class {
|
|
|
8242
8315
|
this.stats.filterRuns++;
|
|
8243
8316
|
}
|
|
8244
8317
|
if (sortChanged) {
|
|
8245
|
-
|
|
8318
|
+
const blockDefs = input.sort.length ? this.deps.sortBlocks?.(input.sort) : null;
|
|
8319
|
+
const blocks = blockDefs?.length ? mergeBlocks(this.filtered.map((n) => n.data), blockDefs) : void 0;
|
|
8320
|
+
this.sorted = sortNodes(this.filtered, input.sort, (id) => this.deps.columnDef(id), collator, blocks);
|
|
8246
8321
|
this.stats.sortRuns++;
|
|
8247
8322
|
}
|
|
8248
8323
|
if (displayChanged) this.full = d ? this.buildDisplay(d, input.sort, collator) : this.sorted;
|
|
@@ -9259,7 +9334,7 @@ var FormatDialog = class {
|
|
|
9259
9334
|
const node = grid.columns.getNode(colId);
|
|
9260
9335
|
if (!node || node.children || isSystemCol(colId)) return;
|
|
9261
9336
|
const loc = grid.store.state.locale;
|
|
9262
|
-
const cf = loc.cf;
|
|
9337
|
+
const cf = { ...en_default.cf, ...loc.cf, aligns: { ...en_default.cf.aligns, ...loc.cf?.aligns } };
|
|
9263
9338
|
const label = node.def.header ?? node.def.field ?? colId;
|
|
9264
9339
|
const popup = el("div", `fg-menu fg-cf-dialog fg-theme-${grid.store.state.theme}`, { role: "dialog", "aria-label": `${cf.title}: ${label}` });
|
|
9265
9340
|
grid.applyFontTo(popup);
|
|
@@ -9301,9 +9376,14 @@ var FormatDialog = class {
|
|
|
9301
9376
|
if (r2.style?.background) sample.style.background = r2.style.background;
|
|
9302
9377
|
if (r2.style?.color) sample.style.color = r2.style.color;
|
|
9303
9378
|
if (r2.style?.bold) sample.style.fontWeight = "700";
|
|
9379
|
+
const align = styleAlign(r2.style);
|
|
9380
|
+
if (align && align !== "center") {
|
|
9381
|
+
sample.style.justifyItems = align === "left" ? "start" : "end";
|
|
9382
|
+
sample.style.padding = "0 3px";
|
|
9383
|
+
}
|
|
9304
9384
|
if (r2.type === "colorScale") sample.style.background = `linear-gradient(to right, ${(r2.colors ?? ["#f8696b", "#ffeb84", "#63be7b"]).join(",")})`;
|
|
9305
9385
|
const text2 = el("span", "fg-cf-rule-text");
|
|
9306
|
-
text2.textContent = `${describe(r2)}${r2.row ? ` \xB7 ${cf.wholeRow}` : ""}`;
|
|
9386
|
+
text2.textContent = `${describe(r2)}${r2.row ? ` \xB7 ${cf.wholeRow}` : ""}${align ? ` \xB7 ${cf.align}: ${cf.aligns[align]}` : ""}`;
|
|
9307
9387
|
text2.title = text2.textContent;
|
|
9308
9388
|
const del = el("button", "fg-chooser-close", { type: "button", "aria-label": `${loc.close}: ${text2.textContent}` });
|
|
9309
9389
|
del.textContent = "\xD7";
|
|
@@ -9355,10 +9435,17 @@ var FormatDialog = class {
|
|
|
9355
9435
|
});
|
|
9356
9436
|
swatches.append(b);
|
|
9357
9437
|
});
|
|
9438
|
+
const alignSel = el("select", "fg-filter-op fg-cf-wide fg-cf-align", { "aria-label": cf.align, title: cf.align });
|
|
9439
|
+
for (const a of ["", "left", "center", "right"]) {
|
|
9440
|
+
const o = el("option");
|
|
9441
|
+
o.value = a;
|
|
9442
|
+
o.textContent = a ? `${cf.align}: ${cf.aligns[a]}` : `${cf.align}: ${cf.aligns.none}`;
|
|
9443
|
+
alignSel.append(o);
|
|
9444
|
+
}
|
|
9358
9445
|
const rowLabel = el("label", "fg-cf-wide fg-cf-row");
|
|
9359
9446
|
const rowBox = el("input", "", { type: "checkbox" });
|
|
9360
9447
|
rowLabel.append(rowBox, ` ${cf.wholeRow}`);
|
|
9361
|
-
form.append(typeSel, opSel, v1, v2, formula, rank, pctLabel, swatches, rowLabel);
|
|
9448
|
+
form.append(typeSel, opSel, v1, v2, formula, rank, pctLabel, swatches, alignSel, rowLabel);
|
|
9362
9449
|
const sync = () => {
|
|
9363
9450
|
const t = typeSel.value;
|
|
9364
9451
|
const op = opSel.value;
|
|
@@ -9367,7 +9454,7 @@ var FormatDialog = class {
|
|
|
9367
9454
|
v2.hidden = t !== "value" || op !== "between" && op !== "notBetween";
|
|
9368
9455
|
formula.hidden = t !== "formula";
|
|
9369
9456
|
rank.hidden = pctLabel.hidden = t !== "top" && t !== "bottom";
|
|
9370
|
-
swatches.hidden = SCALES.includes(t);
|
|
9457
|
+
swatches.hidden = alignSel.hidden = SCALES.includes(t);
|
|
9371
9458
|
rowLabel.hidden = t !== "value" && t !== "formula";
|
|
9372
9459
|
};
|
|
9373
9460
|
typeSel.addEventListener("change", sync);
|
|
@@ -9395,7 +9482,11 @@ var FormatDialog = class {
|
|
|
9395
9482
|
rule.rank = Math.max(1, Math.floor(Number(rank.value) || 10));
|
|
9396
9483
|
if (pct.checked) rule.percent = true;
|
|
9397
9484
|
}
|
|
9398
|
-
if (!SCALES.includes(type))
|
|
9485
|
+
if (!SCALES.includes(type)) {
|
|
9486
|
+
rule.style = { ...CF_PRESETS[preset].style };
|
|
9487
|
+
const a = styleAlign({ align: alignSel.value });
|
|
9488
|
+
if (a) rule.style.align = a;
|
|
9489
|
+
}
|
|
9399
9490
|
if (rowBox.checked && (type === "value" || type === "formula")) rule.row = true;
|
|
9400
9491
|
return rule;
|
|
9401
9492
|
};
|
|
@@ -9424,7 +9515,12 @@ var FormatDialog = class {
|
|
|
9424
9515
|
const r = grid.renderer.root.getBoundingClientRect();
|
|
9425
9516
|
const w = popup.offsetWidth || 340;
|
|
9426
9517
|
popup.style.left = `${Math.max(4, Math.min(r.left + 24, (window.innerWidth || 1024) - w - 4))}px`;
|
|
9427
|
-
|
|
9518
|
+
const vh = window.innerHeight || 768;
|
|
9519
|
+
const h = popup.offsetHeight || 0;
|
|
9520
|
+
const top = Math.max(4, Math.min(r.top + 24, vh - h - 4));
|
|
9521
|
+
popup.style.top = `${top}px`;
|
|
9522
|
+
popup.style.maxHeight = `${Math.max(160, vh - top - 8)}px`;
|
|
9523
|
+
popup.style.overflowY = "auto";
|
|
9428
9524
|
this.popup = popup;
|
|
9429
9525
|
typeSel.focus();
|
|
9430
9526
|
const onDown = (e) => {
|
|
@@ -9941,6 +10037,9 @@ var Grid = class {
|
|
|
9941
10037
|
}
|
|
9942
10038
|
};
|
|
9943
10039
|
this.density = "normal";
|
|
10040
|
+
// ── merge, aggregates, fill, pinned rows ────────────────────
|
|
10041
|
+
/** `sortWithinMerges` is on. */
|
|
10042
|
+
this.sortWithin = false;
|
|
9944
10043
|
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
9945
10044
|
this.aggMemo = null;
|
|
9946
10045
|
/** Bumped on every selection change (group row checkboxes depend on their rows). */
|
|
@@ -9987,8 +10086,11 @@ var Grid = class {
|
|
|
9987
10086
|
quickColumns: () => this.columns.displayed().filter((c) => !isSystemCol(c.id)).map((c) => c.def),
|
|
9988
10087
|
format: (def, v) => getFormatter(def, this.store.state.locale.code)(v),
|
|
9989
10088
|
collator: () => this.collator(),
|
|
9990
|
-
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}
|
|
10089
|
+
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}`,
|
|
10090
|
+
sortBlocks: (sort) => this.sortBlocks(sort)
|
|
9991
10091
|
});
|
|
10092
|
+
this.sortWithin = !!options.sortWithinMerges;
|
|
10093
|
+
if (this.sortWithin && this.rowModel.server) warn("sortWithinMerges is ignored with a datasource: the server sorts the rows");
|
|
9992
10094
|
if (options.groupBy?.length && this.allow(this.rowModel.server ? "serverGroups" : "grouping")) this.groupBy = this.validGroupBy(options.groupBy);
|
|
9993
10095
|
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
10096
|
this.defaultExpanded = options.treeData || this.paid ? options.groupDefaultExpanded ?? 0 : 0;
|
|
@@ -10333,8 +10435,9 @@ var Grid = class {
|
|
|
10333
10435
|
const st = this.store.state;
|
|
10334
10436
|
if (this.pivotCfg || this.options.treeData || st.quickFilter || st.advancedFilter || this.groupBy.length) return true;
|
|
10335
10437
|
const ids = [...st.sort.map((x) => x.colId), ...Object.keys(st.filters)];
|
|
10336
|
-
|
|
10337
|
-
|
|
10438
|
+
const defs = ids.map((id) => this.columns.getNode(id)?.def);
|
|
10439
|
+
if (st.sort.length) defs.push(...this.sortBlocks(st.sort) ?? []);
|
|
10440
|
+
return defs.some((def) => {
|
|
10338
10441
|
if (!def) return false;
|
|
10339
10442
|
if (def.valueGetter || def.formulas || def.formula) return true;
|
|
10340
10443
|
return def.field != null && fields.has(def.field);
|
|
@@ -10651,6 +10754,7 @@ var Grid = class {
|
|
|
10651
10754
|
this.groupBy = next;
|
|
10652
10755
|
this.page = 0;
|
|
10653
10756
|
this.groupPanel?.refresh();
|
|
10757
|
+
if (this.sortWithin) this.rowModel.invalidate();
|
|
10654
10758
|
this.displayChanged();
|
|
10655
10759
|
this.events.emit("groupChange", { groupBy: [...next] });
|
|
10656
10760
|
}
|
|
@@ -11221,7 +11325,32 @@ var Grid = class {
|
|
|
11221
11325
|
});
|
|
11222
11326
|
return out;
|
|
11223
11327
|
}
|
|
11224
|
-
|
|
11328
|
+
/**
|
|
11329
|
+
* Merge columns the client sort must stay inside: the displayed `merge` columns that are
|
|
11330
|
+
* not sort keys. Nothing (a plain sort) when the option is off, merging is not allowed,
|
|
11331
|
+
* or rows are grouped, pivoted or shown as a tree.
|
|
11332
|
+
*/
|
|
11333
|
+
sortBlocks(sort) {
|
|
11334
|
+
if (!this.sortWithin || !this.paid) return null;
|
|
11335
|
+
if (this.pivotCfg || this.options.treeData || this.groupBy.length) {
|
|
11336
|
+
warn("sortWithinMerges is ignored while rows are grouped, pivoted or shown as a tree");
|
|
11337
|
+
return null;
|
|
11338
|
+
}
|
|
11339
|
+
const keys = new Set(sort.map((s) => s.colId));
|
|
11340
|
+
return this.columns.displayed().filter((c) => c.def.merge && !c.def.rowSpan && !isSystemCol(c.id) && !keys.has(c.id)).map((c) => c.def);
|
|
11341
|
+
}
|
|
11342
|
+
setSortWithinMerges(on) {
|
|
11343
|
+
const next = !!on;
|
|
11344
|
+
if (next === this.sortWithin) return;
|
|
11345
|
+
this.sortWithin = next;
|
|
11346
|
+
if (this.rowModel.server) {
|
|
11347
|
+
if (next) warn("sortWithinMerges is ignored with a datasource: the server sorts the rows");
|
|
11348
|
+
return;
|
|
11349
|
+
}
|
|
11350
|
+
this.rowModel.invalidate();
|
|
11351
|
+
this.updateRows();
|
|
11352
|
+
this.renderer.invalidate({ rows: true });
|
|
11353
|
+
}
|
|
11225
11354
|
/** Merge blocks for every `merge` column over the displayed rows (memoized). */
|
|
11226
11355
|
mergeSpans() {
|
|
11227
11356
|
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan)) return NO_SPANS;
|
|
@@ -11861,6 +11990,8 @@ var Grid = class {
|
|
|
11861
11990
|
validate: guard(() => this.validateAll()),
|
|
11862
11991
|
setDensity: guard((d) => this.setDensity(d)),
|
|
11863
11992
|
getDensity: guard(() => this.density),
|
|
11993
|
+
setSortWithinMerges: guard((on) => this.setSortWithinMerges(on)),
|
|
11994
|
+
getSortWithinMerges: guard(() => this.sortWithin),
|
|
11864
11995
|
showOverlay: guard((kind) => {
|
|
11865
11996
|
this.forcedOverlay = kind === "loading" || kind === "noRows" ? kind : null;
|
|
11866
11997
|
this.renderer.invalidate();
|