@angular-bootstrap/ngbootstrap 2.0.2 → 2.0.3

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,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.3 - 2026-07-05
4
+
5
+ ### Fixed
6
+
7
+ - Replaced the default PDF export implementation with a dependency-free browser PDF writer so Angular apps do not need jsPDF optional HTML/canvas dependencies for basic table export.
8
+ - Removed `jspdf` and `jspdf-autotable` from optional peer dependencies.
9
+
3
10
  ## 2.0.2 - 2026-07-05
4
11
 
5
12
  ### Fixed
package/README.md CHANGED
@@ -29,15 +29,21 @@ npm install @angular-bootstrap/ngbootstrap bootstrap bootstrap-icons
29
29
  Optional integrations are installed only when you use those features:
30
30
 
31
31
  ```bash
32
- npm install chart.js jspdf jspdf-autotable
32
+ npm install chart.js
33
33
  ```
34
34
 
35
+ PDF and Excel export are dependency-free by default.
36
+
35
37
  Excel export is dependency-free by default. The built-in `BrowserExcelExportAdapter`
36
38
  generates an Excel-compatible workbook in the browser and avoids unmaintained
37
39
  spreadsheet writer dependencies. It is intended for visible column values and
38
40
  basic scalar cell types; use a custom `ExcelExportAdapter` for formulas, charts,
39
41
  multiple sheets, workbook styling, or other advanced workbook features.
40
42
 
43
+ The built-in PDF adapter generates a simple table PDF in the browser. Provide a custom
44
+ `PdfExportAdapter` when your product needs branded PDFs, images, charts, advanced layout,
45
+ rich typography, headers, footers, or more precise pagination.
46
+
41
47
  ## Use
42
48
 
43
49
  Import standalone components directly in your Angular component.
package/RELEASE_NOTES.md CHANGED
@@ -1,18 +1,15 @@
1
- # @angular-bootstrap/ngbootstrap 2.0.2
1
+ # @angular-bootstrap/ngbootstrap 2.0.3
2
2
 
3
- This patch fixes PDF export bundling for Angular browser apps.
3
+ This patch makes the default PDF export path dependency-free.
4
4
 
5
5
  ## Fixed
6
6
 
7
- - `JsPdfAdapter` now loads jsPDF from its browser UMD bundle.
8
- - This avoids forcing consuming apps to resolve jsPDF optional ESM dependencies such as HTML/canvas sanitization helpers when they only need table PDF export.
7
+ - `JsPdfAdapter` now generates a simple table PDF directly in the browser.
8
+ - Removed `jspdf` and `jspdf-autotable` from optional peer dependencies.
9
+ - Angular apps no longer need jsPDF optional HTML/canvas dependencies for basic DataGrid PDF export.
9
10
 
10
11
  ## Notes
11
12
 
12
- Applications using PDF export should keep both maintained PDF integrations installed:
13
+ PDF and Excel export are dependency-free by default. The built-in PDF adapter is intended for simple table export only.
13
14
 
14
- ```bash
15
- npm install jspdf jspdf-autotable
16
- ```
17
-
18
- Excel export remains dependency-free through `BrowserExcelExportAdapter`.
15
+ Use a custom `PdfExportAdapter` for branded layouts, images, charts, rich typography, or advanced pagination.
@@ -1249,13 +1249,104 @@ class NgbDatagridDefaultEditService {
1249
1249
  }
1250
1250
  }
1251
1251
 
