@quario/xlsx 0.1.0 → 0.3.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 CHANGED
@@ -7,6 +7,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2026-09-02
11
+
12
+ ### Added
13
+
14
+ - **`format` maps to a number format; the cell stays typed.** `number` is
15
+ `#,##0.00`, `percent` `0.00%`, `date` `yyyy-mm-dd`, `currency` the
16
+ instance currency code as `"USD"#,##0.00`. A kind on the wrong type
17
+ contributes nothing.
18
+
19
+ - **Cell borders map; padding and flow spacing do not.** `solid` is exceljs
20
+ `thin`; `dashed` and `dotted` keep their names. A header-row, row, or
21
+ total-row box fans onto that row's cells; a cell that named any of a
22
+ side's three keys owns that side whole. Padding is unread: a worksheet
23
+ cell has no inset. Flow spacing is unread: a grid has no flow.
24
+
25
+ - **`page.margin` and report-header `height` are unread.** A grid has no
26
+ page top to pin from.
27
+
28
+ ### Changed
29
+
30
+ - **A table total emits N rows.** Each `total-row` is one worksheet row, as
31
+ each data row is.
32
+
33
+ - **A cell whose display contains a newline wraps.** `wrapText` is this
34
+ target's mapping of a literal newline as a line break, so `"one\ntwo"` is
35
+ two lines in the grid. Single-line cells are unchanged.
36
+
37
+ ## [0.2.0] - 2026-09-01
38
+
39
+ ### Added
40
+
41
+ - **The report default reaches every cell.** A report's top-level `style`
42
+ replaces this target's baseline, so a document declaring a face and size gets
43
+ them in cells that declare nothing of their own. It is the layer under the
44
+ band-role defaults — a report declaring `size: 12` still writes its report
45
+ header at 14 — and under each cell's own style.
46
+
47
+ - **`uppercase` is accepted and deliberately not read.** A spreadsheet font
48
+ has no text-transform, and writing capitals into the cell instead would turn
49
+ presentation into data — the cell would stop round-tripping and would sort
50
+ differently. The cell keeps the text you wrote; every other declaration on
51
+ it still applies. Same posture as the column widths this target withdrew.
52
+
53
+ - **A split is one row, its slots the cells across it.** The grid's own
54
+ reading of values placed beside each other. Slot `width` shares go unread,
55
+ on exactly the ground the table's column widths do — a worksheet's columns
56
+ are global to the sheet. The split's own style is what its slots sit under,
57
+ layering the way a table row's does, and a slot that renders nothing writes
58
+ an empty cell so the cells either side keep their columns.
59
+
60
+ ### Changed
61
+
62
+ - **Every cell is written at 10 points unless something declares otherwise.**
63
+ The size was previously left to the writer's own default of 11. Now that a
64
+ report can declare its own size, this target and the PDF carrying different
65
+ numbers for one declaration is exactly what the targets' agreement rule
66
+ forbids. A worksheet's row height follows its font size, so existing unstyled
67
+ sheets come out slightly tighter.
68
+
69
+ - **Display-text `Date`s join as ISO 8601 UTC.** A `Date` inside a mixed
70
+ cell (literal text plus interpolation) joined through `String(date)`, which
71
+ bakes the host's timezone and locale into the worksheet. It now joins
72
+ through the engine's shared display rule as `toISOString()` text. Typed
73
+ cells are untouched: a cell that is one bare `Date` interpolation still
74
+ writes a native date.
75
+
76
+ ### Fixed
77
+
78
+ - **Cells carry their font face explicitly.** Text declaring no `family` used
79
+ to inherit whatever the workbook writer defaulted to, which agreed with the
80
+ other targets only by coincidence. It now writes the same face that
81
+ `family: "sans"` resolves to, so the baseline is a rule rather than a
82
+ property of the library underneath.
83
+
10
84
  ## [0.1.0] - 2026-08-27
11
85
 
12
86
  ### Added
package/lib/cell.js CHANGED
@@ -3,24 +3,63 @@
3
3
  * the engine's `typed` stream rule. `value` is a native number, boolean, or
4
4
  * Date when the cell is one bare interpolation (so numbers stay computable in
5
5
  * the sheet), and the display-text join otherwise. `format` is the resolved
6
- * cell-level formatting from the style module.
6
+ * cell-level formatting from the style module. Display text that contains a
7
+ * newline gains `wrapText` so the break is visible in the grid.
7
8
  */
