@radix-ng/primitives 1.0.9 → 1.1.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 (65) hide show
  1. package/README.md +26 -5
  2. package/fesm2022/radix-ng-primitives-autocomplete.mjs +35 -19
  3. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +55 -7
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-combobox.mjs +46 -21
  7. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  8. package/fesm2022/radix-ng-primitives-composite.mjs +221 -36
  9. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-core.mjs +189 -26
  11. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-date-field.mjs +29 -13
  13. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-field.mjs +127 -23
  15. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +9 -0
  17. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-focus-scope.mjs +241 -95
  19. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-form.mjs +35 -4
  21. package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-input.mjs +87 -15
  23. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-menu.mjs +131 -16
  25. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +17 -3
  27. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-popper.mjs +223 -29
  29. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-portal.mjs +16 -5
  31. package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
  33. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-select.mjs +49 -17
  35. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
  37. package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-time-field.mjs +24 -10
  39. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  40. package/package.json +8 -7
  41. package/types/radix-ng-primitives-autocomplete.d.ts +7 -2
  42. package/types/radix-ng-primitives-checkbox.d.ts +10 -1
  43. package/types/radix-ng-primitives-combobox.d.ts +29 -2
  44. package/types/radix-ng-primitives-composite.d.ts +48 -2
  45. package/types/radix-ng-primitives-core.d.ts +165 -104
  46. package/types/radix-ng-primitives-date-field.d.ts +21 -8
  47. package/types/radix-ng-primitives-field.d.ts +55 -8
  48. package/types/radix-ng-primitives-floating-focus-manager.d.ts +7 -0
  49. package/types/radix-ng-primitives-focus-scope.d.ts +61 -44
  50. package/types/radix-ng-primitives-form.d.ts +9 -0
  51. package/types/radix-ng-primitives-input.d.ts +10 -2
  52. package/types/radix-ng-primitives-menu.d.ts +59 -7
  53. package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
  54. package/types/radix-ng-primitives-popover.d.ts +2 -2
  55. package/types/radix-ng-primitives-popper.d.ts +186 -48
  56. package/types/radix-ng-primitives-portal.d.ts +18 -8
  57. package/types/radix-ng-primitives-preview-card.d.ts +2 -2
  58. package/types/radix-ng-primitives-select.d.ts +17 -3
  59. package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
  60. package/types/radix-ng-primitives-time-field.d.ts +21 -10
  61. package/types/radix-ng-primitives-tooltip.d.ts +2 -2
  62. package/fesm2022/radix-ng-primitives-focus-guards.mjs +0 -53
  63. package/fesm2022/radix-ng-primitives-focus-guards.mjs.map +0 -1
  64. package/focus-guards/README.md +0 -1
  65. package/types/radix-ng-primitives-focus-guards.d.ts +0 -15
@@ -1,6 +1,6 @@
1
1
  import * as i0 from '@angular/core';
2
- import { forwardRef, input, booleanAttribute, output, linkedSignal, untracked, Directive, inject, isDevMode, HOST_TAG_NAME, ElementRef, InjectionToken, computed, APP_ID, Injectable, DOCUMENT, PLATFORM_ID, DestroyRef, signal, model, effect, afterNextRender, assertInInjectionContext, Injector } from '@angular/core';
3
- import { NG_VALUE_ACCESSOR } from '@angular/forms';
2
+ import { forwardRef, input, booleanAttribute, output, linkedSignal, untracked, Directive, inject, Injector, DestroyRef, signal, computed, isDevMode, HOST_TAG_NAME, ElementRef, InjectionToken, APP_ID, Injectable, DOCUMENT, PLATFORM_ID, model, effect, afterNextRender, assertInInjectionContext } from '@angular/core';
3
+ import { NG_VALUE_ACCESSOR, NgControl } from '@angular/forms';
4
4
  import { getLocalTimeZone, CalendarDateTime, ZonedDateTime, getDayOfWeek, DateFormatter, createCalendar, toCalendar, CalendarDate, Time, startOfMonth, endOfMonth, today } from '@internationalized/date';
