ngwr 12.2.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 (174) hide show
  1. package/README.md +11 -1
  2. package/border-glow/border-glow.scss +1 -1
  3. package/border-glow/styles/_index.scss +15 -233
  4. package/border-glow/styles/_rules.scss +228 -0
  5. package/breadcrumbs/breadcrumbs.scss +1 -1
  6. package/breadcrumbs/styles/_index.scss +15 -78
  7. package/breadcrumbs/styles/_rules.scss +84 -0
  8. package/card/card.scss +1 -1
  9. package/card/styles/_index.scss +15 -96
  10. package/card/styles/_rules.scss +96 -0
  11. package/cascader/cascader.scss +1 -1
  12. package/cascader/styles/_index.scss +15 -244
  13. package/cascader/styles/_rules.scss +261 -0
  14. package/checkbox/styles/_index.scss +7 -0
  15. package/circular-text/circular-text.scss +1 -1
  16. package/circular-text/styles/_index.scss +15 -63
  17. package/circular-text/styles/_rules.scss +58 -0
  18. package/color-picker/styles/_index.scss +8 -0
  19. package/drag-drop/sortable-list.scss +1 -1
  20. package/drag-drop/styles/_index.scss +14 -143
  21. package/drag-drop/styles/_rules.scss +151 -0
  22. package/falling-text/falling-text.scss +1 -1
  23. package/falling-text/styles/_index.scss +15 -36
  24. package/falling-text/styles/_rules.scss +31 -0
  25. package/fesm2022/ngwr-border-glow.mjs +2 -2
  26. package/fesm2022/ngwr-border-glow.mjs.map +1 -1
  27. package/fesm2022/ngwr-breadcrumbs.mjs +2 -2
  28. package/fesm2022/ngwr-breadcrumbs.mjs.map +1 -1
  29. package/fesm2022/ngwr-card.mjs +2 -2
  30. package/fesm2022/ngwr-card.mjs.map +1 -1
  31. package/fesm2022/ngwr-cascader.mjs +27 -9
  32. package/fesm2022/ngwr-cascader.mjs.map +1 -1
  33. package/fesm2022/ngwr-checkbox.mjs +86 -8
  34. package/fesm2022/ngwr-checkbox.mjs.map +1 -1
  35. package/fesm2022/ngwr-circular-text.mjs +2 -2
  36. package/fesm2022/ngwr-circular-text.mjs.map +1 -1
  37. package/fesm2022/ngwr-color-picker.mjs +57 -7
  38. package/fesm2022/ngwr-color-picker.mjs.map +1 -1
  39. package/fesm2022/ngwr-date-picker.mjs +8 -0
  40. package/fesm2022/ngwr-date-picker.mjs.map +1 -1
  41. package/fesm2022/ngwr-drag-drop.mjs +2 -2
  42. package/fesm2022/ngwr-drag-drop.mjs.map +1 -1
  43. package/fesm2022/ngwr-falling-text.mjs +2 -2
  44. package/fesm2022/ngwr-falling-text.mjs.map +1 -1
  45. package/fesm2022/ngwr-file-upload.mjs +29 -8
  46. package/fesm2022/ngwr-file-upload.mjs.map +1 -1
  47. package/fesm2022/ngwr-form.mjs +48 -1
  48. package/fesm2022/ngwr-form.mjs.map +1 -1
  49. package/fesm2022/ngwr-glitch-text.mjs +2 -2
  50. package/fesm2022/ngwr-glitch-text.mjs.map +1 -1
  51. package/fesm2022/ngwr-gradient-text.mjs +2 -2
  52. package/fesm2022/ngwr-gradient-text.mjs.map +1 -1
  53. package/fesm2022/ngwr-input-otp.mjs +41 -3
  54. package/fesm2022/ngwr-input-otp.mjs.map +1 -1
  55. package/fesm2022/ngwr-knob.mjs +17 -2
  56. package/fesm2022/ngwr-knob.mjs.map +1 -1
  57. package/fesm2022/ngwr-lightbox.mjs +2 -2
  58. package/fesm2022/ngwr-lightbox.mjs.map +1 -1
  59. package/fesm2022/ngwr-list.mjs +2 -2
  60. package/fesm2022/ngwr-list.mjs.map +1 -1
  61. package/fesm2022/ngwr-loading-bar.mjs +2 -2
  62. package/fesm2022/ngwr-loading-bar.mjs.map +1 -1
  63. package/fesm2022/ngwr-marquee.mjs +2 -2
  64. package/fesm2022/ngwr-marquee.mjs.map +1 -1
  65. package/fesm2022/ngwr-pagination.mjs +1 -1
  66. package/fesm2022/ngwr-pagination.mjs.map +1 -1
  67. package/fesm2022/ngwr-radio.mjs +63 -8
  68. package/fesm2022/ngwr-radio.mjs.map +1 -1
  69. package/fesm2022/ngwr-rating.mjs +17 -2
  70. package/fesm2022/ngwr-rating.mjs.map +1 -1
  71. package/fesm2022/ngwr-segmented.mjs +22 -3
  72. package/fesm2022/ngwr-segmented.mjs.map +1 -1
  73. package/fesm2022/ngwr-select.mjs +46 -12
  74. package/fesm2022/ngwr-select.mjs.map +1 -1
  75. package/fesm2022/ngwr-shiny-text.mjs +2 -2
  76. package/fesm2022/ngwr-shiny-text.mjs.map +1 -1
  77. package/fesm2022/ngwr-slider.mjs +21 -6
  78. package/fesm2022/ngwr-slider.mjs.map +1 -1
  79. package/fesm2022/ngwr-spotlight-card.mjs +2 -2
  80. package/fesm2022/ngwr-spotlight-card.mjs.map +1 -1
  81. package/fesm2022/ngwr-star-border.mjs +2 -2
  82. package/fesm2022/ngwr-star-border.mjs.map +1 -1
  83. package/fesm2022/ngwr-switch.mjs +44 -4
  84. package/fesm2022/ngwr-switch.mjs.map +1 -1
  85. package/fesm2022/ngwr-table.mjs +1 -1
  86. package/fesm2022/ngwr-table.mjs.map +1 -1
  87. package/fesm2022/ngwr-textarea.mjs +5 -2
  88. package/fesm2022/ngwr-textarea.mjs.map +1 -1
  89. package/fesm2022/ngwr-theme.mjs +41 -1
  90. package/fesm2022/ngwr-theme.mjs.map +1 -1
  91. package/fesm2022/ngwr-tilt-card.mjs +2 -2
  92. package/fesm2022/ngwr-tilt-card.mjs.map +1 -1
  93. package/fesm2022/ngwr-transfer.mjs +38 -6
  94. package/fesm2022/ngwr-transfer.mjs.map +1 -1
  95. package/fesm2022/ngwr-tree.mjs +33 -7
  96. package/fesm2022/ngwr-tree.mjs.map +1 -1
  97. package/fesm2022/ngwr-typewriter.mjs +2 -2
  98. package/fesm2022/ngwr-typewriter.mjs.map +1 -1
  99. package/fesm2022/ngwr-version.mjs +1 -1
  100. package/fesm2022/ngwr-version.mjs.map +1 -1
  101. package/fesm2022/ngwr-virtual-scroll.mjs +2 -2
  102. package/fesm2022/ngwr-virtual-scroll.mjs.map +1 -1
  103. package/fesm2022/ngwr-waves.mjs +2 -2
  104. package/fesm2022/ngwr-waves.mjs.map +1 -1
  105. package/file-upload/styles/_index.scss +6 -0
  106. package/glitch-text/glitch-text.scss +1 -1
  107. package/glitch-text/styles/_index.scss +14 -164
  108. package/glitch-text/styles/_rules.scss +160 -0
  109. package/gradient-text/gradient-text.scss +1 -1
  110. package/gradient-text/styles/_index.scss +15 -118
  111. package/gradient-text/styles/_rules.scss +113 -0
  112. package/input/styles/_index.scss +13 -0
  113. package/input-otp/styles/_index.scss +8 -0
  114. package/lightbox/lightbox.scss +1 -1
  115. package/lightbox/styles/_index.scss +15 -191
  116. package/lightbox/styles/_rules.scss +198 -0
  117. package/list/list.scss +1 -1
  118. package/list/styles/_index.scss +15 -101
  119. package/list/styles/_rules.scss +108 -0
  120. package/llms-full.txt +3 -3
  121. package/loading-bar/loading-bar.scss +1 -1
  122. package/loading-bar/styles/_index.scss +15 -33
  123. package/loading-bar/styles/_rules.scss +28 -0
  124. package/marquee/marquee.scss +1 -1
  125. package/marquee/styles/_index.scss +15 -155
  126. package/marquee/styles/_rules.scss +150 -0
  127. package/package.json +1 -1
  128. package/segmented/styles/_index.scss +8 -0
  129. package/select/styles/_index.scss +17 -1
  130. package/shiny-text/shiny-text.scss +1 -1
  131. package/shiny-text/styles/_index.scss +14 -105
  132. package/shiny-text/styles/_rules.scss +101 -0
  133. package/slider/styles/_index.scss +8 -0
  134. package/spotlight-card/spotlight-card.scss +1 -1
  135. package/spotlight-card/styles/_index.scss +15 -52
  136. package/spotlight-card/styles/_rules.scss +47 -0
  137. package/star-border/star-border.scss +1 -1
  138. package/star-border/styles/_index.scss +15 -125
  139. package/star-border/styles/_rules.scss +120 -0
  140. package/switch/styles/_index.scss +7 -0
  141. package/theme/styles/_dark.scss +81 -4
  142. package/theme/styles/_index.scss +1 -0
  143. package/theme/styles/_motion.scss +94 -0
  144. package/tilt-card/styles/_index.scss +15 -45
  145. package/tilt-card/styles/_rules.scss +40 -0
  146. package/tilt-card/tilt-card.scss +1 -1
  147. package/tree/styles/_index.scss +15 -1
  148. package/types/ngwr-cascader.d.ts +17 -1
  149. package/types/ngwr-checkbox.d.ts +51 -3
  150. package/types/ngwr-color-picker.d.ts +19 -1
  151. package/types/ngwr-file-upload.d.ts +18 -1
  152. package/types/ngwr-form.d.ts +51 -2
  153. package/types/ngwr-input-otp.d.ts +23 -1
  154. package/types/ngwr-knob.d.ts +14 -0
  155. package/types/ngwr-radio.d.ts +37 -2
  156. package/types/ngwr-rating.d.ts +14 -0
  157. package/types/ngwr-segmented.d.ts +18 -1
  158. package/types/ngwr-select.d.ts +17 -1
  159. package/types/ngwr-slider.d.ts +14 -1
  160. package/types/ngwr-switch.d.ts +26 -2
  161. package/types/ngwr-textarea.d.ts +3 -0
  162. package/types/ngwr-theme.d.ts +24 -0
  163. package/types/ngwr-transfer.d.ts +17 -1
  164. package/types/ngwr-tree.d.ts +21 -1
  165. package/types/ngwr-version.d.ts +1 -1
  166. package/typewriter/styles/_index.scss +15 -38
  167. package/typewriter/styles/_rules.scss +33 -0
  168. package/typewriter/typewriter.scss +1 -1
  169. package/virtual-scroll/styles/_index.scss +15 -32
  170. package/virtual-scroll/styles/_rules.scss +27 -0
  171. package/virtual-scroll/virtual-scroll.scss +1 -1
  172. package/waves/styles/_index.scss +15 -50
  173. package/waves/styles/_rules.scss +45 -0
  174. package/waves/waves.scss +1 -1
