ngwr 12.1.0 → 13.0.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 (187) hide show
  1. package/README.md +11 -1
  2. package/alert/styles/_index.scss +11 -4
  3. package/badge/styles/_index.scss +30 -0
  4. package/badge/styles/_tag.scss +15 -1
  5. package/border-glow/border-glow.scss +1 -1
  6. package/border-glow/styles/_index.scss +15 -233
  7. package/border-glow/styles/_rules.scss +228 -0
  8. package/breadcrumbs/breadcrumbs.scss +1 -1
  9. package/breadcrumbs/styles/_index.scss +15 -78
  10. package/breadcrumbs/styles/_rules.scss +84 -0
  11. package/card/card.scss +1 -1
  12. package/card/styles/_index.scss +15 -96
  13. package/card/styles/_rules.scss +96 -0
  14. package/cascader/cascader.scss +1 -1
  15. package/cascader/styles/_index.scss +15 -244
  16. package/cascader/styles/_rules.scss +261 -0
  17. package/checkbox/styles/_index.scss +7 -0
  18. package/circular-text/circular-text.scss +1 -1
  19. package/circular-text/styles/_index.scss +15 -63
  20. package/circular-text/styles/_rules.scss +58 -0
  21. package/color-picker/styles/_index.scss +8 -0
  22. package/descriptions/styles/_index.scss +13 -3
  23. package/drag-drop/sortable-list.scss +1 -1
  24. package/drag-drop/styles/_index.scss +14 -143
  25. package/drag-drop/styles/_rules.scss +151 -0
  26. package/drawer/styles/_index.scss +95 -1
  27. package/falling-text/falling-text.scss +1 -1
  28. package/falling-text/styles/_index.scss +15 -36
  29. package/falling-text/styles/_rules.scss +31 -0
  30. package/fesm2022/ngwr-border-glow.mjs +2 -2
  31. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  32. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  33. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  34. package/fesm2022/ngwr-card.mjs +2 -2
  35. package/fesm2022/ngwr-card.mjs.map +1 -1
  36. package/fesm2022/ngwr-cascader.mjs +27 -9
  37. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  38. package/fesm2022/ngwr-checkbox.mjs +86 -8
  39. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  40. package/fesm2022/ngwr-circular-text.mjs +2 -2
  41. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  42. package/fesm2022/ngwr-color-picker.mjs +57 -7
  43. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  44. package/fesm2022/ngwr-date-picker.mjs +8 -0
  45. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  46. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  47. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  48. package/fesm2022/ngwr-drawer.mjs +6 -0
  49. package/fesm2022/ngwr-drawer.mjs.map +1 -1
  50. package/fesm2022/ngwr-falling-text.mjs +2 -2
  51. package/fesm2022/ngwr-falling-text.mjs.map +1 -1
  52. package/fesm2022/ngwr-file-upload.mjs +29 -8
  53. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  54. package/fesm2022/ngwr-form.mjs +48 -1
  55. package/fesm2022/ngwr-form.mjs.map +1 -1
  56. package/fesm2022/ngwr-glitch-text.mjs +2 -2
  57. package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
  58. package/fesm2022/ngwr-gradient-text.mjs +2 -2
  59. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  60. package/fesm2022/ngwr-input-otp.mjs +41 -3
  61. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  62. package/fesm2022/ngwr-knob.mjs +17 -2
  63. package/fesm2022/ngwr-knob.mjs.map +1 -1
  64. package/fesm2022/ngwr-lightbox.mjs +2 -2
  65. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  66. package/fesm2022/ngwr-list.mjs +2 -2
  67. package/fesm2022/ngwr-list.mjs.map +1 -1
  68. package/fesm2022/ngwr-loading-bar.mjs +2 -2
  69. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  70. package/fesm2022/ngwr-marquee.mjs +2 -2
  71. package/fesm2022/ngwr-marquee.mjs.map +1 -1
  72. package/fesm2022/ngwr-pagination.mjs +1 -1
  73. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  74. package/fesm2022/ngwr-radio.mjs +63 -8
  75. package/fesm2022/ngwr-radio.mjs.map +1 -1
  76. package/fesm2022/ngwr-rating.mjs +17 -2
  77. package/fesm2022/ngwr-rating.mjs.map +1 -1
  78. package/fesm2022/ngwr-segmented.mjs +22 -3
  79. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  80. package/fesm2022/ngwr-select.mjs +59 -15
  81. package/fesm2022/ngwr-select.mjs.map +1 -1
  82. package/fesm2022/ngwr-shiny-text.mjs +2 -2
  83. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  84. package/fesm2022/ngwr-slider.mjs +21 -6
  85. package/fesm2022/ngwr-slider.mjs.map +1 -1
  86. package/fesm2022/ngwr-spotlight-card.mjs +2 -2
  87. package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
  88. package/fesm2022/ngwr-star-border.mjs +2 -2
  89. package/fesm2022/ngwr-star-border.mjs.map +1 -1
  90. package/fesm2022/ngwr-switch.mjs +44 -4
  91. package/fesm2022/ngwr-switch.mjs.map +1 -1
  92. package/fesm2022/ngwr-table.mjs +1 -1
  93. package/fesm2022/ngwr-table.mjs.map +1 -1
  94. package/fesm2022/ngwr-textarea.mjs +5 -2
  95. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  96. package/fesm2022/ngwr-theme.mjs +41 -1
  97. package/fesm2022/ngwr-theme.mjs.map +1 -1
  98. package/fesm2022/ngwr-tilt-card.mjs +2 -2
  99. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  100. package/fesm2022/ngwr-transfer.mjs +38 -6
  101. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  102. package/fesm2022/ngwr-tree.mjs +33 -7
  103. package/fesm2022/ngwr-tree.mjs.map +1 -1
  104. package/fesm2022/ngwr-typewriter.mjs +2 -2
  105. package/fesm2022/ngwr-typewriter.mjs.map +1 -1
  106. package/fesm2022/ngwr-version.mjs +1 -1
  107. package/fesm2022/ngwr-version.mjs.map +1 -1
  108. package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
  109. package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
  110. package/fesm2022/ngwr-waves.mjs +2 -2
  111. package/fesm2022/ngwr-waves.mjs.map +1 -1
  112. package/file-upload/styles/_index.scss +6 -0
  113. package/glitch-text/glitch-text.scss +1 -1
  114. package/glitch-text/styles/_index.scss +14 -164
  115. package/glitch-text/styles/_rules.scss +160 -0
  116. package/gradient-text/gradient-text.scss +1 -1
  117. package/gradient-text/styles/_index.scss +15 -118
  118. package/gradient-text/styles/_rules.scss +113 -0
  119. package/input/styles/_index.scss +13 -0
  120. package/input-otp/styles/_index.scss +8 -0
  121. package/lightbox/lightbox.scss +1 -1
  122. package/lightbox/styles/_index.scss +15 -191
  123. package/lightbox/styles/_rules.scss +198 -0
  124. package/list/list.scss +1 -1
  125. package/list/styles/_index.scss +15 -101
  126. package/list/styles/_rules.scss +108 -0
  127. package/llms-full.txt +3 -3
  128. package/loading-bar/loading-bar.scss +1 -1
  129. package/loading-bar/styles/_index.scss +15 -33
  130. package/loading-bar/styles/_rules.scss +28 -0
  131. package/marquee/marquee.scss +1 -1
  132. package/marquee/styles/_index.scss +15 -155
  133. package/marquee/styles/_rules.scss +150 -0
  134. package/overlay/styles/_arrow.scss +243 -0
  135. package/package.json +1 -1
  136. package/popconfirm/styles/_index.scss +17 -0
  137. package/popover/styles/_index.scss +49 -207
  138. package/segmented/styles/_index.scss +8 -0
  139. package/select/styles/_index.scss +47 -5
  140. package/shiny-text/shiny-text.scss +1 -1
  141. package/shiny-text/styles/_index.scss +14 -105
  142. package/shiny-text/styles/_rules.scss +101 -0
  143. package/sidebar/styles/_index.scss +4 -1
  144. package/slider/styles/_index.scss +8 -0
  145. package/spotlight-card/spotlight-card.scss +1 -1
  146. package/spotlight-card/styles/_index.scss +15 -52
  147. package/spotlight-card/styles/_rules.scss +47 -0
  148. package/star-border/star-border.scss +1 -1
  149. package/star-border/styles/_index.scss +15 -125
  150. package/star-border/styles/_rules.scss +120 -0
  151. package/stepper/styles/_index.scss +44 -5
  152. package/switch/styles/_index.scss +7 -0
  153. package/theme/styles/_dark.scss +81 -4
  154. package/theme/styles/_index.scss +1 -0
  155. package/theme/styles/_mixins.scss +10 -0
  156. package/theme/styles/_motion.scss +94 -0
  157. package/tilt-card/styles/_index.scss +15 -45
  158. package/tilt-card/styles/_rules.scss +40 -0
  159. package/tilt-card/tilt-card.scss +1 -1
  160. package/tree/styles/_index.scss +25 -3
  161. package/types/ngwr-cascader.d.ts +17 -1
  162. package/types/ngwr-checkbox.d.ts +51 -3
  163. package/types/ngwr-color-picker.d.ts +19 -1
  164. package/types/ngwr-file-upload.d.ts +18 -1
  165. package/types/ngwr-form.d.ts +51 -2
  166. package/types/ngwr-input-otp.d.ts +23 -1
  167. package/types/ngwr-knob.d.ts +14 -0
  168. package/types/ngwr-radio.d.ts +37 -2
  169. package/types/ngwr-rating.d.ts +14 -0
  170. package/types/ngwr-segmented.d.ts +18 -1
  171. package/types/ngwr-select.d.ts +29 -3
  172. package/types/ngwr-slider.d.ts +14 -1
  173. package/types/ngwr-switch.d.ts +26 -2
  174. package/types/ngwr-textarea.d.ts +3 -0
  175. package/types/ngwr-theme.d.ts +24 -0
  176. package/types/ngwr-transfer.d.ts +17 -1
  177. package/types/ngwr-tree.d.ts +21 -1
  178. package/types/ngwr-version.d.ts +1 -1
  179. package/typewriter/styles/_index.scss +15 -38
  180. package/typewriter/styles/_rules.scss +33 -0
  181. package/typewriter/typewriter.scss +1 -1
  182. package/virtual-scroll/styles/_index.scss +15 -32
  183. package/virtual-scroll/styles/_rules.scss +27 -0
  184. package/virtual-scroll/virtual-scroll.scss +1 -1
  185. package/waves/styles/_index.scss +15 -50
  186. package/waves/styles/_rules.scss +45 -0
  187. package/waves/waves.scss +1 -1
