@quario/xlsx 0.7.0 → 0.7.1

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
@@ -1,15 +1,24 @@
1
- # Changelog
1
+ # @quario/xlsx
2
2
 
3
- All notable changes to @quario/xlsx are documented in this file.
3
+ ## 0.7.1
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
5
+ ### Patch Changes
7
6
 
8
- ## [Unreleased]
7
+ - **Every published README says where the documentation is.** Each package now
8
+ carries a Documentation section pointing at the reference, at the report schema
9
+ that normatively specifies what a report may declare, and at the package's own
10
+ API. The paragraphs that used to end on an unstated contract — the event
11
+ stream's field semantics, the style vocabulary, page columns, the Content
12
+ Security Policy a fragment with images needs, the formula mangling, and each
13
+ target's own contract — link the page that states it. Every link is an absolute
14
+ URL, so it resolves from the npm package page as readily as from an installed
15
+ copy.
16
+ - Updated dependencies
17
+ - quario@0.8.0
9
18
 
10
- ## [0.7.0] - 2026-09-07
19
+ ## 0.7.0
11
20
 
12
- ### Added
21
+ ### Minor Changes
13
22
 
14
23
  - **Styled runs become rich text.** A cell of more than one styled run is written
15
24
  as rich text, each run's font merged over the cell's — so bold, italic,
@@ -18,14 +27,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
18
27
  only, and filling the cell would colour text nobody asked to colour. A cell of
19
28
  exactly one run stays a typed cell, so a single styled amount is still a
20
29
  number.
21
-
22
30
  - **A run inside a sentence presents its value.** A run holding one
23
31
  interpolation under a `format` shows the same decimals here as on the page,
24
32
  where a mixed cell used to show the bare value.
25
33
 
26
- ## [0.6.0] - 2026-09-07
34
+ ## 0.6.0
27
35
 
28
- ### Added
36
+ ### Minor Changes
29
37
 
30
38
  - **A date cell's number format follows the `form` its report declares.** The
31
39
  four forms map to `dd/mm/yy`, `dd mmm yyyy`, `dd mmmm yyyy` and
@@ -33,26 +41,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
33
41
  weekday name is spelled in stays the reader's, because that is what a
34
42
  spreadsheet application supplies and pinning it would stop the file reading
35
43
  naturally for whoever opens it.
36
-
37
44
  - **A declared digit count builds the pattern.** A `number` cell asking for
38
45
  three digits writes `#,##0.000`, and a `currency` cell asking for none writes
39
46
  `"EUR"#,##0` — the same count the page presents, taken off the declaration
40
47
  the engine resolved rather than computed here.
41
-
42
48
  - **A cell's own `currency` code picks the number format**, ahead of the
43
49
  instance's default: `"JPY"#,##0` where the row says `JPY`, since the code
44
50
  decides the digits too. The cell stays a number either way. A code the engine
45
51
  could not accept writes no number format at all, rather than labelling the
46
52
  cell in the instance's currency.
47
-
48
- ### Changed
49
-
50
53
  - **A `date` cell with no declared form now writes `dd mmm yyyy`, not
51
54
  `yyyy-mm-dd`.** A bare `date` means the `medium` form throughout, so the grid
52
55
  agrees with the page. A report that wants the ISO shape has no form for it;
53
56
  the nearest is `{ "kind": "date", "form": "short" }`, which writes
54
57
  `dd/mm/yy`. The cell is still a real typed date either way.
55
-
56
58
  - **A cell's number format now comes from the engine's fraction-digit count
57
59
  rather than from four patterns this target held of its own.** The visible
58
60
  change is `currency`: the pattern followed the currency's own minor units,
@@ -61,7 +63,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
61
63
  (`#,##0.00`), `percent` (`0.00%`) and `date` (`yyyy-mm-dd`) are unchanged in
62
64
  a worksheet; what changed for those is that HTML and PDF now show the same
63
65
  decimals the grid always did, instead of their own.
64
-
65
66
  - A `currency` code that is not a readable currency no longer produces a number
66
67
  format at all. The cell keeps its value; before, the code was pasted straight
67
68
  into a pattern while every other target fell back to the unformatted value.
@@ -70,39 +71,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
71
  position still come from the application that opens the file, which is what
71
72
  lets one workbook read naturally wherever it is opened.
72
73
 
73
- ### Fixed
74
-
75
74
  - A `format` kind reaching this target from an `=` expression is now looked up
76
75
  as an own key. Before, a kind resolving to the name of a built-in object
