@fuentis/phoenix-ui 0.0.9-alpha.572 → 0.0.9-alpha.573

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.
@@ -74,9 +74,9 @@ import * as i14 from 'primeng/floatlabel';
74
74
  import { FloatLabelModule } from 'primeng/floatlabel';
75
75
  import * as i2$4 from 'primeng/selectbutton';
76
76
  import { SelectButtonModule } from 'primeng/selectbutton';
77
- import jsPDF from 'jspdf';
78
- import autoTable from 'jspdf-autotable';
79
77
  import * as XLSX from 'xlsx';
78
+ import pdfMake from 'pdfmake/build/pdfmake';
79
+ import pdfFonts from 'pdfmake/build/vfs_fonts';
80
80
  import * as i1$2 from 'primeng/dynamicdialog';
81
81
  import { DialogService } from 'primeng/dynamicdialog';
82
82
  import * as i2$6 from 'primeng/datepicker';
@@ -2407,11 +2407,16 @@ function getNestedValue(obj, path) {
2407
2407
  * - Removes HTML tags
2408
2408
  * - Replaces   and non-breaking spaces
2409
2409
  * - Normalizes Unicode (keeps German umlauts and Serbian letters)
2410
+ * - Removes emoji characters (since PDF fonts don't support them properly)
2410
2411
  */
2411
2412
  function sanitizeText(v) {
2412
2413
  const s = (v ?? '').toString();
2413
2414
  const noHtml = s.replace(/<[^>]*>/g, '');
2414
- return noHtml
2415
+ // Remove emoji characters - they don't render properly in PDF
2416
+ // Emoji ranges: U+1F300–U+1F9FF, U+2600–U+26FF, U+2700–U+27BF, U+FE00–U+FE0F, U+1F900–U+1F9FF, U+1F1E0–U+1F1FF
2417
+ const emojiRegex = /[\u{1F300}-\u{1F9FF}\u{2600}-\u{26FF}\u{2700}-\u{27BF}\u{FE00}-\u{FE0F}\u{1F900}-\u{1F9FF}\u{1F1E0}-\u{1F1FF}]/gu;
2418
+ const noEmoji = noHtml.replace(emojiRegex, '');
2419
+ return noEmoji
2415
2420
  .replace(/&nbsp;/g, ' ')
2416
2421
  .replace(/\u00A0/g, ' ')
2417
2422
  .normalize('NFKC')
@@ -2495,17 +2500,46 @@ function collapseInlineSpaces(s) {
2495
2500
  .trim();
2496
2501
  }
2497
2502
  /**
2498
- * Exports table rows to a PDF file.
2499
- * - Uses jsPDF + autoTable
2503
+ * Loads Noto Sans font from CDN for better emoji support
2504
+ */
2505
+ async function loadNotoSansFont() {
2506
+ try {
2507
+ // Noto Sans has better Unicode and emoji support than Roboto
2508
+ // Try jsDelivr CDN first (more reliable)
2509
+ const response = await fetch('https://cdn.jsdelivr.net/gh/google/fonts@main/ofl/notosans/NotoSans-Regular.ttf');
2510
+ if (!response.ok) {
2511
+ return null;
2512
+ }
2513
+ const arrayBuffer = await response.arrayBuffer();
2514
+ const bytes = new Uint8Array(arrayBuffer);
2515
+ let binary = '';
2516
+ const chunkSize = 8192;
2517
+ for (let i = 0; i < bytes.length; i += chunkSize) {
2518
+ const chunk = bytes.subarray(i, i + chunkSize);
2519
+ binary += String.fromCharCode.apply(null, Array.from(chunk));
2520
+ }
2521
+ return btoa(binary);
2522
+ }
2523
+ catch (error) {
2524
+ console.warn('Failed to load Noto Sans font from CDN:', error);
2525
+ return null;
2526
+ }
2527
+ }
2528
+ /**
2529
+ * Exports table rows to a PDF file using pdfmake.
2530
+ * - Uses pdfmake which has excellent Unicode support for Serbian characters (ć, č, đ, š, ž)
2500
2531
  * - Respects only visible columns
2501
2532
  * - Translates headers with provided translation function
2533
+ * - Attempts to load Noto Sans font for better emoji support
2502
2534
  */
2503
- function exportRowsToPdf(columns, rows, columnTypeMap, columnTypeEnum, t, fileName = 'table.pdf', options = {}) {
2535
+ async function exportRowsToPdf(columns, rows, columnTypeMap, columnTypeEnum, t, fileName = 'table.pdf', options = {}) {
2504
2536
  if (!columns?.length)
2505
2537
  return;
2506
2538
  const locale = options.locale || 'en-US';
2507
- const head = [columns.map((c) => sanitizeText(t(c.header)))];
2508
- const body = (rows ?? []).map((row) => columns.map((col, idx) => {
2539
+ // Prepare headers
2540
+ const headers = columns.map((c) => sanitizeText(t(c.header)));
2541
+ // Prepare body rows
2542
+ const bodyRows = (rows ?? []).map((row) => columns.map((col) => {
2509
2543
  const type = columnTypeMap[col.field] || col.columnType;
2510
2544
  if (type === columnTypeEnum.LIST || type === columnTypeEnum.LIST_TAG) {
2511
2545
  // make each item its own line; keep items intact (no bullets)
@@ -2521,28 +2555,126 @@ function exportRowsToPdf(columns, rows, columnTypeMap, columnTypeEnum, t, fileNa
2521
2555
  const raw = String(getDisplayValue(row, col, columnTypeMap, columnTypeEnum, t, locale));
2522
2556
  return sanitizeText(collapseInlineSpaces(raw));
2523
2557
  }));
2524
- const doc = new jsPDF({ orientation: 'landscape', unit: 'pt', format: 'A4' });
2525
- // Build columnStyles dynamically: all LIST/LIST_TAG columns get min width
2526
- const columnStyles = {};
2527
- columns.forEach((col, idx) => {
2558
+ // Set up pdfmake fonts - pdfmake has built-in support for Unicode characters
2559
+ // pdfFonts contains the vfs object directly
2560
+ if (pdfFonts && pdfFonts.pdfMake) {
2561
+ pdfMake.vfs = pdfFonts.pdfMake.vfs;
2562
+ }
2563
+ else if (pdfFonts && pdfFonts.vfs) {
2564
+ pdfMake.vfs = pdfFonts.vfs;
2565
+ }
2566
+ else {
2567
+ // Fallback: try to access vfs directly
2568
+ pdfMake.vfs = pdfFonts || {};
2569
+ }
2570
+ // Try to load Noto Sans font which has better emoji support
2571
+ // This is optional - if it fails, we'll use Roboto
2572
+ try {
2573
+ const notoSansFont = await loadNotoSansFont();
2574
+ if (notoSansFont) {
2575
+ // Add Noto Sans to pdfmake fonts
2576
+ if (!pdfMake.fonts) {
2577
+ pdfMake.fonts = {};
2578
+ }
2579
+ pdfMake.fonts.NotoSans = {
2580
+ normal: 'NotoSans-Regular.ttf',
2581
+ bold: 'NotoSans-Bold.ttf',
2582
+ italics: 'NotoSans-Italic.ttf',
2583
+ bolditalics: 'NotoSans-BoldItalic.ttf',
2584
+ };
2585
+ // Add font file to vfs
2586
+ pdfMake.vfs['NotoSans-Regular.ttf'] = notoSansFont;
2587
+ pdfMake.vfs['NotoSans-Bold.ttf'] = notoSansFont; // Use same font for bold
2588
+ }
2589
+ }
2590
+ catch (error) {
2591
+ console.warn('Failed to load Noto Sans font, using default Roboto:', error);
2592
+ }
2593
+ // Use 'auto' widths for pdfmake - it will automatically fit columns to page width
2594
+ // pdfmake works well with Serbian characters and handles auto-sizing better
2595
+ const columnWidths = columns.map((col) => {
2528
2596
  const type = columnTypeMap[col.field] || col.columnType;
2529
2597
  if (type === columnTypeEnum.LIST || type === columnTypeEnum.LIST_TAG) {
2530
- columnStyles[idx] = { cellWidth: 150 }; // set your min width
2598
+ return 100; // Fixed width for LIST columns
2531
2599
  }
2600
+ return 'auto'; // Auto width for other columns - pdfmake will fit them
2532
2601
  });
2533
- autoTable(doc, {
2534
- head,
2535
- body,
2602
+ // Build table body for pdfmake
2603
+ const tableBody = [
2604
+ // Header row
2605
+ headers.map((header) => ({
2606
+ text: header,
2607
+ style: 'tableHeader',
2608
+ bold: true,
2609
+ })),
2610
+ // Data rows
2611
+ ...bodyRows.map((row) => row.map((cell, cellIdx) => {
2612
+ const col = columns[cellIdx];
2613
+ const type = columnTypeMap[col?.field] || col?.columnType;
2614
+ return {
2615
+ text: cell || '',
2616
+ style: 'tableCell',
2617
+ // Enable text wrapping for all cells
2618
+ noWrap: false,
2619
+ };
2620
+ })),
2621
+ ];
2622
+ // PDF document definition
2623
+ const docDefinition = {
2624
+ pageOrientation: 'landscape',
2625
+ pageSize: 'A4',
2626
+ content: [
2627
+ {
2628
+ table: {
2629
+ headerRows: 1,
2630
+ widths: columnWidths,
2631
+ body: tableBody,
2632
+ },
2633
+ layout: {
2634
+ hLineWidth: () => 0, // Remove horizontal lines between rows
2635
+ vLineWidth: () => 0, // Remove vertical lines between columns
2636
+ paddingLeft: () => 3,
2637
+ paddingRight: () => 3,
2638
+ paddingTop: () => 3,
2639
+ paddingBottom: () => 3,
2640
+ // Alternating row colors: grey, white, grey, white...
2641
+ fillColor: (rowIndex, node, columnIndex) => {
2642
+ // Header row stays blue RGB(41, 128, 186)
2643
+ if (rowIndex === 0) {
2644
+ return [41, 128, 186];
2645
+ }
2646
+ // Data rows alternate: grey for odd rows (1, 3, 5...), white for even rows (2, 4, 6...)
2647
+ return (rowIndex - 1) % 2 === 0 ? '#f5f5f5' : null; // grey for odd rows, white (null) for even rows
2648
+ },
2649
+ },
2650
+ },
2651
+ ],
2536
2652
  styles: {
2537
- fontSize: 9,
2538
- cellPadding: 5,
2539
- overflow: 'linebreak',
2540
- valign: 'top',
2653
+ tableHeader: {
2654
+ fontSize: 11,
2655
+ bold: true,
2656
+ color: '#ffffff',
2657
+ fillColor: [41, 128, 186], // RGB(41, 128, 186)
2658
+ alignment: 'left',
2659
+ margin: [0, 5, 0, 5],
2660
+ },
2661
+ tableCell: {
2662
+ fontSize: 9,
2663
+ alignment: 'left',
2664
+ margin: [0, 5, 0, 5],
2665
+ // Enable text wrapping
2666
+ noWrap: false,
2667
+ },
2541
2668
  },
2542
- columnStyles: Object.keys(columnStyles).length > 0 ? columnStyles : undefined,
2543
- margin: { top: 36, right: 24, bottom: 36, left: 24 },
2544
- });
2545
- doc.save(fileName);
2669
+ defaultStyle: {
2670
+ // Use NotoSans if loaded, otherwise fallback to Roboto
2671
+ font: pdfMake.fonts?.NotoSans ? 'NotoSans' : 'Roboto',
2672
+ // NotoSans has better Unicode and emoji support than Roboto
2673
+ },
2674
+ pageMargins: [15, 30, 15, 30], // Reduced margins to give more space
2675
+ };
2676
+ // Generate and download PDF
2677
+ pdfMake.createPdf(docDefinition).download(fileName);
2546
2678
  }
2547
2679
  /* -------------------------- Export: Excel -------------------------- */
2548
2680
  /**
@@ -2750,10 +2882,10 @@ class TableComponent {
2750
2882
  initialSortFieldApplied = null;
2751
2883
  /** True while initial sort is running to avoid "flash" of unsorted data */
2752
2884
  initialSortLoading = false;
2753
- /** How many skeleton rows to render while initial sort/data is loading */
2754
- skeletonRowCount = 8;
2755
- /** Array used by Angular @for (must be iterable) */
2756
- skeletonRows = Array.from({ length: this.skeletonRowCount });
2885
+ // /** How many skeleton rows to render while initial sort/data is loading */
2886
+ // skeletonRowCount = 8;
2887
+ // /** Array used by Angular @for (must be iterable) */
2888
+ // skeletonRows = Array.from({ length: this.skeletonRowCount });
2757
2889
  createSortWorkerInline() {
2758
2890
  const code = `
2759
2891
  const compareStringsNatural = (a,b) => a.localeCompare(b, undefined, { numeric:true, sensitivity:'base' });
@@ -3269,7 +3401,9 @@ class TableComponent {
3269
3401
  const file = this.tableConfiguration?.key
3270
3402
  ? buildFileName(this.tableConfiguration.key, 'pdf')
3271
3403
  : buildFileName('table', 'pdf');
3272
- exportRowsToPdf(this.selectedColumns, this.tableData, this.columnTypeMap, this.columnTypeEnum, (k) => this.translateService.instant(k), file);
3404
+ exportRowsToPdf(this.selectedColumns, this.tableData, this.columnTypeMap, this.columnTypeEnum, (k) => this.translateService.instant(k), file).catch((error) => {
3405
+ console.error('Failed to export PDF:', error);
3406
+ });
3273
3407
  return;
3274
3408
  }
3275
3409
  if (event?.action?.key === 'EXPORT_EXCEL') {