8
9
  import { text, typed } from "quario";
9
- import { format, merge } from "./style.js";
10
+ import { format as look, sides } from "./style.js";
11
+
12
+ /** @type {(value: any, fmt: string) => string | null} */
13
+ let whenNum = (value, fmt) => (typeof value === "number" ? fmt : null);
14
+ /** @type {(value: any) => string | null} */
15
+ let whenDate = (value) => (value instanceof Date ? "yyyy-mm-dd" : null);
16
+ /** @type {(value: any, currency: any) => string | null} */
17
+ let whenMoney = (value, currency) =>
18
+ typeof value === "number" && typeof currency === "string" && currency
19
+ ? '"' + currency + '"#,##0.00'
20
+ : null;
21
+ /** @type {Record<string, (value: any, currency: any) => string | null>} */
22
+ let NUMFMT = {
23
+ number: (value) => whenNum(value, "#,##0.00"),
24
+ percent: (value) => whenNum(value, "0.00%"),
25
+ date: (value) => whenDate(value),
26
+ currency: (value, currency) => whenMoney(value, currency),
27
+ };
28
+ /** @type {(kind: any, value: any, currency: any) => string | null} */
29
+ let numFmtOf = (kind, value, currency) => {
30
+ let rule = NUMFMT[kind];
31
+ return rule ? rule(value, currency) : null;
32
+ };
33
+
34
+ /** @type {(cell: { tokens: any[] }) => any} */
35
+ let shownOf = (cell) => {
36
+ let value = typed(cell.tokens);
37
+ return value !== undefined ? value : text(cell.tokens);
38
+ };
39
+ /** @type {(out: any, resolved: any, shown: any, intl: any) => void} */
40
+ let paintFmt = (out, resolved, shown, intl) => {
41
+ let numFmt = numFmtOf(resolved?.format, shown, intl?.currency);
42
+ if (numFmt) out.numFmt = numFmt;
43
+ };
44
+ /** @type {(out: any, shown: any) => void} */
45
+ let paintWrap = (out, shown) => {
46
+ if (typeof shown === "string" && /[\r\n]/.test(shown))
47
+ out.alignment = { ...out.alignment, wrapText: true };
48
+ };
10
49
 
11
50
  /**
12
51
  * Resolve one event cell against its enclosing style (a row's style, or an
13
52
  * item's band-role default — the layer under the cell's own).
14
53
  *
15
- * @type {(cell: { tokens: any[], style?: any }, under: any) => { value: any, format: any }}
54
+ * @type {(cell: { tokens: any[], style?: any }, under: any, intl?: any) => { value: any, format: any }}
16
55
  */