5
5
  import { isPlatformBrowser, DOCUMENT as DOCUMENT$1 } from '@angular/common';
6
6
 
@@ -113,6 +113,94 @@ function injectControlValueAccessor() {
113
113
  return inject(RdxControlValueAccessor);
114
114
  }
115
115
 
116
+ function toValidationErrors(errors) {
117
+ if (!errors) {
118
+ return [];
119
+ }
120
+ return Object.entries(errors).map(([kind, details]) => {
121
+ const message = typeof details === 'string'
122
+ ? details
123
+ : details && typeof details === 'object' && typeof details['message'] === 'string'
124
+ ? details['message']
125
+ : undefined;
126
+ return message === undefined ? { kind } : { kind, message };
127
+ });
128
+ }
129
+ /**
130
+ * Lazily connects to the same-host `NgControl` without creating a circular dependency while Angular
131
+ * is resolving its `ControlValueAccessor`. The unified `AbstractControl.events` stream mirrors
132
+ * programmatic value, status, validation, and interaction-state changes as well as user-driven ones.
133
+ *
134
+ * Must be called in an injection context.
135
+ *
136
+ * @ignore
137
+ */
138
+ function injectNgControlState() {
139
+ const injector = inject(Injector);
140
+ const destroyRef = inject(DestroyRef);
141
+ const connected = signal(false, { debugName: 'RdxNgControlState.connected' });
142
+ const name = signal(undefined, { debugName: 'RdxNgControlState.name' });
143
+ const value = signal(undefined, { debugName: 'RdxNgControlState.value' });
144
+ const valid = signal(false, { debugName: 'RdxNgControlState.valid' });
145
+ const invalid = signal(false, { debugName: 'RdxNgControlState.invalid' });
146
+ const pending = signal(false, { debugName: 'RdxNgControlState.pending' });
147
+ const disabled = signal(false, { debugName: 'RdxNgControlState.disabled' });
148
+ const rawErrors = signal(null, { debugName: 'RdxNgControlState.rawErrors' });
149
+ const errors = computed(() => toValidationErrors(rawErrors()), {
150
+ debugName: 'RdxNgControlState.errors'
151
+ });
152
+ const dirty = signal(false, { debugName: 'RdxNgControlState.dirty' });
153
+ const touched = signal(false, { debugName: 'RdxNgControlState.touched' });
154
+ let destroyed = false;
155
+ let unsubscribe;
156
+ const connect = () => {
157
+ if (destroyed) {
158
+ return;
159
+ }
160
+ const ngControl = injector.get(NgControl, null, { self: true, optional: true });
161
+ const control = ngControl?.control;
162
+ // Signal Forms' `FormField` is also discoverable through Angular's forms DI integration, but
163
+ // it is not an `AbstractControl` and owns state through signals instead of `control.events`.
164
+ if (!control || typeof control.events?.subscribe !== 'function') {
165
+ return;
166
+ }
167
+ const sync = () => {
168
+ name.set(ngControl.name == null ? undefined : String(ngControl.name));
169
+ value.set(control.value);
170
+ valid.set(control.valid);
171
+ invalid.set(control.invalid);
172
+ pending.set(control.pending);
173
+ disabled.set(control.disabled);
174
+ rawErrors.set(control.errors);
175
+ dirty.set(control.dirty);
176
+ touched.set(control.touched);
177
+ };
178
+ sync();
179
+ const subscription = control.events.subscribe(sync);
180
+ unsubscribe = () => subscription.unsubscribe();
181
+ connected.set(true);
182
+ };
183
+ // `NgControl` resolves its value accessor from this same element. Defer the lookup until every
184
+ // directive on the host has finished constructing to avoid a DI cycle.
185
+ queueMicrotask(connect);
186
+ destroyRef.onDestroy(() => {
187
+ destroyed = true;
188
+ unsubscribe?.();
189
+ });
190
+ return {
191
+ connected: connected.asReadonly(),
192
+ name: name.asReadonly(),
193
+ value: value.asReadonly(),
194
+ valid: valid.asReadonly(),
195
+ invalid: invalid.asReadonly(),
196
+ pending: pending.asReadonly(),
197
+ disabled: disabled.asReadonly(),
198
+ errors,
199
+ dirty: dirty.asReadonly(),
200
+ touched: touched.asReadonly()
201
+ };
202
+ }
203
+
116
204
  /**
117
205
  * The `clamp` function restricts a number within a specified range by returning the value itself if it
118
206
  * falls within the range, or the closest boundary value if it exceeds the range.
@@ -1405,7 +1493,7 @@ function useDateField(props) {
1405
1493
  return prevValue;
1406
1494
  const str = prevValue.toString();
1407
1495
  if (str.length === 1) {
1408
- props.modelValue.set(undefined);
1496
+ props.modelValue.set(null);
1409
1497
  return null;
1410
1498
  }
1411
1499
  return Number.parseInt(str.slice(0, -1));
@@ -2008,22 +2096,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2008
2096
  * of the specified class. This is especially useful when you want multiple
2009
2097
  * tokens (or interfaces) to resolve to the same directive/component instance.
2010
2098
  *
2011
- * @template T - The type associated with the injection token.
2012
- * @param token - The InjectionToken or abstract type you want to provide.
2013
- * @param type - The class type whose existing instance will be used for this token.
2099
+ * `Type<T>` ties the class to the token's contract, so a class that does not
2100
+ * satisfy `T` is rejected at compile time instead of failing at runtime.
2101
+ *
2102
+ * @template T - The type associated with the provider token.
2103
+ * @param token - The token (InjectionToken, class, or abstract class) to provide.
2104
+ * @param type - The class whose existing instance resolves this token; must satisfy `T`.
2014
2105
  * @returns A Provider configuration object for Angular's DI system.
2015
2106
  *
2016
2107
  * @example
2017
2108
  *
2018
2109
  * @Directive({
2019
2110
  * providers: [
2020
- * provideToken(RdxFooToken, RdxFooDirective),
2111
+ * provideExistingToken(RdxFooToken, RdxFooDirective),
2021
2112
  * provideValueAccessor(RdxFooDirective)
2022
2113
  * ]
2023
2114
  * })
2024
2115
  * export class RdxFooDirective {}
2025
2116
  */