77
76
  member threw out of the render (`valueOf`, `hasOwnProperty`) or wrote
78
77
  `[object Undefined]` into the cell's number format (`toString`). A literal
79
78
  was never affected: it is checked against the four kinds.
80
-
81
- ### Fixed
82
-
83
79
  - **An image whose size cannot be read now names the item that asked for it.**
84
80
  The failure said only that the size could not be read from the bytes, so a
85
81
  report with two pictures gave no way to tell which one was bad. The message
86
82
  now begins with the item's `source` path, as every other render error does
87
83
  and as it does from every other target.
88
84
 
89
- ## [0.5.0] - 2026-09-05
85
+ ## 0.5.0
90
86
 
91
- ### Added
87
+ ### Minor Changes
92
88
 
93
89
  - **Chrome merges, data does not.** A cell spanning several columns in the
94
90
  header row or a total row is written into the first column it covers, the
95
91
  rest of the range is written empty, and the range is merged. Nothing in the
96
92
  data region merges, so sort, filter and column selection keep working over
97
93
  it.
98
-
99
94
  - **`valign`** writes the cell's vertical alignment for table cells and split
100
95
  slots. Undeclared writes nothing, so the spreadsheet application keeps its
101
96
  own default.
102
97
 
103
- ## [0.4.0] - 2026-09-03
98
+ ## 0.4.0
104
99
 
105
- ### Changed
100
+ ### Minor Changes
106
101
 
107
102
  - **A date string under `format: "date"` now reaches the grid as a date.**
108
103
  The cell carries a real date value under the `yyyy-mm-dd` number format
@@ -110,83 +105,68 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
110
105
  cell declaring the kind is affected; a string in a form the engine does not
111
106
  read stays text. See the `quario` changelog for the forms.
112
107
 
113
- ## [0.3.0] - 2026-09-02
108
+ ## 0.3.0
114
109
 
115
- ### Added
110
+ ### Minor Changes
116
111
 
117
112
  - **`format` maps to a number format; the cell stays typed.** `number` is
118
113
  `#,##0.00`, `percent` `0.00%`, `date` `yyyy-mm-dd`, `currency` the
119
114
  instance currency code as `"USD"#,##0.00`. A kind on the wrong type
120
115
  contributes nothing.
121
-
122
116
  - **Cell borders map; padding and flow spacing do not.** `solid` is exceljs
123
117
  `thin`; `dashed` and `dotted` keep their names. A header-row, row, or
124
118
  total-row box fans onto that row's cells; a cell that named any of a
125
119
  side's three keys owns that side whole. Padding is unread: a worksheet
126
120
  cell has no inset. Flow spacing is unread: a grid has no flow.
127
-
128
121
  - **`page.margin` and report-header `height` are unread.** A grid has no
129
122
  page top to pin from.
130
-
131
- ### Changed
132
-
133
123
  - **A table total emits N rows.** Each `total-row` is one worksheet row, as
134
124
  each data row is.
135
-
136
125
  - **A cell whose display contains a newline wraps.** `wrapText` is this
137
126
  target's mapping of a literal newline as a line break, so `"one\ntwo"` is
138
127
  two lines in the grid. Single-line cells are unchanged.
139
128
 
140
- ## [0.2.0] - 2026-09-01
129
+ ## 0.2.0
141
130
 
142
- ### Added
131
+ ### Minor Changes
143
132
 
144
133
  - **The report default reaches every cell.** A report's top-level `style`
145
134
  replaces this target's baseline, so a document declaring a face and size gets
146
135
  them in cells that declare nothing of their own. It is the layer under the
147
136
  band-role defaults — a report declaring `size: 12` still writes its report
148
137
  header at 14 — and under each cell's own style.
149
-
150
138
  - **`uppercase` is accepted and deliberately not read.** A spreadsheet font
151
139
  has no text-transform, and writing capitals into the cell instead would turn
152
140
  presentation into data — the cell would stop round-tripping and would sort
153
141
  differently. The cell keeps the text you wrote; every other declaration on
154
142
  it still applies. Same posture as the column widths this target withdrew.
155
-
156
143
  - **A split is one row, its slots the cells across it.** The grid's own
157
144
  reading of values placed beside each other. Slot `width` shares go unread,
158
145
  on exactly the ground the table's column widths do — a worksheet's columns
159
146
  are global to the sheet. The split's own style is what its slots sit under,
160
147
  layering the way a table row's does, and a slot that renders nothing writes
161
148
  an empty cell so the cells either side keep their columns.
