@iyulab/flex-table 0.42.0 → 0.44.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
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.44.0] - 2026-10-05
4
+
5
+ ### Added
6
+
7
+ - **`exportToBlob(format, options?)`** — the export as a `Blob` typed with the format's MIME type.
8
+ XLSX comes out DEFLATE-compressed (platform `CompressionStream`), a fraction of the stored size —
9
+ a 500-row sheet is under a quarter of it.
10
+
11
+ ### Changed
12
+
13
+ - **`exportToFile` returns `Promise<void>` and downloads a compressed XLSX.** It was synchronous and
14
+ wrote the workbook uncompressed (ZIP STORE). Calling it without `await` still works.
15
+ `exportToString` stays synchronous and still returns an uncompressed workbook for `'xlsx'` — use
16
+ `exportToBlob` when the bytes leave the browser.
17
+
18
+ ## [0.43.0] - 2026-10-04
19
+
20
+ ### Changed
21
+
22
+ - **Breaking: `useODataSource`'s `onUnauthorized` is called on `401` only.** It was also called on
23
+ `403`, so an app that sends the user to sign in from this hook (its documented use) bounced a
24
+ signed-in user without permission back to the sign-in page — and, after signing in, to the same
25
+ list and the same `403`. A `403` now surfaces only as `error`, like any other failed request.
26
+ Migration: if you handled `403` in `onUnauthorized`, read it from `error` instead.
27
+
3
28
  ## [0.42.0] - 2026-10-04
4
29
 
5
30
  ### Added
package/README.md CHANGED
@@ -276,8 +276,9 @@ Default is `false`, matching `clear-undo-on-data-change`.
276
276
 
277
277
  | Method | Returns | Description |
278
278
  |--------|---------|-------------|
279
- | `exportToString(format, options?)` | `string \| Uint8Array` | Export to `'csv'` / `'tsv'` / `'json'` (a string) or `'xlsx'` (bytes). Pass `{ selectionOnly: true }` for selection range |
280
- | `exportToFile(format, filename?)` | `void` | Export and trigger browser file download |
279
+ | `exportToString(format, options?)` | `string \| Uint8Array` | Export to `'csv'` / `'tsv'` / `'json'` (a string) or `'xlsx'` (bytes, uncompressed). Pass `{ selectionOnly: true }` for selection range |
280
+ | `exportToBlob(format, options?)` | `Promise<Blob>` | The same export as a `Blob` of the format's MIME type — `'xlsx'` is DEFLATE-compressed |
281
+ | `exportToFile(format, filename?)` | `Promise<void>` | Export and trigger browser file download (`'xlsx'` compressed) |
281
282
 
282
283
  ## Events
283
284
 