@@ -0,0 +1,94 @@
1
+ // `prefers-reduced-motion` for the always-on chrome.
2
+ //
3
+ // Why this lives in the theme layer rather than in each component. The
4
+ // animation catalogue (`ngwr/glitch-text`, `ngwr/gradient-text`, …) guards
5
+ // itself, component by component — those are opt-in effects, and each one knows
6
+ // what its own stillness should look like. The ten blocks below are the
7
+ // opposite: chrome nobody opted into. A spinner and a skeleton animate on
8
+ // essentially every page of every app, and four of the animations here never
9
+ // stop at all (`wr-spinner-rotate`, `wr-spinner-dash`, `wr-skeleton-shimmer`,
10
+ // `wr-card-spin`, `wr-lightbox-shimmer`), which is the case vestibular guidance
11
+ // is most emphatic about. Collecting them in one place is what makes the set
12
+ // reviewable — the gap this closes existed because nine separate stylesheets
13
+ // each forgot, and nine separate stylesheets can each forget again.
14
+ //
15
+ // **Every declaration here is `!important`, and that is structural rather than
16
+ // lazy.** The theme layer is loaded first — `styles.scss` forwards it before any
17
+ // component, and a standalone `@use 'ngwr/spinner'` pulls it in ahead of the
18
+ // spinner's own rules — so at equal specificity the component always wins on
19
+ // source order. `!important` is the only lever a stylesheet that must come first
20
+ // has. `.wr-overlay-sheet` needs one more: its animation is itself `!important`
21
+ // (it has to beat four components' panel sizing), so the class is doubled here
22
+ // to out-specify it.
23
+ //
24
+ // What reduced motion MEANS is decided per animation, not globally:
25
+ //
26
+ // - An ENTER animation is removed. The panel still arrives, it just does not
27
+ // travel to get there. Every keyframe set below animates from a transparent,
28
+ // offset or scaled-down state TO the element's resting one, with no
29
+ // `forwards`, so removing it lands on exactly the frame the animation was
30
+ // heading for. Nothing in the library waits on `animationend`, so nothing
31
+ // is left un-dismissed.
32
+ // - A decorative INFINITE loop is removed outright — a skeleton's shimmer and
33
+ // a lightbox's shimmer say nothing the static placeholder does not.
34
+ // - A SPINNER is slowed, not stopped. Its rotation IS the message: a frozen
35
+ // spinner does not read as "reduced motion", it reads as "hung". So the
36
+ // rotation stays at half speed and the pulsing arc — the part that carries
37
+ // no information — is frozen at a fixed sweep. Same call Bootstrap makes,
38
+ // and the same reading as WCAG 2.3.3, which is about non-essential motion.
39
+ // - The TOAST progress bar is deliberately left alone. It is a countdown: it
40
+ // is the only thing on screen saying how long is left before the toast
41
+ // leaves, and freezing it would not remove information, it would state a
42
+ // falsehood — a full bar that never drains. It is also 2px tall and moves
43
+ // along one axis at constant speed, the mildest shape motion sensitivity
44
+ // takes. If that trade is ever revisited, revisit it here.
45
+
46
+ @media (prefers-reduced-motion: reduce) {
47
+ // Overlay enter choreography — dialog, dropdown, popconfirm, toast, lightbox
48
+ // viewer, and all four drawer edges.
49
+ .wr-dialog-panel,
50
+ .wr-dropdown-menu,
51
+ .wr-popconfirm-overlay > .wr-popconfirm,
52
+ .wr-toast,
53
+ .wr-lightbox-viewer,
54
+ .wr-drawer__panel--left,
55
+ .wr-drawer__panel--right,
56
+ .wr-drawer__panel--top,
57
+ .wr-drawer__panel--bottom {
58
+ animation: none !important;
59
+ }
60
+
61
+ // The responsive bottom sheet, which slides a full panel height up the
62
+ // screen — the largest single translation the library performs. Doubled
63
+ // class: `overlay/styles/_index.scss` sets this animation `!important`, and it
64
+ // is loaded after this file.
65
+ .wr-overlay-sheet.wr-overlay-sheet {
66
+ animation: none !important;
67
+ }
68
+
69
+ // Decorative loading shimmers. Both ride a pseudo-element whose resting
70
+ // position is off-screen or at its gradient origin, so stopping them leaves a
71
+ // plain tinted placeholder rather than a half-drawn band.
72
+ .wr-skeleton--animated::after,
73
+ .wr-lightbox--loading::after {
74
+ animation: none !important;
75
+ }
76
+
77
+ // Spinners. The rotation survives at half speed; the arc stops pulsing and
78
+ // holds the sweep the dash animation spends most of its cycle at, so the
79
+ // shape stays a recognisable spinner instead of collapsing to the 1-unit dot
80
+ // its first keyframe starts from.
81
+ .wr-spinner svg {
82
+ animation-duration: 4s !important;
83
+ }
84
+
85
+ .wr-spinner__path {
86
+ animation: none !important;
87
+ stroke-dasharray: 90, 150 !important;
88
+ stroke-dashoffset: 0 !important;
89
+ }
90
+
91
+ .wr-card__spinner {
92
+ animation-duration: 1.4s !important;
93
+ }
94
+ }
@@ -1,45 +1,15 @@
1
- // Pull the whole theme so a standalone `@use 'ngwr/<name>'` brings in every
2
- // design token this stylesheet references. Sass deduplicates `@use` per
3
- // compilation, so the umbrella sheet still emits the theme only once.
4
- @use '../../theme/styles' as theme;
5
-
6
- // 3D mouse-tilt card. The transform is set imperatively by `[wrTilt]`,
7
- // so this stylesheet just covers the card chrome + the optional glare
8
- // overlay that the directive appends when `[glare]="true"`.
9
-
10
- .wr-tilt-card {
11
- display: block;
12
- position: relative;
13
- padding: 1.5rem;
14
- border-radius: 0.75rem;
15
- border: 1px solid var(--wr-color-outline);
16
- background: var(--wr-color-surface);
17
- color: var(--wr-color-on-surface);
18
- box-shadow: 0 1px 2px rgba(var(--wr-color-backdrop-rgb), 0.06);
19
- // Card itself doesn't need overflow:hidden by default — the directive
20
- // toggles overflow when glare is enabled. Authors can opt in.
21
- transform-style: preserve-3d;
22
- transition: transform 0.15s ease-out;
23
- }
24
-
25
- // Glare overlay (appended by `[wrTilt]` when `[glare]="true"`)
26
- // Lives in this package so the visual lives with the card chrome.
27
- .wr-tilt-glare {
28
- position: absolute;
29
- inset: 0;
30
- pointer-events: none;
31
- // Light surfaces: glare reads as a soft moving shade. Dark theme flips
32
- // to the classic white sheen below.
33
- background: radial-gradient(
34
- circle at var(--wr-tilt-glare-x, 50%) var(--wr-tilt-glare-y, 50%),
35
- var(--wr-tilt-glare-color, rgba(var(--wr-color-on-surface-rgb), 0.12)),
36
- transparent 60%
37
- );
38
- mix-blend-mode: multiply;
39
- }
40
-
41
- [data-theme='dark'] .wr-tilt-glare {
42
- --wr-tilt-glare-color: rgb(255 255 255 / 25%);
43
-
44
- mix-blend-mode: overlay;
45
- }
1
+ // Public Sass entry for `@use 'ngwr/tilt-card'`.
2
+ //
3
+ // Pulls the whole theme so a standalone import still brings in every design
4
+ // token these rules reference, then loads the rules themselves from `_rules`.
5
+ //
6
+ // The split is what keeps the token layer out of the JavaScript bundle.
7
+ // `tilt-card.scss` is the component's `styleUrl`, and a component stylesheet is
8
+ // its OWN Sass compilation — `@use` only deduplicates within one. So while the
9
+ // umbrella sheet emits the theme once, forwarding it from here would inline a
10
+ // second, byte-identical copy of `:root`, `[data-theme='dark']` and the
11
+ // box-sizing reset into the compiled component styles, which ng-packagr then
12
+ // embeds in the FESM bundle as a string. `tilt-card.scss` forwards `_rules`
13
+ // instead, so it carries this component's own CSS and nothing else.
14
+ @use '../../theme/styles';
15
+ @forward 'rules';
@@ -0,0 +1,40 @@
1
+ // 3D mouse-tilt card. The transform is set imperatively by `[wrTilt]`,
2
+ // so this stylesheet just covers the card chrome + the optional glare
3
+ // overlay that the directive appends when `[glare]="true"`.
4
+
5
+ .wr-tilt-card {
6
+ display: block;
7
+ position: relative;
8
+ padding: 1.5rem;
9
+ border-radius: 0.75rem;
10
+ border: 1px solid var(--wr-color-outline);
11
+ background: var(--wr-color-surface);
12
+ color: var(--wr-color-on-surface);
13
+ box-shadow: 0 1px 2px rgba(var(--wr-color-backdrop-rgb), 0.06);
14
+ // Card itself doesn't need overflow:hidden by default — the directive
15
+ // toggles overflow when glare is enabled. Authors can opt in.
16
+ transform-style: preserve-3d;
17
+ transition: transform 0.15s ease-out;
18
+ }
19
+
20
+ // Glare overlay (appended by `[wrTilt]` when `[glare]="true"`)
21
+ // Lives in this package so the visual lives with the card chrome.
22
+ .wr-tilt-glare {
23
+ position: absolute;
24
+ inset: 0;
25
+ pointer-events: none;
26
+ // Light surfaces: glare reads as a soft moving shade. Dark theme flips
27
+ // to the classic white sheen below.
28
+ background: radial-gradient(
29
+ circle at var(--wr-tilt-glare-x, 50%) var(--wr-tilt-glare-y, 50%),
30
+ var(--wr-tilt-glare-color, rgba(var(--wr-color-on-surface-rgb), 0.12)),
31
+ transparent 60%
32
+ );
33
+ mix-blend-mode: multiply;
34
+ }
35
+
36
+ [data-theme='dark'] .wr-tilt-glare {
37
+ --wr-tilt-glare-color: rgb(255 255 255 / 25%);
38
+
39
+ mix-blend-mode: overlay;
40
+ }
@@ -1 +1 @@
1
- @forward './styles';
1
+ @forward './styles/rules';
@@ -122,6 +122,15 @@
122
122
  white-space: nowrap;
