@cdevhub/ngx-tw 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +18 -3
  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 +110 -36
  140. package/theme/_high-contrast-dark.css +367 -0
  141. package/theme/_high-contrast.css +13 -2
  142. package/theme/_light.css +276 -0
  143. package/theme/_semantic.css +47 -12
  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
@@ -22,10 +22,31 @@
22
22
  --color-fg-muted: var(--color-gray-600);
23
23
  --color-fg-subtle: var(--color-gray-500);
24
24
 
25
- /* ===== Border ===== */
26
- --color-border: var(--color-gray-300);
25
+ /* ===== Border =====
26
+ `--color-border` and `--color-border-strong` carry the boundary that
27
+ IDENTIFIES a control — the outline of an input, a checkbox, a select
28
+ trigger. WCAG 2.2 SC 1.4.11 (Non-text Contrast) requires 3:1 for exactly
29
+ that, and for an unchecked checkbox the border IS the component. Measured
30
+ against `--color-surface`, gray-300 gave 1.47:1 and gray-400 gave 2.60:1,
31
+ so both failed; the values below are the first palette steps that clear
32
+ 3:1 (4.84:1 and 7.56:1 in light, 4.16:1 and 7.73:1 in dark).
33
+ (The dark `-strong` figure read 8.59 until 2026-09-03; measured it is
34
+ 7.73, and no plausible pairing reproduces 8.59 — gray-400 on gray-900 is
35
+ 6.82. The verdict is unchanged: it was and is the first step clearing 3:1.
36
+ Re-measure with `scratchpad/p6-pairs.mjs` rather than estimating.)
37
+
38
+ `--color-border-muted` is deliberately NOT raised. It is the decorative
39
+ divider — ghost-variant footers, subtle rules between rows — and SC 1.4.11
40
+ exempts boundaries that are purely decorative and not needed to identify a
41
+ component or its state. Raising it would darken every hairline in the
42
+ library for no accessibility gain.
43
+
44
+ axe does not catch any of this: its `color-contrast` rule tests TEXT only,
45
+ so these three tokens were invisible to the a11y sweep and were measured
46
+ by hand. See docs/audit-2026-09-register.md. */
47
+ --color-border: var(--color-gray-500);
27
48
  --color-border-muted: var(--color-gray-200);
28
- --color-border-strong: var(--color-gray-400);
49
+ --color-border-strong: var(--color-gray-600);
29
50
 
