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

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,20 +2500,52 @@ 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
+ /* =========================
2540
+ HEADERS
2541
+ ========================= */
2542
+ const headers = columns.map((c) => sanitizeText(t(c.header)));
2543
+ /* =========================
2544
+ BODY ROWS
2545
+ ========================= */
2546
+ const bodyRows = (rows ?? []).map((row) => columns.map((col) => {
2509
2547
  const type = columnTypeMap[col.field] || col.columnType;
2510
2548
  if (type === columnTypeEnum.LIST || type === columnTypeEnum.LIST_TAG) {
2511
- // make each item its own line; keep items intact (no bullets)
2512
2549
  const rawVal = getNestedValue(row, col.field);
2513
2550
  const items = Array.isArray(rawVal)
2514
2551
  ? rawVal.map((x) => x?.name ?? x)
@@ -2516,33 +2553,148 @@ function exportRowsToPdf(columns, rows, columnTypeMap, columnTypeEnum, t, fileNa
2516
2553
  const cleaned = items
2517
2554
  .map((it) => collapseInlineSpaces(String(it)))
2518
2555
  .filter(Boolean);
2519
- return sanitizeText(cleaned.join(',\n')); // hard break after commas
2556
+ return sanitizeText(cleaned.join(',\n'));
2520
2557
  }
2521
2558
  const raw = String(getDisplayValue(row, col, columnTypeMap, columnTypeEnum, t, locale));
2522
2559
  return sanitizeText(collapseInlineSpaces(raw));
2523
2560
  }));
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) => {
2561
+ /* =========================
2562
+ FONTS
2563
+ ========================= */
2564
+ if (pdfFonts && pdfFonts.pdfMake) {
2565
+ pdfMake.vfs = pdfFonts.pdfMake.vfs;
2566
+ }
2567
+ else if (pdfFonts && pdfFonts.vfs) {
2568
+ pdfMake.vfs = pdfFonts.vfs;
2569
+ }
2570
+ else {
2571
+ pdfMake.vfs = pdfFonts || {};
2572
+ }
2573
+ try {
2574
+ const notoSansFont = await loadNotoSansFont();
2575
+ if (notoSansFont) {
2576
+ pdfMake.fonts ??= {};
2577
+ pdfMake.fonts.NotoSans = {
2578
+ normal: 'NotoSans-Regular.ttf',
2579
+ bold: 'NotoSans-Bold.ttf',
2580
+ italics: 'NotoSans-Italic.ttf',
2581
+ bolditalics: 'NotoSans-BoldItalic.ttf',
2582
+ };
2583
+ pdfMake.vfs['NotoSans-Regular.ttf'] = notoSansFont;
2584
+ pdfMake.vfs['NotoSans-Bold.ttf'] = notoSansFont;
2585
+ }
2586
+ }
2587
+ catch {
2588
+ console.warn('NotoSans not loaded, fallback to Roboto');
2589
+ }
2590
+ /* =========================
2591
+ FIXED TABLE WIDTH LOGIC
2592
+ ========================= */
2593
+ const fixedTableWidth = 750;
2594
+ const listColumnWidth = 80;
2595
+ const minColumnWidth = 40;
2596
+ const listIndexes = [];
2597
+ const regularIndexes = [];
2598
+ columns.forEach((col, i) => {
2528
2599
  const type = columnTypeMap[col.field] || col.columnType;
2529
2600
  if (type === columnTypeEnum.LIST || type === columnTypeEnum.LIST_TAG) {
2530
- columnStyles[idx] = { cellWidth: 150 }; // set your min width
2601
+ listIndexes.push(i);
2602
+ }
2603
+ else {
2604
+ regularIndexes.push(i);
2531
2605
  }
2532
2606
  });
2533
- autoTable(doc, {
2534
- head,
2535
- body,
2607
+ const reservedWidth = listIndexes.length * listColumnWidth;
2608
+ const availableWidth = fixedTableWidth - reservedWidth;
2609
+ const baseRegularWidth = regularIndexes.length > 0
2610
+ ? Math.max(minColumnWidth, availableWidth / regularIndexes.length)
2611
+ : minColumnWidth;
2612
+ let columnWidths = columns.map((_, i) => listIndexes.includes(i) ? listColumnWidth : baseRegularWidth);
2613
+ /* === NORMALIZACIJA (TAČNO 750px) === */
2614
+ const regularSum = regularIndexes.reduce((sum, i) => sum + columnWidths[i], 0);
2615
+ if (regularSum > 0) {
2616
+ const scaleFactor = availableWidth / regularSum;
2617
+ console.log(scaleFactor);
2618
+ // 1. skaliranje + floor
2619
+ regularIndexes.forEach((i) => {
2620
+ columnWidths[i] = Math.floor(columnWidths[i] * scaleFactor);
2621
+ });
2622
+ // 2. korekcija razlike (1px po koloni)
2623
+ let diff = fixedTableWidth - columnWidths.reduce((sum, w) => sum + w, 0);
2624
+ let idx = 0;
2625
+ while (diff !== 0 && regularIndexes.length) {
2626
+ const i = regularIndexes[idx % regularIndexes.length];
2627
+ columnWidths[i] += diff > 0 ? 1 : -1;
2628
+ diff += diff > 0 ? -1 : 1;
2629
+ idx++;
2630
+ }
2631
+ }
2632
+ /* =========================
2633
+ TABLE BODY
2634
+ ========================= */
2635
+ const tableBody = [
2636
+ headers.map((header) => ({
2637
+ text: header,
2638
+ style: 'tableHeader',
2639
+ })),
2640
+ ...bodyRows.map((row) => row.map((cell) => ({
2641
+ text: cell || '',
2642
+ style: 'tableCell',
2643
+ noWrap: false,
2644
+ }))),
2645
+ ];
2646
+ console.log(columnWidths);
2647
+ /* =========================
2648
+ PDF DEFINITION
2649
+ ========================= */
2650
+ const docDefinition = {
2651
+ pageOrientation: 'landscape',
2652
+ pageSize: 'A4',
2653
+ pageMargins: [25, 30, 15, 30],
2654
+ content: [
2655
+ {
2656
+ columns: [
2657
+ {
2658
+ width: fixedTableWidth,
2659
+ table: {
2660
+ headerRows: 1,
2661
+ widths: columnWidths,
2662
+ body: tableBody,
2663
+ },
2664
+ layout: {
2665
+ hLineWidth: () => 0,
2666
+ vLineWidth: () => 0,
2667
+ paddingLeft: () => 3,
2668
+ paddingRight: () => 3,
2669
+ paddingTop: () => 3,
2670
+ paddingBottom: () => 3,
2671
+ fillColor: (rowIndex) => rowIndex === 0
2672
+ ? [41, 128, 186]
2673
+ : (rowIndex - 1) % 2 === 0
2674
+ ? '#f5f5f5'
2675
+ : null,
2676
+ },
2677
+ },
2678
+ ],
2679
+ },
2680
+ ],
2536
2681
  styles: {
2537
- fontSize: 9,
2538
- cellPadding: 5,
2539
- overflow: 'linebreak',
2540
- valign: 'top',
2682
+ tableHeader: {
2683
+ fontSize: 11,
2684
+ bold: true,
2685
+ color: '#ffffff',
2686
+ margin: [0, 5, 0, 5],
2687
+ },
2688
+ tableCell: {
2689
+ fontSize: 9,
2690
+ margin: [0, 5, 0, 5],
2691
+ },
2541
2692
  },
2542
- columnStyles: Object.keys(columnStyles).length > 0 ? columnStyles : undefined,
2543
- margin: { top: 36, right: 24, bottom: 36, left: 24 },
2544
- });
2545
- doc.save(fileName);
2693
+ defaultStyle: {
2694
+ font: pdfMake.fonts?.NotoSans ? 'NotoSans' : 'Roboto',
2695
+ },
2696
+ };
2697
+ pdfMake.createPdf(docDefinition).download(fileName);
2546
2698
  }