@@ -725,7 +726,7 @@ const source = useODataSource('/api/orders', {
725
726
  | `fixedFilter` | — | Filter always applied in addition to search. Changing it resets the page to 0 — see below |
726
727
  | `baseUrl` | `window.location.origin` | Override the request origin (proxy/BFF setups) |
727
728
  | `fetcher` | global `fetch` | Custom transport — pass a wrapper that injects auth headers |
728
- | `onUnauthorized` | — | Called on `401`/`403` responses, before the generic error is set |
729
+ | `onUnauthorized` | — | Called on `401` responses, before the generic error is set. A `403` (signed in, not permitted) does not call it — it surfaces as `error` |
729
730
  | `enabled` | `true` | While `false`, no request is made and `loading` stays `true` — see below |
730
731
 
731
732
  `fetcher`/`onUnauthorized` should be stable references (e.g. wrap in `useCallback`) — they are intentionally excluded from the hook's internal effect dependencies to avoid refetch loops on every render.
@@ -5,9 +5,14 @@ export type ExportFormat = 'csv' | 'tsv' | 'json' | 'xlsx';
5
5
  * Returns string for text formats (csv/tsv/json) or Uint8Array for xlsx.
6
6
  */
7
7
  export declare function exportData(data: DataRow[], columns: ColumnDefinition[], format: ExportFormat): string | Uint8Array<ArrayBuffer>;
8
+ /**
9
+ * Export data as a `Blob` of the format's MIME type. XLSX is DEFLATE-compressed (unlike `exportData`,
10
+ * which stays synchronous and therefore stores the workbook uncompressed).
11
+ */
12
+ export declare function exportDataBlob(data: DataRow[], columns: ColumnDefinition[], format: ExportFormat): Promise<Blob>;
8
13
  /**
9
14
  * Trigger a file download in the browser.
10
15
  */
11
- export declare function downloadFile(content: string | Uint8Array<ArrayBuffer>, filename: string, mimeType: string): void;
16
+ export declare function downloadBlob(blob: Blob, filename: string): void;
12
17
  export declare function getExportMimeType(format: ExportFormat): string;
13
18
  export declare function getExportExtension(format: ExportFormat): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Minimal XLSX writer — no external dependencies.
3
- * Generates OOXML (.xlsx) files using ZIP STORE method (no compression).
3
+ * Generates OOXML (.xlsx) files — ZIP STORE (sync) or DEFLATE (async, platform CompressionStream).
4
4
  * Supports string, number, boolean, and date cell types.
5
5
  */
6
6
  import type { ColumnDefinition, DataRow } from '../models/types.js';
@@ -10,7 +10,14 @@ export interface ZipFile {
10
10
  }
11
11
  /** @internal 테스트가 실물에 가까운 통합 문서(스타일·1904 날짜 체계)를 조립하는 데 쓴다. */
12
12
  export declare function buildZip(files: ZipFile[]): Uint8Array<ArrayBuffer>;
13
+ /**
14
+ * DEFLATE 로 압축한 ZIP. 항목마다 압축본이 원본보다 작을 때만 DEFLATE 를 쓰고, 아니면 STORE 로 둔다.
15
+ * 압축은 플랫폼 `CompressionStream('deflate-raw')` 라 비동기다 — 읽기 쪽이 이미 `DecompressionStream` 을 쓴다.
16
+ */
17
+ export declare function buildZipDeflated(files: ZipFile[]): Promise<Uint8Array<ArrayBuffer>>;
13
18
  /**
14
19
  * Build an XLSX file as a Uint8Array.
15
20
  */
16
21
  export declare function buildXlsx(data: DataRow[], columns: ColumnDefinition[]): Uint8Array<ArrayBuffer>;
22
+ /** Build a DEFLATE-compressed XLSX file — the same workbook as `buildXlsx`, a fraction of the size. */
23
+ export declare function buildXlsxDeflated(data: DataRow[], columns: ColumnDefinition[]): Promise<Uint8Array<ArrayBuffer>>;
@@ -1442,49 +1442,83 @@ function de(e) {
1442
1442
  }
1443
1443
  var F = new TextEncoder();
1444
1444
  function fe(e) {
1445
+ return I(e.map((e) => ({
1446
+ ...e,
1447
+ body: e.data,
1448
+ method: 0
1449
+ })));
1450
+ }
1451
+ async function pe(e) {
1452
+ let t = [];
1453
+ for (let n of e) {
1454
+ let e = await me(n.data);
1455
+ t.push(e.length < n.data.length ? {
1456
+ ...n,
1457
+ body: e,
1458
+ method: 8
1459
+ } : {
1460
+ ...n,
1461
+ body: n.data,
1462
+ method: 0
1463
+ });
1464
+ }
1465
+ return I(t);
1466
+ }
1467
+ async function me(e) {
1468
+ let t = new CompressionStream("deflate-raw"), n = t.writable.getWriter(), r = t.readable.getReader();
1469
+ n.write(e), n.close();
1470
+ let i = [];
1471
+ for (;;) {
1472
+ let { done: e, value: t } = await r.read();
1473
+ if (e) break;
1474
+ i.push(t);
1475
+ }
1476
+ return L(i);
1477
+ }
1478
+ function I(e) {
1445
1479
  let t = [], n = [], r = 0;
1446
1480
  for (let i of e) {
1447
- let e = F.encode(i.name), a = de(i.data), o = i.data.length, s = new ArrayBuffer(30 + e.length), c = new DataView(s);
1448
- c.setUint32(0, 67324752, !0), c.setUint16(4, 20, !0), c.setUint16(6, 0, !0), c.setUint16(8, 0, !0), c.setUint16(10, 0, !0), c.setUint16(12, 33, !0), c.setUint32(14, a, !0), c.setUint32(18, o, !0), c.setUint32(22, o, !0), c.setUint16(26, e.length, !0), c.setUint16(28, 0, !0), new Uint8Array(s, 30).set(e), t.push(new Uint8Array(s)), t.push(i.data);
1449
- let l = new ArrayBuffer(46 + e.length), u = new DataView(l);
1450
- u.setUint32(0, 33639248, !0), u.setUint16(4, 20, !0), u.setUint16(6, 20, !0), u.setUint16(8, 0, !0), u.setUint16(10, 0, !0), u.setUint16(12, 0, !0), u.setUint16(14, 33, !0), u.setUint32(16, a, !0), u.setUint32(20, o, !0), u.setUint32(24, o, !0), u.setUint16(28, e.length, !0), u.setUint16(30, 0, !0), u.setUint16(32, 0, !0), u.setUint16(34, 0, !0), u.setUint16(36, 0, !0), u.setUint32(38, 0, !0), u.setUint32(42, r, !0), new Uint8Array(l, 46).set(e), n.push(new Uint8Array(l)), r += 30 + e.length + o;
1481
+ let e = F.encode(i.name), a = de(i.data), o = i.data.length, s = i.body.length, c = i.method === 8 ? 20 : 10, l = new ArrayBuffer(30 + e.length), u = new DataView(l);
1482
+ u.setUint32(0, 67324752, !0), u.setUint16(4, c, !0), u.setUint16(6, 0, !0), u.setUint16(8, i.method, !0), u.setUint16(10, 0, !0), u.setUint16(12, 33, !0), u.setUint32(14, a, !0), u.setUint32(18, s, !0), u.setUint32(22, o, !0), u.setUint16(26, e.length, !0), u.setUint16(28, 0, !0), new Uint8Array(l, 30).set(e), t.push(new Uint8Array(l)), t.push(i.body);
1483
+ let d = new ArrayBuffer(46 + e.length), f = new DataView(d);
1484
+ f.setUint32(0, 33639248, !0), f.setUint16(4, 20, !0), f.setUint16(6, c, !0), f.setUint16(8, 0, !0), f.setUint16(10, i.method, !0), f.setUint16(12, 0, !0), f.setUint16(14, 33, !0), f.setUint32(16, a, !0), f.setUint32(20, s, !0), f.setUint32(24, o, !0), f.setUint16(28, e.length, !0), f.setUint16(30, 0, !0), f.setUint16(32, 0, !0), f.setUint16(34, 0, !0), f.setUint16(36, 0, !0), f.setUint32(38, 0, !0), f.setUint32(42, r, !0), new Uint8Array(d, 46).set(e), n.push(new Uint8Array(d)), r += 30 + e.length + s;
1451
1485
  }
1452
- let i = I(n), a = /* @__PURE__ */ new ArrayBuffer(22), o = new DataView(a);
1453
- return o.setUint32(0, 101010256, !0), o.setUint16(4, 0, !0), o.setUint16(6, 0, !0), o.setUint16(8, e.length, !0), o.setUint16(10, e.length, !0), o.setUint32(12, i.length, !0), o.setUint32(16, r, !0), o.setUint16(20, 0, !0), I([
1486
+ let i = L(n), a = /* @__PURE__ */ new ArrayBuffer(22), o = new DataView(a);
1487
+ return o.setUint32(0, 101010256, !0), o.setUint16(4, 0, !0), o.setUint16(6, 0, !0), o.setUint16(8, e.length, !0), o.setUint16(10, e.length, !0), o.setUint32(12, i.length, !0), o.setUint32(16, r, !0), o.setUint16(20, 0, !0), L([
1454
1488
  ...t,
1455
1489
  i,
1456
1490
  new Uint8Array(a)
1457
1491
  ]);
1458
1492
  }
1459
- function I(e) {
1493
+ function L(e) {
1460
1494
  let t = e.reduce((e, t) => e + t.length, 0), n = new Uint8Array(t), r = 0;
1461
1495
  for (let t of e) n.set(t, r), r += t.length;
1462
1496
  return n;
1463
1497
  }
1464
- function L(e) {
1498
+ function R(e) {
1465
1499
  return e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
1466
1500
  }
1467
- function pe(e) {
1501
+ function he(e) {
1468
1502
  let t = "";
1469
1503
  for (e++; e > 0;) t = String.fromCharCode(64 + (e % 26 || 26)) + t, e = Math.floor((e - 1) / 26);
1470
1504
  return t;
1471
1505
  }
1472
- function R(e, t) {
1473
- return pe(t) + (e + 1);
1506
+ function z(e, t) {
1507
+ return he(t) + (e + 1);
1474
1508
  }
1475
- function me(e) {
1509
+ function ge(e) {
1476
1510
  let t = Date.UTC(1899, 11, 30);
1477
1511
  return (e.getTime() - t) / 864e5;
1478
1512
  }
1479
- var z = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n <Default Extension=\"xml\" ContentType=\"application/xml\"/>\n <Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>\n <Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>\n <Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/>\n</Types>", he = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>\n</Relationships>", ge = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n <sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets>\n</workbook>", _e = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/>\n <Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>\n</Relationships>", ve = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n <numFmts count=\"1\"><numFmt numFmtId=\"164\" formatCode=\"yyyy-mm-dd\"/></numFmts>\n <fonts count=\"2\">\n <font><sz val=\"11\"/><name val=\"Calibri\"/></font>\n <font><sz val=\"11\"/><b/><name val=\"Calibri\"/></font>\n </fonts>\n <fills count=\"2\">\n <fill><patternFill patternType=\"none\"/></fill>\n <fill><patternFill patternType=\"gray125\"/></fill>\n </fills>\n <borders count=\"1\"><border><left/><right/><top/><bottom/><diagonal/></border></borders>\n <cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>\n <cellXfs count=\"3\">\n <xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>\n <xf numFmtId=\"164\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/>\n <xf numFmtId=\"0\" fontId=\"1\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyFont=\"1\"/>\n </cellXfs>\n</styleSheet>";
1480
- function ye(e, t) {
1481
- let n = [], r = t.map((e, t) => `<c r="${R(0, t)}" t="str" s="2"><v>${L(e.label)}</v></c>`);
1513
+ var _e = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n <Default Extension=\"xml\" ContentType=\"application/xml\"/>\n <Override PartName=\"/xl/workbook.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml\"/>\n <Override PartName=\"/xl/worksheets/sheet1.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml\"/>\n <Override PartName=\"/xl/styles.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml\"/>\n</Types>", ve = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\" Target=\"xl/workbook.xml\"/>\n</Relationships>", ye = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<workbook xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\">\n <sheets><sheet name=\"Sheet1\" sheetId=\"1\" r:id=\"rId1\"/></sheets>\n</workbook>", be = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet\" Target=\"worksheets/sheet1.xml\"/>\n <Relationship Id=\"rId2\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>\n</Relationships>", xe = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">\n <numFmts count=\"1\"><numFmt numFmtId=\"164\" formatCode=\"yyyy-mm-dd\"/></numFmts>\n <fonts count=\"2\">\n <font><sz val=\"11\"/><name val=\"Calibri\"/></font>\n <font><sz val=\"11\"/><b/><name val=\"Calibri\"/></font>\n </fonts>\n <fills count=\"2\">\n <fill><patternFill patternType=\"none\"/></fill>\n <fill><patternFill patternType=\"gray125\"/></fill>\n </fills>\n <borders count=\"1\"><border><left/><right/><top/><bottom/><diagonal/></border></borders>\n <cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\"/></cellStyleXfs>\n <cellXfs count=\"3\">\n <xf numFmtId=\"0\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\"/>\n <xf numFmtId=\"164\" fontId=\"0\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyNumberFormat=\"1\"/>\n <xf numFmtId=\"0\" fontId=\"1\" fillId=\"0\" borderId=\"0\" xfId=\"0\" applyFont=\"1\"/>\n </cellXfs>\n</styleSheet>";
1514
+ function Se(e, t) {
1515
+ let n = [], r = t.map((e, t) => `<c r="${z(0, t)}" t="str" s="2"><v>${R(e.label)}</v></c>`);
1482
1516
  return n.push(`<row r="1">${r.join("")}</row>`), e.forEach((e, r) => {
1483
1517
  let i = t.map((t, n) => {
1484
- let i = R(r + 1, n), a = e[t.key];
1518
+ let i = z(r + 1, n), a = e[t.key];
1485
1519
  if (a == null) return `<c r="${i}" t="str"><v></v></c>`;
1486
1520
  let o = t.type ?? "text";
1487
- return o === "number" && typeof a == "number" ? `<c r="${i}"><v>${a}</v></c>` : (o === "date" || o === "datetime") && a instanceof Date ? `<c r="${i}" s="1"><v>${me(a).toFixed(6)}</v></c>` : o === "boolean" ? `<c r="${i}" t="b"><v>${+!!a}</v></c>` : `<c r="${i}" t="str"><v>${L(String(a))}</v></c>`;
1521
+ return o === "number" && typeof a == "number" ? `<c r="${i}"><v>${a}</v></c>` : (o === "date" || o === "datetime") && a instanceof Date ? `<c r="${i}" s="1"><v>${ge(a).toFixed(6)}</v></c>` : o === "boolean" ? `<c r="${i}" t="b"><v>${+!!a}</v></c>` : `<c r="${i}" t="str"><v>${R(String(a))}</v></c>`;
1488
1522
  });
1489
1523
  n.push(`<row r="${r + 2}">${i.join("")}</row>`);
1490
1524
  }), `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
@@ -1492,24 +1526,30 @@ function ye(e, t) {
1492
1526
  <sheetData>${n.join("")}</sheetData>
1493
1527
  </worksheet>`;
1494
1528
  }
1495
- function be(e, t) {
1496
- let n = ye(e, t);
1497
- return fe([
1529
+ function Ce(e, t) {
1530
+ return fe(B(e, t));
1531
+ }
1532
+ function we(e, t) {
1533
+ return pe(B(e, t));
1534
+ }
1535
+ function B(e, t) {
1536
+ let n = Se(e, t);
1537
+ return [
1498
1538
  {
1499
1539
  name: "[Content_Types].xml",
1500
- data: F.encode(z)
1540
+ data: F.encode(_e)
1501
1541
  },
1502
1542
  {
1503
1543
  name: "_rels/.rels",
1504
- data: F.encode(he)
1544
+ data: F.encode(ve)
1505
1545
  },
1506
1546
  {
1507
1547
  name: "xl/workbook.xml",
1508
- data: F.encode(ge)
1548
+ data: F.encode(ye)
1509
1549
  },
1510
1550
  {
1511
1551
  name: "xl/_rels/workbook.xml.rels",
1512
- data: F.encode(_e)
1552
+ data: F.encode(be)
1513
1553
  },
1514
1554
  {
1515
1555
  name: "xl/worksheets/sheet1.xml",
@@ -1517,30 +1557,34 @@ function be(e, t) {
1517
1557
  },
1518
1558
  {
1519
1559
  name: "xl/styles.xml",
1520
- data: F.encode(ve)
1560
+ data: F.encode(xe)
1521
1561
  }
1522
- ]);
1562
+ ];
1523
1563
  }
1524
1564
  //#endregion
1525
1565
  //#region src/export/export.ts
1526
- function B(e, t, n) {
1566
+ function V(e, t, n) {
1527
1567
  switch (n) {
1528
- case "csv": return V(e, t, ",");
1529
- case "tsv": return V(e, t, " ");
1530
- case "json": return Se(e, t);
1531
- case "xlsx": return be(e, t);
1568
+ case "csv": return H(e, t, ",");
1569
+ case "tsv": return H(e, t, " ");
1570
+ case "json": return De(e, t);
1571
+ case "xlsx": return Ce(e, t);
1532
1572
  }
1533
1573
  }
1534
- function xe(e, t) {
1574
+ async function Te(e, t, n) {
1575
+ let r = n === "xlsx" ? await we(e, t) : V(e, t, n);
1576
+ return new Blob([r], { type: je(n) });
1577
+ }
1578
+ function Ee(e, t) {
1535
1579
  return e == null ? "" : (t.type === "date" || t.type === "datetime") && e instanceof Date ? e.toISOString() : String(e);
1536
1580
  }
1537
- function V(e, t, n) {
1538
- return [t.map((e) => H(e.label, n)).join(n), ...e.map((e) => t.map((t) => H(xe(e[t.key], t), n)).join(n))].join("\n");
1581
+ function H(e, t, n) {
1582
+ return [t.map((e) => U(e.label, n)).join(n), ...e.map((e) => t.map((t) => U(Ee(e[t.key], t), n)).join(n))].join("\n");
1539
1583
  }
1540
- function H(e, t) {
1584
+ function U(e, t) {
1541
1585
  return e.includes(t) || e.includes("\"") || e.includes("\n") || e.includes("\r") ? "\"" + e.replace(/"/g, "\"\"") + "\"" : e;
1542
1586
  }
1543
- function Se(e, t) {
1587
+ function De(e, t) {
1544
1588
  let n = e.map((e) => {
1545
1589
  let n = {};
1546
1590
  for (let r of t) {
@@ -1551,35 +1595,35 @@ function Se(e, t) {
1551
1595
  });
1552
1596
  return JSON.stringify(n, null, 2);
1553
1597
  }
1554
- function Ce(e, t, n) {
1555
- let r = new Blob([e], { type: n }), i = URL.createObjectURL(r), a = document.createElement("a");
1556
- a.href = i, a.download = t, a.style.display = "none", document.body.appendChild(a), a.click(), document.body.removeChild(a), setTimeout(() => URL.revokeObjectURL(i), 100);
1598
+ function Oe(e, t) {
1599
+ let n = URL.createObjectURL(e), r = document.createElement("a");
1600
+ r.href = n, r.download = t, r.style.display = "none", document.body.appendChild(r), r.click(), document.body.removeChild(r), setTimeout(() => URL.revokeObjectURL(n), 100);
1557
1601
  }
1558
- var U = {
1602
+ var ke = {
1559
1603
  csv: "text/csv;charset=utf-8",
1560
1604
  tsv: "text/tab-separated-values;charset=utf-8",
1561
1605
  json: "application/json;charset=utf-8",
1562
1606
  xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
1563
- }, we = {
1607
+ }, Ae = {
1564
1608
  csv: ".csv",
1565
1609
  tsv: ".tsv",
1566
1610
  json: ".json",
1567
1611
  xlsx: ".xlsx"
1568
1612
  };
1569
- function Te(e) {
1570
- return U[e];
1613
+ function je(e) {
1614
+ return ke[e];
1571
1615
  }
1572
- function Ee(e) {
1573
- return we[e];
1616
+ function Me(e) {
1617
+ return Ae[e];
1574
1618
  }
1575
1619
  //#endregion
1576
1620
  //#region src/export/xlsx-reader.ts
1577
- function De(e) {
1621
+ function Ne(e) {
1578
1622
  for (let t = e.byteLength - 22; t >= 0; t--) if (e.getUint32(t, !0) === 101010256) return t;
1579
1623
  throw Error("XLSX: End of central directory not found — not a valid ZIP file");
1580
1624
  }
1581
- function Oe(e) {
1582
- let t = De(e), n = e.getUint32(t + 16, !0), r = e.getUint32(t + 12, !0), i = new TextDecoder("utf-8"), a = [], o = n, s = n + r;
1625
+ function Pe(e) {
1626
+ let t = Ne(e), n = e.getUint32(t + 16, !0), r = e.getUint32(t + 12, !0), i = new TextDecoder("utf-8"), a = [], o = n, s = n + r;
1583
1627
  for (; o < s && e.getUint32(o, !0) === 33639248;) {
1584
1628
  let t = e.getUint16(o + 10, !0), n = e.getUint32(o + 20, !0), r = e.getUint32(o + 24, !0), s = e.getUint16(o + 28, !0), c = e.getUint16(o + 30, !0), l = e.getUint16(o + 32, !0), u = e.getUint32(o + 42, !0), d = i.decode(new Uint8Array(e.buffer, o + 46, s));
1585
1629
  a.push({
@@ -1592,7 +1636,7 @@ function Oe(e) {
1592
1636
  }
1593
1637
  return a;
1594
1638
  }
1595
- async function ke(e) {
1639
+ async function Fe(e) {
1596
1640
  let t = new DecompressionStream("deflate-raw"), n = t.writable.getWriter(), r = t.readable.getReader();
1597
1641
  n.write(e), n.close();
1598
1642
  let i = [];
@@ -1605,28 +1649,28 @@ async function ke(e) {
1605
1649
  for (let e of i) o.set(e, s), s += e.length;
1606
1650
  return o;
1607
1651
  }
1608
- async function Ae(e, t) {
1652
+ async function Ie(e, t) {
1609
1653
  let n = t.localHeaderOffset, r = e.getUint16(n + 26, !0), i = e.getUint16(n + 28, !0), a = n + 30 + r + i, o = new Uint8Array(e.buffer, a, t.compressedSize);
1610
1654
  if (t.compressionMethod === 0) return o.slice();
1611
- if (t.compressionMethod === 8) return ke(o);
1655
+ if (t.compressionMethod === 8) return Fe(o);
1612
1656
  throw Error(`XLSX: Unsupported compression method ${t.compressionMethod}`);
1613
1657
  }
1614
- async function je(e) {
1615
- let t = new DataView(e), n = Oe(t), r = /* @__PURE__ */ new Map();
1616
- for (let e of n) e.compressedSize === 0 && e.name.endsWith("/") || r.set(e.name, await Ae(t, e));
1658
+ async function Le(e) {
1659
+ let t = new DataView(e), n = Pe(t), r = /* @__PURE__ */ new Map();
1660
+ for (let e of n) e.compressedSize === 0 && e.name.endsWith("/") || r.set(e.name, await Ie(t, e));
1617
1661
  return r;
1618
1662
  }
1619
- var Me = new TextDecoder("utf-8");
1663
+ var Re = new TextDecoder("utf-8");
1620
1664
  function W(e) {
1621
- let t = Me.decode(e);
1665
+ let t = Re.decode(e);
1622
1666
  return new DOMParser().parseFromString(t, "application/xml");
1623
1667
  }
1624
- function Ne(e) {
1668
+ function ze(e) {
1625
1669
  if (!e) return [];
1626
1670
  let t = W(e);
1627
1671
  return Array.from(t.querySelectorAll("si")).map((e) => Array.from(e.querySelectorAll("t")).map((e) => e.textContent ?? "").join(""));
1628
1672
  }
1629
- var Pe = /* @__PURE__ */ new Set([
1673
+ var Be = /* @__PURE__ */ new Set([
1630
1674
  14,
1631
1675
  15,
1632
1676
  16,
@@ -1646,29 +1690,29 @@ var Pe = /* @__PURE__ */ new Set([
1646
1690
  57,
1647
1691
  58
1648
1692
  ]);
1649
- function Fe(e) {
1693
+ function Ve(e) {
1650
1694
  let t = e.replace(/"[^"]*"/g, "").replace(/\\./g, "").replace(/\[[^\]]*\]/g, "").split(";")[0];
1651
1695
  return /[dy]/i.test(t);
1652
1696
  }
1653
- function Ie(e) {
1697
+ function He(e) {
1654
1698
  let t = /* @__PURE__ */ new Set();
1655
1699
  if (!e) return t;
1656
1700
  let n = W(e), r = /* @__PURE__ */ new Map();
1657
1701
  for (let e of Array.from(n.querySelectorAll("numFmts > numFmt"))) r.set(parseInt(e.getAttribute("numFmtId") ?? "", 10), e.getAttribute("formatCode") ?? "");
1658
1702
  return Array.from(n.querySelectorAll("cellXfs > xf")).forEach((e, n) => {
1659
1703
  let i = parseInt(e.getAttribute("numFmtId") ?? "0", 10), a = r.get(i);
1660
- (a === void 0 ? Pe.has(i) : Fe(a)) && t.add(n);
1704
+ (a === void 0 ? Be.has(i) : Ve(a)) && t.add(n);
1661
1705
  }), t;
1662
1706
  }
1663
- function Le(e) {
1707
+ function Ue(e) {
1664
1708
  if (!e) return !1;
1665
1709
  let t = W(e).querySelector("workbookPr")?.getAttribute("date1904");
1666
1710
  return t === "1" || t === "true";
1667
1711
  }
1668
- function Re(e, t) {
1712
+ function We(e, t) {
1669
1713
  return new Date((t ? Date.UTC(1904, 0, 1) : Date.UTC(1899, 11, 30)) + Math.floor(e) * 864e5).toISOString().slice(0, 10);
1670
1714
  }
1671
- function ze(e, t, n, r) {
1715
+ function Ge(e, t, n, r) {
1672
1716
  let i = W(e).querySelectorAll("row");
1673
1717
  if (i.length === 0) return [];
1674
1718
  let a = 0, o = /* @__PURE__ */ new Map();
@@ -1676,7 +1720,7 @@ function ze(e, t, n, r) {
1676
1720
  let i = parseInt(e.getAttribute("r") ?? "1", 10) - 1, s = /* @__PURE__ */ new Map();
1677
1721
  o.set(i, s);
1678
1722
  for (let i of e.querySelectorAll("c")) {
1679
- let e = Be(i.getAttribute("r") ?? "");
1723
+ let e = Ke(i.getAttribute("r") ?? "");
1680
1724
  e > a && (a = e);
1681
1725
  let o = i.getAttribute("t") ?? "", c = i.getAttribute("s") ?? "", l = i.querySelector("v")?.textContent ?? "", u = i.querySelector("is > t")?.textContent ?? null, d;
1682
1726
  if (o === "s") d = t[parseInt(l, 10)] ?? "";
@@ -1685,7 +1729,7 @@ function ze(e, t, n, r) {
1685
1729
  else if (l === "") d = "";
1686
1730
  else {
1687
1731
  let e = parseFloat(l);
1688
- d = c !== "" && n.has(parseInt(c, 10)) && !isNaN(e) && e >= 0 ? Re(e, r) : l;
1732
+ d = c !== "" && n.has(parseInt(c, 10)) && !isNaN(e) && e >= 0 ? We(e, r) : l;
1689
1733
  }
1690
1734
  s.set(e, d);
1691
1735
  }
@@ -1699,13 +1743,13 @@ function ze(e, t, n, r) {
1699
1743
  }
1700
1744
  return c;
1701
1745
  }
1702
- function Be(e) {
1746
+ function Ke(e) {
1703
1747
  let t = 0, n = 0;
1704
1748
  for (; n < e.length && e[n] >= "A" && e[n] <= "Z";) t = t * 26 + (e.charCodeAt(n) - 64), n++;
1705
1749
  return t - 1;
1706
1750
  }
1707
- async function Ve(e) {
1708
- let t = await je(e), n = Ne(t.get("xl/sharedStrings.xml")), r = t.get("xl/worksheets/sheet1.xml");
1751
+ async function qe(e) {
1752
+ let t = await Le(e), n = ze(t.get("xl/sharedStrings.xml")), r = t.get("xl/worksheets/sheet1.xml");
1709
1753
  if (!r) {
1710
1754
  for (let [e, n] of t) if (e.toLowerCase().includes("worksheets/sheet")) {
1711
1755
  r = n;
@@ -1713,7 +1757,7 @@ async function Ve(e) {
1713
1757
  }
1714
1758
  }
1715
1759
  if (!r) throw Error("XLSX: No worksheet found");
1716
- let i = Ie(t.get("xl/styles.xml")), a = Le(t.get("xl/workbook.xml")), o = ze(r, n, i, a);
1760
+ let i = He(t.get("xl/styles.xml")), a = Ue(t.get("xl/workbook.xml")), o = Ge(r, n, i, a);
1717
1761
  return o.length === 0 ? {
1718
1762
  headers: [],
1719
1763
  rows: []
@@ -1744,7 +1788,7 @@ var q = {
1744
1788
  lt: "<",
1745
1789
  gte: "≥",
1746
1790
  lte: "≤"
1747
- }, J = 120, Y = 40, X = 20, Z = 32, Q = 5, He = 200, $ = class extends n {
1791
+ }, J = 120, Y = 40, X = 20, Z = 32, Q = 5, Je = 200, $ = class extends n {
1748
1792
  constructor(...e) {
1749
1793
  super(...e), this.columns = [], this._data = [], this._inUndoRedo = !1, this.clearUndoOnDataChange = !1, this.clearSelectionOnDataChange = !1, this._rowHeightFromCss = Z, this.showRowNumbers = !1, this.theme = void 0, this.maxRows = 0, this.editable = !0, this.showFilters = !1, this.showContextMenu = !1, this.frozenRows = 0, this.emptyMessage = "No data", this.noMatchingMessage = "No matching data", this.loading = !1, this.importEnabled = !1, this.selectable = !1, this.dataMode = "client", this.footerData = null, this.stylesheets = [], this._scrollTop = 0, this._scrollLeft = 0, this._viewportHeight = 0, this._viewportWidth = 0, this._colLeftOffsets = [], this._totalRowWidth = 0, this._baseStyleSheets = [], this._activeCell = null, this._editingCell = null, this._sortCriteria = [], this._selection = new re(), this._editing = new ie(), this._rowSelection = new j(), this._undo = new M(), this._filters = [], this._filteredIndices = [], this._sortedIndices = [], this._openFilterKey = null, this._rowSelectionVersion = 0, this._lastCheckboxRowIndex = null, this._checkboxShiftPending = !1, this._autocompleteState = null, this._headerMenu = null, this._bodyContextMenu = null, this._commentPopup = null, this._viewDirty = !0, this._isDragging = !1, this._wasDrag = !1, this._resizing = null, this._resizeCleanup = null, this._wasHeaderDrag = !1, this._wasRowDrag = !1, this._colDrag = null, this._colDragIndicatorLeft = null, this._fillDrag = null, this._rowDrag = null, this._rowDragIndicatorY = null, this._findState = null, this._columnWidths = /* @__PURE__ */ new Map(), this._hostResizeObserver = null, this._comments = /* @__PURE__ */ new Map(), this._isDragOver = !1, this._warnedUnconstrained = !1, this._onCommentPopupOutsideClick = () => {
1750
1794
  this._commitCommentPopup();
@@ -2106,17 +2150,34 @@ var q = {
2106
2150
  return e;
2107
2151
  }
2108
2152
  exportToString(e, t) {
2109
- if (t?.selectionOnly) {
2110
- let t = this._selection.getEffectiveRange();
2111
- if (!t) return "";
2112
- let n = this.visibleColumns.slice(t.startCol, t.endCol + 1), r = [];
2113
- for (let e = t.startRow; e <= t.endRow; e++) r.push(this.data[this._toDataIndex(e)]);
2114
- return B(r, n, e);
2115
- }
2116
- return B(this._sortedIndices.map((e) => this.data[e]), this.visibleColumns, e);
2153
+ let n = this._exportSlice(t);
2154
+ return n ? V(n.rows, n.cols, e) : "";
2117
2155
  }
2118
- exportToFile(e, t) {
2119
- Ce(this.exportToString(e), t ?? `export${Ee(e)}`, Te(e));
2156
+ async exportToBlob(e, t) {
2157
+ let n = this._exportSlice(t) ?? {
2158
+ rows: [],
2159
+ cols: []
2160
+ };
2161
+ return Te(n.rows, n.cols, e);
2162
+ }
2163
+ async exportToFile(e, t) {
2164
+ Oe(await this.exportToBlob(e), t ?? `export${Me(e)}`);
2165
+ }
2166
+ _exportSlice(e) {
2167
+ if (e?.selectionOnly) {
2168
+ let e = this._selection.getEffectiveRange();
2169
+ if (!e) return null;
2170
+ let t = this.visibleColumns.slice(e.startCol, e.endCol + 1), n = [];
2171
+ for (let t = e.startRow; t <= e.endRow; t++) n.push(this.data[this._toDataIndex(t)]);
2172
+ return {
2173
+ rows: n,
2174
+ cols: t
2175
+ };
2176
+ }
2177
+ return {
2178
+ rows: this._sortedIndices.map((e) => this.data[e]),
2179
+ cols: this.visibleColumns
2180
+ };
2120
2181
  }
2121
2182
  setComment(e, t, n) {
2122
2183
  let r = this.getComment(e, t);
@@ -2173,7 +2234,7 @@ var q = {
2173
2234
  async importFromFile(e) {
2174
2235
  let t = e.name.toLowerCase();
2175
2236
  if (t.endsWith(".xlsx")) {
2176
- let t = await Ve(await e.arrayBuffer());
2237
+ let t = await qe(await e.arrayBuffer());
2177
2238
  this._applyImportedSheet(t);
2178
2239
  } else if (t.endsWith(".csv") || t.endsWith(".tsv")) {
2179
2240
  let t = await e.text(), n = g(t);
@@ -2396,7 +2457,7 @@ var q = {
2396
2457
  e !== this._viewportHeight && (this._viewportHeight = e), t !== this._viewportWidth && (this._viewportWidth = t), this._warnIfUnconstrained();
2397
2458
  }
2398
2459
  _warnIfUnconstrained() {
2399
- this._warnedUnconstrained || process.env.NODE_ENV === "production" || this._visibleRowCount < He || this.scrollHeight > this.clientHeight + 1 || (this._warnedUnconstrained = !0, console.warn(`[@iyulab/flex-table] ${this._visibleRowCount} rows but the host has no height constraint — it grew to fit every row, so virtual scrolling is off and all rows are rendered. Give the host a height (e.g. height: 400px, or flex: 1 1 auto with min-height: 0 inside a sized parent) so it becomes the scroll container.`));
2460
+ this._warnedUnconstrained || process.env.NODE_ENV === "production" || this._visibleRowCount < Je || this.scrollHeight > this.clientHeight + 1 || (this._warnedUnconstrained = !0, console.warn(`[@iyulab/flex-table] ${this._visibleRowCount} rows but the host has no height constraint — it grew to fit every row, so virtual scrolling is off and all rows are rendered. Give the host a height (e.g. height: 400px, or flex: 1 1 auto with min-height: 0 inside a sized parent) so it becomes the scroll container.`));
2400
2461
  }
2401
2462
  _onScroll() {
2402
2463
  this._scrollTop = this.scrollTop, this._scrollLeft = this.scrollLeft;
@@ -4394,4 +4455,4 @@ K([s({ type: Array })], $.prototype, "columns", void 0), K([s({
4394
4455
  attribute: "max-undo-size"
4395
4456
  })], $.prototype, "maxUndoSize", null), K([c()], $.prototype, "_scrollTop", void 0), K([c()], $.prototype, "_scrollLeft", void 0), K([c()], $.prototype, "_viewportHeight", void 0), K([c()], $.prototype, "_viewportWidth", void 0), K([c()], $.prototype, "_activeCell", void 0), K([c()], $.prototype, "_editingCell", void 0), K([c()], $.prototype, "_sortCriteria", void 0), K([c()], $.prototype, "_openFilterKey", void 0), K([c()], $.prototype, "_rowSelectionVersion", void 0), K([c()], $.prototype, "_autocompleteState", void 0), K([c()], $.prototype, "_headerMenu", void 0), K([c()], $.prototype, "_bodyContextMenu", void 0), K([c()], $.prototype, "_commentPopup", void 0), K([c()], $.prototype, "_isDragOver", void 0), $ = K([o("flex-table")], $);
4396
4457
  //#endregion
4397
- export { j as a, M as i, G as n, k as o, B as r, w as s, $ as t };
4458
+ export { j as a, M as i, G as n, k as o, V as r, w as s, $ as t };
@@ -251,16 +251,27 @@ export declare class FlexTable extends LitElement {
251
251
  }>): void;
252
252
  private _createEmptyRow;
253
253
  /**
254
- * Export table data to string in the specified format.
254
+ * Export table data to string in the specified format. Synchronous — XLSX comes back as an
255
+ * uncompressed workbook; use `exportToBlob` for a compressed one.
255
256
  * @param options.selectionOnly - Export only the currently selected range
256
257
  */
257
258
  exportToString(format: ExportFormat, options?: {
258
259
  selectionOnly?: boolean;
259
260
  }): string | Uint8Array<ArrayBuffer>;
260
261
  /**
261
- * Export table data and trigger file download.
262
+ * Export table data as a `Blob` of the format's MIME type. XLSX is DEFLATE-compressed.
263
+ * @param options.selectionOnly - Export only the currently selected range
264
+ */
265
+ exportToBlob(format: ExportFormat, options?: {
266
+ selectionOnly?: boolean;
267
+ }): Promise<Blob>;
268
+ /**
269
+ * Export table data and trigger file download. XLSX is DEFLATE-compressed; the promise settles
270
+ * once the download has been handed to the browser.
262
271
  */
263
- exportToFile(format: ExportFormat, filename?: string): void;
272
+ exportToFile(format: ExportFormat, filename?: string): Promise<void>;
273
+ /** 내보낼 행·열 — 선택만이면 선택 범위(없으면 `null`), 아니면 정렬 순서의 전체 행 · 보이는 열. */
274
+ private _exportSlice;
264
275
  /**
265
276
  * Set a comment on the cell at the given data index and column key.
266
277
  * Pass an empty string or null to remove the comment.
@@ -1,2 +1,2 @@
1
- import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./flex-table-BdnkC234.js";
1
+ import { a as e, i as t, n, o as r, r as i, s as a, t as o } from "./flex-table-MnSI0Ogl.js";
2
2
  export { o as FlexTable, e as RowSelectionState, t as UndoStack, n as effectiveAlign, i as exportData, r as flexTableLocale, a as renderCell };
@@ -23,7 +23,10 @@ export interface UseODataSourceOptions {
23
23
  baseUrl?: string;
24
24
  /** 커스텀 fetch transport(예: 인증 헤더를 주입하는 `HttpClient` 래퍼). 기본값: 전역 `fetch`. */
25
25
  fetcher?: (input: string, init: RequestInit) => Promise<Response>;
26
- /** 응답이 401/403일 때 호출(세션 만료 리다이렉트 등). 호출 후에도 기존 에러 처리는 계속 진행된다. */
26
+ /**
27
+ * 응답이 401 일 때 호출(세션 만료 리다이렉트 등). 호출 후에도 기존 에러 처리는 계속 진행된다.
28
+ * 403(인증됐지만 권한 없음)에는 부르지 않는다 — 그 실패는 `error` 로 드러난다.
29
+ */
27
30
  onUnauthorized?: (response: Response) => void;
28
31
  /**
29
32
  * `false` 인 동안 요청하지 않는다. 기본값 `true`.
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as e } from "./flex-table-BdnkC234.js";
1
+ import { t as e } from "./flex-table-MnSI0Ogl.js";
2
2
  import { i as t, t as n } from "./query-Dw4iO0ir.js";
3
3
  import { t as r } from "./view-DiLr-TdY.js";
4
4
  import i, { useCallback as a, useEffect as o, useMemo as s, useRef as c, useState as l } from "react";
@@ -34,7 +34,7 @@ function d(e, r = {}) {
34
34
  }), a = `${m ?? window.location.origin}${e}${r}`, o = async (e) => {
35
35
  let n = await h(e, { signal: t.signal });
36
36
  if (!n.ok) {
37
- (n.status === 401 || n.status === 403) && g && g(n);
37
+ n.status === 401 && g && g(n);
38
38
  let e = await n.text().catch(() => ""), t = `Request failed (${n.status})`;
39
39
  try {
40
40
  let n = JSON.parse(e);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iyulab/flex-table",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "description": "A minimalist, input-centric data grid web component",
5
5
  "type": "module",
6
6
  "main": "./dist/flex-table.js",
@@ -127,8 +127,9 @@ Enter/Escape and blur-to-commit behavior.
127
127
  | `setComment(dataIndex, colKey, text)` | `null` or `''` removes; undoable |
128
128
  | `getComment(dataIndex, colKey)` / `clearComments()` | |
129
129
  | `importFromFile(file)` | `Promise<void>`; `.xlsx`, `.csv`, `.tsv`; fires `data-import` |
130
- | `exportToString(format, { selectionOnly? })` | `string \| Uint8Array`; format `'csv' \| 'tsv' \| 'json' \| 'xlsx'` (xlsx returns bytes) |
131
- | `exportToFile(format, filename?)` | Triggers a download |
130
+ | `exportToString(format, { selectionOnly? })` | `string \| Uint8Array`; format `'csv' \| 'tsv' \| 'json' \| 'xlsx'` (xlsx returns uncompressed bytes — synchronous) |
131
+ | `exportToBlob(format, { selectionOnly? })` | `Promise<Blob>` typed with the format's MIME; xlsx is DEFLATE-compressed |
132
+ | `exportToFile(format, filename?)` | `Promise<void>` — triggers a download (xlsx compressed) |
132
133
 
133
134
  ## Events
134
135
 
@@ -102,7 +102,7 @@ function Orders() {
102
102
  | `fixedFilter` | — | odata-query filter object, always applied; value change resets to page 0 (compared by value) |
103
103
  | `baseUrl` | `window.location.origin` | For proxy/BFF setups |
104
104
  | `fetcher` | global `fetch` | `(input, init) => Promise<Response>`; keep stable |
105
- | `onUnauthorized` | — | `(response) => void` on 401/403; keep stable |
105
+ | `onUnauthorized` | — | `(response) => void` on 401 only (403 surfaces as `error`); keep stable |
106
106
  | `enabled` | `true` | While `false`: no request, `loading` stays `true`, `refresh()` is a no-op, in-flight request is cancelled |
107
107
 
108
108
  ### Result