30
51
  /* ===== Table sticky shadows =====
31
52
  Hairline 1px shadows used to separate sticky table chrome from scrolled
@@ -36,15 +57,6 @@
36
57
  --shadow-table-sticky-cell-start: 1px 0 0 0 var(--color-border);
37
58
  --shadow-table-sticky-cell-end: -1px 0 0 0 var(--color-border);
38
59
 
39
- /* ===== Calendar widths =====
40
- Pinned per size so month / year / multi-year views share the same footprint
41
- across view transitions. Each value is roughly `cell-size × 7 + gutters`. */
42
- --width-calendar-xs: 210px;
43
- --width-calendar-sm: 240px;
44
- --width-calendar-md: 292px;
45
- --width-calendar-lg: 320px;
46
- --width-calendar-xl: 376px;
47
-
48
60
  /* ===== Extra-small typography =====
49
61
  Used for xs-density secondary text (xs descriptions, calendar cell labels,
50
62
  keyboard hints). Sits one step below Tailwind's `text-xs` (0.75rem).
@@ -176,6 +188,29 @@
176
188
  -solid-fg text on -solid (AA body)
177
189
  -border subtle border (outline variant, dividers)
178
190
  -border-strong accent border (indicators, rings)
191
+
192
+ `-border` reads as "subtle", but it is NOT decorative the way
193
+ `--color-border-muted` is: it is the alert `outline` variant's only
194
+ boundary (`alert.ts:50`, transparent background), so SC 1.4.11's 3:1
195
+ applies to it. The four schemes currently disagree about whether they
196
+ honour that, and the disagreement is deliberate, not drift:
197
+
198
+ scheme {role}-border on surface 3:1?
199
+ light {role}-300 1.40 – 1.92 ✗ (all 7 roles)
200
+ dark see `_dark.css` 3.42 – 4.22 ✓ (raised 2026-09-03)
201
+ high-contrast {hue}-700 4.94 – 10.34 ✓
202
+ high-contrast-dark {hue}-300 10.93 – 15.02 ✓
203
+
204
+ The ✓/✗ column is ENFORCED — `theme-contrast.spec.ts` recomputes it for
205
+ every scheme on every run, and `light` is its one dated allowance. The
206
+ ranges and step names are indicative and were measured on 2026-09-03; if
207
+ you need a current number, re-run the harness rather than quoting this.
208
+
209
+ Light is the outstanding one. Raising it means darkening the default
210
+ scheme's outline tier across eight colours — a visual-baseline change
211
+ with its own blast radius, reported for a pass of its own rather than
212
+ folded into the dark fix. Re-measure any row here with
213
+ `scratchpad/p6-contrast.mjs <scheme> <baseline>`; do not estimate.
179
214
  -fg colored text on neutral surface (tab labels, links)
180
215
  -icon icon tint on -soft / surface contexts
181
216
 
@@ -3,6 +3,23 @@
3
3
  --font-sans: 'Inter', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji';
4
4
  --font-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Code', 'Source Code Pro', monospace;
5
5
 
6
+ /* ===== Font sizes — line-heights pinned in `rem`, deliberately =====
7
+ Tailwind's defaults are UNITLESS ratios (e.g. `--text-sm--line-height:
8
+ calc(1.25 / 0.875)`). A consumer who re-units `--text-sm` therefore
9
+ rescales every line box, which silently overflows controls whose height is
10
+ pinned. Absolute values make the line box a fixed 4px multiple that cannot
11
+ be rescaled out from under a pinned height.
12
+
13
+ These reproduce Tailwind's own COMPUTED defaults exactly (16 / 20 / 24px),
14
+ so the visual diff of adopting them is zero. This is robustness, not a
15
+ redesign — do not "simplify" them back to ratios. */
16
+ --text-xs: 0.75rem;
17
+ --text-xs--line-height: 1rem; /* 12 / 16 */
18
+ --text-sm: 0.875rem;
19
+ --text-sm--line-height: 1.25rem; /* 14 / 20 */
20
+ --text-base: 1rem;
21
+ --text-base--line-height: 1.5rem; /* 16 / 24 */
22
+
6
23
  /* ===== Animation durations ===== */
7
24
  --duration-fast: 150ms;
8
25
  --duration-normal: 200ms;