2026
- function provideToken(token, type) {
2117
+ function provideExistingToken(token, type) {
2027
2118
  return {
2028
2119
  provide: token,
2029
2120
  useExisting: forwardRef(() => type)
@@ -2032,8 +2123,8 @@ function provideToken(token, type) {
2032
2123
 
2033
2124
  /**
2034
2125
  * Builds the shared form-UI state and its mutators from a control's input signals, removing the
2035
- * per-control copy-paste of the `invalidState`/`touchedState`/`dirtyState` computeds and the
2036
- * `markAsTouched`/`markDirty` logic.
2126
+ * per-control copy-paste of the validation/interaction computeds and the `markAsTouched`/`markDirty`
2127
+ * logic.
2037
2128
  *
2038
2129
  * **Why the inputs stay on the control (not in here):** Angular's compiler only discovers
2039
2130
  * `input()`/`model()` declared as field initializers, and Signal Forms binds form-written state
@@ -2046,22 +2137,58 @@ function provideToken(token, type) {
2046
2137
  function createFormUiState(options) {
2047
2138
  const dirtyValue = signal(false, /* @ts-ignore */
2048
2139
  ...(ngDevMode ? [{ debugName: "dirtyValue" }] : /* istanbul ignore next */ []));
2140
+ const errorsState = computed(() => {
2141
+ const ownErrors = options.errors() ?? [];
2142
+ const ngControlErrors = options.ngControlState?.connected() ? options.ngControlState.errors() : [];
2143
+ return ngControlErrors.length > 0 ? [...ownErrors, ...ngControlErrors] : ownErrors;
2144
+ }, /* @ts-ignore */
2145
+ ...(ngDevMode ? [{ debugName: "errorsState" }] : /* istanbul ignore next */ []));
2146
+ const invalidState = computed(() => options.invalid() ||
2147
+ errorsState().length > 0 ||
2148
+ Boolean(options.ngControlState?.connected() && options.ngControlState.invalid()), /* @ts-ignore */
2149
+ ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
2150
+ const pendingState = computed(() => (options.pending?.() ?? false) ||
2151
+ Boolean(options.ngControlState?.connected() && options.ngControlState.pending()), /* @ts-ignore */
2152
+ ...(ngDevMode ? [{ debugName: "pendingState" }] : /* istanbul ignore next */ []));
2153
+ const validState = computed(() => {
2154
+ if (pendingState()) {
2155
+ return null;
2156
+ }
2157
+ if (invalidState()) {
2158
+ return false;
2159
+ }
2160
+ if (options.ngControlState?.connected() && options.ngControlState.disabled()) {
2161
+ return null;
2162
+ }
2163
+ return true;
2164
+ }, /* @ts-ignore */
2165
+ ...(ngDevMode ? [{ debugName: "validState" }] : /* istanbul ignore next */ []));
2049
2166
  return {
2050
- invalidState: computed(() => options.invalid() || (options.errors()?.length ?? 0) > 0),
2051
- touchedState: computed(() => options.touched()),
2052
- dirtyState: computed(() => options.dirty() || dirtyValue()),
2167
+ invalidState,
2168
+ pendingState,
2169
+ validState,
2170
+ errorsState,
2171
+ touchedState: computed(() => options.ngControlState?.connected() ? options.ngControlState.touched() : options.touched()),
2172
+ dirtyState: computed(() => options.ngControlState?.connected() ? options.ngControlState.dirty() : options.dirty() || dirtyValue()),
2053
2173
  markDirty: () => dirtyValue.set(true),
2054
2174
  markAsTouched: () => {
2055
2175
  options.cva?.markAsTouched();
2056
2176
  options.touched.set(true);
2057
2177
  options.touch.emit();
2058
- }
2178
+ },
2179
+ resetInteractionState: () => {
2180
+ options.touched.set(false);
2181
+ dirtyValue.set(false);
2182
+ },
2183
+ resetDirtyState: () => dirtyValue.set(false)
2059
2184
  };
2060
2185
  }
2061
2186
  /** Spread helper to wire {@link RdxFormUiStateContext} fields from an {@link RdxFormUiState} into a context factory. */
2062
2187
  function formUiStateContext(state) {
2063
2188
  return {
2064
2189
  invalidState: state.invalidState,
2190
+ pendingState: state.pendingState,
2191
+ validState: state.validState,
2065
2192
  touchedState: state.touchedState,
2066
2193
  dirtyState: state.dirtyState,
2067
2194
  markAsTouched: () => state.markAsTouched()
@@ -2081,15 +2208,21 @@ const RDX_FIELD_VALIDITY = new InjectionToken('RdxFieldValidity');
2081
2208
  /**
2082
2209
  * Tri-state display validity: when inside a `Field` the field's gated `validState` is the **single
2083
2210
  * source** (the control reflects it, including its `validationMode` neutral state); standalone, the
2084
- * control's own binary invalidity.
2211
+ * control's own tri-state validity (pending is neutral).
2085
2212
  *
2086
2213
  * **Contract:** inside a `Field` a control's own `invalid` / `errors` inputs are **not** displayed — the
2087
2214
  * Field owns displayed validity. Drive validity through the Field instead: bind `rdxSignalField`
2088
2215
  * (Signal Forms), or set `[invalid]` on `rdxFieldRoot`. The control's own `invalid`/`errors` are for
2089
2216
  * standalone use.
2090
2217
  */
2091
- function resolveDisplayValid(fieldValidity, ownInvalid) {
2092
- return fieldValidity ? fieldValidity() : ownInvalid() ? false : true;
2218
+ function resolveDisplayValid(fieldValidity, ownInvalid, ownPending) {
2219
+ if (fieldValidity) {
2220
+ return fieldValidity();
2221
+ }
2222
+ if (ownPending?.()) {
2223
+ return null;
2224
+ }
2225
+ return ownInvalid() ? false : true;
2093
2226
  }
2094
2227
  /**
2095
2228
  * Provide a control's {@link RdxFormUiState} on its host element so {@link RdxFormUiStateHost} can
@@ -2126,7 +2259,7 @@ class RdxFormUiStateHost {
2126
2259
  this.formUi = inject(RDX_FORM_UI_STATE);
2127
2260
  this.fieldValidity = inject(RDX_FIELD_VALIDITY, { optional: true });
2128
2261
  /** @ignore Tri-state display validity (enclosing Field's gated state, else own invalidity). */
2129
- this.displayValid = computed(() => resolveDisplayValid(this.fieldValidity, this.formUi.invalidState), /* @ts-ignore */
2262
+ this.displayValid = computed(() => this.fieldValidity ? this.fieldValidity() : this.formUi.validState(), /* @ts-ignore */
2130
2263
  ...(ngDevMode ? [{ debugName: "displayValid" }] : /* istanbul ignore next */ []));
2131
2264
  }
2132
2265
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFormUiStateHost, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
@@ -2149,7 +2282,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
2149
2282
  }] });
2150
2283
  /**
2151
2284
  * Abstract base that declares the optional `FormUiControl` state inputs
2152
- * (`invalid`/`errors`/`touched`/`dirty` + the `touch` output) once and builds the control's
2285
+ * (`invalid`/`pending`/`errors`/`touched`/`dirty` + the `touch` output) once and builds the control's
2153
2286
  * {@link RdxFormUiState} from them, so a control directive can inherit the whole surface with a
2154
2287
  * single `extends` instead of re-declaring it.
2155
2288
  *
@@ -2167,6 +2300,8 @@ class RdxFormUiControlBase {
2167
2300
  constructor() {
2168
2301
  /** Whether the control is invalid. A non-empty {@link errors} list also marks it invalid. */
2169
2302
  this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2303
+ /** Whether async validation is pending. Pending controls publish neither `data-valid` nor `data-invalid`. */
2304
+ this.pending = input(false, { ...(ngDevMode ? { debugName: "pending" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
2170
2305
  /** Whether the control has been touched. A `model()` so Signal Forms can write it; set on blur/focus-out. */
2171
2306
  this.touched = model(false, /* @ts-ignore */
2172
2307
  ...(ngDevMode ? [{ debugName: "touched" }] : /* istanbul ignore next */ []));
@@ -2177,15 +2312,20 @@ class RdxFormUiControlBase {
2177
2312
  ...(ngDevMode ? [{ debugName: "errors" }] : /* istanbul ignore next */ []));
2178
2313
  /** Emits when the control is touched, notifying Signal Forms (stable Angular 22 contract). */
2179
2314
  this.touch = output();
2315
+ this.ngControlState = injectNgControlState();
2180
2316
  /** The shared form-UI state derived from the inputs above. Call `formUi.markDirty()` on value change. */
2181
2317
  this.formUi = createFormUiState({
2182
2318
  invalid: this.invalid,
2319
+ pending: this.pending,
2183
2320
  errors: this.errors,
2184
2321
  touched: this.touched,
2185
2322
  touch: this.touch,
2186
2323
  dirty: this.dirty,
2324
+ ngControlState: this.ngControlState,
2187
2325
  cva: this.formUiTouchTarget()
2188
2326
  });
2327
+ /** Validation errors from the control inputs and a same-host Reactive/template-driven form control. */
2328
+ this.validationErrors = this.formUi.errorsState;
2189
2329
  /** The enclosing Field's tri-state display validity, if any. `protected` so a control whose own
2190
2330
  * invalidity is richer than `formUi.invalidState` (e.g. date/time-field add a parse check) can build
2191
2331
  * its own `displayValid` from it. */
@@ -2193,27 +2333,50 @@ class RdxFormUiControlBase {
2193
2333
  /**
2194
2334
  * Tri-state *displayed* validity for controls that bind their own host attributes (radio, switch,
2195
2335
  * number-field): the enclosing Field's gated state when inside a `rdxFieldRoot`, else this control's
2196
- * own binary invalidity (`formUi.invalidState`). Bind `data-valid`/`data-invalid`/`aria-invalid` to
2336
+ * own validity (`formUi.pendingState` is neutral; otherwise `formUi.invalidState` is binary). Bind
2337
+ * `data-valid`/`data-invalid`/`aria-invalid` to
2197
2338
  * this so a neutral field shows neither. Controls whose `invalidState` is richer than
2198
- * `formUi.invalidState` override this with `resolveDisplayValid(this.fieldValidity, this.invalidState)`.
2339
+ * `formUi.invalidState` override this with
2340
+ * `resolveDisplayValid(this.fieldValidity, this.invalidState, this.formUi.pendingState)`.
2199
2341
  */
2200
- this.displayValid = computed(() => resolveDisplayValid(this.fieldValidity, this.formUi.invalidState), /* @ts-ignore */
2342
+ this.displayValid = computed(() => this.fieldValidity ? this.fieldValidity() : this.formUi.validState(), /* @ts-ignore */
2201
2343
  ...(ngDevMode ? [{ debugName: "displayValid" }] : /* istanbul ignore next */ []));
2344
+ // Reactive/template-driven forms own their NgControl interaction state. Signal Forms instead
2345
+ // writes the public touched/dirty members, so standalone controls retain the existing fallback.
2346
+ effect(() => {
2347
+ if (this.ngControlState.connected()) {
2348
+ this.touched.set(this.ngControlState.touched());
2349
+ if (!this.ngControlState.dirty()) {
2350
+ this.formUi.resetDirtyState?.();
2351
+ }
2352
+ return;
2353
+ }
2354
+ if (!this.dirty()) {
2355
+ this.formUi.resetDirtyState?.();
2356
+ }
2357
+ }, { debugName: 'RdxFormUiControlBase.syncInteractionState' });
2202
2358
  }
2203
2359
  /**
2204
2360
  * Override to bridge the control's `ControlValueAccessor` into `markAsTouched` (dual controls —
2205
2361
  * return `injectControlValueAccessor()` or a `{ markAsTouched }` adapter). Default: no CVA
2206
- * (Signal-Forms-only controls such as select).
2362
+ * (Signal-Forms-only controls such as date-field).
2207
2363
  */
2208
2364
  formUiTouchTarget() {
2209
2365
  return null;
2210
2366
  }
2367
+ /**
2368
+ * Reset control-owned interaction state. Angular Signal Forms calls this optional custom-control
2369
+ * hook from `FieldState.reset()` after restoring the field value.
2370
+ */
2371
+ reset() {
2372
+ this.formUi.resetInteractionState?.();
2373
+ }
2211
2374
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFormUiControlBase, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
2212
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFormUiControlBase, isStandalone: true, inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touched: "touchedChange", touch: "touch" }, ngImport: i0 }); }
2375
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFormUiControlBase, isStandalone: true, inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, pending: { classPropertyName: "pending", publicName: "pending", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, errors: { classPropertyName: "errors", publicName: "errors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { touched: "touchedChange", touch: "touch" }, ngImport: i0 }); }
2213
2376
  }
2214
2377
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFormUiControlBase, decorators: [{
2215
2378
  type: Directive
2216
- }], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
2379
+ }], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], pending: [{ type: i0.Input, args: [{ isSignal: true, alias: "pending", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }, { type: i0.Output, args: ["touchedChange"] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], errors: [{ type: i0.Input, args: [{ isSignal: true, alias: "errors", required: false }] }], touch: [{ type: i0.Output, args: ["touch"] }] } });
2217
2380
 
