@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.
Files changed (52) hide show
  1. package/fesm2022/radix-ng-primitives-autocomplete.mjs +43 -9
  2. package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
  3. package/fesm2022/radix-ng-primitives-checkbox.mjs +89 -8
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  5. package/fesm2022/radix-ng-primitives-combobox.mjs +43 -9
  6. package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
  7. package/fesm2022/radix-ng-primitives-core.mjs +208 -2
  8. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  9. package/fesm2022/radix-ng-primitives-date-field.mjs +50 -9
  10. package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
  11. package/fesm2022/radix-ng-primitives-editable.mjs +31 -5
  12. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  13. package/fesm2022/radix-ng-primitives-field.mjs +265 -46
  14. package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
  15. package/fesm2022/radix-ng-primitives-form.mjs +110 -26
  16. package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
  17. package/fesm2022/radix-ng-primitives-input.mjs +16 -6
  18. package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
  19. package/fesm2022/radix-ng-primitives-number-field.mjs +32 -7
  20. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  21. package/fesm2022/radix-ng-primitives-radio.mjs +18 -4
  22. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  23. package/fesm2022/radix-ng-primitives-select.mjs +26 -8
  24. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  25. package/fesm2022/radix-ng-primitives-signal-forms.mjs +184 -0
  26. package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -0
  27. package/fesm2022/radix-ng-primitives-slider.mjs +24 -8
  28. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  29. package/fesm2022/radix-ng-primitives-switch.mjs +29 -5
  30. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  31. package/fesm2022/radix-ng-primitives-time-field.mjs +49 -9
  32. package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
  33. package/fesm2022/radix-ng-primitives-toggle-group.mjs +23 -12
  34. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  35. package/package.json +11 -1
  36. package/types/radix-ng-primitives-autocomplete.d.ts +21 -2
  37. package/types/radix-ng-primitives-checkbox.d.ts +53 -4
  38. package/types/radix-ng-primitives-combobox.d.ts +51 -2
  39. package/types/radix-ng-primitives-core.d.ts +206 -3
  40. package/types/radix-ng-primitives-date-field.d.ts +27 -2
  41. package/types/radix-ng-primitives-editable.d.ts +15 -2
  42. package/types/radix-ng-primitives-field.d.ts +136 -33
  43. package/types/radix-ng-primitives-form.d.ts +115 -21
  44. package/types/radix-ng-primitives-input.d.ts +8 -1
  45. package/types/radix-ng-primitives-number-field.d.ts +14 -3
  46. package/types/radix-ng-primitives-radio.d.ts +7 -2
  47. package/types/radix-ng-primitives-select.d.ts +11 -3
  48. package/types/radix-ng-primitives-signal-forms.d.ts +97 -0
  49. package/types/radix-ng-primitives-slider.d.ts +9 -7
  50. package/types/radix-ng-primitives-switch.d.ts +15 -2
  51. package/types/radix-ng-primitives-time-field.d.ts +26 -2
  52. package/types/radix-ng-primitives-toggle-group.d.ts +6 -4
