@justybase/spreadsheet-tasks 2.2.0 → 3.0.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,8 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Breaking
11
+
12
+ - `XlsxReader` / `XlsbReader` now return an empty row (`[]`, `getValue()` → `null`)
13
+ for every missing row position, including leading gaps, based on the row `r`
14
+ attribute (XLSX) and `BrtRowHdr` (XLSB). Like xlspy, gap positions are yielded;
15
+ unlike xlspy's padded `[None] * cols`, this reader yields a zero-length row.
16
+ Code that counts rows must account for gap rows.
17
+
18
+ ### Documentation
19
+
20
+ - Date conversion is UTC-based on both write and read: a `Date` is stored by its
21
+ UTC instant (`getTime()`), so `new Date(2024, 5, 30)` in UTC+2 appears as
22
+ 2024-06-29 22:00 in Excel. Use `Date.UTC(...)` for date-only values.
23
+
10
24
  ### Added
11
25
 
26
+ - `selectSheet(nameOrIndex)` and `nextResult()` on `ExcelReaderAbstract`,
27
+ implemented by `XlsxReader` and `XlsbReader`. Default remains sheet 0; state
28
+ (row/EOF, `fieldCount`) resets on selection.
29
+ - Excel limits (`MAX_ROWS=1_048_576`, `MAX_COLS=16_384`, `MAX_CELL_CHARS=32_767`
30
+ UTF-16 units) enforced in `XlsxWriter`, `XlsbWriter`, `BrowserXlsxWriter` and
31
+ `BrowserXlsbWriter`. Overlong text, oversized dimensions and rows/headers
32
+ longer than `columnCount` throw instead of writing corrupt files (xlspy parity).
33
+ - Shared XML helpers: `parseTagAttributes()`, `resolveRelTarget()` and
34
+ single-pass `unescapeXml()` in `src/xmlUtils.ts`, exported from the package
35
+ entry point.
12
36
  - `convertXlsbToXlsx` and `convertXlsxToXlsb` — Windows/Excel COM conversions
13
37
  with protected destinations and optional explicit overwriting.
14
38
  - `replaceSheetDataStream()` and `saveStreaming()` for XLSX/XLSB updaters,
@@ -19,6 +43,36 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
19
43
 
20
44
  ### Fixed
21
45
 
46
+ - `BrowserXlsxWriter` / `BrowserXlsbWriter` skip a `FormattedCell` whose value is
47
+ `null` or `undefined`, matching the Node writers (previously a `TypeError`).
48
+ - Browser sources in `src/browser/` are type-checked under `strict` by
49
+ `tsconfig.browser.json`, which `npm run typecheck` runs. The `@ts-nocheck` pragmas
50
+ were removed.
51
+ - Column letters ≥702 (`AAA`…`XFD`): `XlsxWriter` and `BrowserXlsxWriter` now
52
+ delegate to `columnIndexToLetter()` instead of falling back to `'A'`.
53
+ - XLSB shared-string table: `BrtBeginSst` now writes `total` then `unique`
54
+ (was swapped); `BiffReaderWriter` and updaters already expected that order.
55
+ - Writer finalization is atomic: output goes to a same-directory temp file,
56
+ output/archive errors reject the promise, streams are destroyed and the temp
57
+ file removed; only a complete workbook is renamed into place (xlspy parity).
58
+ `writeAdmZipStreaming()` now observes output errors before/during the append loop.
59
+ - Workbook relationships and sheet paths: absolute `Target="/xl/…"` resolved
60
+ correctly, attribute order no longer matters (`parseTagAttributes` used in
61
+ both readers and both updaters). Verified against openpyxl output.
62
+ - `unescapeXml()` single-pass: `&amp;lt;` stays `&lt;` instead of becoming `<`.
63
+ - Shared strings: `<rPh>` phonetics skipped, `<si/>` yields `""`, rich-text
64
+ `inlineStr` concatenates all `<t>` runs.
65
+ - XLSX cell types: `t="str"` → text, `t="e"` → error text, `t="d"` → `Date`.
66
+ - Self-closing `<row …/>` no longer swallows the next row.
67
+ - 1904 date system: `workbookPr date1904` (XLSX) and `BrtWbProp` (XLSB) set the
68
+ reader/updater epoch (`1904-01-01` vs `1899-12-30`).
69
+ - XLSB dimension: updaters patch `BrtWsDim` (`0x94`, `rwLast@4`/`colLast@12`)
70
+ instead of `BrtSel` (`0x98`).
71
+ - Stale parts after `replaceSheetData`: `xl/calcChain.xml` (`.bin` for XLSB)
72
+ removed with its `Content_Types`/rels entries; XLSX `xl/tables/*.xml` refs
73
+ (`<table>` + `<autoFilter>`) updated to the new dimension when the table's
74
+ width is unchanged, without expanding over other tables. XLSB tables are not
75
+ patched (documented limitation).
22
76
  - Destination replacement during Excel conversion now preserves files created
