@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/dist/index.d.cts CHANGED
@@ -63,6 +63,8 @@ interface SheetSchema {
63
63
  print?: {
64
64
  orientation?: "portrait" | "landscape";
65
65
  fitToWidth?: boolean;
66
+ /** One page wide AND tall: a one-page summary with a chart prints whole. */
67
+ fitToPage?: boolean;
66
68
  /** `{page}` / `{pages}` print the page number and count. */
67
69
  footer?: {
68
70
  left?: string;
@@ -78,10 +80,15 @@ interface ChartSchema {
78
80
  title?: string;
79
81
  /** Axis labels, e.g. "A5:A7" or "'Other sheet'!A5:A7". */
80
82
  categories?: string;
83
+ /** points colours single points (0-based within values) to make the finding stand out. */
81
84
  series: {
82
85
  name?: string;
83
86
  values: string;
84
87
  color?: string;
88
+ points?: {
89
+ index: number;
90
+ color: string;
91
+ }[];
85
92
  }[];
86
93
  /** Top-left and bottom-right cells; defaults to beside the data. */
87
94
  position?: {
@@ -257,7 +264,7 @@ interface SetMetaOp {
257
264
  }
258
265
 
259
266
  /** This package's own version, pinned to package.json by `version.test.ts`. */
260
- declare const VERSION = "2.8.0";
267
+ declare const VERSION = "2.9.0";
261
268
  type Any$6 = any;
262
269
  /**
263
270
  * Agent — the structured-tool surface for Holy Sheet. Mirrors PHP `Agent`.
@@ -329,7 +336,7 @@ type Any$5 = any;
329
336
  * over `Agent`. Provided for parity / DI ergonomics; most callers use `Agent`.
330
337
  */
331
338
  declare class HolySheet {
332
- static readonly VERSION = "2.8.0";
339
+ static readonly VERSION = "2.9.0";
333
340
  validate(schema: Any$5): ValidationError[];
334
341
  toBytes(schema: Any$5): Uint8Array;
335
342
  write(schema: Any$5, path: string): Promise<WriteResult>;
@@ -493,6 +500,8 @@ declare class ChartRange {
493
500
  interface PrintSetup {
494
501
  orientation: "portrait" | "landscape" | null;
495
502
  fitToWidth: boolean;
503
+ /** One page wide AND tall. */
504
+ fitToPage: boolean;
496
505
  footer: {
497
506
  left: string;
498
507
  center: string;
@@ -508,6 +517,10 @@ interface NormalizedChart {
508
517
  name: string | null;
509
518
  values: ChartRange;
510
519
  color: string | null;
520
+ points: {
521
+ index: number;
522
+ color: string;
523
+ }[];
511
524
  }[];
512
525
  /** [col, row], both 0-based. */
513
526
  from: [number, number];
package/dist/index.d.ts CHANGED
@@ -63,6 +63,8 @@ interface SheetSchema {
63
63
  print?: {
64
64
  orientation?: "portrait" | "landscape";
65
65
  fitToWidth?: boolean;
66
+ /** One page wide AND tall: a one-page summary with a chart prints whole. */
67
+ fitToPage?: boolean;
66
68
  /** `{page}` / `{pages}` print the page number and count. */
67
69
  footer?: {
68
70
  left?: string;
@@ -78,10 +80,15 @@ interface ChartSchema {
78
80
  title?: string;
79
81
  /** Axis labels, e.g. "A5:A7" or "'Other sheet'!A5:A7". */
80
82
  categories?: string;
83
+ /** points colours single points (0-based within values) to make the finding stand out. */
81
84
  series: {
82
85
  name?: string;
83
86
  values: string;
84
87
  color?: string;
88
+ points?: {
89
+ index: number;
90
+ color: string;
91
+ }[];
85
92
  }[];
86
93
  /** Top-left and bottom-right cells; defaults to beside the data. */
87
94
  position?: {
@@ -257,7 +264,7 @@ interface SetMetaOp {
257
264
  }
258
265
 
259
266
  /** This package's own version, pinned to package.json by `version.test.ts`. */
260
- declare const VERSION = "2.8.0";
267
+ declare const VERSION = "2.9.0";
261
268
  type Any$6 = any;
262
269
  /**
263
270
  * Agent — the structured-tool surface for Holy Sheet. Mirrors PHP `Agent`.
@@ -329,7 +336,7 @@ type Any$5 = any;
329
336
  * over `Agent`. Provided for parity / DI ergonomics; most callers use `Agent`.
330
337
  */
331
338
  declare class HolySheet {
332
- static readonly VERSION = "2.8.0";
339
+ static readonly VERSION = "2.9.0";
333
340
  validate(schema: Any$5): ValidationError[];
334
341
  toBytes(schema: Any$5): Uint8Array;
335
342
  write(schema: Any$5, path: string): Promise<WriteResult>;
@@ -493,6 +500,8 @@ declare class ChartRange {
493
500
  interface PrintSetup {
494
501
  orientation: "portrait" | "landscape" | null;
495
502
  fitToWidth: boolean;
503
+ /** One page wide AND tall. */
504
+ fitToPage: boolean;
496
505
  footer: {
497
506
  left: string;
498
507
  center: string;
@@ -508,6 +517,10 @@ interface NormalizedChart {
508
517
  name: string | null;
509
518
  values: ChartRange;
510
519
  color: string | null;
520
+ points: {
521
+ index: number;
522
+ color: string;
523
+ }[];
511
524
  }[];
512
525
  /** [col, row], both 0-based. */
513
526
  from: [number, number];
package/dist/index.js CHANGED
@@ -916,6 +916,20 @@ function toInt(v) {
916
916
  const n = Math.trunc(Number(v));
917
917
  return Number.isFinite(n) ? n : 0;
918
918
  }
919
+ function normalizePoints(points2, count) {
920
+ if (!isPlainObject(points2) && !Array.isArray(points2)) return [];
921
+ const byIndex = /* @__PURE__ */ new Map();
922
+ for (const p of Object.values(points2)) {
923
+ const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
924
+ const index = rec ? rec.index : void 0;
925
+ const color2 = rec ? rec.color : void 0;
926
+ const m = typeof color2 === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(color2) : null;
927
+ if (typeof index === "number" && Number.isInteger(index) && index >= 0 && index < count && m) {
928
+ byIndex.set(index, m[1].toUpperCase());
929
+ }
930
+ }
931
+ return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
932
+ }
919
933
  var CHART_TYPES = ["column", "bar", "line", "pie"];
920
934
  function normalizeCharts(charts, sheetName2, cells) {
921
935
  if (!isPlainObject(charts) && !Array.isArray(charts)) return [];
@@ -934,7 +948,12 @@ function normalizeCharts(charts, sheetName2, cells) {
934
948
  if (values2 === null) continue;
935
949
  const rec = s;
936
950
  const m = typeof rec.color === "string" ? /^#([0-9A-Fa-f]{6})$/.exec(rec.color) : null;
937
- series.push({ name: typeof rec.name === "string" ? rec.name : null, values: values2, color: m ? m[1].toUpperCase() : null });
951
+ series.push({
952
+ name: typeof rec.name === "string" ? rec.name : null,
953
+ values: values2,
954
+ color: m ? m[1].toUpperCase() : null,
955
+ points: normalizePoints(rec.points, values2.addresses().length)
956
+ });
938
957
  }
939
958
  if (series.length === 0) continue;
940
959
  const stack = out.length;
@@ -981,6 +1000,7 @@ function normalizePrint(print) {
981
1000
  const p = print;
982
1001
  const orientation = p.orientation === "portrait" || p.orientation === "landscape" ? p.orientation : null;
983
1002
  const fitToWidth = p.fitToWidth === true;
1003
+ const fitToPage = p.fitToPage === true;
984
1004
  let footer = null;
985
1005
  if (isPlainObject(p.footer) || Array.isArray(p.footer)) {
986
1006
  const f = p.footer;
@@ -988,8 +1008,8 @@ function normalizePrint(print) {
988
1008
  footer = { left: slot("left"), center: slot("center"), right: slot("right") };
989
1009
  if (footer.left === "" && footer.center === "" && footer.right === "") footer = null;
990
1010
  }
991
- if (orientation === null && !fitToWidth && footer === null) return null;
992
- return { orientation, fitToWidth, footer };
1011
+ if (orientation === null && !fitToWidth && !fitToPage && footer === null) return null;
1012
+ return { orientation, fitToWidth, fitToPage, footer };
993
1013
  }
994
1014
 
995
1015
  // src/schema/formula-linter.ts
@@ -1904,6 +1924,15 @@ var Validator = class {
1904
1924
  "true scales the sheet to one page wide when printed."
1905
1925
  ));
1906
1926
  }
1927
+ if (print.fitToPage != null && typeof print.fitToPage !== "boolean") {
1928
+ errors.push(error(
1929
+ `${path}.print.fitToPage`,
1930
+ "boolean",
1931
+ typeOf(print.fitToPage),
1932
+ print.fitToPage,
1933
+ "true scales the sheet onto ONE page, wide and tall: a one-page summary with a chart prints whole."
1934
+ ));
1935
+ }
1907
1936
  if (print.footer != null) {
1908
1937
  if (!isPlainObject(print.footer) && !Array.isArray(print.footer)) {
1909
1938
  errors.push(error(
@@ -1975,6 +2004,39 @@ var Validator = class {
1975
2004
  return errors;
1976
2005
  }
1977
2006
  };
2007
+ function validatePoints(points2, at2, values2) {
2008
+ if (!Array.isArray(points2)) {
2009
+ return [
2010
+ error(
2011
+ at2,
2012
+ "a list of points",
2013
+ typeOf(points2),
2014
+ points2,
2015
+ 'Colour single points: [{"index": 2, "color": "#C2410C"}], index 0-based within values.'
2016
+ )
2017
+ ];
2018
+ }
2019
+ const count = values2 === null ? 0 : values2.addresses().length;
2020
+ const errors = [];
2021
+ points2.forEach((p, k) => {
2022
+ const rec = isPlainObject(p) || Array.isArray(p) ? p : null;
2023
+ const index = rec ? rec.index ?? null : null;
2024
+ const color2 = rec ? rec.color ?? null : null;
2025
+ if (typeof index !== "number" || !Number.isInteger(index) || index < 0 || index >= count) {
2026
+ errors.push(error(
2027
+ `${at2}[${k}].index`,
2028
+ "an index within values (0 to " + Math.max(0, count - 1) + ")",
2029
+ typeOf(index),
2030
+ index,
2031
+ 'The 0-based position of the point in its values range: "B5:B7" has points 0, 1 and 2.'
2032
+ ));
2033
+ }
2034
+ if (typeof color2 !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(color2)) {
2035
+ errors.push(error(`${at2}[${k}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#C2410C".'));
2036
+ }
2037
+ });
2038
+ return errors;
2039
+ }
1978
2040
  function validateCharts(charts, path, sheetName2) {
1979
2041
  if (!Array.isArray(charts)) {
1980
2042
  return [
@@ -2031,6 +2093,9 @@ function validateCharts(charts, path, sheetName2) {
2031
2093
  errors.push(error(`${at2}.series[${j}].color`, "#RRGGBB", typeOf(color2), color2, 'A hex colour, like "#1F3A5F".'));
2032
2094
  }
2033
2095
  }
2096
+ if (isObj && s.points != null) {
2097
+ errors.push(...validatePoints(s.points, `${at2}.series[${j}].points`, ChartRange.parse(values2, sheetName2)));
2098
+ }
2034
2099
  });
2035
2100
  }
2036
2101
  if (c.categories != null && ChartRange.parse(c.categories, sheetName2) === null) {
@@ -3135,6 +3200,7 @@ function sheetToSchema(sheet) {
3135
3200
  const print = {};
3136
3201
  if (sheet.print.orientation !== null) print["orientation"] = sheet.print.orientation;
3137
3202
  if (sheet.print.fitToWidth) print["fitToWidth"] = true;
3203
+ if (sheet.print.fitToPage) print["fitToPage"] = true;
3138
3204
  if (sheet.print.footer !== null) {
3139
3205
  const footer = {};
3140
3206
  for (const k of ["left", "center", "right"]) if (sheet.print.footer[k] !== "") footer[k] = sheet.print.footer[k];
@@ -3156,6 +3222,7 @@ function chartToSchema(chart, sheetName2) {
3156
3222
  if (s.name !== null) series["name"] = s.name;
3157
3223
  series["values"] = s.values.toSchema(sheetName2);
3158
3224
  if (s.color !== null) series["color"] = "#" + s.color;
3225
+ if (s.points.length > 0) series["points"] = s.points.map((p) => ({ index: p.index, color: "#" + p.color }));
3159
3226
  return series;
3160
3227
  });
3161
3228
  out["position"] = {
@@ -3680,7 +3747,7 @@ var ChartParser = {
3680
3747
  const catRef = formula(ser, "cat");
3681
3748
  categories = catRef !== null ? ChartRange.parse(catRef, sheetName2) : null;
3682
3749
  }
3683
- series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser) });
3750
+ series.push({ name: seriesName(ser), values: values2, color: seriesColor(ser), points: points(ser) });
3684
3751
  }
3685
3752
  if (series.length === 0) return null;
3686
3753
  const labels = el(plot, "dLbls");
@@ -3736,6 +3803,18 @@ function seriesName(ser) {
3736
3803
  const cached = descendants(tx, "v")[0];
3737
3804
  return cached ? cached.text : null;
3738
3805
  }
3806
+ function points(ser) {
3807
+ const byIndex = /* @__PURE__ */ new Map();
3808
+ for (const dPt of els(ser, "dPt")) {
3809
+ const idx = at(el(dPt, "idx"), "val");
3810
+ const clr = descendants(dPt, "srgbClr")[0];
3811
+ const color2 = (clr ? at(clr, "val") ?? "" : "").toUpperCase();
3812
+ if (idx !== void 0 && /^[0-9]+$/.test(idx) && /^[0-9A-F]{6}$/.test(color2)) {
3813
+ byIndex.set(parseInt(idx, 10), color2);
3814
+ }
3815
+ }
3816
+ return [...byIndex.entries()].sort((a, b) => a[0] - b[0]).map(([index, color2]) => ({ index, color: color2 }));
3817
+ }
3739
3818
  function seriesColor(ser) {
3740
3819
  const spPr = el(ser, "spPr");
3741
3820
  if (!spPr) return null;
@@ -3931,10 +4010,11 @@ function parsePrint(xml) {
3931
4010
  const o = at(setup, "orientation");
3932
4011
  const orientation = o === "portrait" || o === "landscape" ? o : null;
3933
4012
  const fitToWidth = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && ["0", ""].includes(at(setup, "fitToHeight") ?? "");
4013
+ const fitToPageOnly = fitToPage && setup !== void 0 && at(setup, "fitToWidth") === "1" && at(setup, "fitToHeight") === "1";
3934
4014
  const oddFooter = el(el(xml, "headerFooter"), "oddFooter");
3935
4015
  const footer = oddFooter !== void 0 ? parseFooterCodes(oddFooter.text) : null;
3936
- if (orientation === null && !fitToWidth && footer === null) return null;
3937
- return { orientation, fitToWidth, footer };
4016
+ if (orientation === null && !fitToWidth && !fitToPageOnly && footer === null) return null;
4017
+ return { orientation, fitToWidth, fitToPage: fitToPageOnly, footer };
3938
4018
  }
3939
4019
  function parseFooterCodes(codes) {
3940
4020
  const slots = { left: "", center: "", right: "" };
@@ -4412,7 +4492,12 @@ function seriesXml(i, s, chart, workbook) {
4412
4492
  const mark = type === "line" ? '<c:marker><c:symbol val="circle"/><c:size val="6"/></c:marker>' : "";
4413
4493
  const cat = chart.categories !== null ? "<c:cat>" + strRef(chart.categories, workbook) + "</c:cat>" : "";
4414
4494
  const smooth = type === "line" ? '<c:smooth val="0"/>' : "";
4415
- return `<c:ser><c:idx val="${i}"/><c:order val="${i}"/>` + name + fill + invert + mark + cat + "<c:val>" + numRef(s.values, workbook) + "</c:val>" + smooth + "</c:ser>";
4495
+ let points2 = "";
4496
+ for (const p of s.points) {
4497
+ const solid = `<a:solidFill><a:srgbClr val="${p.color}"/></a:solidFill>`;
4498
+ 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>";
4499
+ }
4500
+ 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>";
4416
4501
  }
4417
4502
  function strRef(range, workbook) {
4418
4503
  let pts = "";
@@ -4626,10 +4711,12 @@ var XlsxWriter = class {
4626
4711
  }
4627
4712
  const legacyDrawing = sheet.hasComments() ? '<legacyDrawing r:id="rId1"/>' : "";
4628
4713
  const print = sheet.print;
4629
- const sheetPrXml = print?.fitToWidth ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
4714
+ const fits = (print?.fitToWidth ?? false) || (print?.fitToPage ?? false);
4715
+ const sheetPrXml = fits ? '<sheetPr><pageSetUpPr fitToPage="1"/></sheetPr>' : "";
4630
4716
  let pageSetupXml = "";
4631
- if (print !== null && (print.orientation !== null || print.fitToWidth)) {
4632
- pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + (print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
4717
+ if (print !== null && (print.orientation !== null || fits)) {
4718
+ pageSetupXml = "<pageSetup" + (print.orientation !== null ? ` orientation="${print.orientation}"` : "") + // fitToPage: one page wide AND tall. fitToWidth: one wide, as tall as it needs.
4719
+ (print.fitToPage ? ' fitToWidth="1" fitToHeight="1"' : print.fitToWidth ? ' fitToWidth="1" fitToHeight="0"' : "") + "/>";
4633
4720
  }
4634
4721
  const headerFooterXml = print?.footer ? `<headerFooter><oddFooter>${this.escape(footerCodes(print.footer))}</oddFooter></headerFooter>` : "";
4635
4722
  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>";
@@ -5777,7 +5864,20 @@ var holy_sheet_schema_default = {
5777
5864
  properties: {
5778
5865
  name: { type: "string", description: "Shown in the legend." },
5779
5866
  values: { type: "string", description: 'The numbers, as a range the same length as categories: "B5:B7".' },
5780
- color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." }
5867
+ color: { type: "string", description: "#RRGGBB. Ignored for a pie, whose slices take a palette." },
5868
+ points: {
5869
+ type: "array",
5870
+ 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"}].',
5871
+ items: {
5872
+ type: "object",
5873
+ additionalProperties: false,
5874
+ required: ["index", "color"],
5875
+ properties: {
5876
+ index: { type: "integer", minimum: 0 },
5877
+ color: { type: "string", description: "#RRGGBB" }
5878
+ }
5879
+ }
5880
+ }
5781
5881
  }
5782
5882
  }
5783
5883
  },
@@ -5800,6 +5900,7 @@ var holy_sheet_schema_default = {
5800
5900
  properties: {
5801
5901
  orientation: { type: "string", enum: ["portrait", "landscape"] },
5802
5902
  fitToWidth: { type: "boolean", description: "Scale the sheet to one page wide (as many pages tall as it needs)." },
5903
+ 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." },
5803
5904
  footer: {
5804
5905
  type: "object",
5805
5906
  additionalProperties: false,
@@ -6019,7 +6120,7 @@ var holy_sheet_schema_default = {
6019
6120
  };
6020
6121
 
6021
6122
  // src/agent.ts
6022
- var VERSION = "2.8.0";
6123
+ var VERSION = "2.9.0";
6023
6124
  function toU8(input) {
6024
6125
  return input instanceof Uint8Array ? input : new Uint8Array(input);
6025
6126
  }