162
-
163
- ### Changed
164
-
165
149
  - **Every cell is written at 10 points unless something declares otherwise.**
166
150
  The size was previously left to the writer's own default of 11. Now that a
167
151
  report can declare its own size, this target and the PDF carrying different
168
152
  numbers for one declaration is exactly what the targets' agreement rule
169
153
  forbids. A worksheet's row height follows its font size, so existing unstyled
170
154
  sheets come out slightly tighter.
171
-
172
155
  - **Display-text `Date`s join as ISO 8601 UTC.** A `Date` inside a mixed
173
156
  cell (literal text plus interpolation) joined through `String(date)`, which
174
157
  bakes the host's timezone and locale into the worksheet. It now joins
175
158
  through the engine's shared display rule as `toISOString()` text. Typed
176
159
  cells are untouched: a cell that is one bare `Date` interpolation still
177
160
  writes a native date.
178
-
179
- ### Fixed
180
-
181
161
  - **Cells carry their font face explicitly.** Text declaring no `family` used
182
162
  to inherit whatever the workbook writer defaulted to, which agreed with the
183
163
  other targets only by coincidence. It now writes the same face that
184
164
  `family: "sans"` resolves to, so the baseline is a rule rather than a
185
165
  property of the library underneath.
186
166
 
187
- ## [0.1.0] - 2026-08-27
167
+ ## 0.1.0
188
168
 
189
- ### Added
169
+ ### Minor Changes
190
170
 
191
171
  - **A workbook from the same compiled report.** Bands become rows; a bare
192
172
  interpolation that is a number, boolean, or Date stays that type. The first
package/README.md CHANGED
@@ -110,8 +110,10 @@ on the wrong type, or a code the engine cannot read, writes no format.
110
110
 
111
111
  An image item is not a cell: it takes one worksheet row as its anchor and floats over the sheet as a
112
112
  drawing at its natural pixel size. Placement here is best-effort. `fit`, `alt`, and `style` are
