@radix-ng/primitives 1.0.7 → 1.0.8
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.
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +43 -9
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +89 -8
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +43 -9
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +208 -2
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +50 -9
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-editable.mjs +31 -5
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +265 -46
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +110 -26
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-input.mjs +16 -6
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +32 -7
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +18 -4
- package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +26 -8
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-signal-forms.mjs +184 -0
- package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-slider.mjs +24 -8
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +29 -5
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +49 -9
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +23 -12
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/package.json +11 -1
- package/types/radix-ng-primitives-autocomplete.d.ts +21 -2
- package/types/radix-ng-primitives-checkbox.d.ts +53 -4
- package/types/radix-ng-primitives-combobox.d.ts +51 -2
- package/types/radix-ng-primitives-core.d.ts +206 -3
- package/types/radix-ng-primitives-date-field.d.ts +27 -2
- package/types/radix-ng-primitives-editable.d.ts +15 -2
- package/types/radix-ng-primitives-field.d.ts +136 -33
- package/types/radix-ng-primitives-form.d.ts +115 -21
- package/types/radix-ng-primitives-input.d.ts +8 -1
- package/types/radix-ng-primitives-number-field.d.ts +14 -3
- package/types/radix-ng-primitives-radio.d.ts +7 -2
- package/types/radix-ng-primitives-select.d.ts +11 -3
- package/types/radix-ng-primitives-signal-forms.d.ts +97 -0
- package/types/radix-ng-primitives-slider.d.ts +9 -7
- package/types/radix-ng-primitives-switch.d.ts +15 -2
- package/types/radix-ng-primitives-time-field.d.ts +26 -2
- package/types/radix-ng-primitives-toggle-group.d.ts +6 -4
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { input, booleanAttribute, signal, computed, inject, ElementRef, DestroyRef, Directive, effect, afterNextRender } from '@angular/core';
|
|
3
|
-
import { createContext, injectId, rdxCheckLabelElement } from '@radix-ng/primitives/core';
|
|
3
|
+
import { createContext, injectId, RDX_DEFAULT_VALIDATION_MODE, isValidationRevealed, RDX_FIELD_VALIDITY, rdxCheckLabelElement } from '@radix-ng/primitives/core';
|
|
4
4
|
import { injectFormRootContext } from '@radix-ng/primitives/form';
|
|
5
5
|
|
|
6
|
-
const attr$
|
|
7
|
-
const addId = (ids, id) => (ids.includes(id) ? ids : [...ids, id]);
|
|
8
|
-
const removeId = (ids, id) => ids.filter((item) => item !== id);
|
|
6
|
+
const attr$5 = (value) => (value ? '' : undefined);
|
|
7
|
+
const addId$1 = (ids, id) => (ids.includes(id) ? ids : [...ids, id]);
|
|
8
|
+
const removeId$1 = (ids, id) => ids.filter((item) => item !== id);
|
|
9
9
|
const fieldRootContext = () => {
|
|
10
10
|
const root = injectFieldRoot();
|
|
11
11
|
return {
|
|
@@ -13,10 +13,13 @@ const fieldRootContext = () => {
|
|
|
13
13
|
name: root.name,
|
|
14
14
|
descriptionIds: root.descriptionIds,
|
|
15
15
|
errorIds: root.errorIds,
|
|
16
|
-
/** Combined
|
|
16
|
+
/** Combined messages for `RdxFieldError`: client (provider + form name-routing, when revealed) then
|
|
17
|
+
* server (`errors` input, always). */
|
|
17
18
|
messages: root.messages,
|
|
18
19
|
/** Notify an enclosing Form that this field's control was edited (composite-control opt-in). */
|
|
19
20
|
notifyEdited: () => root.notifyEdited(),
|
|
21
|
+
validState: root.validState,
|
|
22
|
+
formSubmitAttempted: root.formSubmitAttempted,
|
|
20
23
|
invalidState: root.invalidState,
|
|
21
24
|
disabledState: root.disabledState,
|
|
22
25
|
requiredState: root.requiredState,
|
|
@@ -25,10 +28,10 @@ const fieldRootContext = () => {
|
|
|
25
28
|
filledState: root.filledState,
|
|
26
29
|
focusedState: root.focusedState,
|
|
27
30
|
setControlId: (id) => root.controlId.set(id),
|
|
28
|
-
addDescriptionId: (id) => root.descriptionIds.update((ids) => addId(ids, id)),
|
|
29
|
-
removeDescriptionId: (id) => root.descriptionIds.update((ids) => removeId(ids, id)),
|
|
30
|
-
addErrorId: (id) => root.errorIds.update((ids) => addId(ids, id)),
|
|
31
|
-
removeErrorId: (id) => root.errorIds.update((ids) => removeId(ids, id)),
|
|
31
|
+
addDescriptionId: (id) => root.descriptionIds.update((ids) => addId$1(ids, id)),
|
|
32
|
+
removeDescriptionId: (id) => root.descriptionIds.update((ids) => removeId$1(ids, id)),
|
|
33
|
+
addErrorId: (id) => root.errorIds.update((ids) => addId$1(ids, id)),
|
|
34
|
+
removeErrorId: (id) => root.errorIds.update((ids) => removeId$1(ids, id)),
|
|
32
35
|
setFocused: (value) => root.focusedValue.set(value),
|
|
33
36
|
setFilled: (value) => root.filledValue.set(value),
|
|
34
37
|
setDirty: (value) => root.dirtyValue.set(value),
|
|
@@ -40,6 +43,7 @@ const fieldRootContext = () => {
|
|
|
40
43
|
* provider so adapters can restore it on teardown.
|
|
41
44
|
*/
|
|
42
45
|
setStateProvider: (provider) => root.setStateProvider(provider),
|
|
46
|
+
clearStateProvider: (provider, previous) => root.clearStateProvider(provider, previous),
|
|
43
47
|
hasStateProvider: root.hasStateProvider
|
|
44
48
|
};
|
|
45
49
|
};
|
|
@@ -108,6 +112,14 @@ class RdxFieldRoot {
|
|
|
108
112
|
*/
|
|
109
113
|
this.name = input(/* @ts-ignore */
|
|
110
114
|
...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
|
|
115
|
+
/**
|
|
116
|
+
* Overrides when this field reveals its validity (error styling + message). Falls back to the
|
|
117
|
+
* enclosing `rdxFormRoot`'s `validationMode`, then `'onBlur'`. Server errors always show regardless.
|
|
118
|
+
*
|
|
119
|
+
* @group Props
|
|
120
|
+
*/
|
|
121
|
+
this.validationMode = input(/* @ts-ignore */
|
|
122
|
+
...(ngDevMode ? [undefined, { debugName: "validationMode" }] : /* istanbul ignore next */ []));
|
|
111
123
|
/** The enclosing Form, if any. All Form-related behavior is a no-op when this is `null`. */
|
|
112
124
|
this.formContext = injectFormRootContext(true);
|
|
113
125
|
this.controlId = signal(injectId('rdx-field-control-'), /* @ts-ignore */
|
|
@@ -133,37 +145,108 @@ class RdxFieldRoot {
|
|
|
133
145
|
/** Error content from a registered state provider (e.g. a Signal Forms adapter). */
|
|
134
146
|
this.providerErrors = computed(() => this.stateProvider()?.errors?.() ?? [], /* @ts-ignore */
|
|
135
147
|
...(ngDevMode ? [{ debugName: "providerErrors" }] : /* istanbul ignore next */ []));
|
|
136
|
-
/**
|
|
137
|
-
this.
|
|
138
|
-
...(ngDevMode ? [{ debugName: "
|
|
139
|
-
/**
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
148
|
+
/** **Client** validation errors routed by a form-level provider (`rdxSignalForm`) — gated by `validationMode`. */
|
|
149
|
+
this.clientErrors = computed(() => this.formContext?.clientErrorsFor(this.name()) ?? [], /* @ts-ignore */
|
|
150
|
+
...(ngDevMode ? [{ debugName: "clientErrors" }] : /* istanbul ignore next */ []));
|
|
151
|
+
/** **Server/external** errors from the Form's `errors` input — shown eagerly (never gated). */
|
|
152
|
+
this.serverErrors = computed(() => this.formContext?.externalErrorsFor(this.name()) ?? [], /* @ts-ignore */
|
|
153
|
+
...(ngDevMode ? [{ debugName: "serverErrors" }] : /* istanbul ignore next */ []));
|
|
154
|
+
/** Whether the enclosing Form has had a submit attempted; `false` when standalone. A presentation
|
|
155
|
+
* seam an adapter can read to reveal errors after a submit attempt (Base UI's submit-attempt state). */
|
|
156
|
+
this.formSubmitAttempted = computed(() => this.formContext?.submitAttempted() ?? false, /* @ts-ignore */
|
|
157
|
+
...(ngDevMode ? [{ debugName: "formSubmitAttempted" }] : /* istanbul ignore next */ []));
|
|
158
|
+
/**
|
|
159
|
+
* Client messages first (per-field `rdxSignalField` provider, then form-level `rdxSignalForm`
|
|
160
|
+
* name-routing) — only once `validationMode` reveals client validity — then the Form's **server**
|
|
161
|
+
* messages (the `errors` input), which always show. **Deduped by text**: a field can receive the same
|
|
162
|
+
* Signal Forms error from both a per-field provider and the form-level routing (they read the same
|
|
163
|
+
* field state), so deduping renders it once while still surfacing distinct messages from each source.
|
|
164
|
+
*/
|
|
165
|
+
this.messages = computed(() => {
|
|
166
|
+
// **Client** messages (per-field `rdxSignalField` provider + form-level `rdxSignalForm` routing)
|
|
167
|
+
// surface only once the field's `validationMode` reveals them; **server** messages (the `errors`
|
|
168
|
+
// input) always show. So a neutral field stays empty and the polite live region announces on reveal.
|
|
169
|
+
const client = this.validationRevealed()
|
|
170
|
+
? [...this.providerErrors().map((error) => error.message ?? error.kind), ...this.clientErrors()]
|
|
171
|
+
: [];
|
|
172
|
+
const seen = new Set();
|
|
173
|
+
const result = [];
|
|
174
|
+
for (const message of [...client, ...this.serverErrors()]) {
|
|
175
|
+
if (message && !seen.has(message)) {
|
|
176
|
+
seen.add(message);
|
|
177
|
+
result.push(message);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}, /* @ts-ignore */
|
|
144
182
|
...(ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []));
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
183
|
+
/** Effective validation-display mode: this field's override → the enclosing Form's → the default. */
|
|
184
|
+
this.effectiveValidationMode = computed(() => this.validationMode() ?? this.formContext?.validationMode() ?? RDX_DEFAULT_VALIDATION_MODE, /* @ts-ignore */
|
|
185
|
+
...(ngDevMode ? [{ debugName: "effectiveValidationMode" }] : /* istanbul ignore next */ []));
|
|
186
|
+
/** Whether client-side validity is revealed yet, per {@link effectiveValidationMode} + interaction. */
|
|
187
|
+
this.validationRevealed = computed(() => isValidationRevealed(this.effectiveValidationMode(), {
|
|
188
|
+
touched: this.touchedState(),
|
|
189
|
+
dirty: this.dirtyState(),
|
|
190
|
+
submitAttempted: this.formSubmitAttempted()
|
|
191
|
+
}), /* @ts-ignore */
|
|
192
|
+
...(ngDevMode ? [{ debugName: "validationRevealed" }] : /* istanbul ignore next */ []));
|
|
193
|
+
/**
|
|
194
|
+
* Client-side invalidity (gated by `validationMode`): the per-field provider's error content / `invalid`
|
|
195
|
+
* (`rdxSignalField`), a form-level provider's name-routed errors (`rdxSignalForm`), or the `[invalid]`
|
|
196
|
+
* input. Excludes server errors (which are eager).
|
|
197
|
+
*/
|
|
198
|
+
this.clientInvalidState = computed(() => this.providerErrors().length > 0 ||
|
|
199
|
+
this.clientErrors().length > 0 ||
|
|
200
|
+
this.resolve('invalid', () => this.invalid()), /* @ts-ignore */
|
|
201
|
+
...(ngDevMode ? [{ debugName: "clientInvalidState" }] : /* istanbul ignore next */ []));
|
|
202
|
+
/**
|
|
203
|
+
* Tri-state *displayed* validity (`boolean | null`) — the source for `data-valid` / `data-invalid`.
|
|
204
|
+
* Server errors show immediately; client-side validity (including `rdxSignalForm` name-routing) stays
|
|
205
|
+
* **neutral** (`null`) until the field's {@link effectiveValidationMode} reveals it, then `false`/`true`.
|
|
206
|
+
*/
|
|
207
|
+
this.validState = computed(() => {
|
|
208
|
+
if (this.serverErrors().length > 0) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
if (!this.validationRevealed()) {
|
|
212
|
+
return null;
|
|
150
213
|
}
|
|
151
|
-
return this.
|
|
214
|
+
return this.clientInvalidState() ? false : true;
|
|
152
215
|
}, /* @ts-ignore */
|
|
216
|
+
...(ngDevMode ? [{ debugName: "validState" }] : /* istanbul ignore next */ []));
|
|
217
|
+
/**
|
|
218
|
+
* Boolean **displayed** invalidity (= `validState() === false`), used by `rdxFieldError` (hidden) and
|
|
219
|
+
* `rdxFieldControl`'s `aria-describedby` error linking. A neutral (`null`) `validState` reads as not
|
|
220
|
+
* invalid here, so the error region stays hidden until the field's mode reveals it.
|
|
221
|
+
*/
|
|
222
|
+
this.invalidState = computed(() => this.validState() === false, /* @ts-ignore */
|
|
153
223
|
...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
|
|
224
|
+
/**
|
|
225
|
+
* Boolean **actual** invalidity — ungated by the display mode. The enclosing Form aggregates this into
|
|
226
|
+
* `anyInvalid` (→ submit-block / focus-first-invalid), so a field that is really invalid but displayed
|
|
227
|
+
* neutral still blocks submit. The form's presentation `data-invalid` comes from the *displayed*
|
|
228
|
+
* aggregate (`anyDisplayedInvalid`) instead — not from this. Server errors + provider error content +
|
|
229
|
+
* the provider/input `invalid`.
|
|
230
|
+
*/
|
|
231
|
+
this.actualInvalidState = computed(() => this.serverErrors().length > 0 || this.clientInvalidState(), /* @ts-ignore */
|
|
232
|
+
...(ngDevMode ? [{ debugName: "actualInvalidState" }] : /* istanbul ignore next */ []));
|
|
154
233
|
this.disabledState = computed(() => this.resolve('disabled', () => this.disabled()), /* @ts-ignore */
|
|
155
234
|
...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
156
235
|
this.requiredState = computed(() => this.resolve('required', () => this.required()), /* @ts-ignore */
|
|
157
236
|
...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
158
|
-
|
|
237
|
+
// `touched`/`dirty` also OR in the enclosing Form's per-name state (`rdxSignalForm` name-routing), so a
|
|
238
|
+
// field with only a bare `[formField]` (no `rdxSignalField`/`rdxFieldControl`) still reveals on blur.
|
|
239
|
+
this.dirtyState = computed(() => this.resolve('dirty', () => this.dirty() || this.dirtyValue()) ||
|
|
240
|
+
(this.formContext?.dirtyFor(this.name()) ?? false), /* @ts-ignore */
|
|
159
241
|
...(ngDevMode ? [{ debugName: "dirtyState" }] : /* istanbul ignore next */ []));
|
|
160
|
-
this.touchedState = computed(() => this.resolve('touched', () => this.touched() || this.touchedValue())
|
|
242
|
+
this.touchedState = computed(() => this.resolve('touched', () => this.touched() || this.touchedValue()) ||
|
|
243
|
+
(this.formContext?.touchedFor(this.name()) ?? false), /* @ts-ignore */
|
|
161
244
|
...(ngDevMode ? [{ debugName: "touchedState" }] : /* istanbul ignore next */ []));
|
|
162
245
|
this.filledState = computed(() => this.resolve('filled', () => this.filled() ?? this.filledValue()), /* @ts-ignore */
|
|
163
246
|
...(ngDevMode ? [{ debugName: "filledState" }] : /* istanbul ignore next */ []));
|
|
164
247
|
this.focusedState = computed(() => this.resolve('focused', () => this.focused() ?? this.focusedValue()), /* @ts-ignore */
|
|
165
248
|
...(ngDevMode ? [{ debugName: "focusedState" }] : /* istanbul ignore next */ []));
|
|
166
|
-
this.dataAttr = attr$
|
|
249
|
+
this.dataAttr = attr$5;
|
|
167
250
|
this.host = inject(ElementRef).nativeElement;
|
|
168
251
|
// Register with an enclosing Form (if any) for aggregate state, submit guard, and reset; a
|
|
169
252
|
// standalone field never enters this branch and behaves exactly as before.
|
|
@@ -171,7 +254,11 @@ class RdxFieldRoot {
|
|
|
171
254
|
if (formContext) {
|
|
172
255
|
const registration = {
|
|
173
256
|
name: () => this.name(),
|
|
174
|
-
invalid
|
|
257
|
+
// `invalid` reports *actual* validity (ungated) — `form.anyInvalid` + the submit guard must
|
|
258
|
+
// reflect real state even before a field reveals it. `displayValid` reports the *gated*
|
|
259
|
+
// tri-state so the form's presentation `data-invalid` stays neutral on load.
|
|
260
|
+
invalid: () => this.actualInvalidState(),
|
|
261
|
+
displayValid: () => this.validState(),
|
|
175
262
|
dirty: () => this.dirtyState(),
|
|
176
263
|
touched: () => this.touchedState(),
|
|
177
264
|
focus: () => this.focusControl(),
|
|
@@ -215,26 +302,47 @@ class RdxFieldRoot {
|
|
|
215
302
|
this.stateProvider.set(provider);
|
|
216
303
|
return previous;
|
|
217
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Identity-checked teardown — roll back to `previous` only if `provider` is still active. Prevents
|
|
307
|
+
* an old adapter's destroy from clobbering a newer adapter that registered after it (create-before-
|
|
308
|
+
* destroy during a structural view swap).
|
|
309
|
+
*/
|
|
310
|
+
clearStateProvider(provider, previous) {
|
|
311
|
+
if (this.stateProvider() === provider) {
|
|
312
|
+
this.stateProvider.set(previous);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
218
315
|
/**
|
|
219
316
|
* Prefer the registered provider's value for `key` when it exposes one,
|
|
220
|
-
* otherwise fall back to the root inputs / DOM-derived signals.
|
|
317
|
+
* otherwise fall back to the root inputs / DOM-derived signals. `errors` (content) and `valid`
|
|
318
|
+
* (tri-state `boolean | null`) are resolved separately, so they're excluded from this boolean key.
|
|
221
319
|
*/
|
|
222
320
|
resolve(key, fallback) {
|
|
223
321
|
const accessor = this.stateProvider()?.[key];
|
|
224
322
|
return accessor ? accessor() : fallback();
|
|
225
323
|
}
|
|
226
324
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
227
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldRoot, isStandalone: true, selector: "[rdxFieldRoot]", inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, filled: { classPropertyName: "filled", publicName: "filled", isSignal: true, isRequired: false, transformFunction: null }, focused: { classPropertyName: "focused", publicName: "focused", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "input": "notifyEdited()", "change": "notifyEdited()" }, properties: { "attr.data-invalid": "dataAttr(
|
|
325
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldRoot, isStandalone: true, selector: "[rdxFieldRoot]", inputs: { invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, dirty: { classPropertyName: "dirty", publicName: "dirty", isSignal: true, isRequired: false, transformFunction: null }, touched: { classPropertyName: "touched", publicName: "touched", isSignal: true, isRequired: false, transformFunction: null }, filled: { classPropertyName: "filled", publicName: "filled", isSignal: true, isRequired: false, transformFunction: null }, focused: { classPropertyName: "focused", publicName: "focused", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, validationMode: { classPropertyName: "validationMode", publicName: "validationMode", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "input": "notifyEdited()", "change": "notifyEdited()" }, properties: { "attr.data-invalid": "dataAttr(validState() === false)", "attr.data-valid": "dataAttr(validState() === true)", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-dirty": "dataAttr(dirtyState())", "attr.data-touched": "dataAttr(touchedState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, providers: [
|
|
326
|
+
provideFieldRootContext(fieldRootContext),
|
|
327
|
+
// Expose the tri-state display validity so controls inside the field reflect it (single source).
|
|
328
|
+
{ provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldRoot).validState }
|
|
329
|
+
], exportAs: ["rdxFieldRoot"], ngImport: i0 }); }
|
|
228
330
|
}
|
|
229
331
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldRoot, decorators: [{
|
|
230
332
|
type: Directive,
|
|
231
333
|
args: [{
|
|
232
334
|
selector: '[rdxFieldRoot]',
|
|
233
335
|
exportAs: 'rdxFieldRoot',
|
|
234
|
-
providers: [
|
|
336
|
+
providers: [
|
|
337
|
+
provideFieldRootContext(fieldRootContext),
|
|
338
|
+
// Expose the tri-state display validity so controls inside the field reflect it (single source).
|
|
339
|
+
{ provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldRoot).validState }
|
|
340
|
+
],
|
|
235
341
|
host: {
|
|
236
|
-
|
|
237
|
-
|
|
342
|
+
// Tri-state: `data-invalid` when displayed-invalid, `data-valid` only when displayed-valid,
|
|
343
|
+
// and **neither** when neutral (`validState()` is `null`).
|
|
344
|
+
'[attr.data-invalid]': 'dataAttr(validState() === false)',
|
|
345
|
+
'[attr.data-valid]': 'dataAttr(validState() === true)',
|
|
238
346
|
'[attr.data-disabled]': 'dataAttr(disabledState())',
|
|
239
347
|
'[attr.data-required]': 'dataAttr(requiredState())',
|
|
240
348
|
'[attr.data-dirty]': 'dataAttr(dirtyState())',
|
|
@@ -245,12 +353,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
245
353
|
'(change)': 'notifyEdited()'
|
|
246
354
|
}
|
|
247
355
|
}]
|
|
248
|
-
}], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], filled: [{ type: i0.Input, args: [{ isSignal: true, alias: "filled", required: false }] }], focused: [{ type: i0.Input, args: [{ isSignal: true, alias: "focused", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }] } });
|
|
356
|
+
}], ctorParameters: () => [], propDecorators: { invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], dirty: [{ type: i0.Input, args: [{ isSignal: true, alias: "dirty", required: false }] }], touched: [{ type: i0.Input, args: [{ isSignal: true, alias: "touched", required: false }] }], filled: [{ type: i0.Input, args: [{ isSignal: true, alias: "filled", required: false }] }], focused: [{ type: i0.Input, args: [{ isSignal: true, alias: "focused", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], validationMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "validationMode", required: false }] }] } });
|
|
249
357
|
function injectFieldRoot() {
|
|
250
358
|
return inject(RdxFieldRoot);
|
|
251
359
|
}
|
|
252
360
|
|
|
253
|
-
const attr$
|
|
361
|
+
const attr$4 = (value) => (value ? '' : undefined);
|
|
254
362
|
/**
|
|
255
363
|
* Connects a form control to the field label, description, error, and state.
|
|
256
364
|
*
|
|
@@ -276,7 +384,7 @@ class RdxFieldControl {
|
|
|
276
384
|
return ids.length ? ids.join(' ') : undefined;
|
|
277
385
|
}, /* @ts-ignore */
|
|
278
386
|
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
279
|
-
this.dataAttr = attr$
|
|
387
|
+
this.dataAttr = attr$4;
|
|
280
388
|
effect(() => {
|
|
281
389
|
this.rootContext.setControlId(this.id());
|
|
282
390
|
});
|
|
@@ -305,7 +413,7 @@ class RdxFieldControl {
|
|
|
305
413
|
return element.value ?? '';
|
|
306
414
|
}
|
|
307
415
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldControl, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
308
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldControl, isStandalone: true, selector: "[rdxFieldControl]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "input": "syncFilled()", "change": "syncFilled()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "rootContext.
|
|
416
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldControl, isStandalone: true, selector: "[rdxFieldControl]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "focus": "onFocus()", "blur": "onBlur()", "input": "syncFilled()", "change": "syncFilled()" }, properties: { "attr.id": "id()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "rootContext.validState() === false ? \"true\" : undefined", "attr.aria-required": "!isNativeFormControl() && rootContext.requiredState() ? \"true\" : undefined", "attr.aria-disabled": "!isNativeFormControl() && rootContext.disabledState() ? \"true\" : undefined", "attr.disabled": "isNativeFormControl() && rootContext.disabledState() ? \"\" : undefined", "attr.required": "isNativeFormControl() && rootContext.requiredState() ? \"\" : undefined", "attr.data-invalid": "dataAttr(rootContext.validState() === false)", "attr.data-valid": "dataAttr(rootContext.validState() === true)", "attr.data-disabled": "dataAttr(rootContext.disabledState())", "attr.data-required": "dataAttr(rootContext.requiredState())", "attr.data-dirty": "dataAttr(rootContext.dirtyState())", "attr.data-touched": "dataAttr(rootContext.touchedState())", "attr.data-filled": "dataAttr(rootContext.filledState())", "attr.data-focused": "dataAttr(rootContext.focusedState())" } }, exportAs: ["rdxFieldControl"], ngImport: i0 }); }
|
|
309
417
|
}
|
|
310
418
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldControl, decorators: [{
|
|
311
419
|
type: Directive,
|
|
@@ -315,15 +423,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
315
423
|
host: {
|
|
316
424
|
'[attr.id]': 'id()',
|
|
317
425
|
'[attr.aria-describedby]': 'describedBy()',
|
|
318
|
-
|
|
426
|
+
// `aria-invalid`/`data-*` follow the field's tri-state `validState`: while it is neutral (`null`,
|
|
427
|
+
// e.g. a field whose `validationMode` defers display (e.g. `onBlur`) before interaction) the control emits no `aria-invalid`
|
|
428
|
+
// and neither `data-valid` nor `data-invalid`, matching the field root.
|
|
429
|
+
'[attr.aria-invalid]': 'rootContext.validState() === false ? "true" : undefined',
|
|
319
430
|
// On a native control the native `required`/`disabled` attributes already convey the state, so
|
|
320
431
|
// `aria-required`/`aria-disabled` are only emitted on non-native (custom) controls.
|
|
321
432
|
'[attr.aria-required]': '!isNativeFormControl() && rootContext.requiredState() ? "true" : undefined',
|
|
322
433
|
'[attr.aria-disabled]': '!isNativeFormControl() && rootContext.disabledState() ? "true" : undefined',
|
|
323
434
|
'[attr.disabled]': 'isNativeFormControl() && rootContext.disabledState() ? "" : undefined',
|
|
324
435
|
'[attr.required]': 'isNativeFormControl() && rootContext.requiredState() ? "" : undefined',
|
|
325
|
-
'[attr.data-invalid]': 'dataAttr(rootContext.
|
|
326
|
-
'[attr.data-valid]': 'dataAttr(
|
|
436
|
+
'[attr.data-invalid]': 'dataAttr(rootContext.validState() === false)',
|
|
437
|
+
'[attr.data-valid]': 'dataAttr(rootContext.validState() === true)',
|
|
327
438
|
'[attr.data-disabled]': 'dataAttr(rootContext.disabledState())',
|
|
328
439
|
'[attr.data-required]': 'dataAttr(rootContext.requiredState())',
|
|
329
440
|
'[attr.data-dirty]': 'dataAttr(rootContext.dirtyState())',
|
|
@@ -338,7 +449,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
338
449
|
}]
|
|
339
450
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
340
451
|
|
|
341
|
-
const attr$
|
|
452
|
+
const attr$3 = (value) => (value ? '' : undefined);
|
|
342
453
|
/**
|
|
343
454
|
* Describes the field control.
|
|
344
455
|
*
|
|
@@ -354,7 +465,7 @@ class RdxFieldDescription {
|
|
|
354
465
|
*/
|
|
355
466
|
this.id = input(injectId('rdx-field-description-'), /* @ts-ignore */
|
|
356
467
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
357
|
-
this.dataAttr = attr$
|
|
468
|
+
this.dataAttr = attr$3;
|
|
358
469
|
effect((onCleanup) => {
|
|
359
470
|
const id = this.id();
|
|
360
471
|
this.rootContext.addDescriptionId(id);
|
|
@@ -377,7 +488,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
377
488
|
}]
|
|
378
489
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
379
490
|
|
|
380
|
-
const attr$
|
|
491
|
+
const attr$2 = (value) => (value ? '' : undefined);
|
|
381
492
|
/**
|
|
382
493
|
* Describes an invalid field control.
|
|
383
494
|
*
|
|
@@ -394,12 +505,13 @@ class RdxFieldError {
|
|
|
394
505
|
this.id = input(injectId('rdx-field-error-'), /* @ts-ignore */
|
|
395
506
|
...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
396
507
|
/**
|
|
397
|
-
* The field's
|
|
398
|
-
* them
|
|
508
|
+
* The field's validation messages — client (provider / form name-routing, once `validationMode`
|
|
509
|
+
* reveals them) then server (the Form's `errors` input, always); `[]` when none. Render them
|
|
510
|
+
* explicitly via the `exportAs` reference — the directive never injects text content itself:
|
|
399
511
|
* `<p rdxFieldError #err="rdxFieldError">{{ err.messages().join(' ') }}</p>`.
|
|
400
512
|
*/
|
|
401
513
|
this.messages = this.rootContext.messages;
|
|
402
|
-
this.dataAttr = attr$
|
|
514
|
+
this.dataAttr = attr$2;
|
|
403
515
|
effect((onCleanup) => {
|
|
404
516
|
const id = this.id();
|
|
405
517
|
this.rootContext.addErrorId(id);
|
|
@@ -424,6 +536,113 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
424
536
|
}]
|
|
425
537
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }] } });
|
|
426
538
|
|
|
539
|
+
const attr$1 = (value) => (value ? '' : undefined);
|
|
540
|
+
const addId = (ids, id) => (ids.includes(id) ? ids : [...ids, id]);
|
|
541
|
+
const removeId = (ids, id) => ids.filter((item) => item !== id);
|
|
542
|
+
/**
|
|
543
|
+
* The field item context **re-provides** {@link RdxFieldRootContext} so a label, description, and control
|
|
544
|
+
* placed inside the item associate with the **item's** control (own `controlId` / `descriptionIds`),
|
|
545
|
+
* while all validation state (`invalidState` / `dirtyState` / `touchedState` / `filledState` /
|
|
546
|
+
* `focusedState` / `requiredState`, errors and messages) is **delegated to the enclosing `rdxFieldRoot`**.
|
|
547
|
+
* `disabledState` is the item's `disabled` OR'd with the root's. The enclosing root is reached with
|
|
548
|
+
* `skipSelf` (the item provides the same token), so there is no circular injection.
|
|
549
|
+
*/
|
|
550
|
+
const fieldItemContext = () => {
|
|
551
|
+
const item = inject(RdxFieldItem);
|
|
552
|
+
const root = item.root;
|
|
553
|
+
return {
|
|
554
|
+
controlId: item.controlId,
|
|
555
|
+
name: root.name,
|
|
556
|
+
descriptionIds: item.descriptionIds,
|
|
557
|
+
errorIds: root.errorIds,
|
|
558
|
+
messages: root.messages,
|
|
559
|
+
notifyEdited: () => root.notifyEdited(),
|
|
560
|
+
validState: root.validState,
|
|
561
|
+
formSubmitAttempted: root.formSubmitAttempted,
|
|
562
|
+
invalidState: root.invalidState,
|
|
563
|
+
disabledState: item.disabledState,
|
|
564
|
+
requiredState: root.requiredState,
|
|
565
|
+
dirtyState: root.dirtyState,
|
|
566
|
+
touchedState: root.touchedState,
|
|
567
|
+
filledState: root.filledState,
|
|
568
|
+
focusedState: root.focusedState,
|
|
569
|
+
setControlId: (id) => item.controlId.set(id),
|
|
570
|
+
addDescriptionId: (id) => item.descriptionIds.update((ids) => addId(ids, id)),
|
|
571
|
+
removeDescriptionId: (id) => item.descriptionIds.update((ids) => removeId(ids, id)),
|
|
572
|
+
addErrorId: (id) => root.errorIds.update((ids) => addId(ids, id)),
|
|
573
|
+
removeErrorId: (id) => root.errorIds.update((ids) => removeId(ids, id)),
|
|
574
|
+
setFocused: (value) => root.focusedValue.set(value),
|
|
575
|
+
setFilled: (value) => root.filledValue.set(value),
|
|
576
|
+
setDirty: (value) => root.dirtyValue.set(value),
|
|
577
|
+
setTouched: (value) => root.touchedValue.set(value),
|
|
578
|
+
setStateProvider: (provider) => root.setStateProvider(provider),
|
|
579
|
+
clearStateProvider: (provider, previous) => root.clearStateProvider(provider, previous),
|
|
580
|
+
hasStateProvider: root.hasStateProvider
|
|
581
|
+
};
|
|
582
|
+
};
|
|
583
|
+
/**
|
|
584
|
+
* Groups an individual item in a checkbox group or radio group with its own label and description.
|
|
585
|
+
*
|
|
586
|
+
* Use inside a `rdxFieldRoot` (typically wrapping a group) when each control needs its own label /
|
|
587
|
+
* description: the item scopes those associations to its control while reflecting the field's validation
|
|
588
|
+
* state. Mirrors Base UI's `Field.Item`.
|
|
589
|
+
*
|
|
590
|
+
* @group Components
|
|
591
|
+
*/
|
|
592
|
+
class RdxFieldItem {
|
|
593
|
+
constructor() {
|
|
594
|
+
/** The enclosing field root (items are leaves under the root, so `skipSelf` reaches it). */
|
|
595
|
+
this.root = inject(RdxFieldRoot, { skipSelf: true });
|
|
596
|
+
/**
|
|
597
|
+
* Whether the wrapped control should ignore user interaction. The `disabled` on `rdxFieldRoot` takes
|
|
598
|
+
* precedence (it is OR'd in).
|
|
599
|
+
*
|
|
600
|
+
* @group Props
|
|
601
|
+
* @defaultValue false
|
|
602
|
+
*/
|
|
603
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
604
|
+
/** This item's control id — label / description / control inside the item associate with it. */
|
|
605
|
+
this.controlId = signal(injectId('rdx-field-item-control-'), /* @ts-ignore */
|
|
606
|
+
...(ngDevMode ? [{ debugName: "controlId" }] : /* istanbul ignore next */ []));
|
|
607
|
+
/** Description ids for this item's control `aria-describedby`. */
|
|
608
|
+
this.descriptionIds = signal([], /* @ts-ignore */
|
|
609
|
+
...(ngDevMode ? [{ debugName: "descriptionIds" }] : /* istanbul ignore next */ []));
|
|
610
|
+
/** The item's effective disabled state: the root's disabled OR the item's. */
|
|
611
|
+
this.disabledState = computed(() => this.root.disabledState() || this.disabled(), /* @ts-ignore */
|
|
612
|
+
...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
613
|
+
this.dataAttr = attr$1;
|
|
614
|
+
}
|
|
615
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
616
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxFieldItem, isStandalone: true, selector: "[rdxFieldItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-invalid": "dataAttr(root.validState() === false)", "attr.data-valid": "dataAttr(root.validState() === true)", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(root.requiredState())", "attr.data-dirty": "dataAttr(root.dirtyState())", "attr.data-touched": "dataAttr(root.touchedState())", "attr.data-filled": "dataAttr(root.filledState())", "attr.data-focused": "dataAttr(root.focusedState())" } }, providers: [
|
|
617
|
+
provideFieldRootContext(fieldItemContext),
|
|
618
|
+
// Items delegate validity to the root; expose the root's tri-state to controls inside the item.
|
|
619
|
+
{ provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldItem).root.validState }
|
|
620
|
+
], exportAs: ["rdxFieldItem"], ngImport: i0 }); }
|
|
621
|
+
}
|
|
622
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxFieldItem, decorators: [{
|
|
623
|
+
type: Directive,
|
|
624
|
+
args: [{
|
|
625
|
+
selector: '[rdxFieldItem]',
|
|
626
|
+
exportAs: 'rdxFieldItem',
|
|
627
|
+
providers: [
|
|
628
|
+
provideFieldRootContext(fieldItemContext),
|
|
629
|
+
// Items delegate validity to the root; expose the root's tri-state to controls inside the item.
|
|
630
|
+
{ provide: RDX_FIELD_VALIDITY, useFactory: () => inject(RdxFieldItem).root.validState }
|
|
631
|
+
],
|
|
632
|
+
host: {
|
|
633
|
+
// Tri-state, mirroring the root: neither attribute while the root's `validState` is neutral.
|
|
634
|
+
'[attr.data-invalid]': 'dataAttr(root.validState() === false)',
|
|
635
|
+
'[attr.data-valid]': 'dataAttr(root.validState() === true)',
|
|
636
|
+
'[attr.data-disabled]': 'dataAttr(disabledState())',
|
|
637
|
+
'[attr.data-required]': 'dataAttr(root.requiredState())',
|
|
638
|
+
'[attr.data-dirty]': 'dataAttr(root.dirtyState())',
|
|
639
|
+
'[attr.data-touched]': 'dataAttr(root.touchedState())',
|
|
640
|
+
'[attr.data-filled]': 'dataAttr(root.filledState())',
|
|
641
|
+
'[attr.data-focused]': 'dataAttr(root.focusedState())'
|
|
642
|
+
}
|
|
643
|
+
}]
|
|
644
|
+
}], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }] } });
|
|
645
|
+
|
|
427
646
|
const attr = (value) => (value ? '' : undefined);
|
|
428
647
|
/**
|
|
429
648
|
* Labels the field control.
|
|
@@ -466,5 +685,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
466
685
|
* Generated bundle index. Do not edit.
|
|
467
686
|
*/
|
|
468
687
|
|
|
469
|
-
export { RdxFieldControl, RdxFieldDescription, RdxFieldError, RdxFieldLabel, RdxFieldRoot, injectFieldRootContext, provideFieldRootContext };
|
|
688
|
+
export { RdxFieldControl, RdxFieldDescription, RdxFieldError, RdxFieldItem, RdxFieldLabel, RdxFieldRoot, injectFieldRootContext, provideFieldRootContext };
|
|
470
689
|
//# sourceMappingURL=radix-ng-primitives-field.mjs.map
|