@particle-academy/holy-sheet 2.4.2 → 2.5.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/README.md +3 -3
- package/dist/index.cjs +141 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +141 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @particle-academy/holy-sheet
|
|
2
2
|
|
|
3
|
-
[](https://particle.academy)
|
|
4
4
|
|
|
5
5
|
Zero-dependency, **isomorphic** (browser + Node) `.xlsx` writer + reader +
|
|
6
6
|
formula linter for agentic document creation. The Node/TypeScript mirror of the
|
|
7
7
|
PHP [`particle-academy/holy-sheet`](https://github.com/Particle-Academy/holy-sheet)
|
|
8
|
-
— same schema in, same `.xlsx` out (
|
|
9
|
-
|
|
8
|
+
— same schema in, same `.xlsx` out (the PHP package's `dumpJson` and `Toolkit`
|
|
9
|
+
are not mirrored here; `CHANGELOG.md` records which PHP releases each fix mirrors).
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import { Agent } from "@particle-academy/holy-sheet";
|
package/dist/index.cjs
CHANGED
|
@@ -287,6 +287,39 @@ var Cell = class {
|
|
|
287
287
|
}
|
|
288
288
|
};
|
|
289
289
|
|
|
290
|
+
// src/schema/column-widths.ts
|
|
291
|
+
var ColumnWidths = {
|
|
292
|
+
MAX_INDEX: 16383,
|
|
293
|
+
index(key) {
|
|
294
|
+
if (typeof key === "number") {
|
|
295
|
+
return Number.isInteger(key) && key >= 0 && key <= ColumnWidths.MAX_INDEX ? key : null;
|
|
296
|
+
}
|
|
297
|
+
if (typeof key === "string" && /^[0-9]{1,5}$/.test(key)) {
|
|
298
|
+
const index = parseInt(key, 10);
|
|
299
|
+
return index <= ColumnWidths.MAX_INDEX ? index : null;
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
},
|
|
303
|
+
width(px) {
|
|
304
|
+
if (typeof px === "number") {
|
|
305
|
+
return Number.isFinite(px) && px >= 0 ? px : null;
|
|
306
|
+
}
|
|
307
|
+
if (typeof px === "string" && /^[0-9]+(\.[0-9]+)?$/.test(px)) {
|
|
308
|
+
return Number(px);
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
},
|
|
312
|
+
/**
|
|
313
|
+
* A column letter key (`"B"`, `"aa"`) as an index, for repair only. One or two
|
|
314
|
+
* letters (A to ZZ): a longer run like `"abc"` is more likely a mistake than
|
|
315
|
+
* column ABC, and a repair must not guess.
|
|
316
|
+
*/
|
|
317
|
+
fromLetters(key) {
|
|
318
|
+
if (typeof key !== "string" || !/^[A-Za-z]{1,2}$/.test(key.trim())) return null;
|
|
319
|
+
return CellAddress.index(key.trim());
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
290
323
|
// src/workbook/cell-comment.ts
|
|
291
324
|
var CellComment = class {
|
|
292
325
|
constructor(text, author = null, color2 = null) {
|
|
@@ -582,7 +615,7 @@ var Normalizer = class {
|
|
|
582
615
|
name,
|
|
583
616
|
cells,
|
|
584
617
|
this.normalizeMerges(sheet.mergedRegions ?? []),
|
|
585
|
-
this.
|
|
618
|
+
this.normalizeWidths(sheet),
|
|
586
619
|
toInt(sheet.frozenRows ?? 0),
|
|
587
620
|
toInt(sheet.frozenCols ?? 0)
|
|
588
621
|
);
|
|
@@ -628,10 +661,43 @@ var Normalizer = class {
|
|
|
628
661
|
}
|
|
629
662
|
return out;
|
|
630
663
|
}
|
|
664
|
+
/**
|
|
665
|
+
* The two ways a width can be stated, folded into one map.
|
|
666
|
+
*
|
|
667
|
+
* `columns[].width` widens the column at that POSITION; the sheet-level
|
|
668
|
+
* `columnWidths` map keys the same columns by 0-based index. Both have been
|
|
669
|
+
* described in the shared `skills/holy-sheet.schema.json` ("Same as
|
|
670
|
+
* columnWidths but per-column") for as long as the field has existed, and only
|
|
671
|
+
* the map was ever read — so a width written the way the schema documents it
|
|
672
|
+
* emitted no `<cols>` element at all, with no error and no repair note
|
|
673
|
+
* (holy-sheet#8, fixed in PHP 2.4.0; this is the twin).
|
|
674
|
+
*
|
|
675
|
+
* The MAP IS APPLIED LAST and wins, matching PHP. It is the mechanism that
|
|
676
|
+
* already worked, so anyone who reached for it to work around this bug must not
|
|
677
|
+
* now find a leftover `width` quietly overriding it.
|
|
678
|
+
*/
|
|
679
|
+
normalizeWidths(sheet) {
|
|
680
|
+
const out = /* @__PURE__ */ new Map();
|
|
681
|
+
for (const [colIdx, columnDef] of (sheet.columns ?? []).entries()) {
|
|
682
|
+
if (columnDef === null || typeof columnDef !== "object") continue;
|
|
683
|
+
if (columnDef.width === void 0) continue;
|
|
684
|
+
const index = ColumnWidths.index(colIdx);
|
|
685
|
+
const width = ColumnWidths.width(columnDef.width);
|
|
686
|
+
if (index === null || width === null) continue;
|
|
687
|
+
out.set(index, width);
|
|
688
|
+
}
|
|
689
|
+
for (const [index, width] of this.normalizeColumnWidths(sheet.columnWidths ?? {})) {
|
|
690
|
+
out.set(index, width);
|
|
691
|
+
}
|
|
692
|
+
return new Map([...out.entries()].sort((a, b) => a[0] - b[0]));
|
|
693
|
+
}
|
|
631
694
|
normalizeColumnWidths(widths) {
|
|
632
695
|
const out = /* @__PURE__ */ new Map();
|
|
633
696
|
for (const [key, px] of Object.entries(widths)) {
|
|
634
|
-
|
|
697
|
+
const index = ColumnWidths.index(key);
|
|
698
|
+
const width = ColumnWidths.width(px);
|
|
699
|
+
if (index === null || width === null) continue;
|
|
700
|
+
out.set(index, width);
|
|
635
701
|
}
|
|
636
702
|
return out;
|
|
637
703
|
}
|
|
@@ -1329,6 +1395,30 @@ var Repairer = class {
|
|
|
1329
1395
|
sheet.theme = "default";
|
|
1330
1396
|
this.repairs.push(`changed '${path}.theme' from '${original}' to 'default' (unknown theme)`);
|
|
1331
1397
|
}
|
|
1398
|
+
if (sheet.columnWidths != null && (isPlainObject(sheet.columnWidths) || Array.isArray(sheet.columnWidths))) {
|
|
1399
|
+
const widths = {};
|
|
1400
|
+
let changed = false;
|
|
1401
|
+
for (const [key, px] of Object.entries(sheet.columnWidths)) {
|
|
1402
|
+
let index = ColumnWidths.index(key);
|
|
1403
|
+
if (index === null) {
|
|
1404
|
+
const letters = ColumnWidths.fromLetters(key);
|
|
1405
|
+
if (letters !== null) {
|
|
1406
|
+
index = letters;
|
|
1407
|
+
this.repairs.push(`converted '${path}.columnWidths.${key}' to column index ${index}`);
|
|
1408
|
+
changed = true;
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
if (index === null || ColumnWidths.width(px) === null) {
|
|
1412
|
+
this.repairs.push(`dropped '${path}.columnWidths.${key}' (not a column index and a width)`);
|
|
1413
|
+
changed = true;
|
|
1414
|
+
continue;
|
|
1415
|
+
}
|
|
1416
|
+
widths[String(index)] = px;
|
|
1417
|
+
}
|
|
1418
|
+
if (changed) {
|
|
1419
|
+
sheet.columnWidths = widths;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1332
1422
|
if (sheet.cells != null && isPlainObject(sheet.cells)) {
|
|
1333
1423
|
const cleaned = {};
|
|
1334
1424
|
let changed = false;
|
|
@@ -1534,6 +1624,43 @@ var Validator = class {
|
|
|
1534
1624
|
)
|
|
1535
1625
|
);
|
|
1536
1626
|
}
|
|
1627
|
+
if (sheet.columnWidths != null) {
|
|
1628
|
+
if (!isPlainObject(sheet.columnWidths) && !Array.isArray(sheet.columnWidths)) {
|
|
1629
|
+
errors.push(
|
|
1630
|
+
error(
|
|
1631
|
+
`${path}.columnWidths`,
|
|
1632
|
+
"object keyed by 0-based column index",
|
|
1633
|
+
typeOf(sheet.columnWidths),
|
|
1634
|
+
sheet.columnWidths,
|
|
1635
|
+
'Column widths map a 0-based column index to pixels: {"0": 120, "1": 80}.'
|
|
1636
|
+
)
|
|
1637
|
+
);
|
|
1638
|
+
} else {
|
|
1639
|
+
for (const [key, px] of Object.entries(sheet.columnWidths)) {
|
|
1640
|
+
if (ColumnWidths.index(key) === null) {
|
|
1641
|
+
errors.push(
|
|
1642
|
+
error(
|
|
1643
|
+
`${path}.columnWidths.${key}`,
|
|
1644
|
+
`a 0-based column index from 0 to ${ColumnWidths.MAX_INDEX}`,
|
|
1645
|
+
typeOf(key),
|
|
1646
|
+
key,
|
|
1647
|
+
'Keys are 0-based column indexes: "0" is column A, "1" is column B. Use the index, not the letter.'
|
|
1648
|
+
)
|
|
1649
|
+
);
|
|
1650
|
+
} else if (ColumnWidths.width(px) === null) {
|
|
1651
|
+
errors.push(
|
|
1652
|
+
error(
|
|
1653
|
+
`${path}.columnWidths.${key}`,
|
|
1654
|
+
"a non-negative number of pixels",
|
|
1655
|
+
typeOf(px),
|
|
1656
|
+
px,
|
|
1657
|
+
"A width is a number of pixels, like 120."
|
|
1658
|
+
)
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1537
1664
|
return errors;
|
|
1538
1665
|
}
|
|
1539
1666
|
validateColumn(col, path) {
|
|
@@ -1565,6 +1692,17 @@ var Validator = class {
|
|
|
1565
1692
|
)
|
|
1566
1693
|
);
|
|
1567
1694
|
}
|
|
1695
|
+
if (col.width !== void 0 && ColumnWidths.width(col.width) === null) {
|
|
1696
|
+
errors.push(
|
|
1697
|
+
error(
|
|
1698
|
+
`${path}.width`,
|
|
1699
|
+
"a non-negative number of pixels",
|
|
1700
|
+
typeOf(col.width),
|
|
1701
|
+
col.width,
|
|
1702
|
+
"A column width is a number of pixels, like 120. The sheet-level columnWidths map takes precedence over it."
|
|
1703
|
+
)
|
|
1704
|
+
);
|
|
1705
|
+
}
|
|
1568
1706
|
return errors;
|
|
1569
1707
|
}
|
|
1570
1708
|
};
|
|
@@ -5041,7 +5179,7 @@ var holy_sheet_schema_default = {
|
|
|
5041
5179
|
};
|
|
5042
5180
|
|
|
5043
5181
|
// src/agent.ts
|
|
5044
|
-
var VERSION = "2.
|
|
5182
|
+
var VERSION = "2.5.0";
|
|
5045
5183
|
function toU8(input) {
|
|
5046
5184
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
5047
5185
|
}
|