@opencorestack/opengridx 3.1.0 → 3.2.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.
@@ -1446,6 +1446,15 @@ interface PdfExportOptions {
1446
1446
  headerTextColor?: string;
1447
1447
  fontSize?: number;
1448
1448
  font?: { name: string; data: string; boldData?: string }; // v3.0+
1449
+ onProgress?: (p: PdfExportProgress) => void; // v3.2+
1450
+ signal?: AbortSignal; // v3.2+
1451
+ maxRows?: number; // v3.2+, default 20000
1452
+ }
1453
+
1454
+ interface PdfExportProgress {
1455
+ phase: 'prepare' | 'render' | 'save';
1456
+ done: number;
1457
+ total: number;
1449
1458
  }
1450
1459
  ```
1451
1460
 
@@ -1466,3 +1475,8 @@ interface PdfExportOptions {
1466
1475
  | `headerTextColor` | `string` | `'#ffffff'` | Column header cell text color (`#rrggbb` / `#rgb`) |
1467
1476
  | `fontSize` | `number` | `9` | Body cell font size in points |
1468
1477
  | `font` | `{ name, data, boldData? }` | — | Base64 TrueType font for text outside Latin-1 (₹, CJK, Cyrillic…). Without it, such characters are replaced with `?` (v3.0+) |
1478
+ | `onProgress` | `(p: PdfExportProgress) => void` | — | Called per slice with `{ phase, done, total }`; phases `prepare` → `render` → `save`, `done` monotonic per phase and ending at `total` (v3.2+) |
1479
+ | `signal` | `AbortSignal` | — | Abort → the promise rejects with `DOMException` `'AbortError'`, no file saved. The final jsPDF save step cannot be interrupted (v3.2+) |
1480
+ | `maxRows` | `number` | `20000` | Above this many table rows a dev-mode `console.warn` recommends `exportToCsv` / `exportToExcelAdvanced`. Never throws; `Infinity` silences it (v3.2+) |
1481
+
1482
+ The export yields to the event loop between ~30 ms slices while formatting and drawing (v3.2+), so the page stays responsive; only jsPDF's final save is one synchronous step. See [PDF Export › Large exports](./features/pdf-export.md#large-exports).
@@ -16,7 +16,7 @@ npm install jspdf jspdf-autotable
16
16
  > await exportToPdf(rows, columns, { fileName: 'invoices', font: { name: 'NotoSans', data: notoSansBase64 } });
17
17
  > ```
18
18
 
19
- > **Large datasets:** PDF generation runs on the main thread, and time and file size grow with every row: 50,000 grouped rows took 10–16 s (the page is unresponsive meanwhile) and produced a file of about 120 MB. For more than a few thousand rows, export CSV (`exportToCsv`) or `.xlsx` (`exportToExcelAdvanced`) instead, or export a filtered subset.
19
+ > **Large datasets:** time and file size grow with every row (50,000 grouped rows: several seconds and a file of 75–120 MB). Since v3.2 the export is sliced so the page stays responsive, and it reports progress and can be cancelled, but for more than a few thousand rows CSV (`exportToCsv`) or `.xlsx` (`exportToExcelAdvanced`) is still the better format. See [Large exports](#large-exports).
20
20
 
21
21
  ## Basic Usage
22
22
 
@@ -97,6 +97,42 @@ await exportToPdf(
97
97
  | `headerTextColor` | `string` | `'#ffffff'` | Column header cell text color (`#rrggbb` or `#rgb`). Invalid values fall back to the default |
98
98
  | `fontSize` | `number` | `9` | Body cell font size in points |