@@ -0,0 +1,184 @@
1
+ import * as i0 from '@angular/core';
2
+ import { inject, DestroyRef, Directive, input, NgModule } from '@angular/core';
3
+ import { FormField } from '@angular/forms/signals';
4
+ import { injectFieldRootContext } from '@radix-ng/primitives/field';
5
+ import { injectFormRootContext } from '@radix-ng/primitives/form';
6
+
7
+ /**
8
+ * Bridges an Angular **Signal Forms** field into an enclosing `rdxFieldRoot` — with **no duplicate
9
+ * binding**. Place it on the control that already carries `[formField]`; it reads the bound field's
10
+ * state from that directive, so the field expression is written exactly once:
11
+ *
12
+ * ```html
13
+ * <div rdxFieldRoot>
14
+ * <label rdxFieldLabel>Email</label>
15
+ * <input rdxInput [formField]="loginForm.email" rdxSignalField />
16
+ * <p rdxFieldError>Email is required.</p>
17
+ * </div>
18
+ * ```
19
+ *
20
+ * It owns no model and runs no validation — Signal Forms remains the source of truth. It only registers
21
+ * an {@link RdxFieldState} provider on the ancestor Field context so the field's `invalid` / `disabled` /
22
+ * `required` / `dirty` / `touched` data-attributes and the error *content* (`rdxFieldError.messages()`)
23
+ * read authoritative Signal Forms state. `filled` / `focused` stay on Field's DOM heuristic (partial
24
+ * ownership — the seam supports it).
25
+ *
26
+ * It reports the **actual** Signal Forms state only; the `Field` decides *when* to display it from its
27
+ * `validationMode` (default `'onBlur'` — neutral until the field is touched or the form submitted). So an
28
+ * empty required field is neutral on load with no manual `[invalid]`/`[touched]` wiring, and a pristine
29
+ * submit reveals the errors (the `Form` records the submit attempt before checking validity and blocks).
30
+ *
31
+ * See ADR 0018.
32
+ *
33
+ * @group Components
34
+ */
35
+ class RdxSignalField {
36
+ constructor() {
37
+ /** The co-located `[formField]` directive — the single source of the bound field's state. */
38
+ this.formField = inject(FormField);
39
+ this.fieldContext = injectFieldRootContext();
40
+ // The accessors are evaluated lazily by Field's `*State` computeds during change detection (after
41
+ // the `[formField]` binding resolves), so reading `state()` inside these closures is safe. The
42
+ // adapter reports authoritative *actual* state; the Field gates the *display* by its validationMode.
43
+ const state = {
44
+ invalid: () => this.state().invalid(),
45
+ disabled: () => this.state().disabled(),
46
+ required: () => this.state().required(),
47
+ dirty: () => this.state().dirty(),
48
+ touched: () => this.state().touched(),
49
+ errors: () => this.state()
50
+ .errors()
51
+ .map((error) => ({ kind: error.kind, message: error.message }))
52
+ };
53
+ const previous = this.fieldContext.setStateProvider(state);
54
+ // Identity-checked teardown: only roll back if our provider is still active, so a newer adapter
55
+ // mounted during a view swap (create-before-destroy) is not clobbered by this destroy.
56
+ inject(DestroyRef).onDestroy(() => this.fieldContext.clearStateProvider(state, previous));
57
+ }
58
+ /** Current Signal Forms field state, read from the co-located `[formField]` directive. */
59
+ state() {
60
+ return this.formField.state();
61
+ }
62
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalField, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
63
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "22.0.2", type: RdxSignalField, isStandalone: true, selector: "[formField][rdxSignalField]", exportAs: ["rdxSignalField"], ngImport: i0 }); }
64
+ }
65
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalField, decorators: [{
66
+ type: Directive,
67
+ args: [{
68
+ selector: '[formField][rdxSignalField]',
69
+ exportAs: 'rdxSignalField'
70
+ }]
71
+ }], ctorParameters: () => [] });
72
+
73
+ /**
74
+ * Bridges an Angular **Signal Forms** form into an enclosing `form[rdxFormRoot]`.
75
+ *
76
+ * ```html
77
+ * <form rdxFormRoot [rdxSignalForm]="loginForm">
78
+ * <!-- fields bound with [formField] (+ rdxSignalField) -->
79
+ * </form>
80
+ * ```
81
+ *
82
+ * Registers an {@link RdxFormState} provider so the Form's aggregate `data-invalid` / `data-dirty` /
83
+ * `data-touched` / `data-submitting` attributes and the submit guard read authoritative Signal Forms
84
+ * state. This adapter owns only client-side state and `name`-routing; server errors stay a separate
85
+ * eager channel applied through `rdxFormRoot[errors]`, and Signal Forms' own `submit()` owns the
86
+ * submit lifecycle.
87
+ *
88
+ * `errorsFor(name)` routes a field's errors to a `rdxFieldRoot` by its `name`, walking the `FieldTree`
89
+ * so dotted paths into nested object/array fields resolve too (`address.street`, `items.0.name`). A
90
+ * field can surface messages from the form alone — no per-field `rdxSignalField` needed for errors.
91
+ * These are the **two modes** (per-field adapter vs form-level name routing) — prefer one per field; if
92
+ * a field carries both, `rdxFieldError.messages()` deduplicates by text so the shared message is not
93
+ * shown twice.
94
+ *
95
+ * See ADR 0018.
96
+ *
97
+ * @group Components
98
+ */
99
+ class RdxSignalForm {
100
+ constructor() {
101
+ /** The Signal Forms root field (from `form(...)`) whose aggregate state drives the enclosing Form. */
102
+ this.form = input.required({ ...(ngDevMode ? { debugName: "form" } : /* istanbul ignore next */ {}), alias: 'rdxSignalForm' });
103
+ this.formContext = injectFormRootContext();
104
+ const state = {
105
+ invalid: () => this.state().invalid(),
106
+ dirty: () => this.state().dirty(),
107
+ touched: () => this.state().touched(),
108
+ submitting: () => this.state().submitting(),
109
+ errorsFor: (name) => this.errorsFor(name),
110
+ // Per-name interaction state so a name-routed field (bare `[formField]`, no `rdxSignalField`)
111
+ // can reveal its error on blur/change under `validationMode="onBlur"`/`"onChange"`.
112
+ touchedFor: (name) => this.fieldState(name)?.touched() ?? false,
113
+ dirtyFor: (name) => this.fieldState(name)?.dirty() ?? false
114
+ };
115
+ const previous = this.formContext.setStateProvider(state);
116
+ // Identity-checked teardown: only roll back if our provider is still active, so a newer adapter
117
+ // mounted during a view swap (create-before-destroy) is not clobbered by this destroy.
118
+ inject(DestroyRef).onDestroy(() => this.formContext.clearStateProvider(state, previous));
119
+ }
120
+ /**
121
+ * Messages for the field at the dotted `name` path (`message ?? kind` per error). Walks the
122
+ * `FieldTree` so nested object/array fields resolve too — e.g. `address.street`, `items.0.name`.
123
+ */
124
+ errorsFor(name) {
125
+ return (this.fieldState(name)
126
+ ?.errors()
127
+ .map((error) => error.message ?? error.kind) ?? []);
128
+ }
129
+ /**
130
+ * The Signal Forms field *state* at the dotted `name` path, or `null` if it doesn't resolve. Walks the
131
+ * `FieldTree` so nested object/array fields work (`address.street`, `items.0.name`).
132
+ */
133
+ fieldState(name) {
134
+ let node = this.form();
135
+ for (const segment of name.split('.')) {
136
+ if (node == null || (typeof node !== 'object' && typeof node !== 'function')) {
137
+ return null;
138
+ }
139
+ // Only walk into own properties: a `FieldTree` exposes its subfields as own keys, so a
140
+ // segment that is not one (`constructor`, `__proto__`, `toString`, …) is not a real field.
141
+ // Reading it would resolve to an inherited member — and a function one would then be called
142
+ // below — so reject it instead.
143
+ if (!Object.hasOwn(node, segment)) {
144
+ return null;
145
+ }
146
+ node = node[segment];
147
+ }
148
+ return typeof node === 'function' ? node() : null;
149
+ }
150
+ /** The current Signal Forms root field state (a `FieldTree` is callable). */
151
+ state() {
152
+ return this.form()();
153
+ }
154
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalForm, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
155
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxSignalForm, isStandalone: true, selector: "form[rdxFormRoot][rdxSignalForm]", inputs: { form: { classPropertyName: "form", publicName: "rdxSignalForm", isSignal: true, isRequired: true, transformFunction: null } }, exportAs: ["rdxSignalForm"], ngImport: i0 }); }
156
+ }
157
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalForm, decorators: [{
158
+ type: Directive,
159
+ args: [{
160
+ selector: 'form[rdxFormRoot][rdxSignalForm]',
161
+ exportAs: 'rdxSignalForm'
162
+ }]
163
+ }], ctorParameters: () => [], propDecorators: { form: [{ type: i0.Input, args: [{ isSignal: true, alias: "rdxSignalForm", required: true }] }] } });
164
+
165
+ const _importsSignalForms = [RdxSignalField, RdxSignalForm];
166
+ class RdxSignalFormsModule {
167
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalFormsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
168
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalFormsModule, imports: [RdxSignalField, RdxSignalForm], exports: [RdxSignalField, RdxSignalForm] }); }
169
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalFormsModule }); }
170
+ }
171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSignalFormsModule, decorators: [{
172
+ type: NgModule,
173
+ args: [{
174
+ imports: [..._importsSignalForms],
175
+ exports: [..._importsSignalForms]
176
+ }]
177
+ }] });
178
+
179
+ /**
180
+ * Generated bundle index. Do not edit.
181
+ */
182
+
183
+ export { RdxSignalField, RdxSignalForm, RdxSignalFormsModule, _importsSignalForms };
184
+ //# sourceMappingURL=radix-ng-primitives-signal-forms.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"radix-ng-primitives-signal-forms.mjs","sources":["../../../packages/primitives/signal-forms/src/signal-field.ts","../../../packages/primitives/signal-forms/src/signal-form.ts","../../../packages/primitives/signal-forms/index.ts","../../../packages/primitives/signal-forms/radix-ng-primitives-signal-forms.ts"],"sourcesContent":["import { DestroyRef, Directive, inject } from '@angular/core';\nimport { FormField } from '@angular/forms/signals';\nimport { injectFieldRootContext, RdxFieldState } from '@radix-ng/primitives/field';\n\n/**\n * Bridges an Angular **Signal Forms** field into an enclosing `rdxFieldRoot` — with **no duplicate\n * binding**. Place it on the control that already carries `[formField]`; it reads the bound field's\n * state from that directive, so the field expression is written exactly once:\n *\n * ```html\n * <div rdxFieldRoot>\n * <label rdxFieldLabel>Email</label>\n * <input rdxInput [formField]=\"loginForm.email\" rdxSignalField />\n * <p rdxFieldError>Email is required.</p>\n * </div>\n * ```\n *\n * It owns no model and runs no validation — Signal Forms remains the source of truth. It only registers\n * an {@link RdxFieldState} provider on the ancestor Field context so the field's `invalid` / `disabled` /\n * `required` / `dirty` / `touched` data-attributes and the error *content* (`rdxFieldError.messages()`)\n * read authoritative Signal Forms state. `filled` / `focused` stay on Field's DOM heuristic (partial\n * ownership — the seam supports it).\n *\n * It reports the **actual** Signal Forms state only; the `Field` decides *when* to display it from its\n * `validationMode` (default `'onBlur'` — neutral until the field is touched or the form submitted). So an\n * empty required field is neutral on load with no manual `[invalid]`/`[touched]` wiring, and a pristine\n * submit reveals the errors (the `Form` records the submit attempt before checking validity and blocks).\n *\n * See ADR 0018.\n *\n * @group Components\n */\n@Directive({\n selector: '[formField][rdxSignalField]',\n exportAs: 'rdxSignalField'\n})\nexport class RdxSignalField {\n /** The co-located `[formField]` directive — the single source of the bound field's state. */\n private readonly formField = inject(FormField);\n private readonly fieldContext = injectFieldRootContext();\n\n constructor() {\n // The accessors are evaluated lazily by Field's `*State` computeds during change detection (after\n // the `[formField]` binding resolves), so reading `state()` inside these closures is safe. The\n // adapter reports authoritative *actual* state; the Field gates the *display* by its validationMode.\n const state: RdxFieldState = {\n invalid: () => this.state().invalid(),\n disabled: () => this.state().disabled(),\n required: () => this.state().required(),\n dirty: () => this.state().dirty(),\n touched: () => this.state().touched(),\n errors: () =>\n this.state()\n .errors()\n .map((error) => ({ kind: error.kind, message: error.message }))\n };\n\n const previous = this.fieldContext.setStateProvider(state);\n // Identity-checked teardown: only roll back if our provider is still active, so a newer adapter\n // mounted during a view swap (create-before-destroy) is not clobbered by this destroy.\n inject(DestroyRef).onDestroy(() => this.fieldContext.clearStateProvider(state, previous));\n }\n\n /** Current Signal Forms field state, read from the co-located `[formField]` directive. */\n private state() {\n return this.formField.state();\n }\n}\n","import { DestroyRef, Directive, inject, input } from '@angular/core';\nimport type { FieldTree } from '@angular/forms/signals';\nimport { injectFormRootContext, RdxFormState } from '@radix-ng/primitives/form';\n\n/** A Signal Forms subfield read structurally — callable to its state (`errors()` / `touched()` / `dirty()`). */\ntype NamedField = () => {\n errors: () => readonly { kind: string; message?: string }[];\n touched: () => boolean;\n dirty: () => boolean;\n};\n\n/**\n * Bridges an Angular **Signal Forms** form into an enclosing `form[rdxFormRoot]`.\n *\n * ```html\n * <form rdxFormRoot [rdxSignalForm]=\"loginForm\">\n * <!-- fields bound with [formField] (+ rdxSignalField) -->\n * </form>\n * ```\n *\n * Registers an {@link RdxFormState} provider so the Form's aggregate `data-invalid` / `data-dirty` /\n * `data-touched` / `data-submitting` attributes and the submit guard read authoritative Signal Forms\n * state. This adapter owns only client-side state and `name`-routing; server errors stay a separate\n * eager channel applied through `rdxFormRoot[errors]`, and Signal Forms' own `submit()` owns the\n * submit lifecycle.\n *\n * `errorsFor(name)` routes a field's errors to a `rdxFieldRoot` by its `name`, walking the `FieldTree`\n * so dotted paths into nested object/array fields resolve too (`address.street`, `items.0.name`). A\n * field can surface messages from the form alone — no per-field `rdxSignalField` needed for errors.\n * These are the **two modes** (per-field adapter vs form-level name routing) — prefer one per field; if\n * a field carries both, `rdxFieldError.messages()` deduplicates by text so the shared message is not\n * shown twice.\n *\n * See ADR 0018.\n *\n * @group Components\n */\n@Directive({\n selector: 'form[rdxFormRoot][rdxSignalForm]',\n exportAs: 'rdxSignalForm'\n})\nexport class RdxSignalForm {\n /** The Signal Forms root field (from `form(...)`) whose aggregate state drives the enclosing Form. */\n readonly form = input.required<FieldTree<unknown>>({ alias: 'rdxSignalForm' });\n\n private readonly formContext = injectFormRootContext();\n\n constructor() {\n const state: RdxFormState = {\n invalid: () => this.state().invalid(),\n dirty: () => this.state().dirty(),\n touched: () => this.state().touched(),\n submitting: () => this.state().submitting(),\n errorsFor: (name) => this.errorsFor(name),\n // Per-name interaction state so a name-routed field (bare `[formField]`, no `rdxSignalField`)\n // can reveal its error on blur/change under `validationMode=\"onBlur\"`/`\"onChange\"`.\n touchedFor: (name) => this.fieldState(name)?.touched() ?? false,\n dirtyFor: (name) => this.fieldState(name)?.dirty() ?? false\n };\n\n const previous = this.formContext.setStateProvider(state);\n // Identity-checked teardown: only roll back if our provider is still active, so a newer adapter\n // mounted during a view swap (create-before-destroy) is not clobbered by this destroy.\n inject(DestroyRef).onDestroy(() => this.formContext.clearStateProvider(state, previous));\n }\n\n /**\n * Messages for the field at the dotted `name` path (`message ?? kind` per error). Walks the\n * `FieldTree` so nested object/array fields resolve too — e.g. `address.street`, `items.0.name`.\n */\n private errorsFor(name: string): string[] {\n return (\n this.fieldState(name)\n ?.errors()\n .map((error) => error.message ?? error.kind) ?? []\n );\n }\n\n /**\n * The Signal Forms field *state* at the dotted `name` path, or `null` if it doesn't resolve. Walks the\n * `FieldTree` so nested object/array fields work (`address.street`, `items.0.name`).\n */\n private fieldState(name: string): ReturnType<NamedField> | null {\n let node: unknown = this.form();\n for (const segment of name.split('.')) {\n if (node == null || (typeof node !== 'object' && typeof node !== 'function')) {\n return null;\n }\n // Only walk into own properties: a `FieldTree` exposes its subfields as own keys, so a\n // segment that is not one (`constructor`, `__proto__`, `toString`, …) is not a real field.\n // Reading it would resolve to an inherited member — and a function one would then be called\n // below — so reject it instead.\n if (!Object.hasOwn(node, segment)) {\n return null;\n }\n node = (node as Record<string, unknown>)[segment];\n }\n return typeof node === 'function' ? (node as NamedField)() : null;\n }\n\n /** The current Signal Forms root field state (a `FieldTree` is callable). */\n private state() {\n return this.form()();\n }\n}\n","import { NgModule } from '@angular/core';\nimport { RdxSignalField } from './src/signal-field';\nimport { RdxSignalForm } from './src/signal-form';\n\nexport * from './src/signal-field';\nexport * from './src/signal-form';\n\nexport const _importsSignalForms = [RdxSignalField, RdxSignalForm];\n\n@NgModule({\n imports: [..._importsSignalForms],\n exports: [..._importsSignalForms]\n})\nexport class RdxSignalFormsModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { inject, ElementRef, DestroyRef, Directive, computed, input, numberAttribute, booleanAttribute, model, output, signal, effect, afterNextRender, afterRenderEffect, untracked, NgModule } from '@angular/core';
3
3
  import { DOCUMENT } from '@angular/common';