@@ -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);
@@ -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;
@@ -1,5 +1,6 @@
1
1
  import * as _angular_core from '@angular/core';
2
2
  import { InjectionToken, Signal } from '@angular/core';
3
+ import * as ngwr_form from 'ngwr/form';
3
4
  import { FormValueControl } from '@angular/forms/signals';
4
5
 
5
6
  /**
@@ -18,7 +19,10 @@ import { FormValueControl } from '@angular/forms/signals';
18
19
  */
19
20
  type WrRadioSize = 'sm' | 'md' | 'lg';
20
21
  declare class WrRadio {
21
- /** Stable id used to associate the native input with its label. */
22
+ /**
23
+ * Stable id used to associate the native input with its label. Lands on the
24
+ * inner `<input>`; the host never keeps it.
25
+ */
22
26
  readonly id: _angular_core.InputSignal<string>;
23
27
  /** Value selected when this radio is checked. */
24
28
  readonly value: _angular_core.InputSignal<unknown>;
@@ -48,7 +52,20 @@ declare class WrRadio {
48
52
  protected readonly checked: _angular_core.Signal<boolean>;
49
53
  /** Effective disabled — this option's own `disabled` or the group's. */
50
54
  protected readonly isDisabled: _angular_core.Signal<boolean>;
55
+ /**
56
+ * Readonly is a GROUP-level state — a single option cannot be read-only while
57
+ * its siblings are not, because picking any of them rewrites the same value.
58
+ * `aria-readonly` therefore lives on the `role="radiogroup"` host and NOT here:
59
+ * role `radio` does not support it.
60
+ */
61
+ protected readonly isReadonly: _angular_core.Signal<boolean>;
51
62
  protected readonly classes: _angular_core.Signal<string>;
63
+ /**
64
+ * Cancel the selection while the group is read-only. `change` never fires for
65
+ * a click whose default was prevented, so this is the only guard the pointer
66
+ * and Space paths need; `onSelect` keeps its own for a synthetic `change`.
67
+ */
68
+ protected onInputClick(event: Event): void;
52
69
  protected onSelect(): void;
53
70
  protected onBlur(): void;
54
71
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrRadio, never>;
@@ -74,6 +91,8 @@ interface WrRadioGroupContext {
74
91
  readonly value: Signal<unknown>;
75
92
  /** Whether the entire group is disabled. */
76
93
  readonly isDisabled: Signal<boolean>;
94
+ /** Whether the entire group refuses edits while staying focusable. */
95
+ readonly isReadonly: Signal<boolean>;
77
96
  /** Select the given value. */
78
97
  select(value: unknown): void;
79
98
  /**
@@ -124,16 +143,32 @@ declare class WrRadioGroup implements FormValueControl<unknown>, WrRadioGroupCon
124
143
  * @default false
125
144
  */
126
145
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, any>;
146
+ /**
147
+ * Refuse selection changes while every option stays focusable and the value
148
+ * still submits. Bound automatically from the field's readonly state when used
149
+ * with `[formField]`.
150
+ *
151
+ * Native radios ignore the `readonly` attribute, so the group cancels the
152
+ * activation instead and mirrors the state as `aria-readonly`, which role
153
+ * `radiogroup` supports.
154
+ *
155
+ * @default false
156
+ */
157
+ readonly readonly: _angular_core.InputSignalWithTransform<boolean, any>;
158
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
159
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
127
160
  /** The selected radio's value. Bound by `[formField]`, or two-way via `[(value)]`. */
128
161
  readonly value: _angular_core.ModelSignal<unknown>;
129
162
  /** Emitted on blur from any child so a bound field can mark itself touched. */
130
163
  readonly touch: _angular_core.OutputEmitterRef<void>;
131
164
  /** Effective disabled state (WrRadioGroupContext). */
132
165
  readonly isDisabled: _angular_core.Signal<boolean>;
166
+ /** Effective readonly state (WrRadioGroupContext). */
167
+ readonly isReadonly: _angular_core.Signal<boolean>;
133
168
  select(value: unknown): void;
134
169
  markTouched(): void;
135
170
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<WrRadioGroup, never>;
136
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrRadioGroup, "wr-radio-group", never, { "name": { "alias": "name"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "value": { "alias": "value"; "required": false; "isSignal": true; }; }, { "value": "valueChange"; "touch": "touch"; }, never, ["*"], true, never>;
171
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<WrRadioGroup, "wr-radio-group", never, { "name": { "alias": "name"; "required": false; "isSignal": true; }; "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>;
137
172
  }
138
173
 
139
174
  export { WR_RADIO_GROUP, WrRadio, WrRadioGroup };
@@ -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
 
@@ -56,6 +57,17 @@ declare class WrRating implements FormValueControl<number | null> {
56
57
  /** Transient hover preview — overrides `value` for display when set. */
57
58
  protected readonly hoverValue: _angular_core.WritableSignal<number | null>;
58
59
  protected readonly interactive: _angular_core.Signal<boolean>;
60
+ /**
61
+ * Whether the control keeps its tab stop — `disabled` only, never `readonly`.
62
+ *
63
+ * The two states differ precisely here: a disabled control leaves the tab
64
+ * order, a read-only one stays in it and keeps announcing `aria-valuenow`,
65
+ * because a keyboard user still has to be able to READ a value they cannot
66
+ * change. Driving `tabindex` off `interactive()` collapsed that distinction
67
+ * and made `readonly()` unreachable — with an `aria-describedby` pointing at
68
+ * an error message on an element nobody could focus.
69
+ */
70
+ protected readonly focusable: _angular_core.Signal<boolean>;
59
71
  /** What we actually render — hover wins while hovering. */
60
72
  protected readonly displayValue: _angular_core.Signal<number>;
61
73
  protected readonly slots: _angular_core.Signal<number[]>;
@@ -69,6 +81,8 @@ declare class WrRating implements FormValueControl<number | null> {
69
81
  * interaction rather than cached, so a runtime flip needs no subscription.
70
82
  */
71
83
  private readonly directionality;
84
+ /** The surrounding `<wr-form-field>`'s error state. @internal */
85
+ protected readonly fieldAria: ngwr_form.WrFormFieldAria;
72
86
  protected readonly classes: _angular_core.Signal<string>;
73
87
  constructor();
74
88
  /** Fill ratio for slot `i` in `[0, 1]` — drives the CSS clip width. */