99
99
  | `font` | `{ name: string; data: string; boldData?: string }` | — | A TrueType font (base64 `.ttf`) for text outside Latin-1 (v3.0+). See [Non-Latin text](#non-latin-text-and-unicode-fonts) |
100
+ | `onProgress` | `(p: PdfExportProgress) => void` | — | Progress: `{ phase: 'prepare' \| 'render' \| 'save', done, total }` (v3.2+). See [Large exports](#large-exports) |
101
+ | `signal` | `AbortSignal` | — | Cancels the export: the promise rejects with a `DOMException` named `'AbortError'` and nothing is saved (v3.2+) |
102
+ | `maxRows` | `number` | `20000` | Table-row count above which a development-mode warning recommends CSV / `.xlsx`. The export still runs; `Infinity` silences it (v3.2+) |
103
+
104
+ ## Large exports
105
+
106
+ `exportToPdf` returns a `Promise<void>`. Since v3.2 it works in slices of about 30 ms and yields to the event loop between them, both while formatting the rows and while drawing the table, so the page keeps painting and handling input. Pass `onProgress` for a progress bar and `signal` for a Cancel button:
107
+
108
+ ```tsx
109
+ const controllerRef = useRef<AbortController | null>(null);
110
+ const [progress, setProgress] = useState<PdfExportProgress | null>(null);
111
+
112
+ const onExport = async () => {
113
+ const controller = new AbortController();
114
+ controllerRef.current = controller;
115
+ try {
116
+ await exportToPdf(rows, columns, {
117
+ groupedRows: apiRef.current.getGroupedExportRows() ?? undefined,
118
+ signal: controller.signal,
119
+ onProgress: setProgress,
120
+ });
121
+ } catch (error) {
122
+ if (!(error instanceof DOMException && error.name === 'AbortError')) throw error;
123
+ } finally {
124
+ setProgress(null);
125
+ }
126
+ };
127
+ // <button onClick={() => controllerRef.current?.abort()}>Cancel</button>
128
+ ```
129
+
130
+ - **Phases.** `prepare` formats the rows (`total` = rows, or grouped entries with `groupedRows`); `render` draws the table (`total` = table body rows); `save` is reported as `0/1` before the file is written and `1/1` after. Within a phase `done` never decreases and ends at `total`.
131
+ - **Cancel.** When `signal` aborts, the promise rejects with `DOMException` `'AbortError'` at the next slice boundary and no file is saved. An already aborted signal rejects before jsPDF is loaded.
132
+ - **The last step still blocks.** jsPDF builds the whole file in one synchronous call when saving; that step cannot be split or cancelled. It is short next to the rest (about 0.4–0.6 s for 50,000 grouped rows in Chromium), but it grows with the file.
133
+ - **Same output.** The table is drawn as a series of `jspdf-autotable` calls that continue on the same page, with fixed column widths, the header repeated on every page and the footer only at the end. Pages, row positions, shading and page breaks are the same as a single call; the file is marginally larger (repeated style operators).
134
+ - **`maxRows` (default 20,000).** Above it a console warning in development recommends `exportToCsv` or `exportToExcelAdvanced`; nothing is thrown, so existing exports keep working. 20,000 rows is roughly where a PDF passes 30 MB and a few seconds to build, and where viewers start to struggle to open it. Pass a larger value (or `Infinity`) to silence the warning when a large PDF is intended.
135
+ - **Total time.** Slicing makes an export slightly slower overall, not faster. Formatting is a small part of the work; drawing the table dominates.
100
136
 
101
137
  ## Totals and filter summary
102
138
 
@@ -148,7 +184,7 @@ Pass the `font` option with a Unicode TrueType font. See [Non-Latin text](#non-l
148
184
 
149
185
  **Export is slow or the file is huge**
150
186
 
151
- PDF is not suited to tens of thousands of rows (see Installation above). Use CSV or `.xlsx` for large exports.
187
+ PDF is not suited to tens of thousands of rows. Show progress and a Cancel button (see [Large exports](#large-exports)), or use CSV or `.xlsx` for large exports.
152
188
 
153
189
  **Error: "exportToPdf requires 'jspdf' and 'jspdf-autotable'"**
154
190
 
package/lib/index.ts CHANGED
@@ -136,4 +136,5 @@ export type {
136
136
  GridRowMeta,
137
137
  GridLocaleText,
138
138
  PdfExportOptions,
139
+ PdfExportProgress,
139
140
  } from './types';
@@ -527,6 +527,37 @@ export interface PdfExportOptions<R extends GridValidRowModel = GridRowModel> {
527
527
  * Has no effect when the grid has no active `rowGroupingModel`, or when `selectedRows` is non-empty.
528
528
  */
529
529
  groupedRows?: GridGroupedExportRow[];
530
+
531
+ /**
532
+ * Called as the export advances: `prepare` (formatting rows; `total` = source rows or grouped
533
+ * entries), `render` (drawing table rows; `total` = table body rows) and `save` (`0/1` before the
534
+ * file is written, `1/1` after). `done` never decreases within a phase and ends at `total`.
535
+ * @since v3.2
536
+ */
537
+ onProgress?: (progress: PdfExportProgress) => void;
538
+
539
+ /**
540
+ * Cancels the export. When aborted, the returned promise rejects with a `DOMException` named
541
+ * `'AbortError'` and no file is saved. Checked between slices of work, so cancellation takes
542
+ * effect within one slice; the final save step cannot be interrupted once started.
543
+ * @since v3.2
544
+ */
545
+ signal?: AbortSignal;
546
+
547
+ /**
548
+ * Table-row count above which a development-mode warning recommends `exportToCsv` or
549
+ * `exportToExcelAdvanced`. The export still runs (nothing is thrown). Pass `Infinity` to silence it.
550
+ * Default: 20000
551
+ * @since v3.2
552
+ */
553
+ maxRows?: number;
554
+ }
555
+
556
+ /** Progress reported by `PdfExportOptions.onProgress`. @since v3.2 */
557
+ export interface PdfExportProgress {
558
+ phase: 'prepare' | 'render' | 'save';
559
+ done: number;
560
+ total: number;
530
561
  }
531
562
 
532
563
  /**
@@ -57,8 +57,49 @@ interface AutoTableOptions {
57
57
  showFoot?: string;
58
58
  showHead?: string;
59
59
  margin?: { top: number; left: number; right: number; bottom: number };
60
+ willDrawPage?: (data: AutoTableHookData) => void;
60
61
  }
61
62
 
63
+ /** The part of jspdf-autotable's hook data used here; `settings` and `cursor` are the table's live objects. */
64
+ interface AutoTableHookData {
65
+ settings: { showHead: string };
66
+ cursor: { y: number } | null;
67
+ }
68
+
69
+ type AutoTableFn = (doc: JsPDFDoc, opts: AutoTableOptions) => void;
70
+
71
+ /** How the work is sliced. Output does not depend on these values; tests shrink them. */
72
+ export interface PdfExportTuning {
73
+ /** Longest stretch of synchronous work before yielding to the event loop, in ms. */
74
+ sliceMs: number;
75
+ /** Table rows drawn by the first autoTable call; later calls adapt to `sliceMs`. Rounded to even. */
76
+ renderChunkRows: number;
77
+ /** Resize later chunks to fit `sliceMs` (default behaviour); false keeps every chunk at `renderChunkRows`. */
78
+ adaptive: boolean;
79
+ }
80
+
81
+ const DEFAULT_TUNING: PdfExportTuning = { sliceMs: 30, renderChunkRows: 40, adaptive: true };
82
+
83
+ /** Default `maxRows`: above this a PDF is tens of MB and takes seconds to build and open. */
84
+ export const PDF_MAX_ROWS_DEFAULT = 20_000;
85
+
86
+ /**
87
+ * Let the browser paint and run other tasks. A macrotask on purpose: `scheduler.yield()` resumes
88
+ * ahead of queued tasks, which starves timers and React's scheduler (a progress bar would not
89
+ * update until the export finished).
90
+ */
91
+ function yieldToEventLoop(): Promise<void> {
92
+ return new Promise(resolve => setTimeout(resolve, 0));
93
+ }
94
+
95
+ function abortError(): DOMException {
96
+ return new DOMException('PDF export was aborted', 'AbortError');
97
+ }
98
+
99
+ const now = (): number => (typeof performance !== 'undefined' ? performance.now() : Date.now());
100
+
101
+ const evenAtLeast2 = (n: number): number => Math.max(2, Math.floor(n / 2) * 2);
102
+
62
103
  type Rgb = [number, number, number];
63
104
 
64
105
  const DEFAULT_HEADER_BACKGROUND: Rgb = [79, 70, 229];
@@ -144,21 +185,37 @@ const LINE_HEIGHT_MM_PER_PT = 1.15 * 0.3528;
144
185
 
145
186
  export function exportToPdf<R extends GridValidRowModel = GridRowModel>(rows: R[], columns: GridColDef<R>[], options?: PdfExportOptions<R>): Promise<void>;
146
187
  export function exportToPdf<R extends GridValidRowModel = GridRowModel>(rows: R[], columns: GridColDef[], options?: PdfExportOptions<R>): Promise<void>;
147
- export async function exportToPdf<R extends GridRowModel>(
188
+ export function exportToPdf<R extends GridRowModel>(
148
189
  rows: R[],
149
190
  columns: GridColDef<R>[],
150
191
  options: PdfExportOptions<R> = {}
151
192
  ): Promise<void> {
193
+ return runPdfExport(rows, columns, options, DEFAULT_TUNING);
194
+ }
195
+
196
+ /** `exportToPdf` with explicit slicing. Internal: not part of the public API. */
197
+ export async function runPdfExport<R extends GridRowModel>(
198
+ rows: R[],
199
+ columns: GridColDef<R>[],
200
+ options: PdfExportOptions<R>,
201
+ tuning: PdfExportTuning,
202
+ ): Promise<void> {
203
+ const { signal, onProgress } = options;
204
+ const checkAbort = (): void => {
205
+ if (signal?.aborted) throw abortError();
206
+ };
207
+ checkAbort();
208
+
152
209
  // Lazy-load peer deps — provides a clear error if not installed
153
210
  let JsPDF: new (opts: Record<string, unknown>) => JsPDFDoc;
154
- let autoTable: (doc: JsPDFDoc, opts: AutoTableOptions) => void;
211
+ let autoTable: AutoTableFn;
155
212
  try {
156
213
  const [jspdfMod, autoTableMod] = await Promise.all([
157
214
  import('jspdf'),
158
215
  import('jspdf-autotable'),
159
216
  ]);
160
217
  JsPDF = jspdfMod.default as unknown as new (opts: Record<string, unknown>) => JsPDFDoc;
161
- autoTable = autoTableMod.default as unknown as (doc: JsPDFDoc, opts: AutoTableOptions) => void;
218
+ autoTable = autoTableMod.default as unknown as AutoTableFn;
162
219
  } catch {
163
220
  throw new Error(
164
221
  "exportToPdf requires 'jspdf' and 'jspdf-autotable'. Run: npm install jspdf jspdf-autotable"
@@ -181,7 +238,21 @@ export async function exportToPdf<R extends GridRowModel>(
181
238
  fontSize = 9,
182
239
  groupedRows,
183
240
  font,
241
+ maxRows = PDF_MAX_ROWS_DEFAULT,
184
242
  } = options;
243
+ checkAbort();
244
+
245
+ // Time-sliced work: `maybeYield` yields once the current slice has run for `sliceMs`.
246
+ let sliceStart = now();
247
+ const maybeYield = async (): Promise<void> => {
248
+ if (now() - sliceStart < tuning.sliceMs) return;
249
+ await yieldToEventLoop();
250
+ checkAbort();
251
+ sliceStart = now();
252
+ };
253
+ const report = (phase: 'prepare' | 'render' | 'save', done: number, total: number): void => {
254
+ onProgress?.({ phase, done, total });
255
+ };
185
256
 
186
257
  const doc = new JsPDF({ orientation, unit: 'mm', format: 'a4' });
187
258
  const pageWidth = doc.internal.pageSize.getWidth();
@@ -274,30 +345,48 @@ export async function exportToPdf<R extends GridRowModel>(
274
345
  const aggregateTexts = (values: Record<string, unknown>): string[] =>
275
346
  exportColumns.map(col => formatExportAggregate(col, aggMod[col.field], values[col.field], values));
276
347
 
277
- let body: string[][];
348
+ const body: string[][] = [];
278
349
  let foot: string[][] | undefined;
350
+ const pushRow = (cells: string[]): void => {
351
+ body.push(cells.map(pdfText));
352
+ };
279
353
 
280
354
  if (useGrouped) {
281
355
  // Grouped export: flatten the ordered list into body rows with indentation
282
- body = [];
283
- groupedRows.forEach((entry: GridGroupedExportRow) => {
356
+ const total = groupedRows.length;
357
+ report('prepare', 0, total);
358
+ for (let index = 0; index < total; index++) {
359
+ const entry: GridGroupedExportRow = groupedRows[index];
284
360
  const indent = ' '.repeat(entry.depth * 2);
285
361
  if (entry.type === 'group-header') {
286
362
  const label = `${indent}${groupHeaderLabel(entry, columns)}`;
287
- body.push(exportColumns.map((_, i) => i === 0 ? label : ''));
363
+ pushRow(exportColumns.map((_, i) => i === 0 ? label : ''));
288
364
  } else if (entry.type === 'leaf' && entry.row) {
289
365
  const row = entry.row as R;
290
- body.push(exportColumns.map((col, i) => (i === 0 ? indent : '') + resolveValue(row, col)));
366
+ pushRow(exportColumns.map((col, i) => (i === 0 ? indent : '') + resolveValue(row, col)));
291
367
  } else if (entry.type === 'group-subtotal' && entry.aggregatedValues) {
292
368
  const texts = aggregateTexts(entry.aggregatedValues);
293
- body.push(texts.map((text, i) => (i === 0 ? `${indent}${summaryLabelText('Subtotal', text)}` : text)));
369
+ pushRow(texts.map((text, i) => (i === 0 ? `${indent}${summaryLabelText('Subtotal', text)}` : text)));
294
370
  } else if (entry.type === 'grand-total' && entry.aggregatedValues) {
295
371
  const texts = aggregateTexts(entry.aggregatedValues);
296
- foot = [texts.map((text, i) => (i === 0 ? summaryLabelText('Grand Total', text) : text))];
372
+ foot = [texts.map((text, i) => (i === 0 ? summaryLabelText('Grand Total', text) : text)).map(pdfText)];
297
373
  }
298
- });
374
+ if ((index & 63) === 63) {
375
+ report('prepare', index + 1, total);
376
+ await maybeYield();
377
+ }
378
+ }
379
+ report('prepare', total, total);
299
380
  } else {
300
- body = rowsToExport.map(row => exportColumns.map(col => resolveValue(row, col)));
381
+ const total = rowsToExport.length;
382
+ report('prepare', 0, total);
383
+ for (let index = 0; index < total; index++) {
384
+ pushRow(exportColumns.map(col => resolveValue(rowsToExport[index], col)));
385
+ if ((index & 63) === 63) {
386
+ report('prepare', index + 1, total);
387
+ await maybeYield();
388
+ }
389
+ }
301
390
 
302
391
  const totals = aggregationModel
303
392
  ? aggregationForExport(rowsToExport, hasSelection(selectedRows), columns, aggregationResult, aggregationModel)
@@ -305,12 +394,19 @@ export async function exportToPdf<R extends GridRowModel>(
305
394
  if (totals && aggregationModel) {
306
395
  const hasAgg = exportColumns.some(c => aggregationModel[c.field]);
307
396
  const texts = aggregateTexts(totals);
308
- foot = [texts.map((text, i) => (i === 0 && hasAgg ? summaryLabelText('TOTAL', text) : text))];
397
+ foot = [texts.map((text, i) => (i === 0 && hasAgg ? summaryLabelText('TOTAL', text) : text)).map(pdfText)];
309
398
  }
399
+ report('prepare', total, total);
310
400
  }
401
+ checkAbort();
311
402
 
312
- body = body.map(cells => cells.map(pdfText));
313
- if (foot) foot = foot.map(cells => cells.map(pdfText));
403
+ if (body.length > maxRows && process.env.NODE_ENV !== 'production') {
404
+ console.warn(
405
+ `[exportToPdf] Exporting ${body.length} table rows (maxRows: ${maxRows}). PDFs this large are slow to build ` +
406
+ 'and open and can reach tens of MB. Prefer exportToCsv or exportToExcelAdvanced for full-dataset exports, ' +
407
+ 'or pass a larger `maxRows` to silence this warning.'
408
+ );
409
+ }
314
410
 
315
411
  if (replacedCharacters) {
316
412
  console.warn(
@@ -325,11 +421,8 @@ export async function exportToPdf<R extends GridRowModel>(
325
421
  const [hr, hg, hb] = hexToRgb(headerBackgroundColor, DEFAULT_HEADER_BACKGROUND);
326
422
  const [tr, tg, tb] = hexToRgb(headerTextColor, DEFAULT_HEADER_TEXT);
327
423
 
328
- autoTable(doc, {
424
+ const baseTableOptions: Omit<AutoTableOptions, 'body' | 'startY'> = {
329
425
  head,
330
- body,
331
- ...(foot ? { foot } : {}),
332
- startY,
333
426
  styles: { fontSize, cellPadding: 3, font: fontName, overflow: 'linebreak' },
334
427
  headStyles: {
335
428
  fillColor: [hr, hg, hb],
@@ -343,10 +436,59 @@ export async function exportToPdf<R extends GridRowModel>(
343
436
  ? { footStyles: { fillColor: [241, 245, 249], fontStyle: 'bold', textColor: [0, 0, 0] } }
344
437
  : {}),
345
438
  columnStyles,
346
- showFoot: foot ? 'lastPage' : 'never',
347
439
  showHead: 'everyPage',
348
440
  margin: { top: MARGIN, left: MARGIN, right: MARGIN, bottom: MARGIN + 6 },
349
- });
441
+ };
442
+
443
+ // The body is drawn as a series of autoTable calls, each continuing where the previous one
444
+ // ended, so the drawing phase yields too. Column widths are fixed (columnStyles), chunks hold
445
+ // an even number of rows (alternate shading keeps its parity), only the last chunk carries the
446
+ // footer, and a continuation chunk skips the header on the page it starts on but repeats it on
447
+ // every page it adds: the same pages a single call would produce.
448
+ const totalRows = body.length;
449
+ report('render', 0, totalRows);
450
+ let drawn = 0;
451
+ let chunkRows = evenAtLeast2(tuning.renderChunkRows);
452
+ do {
453
+ const chunk = body.slice(drawn, drawn + chunkRows);
454
+ const isFirst = drawn === 0;
455
+ const isLast = drawn + chunk.length >= totalRows;
456
+ const continueY = isFirst ? startY : doc.lastAutoTable.finalY;
457
+ let firstPageOfChunk = true;
458
+ const started = now();
459
+ autoTable(doc, {
460
+ ...baseTableOptions,
461
+ body: chunk,
462
+ ...(isLast && foot ? { foot } : {}),
463
+ // A continuation starts at the top margin so autoTable's "does the table fit below
464
+ // startY" check never adds a page; the hook then moves the cursor to where the previous
465
+ // chunk ended, and each row breaks the page exactly as it would in a single table.
466
+ startY: isFirst ? startY : MARGIN,
467
+ showFoot: isLast && foot ? 'lastPage' : 'never',
468
+ ...(isFirst ? {} : {
469
+ willDrawPage: (data: AutoTableHookData) => {
470
+ if (firstPageOfChunk) {
471
+ firstPageOfChunk = false;
472
+ // Same page as the previous chunk: its header is already drawn.
473
+ data.settings.showHead = 'never';
474
+ if (data.cursor) data.cursor.y = continueY;
475
+ } else {
476
+ data.settings.showHead = 'everyPage';
477
+ }
478
+ },
479
+ }),
480
+ });
481
+ drawn += chunk.length;
482
+ report('render', drawn, totalRows);
483
+ const elapsed = now() - started;
484
+ // Aim each call at one slice; grow at most 4x at a time.
485
+ if (tuning.adaptive) chunkRows = evenAtLeast2(elapsed > 0 ? Math.min(chunkRows * 4, chunkRows * (tuning.sliceMs / elapsed)) : chunkRows * 4);
486
+ if (!isLast) {
487
+ await yieldToEventLoop();
488
+ checkAbort();
489
+ sliceStart = now();
490
+ }
491
+ } while (drawn < totalRows);
350
492
 
351
493
  // Two-pass page numbering — stamp all pages after the table is fully rendered
352
494
  // so each stamp reads the correct final total (not the running count during draw)
@@ -361,7 +503,11 @@ export async function exportToPdf<R extends GridRowModel>(
361
503
  pageHeight - 8,
362
504
  { align: 'right' }
363
505
  );
506
+ await maybeYield();
364
507
  }
365
508
 
509
+ checkAbort();
510
+ report('save', 0, 1);
366
511
  doc.save(`${fileName}.pdf`);
512
+ report('save', 1, 1);
367
513
  }
@@ -690,4 +690,4 @@ function downloadFile(content: string, fileName: string, mimeType: string): void
690
690
  }
691
691
 
692
692
  export { exportToPdf } from './exportToPdf';
693
- export type { PdfExportOptions } from '../../types';
693
+ export type { PdfExportOptions, PdfExportProgress } from '../../types';
package/llms.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenGridX — AI Agent Context
2
2
  # Package: @opencorestack/opengridx
3
- # Version: 3.1.0
3
+ # Version: 3.2.0
4
4
  # Docs: https://opencorestack.github.io/OpenGridX/
5
5
  # This file is machine-readable. AI developers: start here.
6
6
 
@@ -497,10 +497,13 @@ await exportToExcelAdvanced(rows, columns, {
497
497
  });
498
498
  exportToJson(rows, columns, { fileName: 'export.json' });
499
499
  await exportToPdf(rows, columns, { fileName: 'export', title: 'My Report' });
500
+ // 3.2.0+: large PDFs — progress, cancel, size warning (never throws)
501
+ await exportToPdf(rows, columns, { onProgress: ({ phase, done, total }) => {}, signal: controller.signal, maxRows: 20000 });
500
502
  await printGrid(rows, columns, 'My Report Title');
501
503
  ```
502
504
 
503
505
  - Every export accepts `selectedRows`, `getRowId` (pass the grid's getRowId so ids match), `aggregationResult` + `aggregationModel` (totals row) and `groupedRows` from `apiRef.current.getGroupedExportRows()`.
506
+ - exportToPdf (3.2.0+) works in ~30 ms slices and yields between them (prepare and render phases), so the page stays responsive; only jsPDF's final save is one synchronous step. `onProgress({ phase: 'prepare'|'render'|'save', done, total })`; `signal` abort rejects with DOMException 'AbortError' and saves nothing; above `maxRows` (default 20000) a dev console.warn recommends exportToCsv / exportToExcelAdvanced. Output (pages, positions) is the same as before.
504
507
  - CSV and exportToExcel neutralise formulas: text starting with = + - @ tab or CR gets a leading `'` (`escapeFormulas: false` opts out).
505
508
  - exportToExcel writes HTML: a `.xlsx` fileName is saved as `.xls` with a console warning.
506
509
  - Columns with `exportable: false` are left out everywhere; `valueGetter` and `valueFormatter` are applied.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencorestack/opengridx",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
5
5
  "type": "module",
6
6
  "main": "./dist/opengridx.umd.js",