@cdevhub/ngx-tw 0.5.0 → 0.6.1

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 (209) hide show
  1. package/README.md +78 -15
  2. package/fesm2022/cdevhub-ngx-tw-accordion.mjs +16 -5
  3. package/fesm2022/cdevhub-ngx-tw-accordion.mjs.map +1 -1
  4. package/fesm2022/cdevhub-ngx-tw-avatar.mjs +26 -15
  5. package/fesm2022/cdevhub-ngx-tw-avatar.mjs.map +1 -1
  6. package/fesm2022/cdevhub-ngx-tw-badge.mjs +17 -5
  7. package/fesm2022/cdevhub-ngx-tw-badge.mjs.map +1 -1
  8. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs +27 -6
  9. package/fesm2022/cdevhub-ngx-tw-breadcrumbs.mjs.map +1 -1
  10. package/fesm2022/cdevhub-ngx-tw-button.mjs +23 -11
  11. package/fesm2022/cdevhub-ngx-tw-button.mjs.map +1 -1
  12. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs +5 -5
  13. package/fesm2022/cdevhub-ngx-tw-calendar-luxon.mjs.map +1 -1
  14. package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs +3 -3
  15. package/fesm2022/cdevhub-ngx-tw-calendar-testing.mjs.map +1 -1
  16. package/fesm2022/cdevhub-ngx-tw-calendar.mjs +427 -122
  17. package/fesm2022/cdevhub-ngx-tw-calendar.mjs.map +1 -1
  18. package/fesm2022/cdevhub-ngx-tw-card.mjs +21 -11
  19. package/fesm2022/cdevhub-ngx-tw-card.mjs.map +1 -1
  20. package/fesm2022/cdevhub-ngx-tw-carousel.mjs +215 -96
  21. package/fesm2022/cdevhub-ngx-tw-carousel.mjs.map +1 -1
  22. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs +108 -64
  23. package/fesm2022/cdevhub-ngx-tw-checkbox.mjs.map +1 -1
  24. package/fesm2022/cdevhub-ngx-tw-code-block.mjs +18 -6
  25. package/fesm2022/cdevhub-ngx-tw-code-block.mjs.map +1 -1
  26. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs +55 -30
  27. package/fesm2022/cdevhub-ngx-tw-collapsible.mjs.map +1 -1
  28. package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs +218 -0
  29. package/fesm2022/cdevhub-ngx-tw-combobox-testing.mjs.map +1 -0
  30. package/fesm2022/cdevhub-ngx-tw-combobox.mjs +236 -211
  31. package/fesm2022/cdevhub-ngx-tw-combobox.mjs.map +1 -1
  32. package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs +200 -0
  33. package/fesm2022/cdevhub-ngx-tw-command-palette-testing.mjs.map +1 -0
  34. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs +34 -7
  35. package/fesm2022/cdevhub-ngx-tw-command-palette.mjs.map +1 -1
  36. package/fesm2022/cdevhub-ngx-tw-core.mjs +135 -15
  37. package/fesm2022/cdevhub-ngx-tw-core.mjs.map +1 -1
  38. package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs +203 -0
  39. package/fesm2022/cdevhub-ngx-tw-date-picker-testing.mjs.map +1 -0
  40. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs +160 -102
  41. package/fesm2022/cdevhub-ngx-tw-date-picker.mjs.map +1 -1
  42. package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs +209 -0
  43. package/fesm2022/cdevhub-ngx-tw-date-range-picker-testing.mjs.map +1 -0
  44. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs +182 -107
  45. package/fesm2022/cdevhub-ngx-tw-date-range-picker.mjs.map +1 -1
  46. package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs +122 -0
  47. package/fesm2022/cdevhub-ngx-tw-dialog-testing.mjs.map +1 -0
  48. package/fesm2022/cdevhub-ngx-tw-dialog.mjs +11 -2
  49. package/fesm2022/cdevhub-ngx-tw-dialog.mjs.map +1 -1
  50. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs +1 -1
  51. package/fesm2022/cdevhub-ngx-tw-empty-state.mjs.map +1 -1
  52. package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs +149 -0
  53. package/fesm2022/cdevhub-ngx-tw-file-upload-testing.mjs.map +1 -0
  54. package/fesm2022/cdevhub-ngx-tw-file-upload.mjs +57 -67
  55. package/fesm2022/cdevhub-ngx-tw-file-upload.mjs.map +1 -1
  56. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs +42 -24
  57. package/fesm2022/cdevhub-ngx-tw-flip-card.mjs.map +1 -1
  58. package/fesm2022/cdevhub-ngx-tw-form-field.mjs +114 -21
  59. package/fesm2022/cdevhub-ngx-tw-form-field.mjs.map +1 -1
  60. package/fesm2022/cdevhub-ngx-tw-icon.mjs +16 -6
  61. package/fesm2022/cdevhub-ngx-tw-icon.mjs.map +1 -1
  62. package/fesm2022/cdevhub-ngx-tw-input.mjs +52 -61
  63. package/fesm2022/cdevhub-ngx-tw-input.mjs.map +1 -1
  64. package/fesm2022/cdevhub-ngx-tw-item.mjs +28 -4
  65. package/fesm2022/cdevhub-ngx-tw-item.mjs.map +1 -1
  66. package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs +168 -0
  67. package/fesm2022/cdevhub-ngx-tw-menu-testing.mjs.map +1 -0
  68. package/fesm2022/cdevhub-ngx-tw-menu.mjs +58 -21
  69. package/fesm2022/cdevhub-ngx-tw-menu.mjs.map +1 -1
  70. package/fesm2022/cdevhub-ngx-tw-number-input.mjs +34 -1
  71. package/fesm2022/cdevhub-ngx-tw-number-input.mjs.map +1 -1
  72. package/fesm2022/cdevhub-ngx-tw-paginator.mjs +95 -45
  73. package/fesm2022/cdevhub-ngx-tw-paginator.mjs.map +1 -1
  74. package/fesm2022/cdevhub-ngx-tw-popover.mjs +81 -32
  75. package/fesm2022/cdevhub-ngx-tw-popover.mjs.map +1 -1
  76. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs +1 -1
  77. package/fesm2022/cdevhub-ngx-tw-progress-bar.mjs.map +1 -1
  78. package/fesm2022/cdevhub-ngx-tw-radio.mjs +131 -98
  79. package/fesm2022/cdevhub-ngx-tw-radio.mjs.map +1 -1
  80. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs +207 -42
  81. package/fesm2022/cdevhub-ngx-tw-segmented-control.mjs.map +1 -1
  82. package/fesm2022/cdevhub-ngx-tw-select-testing.mjs +165 -0
  83. package/fesm2022/cdevhub-ngx-tw-select-testing.mjs.map +1 -0
  84. package/fesm2022/cdevhub-ngx-tw-select.mjs +386 -210
  85. package/fesm2022/cdevhub-ngx-tw-select.mjs.map +1 -1
  86. package/fesm2022/{cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs → cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs} +3 -3
  87. package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-CqVJwgmp.mjs.map +1 -0
  88. package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs +129 -0
  89. package/fesm2022/cdevhub-ngx-tw-sheet-testing.mjs.map +1 -0
  90. package/fesm2022/cdevhub-ngx-tw-sheet.mjs +23 -7
  91. package/fesm2022/cdevhub-ngx-tw-sheet.mjs.map +1 -1
  92. package/fesm2022/cdevhub-ngx-tw-slider.mjs +89 -54
  93. package/fesm2022/cdevhub-ngx-tw-slider.mjs.map +1 -1
  94. package/fesm2022/cdevhub-ngx-tw-sort.mjs +122 -17
  95. package/fesm2022/cdevhub-ngx-tw-sort.mjs.map +1 -1
  96. package/fesm2022/cdevhub-ngx-tw-split.mjs +72 -19
  97. package/fesm2022/cdevhub-ngx-tw-split.mjs.map +1 -1
  98. package/fesm2022/cdevhub-ngx-tw-stat.mjs +63 -25
  99. package/fesm2022/cdevhub-ngx-tw-stat.mjs.map +1 -1
  100. package/fesm2022/cdevhub-ngx-tw-stepper.mjs +35 -8
  101. package/fesm2022/cdevhub-ngx-tw-stepper.mjs.map +1 -1
  102. package/fesm2022/cdevhub-ngx-tw-switch.mjs +54 -48
  103. package/fesm2022/cdevhub-ngx-tw-switch.mjs.map +1 -1
  104. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs +12 -15
  105. package/fesm2022/cdevhub-ngx-tw-tab-nav.mjs.map +1 -1
  106. package/fesm2022/cdevhub-ngx-tw-table.mjs +74 -13
  107. package/fesm2022/cdevhub-ngx-tw-table.mjs.map +1 -1
  108. package/fesm2022/cdevhub-ngx-tw-tabs.mjs +84 -30
  109. package/fesm2022/cdevhub-ngx-tw-tabs.mjs.map +1 -1
  110. package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs +172 -0
  111. package/fesm2022/cdevhub-ngx-tw-tags-input-testing.mjs.map +1 -0
  112. package/fesm2022/cdevhub-ngx-tw-tags-input.mjs +107 -71
  113. package/fesm2022/cdevhub-ngx-tw-tags-input.mjs.map +1 -1
  114. package/fesm2022/cdevhub-ngx-tw-textarea.mjs +41 -9
  115. package/fesm2022/cdevhub-ngx-tw-textarea.mjs.map +1 -1
  116. package/fesm2022/cdevhub-ngx-tw-theme.mjs +257 -45
  117. package/fesm2022/cdevhub-ngx-tw-theme.mjs.map +1 -1
  118. package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs +201 -0
  119. package/fesm2022/cdevhub-ngx-tw-time-picker-testing.mjs.map +1 -0
  120. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs +184 -77
  121. package/fesm2022/cdevhub-ngx-tw-time-picker.mjs.map +1 -1
  122. package/fesm2022/cdevhub-ngx-tw-timeline.mjs +110 -27
  123. package/fesm2022/cdevhub-ngx-tw-timeline.mjs.map +1 -1
  124. package/fesm2022/{cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs → cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs} +107 -19
  125. package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-BlDxemsU.mjs.map +1 -0
  126. package/fesm2022/cdevhub-ngx-tw-toast.mjs +21 -13
  127. package/fesm2022/cdevhub-ngx-tw-toast.mjs.map +1 -1
  128. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs +102 -15
  129. package/fesm2022/cdevhub-ngx-tw-tooltip.mjs.map +1 -1
  130. package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs +219 -0
  131. package/fesm2022/cdevhub-ngx-tw-transfer-testing.mjs.map +1 -0
  132. package/fesm2022/cdevhub-ngx-tw-transfer.mjs +88 -69
  133. package/fesm2022/cdevhub-ngx-tw-transfer.mjs.map +1 -1
  134. package/fesm2022/cdevhub-ngx-tw-tree.mjs +68 -19
  135. package/fesm2022/cdevhub-ngx-tw-tree.mjs.map +1 -1
  136. package/index.json +1 -1
  137. package/package.json +63 -2
  138. package/theme/_base.css +13 -0
  139. package/theme/_dark.css +111 -36
  140. package/theme/_high-contrast-dark.css +367 -0
  141. package/theme/_high-contrast.css +13 -2
  142. package/theme/_light.css +332 -0
  143. package/theme/_semantic.css +60 -26
  144. package/theme/_typography.css +17 -0
  145. package/theme/index.css +86 -7
  146. package/types/cdevhub-ngx-tw-accordion.d.ts +12 -4
  147. package/types/cdevhub-ngx-tw-avatar.d.ts +10 -3
  148. package/types/cdevhub-ngx-tw-button.d.ts +1 -0
  149. package/types/cdevhub-ngx-tw-calendar-luxon.d.ts +2 -2
  150. package/types/cdevhub-ngx-tw-calendar-testing.d.ts +1 -1
  151. package/types/cdevhub-ngx-tw-calendar.d.ts +179 -73
  152. package/types/cdevhub-ngx-tw-card.d.ts +11 -3
  153. package/types/cdevhub-ngx-tw-carousel.d.ts +89 -20
  154. package/types/cdevhub-ngx-tw-checkbox.d.ts +40 -12
  155. package/types/cdevhub-ngx-tw-code-block.d.ts +11 -2
  156. package/types/cdevhub-ngx-tw-collapsible.d.ts +32 -5
  157. package/types/cdevhub-ngx-tw-combobox-testing.d.ts +157 -0
  158. package/types/cdevhub-ngx-tw-combobox.d.ts +94 -53
  159. package/types/cdevhub-ngx-tw-command-palette-testing.d.ts +136 -0
  160. package/types/cdevhub-ngx-tw-command-palette.d.ts +33 -5
  161. package/types/cdevhub-ngx-tw-core.d.ts +152 -21
  162. package/types/cdevhub-ngx-tw-date-picker-testing.d.ts +141 -0
  163. package/types/cdevhub-ngx-tw-date-picker.d.ts +37 -29
  164. package/types/cdevhub-ngx-tw-date-range-picker-testing.d.ts +148 -0
  165. package/types/cdevhub-ngx-tw-date-range-picker.d.ts +36 -29
  166. package/types/cdevhub-ngx-tw-dialog-testing.d.ts +98 -0
  167. package/types/cdevhub-ngx-tw-dialog.d.ts +12 -3
  168. package/types/cdevhub-ngx-tw-file-upload-testing.d.ts +120 -0
  169. package/types/cdevhub-ngx-tw-file-upload.d.ts +8 -10
  170. package/types/cdevhub-ngx-tw-flip-card.d.ts +18 -11
  171. package/types/cdevhub-ngx-tw-form-field.d.ts +19 -2
  172. package/types/cdevhub-ngx-tw-icon.d.ts +3 -3
  173. package/types/cdevhub-ngx-tw-input.d.ts +5 -8
  174. package/types/cdevhub-ngx-tw-item.d.ts +2 -0
  175. package/types/cdevhub-ngx-tw-menu-testing.d.ts +131 -0
  176. package/types/cdevhub-ngx-tw-menu.d.ts +2 -2
  177. package/types/cdevhub-ngx-tw-number-input.d.ts +20 -0
  178. package/types/cdevhub-ngx-tw-paginator.d.ts +58 -24
  179. package/types/cdevhub-ngx-tw-popover.d.ts +41 -10
  180. package/types/cdevhub-ngx-tw-progress-bar.d.ts +1 -1
  181. package/types/cdevhub-ngx-tw-radio.d.ts +68 -25
  182. package/types/cdevhub-ngx-tw-segmented-control.d.ts +79 -16
  183. package/types/cdevhub-ngx-tw-select-testing.d.ts +114 -0
  184. package/types/cdevhub-ngx-tw-select.d.ts +104 -33
  185. package/types/cdevhub-ngx-tw-sheet-testing.d.ts +104 -0
  186. package/types/cdevhub-ngx-tw-sheet.d.ts +19 -3
  187. package/types/cdevhub-ngx-tw-slider.d.ts +79 -25
  188. package/types/cdevhub-ngx-tw-sort.d.ts +45 -4
  189. package/types/cdevhub-ngx-tw-split.d.ts +32 -2
  190. package/types/cdevhub-ngx-tw-stat.d.ts +14 -4
  191. package/types/cdevhub-ngx-tw-stepper.d.ts +16 -2
  192. package/types/cdevhub-ngx-tw-switch.d.ts +35 -13
  193. package/types/cdevhub-ngx-tw-tab-nav.d.ts +7 -6
  194. package/types/cdevhub-ngx-tw-table.d.ts +53 -10
  195. package/types/cdevhub-ngx-tw-tabs.d.ts +41 -10
  196. package/types/cdevhub-ngx-tw-tags-input-testing.d.ts +135 -0
  197. package/types/cdevhub-ngx-tw-tags-input.d.ts +26 -11
  198. package/types/cdevhub-ngx-tw-textarea.d.ts +3 -2
  199. package/types/cdevhub-ngx-tw-theme.d.ts +245 -39
  200. package/types/cdevhub-ngx-tw-time-picker-testing.d.ts +129 -0
  201. package/types/cdevhub-ngx-tw-time-picker.d.ts +76 -19
  202. package/types/cdevhub-ngx-tw-timeline.d.ts +36 -12
  203. package/types/cdevhub-ngx-tw-toast.d.ts +20 -6
  204. package/types/cdevhub-ngx-tw-tooltip.d.ts +28 -6
  205. package/types/cdevhub-ngx-tw-transfer-testing.d.ts +152 -0
  206. package/types/cdevhub-ngx-tw-transfer.d.ts +21 -8
  207. package/types/cdevhub-ngx-tw-tree.d.ts +6 -1
  208. package/fesm2022/cdevhub-ngx-tw-sheet-sheet-renderer-ByyNluUd.mjs.map +0 -1
  209. package/fesm2022/cdevhub-ngx-tw-toast-toast-renderer-DSu4YoTy.mjs.map +0 -1