1252
+ const PAGE_SIZES = {
1253
+ A4: [595.28, 841.89],
1254
+ Letter: [612, 792],
1255
+ };
1252
1256
  class JsPdfAdapter {
1253
- async export({ fileName, columns, rows, options }) {
1254
- const { jsPDF } = await import('jspdf/dist/jspdf.umd.min.js');
1255
- const autoTable = (await import('jspdf-autotable')).default;
1256
- const doc = new jsPDF({ orientation: options?.landscape ? 'landscape' : 'portrait' });
1257
- autoTable(doc, { head: [columns], body: rows.map(r => columns.map(k => r[k])), margin: options?.margins });
1258
- doc.save(`${fileName}.pdf`);
1257
+ async export(payload) {
1258
+ const pdf = this.createPdf(payload);
1259
+ const blob = new Blob([pdf], { type: 'application/pdf' });
1260
+ const url = URL.createObjectURL(blob);
1261
+ const link = document.createElement('a');
1262
+ link.href = url;
1263
+ link.download = `${payload.fileName}.pdf`;
1264
+ link.click();
1265
+ URL.revokeObjectURL(url);
1266
+ }
1267
+ createPdf({ fileName, columns, rows, options }) {
1268
+ const requestedSize = String(options?.pageSize || 'A4');
1269
+ const baseSize = PAGE_SIZES[requestedSize] || PAGE_SIZES.A4;
1270
+ const landscape = Boolean(options?.landscape);
1271
+ const [pageWidth, pageHeight] = landscape ? [baseSize[1], baseSize[0]] : baseSize;
1272
+ const margin = this.resolveMargin(options?.margins);
1273
+ const lineHeight = 14;
1274
+ const maxLines = Math.max(1, Math.floor((pageHeight - margin.top - margin.bottom - 52) / lineHeight));
1275
+ const tableLines = this.toTableLines(columns, rows);
1276
+ const pages = this.chunk(tableLines, maxLines);
1277
+ const objects = [];
1278
+ const pageIds = [];
1279
+ objects.push('<< /Type /Catalog /Pages 2 0 R >>');
1280
+ objects.push('');
1281
+ for (const [index, lines] of pages.entries()) {
1282
+ const pageObjectId = objects.length + 1;
1283
+ const contentObjectId = pageObjectId + 1;
1284
+ pageIds.push(pageObjectId);
1285
+ objects.push(`<< /Type /Page /Parent 2 0 R /MediaBox [0 0 ${pageWidth} ${pageHeight}] /Resources << /Font << /F1 ${pages.length * 2 + 3} 0 R >> >> /Contents ${contentObjectId} 0 R >>`);
1286
+ objects.push(this.contentStream(fileName, lines, index + 1, pages.length, pageHeight, margin, lineHeight));
1287
+ }
1288
+ objects[1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`;
1289
+ objects.push('<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>');
1290
+ return this.serializePdf(objects);
1291
+ }
1292
+ resolveMargin(margins) {
1293
+ if (!Array.isArray(margins) || margins.length !== 4) {
1294
+ return { top: 40, right: 40, bottom: 40, left: 40 };
1295
+ }
1296
+ const [top, right, bottom, left] = margins.map(value => Math.max(16, Number(value) || 40));
1297
+ return { top, right, bottom, left };
1298
+ }
1299
+ toTableLines(columns, rows) {
1300
+ const headers = columns.map(column => this.printable(column));
1301
+ const body = rows.map(row => columns.map(column => this.printable(row?.[column])));
1302
+ const widths = headers.map((header, index) => Math.min(24, Math.max(header.length, ...body.map(values => values[index]?.length || 0))));
1303
+ const format = (values) => values.map((value, index) => value.padEnd(widths[index]).slice(0, widths[index])).join(' ');
1304
+ return [
1305
+ format(headers),
1306
+ widths.map(width => '-'.repeat(width)).join(' '),
1307
+ ...body.map(format),
1308
+ ];
1309
+ }
1310
+ contentStream(fileName, lines, page, totalPages, pageHeight, margin, lineHeight) {
1311
+ const commands = [
1312
+ 'BT',
1313
+ `/F1 16 Tf ${margin.left} ${pageHeight - margin.top} Td (${this.pdfText(fileName)}) Tj`,
1314
+ `/F1 9 Tf 0 -24 Td (${this.pdfText(`Page ${page} of ${totalPages}`)}) Tj`,
1315
+ ...lines.map(line => `0 -${lineHeight} Td (${this.pdfText(line)}) Tj`),
1316
+ 'ET',
1317
+ ].join('\n');
1318
+ return `<< /Length ${commands.length} >>\nstream\n${commands}\nendstream`;
1319
+ }
1320
+ chunk(items, size) {
1321
+ const pages = [];
1322
+ for (let index = 0; index < items.length; index += size) {
1323
+ pages.push(items.slice(index, index + size));
1324
+ }
1325
+ return pages.length ? pages : [[]];
1326
+ }
1327
+ printable(value) {
1328
+ if (value === null || value === undefined) {
1329
+ return '';
1330
+ }
1331
+ return String(value).replace(/[^\x20-\x7E]/g, '?').replace(/\s+/g, ' ').trim();
1332
+ }
1333
+ pdfText(value) {
1334
+ return value.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)');
1335
+ }
1336
+ serializePdf(objects) {
1337
+ let pdf = '%PDF-1.4\n';
1338
+ const offsets = [0];
1339
+ for (const [index, object] of objects.entries()) {
1340
+ offsets[index + 1] = pdf.length;
1341
+ pdf += `${index + 1} 0 obj\n${object}\nendobj\n`;
1342
+ }
1343
+ const xrefOffset = pdf.length;
1344
+ pdf += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`;
1345
+ for (let index = 1; index <= objects.length; index++) {
1346
+ pdf += `${String(offsets[index]).padStart(10, '0')} 00000 n \n`;
1347
+ }
1348
+ pdf += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`;
1349
+ return pdf;
1259
1350
  }
1260
1351
  }
1261
1352
 
@@ -14254,8 +14345,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.0", ngImpor
14254
14345
  args: ['nodeBtn', { read: ElementRef }]
14255
14346
  }] } });
14256
14347
 
14257
- /// <reference path="./types/optional-peer-deps.d.ts" />
14258
-
14259
14348
  /**
14260
14349
  * Generated bundle index. Do not edit.
14261
14350
  */