23
77
  concurrently and rolls back safely when the final rename fails.
24
78
  - Shared-string `count` values now reflect the replaced worksheet rather than
@@ -26,6 +80,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
26
80
  committed only once.
27
81
  - Updater saves now install the destination atomically through a same-directory
28
82
  temporary file.
83
+ - 1900 date system leap-day gap: Excel counts a fictitious 1900-02-29, so
84
+ serials 1-59 denote 1900-01-01..1900-02-28. `oaDateToTime()` /
85
+ `timeToOaDate()` in `writerHelpers.ts` apply the one-day shift (both
86
+ directions, all readers/writers/updaters incl. browser builds); the 1904
87
+ system is unaffected. Previously serial 1 read as 1899-12-31, disagreeing
88
+ with Excel, xlspy and openpyxl.
89
+ - `XlsbUpdater` date fallback now recognises custom `BrtFmt` date formats
90
+ (e.g. xlspy's `yyyy-mm-dd`) via `BiffReaderWriter.readStyles()` instead of
91
+ only built-in numFmt ranges, so replacement `Date` cells keep a date style
92
+ instead of degrading to raw serials.
29
93
 
30
94
  ## [2.1.0] - 2026-08-07
31
95
 
@@ -181,6 +181,10 @@ var BrowserBigBuffer = class {
181
181
  var BrowserZip = class _BrowserZip {
182
182
  constructor() {
183
183
  this.files = [];
184
+ this.files = [];
185
+ }
186
+ static {
187
+ this._crc32Table = null;
184
188
  }
185
189
  /**
186
190
  * Add a file entry (string or Uint8Array).
@@ -298,7 +302,6 @@ var BrowserZip = class _BrowserZip {
298
302
  return (crc ^ 4294967295) >>> 0;
299
303
  }
300
304
  };
301
- BrowserZip._crc32Table = null;
302
305
 
303
306
  // src/Formats.ts
304
307
  var F = {
@@ -343,15 +346,61 @@ function getFormat(val) {
343
346
  return null;
344
347
  }
345
348
 
349
+ // src/xmlUtils.ts
350
+ function columnIndexToLetter(colIndex) {
351
+ let n = colIndex + 1;
352
+ let result = "";
353
+ while (n > 0) {
354
+ const rem = (n - 1) % 26;
355
+ result = String.fromCharCode(65 + rem) + result;
356
+ n = Math.floor((n - 1) / 26);
357
+ }
358
+ return result;
359
+ }
360
+
361
+ // src/writerHelpers.ts
362
+ var OA_EPOCH_1900 = Date.UTC(1899, 11, 30);
363
+ var MARCH_1_1900 = Date.UTC(1900, 2, 1);
364
+ function timeToOaDate(timeMs, epochMs) {
365
+ let serial = (timeMs - epochMs) / 864e5;
366
+ if (epochMs === OA_EPOCH_1900 && timeMs < MARCH_1_1900) {
367
+ serial -= 1;
368
+ }
369
+ return serial;
370
+ }
371
+ var MAX_ROWS = 1048576;
372
+ var MAX_COLS = 16384;
373
+ var MAX_CELL_CHARS = 32767;
374
+ function assertCellTextLength(text) {
375
+ if (text.length > MAX_CELL_CHARS) {
376
+ throw new Error(
377
+ `Excel cell text exceeds ${MAX_CELL_CHARS} UTF-16 code units (got ${text.length}).`
378
+ );
379
+ }
380
+ }
381
+ function assertColumnCount(columnCount) {
382
+ if (!Number.isInteger(columnCount) || columnCount < 1 || columnCount > MAX_COLS) {
383
+ throw new Error(
384
+ `Excel column count must be 1..${MAX_COLS} (got ${columnCount}).`
385
+ );
386
+ }
387
+ }
388
+ function assertRowFitsColumns(kind, length, columnCount) {
389
+ if (length > columnCount) {
390
+ throw new Error(
391
+ `Excel ${kind} length ${length} exceeds columnCount ${columnCount}.`
392
+ );
393
+ }
394
+ }
395
+ function assertRowCount(totalRows) {
396
+ if (totalRows > MAX_ROWS) {
397
+ throw new Error(
398
+ `Excel row count exceeds ${MAX_ROWS} (got ${totalRows}).`
399
+ );
400
+ }
401
+ }
402
+
346
403
  // src/browser/BrowserXlsxWriter.ts
347
- var COL_LETTERS = (() => {
348
- const l = [];
349
- for (let i = 65; i < 91; i++) l.push(String.fromCharCode(i));
350
- const t = [];
351
- for (const p of l) for (let i = 65; i < 91; i++) t.push(p + String.fromCharCode(i));
352
- l.push(...t);
353
- return l;
354
- })();
355
404
  var BrowserXlsxWriter = class {
356
405
  constructor() {
357
406
  this._zip = new BrowserZip();
@@ -376,7 +425,7 @@ var BrowserXlsxWriter = class {
376
425
  this._nextXf = 6;
377
426
  }
378
427
  _cl(i) {
379
- return i < COL_LETTERS.length ? COL_LETTERS[i] : "A";
428
+ return columnIndexToLetter(i);
380
429
  }
381
430
  _san(n) {
382
431
  if (!n || typeof n !== "string") return `Sheet${this._sc + 1}`;
@@ -389,7 +438,6 @@ var BrowserXlsxWriter = class {
389
438
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
390
439
  }
391
440
  _esc(s) {
392
- if (typeof s !== "string") return s;
393
441
  let need = false;
394
442
  for (let i = 0; i < s.length; i++) {
395
443
  const c = s.charCodeAt(i);
@@ -417,6 +465,7 @@ var BrowserXlsxWriter = class {
417
465
  this._sl.push({ name: sn, path: `xl/worksheets/sheet${this._sc}.xml`, hidden, fn: `sheet${this._sc}.xml`, id: this._sc, rId: `rId${this._sc}`, fhr: null });
418
466
  }
419
467
  _wsc(bb, val, cr, rn, so) {
468
+ assertCellTextLength(val);
420
469
  let idx = this._sstM.get(val);
421
470
  if (idx === void 0) {
422
471
  idx = this._sstA.length;
@@ -428,6 +477,7 @@ var BrowserXlsxWriter = class {
428
477
  bb.writeString(`<c r="${cr}${rn}" t="s"${sa}><v>${idx}</v></c>`);
429
478
  }
430
479
  _wscStyle(bb, val, cr, rn, styleId) {
480
+ assertCellTextLength(val);
431
481
  let idx = this._sstM.get(val);
432
482
  if (idx === void 0) {
433
483
  idx = this._sstA.length;
@@ -438,10 +488,11 @@ var BrowserXlsxWriter = class {
438
488
  bb.writeString(`<c r="${cr}${rn}" t="s" s="${styleId}"><v>${idx}</v></c>`);
439
489
  }
440
490
  _oa(d) {
441
- return (d.getTime() - this._oaE) / 864e5;
491
+ return timeToOaDate(d.getTime(), this._oaE);
442
492
  }
443
493
  _regFmt(fs) {
444
- if (this._fmtXf.has(fs)) return this._fmtXf.get(fs);
494
+ const existing = this._fmtXf.get(fs);
495
+ if (existing !== void 0) return existing;
445
496
  const nid = this._nextNfmt++;
446
497
  this._fmtReg.set(fs, nid);
447
498
  const xf = this._nextXf++;
@@ -451,9 +502,10 @@ var BrowserXlsxWriter = class {
451
502
  _wcv(bb, raw, cr, rn) {
452
503
  if (raw === null || raw === void 0) return;
453
504
  const fs = getFormat(raw);
454
- const v = fs !== null ? unwrapCell(raw) : raw;
505
+ const v = unwrapCell(raw);
455
506
  const xf = fs !== null ? this._regFmt(fs) : -1;
456
507
  const sa = xf >= 0 ? ` s="${xf}"` : "";
508
+ if (v === null || v === void 0) return;
457
509
  if (typeof v === "number") {
458
510
  if (Number.isFinite(v)) bb.writeString(`<c r="${cr}${rn}"${sa}><v>${v}</v></c>`);
459
511
  else this._wsc(bb, v.toString(), cr, rn, xf >= 0 ? xf : void 0);
@@ -480,13 +532,19 @@ var BrowserXlsxWriter = class {
480
532
  }
481
533
  startSheet(name, colCount, headers, options = {}) {
482
534
  if (this._isS) throw new Error("Already streaming");
535
+ assertColumnCount(colCount);
536
+ if (headers !== void 0) {
537
+ assertRowFitsColumns("header", headers.length, colCount);
538
+ for (const h of headers) assertCellTextLength(h);
539
+ }
483
540
  const { hidden = false, doAutofilter = true, headerStyle = "bold" } = options;
484
541
  let hs = 3;
485
542
  if (headerStyle === "fill") hs = 4;
486
543
  else if (headerStyle === "bold+fill") hs = 5;
487
544
  this.addSheet(name, hidden);
488
545
  this._isS = true;
489
- this._csb = new BrowserBigBuffer();
546
+ const csb = new BrowserBigBuffer();
547
+ this._csb = csb;
490
548
  this._csrn = 0;
491
549
  this._csSC = 0;
492
550
  this._csEC = colCount;
@@ -500,33 +558,37 @@ var BrowserXlsxWriter = class {
500
558
  if (w > 80) w = 80;
501
559
  if (this._cw[i] < w) this._cw[i] = w;
502
560
  }
503
- this._sheetHead(this._csb, colCount, this._sc === 1, this._csAF);
561
+ this._sheetHead(csb, colCount, this._sc === 1, this._csAF);
504
562
  if (headers) {
505
563
  this._csrn++;
506
- this._csb.writeString(`<row r="${this._csrn}">`);
507
- for (let c = 0; c < headers.length; c++) this._wscStyle(this._csb, headers[c], cl[c], this._csrn, hs);
508
- this._csb.writeString("</row>");
564
+ csb.writeString(`<row r="${this._csrn}">`);
565
+ for (let c = 0; c < headers.length; c++) this._wscStyle(csb, headers[c], cl[c], this._csrn, hs);
566
+ csb.writeString("</row>");
509
567
  }
510
568
  }
511
569
  writeRow(row) {
512
- if (!this._isS) throw new Error("Not streaming");
570
+ if (!this._isS || this._csb === null) throw new Error("Not streaming");
571
+ if (row.length !== this._csEC) throw new Error(`Row length mismatch. Expected ${this._csEC} columns, got ${row.length}`);
572
+ const csb = this._csb;
513
573
  this._csrn++;
514
- this._csb.writeString(`<row r="${this._csrn}">`);
515
- for (let c = 0; c < row.length; c++) this._wcv(this._csb, row[c], this._csCL[c], this._csrn);
516
- this._csb.writeString("</row>");
574
+ assertRowCount(this._csrn);
575
+ csb.writeString(`<row r="${this._csrn}">`);
576
+ for (let c = 0; c < row.length; c++) this._wcv(csb, row[c], this._csCL[c], this._csrn);
577
+ csb.writeString("</row>");
517
578
  }
518
579
  endSheet() {
519
- if (!this._isS) throw new Error("Not streaming");
520
- this._csb.writeString("</sheetData>");
580
+ if (!this._isS || this._csb === null) throw new Error("Not streaming");
581
+ const csb = this._csb;
582
+ csb.writeString("</sheetData>");
521
583
  if (this._csAF && this._csEC > 0) {
522
584
  this._afOn = true;
523
585
  const fr = `A1:${this._csCL[this._csEC - 1]}${this._csrn}`;
524
- this._csb.writeString(`<autoFilter ref="${fr}"/>`);
586
+ csb.writeString(`<autoFilter ref="${fr}"/>`);
525
587
  const sh = this._sl[this._sc - 1];
526
588
  sh.fhr = `${this._fmn(sh.name)}!$A$1:$${this._csCL[this._csEC - 1]}$${this._csrn}`;
527
589
  }
528
- this._csb.writeString("</worksheet>");
529
- this._zip.addFile(this._sl[this._sc - 1].path, this._csb.toUint8Array());
590
+ csb.writeString("</worksheet>");
591
+ this._zip.addFile(this._sl[this._sc - 1].path, csb.toUint8Array());
530
592
  this._isS = false;
531
593
  this._csb = null;
532
594
  }
@@ -538,6 +600,14 @@ var BrowserXlsxWriter = class {
538
600
  else if (headerStyle === "bold+fill") hs = 5;
539
601
  const bb = new BrowserBigBuffer();
540
602
  const cc = rows.length > 0 ? rows[0].length : headers ? headers.length : 0;
603
+ if (cc > 0) assertColumnCount(cc);
604
+ const tr = rows.length + (headers ? 1 : 0);
605
+ assertRowCount(tr);
606
+ if (headers) {
607
+ assertRowFitsColumns("header", headers.length, cc);
608
+ for (const h of headers) assertCellTextLength(h);
609
+ }
610
+ for (let r = 0; r < rows.length; r++) assertRowFitsColumns("row", rows[r].length, cc);
541
611
  this._cw = new Array(cc).fill(-1);
542
612
  const cl = new Array(cc);
543
613
  for (let i = 0; i < cc; i++) cl[i] = this._cl(i);
@@ -550,11 +620,11 @@ var BrowserXlsxWriter = class {
550
620
  const raw = rows[r][c];
551
621
  if (raw == null) continue;
552
622
  const v = isFormattedCell(raw) ? raw.value : raw;
623
+ if (v === null || v === void 0) continue;
553
624
  let w = 1.3 * (v instanceof Date ? 10 : v.toString().length) + 3;
554
625
  if (w > 80) w = 80;
555
626
  if (this._cw[c] < w) this._cw[c] = w;
556
627
  }
557
- const tr = rows.length + (headers ? 1 : 0);
558
628
  this._sheetHead(bb, cc, this._sc === 1, doAutofilter && !!headers);
559
629
  let rn = 0;
560
630
  if (headers) {
@@ -2468,7 +2538,8 @@ var BrowserXlsbWriter = class {
2468
2538
  return s;
2469
2539
  }
2470
2540
  _registerFormat(fmtString) {
2471
- if (this._fmtMap.has(fmtString)) return this._fmtMap.get(fmtString).xf;
2541
+ const existing = this._fmtMap.get(fmtString);
2542
+ if (existing !== void 0) return existing.xf;
2472
2543
  const nid = this._nextNid++;
2473
2544
  const xf = this._nextXf++;
2474
2545
  this._fmtMap.set(fmtString, { nid, xf });
@@ -2592,6 +2663,11 @@ var BrowserXlsbWriter = class {
2592
2663
  }
2593
2664
  startSheet(sheetName, columnCount, headers, options = {}) {
2594
2665
  if (this._isStreaming) throw new Error("Already streaming");
2666
+ assertColumnCount(columnCount);
2667
+ if (headers !== void 0) {
2668
+ assertRowFitsColumns("header", headers.length, columnCount);
2669
+ for (const h of headers) assertCellTextLength(h);
2670
+ }
2595
2671
  const { hidden = false, doAutofilter = true, headerStyle = "bold" } = options;
2596
2672
  let hs = 3;
2597
2673
  if (headerStyle === "fill") hs = 4;
@@ -2620,15 +2696,19 @@ var BrowserXlsbWriter = class {
2620
2696
  }
2621
2697
  }
2622
2698
  writeRow(row) {
2623
- if (!this._isStreaming) throw new Error("Not streaming");
2624
2699
  const bb = this._currentSheetBuffer;
2700
+ if (!this._isStreaming || bb === null) throw new Error("Not streaming");
2701
+ if (row.length !== this._currentSheetEndCol - this._currentSheetStartCol) {
2702
+ throw new Error(`Row length mismatch. Expected ${this._currentSheetEndCol - this._currentSheetStartCol} columns, got ${row.length}`);
2703
+ }
2704
+ assertRowCount(this._currentSheetRowNum + 1);
2625
2705
  this.createRowHeader(bb, this._currentSheetRowNum, this._currentSheetStartCol, this._currentSheetEndCol);
2626
2706
  for (let c = 0; c < row.length; c++) this._writeCell(bb, row[c], c);
2627
2707
  this._currentSheetRowNum++;
2628
2708
  }
2629
2709
  endSheet() {
2630
- if (!this._isStreaming) throw new Error("Not streaming");
2631
2710
  const bb = this._currentSheetBuffer;
2711
+ if (!this._isStreaming || bb === null) throw new Error("Not streaming");
2632
2712
  bb.write(this._sheet1Bytes.subarray(218, 290));
2633
2713
  if (this._currentSheetDoAutofilter) {
2634
2714
  this._autofilterIsOn = true;
@@ -2664,6 +2744,14 @@ var BrowserXlsbWriter = class {
2664
2744
  else if (headerStyle === "bold+fill") hs = 5;
2665
2745
  const bb = new BrowserBigBuffer();
2666
2746
  const colCount = rows.length > 0 ? rows[0].length : headers ? headers.length : 0;
2747
+ if (colCount > 0) assertColumnCount(colCount);
2748
+ const totalBr = rows.length + (headers ? 1 : 0);
2749
+ assertRowCount(totalBr);
2750
+ if (headers) {
2751
+ assertRowFitsColumns("header", headers.length, colCount);
2752
+ for (const h of headers) assertCellTextLength(h);
2753
+ }
2754
+ for (let r = 0; r < rows.length; r++) assertRowFitsColumns("row", rows[r].length, colCount);
2667
2755
  this._colWidths = new Array(colCount).fill(-1);
2668
2756
  if (headers) {
2669
2757
  for (let i = 0; i < colCount; i++) {
@@ -2678,6 +2766,7 @@ var BrowserXlsbWriter = class {
2678
2766
  const raw = rows[r][c];
2679
2767
  if (raw == null) continue;
2680
2768
  const v = isFormattedCell(raw) ? raw.value : raw;
2769
+ if (v === null || v === void 0) continue;
2681
2770
  const len = v instanceof Date ? 10 : v.toString().length;
2682
2771
  let w = 1.3 * len + 3;
2683
2772
  if (w > 80) w = 80;
@@ -2755,7 +2844,7 @@ var BrowserXlsbWriter = class {
2755
2844
  _writeCell(bb, raw, c) {
2756
2845
  if (raw === null || raw === void 0) return;
2757
2846
  const fmtString = getFormat(raw);
2758
- const val = fmtString !== null ? unwrapCell(raw) : raw;
2847
+ const val = unwrapCell(raw);
2759
2848
  const styleNum = fmtString !== null ? this._registerFormat(fmtString) : 0;
2760
2849
  if (typeof val === "number") {
2761
2850
  if (Number.isInteger(val) && val >= this._rRkIntegerLowerLimit && val <= this._rRkIntegerUpperLimit) {
@@ -2768,9 +2857,9 @@ var BrowserXlsbWriter = class {
2768
2857
  } else if (typeof val === "boolean") {
2769
2858
  this.writeBool(bb, val, c);
2770
2859
  } else if (val instanceof Date) {
2771
- const oaDate = (val.getTime() - this._oaEpoch) / 864e5;
2860
+ const oaDate = timeToOaDate(val.getTime(), this._oaEpoch);
2772
2861
  this.writeDouble(bb, oaDate, c, fmtString !== null ? styleNum : 1);
2773
- } else {
2862
+ } else if (val !== null && val !== void 0) {
2774
2863
  this.writeString(bb, val.toString(), c, 0, fmtString !== null ? styleNum : void 0);
2775
2864
  }
2776
2865
  }
@@ -2823,13 +2912,12 @@ var BrowserXlsbWriter = class {
2823
2912
  bb.writeUnsafeByte(val ? 1 : 0);
2824
2913
  }
2825
2914
  writeDateTime(bb, date, colNum) {
2826
- this.writeDouble(bb, (date.getTime() - this._oaEpoch) / 864e5, colNum, 1);
2915
+ this.writeDouble(bb, timeToOaDate(date.getTime(), this._oaEpoch), colNum, 1);
2827
2916
  }
2828
- writeString(bb, val, colNum, styleIndex = 0, styleOverride = void 0) {
2829
- let index;
2830
- if (this._sstDic.has(val)) {
2831
- index = this._sstDic.get(val);
2832
- } else {
2917
+ writeString(bb, val, colNum, styleIndex = 0, styleOverride) {
2918
+ assertCellTextLength(val);
2919
+ let index = this._sstDic.get(val);
2920
+ if (index === void 0) {
2833
2921
  index = this._sstCntUnique++;
2834
2922
  this._sstDic.set(val, index);
2835
2923
  }
@@ -2850,8 +2938,8 @@ var BrowserXlsbWriter = class {
2850
2938
  bb.writeByte(159);
2851
2939
  bb.writeByte(1);
2852
2940
  bb.writeByte(8);
2853
- bb.writeInt32LE(this._sstCntUnique);
2854
2941
  bb.writeInt32LE(this._sstCntAll);
2942
+ bb.writeInt32LE(this._sstCntUnique);
2855
2943
  for (const [txt] of this._sstDic) {
2856
2944
  const tl = txt.length;
2857
2945
  bb.writeByte(19);
@@ -2927,7 +3015,7 @@ var BrowserXlsbWriter = class {
2927
3015
  }
2928
3016
  wbBuffers.push(this._workbookBinMiddle);
2929
3017
  if (this._autofilterIsOn) {
2930
- const filtered = this._sheetList.filter((s) => s.filterData);
3018
+ const filtered = this._sheetList.filter((s) => s.filterData !== void 0);
2931
3019
  if (filtered.length > 0) {
2932
3020
  wbBuffers.push(this._magicFilterExcel2016Fix0);
2933
3021
  const cnt = filtered.length;