2547
2699
  /* -------------------------- Export: Excel -------------------------- */
2548
2700
  /**
@@ -2750,10 +2902,10 @@ class TableComponent {
2750
2902
  initialSortFieldApplied = null;
2751
2903
  /** True while initial sort is running to avoid "flash" of unsorted data */
2752
2904
  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 });
2905
+ // /** How many skeleton rows to render while initial sort/data is loading */
2906
+ // skeletonRowCount = 8;
2907
+ // /** Array used by Angular @for (must be iterable) */
2908
+ // skeletonRows = Array.from({ length: this.skeletonRowCount });
2757
2909
  createSortWorkerInline() {
2758
2910
  const code = `
2759
2911
  const compareStringsNatural = (a,b) => a.localeCompare(b, undefined, { numeric:true, sensitivity:'base' });
@@ -3269,7 +3421,9 @@ class TableComponent {
3269
3421
  const file = this.tableConfiguration?.key
3270
3422
  ? buildFileName(this.tableConfiguration.key, 'pdf')
3271
3423
  : buildFileName('table', 'pdf');
3272
- exportRowsToPdf(this.selectedColumns, this.tableData, this.columnTypeMap, this.columnTypeEnum, (k) => this.translateService.instant(k), file);
3424
+ exportRowsToPdf(this.selectedColumns, this.tableData, this.columnTypeMap, this.columnTypeEnum, (k) => this.translateService.instant(k), file).catch((error) => {
3425
+ console.error('Failed to export PDF:', error);
3426
+ });
3273
3427
  return;
3274
3428
  }
3275
3429
  if (event?.action?.key === 'EXPORT_EXCEL') {