@@ -121,7 +121,7 @@ const iconVariants = tv({
121
121
  * ```html
122
122
  * <tw-icon name="star" />
123
123
  * <tw-icon name="check" color="success" size="lg" />
124
- * <tw-icon name="alert-triangle" color="warning" ariaLabel="Warning" />
124
+ * <tw-icon name="alert-triangle" color="warning" aria-label="Warning" />
125
125
  * <tw-icon name="star" [svg]="{ strokeWidth: 1.5, absoluteStrokeWidth: true }" />
126
126
  * ```
127
127
  */
@@ -148,9 +148,8 @@ class IconComponent {
148
148
  /** Icon size. Defaults to `'md'` (20px). */
149
149
  size = input('md', /* @ts-ignore */
150
150
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
151
- /** Accessible label. When set, removes `aria-hidden` and applies `aria-label` to the SVG. Defaults to `undefined` (icon is decorative, `aria-hidden="true"`). */
152
- ariaLabel = input(/* @ts-ignore */
153
- ...(ngDevMode ? [undefined, { debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
151
+ /** Accessible label, supplied as the plain `aria-label` attribute. When set, the generated SVG takes `role="img"` plus the label instead of `aria-hidden="true"`. Defaults to `undefined` (icon is decorative, `aria-hidden="true"`). */
152
+ ariaLabel = input(undefined, { ...(ngDevMode ? { debugName: "ariaLabel" } : /* istanbul ignore next */ {}), alias: 'aria-label' });
154
153
  /**
155
154
  * SVG-author configuration: `strokeWidth`, `absoluteStrokeWidth`, `viewBox`.
156
155
  * Unset fields fall back to `{ strokeWidth: 2, absoluteStrokeWidth: false, viewBox: '0 0 24 24' }`.
@@ -266,7 +265,7 @@ class IconComponent {
266
265
  this.prevAriaLabel = label;
267
266
  }
268
267
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: IconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
269
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: IconComponent, isStandalone: true, selector: "tw-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, img: { classPropertyName: "img", publicName: "img", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, svg: { classPropertyName: "svg", publicName: "svg", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
268
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.7", type: IconComponent, isStandalone: true, selector: "tw-icon", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, img: { classPropertyName: "img", publicName: "img", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "aria-label", isSignal: true, isRequired: false, transformFunction: null }, svg: { classPropertyName: "svg", publicName: "svg", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()", "attr.aria-label": "null" } }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
270
269
  }
271
270
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImport: i0, type: IconComponent, decorators: [{
272
271
  type: Component,
@@ -277,9 +276,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.7", ngImpor
277
276
  encapsulation: ViewEncapsulation.None,
278
277
  host: {
279
278
  '[class]': 'classes()',
279
+ // The accessible name belongs on the generated `<svg>` (which takes
280
+ // `role="img"`), never on the role-less `<tw-icon>` host: ARIA 1.2
281
+ // prohibits `aria-label` on an element with no role, and axe flags it as a
282
+ // serious `aria-prohibited-attr` violation.
283
+ //
284
+ // This binding is load-bearing, not defensive. Angular DOES leave the
285
+ // literal `aria-label="…"` on the host after handing it to the aliased
286
+ // input, so without this the host keeps a prohibited attribute. Verified
287
+ // by deleting the line: the "plain aria-label attribute reachable" spec
288
+ // fails. Do not remove it as dead code.
289
+ '[attr.aria-label]': 'null',
280
290
  },
281
291
  }]
282
- }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], img: [{ type: i0.Input, args: [{ isSignal: true, alias: "img", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], svg: [{ type: i0.Input, args: [{ isSignal: true, alias: "svg", required: false }] }] } });
292
+ }], ctorParameters: () => [], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], img: [{ type: i0.Input, args: [{ isSignal: true, alias: "img", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-label", required: false }] }], svg: [{ type: i0.Input, args: [{ isSignal: true, alias: "svg", required: false }] }] } });
283
293
 
284
294
  /**
285
295
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"cdevhub-ngx-tw-icon.mjs","sources":["../../../projects/ngx-tw/icon/icon.registry.ts","../../../projects/ngx-tw/icon/icon.providers.ts","../../../projects/ngx-tw/icon/icon.ts","../../../projects/ngx-tw/icon/cdevhub-ngx-tw-icon.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport type { TwIconData, TwIconMap } from './icon.types';\n\n/**\n * Stores icon data (SVG element tuples) for use by `tw-icon`.\n *\n * Not provided in root — use `provideTwIcons()` or `provideTwLucideIcons()`\n * to register icons and supply this service.\n */\n@Injectable()\nexport class IconRegistry {\n private readonly icons: TwIconMap = {};\n\n /** Merges the given icons into the registry. */\n register(icons: TwIconMap): void {\n Object.assign(this.icons, icons);\n }\n\n /** Returns the icon data for the given PascalCase name, or `null` if not registered. */\n get(name: string): TwIconData | null {\n return this.icons[name] ?? null;\n }\n}\n","import { inject, InjectionToken } from '@angular/core';\nimport type { Provider } from '@angular/core';\nimport type { TwIconMap } from './icon.types';\nimport { IconRegistry } from './icon.registry';\n\n/**\n * Multi-provider token whose factories register icons into the `IconRegistry`.\n * Injected by `IconComponent` to trigger factory execution. The token value is never read.\n */\nexport const TW_ICON_REGISTRAR = new InjectionToken<void[]>('TwIconRegistrar');\n\n/**\n * Registers icons for use with `tw-icon`.\n *\n * Returns an array of providers that includes the `IconRegistry` service\n * and a multi-provider factory that merges the given icons into it.\n * Angular deduplicates the class provider, so multiple calls are safe.\n *\n * Works at all injector levels: app root, lazy route, and component.\n *\n * @example\n * ```ts\n * import { provideTwIcons } from '@cdevhub/ngx-tw/icon';\n *\n * const myIcons = {\n * Star: [['polygon', { points: '12 2 15.09 8.26 ...' }]],\n * };\n *\n * export const appConfig = {\n * providers: [provideTwIcons(myIcons)]\n * };\n * ```\n */\nexport function provideTwIcons(icons: TwIconMap): Provider[] {\n return [\n IconRegistry,\n {\n provide: TW_ICON_REGISTRAR,\n multi: true,\n useFactory: () => inject(IconRegistry).register(icons),\n },\n ];\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DOCUMENT,\n effect,\n ElementRef,\n inject,\n input,\n isDevMode,\n Renderer2,\n ViewEncapsulation,\n} from '@angular/core';\nimport { tv } from 'tailwind-variants';\nimport type { TwSize } from '@cdevhub/ngx-tw/core';\nimport { TW_ICON_REGISTRAR } from './icon.providers';\nimport { IconRegistry } from './icon.registry';\nimport type { TwIconColor, TwIconData, TwIconSvgConfig } from './icon.types';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nconst DEFAULT_STROKE_WIDTH = 2;\nconst DEFAULT_ABSOLUTE_STROKE_WIDTH = false;\nconst DEFAULT_VIEW_BOX = '0 0 24 24';\n\n/** Converts a kebab-case icon name to PascalCase for registry lookup. */\nfunction toPascalCase(str: string): string {\n return str.replace(/(^|[-_\\s])([a-z0-9])/g, (_match, _sep, char: string) =>\n char.toUpperCase(),\n );\n}\n\n/** Pixel sizes for each TwSize value, used as SVG width/height attributes. */\nconst ICON_SIZE_PX: Record<TwSize, number> = {\n xs: 12,\n sm: 16,\n md: 20,\n lg: 24,\n xl: 32,\n};\n\nconst iconVariants = tv({\n base: 'inline-flex items-center justify-center shrink-0 align-middle',\n variants: {\n color: {\n current: '',\n primary: 'text-primary-500',\n secondary: 'text-secondary-500',\n accent: 'text-accent-500',\n neutral: 'text-fg-muted',\n info: 'text-info-500',\n success: 'text-success-500',\n warning: 'text-warning-500',\n error: 'text-error-500',\n },\n size: {\n xs: 'size-3',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-8',\n },\n },\n defaultVariants: {\n color: 'current',\n size: 'md',\n },\n}, {\n twMerge: true,\n});\n\n/**\n * Renders SVG icons from a registry or from direct icon data.\n *\n * Icons inherit `currentColor` by default. Use the `color` input for semantic\n * color variants that respond to theme changes.\n *\n * Register icons tree-shakably via `provideTwIcons()` (raw SVG data) or\n * `provideTwLucideIcons()` from `ngx-tw/icon/lucide` (Lucide adapter).\n *\n * @example\n * ```html\n * <tw-icon name=\"star\" />\n * <tw-icon name=\"check\" color=\"success\" size=\"lg\" />\n * <tw-icon name=\"alert-triangle\" color=\"warning\" ariaLabel=\"Warning\" />\n * <tw-icon name=\"star\" [svg]=\"{ strokeWidth: 1.5, absoluteStrokeWidth: true }\" />\n * ```\n */\n// IMPORTANT: `ViewEncapsulation.None` is required because the SVG is built\n// imperatively via Renderer2 and appended outside Angular's view boundary —\n// emulated-encapsulation attribute selectors would not match it. The host class\n// list (color/size utilities) must also cascade `currentColor` into the SVG\n// children for `stroke=\"currentColor\"` to resolve correctly.\n@Component({\n selector: 'tw-icon',\n template: '',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n },\n})\nexport class IconComponent {\n private readonly doc = inject(DOCUMENT);\n private readonly renderer = inject(Renderer2);\n private readonly elRef = inject(ElementRef<HTMLElement>);\n private readonly registry = inject(IconRegistry);\n private readonly _registrar = inject(TW_ICON_REGISTRAR, { optional: true });\n\n /** Icon name in kebab-case (e.g. `'chevron-right'`). Resolved via the registry. Defaults to `undefined`. */\n readonly name = input<string>();\n\n /** Direct icon data (SVG element tuples). Takes precedence over `name`. Defaults to `undefined`. */\n readonly img = input<TwIconData>();\n\n /** Semantic color. `'current'` inherits from parent text color. Defaults to `'current'`. */\n readonly color = input<TwIconColor>('current');\n\n /** Icon size. Defaults to `'md'` (20px). */\n readonly size = input<TwSize>('md');\n\n /** Accessible label. When set, removes `aria-hidden` and applies `aria-label` to the SVG. Defaults to `undefined` (icon is decorative, `aria-hidden=\"true\"`). */\n readonly ariaLabel = input<string>();\n\n /**\n * SVG-author configuration: `strokeWidth`, `absoluteStrokeWidth`, `viewBox`.\n * Unset fields fall back to `{ strokeWidth: 2, absoluteStrokeWidth: false, viewBox: '0 0 24 24' }`.\n */\n readonly svg = input<TwIconSvgConfig>();\n\n /** Resolves icon data from `img` (priority) or `name` (registry lookup). */\n protected readonly resolvedIcon = computed<TwIconData | undefined>(() => {\n const imgData = this.img();\n if (imgData) return imgData;\n\n const iconName = this.name();\n if (iconName) return this.registry.get(toPascalCase(iconName)) ?? undefined;\n\n return undefined;\n });\n\n /** Pixel size for the current `size` input. */\n protected readonly sizeInPx = computed(() => ICON_SIZE_PX[this.size()]);\n\n /** Effective stroke width, accounting for `absoluteStrokeWidth`. */\n protected readonly effectiveStrokeWidth = computed(() => {\n const cfg = this.svg();\n const sw = cfg?.strokeWidth ?? DEFAULT_STROKE_WIDTH;\n const absolute = cfg?.absoluteStrokeWidth ?? DEFAULT_ABSOLUTE_STROKE_WIDTH;\n if (!absolute) return sw;\n return (sw * 24) / this.sizeInPx();\n });\n\n /** Effective viewBox. */\n protected readonly effectiveViewBox = computed(() =>\n this.svg()?.viewBox ?? DEFAULT_VIEW_BOX,\n );\n\n /** Host class string from tv(). */\n readonly classes = computed(() =>\n iconVariants({ color: this.color(), size: this.size() }),\n );\n\n /** Tracks the previous render state to avoid unnecessary DOM rebuilds. */\n private prevIconData: TwIconData | undefined;\n private prevStrokeWidth: number | undefined;\n private prevViewBox: string | undefined;\n private prevAriaLabel: string | undefined;\n private currentSvg: SVGElement | null = null;\n\n constructor() {\n effect(() => {\n this.renderSvg();\n });\n }\n\n private renderSvg(): void {\n const el = this.elRef.nativeElement;\n const data = this.resolvedIcon();\n const sw = this.effectiveStrokeWidth();\n const vb = this.effectiveViewBox();\n const label = this.ariaLabel();\n const iconName = this.name();\n\n // Dev-mode warning when icon name is provided but not found\n if (isDevMode() && iconName && !data) {\n console.warn(\n `Icon \"${iconName}\" not found in registry. Did you forget to register it with provideTwIcons()?`,\n );\n }\n\n // No icon data — clear and return\n if (!data) {\n if (this.currentSvg) {\n this.renderer.removeChild(el, this.currentSvg);\n this.currentSvg = null;\n this.prevIconData = undefined;\n }\n return;\n }\n\n // SVG caching: skip DOM rebuild if only color/size changed (handled by host classes)\n if (\n this.currentSvg &&\n data === this.prevIconData &&\n sw === this.prevStrokeWidth &&\n vb === this.prevViewBox &&\n label === this.prevAriaLabel\n ) {\n return;\n }\n\n // Remove previous SVG\n if (this.currentSvg) {\n this.renderer.removeChild(el, this.currentSvg);\n }\n\n const size = this.sizeInPx();\n\n // Build SVG element\n const svg = this.doc.createElementNS(SVG_NS, 'svg');\n this.renderer.setAttribute(svg, 'xmlns', SVG_NS);\n this.renderer.setAttribute(svg, 'width', String(size));\n this.renderer.setAttribute(svg, 'height', String(size));\n this.renderer.setAttribute(svg, 'viewBox', vb);\n this.renderer.setAttribute(svg, 'fill', 'none');\n this.renderer.setAttribute(svg, 'stroke', 'currentColor');\n this.renderer.setAttribute(svg, 'stroke-width', String(sw));\n this.renderer.setAttribute(svg, 'stroke-linecap', 'round');\n this.renderer.setAttribute(svg, 'stroke-linejoin', 'round');\n\n if (label) {\n this.renderer.setAttribute(svg, 'role', 'img');\n this.renderer.setAttribute(svg, 'aria-label', label);\n } else {\n this.renderer.setAttribute(svg, 'aria-hidden', 'true');\n }\n\n // Render SVG children from icon data\n for (const [tag, attrs] of data) {\n const child = this.doc.createElementNS(SVG_NS, tag);\n for (const [key, value] of Object.entries(attrs)) {\n this.renderer.setAttribute(child, key, String(value));\n }\n this.renderer.appendChild(svg, child);\n }\n\n this.renderer.appendChild(el, svg);\n\n // Update cache\n this.currentSvg = svg;\n this.prevIconData = data;\n this.prevStrokeWidth = sw;\n this.prevViewBox = vb;\n this.prevAriaLabel = label;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;AAGA;;;;;AAKG;MAEU,YAAY,CAAA;IACN,KAAK,GAAc,EAAE;;AAGtC,IAAA,QAAQ,CAAC,KAAgB,EAAA;QACvB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC;IAClC;;AAGA,IAAA,GAAG,CAAC,IAAY,EAAA;QACd,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI;IACjC;uGAXW,YAAY,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;2GAAZ,YAAY,EAAA,CAAA;;2FAAZ,YAAY,EAAA,UAAA,EAAA,CAAA;kBADxB;;;ACJD;;;AAGG;MACU,iBAAiB,GAAG,IAAI,cAAc,CAAS,iBAAiB;AAE7E;;;;;;;;;;;;;;;;;;;;;AAqBG;AACG,SAAU,cAAc,CAAC,KAAgB,EAAA;IAC7C,OAAO;QACL,YAAY;AACZ,QAAA;AACE,YAAA,OAAO,EAAE,iBAAiB;AAC1B,YAAA,KAAK,EAAE,IAAI;AACX,YAAA,UAAU,EAAE,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC;AACvD,SAAA;KACF;AACH;;ACvBA,MAAM,MAAM,GAAG,4BAA4B;AAE3C,MAAM,oBAAoB,GAAG,CAAC;AAC9B,MAAM,6BAA6B,GAAG,KAAK;AAC3C,MAAM,gBAAgB,GAAG,WAAW;AAEpC;AACA,SAAS,YAAY,CAAC,GAAW,EAAA;IAC/B,OAAO,GAAG,CAAC,OAAO,CAAC,uBAAuB,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,IAAY,KACrE,IAAI,CAAC,WAAW,EAAE,CACnB;AACH;AAEA;AACA,MAAM,YAAY,GAA2B;AAC3C,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;AACN,IAAA,EAAE,EAAE,EAAE;CACP;AAED,MAAM,YAAY,GAAG,EAAE,CAAC;AACtB,IAAA,IAAI,EAAE,+DAA+D;AACrE,IAAA,QAAQ,EAAE;AACR,QAAA,KAAK,EAAE;AACL,YAAA,OAAO,EAAE,EAAE;AACX,YAAA,OAAO,EAAE,kBAAkB;AAC3B,YAAA,SAAS,EAAE,oBAAoB;AAC/B,YAAA,MAAM,EAAE,iBAAiB;AACzB,YAAA,OAAO,EAAE,eAAe;AACxB,YAAA,IAAI,EAAE,eAAe;AACrB,YAAA,OAAO,EAAE,kBAAkB;AAC3B,YAAA,OAAO,EAAE,kBAAkB;AAC3B,YAAA,KAAK,EAAE,gBAAgB;AACxB,SAAA;AACD,QAAA,IAAI,EAAE;AACJ,YAAA,EAAE,EAAE,QAAQ;AACZ,YAAA,EAAE,EAAE,QAAQ;AACZ,YAAA,EAAE,EAAE,QAAQ;AACZ,YAAA,EAAE,EAAE,QAAQ;AACZ,YAAA,EAAE,EAAE,QAAQ;AACb,SAAA;AACF,KAAA;AACD,IAAA,eAAe,EAAE;AACf,QAAA,KAAK,EAAE,SAAS;AAChB,QAAA,IAAI,EAAE,IAAI;AACX,KAAA;CACF,EAAE;AACD,IAAA,OAAO,EAAE,IAAI;AACd,CAAA,CAAC;AAEF;;;;;;;;;;;;;;;;AAgBG;AACH;AACA;AACA;AACA;AACA;MAUa,aAAa,CAAA;AACP,IAAA,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC;AACtB,IAAA,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC;AAC5B,IAAA,KAAK,GAAG,MAAM,EAAC,UAAuB,EAAC;AACvC,IAAA,QAAQ,GAAG,MAAM,CAAC,YAAY,CAAC;IAC/B,UAAU,GAAG,MAAM,CAAC,iBAAiB,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;;AAGlE,IAAA,IAAI,GAAG,KAAK;wFAAU;;AAGtB,IAAA,GAAG,GAAG,KAAK;uFAAc;;IAGzB,KAAK,GAAG,KAAK,CAAc,SAAS;8EAAC;;IAGrC,IAAI,GAAG,KAAK,CAAS,IAAI;6EAAC;;AAG1B,IAAA,SAAS,GAAG,KAAK;6FAAU;AAEpC;;;AAGG;AACM,IAAA,GAAG,GAAG,KAAK;uFAAmB;;AAGpB,IAAA,YAAY,GAAG,QAAQ,CAAyB,MAAK;AACtE,QAAA,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE;AAC1B,QAAA,IAAI,OAAO;AAAE,YAAA,OAAO,OAAO;AAE3B,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE;AAC5B,QAAA,IAAI,QAAQ;AAAE,YAAA,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,IAAI,SAAS;AAE3E,QAAA,OAAO,SAAS;IAClB,CAAC;qFAAC;;AAGiB,IAAA,QAAQ,GAAG,QAAQ,CAAC,MAAM,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;iFAAC;;AAGpD,IAAA,oBAAoB,GAAG,QAAQ,CAAC,MAAK;AACtD,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;AACtB,QAAA,MAAM,EAAE,GAAG,GAAG,EAAE,WAAW,IAAI,oBAAoB;AACnD,QAAA,MAAM,QAAQ,GAAG,GAAG,EAAE,mBAAmB,IAAI,6BAA6B;AAC1E,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,EAAE;QACxB,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,IAAI,CAAC,QAAQ,EAAE;IACpC,CAAC;6FAAC;;AAGiB,IAAA,gBAAgB,GAAG,QAAQ,CAAC,MAC7C,IAAI,CAAC,GAAG,EAAE,EAAE,OAAO,IAAI,gBAAgB;yFACxC;;IAGQ,OAAO,GAAG,QAAQ,CAAC,MAC1B,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gFACzD;;AAGO,IAAA,YAAY;AACZ,IAAA,eAAe;AACf,IAAA,WAAW;AACX,IAAA,aAAa;IACb,UAAU,GAAsB,IAAI;AAE5C,IAAA,WAAA,GAAA;QACE,MAAM,CAAC,MAAK;YACV,IAAI,CAAC,SAAS,EAAE;AAClB,QAAA,CAAC,CAAC;IACJ;IAEQ,SAAS,GAAA;AACf,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa;AACnC,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,EAAE;AAChC,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,oBAAoB,EAAE;AACtC,QAAA,MAAM,EAAE,GAAG,IAAI,CAAC,gBAAgB,EAAE;AAClC,QAAA,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,EAAE;AAC9B,QAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE;;QAG5B,IAAI,SAAS,EAAE,IAAI,QAAQ,IAAI,CAAC,IAAI,EAAE;AACpC,YAAA,OAAO,CAAC,IAAI,CACV,SAAS,QAAQ,CAAA,6EAAA,CAA+E,CACjG;QACH;;QAGA,IAAI,CAAC,IAAI,EAAE;AACT,YAAA,IAAI,IAAI,CAAC,UAAU,EAAE;gBACnB,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC;AAC9C,gBAAA,IAAI,CAAC,UAAU,GAAG,IAAI;AACtB,gBAAA,IAAI,CAAC,YAAY,GAAG,SAAS;YAC/B;YACA;QACF;;QAGA,IACE,IAAI,CAAC,UAAU;YACf,IAAI,KAAK,IAAI,CAAC,YAAY;YAC1B,EAAE,KAAK,IAAI,CAAC,eAAe;YAC3B,EAAE,KAAK,IAAI,CAAC,WAAW;AACvB,YAAA,KAAK,KAAK,IAAI,CAAC,aAAa,EAC5B;YACA;QACF;;AAGA,QAAA,IAAI,IAAI,CAAC,UAAU,EAAE;YACnB,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC;QAChD;AAEA,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE;;AAG5B,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC;QACnD,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC;AAChD,QAAA,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;AACtD,QAAA,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,SAAS,EAAE,EAAE,CAAC;QAC9C,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC;QAC/C,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,EAAE,cAAc,CAAC;AACzD,QAAA,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAC3D,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,CAAC;QAC1D,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,iBAAiB,EAAE,OAAO,CAAC;QAE3D,IAAI,KAAK,EAAE;YACT,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC;YAC9C,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,CAAC;QACtD;aAAO;YACL,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,CAAC;QACxD;;QAGA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,EAAE;AAC/B,YAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC;AACnD,YAAA,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;AAChD,gBAAA,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACvD;YACA,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,KAAK,CAAC;QACvC;QAEA,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,EAAE,GAAG,CAAC;;AAGlC,QAAA,IAAI,CAAC,UAAU,GAAG,GAAG;AACrB,QAAA,IAAI,CAAC,YAAY,GAAG,IAAI;AACxB,QAAA,IAAI,CAAC,eAAe,GAAG,EAAE;AACzB,QAAA,IAAI,CAAC,WAAW,GAAG,EAAE;AACrB,QAAA,IAAI,CAAC,aAAa,GAAG,KAAK;IAC5B;uGAzJW,aAAa,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAb,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,QAAA,EAAA,IAAA,EAAA,aAAa,y0BAPd,EAAE,EAAA,QAAA,EAAA,IAAA,EAAA,eAAA,EAAA,EAAA,CAAA,uBAAA,CAAA,MAAA,EAAA,aAAA,EAAA,EAAA,CAAA,iBAAA,CAAA,IAAA,EAAA,CAAA;;2FAOD,aAAa,EAAA,UAAA,EAAA,CAAA;kBATzB,SAAS;AAAC,YAAA,IAAA,EAAA,CAAA;AACT,oBAAA,QAAQ,EAAE,SAAS;AACnB,oBAAA,QAAQ,EAAE,EAAE;oBACZ,eAAe,EAAE,uBAAuB,CAAC,MAAM;oBAC/C,aAAa,EAAE,iBAAiB,CAAC,IAAI;AACrC,oBAAA,IAAI,EAAE;AACJ,wBAAA,SAAS,EAAE,WAAW;AACvB,qBAAA;AACF,iBAAA;;;ACrGD;;AAEG;;;;"}
1
+ {"version":3,"file":"cdevhub-ngx-tw-icon.mjs","sources":["../../../projects/ngx-tw/icon/icon.registry.ts","../../../projects/ngx-tw/icon/icon.providers.ts","../../../projects/ngx-tw/icon/icon.ts","../../../projects/ngx-tw/icon/cdevhub-ngx-tw-icon.ts"],"sourcesContent":["import { Injectable } from '@angular/core';\nimport type { TwIconData, TwIconMap } from './icon.types';\n\n/**\n * Stores icon data (SVG element tuples) for use by `tw-icon`.\n *\n * Not provided in root — use `provideTwIcons()` or `provideTwLucideIcons()`\n * to register icons and supply this service.\n */\n@Injectable()\nexport class IconRegistry {\n private readonly icons: TwIconMap = {};\n\n /** Merges the given icons into the registry. */\n register(icons: TwIconMap): void {\n Object.assign(this.icons, icons);\n }\n\n /** Returns the icon data for the given PascalCase name, or `null` if not registered. */\n get(name: string): TwIconData | null {\n return this.icons[name] ?? null;\n }\n}\n","import { inject, InjectionToken } from '@angular/core';\nimport type { Provider } from '@angular/core';\nimport type { TwIconMap } from './icon.types';\nimport { IconRegistry } from './icon.registry';\n\n/**\n * Multi-provider token whose factories register icons into the `IconRegistry`.\n * Injected by `IconComponent` to trigger factory execution. The token value is never read.\n */\nexport const TW_ICON_REGISTRAR = new InjectionToken<void[]>('TwIconRegistrar');\n\n/**\n * Registers icons for use with `tw-icon`.\n *\n * Returns an array of providers that includes the `IconRegistry` service\n * and a multi-provider factory that merges the given icons into it.\n * Angular deduplicates the class provider, so multiple calls are safe.\n *\n * Works at all injector levels: app root, lazy route, and component.\n *\n * @example\n * ```ts\n * import { provideTwIcons } from '@cdevhub/ngx-tw/icon';\n *\n * const myIcons = {\n * Star: [['polygon', { points: '12 2 15.09 8.26 ...' }]],\n * };\n *\n * export const appConfig = {\n * providers: [provideTwIcons(myIcons)]\n * };\n * ```\n */\nexport function provideTwIcons(icons: TwIconMap): Provider[] {\n return [\n IconRegistry,\n {\n provide: TW_ICON_REGISTRAR,\n multi: true,\n useFactory: () => inject(IconRegistry).register(icons),\n },\n ];\n}\n","import {\n ChangeDetectionStrategy,\n Component,\n computed,\n DOCUMENT,\n effect,\n ElementRef,\n inject,\n input,\n isDevMode,\n Renderer2,\n ViewEncapsulation,\n} from '@angular/core';\nimport { tv } from 'tailwind-variants';\nimport type { TwSize } from '@cdevhub/ngx-tw/core';\nimport { TW_ICON_REGISTRAR } from './icon.providers';\nimport { IconRegistry } from './icon.registry';\nimport type { TwIconColor, TwIconData, TwIconSvgConfig } from './icon.types';\n\nconst SVG_NS = 'http://www.w3.org/2000/svg';\n\nconst DEFAULT_STROKE_WIDTH = 2;\nconst DEFAULT_ABSOLUTE_STROKE_WIDTH = false;\nconst DEFAULT_VIEW_BOX = '0 0 24 24';\n\n/** Converts a kebab-case icon name to PascalCase for registry lookup. */\nfunction toPascalCase(str: string): string {\n return str.replace(/(^|[-_\\s])([a-z0-9])/g, (_match, _sep, char: string) =>\n char.toUpperCase(),\n );\n}\n\n/** Pixel sizes for each TwSize value, used as SVG width/height attributes. */\nconst ICON_SIZE_PX: Record<TwSize, number> = {\n xs: 12,\n sm: 16,\n md: 20,\n lg: 24,\n xl: 32,\n};\n\nconst iconVariants = tv({\n base: 'inline-flex items-center justify-center shrink-0 align-middle',\n variants: {\n color: {\n current: '',\n primary: 'text-primary-500',\n secondary: 'text-secondary-500',\n accent: 'text-accent-500',\n neutral: 'text-fg-muted',\n info: 'text-info-500',\n success: 'text-success-500',\n warning: 'text-warning-500',\n error: 'text-error-500',\n },\n size: {\n xs: 'size-3',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-8',\n },\n },\n defaultVariants: {\n color: 'current',\n size: 'md',\n },\n}, {\n twMerge: true,\n});\n\n/**\n * Renders SVG icons from a registry or from direct icon data.\n *\n * Icons inherit `currentColor` by default. Use the `color` input for semantic\n * color variants that respond to theme changes.\n *\n * Register icons tree-shakably via `provideTwIcons()` (raw SVG data) or\n * `provideTwLucideIcons()` from `ngx-tw/icon/lucide` (Lucide adapter).\n *\n * @example\n * ```html\n * <tw-icon name=\"star\" />\n * <tw-icon name=\"check\" color=\"success\" size=\"lg\" />\n * <tw-icon name=\"alert-triangle\" color=\"warning\" aria-label=\"Warning\" />\n * <tw-icon name=\"star\" [svg]=\"{ strokeWidth: 1.5, absoluteStrokeWidth: true }\" />\n * ```\n */\n// IMPORTANT: `ViewEncapsulation.None` is required because the SVG is built\n// imperatively via Renderer2 and appended outside Angular's view boundary —\n// emulated-encapsulation attribute selectors would not match it. The host class\n// list (color/size utilities) must also cascade `currentColor` into the SVG\n// children for `stroke=\"currentColor\"` to resolve correctly.\n@Component({\n selector: 'tw-icon',\n template: '',\n changeDetection: ChangeDetectionStrategy.OnPush,\n encapsulation: ViewEncapsulation.None,\n host: {\n '[class]': 'classes()',\n // The accessible name belongs on the generated `<svg>` (which takes\n // `role=\"img\"`), never on the role-less `<tw-icon>` host: ARIA 1.2\n // prohibits `aria-label` on an element with no role, and axe flags it as a\n // serious `aria-prohibited-attr` violation.\n //\n // This binding is load-bearing, not defensive. Angular DOES leave the\n // literal `aria-label=\"…\"` on the host after handing it to the aliased\n // input, so without this the host keeps a prohibited attribute. Verified\n // by deleting the line: the \"plain aria-label attribute reachable\" spec\n // fails. Do not remove it as dead code.\n '[attr.aria-label]': 'null',\n },\n})\nexport class IconComponent {\n private readonly doc = inject(DOCUMENT);\n private readonly renderer = inject(Renderer2);\n private readonly elRef = inject(ElementRef<HTMLElement>);\n private readonly registry = inject(IconRegistry);\n private readonly _registrar = inject(TW_ICON_REGISTRAR, { optional: true });\n\n /** Icon name in kebab-case (e.g. `'chevron-right'`). Resolved via the registry. Defaults to `undefined`. */\n readonly name = input<string>();\n\n /** Direct icon data (SVG element tuples). Takes precedence over `name`. Defaults to `undefined`. */\n readonly img = input<TwIconData>();\n\n /** Semantic color. `'current'` inherits from parent text color. Defaults to `'current'`. */\n readonly color = input<TwIconColor>('current');\n\n /** Icon size. Defaults to `'md'` (20px). */\n readonly size = input<TwSize>('md');\n\n /** Accessible label, supplied as the plain `aria-label` attribute. When set, the generated SVG takes `role=\"img\"` plus the label instead of `aria-hidden=\"true\"`. Defaults to `undefined` (icon is decorative, `aria-hidden=\"true\"`). */\n readonly ariaLabel = input<string | undefined>(undefined, { alias: 'aria-label' });\n\n /**\n * SVG-author configuration: `strokeWidth`, `absoluteStrokeWidth`, `viewBox`.\n * Unset fields fall back to `{ strokeWidth: 2, absoluteStrokeWidth: false, viewBox: '0 0 24 24' }`.\n */\n readonly svg = input<TwIconSvgConfig>();\n\n /** Resolves icon data from `img` (priority) or `name` (registry lookup). */\n protected readonly resolvedIcon = computed<TwIconData | undefined>(() => {\n const imgData = this.img();\n if (imgData) return imgData;\n\n const iconName = this.name();\n if (iconName) return this.registry.get(toPascalCase(iconName)) ?? undefined;\n\n return undefined;\n });\n\n /** Pixel size for the current `size` input. */\n protected readonly sizeInPx = computed(() => ICON_SIZE_PX[this.size()]);\n\n /** Effective stroke width, accounting for `absoluteStrokeWidth`. */\n protected readonly effectiveStrokeWidth = computed(() => {\n const cfg = this.svg();\n const sw = cfg?.strokeWidth ?? DEFAULT_STROKE_WIDTH;\n const absolute = cfg?.absoluteStrokeWidth ?? DEFAULT_ABSOLUTE_STROKE_WIDTH;\n if (!absolute) return sw;\n return (sw * 24) / this.sizeInPx();\n });\n\n /** Effective viewBox. */\n protected readonly effectiveViewBox = computed(() =>\n this.svg()?.viewBox ?? DEFAULT_VIEW_BOX,\n );\n\n /** Host class string from tv(). */\n readonly classes = computed(() =>\n iconVariants({ color: this.color(), size: this.size() }),\n );\n\n /** Tracks the previous render state to avoid unnecessary DOM rebuilds. */\n private prevIconData: TwIconData | undefined;\n private prevStrokeWidth: number | undefined;\n private prevViewBox: string | undefined;\n private prevAriaLabel: string | undefined;\n private currentSvg: SVGElement | null = null;\n\n constructor() {\n effect(() => {\n this.renderSvg();\n });\n }\n\n private renderSvg(): void {\n const el = this.elRef.nativeElement;\n const data = this.resolvedIcon();\n const sw = this.effectiveStrokeWidth();\n const vb = this.effectiveViewBox();\n const label = this.ariaLabel();\n const iconName = this.name();\n\n // Dev-mode warning when icon name is provided but not found\n if (isDevMode() && iconName && !data) {\n console.warn(\n `Icon \"${iconName}\" not found in registry. Did you forget to register it with provideTwIcons()?`,\n );\n }\n\n // No icon data — clear and return\n if (!data) {\n if (this.currentSvg) {\n this.renderer.removeChild(el, this.currentSvg);\n this.currentSvg = null;\n this.prevIconData = undefined;\n }\n return;\n }\n\n // SVG caching: skip DOM rebuild if only color/size changed (handled by host classes)\n if (\n this.currentSvg &&\n data === this.prevIconData &&\n sw === this.prevStrokeWidth &&\n vb === this.prevViewBox &&\n label === this.prevAriaLabel\n ) {\n return;\n }\n\n // Remove previous SVG\n if (this.currentSvg) {\n this.renderer.removeChild(el, this.currentSvg);\n }\n\n const size = this.sizeInPx();\n\n // Build SVG element\n const svg = this.doc.createElementNS(SVG_NS, 'svg');\n this.renderer.setAttribute(svg, 'xmlns', SVG_NS);\n this.renderer.setAttribute(svg, 'width', String(size));\n this.renderer.setAttribute(svg, 'height', String(size));\n this.renderer.setAttribute(svg, 'viewBox', vb);\n this.renderer.setAttribute(svg, 'fill', 'none');\n this.renderer.setAttribute(svg, 'stroke', 'currentColor');\n this.renderer.setAttribute(svg, 'stroke-width', String(sw));\n this.renderer.setAttribute(svg, 'stroke-linecap', 'round');\n this.renderer.setAttribute(svg, 'stroke-linejoin', 'round');\n\n if (label) {\n this.renderer.setAttribute(svg, 'role', 'img');\n this.renderer.setAttribute(svg, 'aria-label', label);\n } else {\n this.renderer.setAttribute(svg, 'aria-hidden', 'true');\n }\n\n // Render SVG children from icon data\n for (const [tag, attrs] of data) {\n const child = this.doc.createElementNS(SVG_NS, tag);\n for (const [key, value] of Object.entries(attrs)) {\n this.renderer.setAttribute(child, key, String(value));\n }\n this.renderer.appendChild(svg, child);\n }\n\n this.renderer.appendChild(el, svg);\n\n // Update cache\n this.currentSvg = svg;\n this.prevIconData = data;\n this.prevStrokeWidth = sw;\n this.prevViewBox = vb;\n this.prevAriaLabel = label;\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,13 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, ElementRef, DestroyRef, input, booleanAttribute, signal, computed, isSignal, effect, untracked, isDevMode, Directive } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
- import { NgControl, NgForm, FormGroupDirective, Validators } from '@angular/forms';
4
+ import { NgControl } from '@angular/forms';
5
5
  import { FocusMonitor } from '@angular/cdk/a11y';
6
6
  import { AutofillMonitor } from '@angular/cdk/text-field';
7
7
  import { Platform } from '@angular/cdk/platform';
8
- import { merge } from 'rxjs';
9
8
  import { tv } from 'tailwind-variants';
10
- import { TW_ERROR_STATE_MATCHER } from '@cdevhub/ngx-tw/core';
9
+ import { wireErrorState } from '@cdevhub/ngx-tw/core';
11
10
  import { FormFieldControl, TW_FORM_FIELD, TW_FORM_FIELD_CONTROL } from '@cdevhub/ngx-tw/form-field';
12
11
 
13
12
  /**
@@ -43,6 +42,23 @@ const inputVariants = tv({
43
42
  true: 'border-0 p-0 shadow-none focus:outline-none focus-visible:outline-none',
44
43
  false: 'rounded-md border border-border transition-[color,border-color,box-shadow] duration-normal motion-reduce:transition-none hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',
45
44
  },
45
+ // Height strategy for the standalone (`inFormField: false`) size scale —
46
+ // see `docs/vertical-rhythm.md`. Driven by the host tag, not an input.
47
+ //
48
+ // `false` (a single-line `<input>`) pins the border-box height
49
+ // (`h-6` … `h-12`) and deletes the vertical padding, so padding and
50
+ // height cannot fight. Browsers centre an `<input>`'s text natively, so
51
+ // no flex box is needed.
52
+ //
53
+ // `true` (a `<textarea>`) is multi-line by definition: the vertical
54
+ // padding is retained and no height utility is emitted here. The
55
+ // control-scale `min-h-*` floor is applied by `TextareaDirective`
56
+ // instead, because it has to be droppable while CDK autosize owns the
57
+ // height — see the `heightFloor` variant in `textarea.ts`.
58
+ multiline: {
59
+ true: '',
60
+ false: '',
61
+ },
46
62
  size: {
47
63
  xs: '',
48
64
  sm: '',
@@ -60,12 +76,20 @@ const inputVariants = tv({
60
76
  },
61
77
  },
62
78
  compoundVariants: [
63
- // Standalone density × size — inline padding + font scale.
64
- { inFormField: false, size: 'xs', class: 'px-2 py-1 text-xs' },
65
- { inFormField: false, size: 'sm', class: 'px-3 py-1.5 text-sm' },
66
- { inFormField: false, size: 'md', class: 'px-4 py-2 text-sm' },
67
- { inFormField: false, size: 'lg', class: 'px-5 py-2.5 text-base' },
68
- { inFormField: false, size: 'xl', class: 'px-6 py-3 text-base' },
79
+ // Standalone density × size — single-line: horizontal padding, font
80
+ // scale, and the pinned control height (no vertical padding).
81
+ { inFormField: false, multiline: false, size: 'xs', class: 'px-2 text-xs h-6' },
82
+ { inFormField: false, multiline: false, size: 'sm', class: 'px-3 text-sm h-8' },
83
+ { inFormField: false, multiline: false, size: 'md', class: 'px-4 text-sm h-9' },
84
+ { inFormField: false, multiline: false, size: 'lg', class: 'px-5 text-base h-11' },
85
+ { inFormField: false, multiline: false, size: 'xl', class: 'px-6 text-base h-12' },
86
+ // Standalone density × size — multi-line: vertical padding is kept so the
87
+ // box can grow; the height floor is applied by `TextareaDirective`.
88
+ { inFormField: false, multiline: true, size: 'xs', class: 'px-2 py-1 text-xs' },
89
+ { inFormField: false, multiline: true, size: 'sm', class: 'px-3 py-1.5 text-sm' },
90
+ { inFormField: false, multiline: true, size: 'md', class: 'px-4 py-2 text-sm' },
91
+ { inFormField: false, multiline: true, size: 'lg', class: 'px-5 py-2.5 text-base' },
92
+ { inFormField: false, multiline: true, size: 'xl', class: 'px-6 py-3 text-base' },
69
93
  // Standalone error state — colored border and focus ring.
70
94
  {
71
95
  inFormField: false,
@@ -75,6 +99,7 @@ const inputVariants = tv({
75
99
  ],
76
100
  defaultVariants: {
77
101
  inFormField: false,
102
+ multiline: false,
78
103
  size: 'md',
79
104
  errorState: false,
80
105
  disabled: false,
@@ -92,7 +117,7 @@ let nextInputId = 0;
92
117
  * and provide under `TW_FORM_FIELD_CONTROL`.
93
118
  * - Swap value storage for an existing `<input twInput>` (masked input,
94
119
  * date parser, etc.) → provide {@link TW_INPUT_VALUE_ACCESSOR}.
95
- * - Change when errors show → override {@link TW_ERROR_STATE_MATCHER} at any
120
+ * - Change when errors show → override `TW_ERROR_STATE_MATCHER` at any
96
121
  * injector level, or pass `errorStateMatcher` per instance.
97
122
  *
98
123
  * Forms integration is inherited: Angular's native value accessors attach to
@@ -110,11 +135,6 @@ class InputDirective extends FormFieldControl {
110
135
  optional: true,
111
136
  self: true,
112
137
  });
113
- parentForm = inject(NgForm, { optional: true });
114
- parentFormGroup = inject(FormGroupDirective, {
115
- optional: true,
116
- });
117
- defaultMatcher = inject(TW_ERROR_STATE_MATCHER);
118
138
  valueAccessor = inject(TW_INPUT_VALUE_ACCESSOR, {
119
139
  optional: true,
120
140
  self: true,
@@ -129,7 +149,7 @@ class InputDirective extends FormFieldControl {
129
149
  /** Native HTML input `type`. Defaults to `'text'`. Dev-mode throws on unsupported values (`checkbox`, `radio`, `submit`, etc.) — use the dedicated component instead. Ignored on `<textarea>`. */
130
150
  type = input('text', /* @ts-ignore */
131
151
  ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
132
- /** Density of a standalone field. Maps to the inline-padding scale (`px-2 py-1` xs … `px-6 py-3` xl) and font scale (`text-xs` xs, `text-sm` sm/md, `text-base` lg/xl). Ignored inside a `<tw-form-field>` — the wrapper's `size` carries density. Defaults to `'md'`. */
152
+ /** Density of a standalone field. Maps to the pinned control-height scale (24/32/36/44/48px, `h-6` xs … `h-12` xl), the horizontal-padding scale (`px-2` xs … `px-6` xl), and the font scale (`text-xs` xs, `text-sm` sm/md, `text-base` lg/xl). On a `<textarea>` the height becomes a `min-h-*` floor and the vertical padding is retained. Ignored inside a `<tw-form-field>` — the wrapper's `size` carries density. Defaults to `'md'`. */
133
153
  size = input('md', /* @ts-ignore */
134
154
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
135
155
  /** Disables the control. Also reflects `ngControl.disabled` when the element is bound to a reactive form. Defaults to `false`. */
@@ -155,10 +175,13 @@ class InputDirective extends FormFieldControl {
155
175
  ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
156
176
  _autofilled = signal(false, /* @ts-ignore */
157
177
  ...(ngDevMode ? [{ debugName: "_autofilled" }] : /* istanbul ignore next */ []));
158
- _ngControlRev = signal(0, /* @ts-ignore */
159
- ...(ngDevMode ? [{ debugName: "_ngControlRev" }] : /* istanbul ignore next */ []));
160
- _formSubmitRev = signal(0, /* @ts-ignore */
161
- ...(ngDevMode ? [{ debugName: "_formSubmitRev" }] : /* istanbul ignore next */ []));
178
+ /** @internal Shared `errorState` / `required` / `errors` derivation — see `wireErrorState`. */
179
+ errorWiring = wireErrorState({
180
+ ngControl: () => this.ngControl,
181
+ matcher: () => this.errorStateMatcher(),
182
+ required: () => this.requiredInput(),
183
+ track: [() => this._focused()],
184
+ });
162
185
  // ── FormFieldControl signals ──
163
186
  /** @internal */
164
187
  id = computed(() => this.idInput() ?? this.uid, /* @ts-ignore */
@@ -178,38 +201,20 @@ class InputDirective extends FormFieldControl {
178
201
  ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
179
202
  /** @internal */
180
203
  disabled = computed(() => {
181
- this._ngControlRev();
204
+ this.errorWiring.rev();
182
205
  return this.disabledInput() || !!this.ngControl?.disabled;
183
206
  }, /* @ts-ignore */
184
207
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
185
208
  /** @internal */
186
- required = computed(() => {
187
- this._ngControlRev();
188
- if (this.requiredInput())
189
- return true;
190
- return !!this.ngControl?.control?.hasValidator(Validators.required);
191
- }, /* @ts-ignore */
192
- ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
209
+ required = this.errorWiring.required;
193
210
  /** @internal */
194
- errorState = computed(() => {
195
- this._ngControlRev();
196
- this._formSubmitRev();
197
- this._focused();
198
- const matcher = this.errorStateMatcher() ?? this.defaultMatcher;
199
- const form = this.parentFormGroup ??
200
- this.parentForm;
201
- return matcher.isErrorState(this.ngControl?.control ?? null, form);
202
- }, /* @ts-ignore */
203
- ...(ngDevMode ? [{ debugName: "errorState" }] : /* istanbul ignore next */ []));
204
- /** @internal Active validation errors map from the bound `NgControl` (or `null` when the control reports none / is unbound). Recomputes on every `_ngControlRev` tick so `[twError match="…"]` reacts to validator transitions, including rules that fire/clear without flipping `VALID`/`INVALID`. */
205
- errors = computed(() => {
206
- this._ngControlRev();
207
- return this.ngControl?.control?.errors ?? null;
208
- }, /* @ts-ignore */
209
- ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
211
+ errorState = this.errorWiring.errorState;
212
+ /** @internal Active validation errors map from the bound `NgControl`, for `[twError match="…"]` filtering. */
213
+ errors = this.errorWiring.errors;
210
214
  /** @internal */
211
215
  classes = computed(() => inputVariants({
212
216
  inFormField: !!this.formField,
217
+ multiline: this._isTextarea,
213
218
  size: this.size(),
214
219
  errorState: this.errorState(),
215
220
  disabled: this.disabled(),
@@ -226,7 +231,7 @@ class InputDirective extends FormFieldControl {
226
231
  if (wasFocused && !origin) {
227
232
  // Blur often flips `touched` on the bound `NgControl`; bump the
228
233
  // revision signal so `errorState` recomputes.
229
- this._ngControlRev.update((n) => n + 1);
234
+ this.errorWiring.bump();
230
235
  }
231
236
  });
232
237
  this.destroyRef.onDestroy(() => this.focusMonitor.stopMonitoring(this.elementRef));
@@ -266,23 +271,9 @@ class InputDirective extends FormFieldControl {
266
271
  this.destroyRef.onDestroy(() => this.autofillMonitor.stopMonitoring(this.elementRef.nativeElement));
267
272
  }
268
273
  // NgControl's `control` is set by the parent `FormControl*` directive's
269
- // own `ngOnChanges`, which runs before children's `ngOnInit`. Subscribe
274
+ // own `ngOnChanges`, which runs before children's `ngOnInit`. Connect
270
275
  // here so `statusChanges`/`valueChanges` are actually available.
271
- const ctrl = this.ngControl?.control;
272
- if (ctrl) {
273
- const streams = [ctrl.statusChanges, ctrl.valueChanges].filter((s) => !!s);
274
- if (streams.length) {
275
- merge(...streams)
276
- .pipe(takeUntilDestroyed(this.destroyRef))
277
- .subscribe(() => this._ngControlRev.update((n) => n + 1));
278
- }
279
- }
280
- const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;
281
- if (submit) {
282
- submit
283
- .pipe(takeUntilDestroyed(this.destroyRef))
284
- .subscribe(() => this._formSubmitRev.update((n) => n + 1));
285
- }
276
+ this.errorWiring.connect();
286
277
  }
287
278
  /** Moves focus to the underlying element. */
288
279
  focus(options) {
@@ -1 +1 @@
1
- {"version":3,"file":"cdevhub-ngx-tw-input.mjs","sources":["../../../projects/ngx-tw/input/input.ts","../../../projects/ngx-tw/input/cdevhub-ngx-tw-input.ts"],"sourcesContent":["import {\n booleanAttribute,\n computed,\n DestroyRef,\n Directive,\n effect,\n ElementRef,\n inject,\n InjectionToken,\n input,\n isDevMode,\n isSignal,\n type OnInit,\n signal,\n type Signal,\n untracked,\n type WritableSignal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport {\n FormGroupDirective,\n NgControl,\n NgForm,\n Validators,\n} from '@angular/forms';\nimport { FocusMonitor } from '@angular/cdk/a11y';\nimport { AutofillMonitor } from '@angular/cdk/text-field';\nimport { Platform } from '@angular/cdk/platform';\nimport { merge } from 'rxjs';\nimport { tv } from 'tailwind-variants';\nimport {\n type ErrorStateMatcher,\n TW_ERROR_STATE_MATCHER,\n type TwFormSubmitted,\n type TwSize,\n} from '@cdevhub/ngx-tw/core';\nimport {\n FormFieldControl,\n TW_FORM_FIELD,\n TW_FORM_FIELD_CONTROL,\n} from '@cdevhub/ngx-tw/form-field';\n\n/**\n * Extension point for directives that wrap an `<input twInput>` and need to\n * own how its value is read and written. The directive's `value` property can\n * be a plain object slot (`{ value: T }`) or a `WritableSignal<T>`. Mirrors\n * Angular Material's `MAT_INPUT_VALUE_ACCESSOR`.\n */\nexport const TW_INPUT_VALUE_ACCESSOR = new InjectionToken<{\n value: unknown | WritableSignal<unknown>;\n}>('TW_INPUT_VALUE_ACCESSOR');\n\nconst INVALID_INPUT_TYPES = new Set([\n 'button',\n 'checkbox',\n 'file',\n 'hidden',\n 'image',\n 'radio',\n 'range',\n 'reset',\n 'submit',\n]);\n\nconst NEVER_EMPTY_TYPES = new Set([\n 'date',\n 'datetime-local',\n 'month',\n 'time',\n 'week',\n 'color',\n]);\n\nconst inputVariants = tv(\n {\n base: 'block w-full bg-transparent text-fg placeholder:text-fg-subtle outline-none',\n variants: {\n inFormField: {\n true: 'border-0 p-0 shadow-none focus:outline-none focus-visible:outline-none',\n false:\n 'rounded-md border border-border transition-[color,border-color,box-shadow] duration-normal motion-reduce:transition-none hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',\n },\n size: {\n xs: '',\n sm: '',\n md: '',\n lg: '',\n xl: '',\n },\n errorState: {\n true: '',\n false: '',\n },\n disabled: {\n true: 'opacity-50 cursor-not-allowed',\n false: '',\n },\n },\n compoundVariants: [\n // Standalone density × size — inline padding + font scale.\n { inFormField: false, size: 'xs', class: 'px-2 py-1 text-xs' },\n { inFormField: false, size: 'sm', class: 'px-3 py-1.5 text-sm' },\n { inFormField: false, size: 'md', class: 'px-4 py-2 text-sm' },\n { inFormField: false, size: 'lg', class: 'px-5 py-2.5 text-base' },\n { inFormField: false, size: 'xl', class: 'px-6 py-3 text-base' },\n // Standalone error state — colored border and focus ring.\n {\n inFormField: false,\n errorState: true,\n class: 'border-error-500 focus-visible:outline-error-500',\n },\n ],\n defaultVariants: {\n inFormField: false,\n size: 'md',\n errorState: false,\n disabled: false,\n },\n },\n { twMerge: true },\n);\n\nlet nextInputId = 0;\n\n/**\n * Adapts a native `<input>` or `<textarea>` into an ngx-tw form-field-compatible\n * control. Works standalone (applies its own border and focus styling) or\n * inside a `<tw-form-field>` (strips its chrome and provides signals the\n * form-field uses to float the label and wire `aria-describedby`).\n *\n * Extension points:\n * - Implement a completely custom control → extend {@link FormFieldControl}\n * and provide under `TW_FORM_FIELD_CONTROL`.\n * - Swap value storage for an existing `<input twInput>` (masked input,\n * date parser, etc.) → provide {@link TW_INPUT_VALUE_ACCESSOR}.\n * - Change when errors show → override {@link TW_ERROR_STATE_MATCHER} at any\n * injector level, or pass `errorStateMatcher` per instance.\n *\n * Forms integration is inherited: Angular's native value accessors attach to\n * the underlying element, so the directive works with template-driven,\n * reactive, and signal-based forms without additional glue.\n */\n@Directive({\n selector: 'input[twInput], textarea[twInput]',\n exportAs: 'twInput',\n providers: [\n { provide: TW_FORM_FIELD_CONTROL, useExisting: InputDirective },\n ],\n host: {\n '[class]': 'classes()',\n '[attr.id]': 'id()',\n '[attr.type]': '_isTextarea ? null : type()',\n '[disabled]': 'disabled()',\n '[attr.aria-invalid]': 'errorState() || null',\n '[attr.aria-required]': 'required() || null',\n '(input)': '_onInput()',\n },\n})\nexport class InputDirective\n extends FormFieldControl<string>\n implements OnInit\n{\n private readonly elementRef = inject<\n ElementRef<HTMLInputElement | HTMLTextAreaElement>\n >(ElementRef);\n private readonly focusMonitor = inject(FocusMonitor);\n private readonly autofillMonitor = inject(AutofillMonitor);\n private readonly platform = inject(Platform);\n private readonly destroyRef = inject(DestroyRef);\n private readonly formField = inject(TW_FORM_FIELD, { optional: true });\n private readonly ngControl = inject(NgControl, {\n optional: true,\n self: true,\n });\n private readonly parentForm = inject(NgForm, { optional: true });\n private readonly parentFormGroup = inject(FormGroupDirective, {\n optional: true,\n });\n private readonly defaultMatcher = inject(TW_ERROR_STATE_MATCHER);\n private readonly valueAccessor = inject(TW_INPUT_VALUE_ACCESSOR, {\n optional: true,\n self: true,\n });\n\n /** @internal */\n readonly _isTextarea =\n this.elementRef.nativeElement.tagName.toLowerCase() === 'textarea';\n\n /** Contract value — `'input'` for `<input>` and `'textarea'` for `<textarea>`. Form-field appends it as `tw-form-field-type-*` for styling hooks. */\n readonly controlType: string = this._isTextarea ? 'textarea' : 'input';\n\n private readonly uid = `tw-input-${nextInputId++}`;\n\n /** Id on the underlying element. Auto-generated as `tw-input-N` when not provided. Used by the form-field's `<label for>` attribute. */\n readonly idInput = input<string | undefined>(undefined, { alias: 'id' });\n\n /** Native HTML input `type`. Defaults to `'text'`. Dev-mode throws on unsupported values (`checkbox`, `radio`, `submit`, etc.) — use the dedicated component instead. Ignored on `<textarea>`. */\n readonly type = input<string>('text');\n\n /** Density of a standalone field. Maps to the inline-padding scale (`px-2 py-1` xs … `px-6 py-3` xl) and font scale (`text-xs` xs, `text-sm` sm/md, `text-base` lg/xl). Ignored inside a `<tw-form-field>` — the wrapper's `size` carries density. Defaults to `'md'`. */\n readonly size = input<TwSize>('md');\n\n /** Disables the control. Also reflects `ngControl.disabled` when the element is bound to a reactive form. Defaults to `false`. */\n readonly disabledInput = input<boolean, unknown>(false, {\n alias: 'disabled',\n transform: booleanAttribute,\n });\n\n /** Marks the control as required. Also inferred from `Validators.required` on a bound `NgControl`. Defaults to `false`. */\n readonly requiredInput = input<boolean, unknown>(false, {\n alias: 'required',\n transform: booleanAttribute,\n });\n\n /** Makes the control read-only (native `readonly` attribute). Defaults to `false`. */\n readonly readonlyInput = input<boolean, unknown>(false, {\n alias: 'readonly',\n transform: booleanAttribute,\n });\n\n /** Per-instance override of the {@link ErrorStateMatcher}. When omitted, the directive uses the `TW_ERROR_STATE_MATCHER` token's value. */\n readonly errorStateMatcher = input<ErrorStateMatcher | undefined>(undefined);\n\n /** Consumer-supplied `aria-describedby` ids. The form-field preserves these when merging hint and error ids. Alias: `aria-describedby`. */\n override readonly userAriaDescribedBy = input<string | undefined>(undefined, {\n alias: 'aria-describedby',\n });\n\n /** Consumer-supplied `aria-labelledby` ids. The form-field preserves these when merging in the projected label id. Alias: `aria-labelledby`. */\n override readonly userAriaLabelledby = input<string | undefined>(undefined, {\n alias: 'aria-labelledby',\n });\n\n // ── Internal state signals ──\n\n private readonly _value = signal<string>(this._readInitialValue());\n private readonly _focused = signal(false);\n private readonly _autofilled = signal(false);\n private readonly _ngControlRev = signal(0);\n private readonly _formSubmitRev = signal(0);\n\n // ── FormFieldControl signals ──\n\n /** @internal */\n readonly id = computed(() => this.idInput() ?? this.uid);\n\n /** @internal */\n readonly value = computed<string | null>(() => this._value() || null);\n\n /** @internal */\n readonly focused = this._focused.asReadonly();\n\n /** @internal */\n readonly empty = computed(() => {\n if (!this._isTextarea && NEVER_EMPTY_TYPES.has(this.type())) {\n return false;\n }\n return !this._value() && !this._autofilled();\n });\n\n /** @internal */\n readonly disabled = computed(() => {\n this._ngControlRev();\n return this.disabledInput() || !!this.ngControl?.disabled;\n });\n\n /** @internal */\n readonly required = computed(() => {\n this._ngControlRev();\n if (this.requiredInput()) return true;\n return !!this.ngControl?.control?.hasValidator(Validators.required);\n });\n\n /** @internal */\n readonly errorState = computed(() => {\n this._ngControlRev();\n this._formSubmitRev();\n this._focused();\n const matcher = this.errorStateMatcher() ?? this.defaultMatcher;\n const form: TwFormSubmitted | null =\n (this.parentFormGroup as TwFormSubmitted | null) ??\n (this.parentForm as TwFormSubmitted | null);\n return matcher.isErrorState(this.ngControl?.control ?? null, form);\n });\n\n /** @internal Active validation errors map from the bound `NgControl` (or `null` when the control reports none / is unbound). Recomputes on every `_ngControlRev` tick so `[twError match=\"…\"]` reacts to validator transitions, including rules that fire/clear without flipping `VALID`/`INVALID`. */\n override readonly errors = computed<Record<string, unknown> | null>(() => {\n this._ngControlRev();\n return (this.ngControl?.control?.errors as Record<string, unknown> | null) ?? null;\n });\n\n /** @internal */\n readonly classes = computed(() =>\n inputVariants({\n inFormField: !!this.formField,\n size: this.size(),\n errorState: this.errorState(),\n disabled: this.disabled(),\n }),\n );\n\n constructor() {\n super();\n\n this.focusMonitor\n .monitor(this.elementRef)\n .pipe(takeUntilDestroyed())\n .subscribe((origin) => {\n const wasFocused = this._focused();\n this._focused.set(!!origin);\n if (wasFocused && !origin) {\n // Blur often flips `touched` on the bound `NgControl`; bump the\n // revision signal so `errorState` recomputes.\n this._ngControlRev.update((n) => n + 1);\n }\n });\n this.destroyRef.onDestroy(() =>\n this.focusMonitor.stopMonitoring(this.elementRef),\n );\n\n // Keep `_value` in sync when the accessor's value is itself a signal.\n if (this.valueAccessor && isSignal(this.valueAccessor.value)) {\n effect(() => {\n const v = (this.valueAccessor!.value as Signal<unknown>)();\n untracked(() => this._value.set(this._stringify(v)));\n });\n }\n\n // Sync the `readonly` input to the native attribute.\n effect(() => {\n const el = this.elementRef.nativeElement;\n if (this.readonlyInput()) {\n el.setAttribute('readonly', 'true');\n } else {\n el.removeAttribute('readonly');\n }\n });\n\n // Validate the input type in dev mode.\n effect(() => {\n const t = this.type();\n if (\n isDevMode() &&\n !this._isTextarea &&\n INVALID_INPUT_TYPES.has(t)\n ) {\n throw new Error(\n `tw-input: unsupported input type \"${t}\". Use a dedicated ngx-tw component for that control.`,\n );\n }\n });\n }\n\n ngOnInit(): void {\n if (this.platform.isBrowser) {\n this.autofillMonitor\n .monitor(this.elementRef.nativeElement)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((event) => this._autofilled.set(event.isAutofilled));\n this.destroyRef.onDestroy(() =>\n this.autofillMonitor.stopMonitoring(this.elementRef.nativeElement),\n );\n }\n\n // NgControl's `control` is set by the parent `FormControl*` directive's\n // own `ngOnChanges`, which runs before children's `ngOnInit`. Subscribe\n // here so `statusChanges`/`valueChanges` are actually available.\n const ctrl = this.ngControl?.control;\n if (ctrl) {\n const streams = [ctrl.statusChanges, ctrl.valueChanges].filter(\n (s): s is NonNullable<typeof s> => !!s,\n );\n if (streams.length) {\n merge(...streams)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this._ngControlRev.update((n) => n + 1));\n }\n }\n\n const submit = this.parentFormGroup?.ngSubmit ?? this.parentForm?.ngSubmit;\n if (submit) {\n submit\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe(() => this._formSubmitRev.update((n) => n + 1));\n }\n }\n\n /** Moves focus to the underlying element. */\n focus(options?: FocusOptions): void {\n this.elementRef.nativeElement.focus(options);\n }\n\n /** @internal Called when `<tw-form-field>` has computed the merged describedby ids. */\n setDescribedByIds(ids: string[]): void {\n const el = this.elementRef.nativeElement;\n if (ids.length) {\n el.setAttribute('aria-describedby', ids.join(' '));\n } else {\n el.removeAttribute('aria-describedby');\n }\n }\n\n /** @internal Called when `<tw-form-field>` has computed the merged labelledby ids. The native `<label for>` association covers most cases; this attribute is mainly useful when a consumer also supplies `aria-labelledby` for an external label. */\n override setLabelledByIds(ids: string[]): void {\n const el = this.elementRef.nativeElement;\n // Skip applying the projected label id by itself — native `<label for=>` already wires it\n // and a redundant `aria-labelledby` can cause double-announcements in some screen readers.\n // Only apply when the consumer supplied additional ids via `aria-labelledby`.\n const userIds = this.userAriaLabelledby();\n if (userIds || (ids.length > 1)) {\n el.setAttribute('aria-labelledby', ids.join(' '));\n } else {\n el.removeAttribute('aria-labelledby');\n }\n }\n\n /** @internal Called when the form-field container is clicked. */\n onContainerClick(_event: MouseEvent): void {\n if (!this._focused()) {\n this.focus();\n }\n }\n\n /** @internal */\n _onInput(): void {\n this._value.set(this._currentValue());\n }\n\n private _currentValue(): string {\n if (this.valueAccessor) {\n const v = this.valueAccessor.value;\n if (isSignal(v)) return this._stringify((v as Signal<unknown>)());\n return this._stringify(v);\n }\n return this.elementRef.nativeElement.value;\n }\n\n private _readInitialValue(): string {\n const el = this.elementRef.nativeElement;\n return el.value ?? '';\n }\n\n private _stringify(v: unknown): string {\n return v === null || v === undefined ? '' : String(v);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cdevhub-ngx-tw-input.mjs","sources":["../../../projects/ngx-tw/input/input.ts","../../../projects/ngx-tw/input/cdevhub-ngx-tw-input.ts"],"sourcesContent":["import {\n booleanAttribute,\n computed,\n DestroyRef,\n Directive,\n effect,\n ElementRef,\n inject,\n InjectionToken,\n input,\n isDevMode,\n isSignal,\n type OnInit,\n signal,\n type Signal,\n untracked,\n type WritableSignal,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { NgControl } from '@angular/forms';\nimport { FocusMonitor } from '@angular/cdk/a11y';\nimport { AutofillMonitor } from '@angular/cdk/text-field';\nimport { Platform } from '@angular/cdk/platform';\nimport { tv } from 'tailwind-variants';\nimport {\n type ErrorStateMatcher,\n type TwSize,\n wireErrorState,\n} from '@cdevhub/ngx-tw/core';\nimport {\n FormFieldControl,\n TW_FORM_FIELD,\n TW_FORM_FIELD_CONTROL,\n} from '@cdevhub/ngx-tw/form-field';\n\n/**\n * Extension point for directives that wrap an `<input twInput>` and need to\n * own how its value is read and written. The directive's `value` property can\n * be a plain object slot (`{ value: T }`) or a `WritableSignal<T>`. Mirrors\n * Angular Material's `MAT_INPUT_VALUE_ACCESSOR`.\n */\nexport const TW_INPUT_VALUE_ACCESSOR = new InjectionToken<{\n value: unknown | WritableSignal<unknown>;\n}>('TW_INPUT_VALUE_ACCESSOR');\n\nconst INVALID_INPUT_TYPES = new Set([\n 'button',\n 'checkbox',\n 'file',\n 'hidden',\n 'image',\n 'radio',\n 'range',\n 'reset',\n 'submit',\n]);\n\nconst NEVER_EMPTY_TYPES = new Set([\n 'date',\n 'datetime-local',\n 'month',\n 'time',\n 'week',\n 'color',\n]);\n\nconst inputVariants = tv(\n {\n base: 'block w-full bg-transparent text-fg placeholder:text-fg-subtle outline-none',\n variants: {\n inFormField: {\n true: 'border-0 p-0 shadow-none focus:outline-none focus-visible:outline-none',\n false:\n 'rounded-md border border-border transition-[color,border-color,box-shadow] duration-normal motion-reduce:transition-none hover:border-border-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-500',\n },\n // Height strategy for the standalone (`inFormField: false`) size scale —\n // see `docs/vertical-rhythm.md`. Driven by the host tag, not an input.\n //\n // `false` (a single-line `<input>`) pins the border-box height\n // (`h-6` … `h-12`) and deletes the vertical padding, so padding and\n // height cannot fight. Browsers centre an `<input>`'s text natively, so\n // no flex box is needed.\n //\n // `true` (a `<textarea>`) is multi-line by definition: the vertical\n // padding is retained and no height utility is emitted here. The\n // control-scale `min-h-*` floor is applied by `TextareaDirective`\n // instead, because it has to be droppable while CDK autosize owns the\n // height — see the `heightFloor` variant in `textarea.ts`.\n multiline: {\n true: '',\n false: '',\n },\n size: {\n xs: '',\n sm: '',\n md: '',\n lg: '',\n xl: '',\n },\n errorState: {\n true: '',\n false: '',\n },\n disabled: {\n true: 'opacity-50 cursor-not-allowed',\n false: '',\n },\n },\n compoundVariants: [\n // Standalone density × size — single-line: horizontal padding, font\n // scale, and the pinned control height (no vertical padding).\n { inFormField: false, multiline: false, size: 'xs', class: 'px-2 text-xs h-6' },\n { inFormField: false, multiline: false, size: 'sm', class: 'px-3 text-sm h-8' },\n { inFormField: false, multiline: false, size: 'md', class: 'px-4 text-sm h-9' },\n { inFormField: false, multiline: false, size: 'lg', class: 'px-5 text-base h-11' },\n { inFormField: false, multiline: false, size: 'xl', class: 'px-6 text-base h-12' },\n // Standalone density × size — multi-line: vertical padding is kept so the\n // box can grow; the height floor is applied by `TextareaDirective`.\n { inFormField: false, multiline: true, size: 'xs', class: 'px-2 py-1 text-xs' },\n { inFormField: false, multiline: true, size: 'sm', class: 'px-3 py-1.5 text-sm' },\n { inFormField: false, multiline: true, size: 'md', class: 'px-4 py-2 text-sm' },\n { inFormField: false, multiline: true, size: 'lg', class: 'px-5 py-2.5 text-base' },\n { inFormField: false, multiline: true, size: 'xl', class: 'px-6 py-3 text-base' },\n // Standalone error state — colored border and focus ring.\n {\n inFormField: false,\n errorState: true,\n class: 'border-error-500 focus-visible:outline-error-500',\n },\n ],\n defaultVariants: {\n inFormField: false,\n multiline: false,\n size: 'md',\n errorState: false,\n disabled: false,\n },\n },\n { twMerge: true },\n);\n\nlet nextInputId = 0;\n\n/**\n * Adapts a native `<input>` or `<textarea>` into an ngx-tw form-field-compatible\n * control. Works standalone (applies its own border and focus styling) or\n * inside a `<tw-form-field>` (strips its chrome and provides signals the\n * form-field uses to float the label and wire `aria-describedby`).\n *\n * Extension points:\n * - Implement a completely custom control → extend {@link FormFieldControl}\n * and provide under `TW_FORM_FIELD_CONTROL`.\n * - Swap value storage for an existing `<input twInput>` (masked input,\n * date parser, etc.) → provide {@link TW_INPUT_VALUE_ACCESSOR}.\n * - Change when errors show → override `TW_ERROR_STATE_MATCHER` at any\n * injector level, or pass `errorStateMatcher` per instance.\n *\n * Forms integration is inherited: Angular's native value accessors attach to\n * the underlying element, so the directive works with template-driven,\n * reactive, and signal-based forms without additional glue.\n */\n@Directive({\n selector: 'input[twInput], textarea[twInput]',\n exportAs: 'twInput',\n providers: [\n { provide: TW_FORM_FIELD_CONTROL, useExisting: InputDirective },\n ],\n host: {\n '[class]': 'classes()',\n '[attr.id]': 'id()',\n '[attr.type]': '_isTextarea ? null : type()',\n '[disabled]': 'disabled()',\n '[attr.aria-invalid]': 'errorState() || null',\n '[attr.aria-required]': 'required() || null',\n '(input)': '_onInput()',\n },\n})\nexport class InputDirective\n extends FormFieldControl<string>\n implements OnInit\n{\n private readonly elementRef = inject<\n ElementRef<HTMLInputElement | HTMLTextAreaElement>\n >(ElementRef);\n private readonly focusMonitor = inject(FocusMonitor);\n private readonly autofillMonitor = inject(AutofillMonitor);\n private readonly platform = inject(Platform);\n private readonly destroyRef = inject(DestroyRef);\n private readonly formField = inject(TW_FORM_FIELD, { optional: true });\n private readonly ngControl = inject(NgControl, {\n optional: true,\n self: true,\n });\n private readonly valueAccessor = inject(TW_INPUT_VALUE_ACCESSOR, {\n optional: true,\n self: true,\n });\n\n /** @internal */\n readonly _isTextarea =\n this.elementRef.nativeElement.tagName.toLowerCase() === 'textarea';\n\n /** Contract value — `'input'` for `<input>` and `'textarea'` for `<textarea>`. Form-field appends it as `tw-form-field-type-*` for styling hooks. */\n readonly controlType: string = this._isTextarea ? 'textarea' : 'input';\n\n private readonly uid = `tw-input-${nextInputId++}`;\n\n /** Id on the underlying element. Auto-generated as `tw-input-N` when not provided. Used by the form-field's `<label for>` attribute. */\n readonly idInput = input<string | undefined>(undefined, { alias: 'id' });\n\n /** Native HTML input `type`. Defaults to `'text'`. Dev-mode throws on unsupported values (`checkbox`, `radio`, `submit`, etc.) — use the dedicated component instead. Ignored on `<textarea>`. */\n readonly type = input<string>('text');\n\n /** Density of a standalone field. Maps to the pinned control-height scale (24/32/36/44/48px, `h-6` xs … `h-12` xl), the horizontal-padding scale (`px-2` xs … `px-6` xl), and the font scale (`text-xs` xs, `text-sm` sm/md, `text-base` lg/xl). On a `<textarea>` the height becomes a `min-h-*` floor and the vertical padding is retained. Ignored inside a `<tw-form-field>` — the wrapper's `size` carries density. Defaults to `'md'`. */\n readonly size = input<TwSize>('md');\n\n /** Disables the control. Also reflects `ngControl.disabled` when the element is bound to a reactive form. Defaults to `false`. */\n readonly disabledInput = input<boolean, unknown>(false, {\n alias: 'disabled',\n transform: booleanAttribute,\n });\n\n /** Marks the control as required. Also inferred from `Validators.required` on a bound `NgControl`. Defaults to `false`. */\n readonly requiredInput = input<boolean, unknown>(false, {\n alias: 'required',\n transform: booleanAttribute,\n });\n\n /** Makes the control read-only (native `readonly` attribute). Defaults to `false`. */\n readonly readonlyInput = input<boolean, unknown>(false, {\n alias: 'readonly',\n transform: booleanAttribute,\n });\n\n /** Per-instance override of the {@link ErrorStateMatcher}. When omitted, the directive uses the `TW_ERROR_STATE_MATCHER` token's value. */\n readonly errorStateMatcher = input<ErrorStateMatcher | undefined>(undefined);\n\n /** Consumer-supplied `aria-describedby` ids. The form-field preserves these when merging hint and error ids. Alias: `aria-describedby`. */\n override readonly userAriaDescribedBy = input<string | undefined>(undefined, {\n alias: 'aria-describedby',\n });\n\n /** Consumer-supplied `aria-labelledby` ids. The form-field preserves these when merging in the projected label id. Alias: `aria-labelledby`. */\n override readonly userAriaLabelledby = input<string | undefined>(undefined, {\n alias: 'aria-labelledby',\n });\n\n // ── Internal state signals ──\n\n private readonly _value = signal<string>(this._readInitialValue());\n private readonly _focused = signal(false);\n private readonly _autofilled = signal(false);\n\n /** @internal Shared `errorState` / `required` / `errors` derivation — see `wireErrorState`. */\n private readonly errorWiring = wireErrorState({\n ngControl: () => this.ngControl,\n matcher: () => this.errorStateMatcher(),\n required: () => this.requiredInput(),\n track: [() => this._focused()],\n });\n\n // ── FormFieldControl signals ──\n\n /** @internal */\n readonly id = computed(() => this.idInput() ?? this.uid);\n\n /** @internal */\n readonly value = computed<string | null>(() => this._value() || null);\n\n /** @internal */\n readonly focused = this._focused.asReadonly();\n\n /** @internal */\n readonly empty = computed(() => {\n if (!this._isTextarea && NEVER_EMPTY_TYPES.has(this.type())) {\n return false;\n }\n return !this._value() && !this._autofilled();\n });\n\n /** @internal */\n readonly disabled = computed(() => {\n this.errorWiring.rev();\n return this.disabledInput() || !!this.ngControl?.disabled;\n });\n\n /** @internal */\n readonly required = this.errorWiring.required;\n\n /** @internal */\n readonly errorState = this.errorWiring.errorState;\n\n /** @internal Active validation errors map from the bound `NgControl`, for `[twError match=\"…\"]` filtering. */\n override readonly errors = this.errorWiring.errors;\n\n /** @internal */\n readonly classes = computed(() =>\n inputVariants({\n inFormField: !!this.formField,\n multiline: this._isTextarea,\n size: this.size(),\n errorState: this.errorState(),\n disabled: this.disabled(),\n }),\n );\n\n constructor() {\n super();\n\n this.focusMonitor\n .monitor(this.elementRef)\n .pipe(takeUntilDestroyed())\n .subscribe((origin) => {\n const wasFocused = this._focused();\n this._focused.set(!!origin);\n if (wasFocused && !origin) {\n // Blur often flips `touched` on the bound `NgControl`; bump the\n // revision signal so `errorState` recomputes.\n this.errorWiring.bump();\n }\n });\n this.destroyRef.onDestroy(() =>\n this.focusMonitor.stopMonitoring(this.elementRef),\n );\n\n // Keep `_value` in sync when the accessor's value is itself a signal.\n if (this.valueAccessor && isSignal(this.valueAccessor.value)) {\n effect(() => {\n const v = (this.valueAccessor!.value as Signal<unknown>)();\n untracked(() => this._value.set(this._stringify(v)));\n });\n }\n\n // Sync the `readonly` input to the native attribute.\n effect(() => {\n const el = this.elementRef.nativeElement;\n if (this.readonlyInput()) {\n el.setAttribute('readonly', 'true');\n } else {\n el.removeAttribute('readonly');\n }\n });\n\n // Validate the input type in dev mode.\n effect(() => {\n const t = this.type();\n if (\n isDevMode() &&\n !this._isTextarea &&\n INVALID_INPUT_TYPES.has(t)\n ) {\n throw new Error(\n `tw-input: unsupported input type \"${t}\". Use a dedicated ngx-tw component for that control.`,\n );\n }\n });\n }\n\n ngOnInit(): void {\n if (this.platform.isBrowser) {\n this.autofillMonitor\n .monitor(this.elementRef.nativeElement)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe((event) => this._autofilled.set(event.isAutofilled));\n this.destroyRef.onDestroy(() =>\n this.autofillMonitor.stopMonitoring(this.elementRef.nativeElement),\n );\n }\n\n // NgControl's `control` is set by the parent `FormControl*` directive's\n // own `ngOnChanges`, which runs before children's `ngOnInit`. Connect\n // here so `statusChanges`/`valueChanges` are actually available.\n this.errorWiring.connect();\n }\n\n /** Moves focus to the underlying element. */\n focus(options?: FocusOptions): void {\n this.elementRef.nativeElement.focus(options);\n }\n\n /** @internal Called when `<tw-form-field>` has computed the merged describedby ids. */\n setDescribedByIds(ids: string[]): void {\n const el = this.elementRef.nativeElement;\n if (ids.length) {\n el.setAttribute('aria-describedby', ids.join(' '));\n } else {\n el.removeAttribute('aria-describedby');\n }\n }\n\n /** @internal Called when `<tw-form-field>` has computed the merged labelledby ids. The native `<label for>` association covers most cases; this attribute is mainly useful when a consumer also supplies `aria-labelledby` for an external label. */\n override setLabelledByIds(ids: string[]): void {\n const el = this.elementRef.nativeElement;\n // Skip applying the projected label id by itself — native `<label for=>` already wires it\n // and a redundant `aria-labelledby` can cause double-announcements in some screen readers.\n // Only apply when the consumer supplied additional ids via `aria-labelledby`.\n const userIds = this.userAriaLabelledby();\n if (userIds || (ids.length > 1)) {\n el.setAttribute('aria-labelledby', ids.join(' '));\n } else {\n el.removeAttribute('aria-labelledby');\n }\n }\n\n /** @internal Called when the form-field container is clicked. */\n onContainerClick(_event: MouseEvent): void {\n if (!this._focused()) {\n this.focus();\n }\n }\n\n /** @internal */\n _onInput(): void {\n this._value.set(this._currentValue());\n }\n\n private _currentValue(): string {\n if (this.valueAccessor) {\n const v = this.valueAccessor.value;\n if (isSignal(v)) return this._stringify((v as Signal<unknown>)());\n return this._stringify(v);\n }\n return this.elementRef.nativeElement.value;\n }\n\n private _readInitialValue(): string {\n const el = this.elementRef.nativeElement;\n return el.value ?? '';\n }\n\n private _stringify(v: unknown): string {\n return v === null || v === undefined ? '' : String(v);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -16,16 +16,29 @@ const itemVariants = tv({
16
16
  trailing: 'flex shrink-0',
17
17
  },
18
18
  variants: {
19
+ // `tw-item` is on the `min-h-*` list (docs/vertical-rhythm.md §2), not the
20
+ // pinned one: a title + description is a two-line stack, so the box must
21
+ // stay free to grow. `py-*` is therefore kept exactly as it is.
22
+ //
23
+ // The three-step `ItemSize` axis maps onto the control scale by its own
24
+ // single-line natural height, so each floor binds at the size it belongs
25
+ // to rather than being decorative:
26
+ // sm → 32 (`min-h-8`) — py-1.5 (12) + text-sm line box (20)
27
+ // md → 36 (`min-h-9`) — py-2 (16) + text-sm line box (20)
28
+ // lg → 48 (`min-h-12`) — py-3 (24) + text-base line box (24)
29
+ // The scale's 24px (`xs`) step sits below `tw-item`'s densest row and its
30
+ // 44px (`lg`) step sits below the section-header line box, so both are
31
+ // skipped: `lg` takes the 48px step it already measures.
19
32
  size: {
20
33
  sm: {
21
- root: 'gap-2 py-1.5',
34
+ root: 'gap-2 py-1.5 min-h-8',
22
35
  content: 'gap-0',
23
36
  title: 'text-sm truncate',
24
37
  description: 'text-xs truncate',
25
38
  trailing: 'gap-1.5',
26
39
  },
27
40
  md: {
28
- root: 'gap-3 py-2',
41
+ root: 'gap-3 py-2 min-h-9',
29
42
  leading: 'mt-0.5',
30
43
  content: 'gap-1',
31
44
  title: 'text-sm',
@@ -33,7 +46,7 @@ const itemVariants = tv({
33
46
  trailing: 'gap-2',
34
47
  },
35
48
  lg: {
36
- root: 'gap-3 py-3',
49
+ root: 'gap-3 py-3 min-h-12',
37
50
  leading: 'mt-0.5',
38
51
  content: 'gap-1',
39
52
  title: 'text-base font-semibold',
@@ -66,7 +79,16 @@ const itemVariants = tv({
66
79
  },
67
80
  current: {
68
81
  true: {
69
- root: 'bg-primary-soft ring-2 ring-inset ring-primary-border',
82
+ // `-border-strong`, not `-border`. This ring IS the selection
83
+ // indicator, so WCAG 2.2 SC 1.4.11 applies to it against the
84
+ // `bg-primary-soft` fill it sits on — and the subtle tier does not
85
+ // clear 3:1 there in every scheme (measured 2026-09-04: 7 of 8 roles
86
+ // failed in `light`, 4 of 8 in `dark`). The theme layer cannot fix
87
+ // that: raising `-border` far enough collapses it onto
88
+ // `-border-strong`, which is the tier already sized for rings.
89
+ // `theme-contrast.spec.ts` asserts the `-border-strong`-on-`-soft`
90
+ // pairing for every role in every scheme.
91
+ root: 'bg-primary-soft ring-2 ring-inset ring-primary-border-strong',
70
92
  },
71
93
  false: {},
72
94
  },
@@ -134,11 +156,13 @@ class ItemComponent {
134
156
  this.focusMonitor.stopMonitoring(this.elementRef);
135
157
  });
136
158
  }
159
+ /** @internal Emits `selected` on click when the item is interactive and enabled. */
137
160
  onActivate(event) {
138
161
  if (!this.interactive() || this.disabled())
139
162
  return;
140
163
  this.selected.emit(event);
141
164
  }
165
+ /** @internal Emits `selected` on Enter/Space when the item is interactive and enabled. */
142
166
  onKeydown(event) {
143
167
  if (!this.interactive() || this.disabled())
144
168
  return;