4
4
  import * as i2 from '@radix-ng/primitives/core';
5
- import { createContext, clamp, injectControlValueAccessor, injectDocument, injectId, createCancelableChangeEventDetails, RdxControlValueAccessor } from '@radix-ng/primitives/core';
5
+ import { createContext, clamp, RdxFormUiControlBase, injectControlValueAccessor, injectDocument, injectId, createCancelableChangeEventDetails, provideFormUiState, RdxFormUiStateHost, RdxControlValueAccessor } from '@radix-ng/primitives/core';
6
6
  export { clamp } from '@radix-ng/primitives/core';
7
7
  import * as i1 from '@radix-ng/primitives/composite';
8
8
  import { RdxCompositeList, RdxCompositeListItem } from '@radix-ng/primitives/composite';
@@ -718,8 +718,12 @@ function cloneChangeEventWithTarget(event, value, name) {
718
718
  *
719
719
  * @see https://base-ui.com/react/components/slider
720
720
  */
721
- class RdxSliderRoot {
721
+ // NOTE: does not `implements RdxFormValueControl` — the slider value is a `number | number[]` union,
722
+ // while `min`/`max` are scalar `number`s; the shim ties `min`/`max` to `NonNullable<TValue>`, which the
723
+ // union can't satisfy. The runtime contract (value model + the inherited `FormUiControl` state) holds.
724
+ class RdxSliderRoot extends RdxFormUiControlBase {
722
725
  constructor() {
726
+ super(...arguments);
723
727
  /** @ignore */
724
728
  this.cva = injectControlValueAccessor();
725
729
  this.document = injectDocument();
@@ -935,6 +939,7 @@ class RdxSliderRoot {
935
939
  this.lastChangeEvent = eventDetails.event;
936
940
  this.value.set(next);
937
941
  this.cva.setValue(next);
942
+ this.formUi.markDirty();
938
943
  return true;
939
944
  }
940
945
  /** @ignore Keyboard / native input path: clamps to neighbours, commits immediately. */
@@ -948,7 +953,7 @@ class RdxSliderRoot {
948
953
  }
949
954
  const arr = Array.isArray(result) ? result : [result];
950
955
  const applied = this.setValue(arr, reason, event, index);
951
- this.cva.markAsTouched();
956
+ this.markAsTouched();
952
957
  if (applied) {
953
958
  this.commitValue(event, reason);
954
959
  }
@@ -966,9 +971,13 @@ class RdxSliderRoot {
966
971
  }
967
972
  });
968
973
  }
969
- /** @ignore */
974
+ /** @ignore Bridge the CVA into `markAsTouched` (dual). */
975
+ formUiTouchTarget() {
976
+ return injectControlValueAccessor();
977
+ }
978
+ /** @ignore Mark touched: CVA (Reactive/template) + `touched` model + `touch` (Signal Forms). */
970
979
  markAsTouched() {
971
- this.cva.markAsTouched();
980
+ this.formUi.markAsTouched();
972
981
  }
973
982
  /** @ignore */
974
983
  setDragging(dragging) {
@@ -984,17 +993,24 @@ class RdxSliderRoot {
984
993
  getOwnerWindow() {
985
994
  return this.document.defaultView ?? undefined;
986
995
  }
987
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSliderRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
988
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxSliderRoot, isStandalone: true, selector: "div[rdxSliderRoot]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, minStepsBetweenValues: { classPropertyName: "minStepsBetweenValues", publicName: "minStepsBetweenValues", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, thumbCollisionBehavior: { classPropertyName: "thumbCollisionBehavior", publicName: "thumbCollisionBehavior", isSignal: true, isRequired: false, transformFunction: null }, thumbAlignment: { classPropertyName: "thumbAlignment", publicName: "thumbAlignment", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onValueCommitted: "onValueCommitted" }, host: { attributes: { "role": "group" }, properties: { "id": "id()", "attr.aria-labelledby": "ariaLabelledBy()", "attr.dir": "dir()", "attr.data-orientation": "orientation()", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-dragging": "dragging() ? \"\" : undefined" } }, providers: [provideSliderRootContext(() => inject(RdxSliderRoot))], exportAs: ["rdxSliderRoot"], hostDirectives: [{ directive: i1.RdxCompositeList }, { directive: i2.RdxControlValueAccessor, inputs: ["value", "value", "disabled", "disabled"] }], ngImport: i0 }); }
996
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSliderRoot, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
997
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxSliderRoot, isStandalone: true, selector: "div[rdxSliderRoot]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, minStepsBetweenValues: { classPropertyName: "minStepsBetweenValues", publicName: "minStepsBetweenValues", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, thumbCollisionBehavior: { classPropertyName: "thumbCollisionBehavior", publicName: "thumbCollisionBehavior", isSignal: true, isRequired: false, transformFunction: null }, thumbAlignment: { classPropertyName: "thumbAlignment", publicName: "thumbAlignment", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onValueCommitted: "onValueCommitted" }, host: { attributes: { "role": "group" }, properties: { "id": "id()", "attr.aria-labelledby": "ariaLabelledBy()", "attr.dir": "dir()", "attr.data-orientation": "orientation()", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-dragging": "dragging() ? \"\" : undefined" } }, providers: [
998
+ provideSliderRootContext(() => inject(RdxSliderRoot)),
999
+ provideFormUiState(() => inject(RdxSliderRoot).formUi)
1000
+ ], exportAs: ["rdxSliderRoot"], usesInheritance: true, hostDirectives: [{ directive: i1.RdxCompositeList }, { directive: i2.RdxFormUiStateHost }, { directive: i2.RdxControlValueAccessor, inputs: ["value", "value", "disabled", "disabled"] }], ngImport: i0 }); }
989
1001
  }
990
1002
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxSliderRoot, decorators: [{
991
1003
  type: Directive,
992
1004
  args: [{
993
1005
  selector: 'div[rdxSliderRoot]',
994
1006
  exportAs: 'rdxSliderRoot',
995
- providers: [provideSliderRootContext(() => inject(RdxSliderRoot))],
1007
+ providers: [
1008
+ provideSliderRootContext(() => inject(RdxSliderRoot)),
1009
+ provideFormUiState(() => inject(RdxSliderRoot).formUi)
1010
+ ],
996
1011
  hostDirectives: [
997
1012
  RdxCompositeList,
1013
+ RdxFormUiStateHost,
998
1014
  {
999
1015
  directive: RdxControlValueAccessor,
1000
1016
  inputs: ['value: value', 'disabled']