2218
2381
  /** The default validation-display mode when neither `rdxFieldRoot` nor `rdxFormRoot` sets one. */
2219
2382
  const RDX_DEFAULT_VALIDATION_MODE = 'onBlur';
@@ -4281,5 +4444,5 @@ var RdxPositionAlign;
4281
4444
  * Generated bundle index. Do not edit.
4282
4445
  */
4283
4446
 
4284
- export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_DEFAULT_VALIDATION_MODE, RDX_FIELD_VALIDITY, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_FORM_UI_STATE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxFormUiControlBase, RdxFormUiStateHost, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormUiState, createFormatter, createMonth, createMonths, docsUrl, elementSize, formUiStateContext, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isValidationRevealed, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideFormUiState, provideToken, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveDisplayValid, resolveFloatingTree, roundToStepPrecision, segmentBuilders, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, usePointerDrag, useScrollLock, useTransitionStatus, watch };
4447
+ export { A, ALT, ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP, ASTERISK, BACKSPACE, CAPS_LOCK, CONTROL, CTRL, DELETE, DOCS_BASE_URL, END, ENTER, ESCAPE, F1, F10, F11, F12, F2, F3, F4, F5, F6, F7, F8, F9, HOME, META, P, PAGE_DOWN, PAGE_UP, RDX_DEFAULT_VALIDATION_MODE, RDX_FIELD_VALIDITY, RDX_FLOATING_REGISTRATION, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_TREE, RDX_FORM_UI_STATE, RDX_INTERNAL_BACKDROP_ATTR, RDX_SCROLL_LOCKED_ATTR, RdxControlValueAccessor, RdxFloatingNode, RdxFloatingNodeRegistration, RdxFloatingRegistrationContext, RdxFloatingRootContext, RdxFloatingTree, RdxFormUiControlBase, RdxFormUiStateHost, RdxIdGenerator, RdxLiveAnnouncer, RdxPositionAlign, RdxPositionSide, RdxTriggerRegistry, SHIFT, SPACE, SPACE_CODE, TAB, TIME_GRANULARITIES, a, areAllDaysBetweenValid, clamp, createCancelableChangeEventDetails, createContent, createContext, createFloatingEvents, createFloatingRootContext, createFormUiState, createFormatter, createMonth, createMonths, docsUrl, elementSize, formUiStateContext, getActiveElement, getDaysBetween, getDaysInMonth, getDefaultDate, getDefaultTime, getLastFirstDayOfWeek, getMaxTransitionDuration, getNextLastDayOfWeek, getOptsByGranularity, getPlaceholder, getSegmentElements, getWeekNumber, handleAndDispatchCustomEvent, handleCalendarInitialFocus, hasTime, initializeSegmentValues, injectControlValueAccessor, injectDocument, injectFloatingRootContext, injectId, injectNgControlState, isAcceptableSegmentKey, isAfter, isAfterOrSame, isBefore, isBeforeOrSame, isBetween, isBetweenInclusive, isCalendarDateTime, isEqual, isItemEqualToValue, isNullish, isNumberString, isSegmentNavigationKey, isValidationRevealed, isZonedDateTime, itemToStringLabel, itemToStringValue, j, k, n, normalizeDateStep, normalizeHour12, normalizeHourCycle, p, provideExistingToken, provideFloatingRegistration, provideFloatingRootContext, provideFloatingTree, provideFormUiState, provideValueAccessor, rdxCheckLabelElement, rdxCheckTriggerElement, rdxDevError, rdxDevWarning, resetRdxDevWarnings, resizeEffect, resolveDisplayValid, resolveFloatingTree, roundToStepPrecision, segmentBuilders, setupInternalBackdrop, snapValueToStep, syncSegmentValues, syncTimeSegmentValues, toDate, useAnchoredScrollLock, useArrowNavigation, useDateField, useFilter, useGraceArea, useListHighlight, usePointerDrag, useScrollLock, useTransitionStatus, watch };
4285
4448
  //# sourceMappingURL=radix-ng-primitives-core.mjs.map