@particle-academy/holy-sheet 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/dist/index.cjs +113 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -2
- package/dist/index.d.ts +15 -2
- package/dist/index.js +113 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [2.9.0] — 2026-10-19
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`print.fitToPage`: one page wide AND tall.** `fitToWidth` scales a sheet
|
|
18
|
+
to one page wide and lets it run as many pages tall as it needs, so the page
|
|
19
|
+
break fell through the chart below a board summary in three of four
|
|
20
|
+
agent-built workbooks reviewed. `fitToPage: true` scales the whole sheet,
|
|
21
|
+
table, notes and chart, onto one sheet of paper.
|
|
22
|
+
- **`charts[].series[].points`: colour single points.** `[{"index": 2,
|
|
23
|
+
"color": "#C2410C"}]` colours the one bar, slice or line marker the chart is
|
|
24
|
+
about (index 0-based within `values`). Reviewers marked down every comparison
|
|
25
|
+
chart whose bars were all one colour, because it did not say which bar
|
|
26
|
+
mattered. Written as DrawingML data points, read back by `describe()` /
|
|
27
|
+
`read()`, validated with a hint per point, and published in the schema.
|
|
28
|
+
- **Nothing to do**: a workbook that uses neither is byte-identical to before.
|
|
29
|
+
|
|
13
30
|
## [2.8.0] — 2026-10-19
|
|
14
31
|
|
|
15
32
|
### Added
|
package/dist/index.cjs
CHANGED
|
@@ -918,6 +918,20 @@ function toInt(v) {
|
|
|
918
918
|
const n = Math.trunc(Number(v));
|
|
919
919
|
return Number.isFinite(n) ? n : 0;
|
|
920
920
|
}
|
|
921
|
+
function normalizePoints(points2, count) {
|
|
922
|
+
if (!isPlainObject(points2) && !Array.isArray(points2)) return [];
|
|
923
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
924
|
+
for (const p of Object.values(points2)) {
|
|
925
|
+
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
926
|
+
const index = rec ? rec.index : void 0;
|
|
927
|
+
const color2 = rec ? rec.color : void 0;
|
|
928
|
+
const m = typeof color2 === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(color2) : null;
|
|
929
|
+
if (typeof index === "number" && Number.isInteger(index) && index >= 0 && index < count && m) {
|
|
930
|
+
byIndex.set(index, m[1].toUpperCase());
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
|
|
934
|
+
}
|
|
921
935
|
var CHART_TYPES = ["column", "bar", "line", "pie"];
|
|
922
936
|
function normalizeCharts(charts, sheetName2, cells) {
|
|
923
937
|
if (!isPlainObject(charts) && !Array.isArray(charts)) return [];
|
|
@@ -936,7 +950,12 @@ function normalizeCharts(charts, sheetName2, cells) {
|
|
|
936
950
|
if (values2 === null) continue;
|
|
937
951
|
const rec = s;
|
|
938
952
|
const m = typeof rec.color === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(rec.color) : null;
|
|
939
|
-
series.push({
|
|
953
|
+
series.push({
|
|
954
|
+
name: typeof rec.name === "string" ? rec.name : null,
|
|
955
|
+
values: values2,
|
|
956
|
+
color: m ? m[1].toUpperCase() : null,
|
|
957
|
+
points: normalizePoints(rec.points, values2.addresses().length)
|
|
958
|
+
});
|
|
940
959
|
}
|
|
941
960
|
if (series.length === 0) continue;
|
|
942
961
|
const stack = out.length;
|
|
@@ -983,6 +1002,7 @@ function normalizePrint(print) {
|
|
|
983
1002
|
const p = print;
|
|
984
1003
|
const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
|
|
985
1004
|
const fitToWidth = p.fitToWidth === true;
|
|
1005
|
+
const fitToPage = p.fitToPage === true;
|
|
986
1006
|
let footer = null;
|
|
987
1007
|
if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
|
|
988
1008
|
const f = p.footer;
|
|
@@ -990,8 +1010,8 @@ function normalizePrint(print) {
|
|
|
990
1010
|
footer = { left: slot("left"), center: slot("center"), right: slot("right") };
|
|
991
1011
|
if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
|
|
992
1012
|
}
|
|
993
|
-
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
994
|
-
return { orientation, fitToWidth, footer };
|
|
1013
|
+
if (orientation === null && !fitToWidth && !fitToPage && footer === null) return null;
|
|
1014
|
+
return { orientation, fitToWidth, fitToPage, footer };
|
|
995
1015
|
}
|
|
996
1016
|
|
|
997
1017
|
// src/schema/formula-linter.ts
|
|
@@ -1906,6 +1926,15 @@ var Validator = class {
|
|
|
1906
1926
|
"true scales the sheet to one page wide when printed."
|
|
1907
1927
|
));
|
|
1908
1928
|
}
|
|
1929
|
+
if (print.fitToPage != null && typeof print.fitToPage !== "boolean") {
|
|
1930
|
+
errors.push(error(
|
|
1931
|
+
`${path}.print.fitToPage`,
|
|
1932
|
+
"boolean",
|
|
1933
|
+
typeOf(print.fitToPage),
|
|
1934
|
+
print.fitToPage,
|
|
1935
|
+
"true scales the sheet onto ONE page, wide and tall: a one-page summary with a chart prints whole."
|
|
1936
|
+
));
|
|
1937
|
+
}
|
|
1909
1938
|
if (print.footer != null) {
|
|
1910
1939
|
if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
|
|
1911
1940
|
errors.push(error(
|
|
@@ -1977,6 +2006,39 @@ var Validator = class {
|
|
|
1977
2006
|
return errors;
|
|
1978
2007
|
}
|
|
1979
2008
|
};
|
|
2009
|
+
function validatePoints(points2, at2, values2) {
|
|
2010
|
+
if (!Array.isArray(points2)) {
|
|
2011
|
+
return [
|
|
2012
|
+
error(
|
|
2013
|
+
at2,
|
|
2014
|
+
"a list of points",
|
|
2015
|
+
typeOf(points2),
|
|
2016
|
+
points2,
|
|
2017
|
+
'Colour single points: [{"index": 2, "color": "#C2410C"}], index 0-based within values.'
|
|
2018
|
+
)
|
|
2019
|
+
];
|
|
2020
|
+
}
|
|
2021
|
+
const count = values2 === null ? 0 : values2.addresses().length;
|
|
2022
|
+
const errors = [];
|
|
2023
|
+
points2.forEach((p, k) => {
|
|
2024
|
+
const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
|
|
2025
|
+
const index = rec ? rec.index ?? null : null;
|
|
2026
|
+
const color2 = rec ? rec.color ?? null : null;
|
|
2027
|
+
if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= count) {
|
|
2028
|
+
errors.push(error(
|
|
2029
|
+
`${at2}[${k}].index`,
|
|
2030
|
+
"an index within values (0 to " + Math.max(0, count - 1) + ")",
|
|
2031
|
+
typeOf(index),
|
|
2032
|
+
index,
|
|
2033
|
+
'The 0-based position of the point in its values range: "B5:B7" has points 0, 1 and 2.'
|
|
2034
|
+
));
|
|
2035
|
+
}
|
|
2036
|
+
if (typeof color2 !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(color2)) {
|
|
2037
|
+
errors.push(error(`${at2}[${k}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#C2410C".'));
|
|
2038
|
+
}
|
|
2039
|
+
});
|
|
2040
|
+
return errors;
|
|
2041
|
+
}
|
|
1980
2042
|
function validateCharts(charts, path, sheetName2) {
|
|
1981
2043
|
if (!Array.isArray(charts)) {
|
|
1982
2044
|
return [
|
|
@@ -2033,6 +2095,9 @@ function validateCharts(charts, path, sheetName2) {
|
|
|
2033
2095
|
errors.push(error(`${at2}.series[${j}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#1F3A5F".'));
|
|
2034
2096
|
}
|
|
2035
2097
|
}
|
|
2098
|
+
if (isObj && s.points != null) {
|
|
2099
|
+
errors.push(...validatePoints(s.points, `${at2}.series[${j}].points`, ChartRange.parse(values2, sheetName2)));
|
|
2100
|
+
}
|
|
2036
2101
|
});
|
|
2037
2102
|
}
|
|
2038
2103
|
if (c.categories != null && ChartRange.parse(c.categories, sheetName2) === null) {
|
|
@@ -3137,6 +3202,7 @@ function sheetToSchema(sheet) {
|
|
|
3137
3202
|
const print = {};
|
|
3138
3203
|
if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
|
|
3139
3204
|
if (sheet.print.fitToWidth) print["fitToWidth"] = true;
|
|
3205
|
+
if (sheet.print.fitToPage) print["fitToPage"] = true;
|
|
3140
3206
|
if (sheet.print.footer !== null) {
|
|
3141
3207
|
const footer = {};
|
|
3142
3208
|
for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
|
|
@@ -3158,6 +3224,7 @@ function chartToSchema(chart, sheetName2) {
|
|
|
3158
3224
|
if (s.name !== null) series["name"] = s.name;
|
|
3159
3225
|
series["values"] = s.values.toSchema(sheetName2);
|
|
3160
3226
|
if (s.color !== null) series["color"] = "#" + s.color;
|
|
3227
|
+
if (s.points.length > 0) series["points"] = s.points.map((p) => ({ index: p.index, color: "#" + p.color }));
|
|
3161
3228
|
return series;
|
|
3162
3229
|
});
|
|
3163
3230
|
out["position"] = {
|
|
@@ -3682,7 +3749,7 @@ var ChartParser = {
|
|
|
3682
3749
|
const catRef = formula(ser, "cat");
|
|
3683
3750
|
categories = catRef !== null ? ChartRange.parse(catRef, sheetName2) : null;
|
|
3684
3751
|
}
|
|
3685
|
-
series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser) });
|
|
3752
|
+
series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser), points: points(ser) });
|
|
3686
3753
|
}
|
|
3687
3754
|
if (series.length === 0) return null;
|
|
3688
3755
|
const labels = el(plot, "dLbls");
|
|
@@ -3738,6 +3805,18 @@ function seriesName(ser) {
|
|
|
3738
3805
|
const cached = descendants(tx, "v")[0];
|
|
3739
3806
|
return cached ? cached.text : null;
|
|
3740
3807
|
}
|
|
3808
|
+
function points(ser) {
|
|
3809
|
+
const byIndex = /* @__PURE__ */ new Map();
|
|
3810
|
+
for (const dPt of els(ser, "dPt")) {
|
|
3811
|
+
const idx = at(el(dPt, "idx"), "val");
|
|
3812
|
+
const clr = descendants(dPt, "srgbClr")[0];
|
|
3813
|
+
const color2 = (clr ? at(clr, "val") ?? "" : "").toUpperCase();
|
|
3814
|
+
if (idx !== void 0 && /^[0-9]+$/.test(idx) && /^[0-9A-F]{6}$/.test(color2)) {
|
|
3815
|
+
byIndex.set(parseInt(idx, 10), color2);
|
|
3816
|
+
}
|
|
3817
|
+
}
|
|
3818
|
+
return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
|
|
3819
|
+
}
|
|
3741
3820
|
function seriesColor(ser) {
|
|
3742
3821
|
const spPr = el(ser, "spPr");
|
|
3743
3822
|
if (!spPr) return null;
|
|
@@ -3933,10 +4012,11 @@ function parsePrint(xml) {
|
|
|
3933
4012
|
const o = at(setup, "orientation");
|
|
3934
4013
|
const orientation = o === "portrait" || o === "landscape" ? o : null;
|
|
3935
4014
|
const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
|
|
4015
|
+
const fitToPageOnly = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && at(setup, "fitToHeight") === "1";
|
|
3936
4016
|
const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
|
|
3937
4017
|
const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
|
|
3938
|
-
if (orientation === null && !fitToWidth && footer === null) return null;
|
|
3939
|
-
return { orientation, fitToWidth, footer };
|
|
4018
|
+
if (orientation === null && !fitToWidth && !fitToPageOnly && footer === null) return null;
|
|
4019
|
+
return { orientation, fitToWidth, fitToPage: fitToPageOnly, footer };
|
|
3940
4020
|
}
|
|
3941
4021
|
function parseFooterCodes(codes) {
|
|
3942
4022
|
const slots = { left: "", center: "", right: "" };
|
|
@@ -4414,7 +4494,12 @@ function seriesXml(i, s, chart, workbook) {
|
|
|
4414
4494
|
const mark = type === "line" ? '<c:marker><c:symbol val="circle"/><c:size val="6"/></c:marker>' : "";
|
|
4415
4495
|
const cat = chart.categories !== null ? "<c:cat>" + strRef(chart.categories, workbook) + "</c:cat>" : "";
|
|
4416
4496
|
const smooth = type === "line" ? '<c:smooth val="0"/>' : "";
|
|
4417
|
-
|
|
4497
|
+
let points2 = "";
|
|
4498
|
+
for (const p of s.points) {
|
|
4499
|
+
const solid = `<a:solidFill><a:srgbClr val="${p.color}"/></a:solidFill>`;
|
|
4500
|
+
points2 += `<c:dPt><c:idx val="${p.index}"/>` + (type === "line" ? `<c:marker><c:symbol val="circle"/><c:size val="6"/><c:spPr>${solid}<a:ln>${solid}</a:ln></c:spPr></c:marker><c:bubble3D val="0"/>` : type === "pie" ? `<c:bubble3D val="0"/><c:spPr>${solid}</c:spPr>` : `<c:invertIfNegative val="0"/><c:bubble3D val="0"/><c:spPr>${solid}</c:spPr>`) + "</c:dPt>";
|
|
4501
|
+
}
|
|
4502
|
+
return `<c:ser><c:idx val="${i}"/><c:order val="${i}"/>` + name + fill + invert + mark + points2 + cat + "<c:val>" + numRef(s.values, workbook) + "</c:val>" + smooth + "</c:ser>";
|
|
4418
4503
|
}
|
|
4419
4504
|
function strRef(range, workbook) {
|
|
4420
4505
|
let pts = "";
|
|
@@ -4628,10 +4713,12 @@ var XlsxWriter = class {
|
|
|
4628
4713
|
}
|
|
4629
4714
|
const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
|
|
4630
4715
|
const print = sheet.print;
|
|
4631
|
-
const
|
|
4716
|
+
const fits = (print?.fitToWidth ?? false) || (print?.fitToPage ?? false);
|
|
4717
|
+
const sheetPrXml = fits ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
|
|
4632
4718
|
let pageSetupXml = "";
|
|
4633
|
-
if (print !== null && (print.orientation !== null ||
|
|
4634
|
-
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") +
|
|
4719
|
+
if (print !== null && (print.orientation !== null || fits)) {
|
|
4720
|
+
pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + // fitToPage: one page wide AND tall. fitToWidth: one wide, as tall as it needs.
|
|
4721
|
+
(print.fitToPage ? ' fitToWidth="1" fitToHeight="1"' : print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
|
|
4635
4722
|
}
|
|
4636
4723
|
const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
|
|
4637
4724
|
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?><worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' + sheetPrXml + sheetViewsXml + colsXml + `<sheetData>${rowsXml}</sheetData>` + mergesXml + pageSetupXml + headerFooterXml + (sheet.charts.length > 0 ? `<drawing r:id="${drawingRelId(sheet.hasComments())}"/>` : "") + legacyDrawing + "</worksheet>";
|
|
@@ -5779,7 +5866,20 @@ var holy_sheet_schema_default = {
|
|
|
5779
5866
|
properties: {
|
|
5780
5867
|
name: { type: "string", description: "Shown in the legend." },
|
|
5781
5868
|
values: { type: "string", description: 'The numbers, as a range the same length as categories: "B5:B7".' },
|
|
5782
|
-
color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." }
|
|
5869
|
+
color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." },
|
|
5870
|
+
points: {
|
|
5871
|
+
type: "array",
|
|
5872
|
+
description: 'Colour single points to make the finding stand out: the one bar or slice the title is about. index is 0-based within values: [{"index": 2, "color": "#C2410C"}].',
|
|
5873
|
+
items: {
|
|
5874
|
+
type: "object",
|
|
5875
|
+
additionalProperties: false,
|
|
5876
|
+
required: ["index", "color"],
|
|
5877
|
+
properties: {
|
|
5878
|
+
index: { type: "integer", minimum: 0 },
|
|
5879
|
+
color: { type: "string", description: "#RRGGBB" }
|
|
5880
|
+
}
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5783
5883
|
}
|
|
5784
5884
|
}
|
|
5785
5885
|
},
|
|
@@ -5802,6 +5902,7 @@ var holy_sheet_schema_default = {
|
|
|
5802
5902
|
properties: {
|
|
5803
5903
|
orientation: { type: "string", enum: ["portrait", "landscape"] },
|
|
5804
5904
|
fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
|
|
5905
|
+
fitToPage: { type: "boolean", description: "Scale the sheet onto ONE page, wide and tall. Use it for a one-page summary with a chart, so the page break never cuts the chart." },
|
|
5805
5906
|
footer: {
|
|
5806
5907
|
type: "object",
|
|
5807
5908
|
additionalProperties: false,
|
|
@@ -6021,7 +6122,7 @@ var holy_sheet_schema_default = {
|
|
|
6021
6122
|
};
|
|
6022
6123
|
|
|
6023
6124
|
// src/agent.ts
|
|
6024
|
-
var VERSION = "2.
|
|
6125
|
+
var VERSION = "2.9.0";
|
|
6025
6126
|
function toU8(input) {
|
|
6026
6127
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
6027
6128
|
}
|