werkmap 0.2.0 → 0.4.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 CHANGED
@@ -6,10 +6,10 @@ Writing is the whole surface, so the package stays small enough to audit. The co
6
6
 
7
7
  - **Byte-identical output.** Nothing consults a clock, a locale or a random source, and the ZIP epoch is pinned, so two renders of the same report are the same bytes and a build that caches by content hash keeps working.
8
8
  - **Foreign readers accept it.** The suite loads every workbook it writes back through ExcelJS, an independent implementation — so the tests prove a real reader opens the file, not just that the bytes look plausible.
9
- - **Zero dependencies.** 5.7 kB minified and brotlied, for the whole writer.
9
+ - **Zero dependencies.** 7.0 kB minified and brotlied, for the whole writer.
10
10
  - **Strict CSP, including in the browser.** No `unsafe-eval` and no `unsafe-inline`. A Chromium page under `default-src 'none'; script-src 'self'` writes a workbook and reports any violation back, and the Node suite runs on `--disallow-code-generation-from-strings`.
11
11
  - **Write-only, deliberately.** No reader, no formula engine, no chart support — see [Is werkmap the right tool?](#is-werkmap-the-right-tool) before you install it.
12
- - **Hardened.** 53 tests at 100% branch coverage.
12
+ - **Hardened.** 72 tests at 100% branch coverage.
13
13
 
14
14
  ```js
15
15
  import { workbook } from "werkmap";
@@ -89,7 +89,7 @@ werkmap writes the slice of the format a report needs, and refuses the rest.
89
89
 
90
90
  - You need to read, edit or convert an existing workbook.
91
91
  - You need formulas, charts, pivot tables, conditional formatting, data validation, hyperlinks, comments, sheet protection, or a page header and footer.
92
- - You need column widths or row heights. A reader sizes columns from its own defaults.
92
+ - You need row heights. A reader sizes rows from its own defaults. Column widths it does write — see `sheet.widths`.
93
93
  - You have more rows than fit in memory. There is no streaming, row-at-a-time output.
94
94
 
95
95
  ## API
@@ -141,12 +141,34 @@ One merged range across `width` columns of `row`, starting at the 1-based column
141
141
 
142
142
  Freeze the top `rows` rows. `0` clears.
143
143
 
144
+ ### `sheet.widths(list)`
145
+
146
+ Column widths by position: the first entry is column A, and `null` leaves a column unset so a reader keeps its own default for it.
147
+
148
+ The unit is the format's own — **a count of characters of the workbook's default font**, which is the number Excel's column-width box shows. A width is above 0 and at most 255. Pixels would read more naturally beside `place`, but converting them runs through the default font's maximum digit width, and any cell here may name a font of its own, so that constant would be wrong for most workbooks. The awkward unit invents no number.
149
+
150
+ The list **replaces** rather than merging, and an empty list clears — a hole in a positional list cannot mean both "leave this one alone" and "clear it". Call it before or after the rows; a width never widens the sheet, because `dimension` describes the cells that were written and sizing a column writes no cell. A worksheet that never calls this carries **no `cols` element at all**.
151
+
152
+ What is written is the number you gave, verbatim. Excel may report a slightly different one after a round trip, because it re-derives a width from the default font's digit width — that is the reader's arithmetic, not this writer's.
153
+
154
+ There is no `hidden`, no outline level and no per-column style: a zero width is the back door to a hidden column, so `0` throws and points at `null`.
155
+
156
+ ### `sheet.filter(range)`
157
+
158
+ Put an autofilter over `{ top, left, bottom, right }` — 1-based, and inclusive on all four sides. It adds the dropdown controls and **hides nothing**: every row you wrote is still in the document, and what the reader does with the control is theirs.
159
+
160
+ A worksheet takes one, so a second call **replaces** the first, and `null` clears it. The range must already have been written — its bottom row and its rightmost column both, the way a merge's row must — because a filter over cells nobody wrote is a caller's mistake rather than an empty range. Both are checked when you call, so write the rows first. A worksheet that never calls this carries **no `autoFilter` element at all**.
161
+
162
+ Excel also records an autofilter as a sheet-scoped `_xlnm._FilterDatabase` defined name, and this writes none. That is measured rather than assumed, in both readers: Excel opens a file carrying only the element, with no repair prompt, and reports the sheet's autofilter as on; LibreOffice opens it, keeps the filter, and writes that name itself on save. The name is a reader's bookkeeping rather than something a file owes.
163
+
144
164
  ### `sheet.print(setup)`
145
165
 
146
- How this worksheet prints: `{ margin, size, orientation, fit }`, every key optional. `margin` is all four page margins in points, `size` one of `letter`, `tabloid`, `legal`, `A3`, `A4`, `A5`, `orientation` either `portrait` or `landscape`, and `fit: true` scales the sheet to one page wide and as many pages tall as it takes.
166
+ How this worksheet prints: `{ margin, size, orientation, fit, titles }`, every key optional. `margin` is all four page margins in points, `size` one of `letter`, `tabloid`, `legal`, `A3`, `A4`, `A5`, `orientation` either `portrait` or `landscape`, and `fit: true` scales the sheet to one page wide and as many pages tall as it takes.
147
167
 
148
168
  A worksheet that never calls this carries **no print setup at all**, so a reader applies its own defaults rather than this writer's opinion. Calls merge, so two calls naming different keys both take effect. Print setup is per worksheet, which is where OOXML puts it.
149
169
 
170
+ `titles: n` repeats the top `n` rows at the top of every printed page — the paper counterpart of `freeze`, which keeps them in view on screen. `0` clears it, as it does for a freeze. This one is written as a `_xlnm.Print_Titles` defined name in `xl/workbook.xml` rather than in the sheet part, and it is scoped to this worksheet alone, so each sheet of a workbook repeats its own rows.
171
+
150
172
  ### `sheet.place(id, { row, col, width, height })`
151
173
 
152
174
  One floating picture anchored to the top-left of a 1-based cell, drawn at `width` × `height` CSS pixels at 96 dpi.
package/lib/index.d.ts CHANGED
@@ -84,6 +84,19 @@ export interface PrintSetup {
84
84
  orientation?: "portrait" | "landscape";
85
85
  /** Scale the sheet to one page wide, and as many pages tall as it takes. */
86
86
  fit?: boolean;
87
+ /**
88
+ * Repeat the top `rows` rows at the top of every printed page. `0` clears,
89
+ * the way a `freeze` of `0` does. Scoped to this worksheet alone.
90
+ */
91
+ titles?: number;
92
+ }
93
+
94
+ /** The rectangle an autofilter covers. 1-based, and inclusive on all four sides. */
95
+ export interface FilterRange {
96
+ top: number;
97
+ left: number;
98
+ bottom: number;
99
+ right: number;
87
100
  }
88
101
 
89
102
  export interface Sheet {
@@ -101,12 +114,30 @@ export interface Sheet {
101
114
  merge(row: number, at: number, width: number): void;
102
115
  /** Freeze the top `count` rows. `0` clears. */
103
116
  freeze(count: number): void;
117
+ /**
118
+ * Column widths by position: the first entry is column A, and `null` leaves
119
+ * a column unset so a reader keeps its own default for it.
120
+ *
121
+ * The unit is the format's own — a count of characters of the workbook's
122
+ * default font, the number Excel's column-width box shows. A width is above
123
+ * 0 and at most 255.
124
+ *
125
+ * Replaces rather than merges, and an empty list clears.
126
+ */
127
+ widths(list: readonly (number | null)[]): void;
104
128
  /**
105
129
  * How this worksheet prints. A worksheet that never calls this carries no
106
130
  * print setup at all, so a reader applies its own defaults. Calls merge, so
107
131
  * two calls naming different keys both take effect.
108
132
  */
109
133
  print(setup: PrintSetup): void;
134
+ /**
135
+ * Put an autofilter over a range, 1-based and inclusive on all four sides.
136
+ * A worksheet takes one, so this replaces rather than merges, and `null`
137
+ * clears it. The range's bottom row and rightmost column must already have
138
+ * been written, checked when you call.
139
+ */
140
+ filter(range: FilterRange | null): void;
110
141
  /** Float a picture over the sheet, anchored to one cell. */
111
142
  place(id: number, at: Placement): void;
112
143
  }