@caribeng/ui 22.4.0 → 22.5.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.
Files changed (186) hide show
  1. package/README.md +311 -313
  2. package/fesm2022/caribeng-ui-account-picker.mjs +533 -0
  3. package/fesm2022/caribeng-ui-aging.mjs +488 -0
  4. package/fesm2022/{caribeng-ui-caribe-alert.mjs → caribeng-ui-alert.mjs} +45 -45
  5. package/fesm2022/caribeng-ui-amount.mjs +249 -0
  6. package/fesm2022/caribeng-ui-anchor.mjs +2 -2
  7. package/fesm2022/{caribeng-ui-caribe-animation-lottie.mjs → caribeng-ui-animation-lottie.mjs} +41 -41
  8. package/fesm2022/caribeng-ui-audit-log.mjs +323 -0
  9. package/fesm2022/caribeng-ui-avatar.mjs +2 -2
  10. package/fesm2022/caribeng-ui-breadcrumb.mjs +13 -3
  11. package/fesm2022/{caribeng-ui-caribe-button-group.mjs → caribeng-ui-button-group.mjs} +15 -15
  12. package/fesm2022/caribeng-ui-button.mjs +2 -2
  13. package/fesm2022/{caribeng-ui-caribe-card-content.mjs → caribeng-ui-card-content.mjs} +39 -39
  14. package/fesm2022/{caribeng-ui-caribe-card-move.mjs → caribeng-ui-card-move.mjs} +65 -65
  15. package/fesm2022/{caribeng-ui-caribe-card-primary.mjs → caribeng-ui-card-primary.mjs} +31 -31
  16. package/fesm2022/{caribeng-ui-caribe-card-select.mjs → caribeng-ui-card-select.mjs} +67 -67
  17. package/fesm2022/caribeng-ui-carousel.mjs +2 -2
  18. package/fesm2022/caribeng-ui-cascadeselect.mjs +45 -9
  19. package/fesm2022/caribeng-ui-chat.mjs +2 -2
  20. package/fesm2022/{caribeng-ui-caribe-coachmark.mjs → caribeng-ui-coachmark.mjs} +85 -85
  21. package/fesm2022/caribeng-ui-command-palette.mjs +2 -2
  22. package/fesm2022/caribeng-ui-comments.mjs +864 -0
  23. package/fesm2022/caribeng-ui-datagrid.mjs +1635 -0
  24. package/fesm2022/caribeng-ui-dataview.mjs +2 -2
  25. package/fesm2022/caribeng-ui-datepicker.mjs +2 -2
  26. package/fesm2022/caribeng-ui-document.mjs +495 -0
  27. package/fesm2022/{caribeng-ui-caribe-dots-indicator.mjs → caribeng-ui-dots-indicator.mjs} +41 -41
  28. package/fesm2022/caribeng-ui-dynamic-form.mjs +1581 -0
  29. package/fesm2022/caribeng-ui-editor.mjs +2303 -1311
  30. package/fesm2022/{caribeng-ui-caribe-expansion-panel.mjs → caribeng-ui-expansion-panel.mjs} +39 -39
  31. package/fesm2022/{caribeng-ui-caribe-fab-menu.mjs → caribeng-ui-fab-menu.mjs} +75 -75
  32. package/fesm2022/caribeng-ui-file-card.mjs +2 -2
  33. package/fesm2022/{caribeng-ui-caribe-file-download.mjs → caribeng-ui-file-download.mjs} +63 -63
  34. package/fesm2022/caribeng-ui-financial-report.mjs +380 -0
  35. package/fesm2022/{caribeng-ui-caribe-float-menu.mjs → caribeng-ui-float-menu.mjs} +31 -31
  36. package/fesm2022/caribeng-ui-floatlabel.mjs +2 -2
  37. package/fesm2022/caribeng-ui-flow.mjs +1508 -0
  38. package/fesm2022/{caribeng-ui-caribe-footer.mjs → caribeng-ui-footer.mjs} +19 -19
  39. package/fesm2022/caribeng-ui-gantt.mjs +1094 -0
  40. package/fesm2022/{caribeng-ui-caribe-header.mjs → caribeng-ui-header.mjs} +49 -49
  41. package/fesm2022/{caribeng-ui-caribe-icon-button.mjs → caribeng-ui-icon-button.mjs} +23 -23
  42. package/fesm2022/caribeng-ui-image-cropper.mjs +671 -0
  43. package/fesm2022/caribeng-ui-import-wizard.mjs +746 -0
  44. package/fesm2022/caribeng-ui-infinite-scroll.mjs +226 -0
  45. package/fesm2022/caribeng-ui-input-number.mjs +22 -6
  46. package/fesm2022/{caribeng-ui-caribe-input-search.mjs → caribeng-ui-input-search.mjs} +65 -65
  47. package/fesm2022/{caribeng-ui-caribe-input-token.mjs → caribeng-ui-input-token.mjs} +59 -59
  48. package/fesm2022/caribeng-ui-inputotp.mjs +2 -2
  49. package/fesm2022/caribeng-ui-journal-entry.mjs +340 -0
  50. package/fesm2022/caribeng-ui-kanban.mjs +6 -6
  51. package/fesm2022/caribeng-ui-kpi.mjs +189 -0
  52. package/fesm2022/{caribeng-ui-caribe-link.mjs → caribeng-ui-link.mjs} +37 -37
  53. package/fesm2022/caribeng-ui-masonry.mjs +93 -0
  54. package/fesm2022/caribeng-ui-media-player.mjs +821 -0
  55. package/fesm2022/caribeng-ui-mentions.mjs +2 -2
  56. package/fesm2022/caribeng-ui-notification-center.mjs +636 -0
  57. package/fesm2022/{caribeng-ui-caribe-off-canvas.mjs → caribeng-ui-off-canvas.mjs} +55 -55
  58. package/fesm2022/{caribeng-ui-caribe-onboarding.mjs → caribeng-ui-onboarding.mjs} +105 -105
  59. package/fesm2022/caribeng-ui-orderlist.mjs +2 -2
  60. package/fesm2022/caribeng-ui-pdfviewer.mjs +1755 -25
  61. package/fesm2022/caribeng-ui-period-picker.mjs +637 -0
  62. package/fesm2022/caribeng-ui-phone-input.mjs +2 -2
  63. package/fesm2022/caribeng-ui-picklist.mjs +2 -2
  64. package/fesm2022/caribeng-ui-pivot.mjs +419 -0
  65. package/fesm2022/caribeng-ui-pricing.mjs +2 -2
  66. package/fesm2022/caribeng-ui-rating.mjs +14 -17
  67. package/fesm2022/caribeng-ui-reconciliation.mjs +386 -0
  68. package/fesm2022/caribeng-ui-scheduler.mjs +1027 -0
  69. package/fesm2022/caribeng-ui-select.mjs +2 -2
  70. package/fesm2022/{caribeng-ui-caribe-shortcut.mjs → caribeng-ui-shortcut.mjs} +29 -29
  71. package/fesm2022/caribeng-ui-sidebar.mjs +2 -2
  72. package/fesm2022/caribeng-ui-signature-pad.mjs +654 -0
  73. package/fesm2022/{caribeng-ui-caribe-status.mjs → caribeng-ui-status.mjs} +11 -11
  74. package/fesm2022/{caribeng-ui-caribe-step-indicator.mjs → caribeng-ui-step-indicator.mjs} +53 -53
  75. package/fesm2022/caribeng-ui-stepper.mjs +2 -2
  76. package/fesm2022/caribeng-ui-steps.mjs +2 -2
  77. package/fesm2022/caribeng-ui-table.mjs +323 -15
  78. package/fesm2022/caribeng-ui-tabmenu.mjs +2 -2
  79. package/fesm2022/caribeng-ui-tabs.mjs +2 -2
  80. package/fesm2022/{caribeng-ui-caribe-tag-button.mjs → caribeng-ui-tag-button.mjs} +25 -25
  81. package/fesm2022/caribeng-ui-tag.mjs +2 -2
  82. package/fesm2022/caribeng-ui-tieredmenu.mjs +6 -6
  83. package/fesm2022/{caribeng-ui-caribe-tracker.mjs → caribeng-ui-tracker.mjs} +29 -29
  84. package/fesm2022/{caribeng-ui-caribe-transaction-status.mjs → caribeng-ui-transaction-status.mjs} +11 -11
  85. package/fesm2022/caribeng-ui-treeselect.mjs +44 -9
  86. package/fesm2022/caribeng-ui-treetable.mjs +236 -17
  87. package/fesm2022/{caribeng-ui-caribe-voucher.mjs → caribeng-ui-voucher.mjs} +45 -45
  88. package/fesm2022/caribeng-ui-xlsx.mjs +486 -0
  89. package/fesm2022/caribeng-ui.mjs +1181 -1
  90. package/package.json +217 -177
  91. package/types/caribeng-ui-account-picker.d.ts +142 -0
  92. package/types/caribeng-ui-aging.d.ts +123 -0
  93. package/types/{caribeng-ui-caribe-alert.d.ts → caribeng-ui-alert.d.ts} +2 -2
  94. package/types/caribeng-ui-amount.d.ts +82 -0
  95. package/types/{caribeng-ui-caribe-animation-lottie.d.ts → caribeng-ui-animation-lottie.d.ts} +2 -2
  96. package/types/caribeng-ui-audit-log.d.ts +86 -0
  97. package/types/caribeng-ui-breadcrumb.d.ts +2 -2
  98. package/types/{caribeng-ui-caribe-button-group.d.ts → caribeng-ui-button-group.d.ts} +2 -2
  99. package/types/{caribeng-ui-caribe-card-content.d.ts → caribeng-ui-card-content.d.ts} +2 -2
  100. package/types/{caribeng-ui-caribe-card-move.d.ts → caribeng-ui-card-move.d.ts} +1 -1
  101. package/types/{caribeng-ui-caribe-card-primary.d.ts → caribeng-ui-card-primary.d.ts} +2 -2
  102. package/types/{caribeng-ui-caribe-card-select.d.ts → caribeng-ui-card-select.d.ts} +2 -2
  103. package/types/caribeng-ui-cascadeselect.d.ts +6 -0
  104. package/types/{caribeng-ui-caribe-coachmark.d.ts → caribeng-ui-coachmark.d.ts} +2 -2
  105. package/types/caribeng-ui-comments.d.ts +214 -0
  106. package/types/caribeng-ui-datagrid.d.ts +269 -0
  107. package/types/caribeng-ui-document.d.ts +138 -0
  108. package/types/{caribeng-ui-caribe-dots-indicator.d.ts → caribeng-ui-dots-indicator.d.ts} +2 -2
  109. package/types/caribeng-ui-dynamic-form.d.ts +409 -0
  110. package/types/caribeng-ui-editor.d.ts +110 -12
  111. package/types/{caribeng-ui-caribe-expansion-panel.d.ts → caribeng-ui-expansion-panel.d.ts} +1 -1
  112. package/types/{caribeng-ui-caribe-fab-menu.d.ts → caribeng-ui-fab-menu.d.ts} +2 -2
  113. package/types/{caribeng-ui-caribe-file-download.d.ts → caribeng-ui-file-download.d.ts} +1 -1
  114. package/types/caribeng-ui-financial-report.d.ts +102 -0
  115. package/types/{caribeng-ui-caribe-float-menu.d.ts → caribeng-ui-float-menu.d.ts} +2 -2
  116. package/types/caribeng-ui-flow.d.ts +228 -0
  117. package/types/{caribeng-ui-caribe-footer.d.ts → caribeng-ui-footer.d.ts} +2 -2
  118. package/types/caribeng-ui-gantt.d.ts +244 -0
  119. package/types/{caribeng-ui-caribe-header.d.ts → caribeng-ui-header.d.ts} +2 -2
  120. package/types/{caribeng-ui-caribe-icon-button.d.ts → caribeng-ui-icon-button.d.ts} +2 -2
  121. package/types/{caribeng-ui-caribe-illustration.d.ts → caribeng-ui-illustration.d.ts} +1 -1
  122. package/types/caribeng-ui-image-cropper.d.ts +169 -0
  123. package/types/caribeng-ui-import-wizard.d.ts +149 -0
  124. package/types/caribeng-ui-infinite-scroll.d.ts +75 -0
  125. package/types/caribeng-ui-input-number.d.ts +1 -1
  126. package/types/{caribeng-ui-caribe-input-search.d.ts → caribeng-ui-input-search.d.ts} +2 -2
  127. package/types/{caribeng-ui-caribe-input-token.d.ts → caribeng-ui-input-token.d.ts} +2 -2
  128. package/types/caribeng-ui-journal-entry.d.ts +110 -0
  129. package/types/caribeng-ui-kpi.d.ts +64 -0
  130. package/types/{caribeng-ui-caribe-link.d.ts → caribeng-ui-link.d.ts} +2 -2
  131. package/types/{caribeng-ui-caribe-logo.d.ts → caribeng-ui-logo.d.ts} +1 -1
  132. package/types/caribeng-ui-masonry.d.ts +32 -0
  133. package/types/caribeng-ui-media-player.d.ts +140 -0
  134. package/types/caribeng-ui-notification-center.d.ts +153 -0
  135. package/types/{caribeng-ui-caribe-off-canvas.d.ts → caribeng-ui-off-canvas.d.ts} +2 -2
  136. package/types/{caribeng-ui-caribe-onboarding.d.ts → caribeng-ui-onboarding.d.ts} +2 -2
  137. package/types/caribeng-ui-pdfviewer.d.ts +245 -5
  138. package/types/caribeng-ui-period-picker.d.ts +148 -0
  139. package/types/caribeng-ui-pivot.d.ts +116 -0
  140. package/types/caribeng-ui-reconciliation.d.ts +109 -0
  141. package/types/caribeng-ui-scheduler.d.ts +188 -0
  142. package/types/{caribeng-ui-caribe-shortcut.d.ts → caribeng-ui-shortcut.d.ts} +2 -2
  143. package/types/caribeng-ui-signature-pad.d.ts +151 -0
  144. package/types/{caribeng-ui-caribe-status.d.ts → caribeng-ui-status.d.ts} +2 -2
  145. package/types/{caribeng-ui-caribe-step-indicator.d.ts → caribeng-ui-step-indicator.d.ts} +2 -2
  146. package/types/caribeng-ui-table.d.ts +66 -2
  147. package/types/{caribeng-ui-caribe-tag-button.d.ts → caribeng-ui-tag-button.d.ts} +2 -2
  148. package/types/{caribeng-ui-caribe-tracker.d.ts → caribeng-ui-tracker.d.ts} +2 -2
  149. package/types/{caribeng-ui-caribe-transaction-status.d.ts → caribeng-ui-transaction-status.d.ts} +2 -2
  150. package/types/caribeng-ui-treeselect.d.ts +6 -0
  151. package/types/caribeng-ui-treetable.d.ts +67 -2
  152. package/types/{caribeng-ui-caribe-voucher.d.ts → caribeng-ui-voucher.d.ts} +2 -2
  153. package/types/caribeng-ui-xlsx.d.ts +79 -0
  154. package/types/caribeng-ui.d.ts +711 -2
  155. package/fesm2022/caribeng-ui-caribe-calendar.mjs +0 -92
  156. package/fesm2022/caribeng-ui-caribe-circle-loading.mjs +0 -130
  157. package/fesm2022/caribeng-ui-caribe-data-table.mjs +0 -94
  158. package/fesm2022/caribeng-ui-caribe-file-select.mjs +0 -88
  159. package/fesm2022/caribeng-ui-caribe-inline-alert.mjs +0 -100
  160. package/fesm2022/caribeng-ui-caribe-input-date.mjs +0 -138
  161. package/fesm2022/caribeng-ui-caribe-input-select.mjs +0 -154
  162. package/fesm2022/caribeng-ui-caribe-loader.mjs +0 -48
  163. package/fesm2022/caribeng-ui-caribe-modal.mjs +0 -119
  164. package/fesm2022/caribeng-ui-caribe-pictogram.mjs +0 -52
  165. package/fesm2022/caribeng-ui-caribe-pop-over.mjs +0 -104
  166. package/fesm2022/caribeng-ui-caribe-preloader.mjs +0 -57
  167. package/fesm2022/caribeng-ui-caribe-progress-bar.mjs +0 -84
  168. package/fesm2022/caribeng-ui-caribe-stroke.mjs +0 -69
  169. package/fesm2022/caribeng-ui-caribe-switch.mjs +0 -182
  170. package/types/caribeng-ui-caribe-calendar.d.ts +0 -27
  171. package/types/caribeng-ui-caribe-circle-loading.d.ts +0 -28
  172. package/types/caribeng-ui-caribe-data-table.d.ts +0 -26
  173. package/types/caribeng-ui-caribe-file-select.d.ts +0 -31
  174. package/types/caribeng-ui-caribe-inline-alert.d.ts +0 -28
  175. package/types/caribeng-ui-caribe-input-date.d.ts +0 -40
  176. package/types/caribeng-ui-caribe-input-select.d.ts +0 -47
  177. package/types/caribeng-ui-caribe-loader.d.ts +0 -22
  178. package/types/caribeng-ui-caribe-modal.d.ts +0 -21
  179. package/types/caribeng-ui-caribe-pictogram.d.ts +0 -15
  180. package/types/caribeng-ui-caribe-pop-over.d.ts +0 -17
  181. package/types/caribeng-ui-caribe-preloader.d.ts +0 -24
  182. package/types/caribeng-ui-caribe-progress-bar.d.ts +0 -28
  183. package/types/caribeng-ui-caribe-stroke.d.ts +0 -17
  184. package/types/caribeng-ui-caribe-switch.d.ts +0 -55
  185. /package/fesm2022/{caribeng-ui-caribe-illustration.mjs → caribeng-ui-illustration.mjs} +0 -0
  186. /package/fesm2022/{caribeng-ui-caribe-logo.mjs → caribeng-ui-logo.mjs} +0 -0
