@freegridjs/core 1.9.1 → 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 +541 -525
- 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 +391 -235
- 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 +541 -525
- 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,6 +2522,50 @@ var Renderer = class {
|
|
|
2271
2522
|
else this.paintRow(row.el, node, i, cols, isSel, focusCol, hits, spans, range.first);
|
|
2272
2523
|
}
|
|
2273
2524
|
}
|
|
2525
|
+
if (spans.size) this.stickMergedText(geo.top, geo.ch || Number.MAX_SAFE_INTEGER / 4);
|
|
2526
|
+
}
|
|
2527
|
+
/**
|
|
2528
|
+
* A vertically merged cell taller than the view keeps its text in sight: the text moves down to the top of the
|
|
2529
|
+
* visible part of the block and stops in the block's last row. Runs on every scroll, also for rows not repainted.
|
|
2530
|
+
*/
|
|
2531
|
+
stickMergedText(viewTop, viewHeight) {
|
|
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
|
+
}
|
|
2549
|
+
for (const row of this.painted[r].values()) {
|
|
2550
|
+
for (const c of row.el.querySelectorAll(".fg-cell-merged")) {
|
|
2551
|
+
const blockTop = Number(c.dataset.mergeTop);
|
|
2552
|
+
const last = Number(c.dataset.mergeLast);
|
|
2553
|
+
if (!Number.isFinite(blockTop) || !Number.isFinite(last)) continue;
|
|
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`;
|
|
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);
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2274
2569
|
}
|
|
2275
2570
|
/**
|
|
2276
2571
|
* Same row, only the column window moved: drop the cells that left and paint the ones that came in.
|
|
@@ -2349,9 +2644,24 @@ var Renderer = class {
|
|
|
2349
2644
|
cellEl.style.top = `${-(this.rowTop(i) - this.rowTop(s.start[i]))}px`;
|
|
2350
2645
|
cellEl.style.height = `${this.rowTop(s.end[i]) + this.rowH(s.end[i]) - this.rowTop(s.start[i])}px`;
|
|
2351
2646
|
cellEl.style.bottom = "auto";
|
|
2647
|
+
cellEl.dataset.mergeTop = String(this.rowTop(s.start[i]));
|
|
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");
|
|
2352
2653
|
drawsMerge = true;
|
|
2353
2654
|
} else cellEl.classList.add("fg-cell-merge-hidden");
|
|
2354
2655
|
}
|
|
2656
|
+
if (!cellEl.classList.contains("fg-cell-merged") && cellEl.dataset.mergeTop !== void 0) {
|
|
2657
|
+
delete cellEl.dataset.mergeTop;
|
|
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");
|
|
2662
|
+
cellEl.style.removeProperty("--fg-merge-shift");
|
|
2663
|
+
cellEl.style.removeProperty("--fg-merge-shift-bottom");
|
|
2664
|
+
}
|
|
2355
2665
|
if (!isSystemCol(col.id) && bounds.some((b) => col.index >= b.c1 && col.index <= b.c2)) cellEl.classList.add("fg-cell-range");
|
|
2356
2666
|
});
|
|
2357
2667
|
rowEl.style.zIndex = drawsMerge ? "1" : "";
|
|
@@ -2390,7 +2700,8 @@ var Renderer = class {
|
|
|
2390
2700
|
const def = col.def;
|
|
2391
2701
|
const value = getCellValue(def, node.data);
|
|
2392
2702
|
const formatted = getFormatter(def, locale.code)(value);
|
|
2393
|
-
|
|
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")}`;
|
|
2394
2705
|
const params = { value, formatted, row: node.data, rowIndex: i, column: def };
|
|
2395
2706
|
if (def.cellClass) {
|
|
2396
2707
|
try {
|
|
@@ -2405,7 +2716,6 @@ var Renderer = class {
|
|
|
2405
2716
|
if (typeof def.editable === "function" && i >= 0 && !cellEditable(def, node.data, i)) cls += " fg-cell-readonly";
|
|
2406
2717
|
if (i >= 0) cls += this.grid.finder.classFor(i, col.id);
|
|
2407
2718
|
cls += this.grid.flashClass(node.id, col.id);
|
|
2408
|
-
const cf = i >= 0 && !node.kind ? this.grid.cellFormat(node.data, col.id, value) : null;
|
|
2409
2719
|
if (cf?.bar) cls += " fg-cf-bar";
|
|
2410
2720
|
cell.className = cls;
|
|
2411
2721
|
this.applyFormat(cell, cf);
|
|
@@ -3298,6 +3608,8 @@ var en = {
|
|
|
3298
3608
|
style: "Format",
|
|
3299
3609
|
wholeRow: "Whole row",
|
|
3300
3610
|
count: "Count",
|
|
3611
|
+
align: "Align",
|
|
3612
|
+
aligns: { none: "Default", left: "Left", center: "Center", right: "Right" },
|
|
3301
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" },
|
|
3302
3614
|
ops: { notBetween: "Not between", notContains: "Does not contain" },
|
|
3303
3615
|
styles: { red: "Light red fill", yellow: "Yellow fill", green: "Green fill", blue: "Blue fill", redText: "Red text", bold: "Bold" }
|
|
@@ -5830,225 +6142,6 @@ ${xref}
|
|
|
5830
6142
|
return out;
|
|
5831
6143
|
}
|
|
5832
6144
|
|
|
5833
|
-
// src/conditional.ts
|
|
5834
|
-
var NUMERIC = (v) => {
|
|
5835
|
-
if (typeof v === "number") return Number.isFinite(v) ? v : null;
|
|
5836
|
-
if (typeof v === "string" && v.trim() !== "" && Number.isFinite(Number(v))) return Number(v);
|
|
5837
|
-
if (v instanceof Date) return v.getTime();
|
|
5838
|
-
return null;
|
|
5839
|
-
};
|
|
5840
|
-
var STAT_TYPES = /* @__PURE__ */ new Set(["top", "bottom", "aboveAverage", "belowAverage", "duplicate", "unique", "dataBar", "colorScale", "iconSet"]);
|
|
5841
|
-
var ICON_SETS = {
|
|
5842
|
-
// high, middle, low
|
|
5843
|
-
arrows: [{ char: "\u25B2", color: "#16a34a", label: "up" }, { char: "\u25B6", color: "#d97706", label: "level" }, { char: "\u25BC", color: "#dc2626", label: "down" }],
|
|
5844
|
-
traffic: [{ char: "\u25CF", color: "#16a34a", label: "good" }, { char: "\u25CF", color: "#f59e0b", label: "fair" }, { char: "\u25CF", color: "#dc2626", label: "poor" }],
|
|
5845
|
-
signs: [{ char: "\u2714", color: "#16a34a", label: "good" }, { char: "!", color: "#d97706", label: "fair" }, { char: "\u2716", color: "#dc2626", label: "poor" }]
|
|
5846
|
-
};
|
|
5847
|
-
function hex(c) {
|
|
5848
|
-
const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(c.trim());
|
|
5849
|
-
if (!m) return null;
|
|
5850
|
-
const h = m[1].length === 3 ? [...m[1]].map((x) => x + x).join("") : m[1];
|
|
5851
|
-
return [0, 2, 4].map((i) => parseInt(h.slice(i, i + 2), 16));
|
|
5852
|
-
}
|
|
5853
|
-
function mixColor(a, b, t) {
|
|
5854
|
-
const x = hex(a), y = hex(b);
|
|
5855
|
-
if (!x || !y) return t < 0.5 ? a : b;
|
|
5856
|
-
const c = x.map((v, i) => Math.round(v + (y[i] - v) * Math.max(0, Math.min(1, t))));
|
|
5857
|
-
return `#${c.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
5858
|
-
}
|
|
5859
|
-
function textOf(v) {
|
|
5860
|
-
return v == null ? "" : v instanceof Date ? v.toISOString() : String(v);
|
|
5861
|
-
}
|
|
5862
|
-
function valueMatches(rule, value) {
|
|
5863
|
-
const op = rule.op ?? "eq";
|
|
5864
|
-
if (op === "blank") return isBlank(value);
|
|
5865
|
-
if (op === "notBlank") return !isBlank(value);
|
|
5866
|
-
if (op === "contains" || op === "notContains" || op === "startsWith" || op === "endsWith") {
|
|
5867
|
-
const s2 = textOf(value).toLowerCase(), q = textOf(rule.value).toLowerCase();
|
|
5868
|
-
const hit = op === "startsWith" ? s2.startsWith(q) : op === "endsWith" ? s2.endsWith(q) : s2.includes(q);
|
|
5869
|
-
return op === "notContains" ? !hit : hit;
|
|
5870
|
-
}
|
|
5871
|
-
if (isBlank(value)) return op === "ne";
|
|
5872
|
-
const a = NUMERIC(value), b = NUMERIC(rule.value), c = NUMERIC(rule.valueTo);
|
|
5873
|
-
if (a !== null && b !== null) {
|
|
5874
|
-
switch (op) {
|
|
5875
|
-
case "eq":
|
|
5876
|
-
return a === b;
|
|
5877
|
-
case "ne":
|
|
5878
|
-
return a !== b;
|
|
5879
|
-
case "gt":
|
|
5880
|
-
return a > b;
|
|
5881
|
-
case "gte":
|
|
5882
|
-
return a >= b;
|
|
5883
|
-
case "lt":
|
|
5884
|
-
return a < b;
|
|
5885
|
-
case "lte":
|
|
5886
|
-
return a <= b;
|
|
5887
|
-
case "between":
|
|
5888
|
-
case "notBetween": {
|
|
5889
|
-
if (c === null) return false;
|
|
5890
|
-
const inside = a >= Math.min(b, c) && a <= Math.max(b, c);
|
|
5891
|
-
return op === "between" ? inside : !inside;
|
|
5892
|
-
}
|
|
5893
|
-
}
|
|
5894
|
-
}
|
|
5895
|
-
const s = textOf(value).toLowerCase(), t = textOf(rule.value).toLowerCase(), u = textOf(rule.valueTo).toLowerCase();
|
|
5896
|
-
switch (op) {
|
|
5897
|
-
case "eq":
|
|
5898
|
-
return s === t;
|
|
5899
|
-
case "ne":
|
|
5900
|
-
return s !== t;
|
|
5901
|
-
case "gt":
|
|
5902
|
-
return s > t;
|
|
5903
|
-
case "gte":
|
|
5904
|
-
return s >= t;
|
|
5905
|
-
case "lt":
|
|
5906
|
-
return s < t;
|
|
5907
|
-
case "lte":
|
|
5908
|
-
return s <= t;
|
|
5909
|
-
case "between":
|
|
5910
|
-
return s >= t && s <= u;
|
|
5911
|
-
case "notBetween":
|
|
5912
|
-
return s < t || s > u;
|
|
5913
|
-
}
|
|
5914
|
-
return false;
|
|
5915
|
-
}
|
|
5916
|
-
var ConditionalFormatter = class {
|
|
5917
|
-
constructor(deps) {
|
|
5918
|
-
this.deps = deps;
|
|
5919
|
-
this.stats = /* @__PURE__ */ new Map();
|
|
5920
|
-
this.statsKey = [];
|
|
5921
|
-
this.rowCache = /* @__PURE__ */ new WeakMap();
|
|
5922
|
-
}
|
|
5923
|
-
/** True when any rule looks at other rows (an update to one row can restyle others). */
|
|
5924
|
-
dependsOnOtherRows() {
|
|
5925
|
-
return this.deps.rules().some((r) => r && (STAT_TYPES.has(r.type ?? "value") || r.type === "formula"));
|
|
5926
|
-
}
|
|
5927
|
-
fresh() {
|
|
5928
|
-
const key = [this.deps.rows(), this.deps.version(), this.deps.rules()];
|
|
5929
|
-
if (key.every((k, i) => k === this.statsKey[i])) return;
|
|
5930
|
-
this.statsKey = key;
|
|
5931
|
-
this.stats.clear();
|
|
5932
|
-
this.rowCache = /* @__PURE__ */ new WeakMap();
|
|
5933
|
-
}
|
|
5934
|
-
statsOf(colId) {
|
|
5935
|
-
let s = this.stats.get(colId);
|
|
5936
|
-
if (!s) {
|
|
5937
|
-
const nums = [];
|
|
5938
|
-
const counts = /* @__PURE__ */ new Map();
|
|
5939
|
-
for (const row of this.deps.rows()) {
|
|
5940
|
-
const v = this.deps.value(colId, row);
|
|
5941
|
-
const n = NUMERIC(v);
|
|
5942
|
-
if (n !== null) nums.push(n);
|
|
5943
|
-
if (!isBlank(v)) {
|
|
5944
|
-
const k = setKey(v);
|
|
5945
|
-
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
5946
|
-
}
|
|
5947
|
-
}
|
|
5948
|
-
nums.sort((a, b) => a - b);
|
|
5949
|
-
const sum = nums.reduce((a, b) => a + b, 0);
|
|
5950
|
-
s = { min: nums[0] ?? 0, max: nums[nums.length - 1] ?? 0, avg: nums.length ? sum / nums.length : 0, sorted: nums, counts };
|
|
5951
|
-
this.stats.set(colId, s);
|
|
5952
|
-
}
|
|
5953
|
-
return s;
|
|
5954
|
-
}
|
|
5955
|
-
test(rule, colId, value, row) {
|
|
5956
|
-
const type = rule.type ?? "value";
|
|
5957
|
-
switch (type) {
|
|
5958
|
-
case "value":
|
|
5959
|
-
return valueMatches(rule, value);
|
|
5960
|
-
case "formula": {
|
|
5961
|
-
if (typeof rule.formula !== "string") return false;
|
|
5962
|
-
const r = this.deps.formula(rule.formula.startsWith("=") ? rule.formula : `=${rule.formula}`, row);
|
|
5963
|
-
return r === true || typeof r === "number" && r !== 0;
|
|
5964
|
-
}
|
|
5965
|
-
case "top":
|
|
5966
|
-
case "bottom": {
|
|
5967
|
-
const n = NUMERIC(value);
|
|
5968
|
-
if (n === null) return false;
|
|
5969
|
-
const s = this.statsOf(colId);
|
|
5970
|
-
if (!s.sorted.length) return false;
|
|
5971
|
-
const k = Math.max(1, rule.percent ? Math.ceil(s.sorted.length * (rule.rank ?? 10) / 100) : Math.floor(rule.rank ?? 10));
|
|
5972
|
-
return type === "top" ? n >= s.sorted[Math.max(0, s.sorted.length - k)] : n <= s.sorted[Math.min(s.sorted.length - 1, k - 1)];
|
|
5973
|
-
}
|
|
5974
|
-
case "aboveAverage":
|
|
5975
|
-
case "belowAverage": {
|
|
5976
|
-
const n = NUMERIC(value);
|
|
5977
|
-
if (n === null) return false;
|
|
5978
|
-
const avg = this.statsOf(colId).avg;
|
|
5979
|
-
return type === "aboveAverage" ? n > avg : n < avg;
|
|
5980
|
-
}
|
|
5981
|
-
case "duplicate":
|
|
5982
|
-
case "unique": {
|
|
5983
|
-
if (isBlank(value)) return false;
|
|
5984
|
-
const c = this.statsOf(colId).counts.get(setKey(value)) ?? 0;
|
|
5985
|
-
return type === "duplicate" ? c > 1 : c === 1;
|
|
5986
|
-
}
|
|
5987
|
-
}
|
|
5988
|
-
return false;
|
|
5989
|
-
}
|
|
5990
|
-
/** Style from rules with `row: true`, for every cell of the row. */
|
|
5991
|
-
rowStyle(row) {
|
|
5992
|
-
if (this.rowCache.has(row)) return this.rowCache.get(row);
|
|
5993
|
-
let style = null;
|
|
5994
|
-
for (const rule of this.deps.rules()) {
|
|
5995
|
-
if (!rule?.row || !Array.isArray(rule.columns) || !rule.columns.length) continue;
|
|
5996
|
-
const colId = rule.columns[0];
|
|
5997
|
-
if (this.test(rule, colId, this.deps.value(colId, row), row)) {
|
|
5998
|
-
style = { ...style ?? {}, ...rule.style };
|
|
5999
|
-
if (rule.stopIfTrue) break;
|
|
6000
|
-
}
|
|
6001
|
-
}
|
|
6002
|
-
this.rowCache.set(row, style);
|
|
6003
|
-
return style;
|
|
6004
|
-
}
|
|
6005
|
-
/** Format of one cell; null when no rule applies. */
|
|
6006
|
-
cell(colId, row, value) {
|
|
6007
|
-
const rules = this.deps.rules();
|
|
6008
|
-
if (!rules.length) return null;
|
|
6009
|
-
this.fresh();
|
|
6010
|
-
let out = null;
|
|
6011
|
-
const rs = this.rowStyle(row);
|
|
6012
|
-
if (rs) out = { style: { ...rs } };
|
|
6013
|
-
for (const rule of rules) {
|
|
6014
|
-
if (!rule || rule.row || !Array.isArray(rule.columns) || !rule.columns.includes(colId)) continue;
|
|
6015
|
-
const type = rule.type ?? "value";
|
|
6016
|
-
if (type === "dataBar" || type === "colorScale" || type === "iconSet") {
|
|
6017
|
-
const n = NUMERIC(value);
|
|
6018
|
-
if (n === null) continue;
|
|
6019
|
-
const s = this.statsOf(colId);
|
|
6020
|
-
const lo = typeof rule.min === "number" ? rule.min : s.min;
|
|
6021
|
-
const hi = typeof rule.max === "number" ? rule.max : s.max;
|
|
6022
|
-
const t = hi === lo ? 1 : Math.max(0, Math.min(1, (n - lo) / (hi - lo)));
|
|
6023
|
-
out ?? (out = { style: {} });
|
|
6024
|
-
if (type === "dataBar") {
|
|
6025
|
-
const zero = lo < 0 && hi > 0 ? -lo / (hi - lo) : lo >= 0 ? 0 : 1;
|
|
6026
|
-
const at = hi === lo ? 1 : t;
|
|
6027
|
-
out.bar = { from: Math.min(zero, at), to: Math.max(zero, at), color: rule.color || "var(--fg-accent)" };
|
|
6028
|
-
} else if (type === "colorScale") {
|
|
6029
|
-
const cs = Array.isArray(rule.colors) && rule.colors.length >= 2 ? rule.colors : ["#f8696b", "#ffeb84", "#63be7b"];
|
|
6030
|
-
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);
|
|
6031
|
-
out.style = { ...out.style, background: bg };
|
|
6032
|
-
} else {
|
|
6033
|
-
const set = ICON_SETS[rule.icons ?? "arrows"] ?? ICON_SETS.arrows;
|
|
6034
|
-
out.icon = t >= 2 / 3 ? set[0] : t >= 1 / 3 ? set[1] : set[2];
|
|
6035
|
-
}
|
|
6036
|
-
continue;
|
|
6037
|
-
}
|
|
6038
|
-
if (this.test(rule, colId, value, row)) {
|
|
6039
|
-
out ?? (out = { style: {} });
|
|
6040
|
-
out.style = { ...out.style, ...rule.style };
|
|
6041
|
-
if (rule.stopIfTrue) break;
|
|
6042
|
-
}
|
|
6043
|
-
}
|
|
6044
|
-
return out;
|
|
6045
|
-
}
|
|
6046
|
-
};
|
|
6047
|
-
function argbOf(c) {
|
|
6048
|
-
const x = c ? hex(c) : null;
|
|
6049
|
-
return x ? `FF${x.map((v) => v.toString(16).padStart(2, "0")).join("").toUpperCase()}` : void 0;
|
|
6050
|
-
}
|
|
6051
|
-
|
|
6052
6145
|
// src/rows/sort.ts
|
|
6053
6146
|
var PLAIN_NUMBER = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i;
|
|
6054
6147
|
function numeric(v) {
|
|
@@ -6093,7 +6186,7 @@ function compareGroupValues(a, b, collator, dir) {
|
|
|
6093
6186
|
if (ba || bb) return ba === bb ? 0 : ba ? 1 : -1;
|
|
6094
6187
|
return compareNonBlank(a, b, collator) * dir;
|
|
6095
6188
|
}
|
|
6096
|
-
function sortNodes(nodes, sort, resolve, collator) {
|
|
6189
|
+
function sortNodes(nodes, sort, resolve, collator, blocks) {
|
|
6097
6190
|
const keys = sort.map((s) => ({ def: resolve(s.colId), mul: s.dir === "desc" ? -1 : 1 })).filter((k) => !!k.def);
|
|
6098
6191
|
if (!keys.length) return nodes;
|
|
6099
6192
|
const values = keys.map((k) => nodes.map((n) => {
|
|
@@ -6102,6 +6195,7 @@ function sortNodes(nodes, sort, resolve, collator) {
|
|
|
6102
6195
|
}));
|
|
6103
6196
|
const order = nodes.map((_, i) => i);
|
|
6104
6197
|
order.sort((i, j) => {
|
|
6198
|
+
if (blocks && blocks[i] !== blocks[j]) return blocks[i] - blocks[j];
|
|
6105
6199
|
for (let k = 0; k < keys.length; k++) {
|
|
6106
6200
|
const { def, mul } = keys[k];
|
|
6107
6201
|
const a = values[k][i], b = values[k][j];
|
|
@@ -6696,6 +6790,8 @@ function printModel(grid, opts = {}) {
|
|
|
6696
6790
|
if (s.bold) cell.bold = true;
|
|
6697
6791
|
if (s.italic) cell.italic = true;
|
|
6698
6792
|
if (s.strike) cell.strike = true;
|
|
6793
|
+
const align2 = styleAlign(s);
|
|
6794
|
+
if (align2) cell.align = align2;
|
|
6699
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" };
|
|
6700
6796
|
if (cf.icon) cell.icon = { char: cf.icon.char, color: cf.icon.color };
|
|
6701
6797
|
}
|
|
@@ -7012,6 +7108,8 @@ function buildXlsxSheet(grid, opts = {}) {
|
|
|
7012
7108
|
if (color) style.color = color;
|
|
7013
7109
|
if (fill) style.fill = fill;
|
|
7014
7110
|
if (cf.style.bold) style.bold = true;
|
|
7111
|
+
const align = styleAlign(cf.style);
|
|
7112
|
+
if (align) style.align = align;
|
|
7015
7113
|
}
|
|
7016
7114
|
if (!Object.keys(style).length) return cell;
|
|
7017
7115
|
return cell !== null && typeof cell === "object" && !(cell instanceof Date) ? { ...cell, ...style } : { v: cell, ...style };
|
|
@@ -7644,7 +7742,7 @@ var PAID_FEATURES = {
|
|
|
7644
7742
|
};
|
|
7645
7743
|
|
|
7646
7744
|
// src/version.ts
|
|
7647
|
-
var version = "1.9.
|
|
7745
|
+
var version = "1.9.3";
|
|
7648
7746
|
|
|
7649
7747
|
// src/license.ts
|
|
7650
7748
|
var PUBLIC_KEY = { kty: "EC", crv: "P-256", x: "sUTKkglZmk-HMKPNub8p7AeLBKesHCU8irIkw9rJlbE", y: "j0U_aKRDR7iApY9UpaD3hUemhBLZJwDv9-QiT_hUyw8" };
|
|
@@ -8217,7 +8315,9 @@ var RowModel = class {
|
|
|
8217
8315
|
this.stats.filterRuns++;
|
|
8218
8316
|
}
|
|
8219
8317
|
if (sortChanged) {
|
|
8220
|
-
|
|
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);
|
|
8221
8321
|
this.stats.sortRuns++;
|
|
8222
8322
|
}
|
|
8223
8323
|
if (displayChanged) this.full = d ? this.buildDisplay(d, input.sort, collator) : this.sorted;
|
|
@@ -9234,7 +9334,7 @@ var FormatDialog = class {
|
|
|
9234
9334
|
const node = grid.columns.getNode(colId);
|
|
9235
9335
|
if (!node || node.children || isSystemCol(colId)) return;
|
|
9236
9336
|
const loc = grid.store.state.locale;
|
|
9237
|
-
const cf = loc.cf;
|
|
9337
|
+
const cf = { ...en_default.cf, ...loc.cf, aligns: { ...en_default.cf.aligns, ...loc.cf?.aligns } };
|
|
9238
9338
|
const label = node.def.header ?? node.def.field ?? colId;
|
|
9239
9339
|
const popup = el("div", `fg-menu fg-cf-dialog fg-theme-${grid.store.state.theme}`, { role: "dialog", "aria-label": `${cf.title}: ${label}` });
|
|
9240
9340
|
grid.applyFontTo(popup);
|
|
@@ -9276,9 +9376,14 @@ var FormatDialog = class {
|
|
|
9276
9376
|
if (r2.style?.background) sample.style.background = r2.style.background;
|
|
9277
9377
|
if (r2.style?.color) sample.style.color = r2.style.color;
|
|
9278
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
|
+
}
|
|
9279
9384
|
if (r2.type === "colorScale") sample.style.background = `linear-gradient(to right, ${(r2.colors ?? ["#f8696b", "#ffeb84", "#63be7b"]).join(",")})`;
|
|
9280
9385
|
const text2 = el("span", "fg-cf-rule-text");
|
|
9281
|
-
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]}` : ""}`;
|
|
9282
9387
|
text2.title = text2.textContent;
|
|
9283
9388
|
const del = el("button", "fg-chooser-close", { type: "button", "aria-label": `${loc.close}: ${text2.textContent}` });
|
|
9284
9389
|
del.textContent = "\xD7";
|
|
@@ -9330,10 +9435,17 @@ var FormatDialog = class {
|
|
|
9330
9435
|
});
|
|
9331
9436
|
swatches.append(b);
|
|
9332
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
|
+
}
|
|
9333
9445
|
const rowLabel = el("label", "fg-cf-wide fg-cf-row");
|
|
9334
9446
|
const rowBox = el("input", "", { type: "checkbox" });
|
|
9335
9447
|
rowLabel.append(rowBox, ` ${cf.wholeRow}`);
|
|
9336
|
-
form.append(typeSel, opSel, v1, v2, formula, rank, pctLabel, swatches, rowLabel);
|
|
9448
|
+
form.append(typeSel, opSel, v1, v2, formula, rank, pctLabel, swatches, alignSel, rowLabel);
|
|
9337
9449
|
const sync = () => {
|
|
9338
9450
|
const t = typeSel.value;
|
|
9339
9451
|
const op = opSel.value;
|
|
@@ -9342,7 +9454,7 @@ var FormatDialog = class {
|
|
|
9342
9454
|
v2.hidden = t !== "value" || op !== "between" && op !== "notBetween";
|
|
9343
9455
|
formula.hidden = t !== "formula";
|
|
9344
9456
|
rank.hidden = pctLabel.hidden = t !== "top" && t !== "bottom";
|
|
9345
|
-
swatches.hidden = SCALES.includes(t);
|
|
9457
|
+
swatches.hidden = alignSel.hidden = SCALES.includes(t);
|
|
9346
9458
|
rowLabel.hidden = t !== "value" && t !== "formula";
|
|
9347
9459
|
};
|
|
9348
9460
|
typeSel.addEventListener("change", sync);
|
|
@@ -9370,7 +9482,11 @@ var FormatDialog = class {
|
|
|
9370
9482
|
rule.rank = Math.max(1, Math.floor(Number(rank.value) || 10));
|
|
9371
9483
|
if (pct.checked) rule.percent = true;
|
|
9372
9484
|
}
|
|
9373
|
-
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
|
+
}
|
|
9374
9490
|
if (rowBox.checked && (type === "value" || type === "formula")) rule.row = true;
|
|
9375
9491
|
return rule;
|
|
9376
9492
|
};
|
|
@@ -9399,7 +9515,12 @@ var FormatDialog = class {
|
|
|
9399
9515
|
const r = grid.renderer.root.getBoundingClientRect();
|
|
9400
9516
|
const w = popup.offsetWidth || 340;
|
|
9401
9517
|
popup.style.left = `${Math.max(4, Math.min(r.left + 24, (window.innerWidth || 1024) - w - 4))}px`;
|
|
9402
|
-
|
|
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";
|
|
9403
9524
|
this.popup = popup;
|
|
9404
9525
|
typeSel.focus();
|
|
9405
9526
|
const onDown = (e) => {
|
|
@@ -9916,6 +10037,9 @@ var Grid = class {
|
|
|
9916
10037
|
}
|
|
9917
10038
|
};
|
|
9918
10039
|
this.density = "normal";
|
|
10040
|
+
// ── merge, aggregates, fill, pinned rows ────────────────────
|
|
10041
|
+
/** `sortWithinMerges` is on. */
|
|
10042
|
+
this.sortWithin = false;
|
|
9919
10043
|
/** Count / sum / min / max over the selected range (numbers only for the math). */
|
|
9920
10044
|
this.aggMemo = null;
|
|
9921
10045
|
/** Bumped on every selection change (group row checkboxes depend on their rows). */
|
|
@@ -9962,8 +10086,11 @@ var Grid = class {
|
|
|
9962
10086
|
quickColumns: () => this.columns.displayed().filter((c) => !isSystemCol(c.id)).map((c) => c.def),
|
|
9963
10087
|
format: (def, v) => getFormatter(def, this.store.state.locale.code)(v),
|
|
9964
10088
|
collator: () => this.collator(),
|
|
9965
|
-
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}
|
|
10089
|
+
textVersion: () => `${this.store.state.locale.code}|${this.editVersion}`,
|
|
10090
|
+
sortBlocks: (sort) => this.sortBlocks(sort)
|
|
9966
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");
|
|
9967
10094
|
if (options.groupBy?.length && this.allow(this.rowModel.server ? "serverGroups" : "grouping")) this.groupBy = this.validGroupBy(options.groupBy);
|
|
9968
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] }));
|
|
9969
10096
|
this.defaultExpanded = options.treeData || this.paid ? options.groupDefaultExpanded ?? 0 : 0;
|
|
@@ -10308,8 +10435,9 @@ var Grid = class {
|
|
|
10308
10435
|
const st = this.store.state;
|
|
10309
10436
|
if (this.pivotCfg || this.options.treeData || st.quickFilter || st.advancedFilter || this.groupBy.length) return true;
|
|
10310
10437
|
const ids = [...st.sort.map((x) => x.colId), ...Object.keys(st.filters)];
|
|
10311
|
-
|
|
10312
|
-
|
|
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) => {
|
|
10313
10441
|
if (!def) return false;
|
|
10314
10442
|
if (def.valueGetter || def.formulas || def.formula) return true;
|
|
10315
10443
|
return def.field != null && fields.has(def.field);
|
|
@@ -10626,6 +10754,7 @@ var Grid = class {
|
|
|
10626
10754
|
this.groupBy = next;
|
|
10627
10755
|
this.page = 0;
|
|
10628
10756
|
this.groupPanel?.refresh();
|
|
10757
|
+
if (this.sortWithin) this.rowModel.invalidate();
|
|
10629
10758
|
this.displayChanged();
|
|
10630
10759
|
this.events.emit("groupChange", { groupBy: [...next] });
|
|
10631
10760
|
}
|
|
@@ -11196,7 +11325,32 @@ var Grid = class {
|
|
|
11196
11325
|
});
|
|
11197
11326
|
return out;
|
|
11198
11327
|
}
|
|
11199
|
-
|
|
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
|
+
}
|
|
11200
11354
|
/** Merge blocks for every `merge` column over the displayed rows (memoized). */
|
|
11201
11355
|
mergeSpans() {
|
|
11202
11356
|
if (this.rowModel.server || !this.columns.displayed().some((c) => c.def.merge || c.def.rowSpan)) return NO_SPANS;
|
|
@@ -11836,6 +11990,8 @@ var Grid = class {
|
|
|
11836
11990
|
validate: guard(() => this.validateAll()),
|
|
11837
11991
|
setDensity: guard((d) => this.setDensity(d)),
|
|
11838
11992
|
getDensity: guard(() => this.density),
|
|
11993
|
+
setSortWithinMerges: guard((on) => this.setSortWithinMerges(on)),
|
|
11994
|
+
getSortWithinMerges: guard(() => this.sortWithin),
|
|
11839
11995
|
showOverlay: guard((kind) => {
|
|
11840
11996
|
this.forcedOverlay = kind === "loading" || kind === "noRows" ? kind : null;
|
|
11841
11997
|
this.renderer.invalidate();
|