package/theme/index.css CHANGED
@@ -3,23 +3,85 @@
3
3
  /* ===== Dark variant binding =====
4
4
  Re-binds Tailwind's `dark:` variant so it activates on the same condition
5
5
  that flips the semantic tokens in `_dark.css`:
6
- 1. an explicit `[data-theme="dark"]` ancestor, OR
7
- 2. `prefers-color-scheme: dark` AND no explicit `data-theme` opt-out.
6
+ 1. an explicit `[data-theme="dark"]` or `[data-theme="high-contrast-dark"]`
7
+ ancestor, OR
8
+ 2. `prefers-color-scheme: dark` AND no explicit `data-theme` opt-out,
9
+ and in BOTH cases only while no nearer `[data-theme="light"]` /
10
+ `[data-theme="high-contrast"]` ancestor has opted the subtree back out.
11
+
12
+ `high-contrast-dark` counts as dark here because it IS dark — a subtree
13
+ scoped to it must keep the same `dark:` utilities a `dark` subtree gets, or
14
+ consumer overrides silently stop applying inside it.
8
15
 
9
16
  Without this binding `dark:` defaults to `@media (prefers-color-scheme: dark)`,
10
17
  which drifts from the `[data-theme="dark"]` selector — producing inverted
11
18
  token state but un-inverted dark utilities (or vice versa) whenever OS
12
- preference disagrees with the consumer's chosen theme. Keep this rule in
13
- lock-step with the activation predicates in `_dark.css`. */
19
+ preference disagrees with the consumer's chosen theme.
20
+
21
+ Keep this rule in lock-step with the activation predicates in `_dark.css`
22
+ and `_light.css` — with ONE deliberate divergence, stated here so a later
23
+ pass does not "fix" it into a bug. `_dark.css`'s media branch excludes
24
+ `[data-theme="high-contrast-dark"]` roots, because a root asking for the
25
+ dark high-contrast TOKENS must not be handed the plain dark ones. The media
26
+ branch below does NOT exclude them, because a root asking for dark high
27
+ contrast is still asking for dark, and `dark:` utilities must keep applying
28
+ there. The two predicates answer different questions — "which token set" and
29
+ "is this dark" — and only agree on three of the four schemes.
30
+
31
+ The `:not(:where(…))` exclusions carry zero specificity, so they cannot
32
+ change how a consumer's own `dark:` utility resolves against their other
33
+ rules — they only narrow *when* it applies. Both branches need one, but the
34
+ two are deliberately NOT the same shape:
35
+
36
+ - The media branch excludes any `[data-theme="light"]` /
37
+ `[data-theme="high-contrast"]` ancestor outright. Re-entering dark below
38
+ such a subtree is still handled, because the explicit branch above matches
39
+ it independently.
40
+ - The explicit branch can only exclude a light/high-contrast boundary that
41
+ sits *below* a dark one (`[data-theme="dark"] [data-theme="light"] *`).
42
+ The plain form would also strip `dark:` from a dark subtree nested inside
43
+ a light one, where it is correct — CSS has no "nearest themed ancestor"
44
+ combinator, and inherited custom properties get that semantic for free
45
+ while selectors do not. The residual gap is depth-3 alternation
46
+ (dark → light → dark), which is no worse than before this rule existed.
47
+
48
+ With two dark schemes and two light ones that exclusion is a 2×2 product,
49
+ so it is written with nested `:where()` groups rather than four spelled-out
50
+ descendant pairs. `:where()` contributes zero specificity and the whole
51
+ thing already sits inside a zero-specificity `:not(:where(…))`, so the
52
+ grouped form is exactly equivalent to the enumeration — just not four times
53
+ as long.
54
+
55
+ The library ships no `dark:` variants of its own, so this is consumer-facing
56
+ only. */
14
57
  @custom-variant dark {
15
- &:where([data-theme="dark"], [data-theme="dark"] *) {
58
+ &:where(
59
+ [data-theme="dark"],
60
+ [data-theme="dark"] *,
61
+ [data-theme="high-contrast-dark"],
62
+ [data-theme="high-contrast-dark"] *
63
+ ):not(
64
+ :where(
65
+ :where([data-theme="dark"], [data-theme="high-contrast-dark"])
66
+ :where([data-theme="light"], [data-theme="high-contrast"]),
67
+ :where([data-theme="dark"], [data-theme="high-contrast-dark"])
68
+ :where([data-theme="light"], [data-theme="high-contrast"]) *
69
+ )
70
+ ) {
16
71
  @slot;
17
72
  }
18
73
  @media (prefers-color-scheme: dark) {
19
74
  &:where(
20
75
  :root:not([data-theme="light"]):not([data-theme="high-contrast"]),
21
76
  :root:not([data-theme="light"]):not([data-theme="high-contrast"]) *
22
- ) {
77
+ ):not(
78
+ :where(
79
+ [data-theme="light"],
80
+ [data-theme="light"] *,
81
+ [data-theme="high-contrast"],
82
+ [data-theme="high-contrast"] *
83
+ )
84
+ ) {
23
85
  @slot;
24
86
  }
25
87
  }
@@ -54,8 +116,25 @@
54
116
  /* Semantic token definitions */
55
117
  @import "./_semantic.css";
56
118
 
57
- /* Theme overrides */
119
+ /* Theme overrides.
120
+ `_high-contrast.css` must stay LAST of the four, and every other scheme file
121
+ before it. Each scheme's own block is a bare `[data-theme=…]` at specificity
122
+ 0,1,0, and `_high-contrast.css`'s `@media (forced-colors: active)` list
123
+ names all four of them at that same 0,1,0 — so source order is the only
124
+ tie-breaker and the forced-colors remap has to come last to win. That is why
125
+ `_light.css` leads and why `_high-contrast-dark.css` sits above
126
+ `_high-contrast.css` rather than below it.
127
+
128
+ Between the first three the order is grouping, not cascade: `[data-theme]`
129
+ values are matched exactly, so no two scheme blocks can ever apply to the
130
+ same element. `_high-contrast-dark.css` sits next to `_dark.css` because it
131
+ is the dark family's contrast variant, and because `_dark.css`'s
132
+ `prefers-color-scheme` branch has to exclude it by name — that branch is
133
+ 0,4,0 and would otherwise outrank it. See the header of
134
+ `_high-contrast-dark.css`. */
135
+ @import "./_light.css";
58
136
  @import "./_dark.css";
137
+ @import "./_high-contrast-dark.css";
59
138
  @import "./_high-contrast.css";
60
139
 
61
140
  /* Base/reset */
@@ -4,7 +4,13 @@ import { CollapsibleGroupComponent } from '@cdevhub/ngx-tw/collapsible';
4
4
  /** Open mode of the accordion. */
5
5
  type AccordionType = 'single' | 'multiple';
6
6
  /** Visual style of the accordion container. */
7
- type AccordionVariant = 'default' | 'bordered' | 'ghost';
7
+ type AccordionVariant = 'default' | 'outline' | 'ghost' | AccordionVariantLegacy;
8
+ /**
9
+ * Legacy `variant` spellings, kept so existing templates keep compiling.
10
+ * @deprecated `'bordered'` is an alias for `'outline'` and renders identically.
11
+ * It will be removed in the next major — prefer `'outline'`.
12
+ */
13
+ type AccordionVariantLegacy = 'bordered';
8
14
  /**
9
15
  * Accordion — single- or multiple-open-panel group built on top of
10
16
  * `<tw-collapsible>` children. Extends `CollapsibleGroupComponent` to inherit
@@ -24,16 +30,18 @@ type AccordionVariant = 'default' | 'bordered' | 'ghost';
24
30
  declare class AccordionComponent extends CollapsibleGroupComponent {
25
31
  /** Open mode. `'single'` allows one panel open at a time; `'multiple'` allows many. Defaults to `'single'`. */
26
32
  readonly type: _angular_core.InputSignal<AccordionType>;
27
- /** Visual style of the accordion container. Defaults to `'default'`. */
33
+ /** Visual style of the accordion container. Defaults to `'default'`. `'bordered'` is a deprecated alias for `'outline'` and renders identically. */
28
34
  readonly variant: _angular_core.InputSignal<AccordionVariant>;
29
35
  /** In `'single'` mode, whether re-clicking the open panel closes it. Defaults to `true` — accordions are collapsible by definition; opt-out only. */
30
36
  readonly collapsible: _angular_core.InputSignalWithTransform<boolean, unknown>;
31
- /** Accessible name for the accordion. Use when surrounding context doesn't make the purpose obvious. */
37
+ /** Accessible name for the accordion. Use when surrounding context doesn't make the purpose obvious. Defaults to `undefined`. Alias: `aria-label`. */
32
38
  readonly ariaLabel: _angular_core.InputSignal<string | undefined>;
33
- /** ID(s) of element(s) that label the accordion. Use instead of `aria-label` when a visible heading is available. */
39
+ /** ID(s) of element(s) that label the accordion. Use instead of `aria-label` when a visible heading is available. Defaults to `undefined`. Alias: `aria-labelledby`. */
34
40
  readonly ariaLabelledby: _angular_core.InputSignal<string | undefined>;
35
41
  /** @internal APG: accordions don't carry `role="group"`. */
36
42
  readonly hostRole: _angular_core.Signal<string | null>;
43
+ /** @internal Canonical variant with legacy spellings folded in. */
44
+ private readonly resolvedVariant;
37
45
  /** @internal Variant-driven container classes; replaces the parent's default group string. */
38
46
  readonly hostClasses: _angular_core.Signal<string>;
39
47
  /** @internal Drive single-open-panel behaviour from `type`, ignoring the inherited `accordion` input. */
@@ -17,6 +17,13 @@ interface AvatarAppearance {
17
17
  * Injection token used by `AvatarGroupComponent` to propagate its size
18
18
  * to child avatars. When present, overrides the individual avatar's `size` input.
19
19
  */
20
+ declare const TW_AVATAR_GROUP_SIZE: InjectionToken<() => TwSize>;
21
+ /**
22
+ * @deprecated Renamed to {@link TW_AVATAR_GROUP_SIZE} for consistency with every
23
+ * other ngx-tw injection token. This is the *same token instance*, not a copy —
24
+ * providing under either name and injecting under the other resolves — so the
25
+ * rename is safe to adopt incrementally. Removed in the next major.
26
+ */
20
27
  declare const AVATAR_GROUP_SIZE: InjectionToken<() => TwSize>;
21
28
  declare class AvatarComponent {
22
29
  /** URL of the avatar image. When set, renders an `<img>`. Falls back to initials or projected content on load error. Defaults to `null`. */
@@ -64,7 +71,7 @@ declare class AvatarGroupComponent {
64
71
  readonly size: _angular_core.InputSignal<TwSize>;
65
72
  /** Maximum number of avatars to display. Remaining count is shown as a "+N" overflow indicator. Defaults to `null` (show all). */
66
73
  readonly max: _angular_core.InputSignal<number | null>;
67
- /** Accessible label for the avatar group. Defaults to `'Avatar group'` (English) — override for localisation. */
74
+ /** Accessible label for the avatar group, mirrored to `aria-label`. Defaults to `'Avatar group'` (English) — override for localisation. */
68
75
  readonly ariaLabel: _angular_core.InputSignal<string>;
69
76
  private readonly avatars;
70
77
  readonly overflowCount: _angular_core.Signal<number>;
@@ -72,8 +79,8 @@ declare class AvatarGroupComponent {
72
79
  readonly overflowClasses: _angular_core.Signal<string>;
73
80
  private readonly visibilityEffect;
74
81
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<AvatarGroupComponent, never>;
75
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<AvatarGroupComponent, "tw-avatar-group", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, ["avatars"], ["*"], true, never>;
82
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<AvatarGroupComponent, "tw-avatar-group", never, { "size": { "alias": "size"; "required": false; "isSignal": true; }; "max": { "alias": "max"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "aria-label"; "required": false; "isSignal": true; }; }, {}, ["avatars"], ["*"], true, never>;
76
83
  }
77
84
 
78
- export { AVATAR_GROUP_SIZE, AvatarComponent, AvatarGroupComponent };
85
+ export { AVATAR_GROUP_SIZE, AvatarComponent, AvatarGroupComponent, TW_AVATAR_GROUP_SIZE };
79
86
  export type { AvatarAppearance, AvatarRounded, AvatarStatus };
@@ -49,6 +49,7 @@ declare class ButtonDirective implements OnInit {
49
49
  readonly isNativeButton: _angular_core.Signal<boolean>;
50
50
  readonly classes: _angular_core.Signal<string>;
51
51
  ngOnInit(): void;
52
+ /** @internal Swallows clicks while the button is disabled or loading, so neither navigation nor consumer handlers run. */
52
53
  handleClick(event: MouseEvent): void;
53
54
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<ButtonDirective, never>;
54
55
  static ɵdir: _angular_core.ɵɵDirectiveDeclaration<ButtonDirective, "[twButton]", ["twButton"], { "variant": { "alias": "variant"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "loading": { "alias": "loading"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
@@ -20,7 +20,7 @@ interface TwLuxonDateFormat {
20
20
  readonly preset?: Intl.DateTimeFormatOptions;
21
21
  }
22
22
  /**
23
- * Luxon-backed `DateAdapter`. Honors the calendar's `TZ_OVERRIDE` injection
23
+ * Luxon-backed `DateAdapter`. Honors the calendar's `TW_TZ_OVERRIDE` injection
24
24
  * token so `today()` resolves against the consumer-configured timezone, and
25
25
  * implements the §20.2 TZ-aware virtuals (`getTimezone`, `withTimezone`,
26
26
  * `isDST`, `resolveAmbiguous`).
@@ -45,7 +45,7 @@ declare class LuxonDateAdapter extends DateAdapter<DateTime> {
45
45
  private get locale();
46
46
  /**
47
47
  * Returns the configured timezone for new dates. Precedence: per-instance
48
- * `TZ_OVERRIDE` → Luxon's `Settings.defaultZone` → `'local'`.
48
+ * `TW_TZ_OVERRIDE` → Luxon's `Settings.defaultZone` → `'local'`.
49
49
  */
50
50
  private get zone();
51
51
  /** Builds a `DateTime` in the adapter's configured zone + locale. */
@@ -19,7 +19,7 @@ declare class CalendarCellHarness extends ComponentHarness {
19
19
  getText(): Promise<string>;
20
20
  /** The cell's `aria-label`. */
21
21
  getAriaLabel(): Promise<string | null>;
22
- /** Whether the cell carries `aria-selected="true"`. */
22
+ /** Whether the cell carries `aria-selected="true"`. Read from the `role="gridcell"` host, which is the element that carries selection state — a `<button>`'s implicit role does not support `aria-selected`. */
23
23
  isSelected(): Promise<boolean>;
24
24
  /** Whether the cell is disabled — true if the button has `disabled` or carries `aria-disabled="true"`. */
25
25
  isDisabled(): Promise<boolean>;