113
- unused, and all of it may change without a breaking change. Bytes too short to carry a size reject
114
- the render rather than vanish.
113
+ unused, and all of it may change without a breaking change (see the
114
+ [support matrix](https://getquario.com/docs/reference/support-matrix/#output)). Bytes too short to
115
+ carry a size never reach this target: the engine reads every image's size and rejects them for
116
+ every target alike.
115
117
 
116
118
  ### Styles
117
119
 
@@ -145,8 +147,9 @@ table for a share to be a share of.
145
147
 
146
148
  ### Page columns are withdrawn
147
149
 
148
- A page column count sizes and splits nothing here, and will not change, as `break: "page"` is
149
- ignored. Page columns are strips of a page, and this target has no page to strip.
150
+ A [page column](https://getquario.com/docs/reference/support-matrix/#page-and-pagination) count
151
+ sizes and splits nothing here, and will not change, as `break: "page"` is ignored. Page columns
152
+ are strips of a page, and this target has no page to strip.
150
153
 
151
154
  ## Unlicensed marking
152
155
 
@@ -161,6 +164,17 @@ The document model is deterministic: the workbook's dates are pinned rather than
161
164
  `meta` is opt-in. The **bytes** are not. The writer stamps archive entries with the packing time.
162
165
  Compare extracted content, not digests.
163
166
 
167
+ The full contract is [The XLSX target](https://getquario.com/docs/diving-deeper/xlsx-target/),
168
+ with each declaration's fate in the
169
+ [support matrix](https://getquario.com/docs/reference/support-matrix/).
170
+
171
+ ## Documentation
172
+
173
+ [The quario documentation](https://getquario.com/docs/) is the reference.
174
+ The [report schema](https://getquario.com/docs/reference/report-schema/) is the normative
175
+ specification of what a report may declare, and
176
+ [`@quario/xlsx`](https://getquario.com/docs/reference/xlsx/) is this package's own API.
177
+
164
178
  ## License
165
179
 
166
180
  Commercial software with readable source. Free, unlimited, watermarked evaluation; per-developer
package/lib/index.js CHANGED
@@ -15,10 +15,9 @@
15
15
  * the packing time. Byte-for-byte output is a goal for the writer that will
16
16
  * replace `workbook.js`.
17
17
  */
18
- import { imageError, walk } from "quario";
18
+ import { walk } from "quario";
19
19
  import { field } from "./cell.js";
20
20
  import { merge as under } from "./style.js";
21
- import { pixels } from "./image.js";
22
21
  import { append, create, embed, freeze, mark, place, save, sheet, span } from "./workbook.js";
23
22
 
24
23
  // The options are described once, in the hand-written public declarations, and
@@ -197,15 +196,14 @@ export function xlsx(options) {
197
196
  image: (event) => {
198
197
  let known = images.get(event.bytes);
199
198
  if (!known) {
200
- let size = pixels(event.bytes, event.format);
201
- // The engine vouched for the magic numbers, not for the rest of the
202
- // file. A header too short to carry a size leaves nothing to place a
203
- // drawing at, and failing beats dropping the picture in silence
204
- // (SCHEMA.md, "Image item"). Named on the item through the engine's
205
- // own mint, so this failure reads the same from here as it does from
206
- // a target that paints or embeds.
207
- if (!size) throw imageError(event.path, "could not read the image's size from its bytes");
208
- known = { ...size, id: embed(workbook, event.bytes, event.format) };
199
+ // The event states the size in pixels, which is the unit a floating
200
+ // drawing is placed in, so this target has nothing to work out: the
201
+ // engine read the header once for every target (`docs/adr/0067`).
202
+ known = {
203
+ width: event.width,
204
+ height: event.height,
205
+ id: embed(workbook, event.bytes, event.format),
206
+ };
209
207
  images.set(event.bytes, known);
210
208
  }
211
209
  if (split) {
package/lib/style.js CHANGED
@@ -22,6 +22,7 @@ let FAMILY = { sans: "Calibri", serif: "Times New Roman", mono: "Courier New" };
22
22
 
23
23
  // A declared colour as an ARGB string, or null when the value is not one.
24
24
  /** @type {(value: any) => string | null} */
25
+ // fallow-ignore-next-line code-duplication -- each target reads a declared hex into its own type; neither may import a sibling, and the engine's stream is all they share
25
26
  let argb = (value) => {
26
27
  let match = typeof value === "string" && HEX.exec(value);
27
28
  if (!match) return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quario/xlsx",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
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.7.0",
45
+ "quario": "^0.8.0",
46
46
  "size-limit": "^13.0.3",
47
47
  "typescript": "^7.0.2"
48
48
  },
49
49
  "peerDependencies": {
50
- "quario": "^0.7.0"
50
+ "quario": "^0.8.0"
51
51
  },
52
52
  "size-limit": [
53
53
  {
package/lib/image.js DELETED
@@ -1,45 +0,0 @@
1
- /**
2
- * An image's own size, read from its header. A floating drawing is placed in
3
- * pixels, and the event carries the bytes and the format the engine sniffed
4
- * but no dimensions, so the one thing this target needs to place a picture is
5
- * worked out here — from the header alone, never by decoding the image.
6
- *
7
- * The PDF target reads the same two headers for its own placement, in points.
8
- * Restated per target rather than shared, like every other coercion at a
9
- * target's edge: neither package imports the other, and the engine's stream is
10
- * all they have in common.
11
- */
12
-
13
- /** @type {(bytes: Uint8Array, at: number) => number} */
14
- let word = (bytes, at) => (bytes[at] << 8) | bytes[at + 1];
15
-
16
- // The dimensions live in the frame header, the first SOFn marker: every
17
- // code in C0..CF except the three in that range that are not frames --
18
- // DHT, the JPG extension, and DAC.
19
- /** @type {Record<number, number>} */
20
- let SKIP = { 0xc4: 1, 0xc8: 1, 0xcc: 1 };
21
- /** @type {(code: number) => any} */
22
- let sof = (code) => code >= 0xc0 && code <= 0xcf && !SKIP[code];
23
-
24
- /** @type {(bytes: Uint8Array) => { width: number, height: number }} */
25
- let jpeg = (bytes) => {
26
- for (let at = 2; at + 9 < bytes.length; at += 2 + word(bytes, at + 2)) {
27
- if (bytes[at] !== 0xff) break;
28
- if (sof(bytes[at + 1])) return { width: word(bytes, at + 7), height: word(bytes, at + 5) };
29
- }
30
- return { width: 0, height: 0 };
31
- };
32
-
33
- /**
34
- * The image's size in pixels, or null when the header does not carry one —
35
- * bytes the engine vouched the magic numbers of, truncated before the size.
36
- *
37
- * @param {Uint8Array} bytes The image file.
38
- * @param {string} format The engine's sniff: `"png"` or `"jpeg"`.
39
- * @returns {{ width: number, height: number } | null} The size, if readable.
40
- */
41
- export let pixels = (bytes, format) => {
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);
44
- return size.width > 0 && size.height > 0 ? size : null;
45
- };