17
- let field = (cell, under) => {
18
- let resolved = merge(under, cell.style);
19
- let value = typed(cell.tokens);
20
- return {
21
- value: value !== undefined ? value : text(cell.tokens),
22
- format: format(resolved),
23
- };
56
+ let field = (cell, under, intl) => {
57
+ let resolved = sides(under, cell.style);
58
+ let shown = shownOf(cell);
59
+ let out = look(resolved);
60
+ paintFmt(out, resolved, shown, intl);
61
+ paintWrap(out, shown);
62
+ return { value: shown, format: out };
24
63
  };
25
64
 
26
65
  export { field };
package/lib/image.js CHANGED
@@ -13,9 +13,6 @@
13
13
  /** @type {(bytes: Uint8Array, at: number) => number} */
14
14
  let word = (bytes, at) => (bytes[at] << 8) | bytes[at + 1];
15
15
 
16
- /** @type {(bytes: Uint8Array) => { width: number, height: number }} */
17
- let png = (bytes) => ({ width: word(bytes, 18), height: word(bytes, 22) });
18
-
19
16
  // The dimensions live in the frame header, the first SOFn marker: every
20
17
  // code in C0..CF except the three in that range that are not frames --
21
18
  // DHT, the JPG extension, and DAC.
@@ -42,6 +39,7 @@ let jpeg = (bytes) => {
42
39
  * @returns {{ width: number, height: number } | null} The size, if readable.
43
40
  */
44
41
  export let pixels = (bytes, format) => {
45
- let size = format === "png" ? png(bytes) : jpeg(bytes);
42
+ // A PNG carries the two numbers in its IHDR at a fixed offset.
43
+ let size = format === "png" ? { width: word(bytes, 18), height: word(bytes, 22) } : jpeg(bytes);
46
44
  return size.width > 0 && size.height > 0 ? size : null;
47
45
  };
package/lib/index.js CHANGED
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import { walk } from "quario";
19
19
  import { field } from "./cell.js";
20
+ import { sides as under } from "./style.js";
20
21
  import { pixels } from "./image.js";
21
22
  import { append, create, embed, freeze, mark, place, save, sheet } from "./workbook.js";
22
23
 
@@ -40,6 +41,14 @@ import { append, create, embed, freeze, mark, place, save, sheet } from "./workb
40
41
  /** @type {Record<string, any>} */
41
42
  let ROLES = { "report-header": { bold: true, size: 14 }, "group-header": { bold: true } };
42
43
 
44
+ // This target's baseline type size, written into every cell rather than left
45
+ // to the writer's own 11. The PDF target carries the same 10, and once a report
46
+ // can declare its own `style.size` the two supplying different numbers for one
47
+ // declaration is exactly what docs/adr/0014's corollary forbids. A worksheet's
48
+ // row height follows its font size, so this is visible in the grid as well as
49
+ // in the type (docs/adr/0033).
50
+ let BASELINE = { size: 10 };
51
+
43
52
  // The unlicensed-output marking (LICENSE section 6): one styled banner row
44
53
  // above the report, written only on keyless renders through the same cell
45
54
  // path as every other row. This is the banner's look; the wording arrives on
@@ -71,22 +80,76 @@ export function xlsx(options) {
71
80
  // down a thousand rows is one copy in the file rather than a thousand.
72
81
  /** @type {Map<Uint8Array, any>} */
73
82
  let images = new Map();
83
+ // The layers under an event's own style, outermost first: this target's
84
+ // baseline, then the report default the author declared, then the band-role
85
+ // default if the role carries one. `base` is the first two, settled once
86
+ // per render off `report-start`.
87
+ let base = BASELINE;
88
+ /** @type {{ locale?: string, currency?: string, timeZone?: string } | null} */
89
+ let intl = null;
90
+ // One cell, wearing everything beneath it. Every cell in the sheet goes
91
+ // through here rather than through `field` directly, so `base` -- this
92
+ // target's baseline plus the author's report default -- cannot be
93
+ // forgotten by a call site that means "nothing encloses this". The
94
+ // marking is the one deliberate exception, and says so where it is
95
+ // written.
96
+ let cell = (/** @type {any} */ value, /** @type {any} */ over = null) =>
97
+ field(value, under(base, over), intl);
98
+ // The band-role default an event sits under, if its role carries one.
99
+ let roleOf = (/** @type {any} */ event) =>
100
+ Object.hasOwn(ROLES, event.role) ? ROLES[event.role] : null;
101
+ // The split being filled, if any: the fields its slots have written and
102
+ // the pictures owed an anchor, which is the row the whole split lands on.
103
+ // Splits never nest, so one is enough.
104
+ /** @type {{ fields: any[], images: any[], under: any } | null} */
105
+ let split = null;
74
106
  // A table row and a total row differ only in the style they sit under.
75
- let record = (/** @type {any} */ event, /** @type {any} */ under) =>
107
+ let record = (/** @type {any} */ event, /** @type {any} */ layer) =>
76
108
  append(
77
109
  worksheet,
78
- event.cells.map((/** @type {any} */ cell) => field(cell, under)),
110
+ event.cells.map((/** @type {any} */ each) => cell(each, layer)),
79
111
  );
80
112
  await walk(stream(data), {
81
113
  "report-start": (event) => {
114
+ // The report default over this target's baseline. The marking is
115
+ // written under the baseline alone: an author's `style` must not be
116
+ // able to resize it (docs/adr/0002).
117
+ intl = { locale: event.locale, currency: event.currency, timeZone: event.timeZone };
118
+ if (event.style) base = under(BASELINE, event.style);
82
119
  if (event.marking) {
83
120
  mark(workbook, event.marking);
84
- append(worksheet, [field({ tokens: [{ literal: event.marking }], style: BANNER }, null)]);
121
+ append(worksheet, [
122
+ field({ tokens: [{ literal: event.marking }], style: BANNER }, BASELINE),
123
+ ]);
85
124
  }
86
125
  },
87
126
  item: (event) => {
88
- let role = Object.hasOwn(ROLES, event.role) ? ROLES[event.role] : null;
89
- append(worksheet, [field({ tokens: event.tokens, style: event.style }, role)]);
127
+ let written = cell(
128
+ { tokens: event.tokens, style: event.style },
129
+ split ? split.under : roleOf(event),
130
+ );
131
+ if (split) split.fields.push(written);
132
+ else append(worksheet, [written]);
133
+ },
134
+ // A split is one row, its slots the cells across it -- the grid's own
135
+ // reading of items placed beside each other. Slot `width` shares go
136
+ // unread on the same ground the table's column widths do: a worksheet's
137
+ // columns are global to the sheet. The split's own style is what its
138
+ // slots sit under, exactly as a table row's is.
139
+ "split-start": (event) => {
140
+ // The split's own style layers over its band-role default, so a styled
141
+ // split keeps the weight a plain item in the same band would have.
142
+ split = { fields: [], images: [], under: under(roleOf(event), event.style) };
143
+ },
144
+ "split-end": () => {
145
+ // The engine emits the bracket as one array, so a `split-end` always
146
+ // has its opening and `split` is never null here.
147
+ let owed = /** @type {{ fields: any[], images: any[] }} */ (split);
148
+ split = null;
149
+ let row = append(worksheet, owed.fields);
150
+ // A picture in a slot floats from the split's own row: the cell under
151
+ // it holds the slot so the fields either side keep their columns.
152
+ for (let known of owed.images) place(worksheet, known.id, known, row);
90
153
  },
91
154
  // An image is not a cell: it takes one row as its anchor and floats
92
155
  // over the sheet from there, at its own pixel size. `fit` is not read
@@ -106,12 +169,21 @@ export function xlsx(options) {
106
169
  known = { ...size, id: embed(workbook, event.bytes, event.format) };
107
170
  images.set(event.bytes, known);
108
171
  }
172
+ if (split) {
173
+ // The slot keeps its cell, empty, and the picture waits for the row
174
+ // the whole split lands on.
175
+ split.fields.push(cell({ tokens: [] }));
176
+ split.images.push(known);
177
+ return;
178
+ }
109
179
  // The anchor: a row of its own, with nothing written in it, which the
110
180
  // picture then floats over.
111
181
  place(worksheet, known.id, known, append(worksheet, []));
112
182
  },
113
183
  "table-start": (event) => {
114
- let headers = event.columns.map((/** @type {any} */ column) => field(column.header, null));
184
+ let headers = event.columns.map((/** @type {any} */ column) =>
185
+ cell(column.header, event.style),
186
+ );
115
187
  let row = append(worksheet, headers);
116
188
  if (!frozen) {
117
189
  freeze(worksheet, row);
@@ -119,7 +191,7 @@ export function xlsx(options) {
119
191
  }
120
192
  },
121
193
  row: (event) => record(event, event.style),
122
- "total-row": (event) => record(event, null),
194
+ "total-row": (event) => record(event, event.style),
123
195
  });
124
196
  return save(workbook);
125
197
  };
package/lib/style.js CHANGED
@@ -11,14 +11,17 @@
11
11
  */
12
12
 
13
13
  /** @type {(value: any) => boolean} */
14
- let finite = (value) => typeof value === "number" && Number.isFinite(value);
14
+ let finite = (value) => Number.isFinite(value);
15
15
  let HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
16
+ let SIDES = ["Top", "Right", "Bottom", "Left"];
17
+ let BORDER_PARTS = ["Width", "Style", "Color"];
18
+ /** @type {Record<string, string>} */
19
+ let LINE = { solid: "thin", dashed: "dashed", dotted: "dotted" };
16
20
 
17
21
  // The generic families as the fonts spreadsheet apps ship with; any other
18
22
  // name passes through verbatim for the host application to resolve.
19
23
  /** @type {Record<string, string>} */
20
24
  let FAMILY = { sans: "Calibri", serif: "Times New Roman", mono: "Courier New" };
21
- let ALIGNMENTS = ["left", "center", "right"];
22
25
 
23
26
  // A declared colour as an ARGB string, or null when the value is not one.
24
27
  /** @type {(value: any) => string | null} */
@@ -30,9 +33,28 @@ let argb = (value) => {
30
33
  return "FF" + digits.toUpperCase();
31
34
  };
32
35
 
33
- // Style blocks layer outward-in: row under cell.
36
+ // Style blocks layer outward-in: row under cell. A border side is won whole
37
+ // by the inner block when that block named any of its three keys, so a cell
38
+ // that failed-soft on a side does not pick up the row's other two names.
34
39
  /** @type {(under: any, over: any) => any} */
35
40
  let merge = (under, over) => (under ? (over ? { ...under, ...over } : under) : over || {});
41
+ /** @type {(style: any, side: string) => boolean} */
42
+ let owns = (style, side) =>
43
+ !!style && BORDER_PARTS.some((part) => Object.hasOwn(style, "border" + side + part));
44
+ /** @type {(out: any, over: any, side: string) => void} */
45
+ let take = (out, over, side) => {
46
+ for (let part of BORDER_PARTS) {
47
+ let name = "border" + side + part;
48
+ if (Object.hasOwn(over, name)) out[name] = over[name];
49
+ else delete out[name];
50
+ }
51
+ };
52
+ /** @type {(under: any, over: any) => any} */
53
+ let sides = (under, over) => {
54
+ let out = merge(under, over);
55
+ if (over) for (let side of SIDES) if (owns(over, side)) take(out, over, side);
56
+ return out;
57
+ };
36
58
 
37
59
  /** @type {(out: any, key: string, value: any) => any} */
38
60
  let put = (out, key, value) => {
@@ -41,9 +63,13 @@ let put = (out, key, value) => {
41
63
  return out;
42
64
  };
43
65
 
66
+ // The face a declared family names, or the baseline one an undeclared family
67
+ // resolves to: text that declares nothing renders in the same face
68
+ // `family: "sans"` does, written rather than left to the writer's own default
69
+ // (docs/adr/0014). One answer, in one place.
44
70
  /** @type {(family: any) => string} */
45
71
  let face = (family) => {
46
- if (typeof family !== "string" || !family) return "";
72
+ if (typeof family !== "string" || !family) return FAMILY.sans;
47
73
  let key = family.toLowerCase();
48
74
  // Own-key lookup: `constructor` must not resolve an inherited member.
49
75
  return Object.hasOwn(FAMILY, key) ? FAMILY[key] : family;
@@ -59,8 +85,8 @@ let tint = (value) => {
59
85
  return color ? { argb: color } : null;
60
86
  };
61
87
 
62
- // The font half of a resolved style, or null when the style declares none
63
- // of it.
88
+ // The font half of a resolved style. Never null: every cell carries the
89
+ // baseline face even when it declares nothing else.
64
90
  /** @type {(style: any) => any} */
65
91
  let font = (style) => {
66
92
  let out = put(null, "name", face(style.family));
@@ -70,6 +96,12 @@ let font = (style) => {
70
96
  out = put(out, "underline", flag(style.underline));
71
97
  // exceljs names the flag `strike`; the authoring surface says strikethrough.
72
98
  out = put(out, "strike", flag(style.strikethrough));
99
+ // `uppercase` is deliberately unread: a spreadsheet font has no
100
+ // text-transform (OOXML's font carries none, and neither does the writer's
101
+ // model), and uppercasing the string instead would make presentation into
102
+ // data -- the cell would stop round-tripping and sort differently. A
103
+ // mapping quario cannot honestly make is withdrawn rather than invented,
104
+ // exactly as the column widths were (docs/adr/0008).
73
105
  return put(out, "color", tint(style.color));
74
106
  };
75
107
 
@@ -79,14 +111,40 @@ let fill = (value) => {
79
111
  return color ? { type: "pattern", pattern: "solid", fgColor: { argb: color } } : null;
80
112
  };
81
113
  /** @type {(value: any) => any} */
82
- let align = (value) => (ALIGNMENTS.includes(value) ? { horizontal: value } : null);
114
+ let align = (value) =>
115
+ value === "left" || value === "center" || value === "right" ? { horizontal: value } : null;
116
+
117
+ /** @type {(width: any) => boolean} */
118
+ let isStroke = (width) => finite(width) && width > 0;
119
+ /** @type {(named: any) => string | null} */
120
+ let lineOf = (named) => {
121
+ if (typeof named !== "string") return null;
122
+ return Object.hasOwn(LINE, named) ? LINE[named] : null;
123
+ };
124
+
125
+ /** @type {(style: any, side: string) => any} */
126
+ let edge = (style, side) => {
127
+ let line = lineOf(style["border" + side + "Style"]);
128
+ let color = argb(style["border" + side + "Color"]);
129
+ if (!isStroke(style["border" + side + "Width"]) || !line || !color) return null;
130
+ return { style: line, color: { argb: color } };
131
+ };
132
+
133
+ /** @type {(style: any) => any} */
134
+ let border = (style) => {
135
+ let out = null;
136
+ for (let side of SIDES) out = put(out, side.toLowerCase(), edge(style, side));
137
+ return out;
138
+ };
83
139
 
84
140
  // The whole-cell reading: the font plus the parts only a cell can carry.
141
+ // Padding is unread: a worksheet cell has no inset (docs/adr/0008).
85
142
  /** @type {(style: any) => any} */
86
143
  let format = (style) => {
87
144
  let out = put(null, "font", font(style));
88
145
  out = put(out, "fill", fill(style.background));
146
+ out = put(out, "border", border(style));
89
147
  return put(out, "alignment", align(style.align));
90
148
  };
91
149
 
92
- export { format, merge };
150
+ export { format, sides };
package/lib/workbook.js CHANGED
@@ -9,14 +9,13 @@
9
9
  */
10
10
  import ExcelJS from "exceljs";
11
11
 
12
- // Host metadata keys as the workbook properties they become.
13
- /** @type {Record<string, string>} */
14
- let META = { title: "title", author: "creator", subject: "subject" };
15
- /** @type {(workbook: any, meta: any) => any} */
12
+ // Host metadata keys as the workbook properties they become -- `author` is the
13
+ // only one exceljs names differently.
14
+ /** @type {(workbook: any, meta: any) => void} */
16
15
  let describe = (workbook, meta) => {
17
- if (!meta) return workbook;
18
- for (let from in META) if (typeof meta[from] === "string") workbook[META[from]] = meta[from];
19
- return workbook;
16
+ if (typeof meta.title === "string") workbook.title = meta.title;
17
+ if (typeof meta.author === "string") workbook.creator = meta.author;
18
+ if (typeof meta.subject === "string") workbook.subject = meta.subject;
20
19
  };
21
20
 
22
21
  // A fresh workbook. Never the current time: a timestamp would make the same
@@ -26,16 +25,17 @@ let create = (meta) => {
26
25
  let workbook = new ExcelJS.Workbook();
27
26
  workbook.created = new Date(0);
28
27
  workbook.modified = new Date(0);
29
- return describe(workbook, meta);
28
+ if (meta) describe(workbook, meta);
29
+ return workbook;
30
30
  };
31
31
 
32
32
  /** @type {(workbook: any) => any} */
33
33
  let sheet = (workbook) => workbook.addWorksheet("Report");
34
34
 
35
- let KEYS = ["font", "fill", "alignment"];
35
+ let SLOTS = ["font", "fill", "border", "alignment", "numFmt"];
36
36
  /** @type {(cell: any, format: any) => void} */
37
37
  let paint = (cell, format) => {
38
- for (let key of KEYS) if (format[key]) cell[key] = format[key];
38
+ for (let key of SLOTS) if (format[key]) cell[key] = format[key];
39
39
  };
40
40
 
41
41
  // Write one worksheet row from cell descriptors; returns the row number.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/xlsx",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "The spreadsheet render target for quario — in the makings, not yet released",
5
5
  "homepage": "https://getquario.com",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -42,12 +42,12 @@
42
42
  "@arethetypeswrong/cli": "^0.18.3",
43
43
  "@size-limit/preset-small-lib": "^13.0.3",
44
44
  "@types/node": "^22.20.1",
45
- "quario": "^0.1.0",
45
+ "quario": "^0.3.0",
46
46
  "size-limit": "^13.0.3",
47
47
  "typescript": "^7.0.2"
48
48
  },
49
49
  "peerDependencies": {
50
- "quario": "^0.1.0"
50
+ "quario": "^0.3.0"
51
51
  },
52
52
  "size-limit": [
53
53
  {
@@ -56,7 +56,7 @@
56
56
  "quario",
57
57
  "exceljs"
58
58
  ],
59
- "limit": "2 kB"
59
+ "limit": "2.4 kB"
60
60
  }
61
61
  ],
62
62
  "engines": {