@@ -0,0 +1,654 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, Injector, DestroyRef, model, input, booleanAttribute, numberAttribute, output, signal, computed, viewChild, ElementRef, afterNextRender, untracked, effect, forwardRef, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
3
+ import { NG_VALUE_ACCESSOR } from '@angular/forms';
4
+ import { injectCbTranslation, cbFieldState } from '@caribeng/ui';
5
+ import { CbIconRegistry, CbIcon } from '@caribeng/ui/icon';
6
+
7
+ /** Tipografías manuscritas del sistema (sin descargar fuentes) para el modo escrito. */
8
+ const CB_SIGNATURE_FONT = "'Segoe Script', 'Bradley Hand', 'Brush Script MT', 'Snell Roundhand', 'URW Chancery L', cursive";
9
+ const r = (n) => Math.round(n * 100) / 100;
10
+ /**
11
+ * Convierte un trazo en segmentos de curva (cuadráticas por los puntos medios) con grosor
12
+ * variable: más fino cuanto más rápido se mueve el trazo y más grueso con más presión.
13
+ * Un toque sin movimiento es un punto.
14
+ */
15
+ function strokeSegments(stroke, minWidth, maxWidth) {
16
+ if (!stroke.length)
17
+ return [];
18
+ const widthAt = (speed, pressure) => {
19
+ const bySpeed = maxWidth - (maxWidth - minWidth) * Math.min(1, speed / 2.5);
20
+ // Ratón y dedo informan 0.5 (o 0): no alteran el grosor.
21
+ const byPressure = pressure > 0 && pressure !== 0.5 ? 0.6 + pressure * 0.8 : 1;
22
+ return Math.max(minWidth * 0.5, bySpeed * byPressure);
23
+ };
24
+ if (stroke.length === 1) {
25
+ const p = stroke[0];
26
+ return [{ d: `M${r(p.x)} ${r(p.y)}l0 0.01`, width: r(maxWidth * 1.1) }];
27
+ }
28
+ const segments = [];
29
+ let width = widthAt(0, stroke[0].p);
30
+ const mid = (a, b) => ({ x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 });
31
+ for (let i = 1; i < stroke.length; i++) {
32
+ const a = stroke[i - 1];
33
+ const b = stroke[i];
34
+ const dist = Math.hypot(b.x - a.x, b.y - a.y);
35
+ const speed = dist / Math.max(1, b.t - a.t);
36
+ // Suavizado del grosor para que no dé saltos entre segmentos.
37
+ width = width * 0.65 + widthAt(speed, b.p) * 0.35;
38
+ const from = i === 1 ? a : mid(stroke[i - 2], a);
39
+ const to = i === stroke.length - 1 ? b : mid(a, b);
40
+ const d = i === 1 && stroke.length > 2
41
+ ? `M${r(from.x)} ${r(from.y)}L${r(to.x)} ${r(to.y)}`
42
+ : `M${r(from.x)} ${r(from.y)}Q${r(a.x)} ${r(a.y)} ${r(to.x)} ${r(to.y)}`;
43
+ segments.push({ d, width: r(width) });
44
+ }
45
+ return segments;
46
+ }
47
+ /** Tamaño de letra estimado del nombre escrito (el componente lo ajusta midiendo el texto pintado). */
48
+ function typedFontSize(text, width, height) {
49
+ const chars = Math.max(1, text.trim().length);
50
+ return Math.round(Math.min(height * 0.42, (width * 0.85) / (chars * 0.55)));
51
+ }
52
+ function escapeXml(text) {
53
+ return text.replace(/[<>&'"]/g, (c) => ({ '<': '&lt;', '>': '&gt;', '&': '&amp;', "'": '&apos;', '"': '&quot;' })[c]);
54
+ }
55
+ /** ¿Hay algo firmado? */
56
+ function isSignatureEmpty(data) {
57
+ return data.text !== undefined ? !data.text.trim() : !data.strokes.length;
58
+ }
59
+ /**
60
+ * La firma como documento SVG independiente (sin dependencias de la página). `background`
61
+ * vacío o `transparent` lo deja sin fondo; `image` es una firma previa que va debajo.
62
+ */
63
+ function signatureToSvg(data, options) {
64
+ const { width: w, height: h } = data;
65
+ const parts = [];
66
+ if (options.background && options.background !== 'transparent') {
67
+ parts.push(`<rect width="${w}" height="${h}" fill="${escapeXml(options.background)}"/>`);
68
+ }
69
+ if (options.image)
70
+ parts.push(`<image href="${escapeXml(options.image)}" width="${w}" height="${h}" preserveAspectRatio="xMidYMid meet"/>`);
71
+ if (data.text !== undefined) {
72
+ if (data.text.trim()) {
73
+ const size = options.fontSize ?? typedFontSize(data.text, w, h);
74
+ parts.push(`<text x="${w / 2}" y="${r(h * 0.62)}" text-anchor="middle" font-family="${escapeXml(options.font ?? CB_SIGNATURE_FONT)}" font-size="${size}" fill="${escapeXml(data.color)}">${escapeXml(data.text.trim())}</text>`);
75
+ }
76
+ }
77
+ else {
78
+ const paths = data.strokes
79
+ .flatMap((s) => strokeSegments(s, options.minWidth, options.maxWidth))
80
+ .map((seg) => `<path d="${seg.d}" stroke-width="${seg.width}"/>`);
81
+ if (paths.length) {
82
+ parts.push(`<g fill="none" stroke="${escapeXml(data.color)}" stroke-linecap="round" stroke-linejoin="round">${paths.join('')}</g>`);
83
+ }
84
+ }
85
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">${parts.join('')}</svg>`;
86
+ }
87
+ /** Data URL de un SVG. */
88
+ function svgDataUrl(svg) {
89
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
90
+ }
91
+ /** Pinta el SVG en un canvas y lo devuelve como PNG o JPEG (a `scale` × el tamaño, para que no pixele). */
92
+ async function rasterize(svg, width, height, type, scale = 2) {
93
+ const img = new Image();
94
+ img.src = svgDataUrl(svg);
95
+ await img.decode();
96
+ const canvas = document.createElement('canvas');
97
+ canvas.width = Math.round(width * scale);
98
+ canvas.height = Math.round(height * scale);
99
+ const ctx = canvas.getContext('2d');
100
+ if (!ctx)
101
+ throw new Error('canvas 2d no disponible');
102
+ if (type === 'jpeg') {
103
+ // JPEG no tiene transparencia: fondo blanco si la firma no trae uno.
104
+ ctx.fillStyle = '#ffffff';
105
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
106
+ }
107
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
108
+ return canvas.toDataURL(`image/${type}`, 0.92);
109
+ }
110
+
111
+ let nextUid = 0;
112
+ /**
113
+ * Firma digital de CaribeNG: área para firmar con ratón, dedo o lápiz (grosor según velocidad y
114
+ * presión) o, como alternativa accesible, escribiendo el nombre en letra manuscrita. Devuelve la
115
+ * firma como PNG, JPEG o SVG (`[(value)]`, también con Angular Forms) y permite deshacer y borrar.
116
+ *
117
+ * @example
118
+ * ```html
119
+ * <cb-signature-pad [(value)]="firma" format="png" />
120
+ * ```
121
+ */
122
+ class CbSignaturePad {
123
+ constructor() {
124
+ this.t = injectCbTranslation();
125
+ this.injector = inject(Injector);
126
+ this.destroyRef = inject(DestroyRef);
127
+ this.hintId = `cb-signature-${++nextUid}-hint`;
128
+ /** Firma como data URL en `format` (`''` si está vacía). Una firma que llega de fuera se muestra de fondo. */
129
+ this.value = model('', /* @ts-ignore */
130
+ ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
131
+ /** Formato de `value`: PNG (por defecto), JPEG (fondo blanco) o SVG. */
132
+ this.format = input('png', /* @ts-ignore */
133
+ ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
134
+ /** Dibujar a mano o escribir el nombre. */
135
+ this.mode = model('draw', /* @ts-ignore */
136
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
137
+ /** Ofrece el modo escrito, la alternativa para quien no puede dibujar (teclado, lector de pantalla). */
138
+ this.typing = input(true, { ...(ngDevMode ? { debugName: "typing" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
139
+ this.penColor = input('#1e293b', /* @ts-ignore */
140
+ ...(ngDevMode ? [{ debugName: "penColor" }] : /* istanbul ignore next */ []));
141
+ /** Fondo del área y de la imagen exportada (`transparent` para PNG o SVG sin fondo). */
142
+ this.background = input('#ffffff', /* @ts-ignore */
143
+ ...(ngDevMode ? [{ debugName: "background" }] : /* istanbul ignore next */ []));
144
+ /** Grosor mínimo y máximo del trazo (en píxeles del área). */
145
+ this.minWidth = input(1, { ...(ngDevMode ? { debugName: "minWidth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
146
+ this.maxWidth = input(3.2, { ...(ngDevMode ? { debugName: "maxWidth" } : /* istanbul ignore next */ {}), transform: numberAttribute });
147
+ this.height = input('11rem', /* @ts-ignore */
148
+ ...(ngDevMode ? [{ debugName: "height" }] : /* istanbul ignore next */ []));
149
+ /** Tipografía del nombre escrito (por defecto, manuscritas del sistema). */
150
+ this.font = input(CB_SIGNATURE_FONT, /* @ts-ignore */
151
+ ...(ngDevMode ? [{ debugName: "font" }] : /* istanbul ignore next */ []));
152
+ this.placeholder = input(undefined, /* @ts-ignore */
153
+ ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
154
+ this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
155
+ this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
156
+ this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
157
+ /** Nombre del área (por defecto, "Área de firma"). */
158
+ this.ariaLabel = input('', /* @ts-ignore */
159
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
160
+ /** Empieza un trazo. */
161
+ this.signStart = output();
162
+ /** Termina un trazo o cambia el nombre escrito (con la firma completa). */
163
+ this.signEnd = output();
164
+ this.cleared = output();
165
+ this.fieldState = cbFieldState({ invalid: () => this.invalid() });
166
+ this.cvaDisabled = signal(false, /* @ts-ignore */
167
+ ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
168
+ this.isDisabled = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
169
+ ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
170
+ this.strokes = signal([], /* @ts-ignore */
171
+ ...(ngDevMode ? [{ debugName: "strokes" }] : /* istanbul ignore next */ []));
172
+ this.current = signal([], /* @ts-ignore */
173
+ ...(ngDevMode ? [{ debugName: "current" }] : /* istanbul ignore next */ []));
174
+ this.text = signal('', /* @ts-ignore */
175
+ ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
176
+ /** Firma previa (de `value`), pintada de fondo. */
177
+ this.image = signal('', /* @ts-ignore */
178
+ ...(ngDevMode ? [{ debugName: "image" }] : /* istanbul ignore next */ []));
179
+ /** Tamaño lógico del lienzo (el del área al montarse). */
180
+ this.box = signal({ w: 600, h: 200 }, /* @ts-ignore */
181
+ ...(ngDevMode ? [{ debugName: "box" }] : /* istanbul ignore next */ []));
182
+ this.announcement = signal('', /* @ts-ignore */
183
+ ...(ngDevMode ? [{ debugName: "announcement" }] : /* istanbul ignore next */ []));
184
+ this.canDraw = computed(() => this.mode() === 'draw' && !this.isDisabled() && !this.readonly(), /* @ts-ignore */
185
+ ...(ngDevMode ? [{ debugName: "canDraw" }] : /* istanbul ignore next */ []));
186
+ this.segments = computed(() => {
187
+ const min = this.minWidth();
188
+ const max = this.maxWidth();
189
+ return [...this.strokes(), this.current()].flatMap((s) => strokeSegments(s, min, max));
190
+ }, /* @ts-ignore */
191
+ ...(ngDevMode ? [{ debugName: "segments" }] : /* istanbul ignore next */ []));
192
+ /** Tamaño medido para que el nombre quepa (cada tipografía manuscrita tiene su ancho). */
193
+ this.fittedSize = signal(null, /* @ts-ignore */
194
+ ...(ngDevMode ? [{ debugName: "fittedSize" }] : /* istanbul ignore next */ []));
195
+ this.fontSize = computed(() => this.fittedSize() ?? typedFontSize(this.text(), this.box().w, this.box().h), /* @ts-ignore */
196
+ ...(ngDevMode ? [{ debugName: "fontSize" }] : /* istanbul ignore next */ []));
197
+ this.empty = computed(() => isSignatureEmpty(this.data()) && !this.image(), /* @ts-ignore */
198
+ ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
199
+ this.padLabel = computed(() => {
200
+ const t = this.t();
201
+ return `${this.ariaLabel() || t.signaturePad}: ${this.empty() ? t.signatureEmptyState : t.signatureSignedState}`;
202
+ }, /* @ts-ignore */
203
+ ...(ngDevMode ? [{ debugName: "padLabel" }] : /* istanbul ignore next */ []));
204
+ this.svg = viewChild('svg', /* @ts-ignore */
205
+ ...(ngDevMode ? [{ debugName: "svg" }] : /* istanbul ignore next */ []));
206
+ this.pointerId = null;
207
+ this.lastEmitted = '';
208
+ this.emitToken = 0;
209
+ this.onChange = () => { };
210
+ this.onTouched = () => { };
211
+ this.announceFlip = false;
212
+ const registry = inject(CbIconRegistry);
213
+ for (const [name, body] of Object.entries(ICONS)) {
214
+ if (!registry.has(name))
215
+ registry.register({ [name]: body });
216
+ }
217
+ // El lienzo toma el tamaño del área (mientras está vacío, también si cambia).
218
+ const host = inject(ElementRef);
219
+ afterNextRender(() => {
220
+ const measure = () => {
221
+ const el = this.svg()?.nativeElement;
222
+ if (!el || !untracked(this.empty))
223
+ return;
224
+ const { width, height } = el.getBoundingClientRect();
225
+ if (width > 0 && height > 0)
226
+ this.box.set({ w: Math.round(width), h: Math.round(height) });
227
+ };
228
+ measure();
229
+ if (typeof ResizeObserver !== 'undefined') {
230
+ const ro = new ResizeObserver(measure);
231
+ ro.observe(host.nativeElement);
232
+ this.destroyRef.onDestroy(() => ro.disconnect());
233
+ }
234
+ });
235
+ // Valor que llega de fuera (no el que emitimos): se muestra como firma previa.
236
+ effect(() => {
237
+ const value = this.value() ?? '';
238
+ untracked(() => {
239
+ if (value === this.lastEmitted)
240
+ return;
241
+ this.lastEmitted = value;
242
+ this.strokes.set([]);
243
+ this.current.set([]);
244
+ this.text.set('');
245
+ this.image.set(value);
246
+ });
247
+ });
248
+ }
249
+ // --- API pública ---
250
+ /** La firma actual (trazos o nombre escrito), para guardarla y restaurarla con `setData`. */
251
+ data() {
252
+ const { w, h } = this.box();
253
+ return this.mode() === 'type'
254
+ ? { width: w, height: h, strokes: [], text: this.text(), color: this.penColor() }
255
+ : { width: w, height: h, strokes: this.strokes(), color: this.penColor() };
256
+ }
257
+ /** Restaura una firma guardada con `data()`. */
258
+ setData(data) {
259
+ this.box.set({ w: data.width, h: data.height });
260
+ this.image.set('');
261
+ if (data.text !== undefined) {
262
+ this.mode.set('type');
263
+ this.text.set(data.text);
264
+ this.strokes.set([]);
265
+ }
266
+ else {
267
+ this.mode.set('draw');
268
+ this.strokes.set(data.strokes);
269
+ this.text.set('');
270
+ }
271
+ this.emit();
272
+ }
273
+ /** La firma como SVG independiente. */
274
+ toSvg() {
275
+ return signatureToSvg(this.data(), {
276
+ minWidth: this.minWidth(),
277
+ maxWidth: this.maxWidth(),
278
+ background: this.background(),
279
+ font: this.font(),
280
+ fontSize: this.mode() === 'type' ? this.fontSize() : undefined,
281
+ image: this.image() || undefined,
282
+ });
283
+ }
284
+ /** La firma como data URL (PNG y JPEG se generan en un canvas, de forma asíncrona). */
285
+ async toDataUrl(format = this.format()) {
286
+ if (this.empty())
287
+ return '';
288
+ const svg = this.toSvg();
289
+ if (format === 'svg')
290
+ return svgDataUrl(svg);
291
+ const { w, h } = this.box();
292
+ return rasterize(svg, w, h, format);
293
+ }
294
+ /** Borra la firma. */
295
+ clear() {
296
+ if (this.empty())
297
+ return;
298
+ this.strokes.set([]);
299
+ this.current.set([]);
300
+ this.text.set('');
301
+ this.image.set('');
302
+ this.emit();
303
+ this.cleared.emit();
304
+ this.announce(this.t().signatureCleared);
305
+ }
306
+ /** Deshace el último trazo. */
307
+ undo() {
308
+ if (!this.strokes().length)
309
+ return;
310
+ this.strokes.update((s) => s.slice(0, -1));
311
+ this.emit();
312
+ this.announce(this.t().signatureUndone);
313
+ }
314
+ // --- ControlValueAccessor ---
315
+ writeValue(value) {
316
+ this.value.set(value ?? '');
317
+ }
318
+ registerOnChange(fn) {
319
+ this.onChange = fn;
320
+ }
321
+ registerOnTouched(fn) {
322
+ this.onTouched = fn;
323
+ }
324
+ setDisabledState(disabled) {
325
+ this.cvaDisabled.set(disabled);
326
+ }
327
+ // --- Eventos ---
328
+ setMode(mode) {
329
+ if (mode === this.mode())
330
+ return;
331
+ this.mode.set(mode);
332
+ this.emit();
333
+ if (mode === 'type') {
334
+ afterNextRender(() => this.svg()?.nativeElement.closest('.cb-signature-pad')?.querySelector('.cb-sig__input')?.focus(), {
335
+ injector: this.injector,
336
+ });
337
+ }
338
+ }
339
+ onType(event) {
340
+ this.text.set(event.target.value);
341
+ this.image.set('');
342
+ this.fittedSize.set(null);
343
+ // Tras pintar, se mide el texto y se reduce si no cabe; después se emite con ese tamaño.
344
+ afterNextRender(() => {
345
+ this.fitText();
346
+ this.emit();
347
+ this.signEnd.emit(this.data());
348
+ }, { injector: this.injector });
349
+ }
350
+ fitText() {
351
+ const text = this.svg()?.nativeElement.querySelector('text');
352
+ const width = text && typeof text.getComputedTextLength === 'function' ? text.getComputedTextLength() : 0;
353
+ const max = this.box().w * 0.9;
354
+ if (width > max)
355
+ this.fittedSize.set(Math.floor(this.fontSize() * (max / width)));
356
+ }
357
+ onPointerDown(event) {
358
+ if (!this.canDraw() || (event.pointerType === 'mouse' && event.button !== 0))
359
+ return;
360
+ event.preventDefault();
361
+ this.pointerId = event.pointerId;
362
+ event.currentTarget.setPointerCapture?.(event.pointerId);
363
+ this.current.set([this.point(event)]);
364
+ this.signStart.emit();
365
+ }
366
+ onPointerMove(event) {
367
+ if (this.pointerId !== event.pointerId)
368
+ return;
369
+ const events = typeof event.getCoalescedEvents === 'function' ? event.getCoalescedEvents() : [];
370
+ const list = events.length ? events : [event];
371
+ const pts = [...this.current()];
372
+ for (const e of list) {
373
+ const p = this.point(e);
374
+ const last = pts[pts.length - 1];
375
+ // Puntos casi encima del anterior solo añaden ruido.
376
+ if (!last || Math.hypot(p.x - last.x, p.y - last.y) > 0.75)
377
+ pts.push(p);
378
+ }
379
+ this.current.set(pts);
380
+ }
381
+ onPointerUp(event) {
382
+ if (this.pointerId !== event.pointerId)
383
+ return;
384
+ this.pointerId = null;
385
+ const stroke = this.current();
386
+ this.current.set([]);
387
+ if (!stroke.length)
388
+ return;
389
+ const wasEmpty = this.empty();
390
+ this.strokes.update((s) => [...s, stroke]);
391
+ this.emit();
392
+ this.onTouched();
393
+ this.signEnd.emit(this.data());
394
+ if (wasEmpty)
395
+ this.announce(this.t().signatureSignedState);
396
+ }
397
+ // --- Internos ---
398
+ point(event) {
399
+ const rect = (this.svg()?.nativeElement ?? event.currentTarget).getBoundingClientRect();
400
+ const { w, h } = this.box();
401
+ // Sin tamaño (área oculta o sin maquetar) se usan las coordenadas tal cual.
402
+ const x = rect.width ? ((event.clientX - rect.left) / rect.width) * w : event.clientX;
403
+ const y = rect.height ? ((event.clientY - rect.top) / rect.height) * h : event.clientY;
404
+ return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10, t: Math.round(event.timeStamp), p: event.pressure ?? 0.5 };
405
+ }
406
+ /** Recalcula `value` (PNG y JPEG llegan cuando termina el canvas; uno viejo se descarta). */
407
+ emit() {
408
+ const token = ++this.emitToken;
409
+ const publish = (url) => {
410
+ if (token !== this.emitToken)
411
+ return;
412
+ this.lastEmitted = url;
413
+ this.value.set(url);
414
+ this.onChange(url);
415
+ };
416
+ if (this.empty()) {
417
+ publish('');
418
+ return;
419
+ }
420
+ if (this.format() === 'svg') {
421
+ publish(svgDataUrl(this.toSvg()));
422
+ return;
423
+ }
424
+ this.toDataUrl().then(publish, () => publish(svgDataUrl(this.toSvg())));
425
+ }
426
+ /** Un espacio duro alterno hace que el mismo texto cuente como cambio y se vuelva a anunciar. */
427
+ announce(text) {
428
+ this.announceFlip = !this.announceFlip;
429
+ this.announcement.set(this.announceFlip ? text : `${text} `);
430
+ }
431
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbSignaturePad, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
432
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.5", type: CbSignaturePad, isStandalone: true, selector: "cb-signature-pad", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, typing: { classPropertyName: "typing", publicName: "typing", isSignal: true, isRequired: false, transformFunction: null }, penColor: { classPropertyName: "penColor", publicName: "penColor", isSignal: true, isRequired: false, transformFunction: null }, background: { classPropertyName: "background", publicName: "background", isSignal: true, isRequired: false, transformFunction: null }, minWidth: { classPropertyName: "minWidth", publicName: "minWidth", isSignal: true, isRequired: false, transformFunction: null }, maxWidth: { classPropertyName: "maxWidth", publicName: "maxWidth", isSignal: true, isRequired: false, transformFunction: null }, height: { classPropertyName: "height", publicName: "height", isSignal: true, isRequired: false, transformFunction: null }, font: { classPropertyName: "font", publicName: "font", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", mode: "modeChange", signStart: "signStart", signEnd: "signEnd", cleared: "cleared" }, host: { properties: { "class.cb-is-disabled": "isDisabled()", "class.cb-is-invalid": "fieldState.invalid()" }, classAttribute: "cb-signature-pad" }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CbSignaturePad), multi: true }], viewQueries: [{ propertyName: "svg", first: true, predicate: ["svg"], descendants: true, isSignal: true }], ngImport: i0, template: `
433
+ <div class="cb-sig__bar">
434
+ @if (typing()) {
435
+ <div class="cb-sig__modes" role="group" [attr.aria-label]="t().signatureModes">
436
+ <button type="button" class="cb-sig__mode" [attr.aria-pressed]="mode() === 'draw'" [disabled]="isDisabled() || readonly()" (click)="setMode('draw')">
437
+ <cb-icon name="pencil" [size]="15" aria-hidden="true" />
438
+ {{ t().signatureDraw }}
439
+ </button>
440
+ <button type="button" class="cb-sig__mode" [attr.aria-pressed]="mode() === 'type'" [disabled]="isDisabled() || readonly()" (click)="setMode('type')">
441
+ <cb-icon name="cb-type" [size]="15" aria-hidden="true" />
442
+ {{ t().signatureType }}
443
+ </button>
444
+ </div>
445
+ }
446
+ <span class="cb-sig__spacer"></span>
447
+ @if (!readonly()) {
448
+ @if (mode() === 'draw') {
449
+ <button
450
+ type="button"
451
+ class="cb-sig__tool"
452
+ [attr.aria-label]="t().signatureUndo"
453
+ [attr.title]="t().signatureUndo"
454
+ [disabled]="isDisabled() || !strokes().length"
455
+ (click)="undo()"
456
+ >
457
+ <cb-icon name="cb-undo" [size]="16" aria-hidden="true" />
458
+ </button>
459
+ }
460
+ <button type="button" class="cb-sig__tool" [attr.aria-label]="t().signatureClear" [attr.title]="t().signatureClear" [disabled]="isDisabled() || empty()" (click)="clear()">
461
+ <cb-icon name="trash" [size]="16" aria-hidden="true" />
462
+ </button>
463
+ }
464
+ </div>
465
+
466
+ <div class="cb-sig__pad" [style.height]="height()" [style.background]="background() === 'transparent' ? null : background()">
467
+ <svg
468
+ #svg
469
+ class="cb-sig__svg"
470
+ [class.cb-sig__svg--draw]="canDraw()"
471
+ [attr.viewBox]="'0 0 ' + box().w + ' ' + box().h"
472
+ preserveAspectRatio="none"
473
+ role="img"
474
+ [attr.aria-label]="padLabel()"
475
+ [attr.aria-describedby]="hintId"
476
+ (pointerdown)="onPointerDown($event)"
477
+ (pointermove)="onPointerMove($event)"
478
+ (pointerup)="onPointerUp($event)"
479
+ (pointercancel)="onPointerUp($event)"
480
+ (lostpointercapture)="onPointerUp($event)"
481
+ >
482
+ @if (image()) {
483
+ <image [attr.href]="image()" [attr.width]="box().w" [attr.height]="box().h" preserveAspectRatio="xMidYMid meet" />
484
+ }
485
+ @if (mode() === 'type') {
486
+ @if (text().trim()) {
487
+ <text
488
+ [attr.x]="box().w / 2"
489
+ [attr.y]="box().h * 0.62"
490
+ text-anchor="middle"
491
+ [attr.font-family]="font()"
492
+ [attr.font-size]="fontSize()"
493
+ [attr.fill]="penColor()"
494
+ >{{ text().trim() }}</text>
495
+ }
496
+ } @else {
497
+ <g fill="none" [attr.stroke]="penColor()" stroke-linecap="round" stroke-linejoin="round">
498
+ @for (seg of segments(); track $index) {
499
+ <path [attr.d]="seg.d" [attr.stroke-width]="seg.width" />
500
+ }
501
+ </g>
502
+ }
503
+ </svg>
504
+ <span class="cb-sig__line" aria-hidden="true"><span class="cb-sig__x">×</span></span>
505
+ @if (empty() && mode() === 'draw') {
506
+ <span class="cb-sig__placeholder" aria-hidden="true">{{ placeholder() ?? t().signaturePlaceholder }}</span>
507
+ }
508
+ </div>
509
+
510
+ @if (mode() === 'type') {
511
+ <label class="cb-sig__type">
512
+ <span class="cb-sig__type-label">{{ t().signatureTypeLabel }}</span>
513
+ <input
514
+ #typeInput
515
+ type="text"
516
+ class="cb-sig__input"
517
+ autocomplete="name"
518
+ [value]="text()"
519
+ [disabled]="isDisabled()"
520
+ [readOnly]="readonly()"
521
+ [attr.aria-invalid]="fieldState.invalid() ? 'true' : null"
522
+ [attr.aria-required]="fieldState.required() || null"
523
+ (input)="onType($event)"
524
+ (blur)="onTouched()"
525
+ />
526
+ </label>
527
+ }
528
+
529
+ <span class="cb-sig__sr" [id]="hintId">{{ typing() ? t().signatureHint : t().signatureHintDraw }}</span>
530
+ <span class="cb-sig__sr" aria-live="polite">{{ announcement() }}</span>
531
+ `, isInline: true, styles: [":host{display:flex;flex-direction:column;gap:.5rem;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-sig__bar{display:flex;align-items:center;gap:.5rem;min-height:2rem}.cb-sig__spacer{flex:1}.cb-sig__modes{display:inline-flex;padding:2px;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-full, 999px)}.cb-sig__mode{display:inline-flex;align-items:center;gap:.375rem;padding:.25rem .75rem;border:0;border-radius:var(--cb-radius-full, 999px);background:transparent;color:var(--cb-color-text-muted);font:inherit;font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);cursor:pointer}.cb-sig__mode:hover:not(:disabled){color:var(--cb-color-text)}.cb-sig__mode[aria-pressed=true]{background:var(--cb-color-surface-hover);color:var(--cb-color-text)}.cb-sig__tool{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--cb-radius-md, 6px);background:transparent;color:var(--cb-color-text-muted);cursor:pointer}.cb-sig__tool:hover:not(:disabled){background:var(--cb-color-surface-hover);color:var(--cb-color-text)}.cb-sig__mode:disabled,.cb-sig__tool:disabled{opacity:.45;cursor:not-allowed}.cb-sig__pad{position:relative;overflow:hidden;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-lg, 8px)}:host(.cb-is-invalid) .cb-sig__pad{border-color:var(--cb-color-danger)}:host(.cb-is-disabled) .cb-sig__pad{opacity:.6}.cb-sig__svg{position:relative;z-index:1;display:block;width:100%;height:100%;touch-action:none;-webkit-user-select:none;user-select:none}.cb-sig__svg--draw{cursor:crosshair}.cb-sig__line{position:absolute;right:8%;bottom:24%;left:8%;border-bottom:1px solid #cbd5e1;pointer-events:none}.cb-sig__x{position:absolute;bottom:.125rem;left:0;color:#94a3b8;font-size:1rem;line-height:1}.cb-sig__placeholder{position:absolute;top:50%;left:50%;color:#94a3b8;transform:translate(-50%,-80%);pointer-events:none}.cb-sig__type{display:flex;flex-direction:column;gap:.25rem}.cb-sig__type-label{font-weight:var(--cb-font-weight-medium, 500)}.cb-sig__input{min-height:2.5rem;padding:0 .75rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-md, 6px);background:var(--cb-color-surface-raised, var(--cb-color-surface));color:var(--cb-color-text);font:inherit}.cb-sig__input[aria-invalid=true]{border-color:var(--cb-color-danger)}.cb-sig__mode:focus-visible,.cb-sig__tool:focus-visible,.cb-sig__input:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-sig__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}\n"], dependencies: [{ kind: "component", type: CbIcon, selector: "cb-icon", inputs: ["name", "size", "spin", "ariaLabel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
532
+ }
533
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.5", ngImport: i0, type: CbSignaturePad, decorators: [{
534
+ type: Component,
535
+ args: [{ selector: 'cb-signature-pad', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.Emulated, imports: [CbIcon], providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CbSignaturePad), multi: true }], host: {
536
+ class: 'cb-signature-pad',
537
+ '[class.cb-is-disabled]': 'isDisabled()',
538
+ '[class.cb-is-invalid]': 'fieldState.invalid()',
539
+ }, template: `
540
+ <div class="cb-sig__bar">
541
+ @if (typing()) {
542
+ <div class="cb-sig__modes" role="group" [attr.aria-label]="t().signatureModes">
543
+ <button type="button" class="cb-sig__mode" [attr.aria-pressed]="mode() === 'draw'" [disabled]="isDisabled() || readonly()" (click)="setMode('draw')">
544
+ <cb-icon name="pencil" [size]="15" aria-hidden="true" />
545
+ {{ t().signatureDraw }}
546
+ </button>
547
+ <button type="button" class="cb-sig__mode" [attr.aria-pressed]="mode() === 'type'" [disabled]="isDisabled() || readonly()" (click)="setMode('type')">
548
+ <cb-icon name="cb-type" [size]="15" aria-hidden="true" />
549
+ {{ t().signatureType }}
550
+ </button>
551
+ </div>
552
+ }
553
+ <span class="cb-sig__spacer"></span>
554
+ @if (!readonly()) {
555
+ @if (mode() === 'draw') {
556
+ <button
557
+ type="button"
558
+ class="cb-sig__tool"
559
+ [attr.aria-label]="t().signatureUndo"
560
+ [attr.title]="t().signatureUndo"
561
+ [disabled]="isDisabled() || !strokes().length"
562
+ (click)="undo()"
563
+ >
564
+ <cb-icon name="cb-undo" [size]="16" aria-hidden="true" />
565
+ </button>
566
+ }
567
+ <button type="button" class="cb-sig__tool" [attr.aria-label]="t().signatureClear" [attr.title]="t().signatureClear" [disabled]="isDisabled() || empty()" (click)="clear()">
568
+ <cb-icon name="trash" [size]="16" aria-hidden="true" />
569
+ </button>
570
+ }
571
+ </div>
572
+
573
+ <div class="cb-sig__pad" [style.height]="height()" [style.background]="background() === 'transparent' ? null : background()">
574
+ <svg
575
+ #svg
576
+ class="cb-sig__svg"
577
+ [class.cb-sig__svg--draw]="canDraw()"
578
+ [attr.viewBox]="'0 0 ' + box().w + ' ' + box().h"
579
+ preserveAspectRatio="none"
580
+ role="img"
581
+ [attr.aria-label]="padLabel()"
582
+ [attr.aria-describedby]="hintId"
583
+ (pointerdown)="onPointerDown($event)"
584
+ (pointermove)="onPointerMove($event)"
585
+ (pointerup)="onPointerUp($event)"
586
+ (pointercancel)="onPointerUp($event)"
587
+ (lostpointercapture)="onPointerUp($event)"
588
+ >
589
+ @if (image()) {
590
+ <image [attr.href]="image()" [attr.width]="box().w" [attr.height]="box().h" preserveAspectRatio="xMidYMid meet" />
591
+ }
592
+ @if (mode() === 'type') {
593
+ @if (text().trim()) {
594
+ <text
595
+ [attr.x]="box().w / 2"
596
+ [attr.y]="box().h * 0.62"
597
+ text-anchor="middle"
598
+ [attr.font-family]="font()"
599
+ [attr.font-size]="fontSize()"
600
+ [attr.fill]="penColor()"
601
+ >{{ text().trim() }}</text>
602
+ }
603
+ } @else {
604
+ <g fill="none" [attr.stroke]="penColor()" stroke-linecap="round" stroke-linejoin="round">
605
+ @for (seg of segments(); track $index) {
606
+ <path [attr.d]="seg.d" [attr.stroke-width]="seg.width" />
607
+ }
608
+ </g>
609
+ }
610
+ </svg>
611
+ <span class="cb-sig__line" aria-hidden="true"><span class="cb-sig__x">×</span></span>
612
+ @if (empty() && mode() === 'draw') {
613
+ <span class="cb-sig__placeholder" aria-hidden="true">{{ placeholder() ?? t().signaturePlaceholder }}</span>
614
+ }
615
+ </div>
616
+
617
+ @if (mode() === 'type') {
618
+ <label class="cb-sig__type">
619
+ <span class="cb-sig__type-label">{{ t().signatureTypeLabel }}</span>
620
+ <input
621
+ #typeInput
622
+ type="text"
623
+ class="cb-sig__input"
624
+ autocomplete="name"
625
+ [value]="text()"
626
+ [disabled]="isDisabled()"
627
+ [readOnly]="readonly()"
628
+ [attr.aria-invalid]="fieldState.invalid() ? 'true' : null"
629
+ [attr.aria-required]="fieldState.required() || null"
630
+ (input)="onType($event)"
631
+ (blur)="onTouched()"
632
+ />
633
+ </label>
634
+ }
635
+
636
+ <span class="cb-sig__sr" [id]="hintId">{{ typing() ? t().signatureHint : t().signatureHintDraw }}</span>
637
+ <span class="cb-sig__sr" aria-live="polite">{{ announcement() }}</span>
638
+ `, styles: [":host{display:flex;flex-direction:column;gap:.5rem;color:var(--cb-color-text);font-family:var(--cb-font-sans);font-size:var(--cb-text-sm, .875rem)}.cb-sig__bar{display:flex;align-items:center;gap:.5rem;min-height:2rem}.cb-sig__spacer{flex:1}.cb-sig__modes{display:inline-flex;padding:2px;border:1px solid var(--cb-color-border);border-radius:var(--cb-radius-full, 999px)}.cb-sig__mode{display:inline-flex;align-items:center;gap:.375rem;padding:.25rem .75rem;border:0;border-radius:var(--cb-radius-full, 999px);background:transparent;color:var(--cb-color-text-muted);font:inherit;font-size:var(--cb-text-xs, .75rem);font-weight:var(--cb-font-weight-medium, 500);cursor:pointer}.cb-sig__mode:hover:not(:disabled){color:var(--cb-color-text)}.cb-sig__mode[aria-pressed=true]{background:var(--cb-color-surface-hover);color:var(--cb-color-text)}.cb-sig__tool{display:inline-flex;align-items:center;justify-content:center;width:2rem;height:2rem;border:0;border-radius:var(--cb-radius-md, 6px);background:transparent;color:var(--cb-color-text-muted);cursor:pointer}.cb-sig__tool:hover:not(:disabled){background:var(--cb-color-surface-hover);color:var(--cb-color-text)}.cb-sig__mode:disabled,.cb-sig__tool:disabled{opacity:.45;cursor:not-allowed}.cb-sig__pad{position:relative;overflow:hidden;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-lg, 8px)}:host(.cb-is-invalid) .cb-sig__pad{border-color:var(--cb-color-danger)}:host(.cb-is-disabled) .cb-sig__pad{opacity:.6}.cb-sig__svg{position:relative;z-index:1;display:block;width:100%;height:100%;touch-action:none;-webkit-user-select:none;user-select:none}.cb-sig__svg--draw{cursor:crosshair}.cb-sig__line{position:absolute;right:8%;bottom:24%;left:8%;border-bottom:1px solid #cbd5e1;pointer-events:none}.cb-sig__x{position:absolute;bottom:.125rem;left:0;color:#94a3b8;font-size:1rem;line-height:1}.cb-sig__placeholder{position:absolute;top:50%;left:50%;color:#94a3b8;transform:translate(-50%,-80%);pointer-events:none}.cb-sig__type{display:flex;flex-direction:column;gap:.25rem}.cb-sig__type-label{font-weight:var(--cb-font-weight-medium, 500)}.cb-sig__input{min-height:2.5rem;padding:0 .75rem;border:1px solid var(--cb-color-border-strong, var(--cb-color-border));border-radius:var(--cb-radius-md, 6px);background:var(--cb-color-surface-raised, var(--cb-color-surface));color:var(--cb-color-text);font:inherit}.cb-sig__input[aria-invalid=true]{border-color:var(--cb-color-danger)}.cb-sig__mode:focus-visible,.cb-sig__tool:focus-visible,.cb-sig__input:focus-visible{outline:2px solid var(--cb-color-focus-ring);outline-offset:1px}.cb-sig__sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}\n"] }]
639
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }, { type: i0.Output, args: ["modeChange"] }], typing: [{ type: i0.Input, args: [{ isSignal: true, alias: "typing", required: false }] }], penColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "penColor", required: false }] }], background: [{ type: i0.Input, args: [{ isSignal: true, alias: "background", required: false }] }], minWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "minWidth", required: false }] }], maxWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxWidth", required: false }] }], height: [{ type: i0.Input, args: [{ isSignal: true, alias: "height", required: false }] }], font: [{ type: i0.Input, args: [{ isSignal: true, alias: "font", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], signStart: [{ type: i0.Output, args: ["signStart"] }], signEnd: [{ type: i0.Output, args: ["signEnd"] }], cleared: [{ type: i0.Output, args: ["cleared"] }], svg: [{ type: i0.ViewChild, args: ['svg', { isSignal: true }] }] } });
640
+ /** Iconos propios (Lucide `undo-2` y `type`). */
641
+ const ICONS = {
642
+ 'cb-undo': '<path d="M9 14 4 9l5-5" /><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11" />',
643
+ 'cb-type': '<path d="M12 4v16" /><path d="M4 7V5a1 1 0 0 1 1-1h14a1 1 0 0 1 1 1v2" /><path d="M9 20h6" />',
644
+ };
645
+
646
+ /*
647
+ * Public API Surface de @caribeng/ui/signature-pad
648
+ */
649
+
650
+ /**
651
+ * Generated bundle index. Do not edit.
652
+ */
653
+
654
+ export { CB_SIGNATURE_FONT, CbSignaturePad, signatureToSvg as cbSignatureToSvg };