123
123
  }
124
124
 
125
+ // A read-only tree still expands, still takes focus and still submits its
126
+ // value, so it keeps the visual and only stops inviting the click that would
127
+ // change the selection. The trigger is covered by the same rule in combobox
128
+ // mode, where the whole panel is refused.
129
+ &--readonly &__row,
130
+ &--readonly &__trigger {
131
+ cursor: default;
132
+ }
133
+
125
134
  &--disabled {
126
135
  opacity: var(--wr-disabled-opacity);
127
136
  pointer-events: none;
@@ -145,6 +154,11 @@
145
154
  --wr-tree-trigger-font-size: var(--wr-control-font-size-md);
146
155
  --wr-tree-trigger-line-height: var(--wr-control-line-height-md);
147
156
  --wr-tree-panel-max-height: 18rem;
157
+ // Trigger width floor — hooked for the same reason as `--wr-select-min-width`:
158
+ // in a narrow grid column the literal wins over `minmax(0, 1fr)` and the
159
+ // trigger overhangs its cell, and the only escape was overriding
160
+ // `.wr-tree__trigger` from outside. Set it to 0 to defer to the layout.
161
+ --wr-tree-trigger-min-width: 12rem;
148
162
 
149
163
  display: inline-flex;
150
164
  vertical-align: middle;
@@ -160,7 +174,7 @@
160
174
  align-items: center;
161
175
  gap: 0.5rem;
162
176
  width: 100%;
163
- min-width: 12rem;
177
+ min-width: var(--wr-tree-trigger-min-width);
164
178
  appearance: none;
165
179
  background: var(--wr-tree-trigger-bg);
166
180
  color: var(--wr-tree-trigger-color);
@@ -270,7 +284,10 @@
270
284
  // Roomier on the label side, tighter where the remove (×) sits.
271
285
  padding-block: 0.125rem;
272
286
  padding-inline: 0.5rem 0.25rem;
273
- border-radius: 999px;
287
+ // Not a pill: a chip is a shape nested INSIDE the trigger, so it repeats the
288
+ // trigger's own corner instead of deciding one for itself — and an override
289
+ // of `--wr-tree-trigger-radius` carries the chips with it.
290
+ border-radius: var(--wr-tree-trigger-radius);
274
291
  background: var(--wr-color-primary-soft);
275
292
  // The chip wraps a label, so this paints text on the chip's own tint.
276
293
  color: var(--wr-color-primary-ink);
@@ -292,7 +309,12 @@
292
309
  justify-content: center;
293
310
  width: 1rem;
294
311
  height: 1rem;
295
- border-radius: 999px;
312
+ // Concentric with the chip: inset from the chip's edge by its 2px block
313
+ // padding, and an inner corner only nests cleanly when it is the outer
314
+ // corner minus that inset. `max()` keeps the value legal for a consumer who
315
+ // sets `--wr-tree-trigger-radius: 0` — a negative radius invalidates the
316
+ // whole declaration.
317
+ border-radius: max(0px, calc(var(--wr-tree-trigger-radius) - 2px));
296
318
  cursor: pointer;
297
319
  transition: background 0.12s ease;
298
320
 
@@ -1,3 +1,4 @@
1
+ import * as ngwr_form from 'ngwr/form';
1
2
  import * as _angular_core from '@angular/core';
2
3
  import { TemplateRef } from '@angular/core';
3
4
  import { FormValueControl } from '@angular/forms/signals';
@@ -73,6 +74,21 @@ declare class WrCascader<T = string> implements FormValueControl<unknown> {
73
74
  * @default false
74
75
  */
75
76
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
77
+ /**
78
+ * Refuse changes while the trigger stays focusable and the path still submits.
79
+ * Bound automatically from the field's readonly state when used with
80
+ * `[formField]`.
81
+ *
82
+ * The panel is where every edit happens, so a read-only cascader simply does
83
+ * not open — there is nothing to browse that is not already on the trigger —
84
+ * and the clear button goes away with it. Mirrored as `aria-readonly`, which
85
+ * role `combobox` supports.
86
+ *
87
+ * @default false
88
+ */
89
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
90
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
91
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
76
92
  /** Control size — shares the `--wr-control-*` contract. @default 'md' */
77
93
  readonly size: _angular_core.InputSignal<WrCascaderSize>;
78
94
  /** Show a clear-all (×) button on the trigger when a path is selected. @default true */
@@ -143,7 +159,7 @@ declare class WrCascader<T = string> implements FormValueControl<unknown> {
143
159
  private focusPanelOption;
144
160
  private closeOverlay;
145
161
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrCascader<any>, never>;
146
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCascader<any>, "wr-cascader", never, { "clearLabel": { "alias": "clearLabel"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "changeOnSelect": { "alias": "changeOnSelect"; "required": false; "isSignal": true; }; "separator": { "alias": "separator"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
162
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCascader<any>, "wr-cascader", never, { "clearLabel": { "alias": "clearLabel"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": true; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "clearable": { "alias": "clearable"; "required": false; "isSignal": true; }; "changeOnSelect": { "alias": "changeOnSelect"; "required": false; "isSignal": true; }; "separator": { "alias": "separator"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
147
163
  }
148
164
 
149
165
  export { WrCascader };
@@ -1,6 +1,7 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { InjectionToken, Signal } from '@angular/core';
3
3
  import { FormCheckboxControl, FormValueControl } from '@angular/forms/signals';
4
+ import * as ngwr_form from 'ngwr/form';
4
5
 
5
6
  /**
6
7
  * Two-state checkbox.
@@ -35,7 +36,8 @@ import { FormCheckboxControl, FormValueControl } from '@angular/forms/signals';
35
36
  type WrCheckboxSize = 'sm' | 'md' | 'lg';
36
37
  declare class WrCheckbox implements FormCheckboxControl {
37
38
  /**
38
- * Stable id used to associate the native input with its label.
39
+ * Stable id used to associate the native input with its label. Lands on the
40
+ * inner `<input>`; the host never keeps it.
39
41
  *
40
42
  * @default Randomly generated
41
43
  */
@@ -70,6 +72,18 @@ declare class WrCheckbox implements FormCheckboxControl {
70
72
  * @default false
71
73
  */
72
74
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
75
+ /**
76
+ * Refuse edits while staying focusable and submittable. Bound automatically
77
+ * from the field's readonly state when used with `[formField]`.
78
+ *
79
+ * A native `<input type="checkbox">` ignores the `readonly` attribute, so this
80
+ * cancels the click's activation behaviour instead — which covers Space too,
81
+ * since a checkbox turns Space into a click — and mirrors the state as
82
+ * `aria-readonly`, which role `checkbox` supports.
83
+ *
84
+ * @default false
85
+ */
86
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
73
87
  /**
74
88
  * Control size — shares the `--wr-control-*` contract. Unset falls back to the
75
89
  * `checkbox.size` app default from `provideWrConfig()`. @default 'md'
@@ -96,11 +110,28 @@ declare class WrCheckbox implements FormCheckboxControl {
96
110
  protected readonly isChecked: _angular_core.Signal<boolean>;
97
111
  /** Effective disabled — the `disabled` input wins, then the group. */
98
112
  protected readonly effectiveDisabled: _angular_core.Signal<boolean>;
113
+ /** Effective readonly — this box's own `readonly` input, or the group's. */
114
+ protected readonly effectiveReadonly: _angular_core.Signal<boolean>;
115
+ /**
116
+ * The surrounding `<wr-form-field>`'s error state, mirrored onto the native
117
+ * input. Inside a `<wr-checkbox-group>` the GROUP is the bound control and
118
+ * carries the state, so a grouped box shields itself and stays silent — nine
119
+ * boxes each announcing the group's one error is noise, not information.
120
+ */
121
+ private readonly fieldAria;
122
+ protected readonly ariaInvalid: _angular_core.Signal<"true" | null>;
123
+ protected readonly describedBy: _angular_core.Signal<string | null>;
99
124
  protected readonly classes: _angular_core.Signal<string>;
125
+ /**
126
+ * Cancel the toggle while readonly. `change` never fires for a click whose
127
+ * default was prevented, so this is the only guard the pointer and Space
128
+ * paths need; `onInputChange` keeps its own for a synthetic `change`.
129
+ */
130
+ protected onInputClick(event: Event): void;
100
131
  protected onInputChange(event: Event): void;
101
132
  protected onInputBlur(): void;
102
133
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrCheckbox, never>;
103
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCheckbox, "wr-checkbox", never, { "id": { "alias": "id"; "required": false; "isSignal": true; }; "checkboxValue": { "alias": "checkboxValue"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "checked": { "alias": "checked"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "indeterminate": { "alias": "indeterminate"; "required": false; "isSignal": true; }; }, { "checked": "checkedChange"; "touch": "touch"; }, never, ["*"], true, never>;
134
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCheckbox, "wr-checkbox", never, { "id": { "alias": "id"; "required": false; "isSignal": true; }; "checkboxValue": { "alias": "checkboxValue"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "checked": { "alias": "checked"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "indeterminate": { "alias": "indeterminate"; "required": false; "isSignal": true; }; }, { "checked": "checkedChange"; "touch": "touch"; }, never, ["*"], true, never>;
104
135
  }
105
136
 
106
137
  /**
@@ -133,6 +164,8 @@ interface WrCheckboxGroupContext {
133
164
  blurred(): void;
134
165
  /** Whether the entire group is disabled. */
135
166
  readonly isDisabled: Signal<boolean>;
167
+ /** Whether the entire group refuses edits while staying focusable. */
168
+ readonly isReadonly: Signal<boolean>;
136
169
  }
137
170
  /**
138
171
  * Token a `<wr-checkbox>` injects to read selection state from — and
@@ -169,6 +202,19 @@ declare class WrCheckboxGroup implements FormValueControl<unknown[]>, WrCheckbox
169
202
  * @default false
170
203
  */
171
204
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
205
+ /**
206
+ * Refuse edits on every child while they stay focusable and the value still
207
+ * submits. Bound automatically from the field's readonly state when used with
208
+ * `[formField]`.
209
+ *
210
+ * No `aria-readonly` on the host: role `group` does not support it (the state
211
+ * is not global), so each child box mirrors its own instead.
212
+ *
213
+ * @default false
214
+ */
215
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
216
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
217
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
172
218
  /** The checked items' values. Bound by `[formField]`, or two-way via `[(value)]`. */
173
219
  readonly value: _angular_core.ModelSignal<unknown[]>;
174
220
  /** Emitted when a child toggles so a bound field can mark itself touched. */
@@ -176,12 +222,14 @@ declare class WrCheckboxGroup implements FormValueControl<unknown[]>, WrCheckbox
176
222
  private readonly selected;
177
223
  /** Effective disabled state (WrCheckboxGroupContext). */
178
224
  readonly isDisabled: _angular_core.Signal<boolean>;
225
+ /** Effective readonly state (WrCheckboxGroupContext). */
226
+ readonly isReadonly: _angular_core.Signal<boolean>;
179
227
  isSelected(value: unknown): boolean;
180
228
  toggle(value: unknown): void;
181
229
  /** @internal A child checkbox lost focus — see {@link WrCheckboxGroupContext.blurred}. */
182
230
  blurred(): void;
183
231
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrCheckboxGroup, never>;
184
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCheckboxGroup, "wr-checkbox-group", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, ["*"], true, never>;
232
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrCheckboxGroup, "wr-checkbox-group", never, { "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, ["*"], true, never>;
185
233
  }
186
234
 
187
235
  export { WR_CHECKBOX_GROUP, WrCheckbox, WrCheckboxGroup };
@@ -1,3 +1,4 @@
1
+ import * as ngwr_form from 'ngwr/form';
1
2
  import * as _angular_core from '@angular/core';
2
3
  import { FormValueControl } from '@angular/forms/signals';
3
4
  import { WrSegmentedOption } from 'ngwr/segmented';
@@ -196,6 +197,23 @@ declare class WrColorPicker implements FormValueControl<string> {
196
197
  readonly alpha: _angular_core.InputSignalWithTransform<boolean, any>;
197
198
  /** Disable interaction. @default false */
198
199
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
200
+ /**
201
+ * Refuse changes while every surface stays focusable and the colour still
202
+ * submits. Bound automatically from the field's readonly state when used with
203
+ * `[formField]`.
204
+ *
205
+ * The two sliders keep their tab stop and keep announcing `aria-valuenow` —
206
+ * they simply do not move — the hex / channel fields take the native
207
+ * `readonly` attribute, and the swatches go inert. Switching TABS still works:
208
+ * that changes which notation is displayed, not the colour. `aria-readonly` is
209
+ * mirrored on the sliders, which support it; the saturation surface is a
210
+ * `role="group"`, which does not, so it carries nothing.
211
+ *
212
+ * @default false
213
+ */
214
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
215
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
216
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
199
217
  /**
200
218
  * Format the control writes into `value`. See {@link WrColorFormat} — all
201
219
  * three are accepted on the way in whichever one is set. @default 'hex'
@@ -283,7 +301,7 @@ declare class WrColorPicker implements FormValueControl<string> {
283
301
  /** Push an RGB triple into the HSV source of truth. */
284
302
  private applyRgb;
285
303
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrColorPicker, never>;
286
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrColorPicker, "wr-color-picker", never, { "alpha": { "alias": "alpha"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "swatches": { "alias": "swatches"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
304
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrColorPicker, "wr-color-picker", never, { "alpha": { "alias": "alpha"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "format": { "alias": "format"; "required": false; "isSignal": true; }; "swatches": { "alias": "swatches"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, never, true, never>;
287
305
  }
288
306
 
289
307
  /**
@@ -1,3 +1,4 @@
1
+ import * as ngwr_form from 'ngwr/form';
1
2
  import * as _angular_core from '@angular/core';
2
3
  import { ElementRef } from '@angular/core';
3
4
  import { FormValueControl } from '@angular/forms/signals';
@@ -57,6 +58,22 @@ declare class WrFileUpload implements FormValueControl<File | readonly File[] |
57
58
  * @default false
58
59
  */
59
60
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
61
+ /**
62
+ * Refuse changes to the selection while the zone stays focusable and the files
63
+ * still submit. Bound automatically from the field's readonly state when used
64
+ * with `[formField]`.
65
+ *
66
+ * The zone stops opening the picker, a drop is refused and the per-file remove
67
+ * buttons go inert — but the list stays readable, which is the point. No
68
+ * `aria-readonly`: the zone is a `role="button"`, and ARIA does not define the
69
+ * state for that role, so `aria-disabled` would be the only mirror available
70
+ * and it would say the wrong thing.
71
+ *
72
+ * @default false
73
+ */
74
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
75
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
76
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
60
77
  /** Primary call-to-action label. Falls back to `fileUpload.browse`. */
61
78
  readonly pickLabel: _angular_core.InputSignal<string | null>;
62
79
  /** Secondary instruction below the CTA. Falls back to `fileUpload.dropZone`. */
@@ -98,7 +115,7 @@ declare class WrFileUpload implements FormValueControl<File | readonly File[] |
98
115
  private reject;
99
116
  private emit;
100
117
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrFileUpload, never>;
101
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrFileUpload, "wr-file-upload", never, { "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "showList": { "alias": "showList"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "pickLabel": { "alias": "pickLabel"; "required": false; "isSignal": true; }; "dropLabel": { "alias": "dropLabel"; "required": false; "isSignal": true; }; "dropZoneLabel": { "alias": "dropZoneLabel"; "required": false; "isSignal": true; }; "removeFileLabel": { "alias": "removeFileLabel"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "rejected": "rejected"; "touch": "touch"; }, never, never, true, never>;
118
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrFileUpload, "wr-file-upload", never, { "multiple": { "alias": "multiple"; "required": false; "isSignal": true; }; "accept": { "alias": "accept"; "required": false; "isSignal": true; }; "maxSize": { "alias": "maxSize"; "required": false; "isSignal": true; }; "maxFiles": { "alias": "maxFiles"; "required": false; "isSignal": true; }; "showList": { "alias": "showList"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "pickLabel": { "alias": "pickLabel"; "required": false; "isSignal": true; }; "dropLabel": { "alias": "dropLabel"; "required": false; "isSignal": true; }; "dropZoneLabel": { "alias": "dropZoneLabel"; "required": false; "isSignal": true; }; "removeFileLabel": { "alias": "removeFileLabel"; "required": false; "isSignal": true; }; "helperText": { "alias": "helperText"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "rejected": "rejected"; "touch": "touch"; }, never, never, true, never>;
102
119
  }
103
120
 
104
121
  export { WrFileUpload };
@@ -214,6 +214,55 @@ declare class WrFormField implements WrFormFieldContext {
214
214
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrFormField, "wr-form-field", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "hint": { "alias": "hint"; "required": false; "isSignal": true; }; "required": { "alias": "required"; "required": false; "isSignal": true; }; "optional": { "alias": "optional"; "required": false; "isSignal": true; }; "optionalLabel": { "alias": "optionalLabel"; "required": false; "isSignal": true; }; "controlId": { "alias": "controlId"; "required": false; "isSignal": true; }; "autoErrors": { "alias": "autoErrors"; "required": false; "isSignal": true; }; }, {}, ["ngControl", "projectedErrors"], ["*", "wr-form-error"], true, never>;
215
215
  }
216
216
 
217
+ /**
218
+ * @license
219
+ *
220
+ * Use of this source code is governed by an MIT-style license that can be
221
+ * found in the LICENSE file at https://github.com/thekhegay/ngwr/blob/main/LICENSE
222
+ */
223
+
224
+ /** What {@link useFormFieldAria} hands a control to bind on its own element. */
225
+ interface WrFormFieldAria {
226
+ /** Id of the field's message block, or `null` when it is showing none. */
227
+ readonly describedBy: Signal<string | null>;
228
+ /** `'true'` while the field is showing a message, `null` otherwise. */
229
+ readonly ariaInvalid: Signal<'true' | null>;
230
+ }
231
+ /**
232
+ * The two attributes that make a `<wr-form-field>`'s visible error reach a
233
+ * screen reader — bind both on whichever element carries the control's role.
234
+ *
235
+ * Without them the messages the field renders are visible and nothing else: the
236
+ * control announces itself as valid, and the message is associated with nothing.
237
+ * No automated gate catches that — axe has no rule that a visible error must be
238
+ * programmatically associated with its control — so this helper exists to make
239
+ * the wiring one line per control rather than three, and the specs are the gate.
240
+ *
241
+ * `aria-invalid` is keyed on the message EXISTING rather than on `errorKeys()`,
242
+ * because the field only publishes an id once it is showing something —
243
+ * announcing "invalid" while pointing at nothing is worse than staying quiet.
244
+ *
245
+ * Must be called from an injection context (a field initialiser or a
246
+ * constructor). Returns inert signals outside a `<wr-form-field>`.
247
+ *
248
+ * Pass `skipSelf` from a COMPOSITE control — one whose own template renders
249
+ * other ngwr controls. Such a component shields its subtree by re-providing
250
+ * `WR_FORM_FIELD` as `null`, or the inner parts each announce the outer field's
251
+ * error as their own; a component sees its own providers, so it has to read the
252
+ * real field from one level up.
253
+ *
254
+ * @example
255
+ * ```ts
256
+ * protected readonly fieldAria = useFormFieldAria();
257
+ * ```
258
+ * ```html
259
+ * <input [attr.aria-invalid]="fieldAria.ariaInvalid()" [attr.aria-describedby]="fieldAria.describedBy()" />
260
+ * ```
261
+ */
262
+ declare function useFormFieldAria(options?: {
263
+ readonly skipSelf?: boolean;
264
+ }): WrFormFieldAria;
265
+
217
266
  /**
218
267
  * @license
219
268
  *
@@ -275,5 +324,5 @@ declare function provideWrFormErrors(messages?: WrFormErrorMessages): Environmen
275
324
  */
276
325
  declare const WR_FORM_ERROR_FALLBACKS: WrFormErrorMessages;
277
326
 
278
- export { WR_FORM_ERRORS, WR_FORM_ERROR_FALLBACKS, WR_FORM_FIELD, WrFormError, WrFormField, WrFormItem, provideWrFormErrors };
279
- export type { WrFormErrorContext, WrFormErrorMessage, WrFormErrorMessages, WrFormFieldContext };
327
+ export { WR_FORM_ERRORS, WR_FORM_ERROR_FALLBACKS, WR_FORM_FIELD, WrFormError, WrFormField, WrFormItem, provideWrFormErrors, useFormFieldAria };
328
+ export type { WrFormErrorContext, WrFormErrorMessage, WrFormErrorMessages, WrFormFieldAria, WrFormFieldContext };
@@ -1,3 +1,4 @@
1
+ import * as ngwr_form from 'ngwr/form';
1
2
  import * as _angular_core from '@angular/core';
2
3
  import { ElementRef } from '@angular/core';
3
4
  import { FormValueControl } from '@angular/forms/signals';
@@ -72,6 +73,27 @@ declare class WrInputOtp implements FormValueControl<string> {
72
73
  * @default false
73
74
  */
74
75
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
76
+ /**
77
+ * Refuse edits while every box stays focusable and the code still submits.
78
+ * Bound automatically from the field's readonly state when used with
79
+ * `[formField]`.
80
+ *
81
+ * Each box is a real text input, so this is the native `readonly` attribute —
82
+ * arrow keys, Home / End and selection keep working, which is the whole
83
+ * difference from `disabled`. Paste is cancelled too, since a `paste` still
84
+ * reaches a read-only input even though typing does not.
85
+ *
86
+ * @default false
87
+ */
88
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
89
+ /**
90
+ * The surrounding `<wr-form-field>`'s error state — bound per BOX rather than
91
+ * on the `role="group"` host, because the box is what takes focus and a reader
92
+ * queries the focused element.
93
+ *
94
+ * @internal
95
+ */
96
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
75
97
  /** Character shown in empty cells. @default '•' */
76
98
  readonly placeholder: _angular_core.InputSignal<string>;
77
99
  /** The entered code. Bound by `[formField]`, or two-way via `[(value)]`. */
@@ -116,7 +138,7 @@ declare class WrInputOtp implements FormValueControl<string> {
116
138
  */
117
139
  private inlineKey;
118
140
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrInputOtp, never>;
119
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrInputOtp, "wr-input-otp", never, { "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "length": { "alias": "length"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "mask": { "alias": "mask"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "completed": "completed"; "touch": "touch"; }, never, never, true, never>;
141
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrInputOtp, "wr-input-otp", never, { "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; "length": { "alias": "length"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; "mask": { "alias": "mask"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "readonly": { "alias": "readonly"; "required": false; "isSignal": true; }; "placeholder": { "alias": "placeholder"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "completed": "completed"; "touch": "touch"; }, never, never, true, never>;
120
142
  }
121
143
 
122
144
  export { WrInputOtp };
@@ -1,3 +1,4 @@
1
+ import * as ngwr_form from 'ngwr/form';
1
2
  import * as _angular_core from '@angular/core';
2
3
  import { FormValueControl } from '@angular/forms/signals';
3
4
 
@@ -54,6 +55,19 @@ declare class WrKnob implements FormValueControl<number> {
54
55
  readonly touch: _angular_core.OutputEmitterRef<void>;
55
56
  private dragging;
56
57
  protected readonly interactive: _angular_core.Signal<boolean>;
58
+ /**
59
+ * Whether the control keeps its tab stop — `disabled` only, never `readonly`.
60
+ *
61
+ * The two states differ precisely here: a disabled control leaves the tab
62
+ * order, a read-only one stays in it and keeps announcing `aria-valuenow`,
63
+ * because a keyboard user still has to be able to READ a value they cannot
64
+ * change. Driving `tabindex` off `interactive()` collapsed that distinction
65
+ * and made `readonly()` unreachable — with an `aria-describedby` pointing at
66
+ * an error message on an element nobody could focus.
67
+ */
68
+ protected readonly focusable: _angular_core.Signal<boolean>;
69
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
70
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
57
71
  protected readonly classes: _angular_core.Signal<string>;
58
72
  protected readonly cx = 50;
59
73
  protected readonly cy = 50;