@radix-ng/primitives 1.0.10 → 1.1.1
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/README.md +26 -5
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +88 -21
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +199 -129
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +203 -52
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-composite.mjs +221 -36
- package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +352 -24
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +11 -5
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +127 -23
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +35 -4
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-input.mjs +87 -15
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +34 -7
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +38 -7
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +80 -11
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-radio.mjs +49 -2
- package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +6 -3
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +70 -14
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-signal-forms.mjs +27 -11
- package/fesm2022/radix-ng-primitives-signal-forms.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +17 -2
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +60 -7
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +12 -6
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +25 -3
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/package.json +8 -3
- package/types/radix-ng-primitives-autocomplete.d.ts +16 -2
- package/types/radix-ng-primitives-checkbox.d.ts +66 -44
- package/types/radix-ng-primitives-combobox.d.ts +74 -3
- package/types/radix-ng-primitives-composite.d.ts +48 -2
- package/types/radix-ng-primitives-core.d.ts +212 -96
- package/types/radix-ng-primitives-date-field.d.ts +7 -4
- package/types/radix-ng-primitives-field.d.ts +55 -8
- package/types/radix-ng-primitives-form.d.ts +9 -0
- package/types/radix-ng-primitives-input.d.ts +10 -2
- package/types/radix-ng-primitives-menu.d.ts +45 -4
- package/types/radix-ng-primitives-navigation-menu.d.ts +2 -2
- package/types/radix-ng-primitives-number-field.d.ts +12 -5
- package/types/radix-ng-primitives-popover.d.ts +2 -2
- package/types/radix-ng-primitives-popper.d.ts +60 -15
- package/types/radix-ng-primitives-preview-card.d.ts +2 -2
- package/types/radix-ng-primitives-radio.d.ts +6 -1
- package/types/radix-ng-primitives-select.d.ts +25 -4
- package/types/radix-ng-primitives-signal-forms.d.ts +22 -8
- package/types/radix-ng-primitives-slider.d.ts +2 -1
- package/types/radix-ng-primitives-switch.d.ts +19 -3
- package/types/radix-ng-primitives-time-field.d.ts +9 -6
- package/types/radix-ng-primitives-toggle-group.d.ts +5 -1
- package/types/radix-ng-primitives-tooltip.d.ts +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @radix-ng/primitives
|
|
2
2
|
|
|
3
|
-
> **Headless, signals-first UI primitives
|
|
3
|
+
> **Headless, signals-first UI primitives with first-class Angular Forms support.**
|
|
4
4
|
|
|
5
5
|
Radix NG is a low-level UI primitive library for Angular with a focus on accessibility,
|
|
6
6
|
customization, and developer experience. The primitives are **headless** — they ship no styles and
|
|
@@ -30,18 +30,18 @@ Each primitive is its own secondary entry point — import only what you use:
|
|
|
30
30
|
```ts
|
|
31
31
|
import { Component } from '@angular/core';
|
|
32
32
|
import {
|
|
33
|
+
RdxCollapsiblePanelDirective,
|
|
33
34
|
RdxCollapsibleRootDirective,
|
|
34
|
-
RdxCollapsibleTriggerDirective
|
|
35
|
-
RdxCollapsibleContentDirective
|
|
35
|
+
RdxCollapsibleTriggerDirective
|
|
36
36
|
} from '@radix-ng/primitives/collapsible';
|
|
37
37
|
|
|
38
38
|
@Component({
|
|
39
39
|
selector: 'app-demo',
|
|
40
|
-
imports: [RdxCollapsibleRootDirective, RdxCollapsibleTriggerDirective,
|
|
40
|
+
imports: [RdxCollapsibleRootDirective, RdxCollapsibleTriggerDirective, RdxCollapsiblePanelDirective],
|
|
41
41
|
template: `
|
|
42
42
|
<div rdxCollapsibleRoot>
|
|
43
43
|
<button rdxCollapsibleTrigger>Toggle</button>
|
|
44
|
-
<div
|
|
44
|
+
<div rdxCollapsiblePanel>Content</div>
|
|
45
45
|
</div>
|
|
46
46
|
`
|
|
47
47
|
})
|
|
@@ -51,6 +51,27 @@ export class DemoComponent {}
|
|
|
51
51
|
Style the parts via the `data-*` attributes they expose (e.g. `[data-state="open"]`,
|
|
52
52
|
`[data-disabled]`) with any CSS approach — Tailwind, CSS modules, vanilla CSS.
|
|
53
53
|
|
|
54
|
+
## Angular forms, one Field contract
|
|
55
|
+
|
|
56
|
+
The same controls and accessible Field parts work across every Angular form API while Angular owns
|
|
57
|
+
the model and validation:
|
|
58
|
+
|
|
59
|
+
```html
|
|
60
|
+
<!-- Reactive Forms / ngModel -->
|
|
61
|
+
<input rdxInput formControlName="email" rdxNgControlField />
|
|
62
|
+
|
|
63
|
+
<!-- Signal Forms -->
|
|
64
|
+
<input rdxInput [formField]="accountForm.email" rdxSignalField />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
ControlValueAccessors remain available for Reactive and template-driven forms. The optional
|
|
68
|
+
`@radix-ng/primitives/signal-forms` entry adds Angular Signal Forms without a separate Radix NG form
|
|
69
|
+
model. Signal Forms is stable in Angular 22; Reactive Forms and `ngModel` remain supported on Angular
|
|
70
|
+
21 and 22. Compare the [paired recipes and runtime-verified
|
|
71
|
+
matrix](https://radix-ng.com/docs/?path=/docs/primitives-signal-forms--docs#one-recipe-two-angular-apis),
|
|
72
|
+
and see the [migration guide](https://radix-ng.com/docs/?path=/docs/guides-forms-migration--docs) when
|
|
73
|
+
upgrading from 1.0.10.
|
|
74
|
+
|
|
54
75
|
## Documentation
|
|
55
76
|
|
|
56
77
|
- 📖 [radix-ng.com](https://radix-ng.com) — documentation, examples & API reference
|
|
@@ -8,7 +8,7 @@ import * as i1$2 from '@radix-ng/primitives/dismissable-layer';
|
|
|
8
8
|
import { RdxFloatingInsideElement, RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
|
|
9
9
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
10
10
|
import * as i2 from '@radix-ng/primitives/core';
|
|
11
|
-
import { RdxFormUiControlBase, createFloatingRootContext, rdxDevWarning, isItemEqualToValue, itemToStringLabel, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, setupInternalBackdrop, injectId, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useAnchoredScrollLock, RdxFloatingNodeRegistration, rdxDevError } from '@radix-ng/primitives/core';
|
|
11
|
+
import { RdxFormUiControlBase, createFloatingRootContext, useNativeFormControl, serializeNativeFormValue, rdxDevWarning, isItemEqualToValue, itemToStringLabel, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, setupInternalBackdrop, injectId, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useAnchoredScrollLock, RdxFloatingNodeRegistration, rdxDevError } from '@radix-ng/primitives/core';
|
|
12
12
|
import { injectDirection } from '@radix-ng/primitives/direction-provider';
|
|
13
13
|
import { injectFieldRootContext } from '@radix-ng/primitives/field';
|
|
14
14
|
|
|
@@ -89,6 +89,9 @@ const context = () => {
|
|
|
89
89
|
dir: root.dir,
|
|
90
90
|
value: root.value,
|
|
91
91
|
inputValue: root.value,
|
|
92
|
+
name: root.name,
|
|
93
|
+
form: root.form,
|
|
94
|
+
inputOwnsFormValue: root.inputOwnsFormValue,
|
|
92
95
|
open: root.open,
|
|
93
96
|
present: root.present,
|
|
94
97
|
multiple: root.alwaysFalse,
|
|
@@ -100,6 +103,11 @@ const context = () => {
|
|
|
100
103
|
modal: root.modal,
|
|
101
104
|
virtualized: root.virtualized,
|
|
102
105
|
grid: root.grid,
|
|
106
|
+
invalidState: root.invalidState,
|
|
107
|
+
pendingState: root.formUi.pendingState,
|
|
108
|
+
validState: root.formUi.validState,
|
|
109
|
+
touchedState: root.touchedState,
|
|
110
|
+
dirtyState: root.dirtyState,
|
|
103
111
|
filteredItems: engine.filteredItems,
|
|
104
112
|
highlightedItem: engine.highlightedItem,
|
|
105
113
|
highlightedIndex: engine.highlightedIndex,
|
|
@@ -142,9 +150,6 @@ const context = () => {
|
|
|
142
150
|
setInputValue: (value) => root.setQuery(value),
|
|
143
151
|
openAndHighlight: (edge, reason, event) => root.openAndHighlight(edge, reason, event),
|
|
144
152
|
navigateByKeyboard: (direction, event) => root.navigateByKeyboard(direction, event),
|
|
145
|
-
invalidState: root.invalidState,
|
|
146
|
-
touchedState: root.touchedState,
|
|
147
|
-
dirtyState: root.dirtyState,
|
|
148
153
|
select: (item, event) => root.handleSelect(item, event),
|
|
149
154
|
selectIndex: (index, event) => root.selectIndex(index, event),
|
|
150
155
|
selectHighlighted: (event) => root.selectHighlighted(event),
|
|
@@ -264,6 +269,12 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
|
|
|
264
269
|
this.readOnly = input(false, { ...(ngDevMode ? { debugName: "readOnly" } : /* istanbul ignore next */ {}), alias: 'readOnly', transform: booleanAttribute });
|
|
265
270
|
/** Whether a value is required (for forms). */
|
|
266
271
|
this.required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
272
|
+
/** Name used when serializing this composite control into native `FormData`. */
|
|
273
|
+
this.name = input(/* @ts-ignore */
|
|
274
|
+
...(ngDevMode ? [undefined, { debugName: "name" }] : /* istanbul ignore next */ []));
|
|
275
|
+
/** Id of an external form that owns this control. */
|
|
276
|
+
this.form = input(/* @ts-ignore */
|
|
277
|
+
...(ngDevMode ? [undefined, { debugName: "form" }] : /* istanbul ignore next */ []));
|
|
267
278
|
/** Whether keyboard navigation wraps at the list boundaries. */
|
|
268
279
|
this.loopFocus = input(true, { ...(ngDevMode ? { debugName: "loopFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
269
280
|
/**
|
|
@@ -304,6 +315,9 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
|
|
|
304
315
|
*/
|
|
305
316
|
this.filter = input(undefined, /* @ts-ignore */
|
|
306
317
|
...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
318
|
+
/** Locale for the default `contains` filter's string comparison. Defaults to the runtime locale. */
|
|
319
|
+
this.locale = input(/* @ts-ignore */
|
|
320
|
+
...(ngDevMode ? [undefined, { debugName: "locale" }] : /* istanbul ignore next */ []));
|
|
307
321
|
/** Maximum number of matching items to show. `-1` (default) means no limit. */
|
|
308
322
|
this.limit = input(-1, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
309
323
|
/** The full set of item values for {@link virtualized} mode. */
|
|
@@ -328,16 +342,42 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
|
|
|
328
342
|
/** Constant signals exposed to the combobox context (autocomplete is always single-value). */
|
|
329
343
|
this.alwaysFalse = signal(false, /* @ts-ignore */
|
|
330
344
|
...(ngDevMode ? [{ debugName: "alwaysFalse" }] : /* istanbul ignore next */ []));
|
|
345
|
+
this.inputOwnsFormValue = computed(() => this.engine.inputLayout() !== 'inside', /* @ts-ignore */
|
|
346
|
+
...(ngDevMode ? [{ debugName: "inputOwnsFormValue" }] : /* istanbul ignore next */ []));
|
|
331
347
|
this.noneMode = signal('none', /* @ts-ignore */
|
|
332
348
|
...(ngDevMode ? [{ debugName: "noneMode" }] : /* istanbul ignore next */ []));
|
|
333
349
|
this.cvaDisabled = signal(false, /* @ts-ignore */
|
|
334
350
|
...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
335
351
|
this.disabledState = computed(() => this.disabled() || this.cvaDisabled(), /* @ts-ignore */
|
|
336
352
|
...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
353
|
+
this.nativeFormControl = useNativeFormControl({
|
|
354
|
+
name: this.name,
|
|
355
|
+
form: this.form,
|
|
356
|
+
disabled: this.disabledState,
|
|
357
|
+
value: this.value,
|
|
358
|
+
serialize: serializeNativeFormValue,
|
|
359
|
+
hasNativeControl: this.inputOwnsFormValue,
|
|
360
|
+
syncNativeControl: () => {
|
|
361
|
+
const input = this.engine.inputElement();
|
|
362
|
+
if (input && this.inputOwnsFormValue()) {
|
|
363
|
+
input.value = this.value();
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
defaultValue: () => this.defaultValue() ?? this.value(),
|
|
367
|
+
onReset: (value) => {
|
|
368
|
+
this.writeValue(value);
|
|
369
|
+
if (!this.resetNgControl(value)) {
|
|
370
|
+
this.onChange?.(value);
|
|
371
|
+
}
|
|
372
|
+
this.formUi.resetInteractionState?.();
|
|
373
|
+
}
|
|
374
|
+
});
|
|
337
375
|
this.requiredState = computed(() => this.required(), /* @ts-ignore */
|
|
338
376
|
...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
339
377
|
/** @ignore */
|
|
340
378
|
this.invalidState = this.formUi.invalidState;
|
|
379
|
+
this.pendingState = this.formUi.pendingState;
|
|
380
|
+
this.validState = this.formUi.validState;
|
|
341
381
|
/** @ignore */
|
|
342
382
|
this.touchedState = this.formUi.touchedState;
|
|
343
383
|
/** @ignore */
|
|
@@ -375,6 +415,7 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
|
|
|
375
415
|
virtualized: this.virtualized,
|
|
376
416
|
items: this.items,
|
|
377
417
|
filter: this.filter,
|
|
418
|
+
locale: this.locale,
|
|
378
419
|
limit: this.limit,
|
|
379
420
|
grid: this.grid,
|
|
380
421
|
rowOf: (element) => element.closest('[rdxAutocompleteRow]'),
|
|
@@ -538,7 +579,7 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
|
|
|
538
579
|
}
|
|
539
580
|
maybeSubmit() {
|
|
540
581
|
if (this.submitOnItemClick()) {
|
|
541
|
-
this.
|
|
582
|
+
this.nativeFormControl.requestSubmit();
|
|
542
583
|
}
|
|
543
584
|
}
|
|
544
585
|
selectHighlighted(event = new Event('autocomplete.item-press')) {
|
|
@@ -670,7 +711,7 @@ class RdxAutocompleteRoot extends RdxFormUiControlBase {
|
|
|
670
711
|
this.cvaDisabled.set(isDisabled);
|
|
671
712
|
}
|
|
672
713
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxAutocompleteRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
673
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxAutocompleteRoot, isStandalone: true, selector: "[rdxAutocompleteRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, loopFocus: { classPropertyName: "loopFocus", publicName: "loopFocus", isSignal: true, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: true, isRequired: false, transformFunction: null }, highlightItemOnHover: { classPropertyName: "highlightItemOnHover", publicName: "highlightItemOnHover", isSignal: true, isRequired: false, transformFunction: null }, keepHighlight: { classPropertyName: "keepHighlight", publicName: "keepHighlight", isSignal: true, isRequired: false, transformFunction: null }, openOnInputClick: { classPropertyName: "openOnInputClick", publicName: "openOnInputClick", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, submitOnItemClick: { classPropertyName: "submitOnItemClick", publicName: "submitOnItemClick", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, virtualized: { classPropertyName: "virtualized", publicName: "virtualized", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringValue: { classPropertyName: "itemToStringValue", publicName: "itemToStringValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onItemHighlighted: "onItemHighlighted", onOpenChangeComplete: "onOpenChangeComplete" }, host: { properties: { "attr.data-disabled": "disabledState() ? \"\" : undefined" } }, providers: [
|
|
714
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxAutocompleteRoot, isStandalone: true, selector: "[rdxAutocompleteRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, defaultOpen: { classPropertyName: "defaultOpen", publicName: "defaultOpen", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readOnly: { classPropertyName: "readOnly", publicName: "readOnly", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", 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 }, loopFocus: { classPropertyName: "loopFocus", publicName: "loopFocus", isSignal: true, isRequired: false, transformFunction: null }, autoHighlight: { classPropertyName: "autoHighlight", publicName: "autoHighlight", isSignal: true, isRequired: false, transformFunction: null }, highlightItemOnHover: { classPropertyName: "highlightItemOnHover", publicName: "highlightItemOnHover", isSignal: true, isRequired: false, transformFunction: null }, keepHighlight: { classPropertyName: "keepHighlight", publicName: "keepHighlight", isSignal: true, isRequired: false, transformFunction: null }, openOnInputClick: { classPropertyName: "openOnInputClick", publicName: "openOnInputClick", isSignal: true, isRequired: false, transformFunction: null }, modal: { classPropertyName: "modal", publicName: "modal", isSignal: true, isRequired: false, transformFunction: null }, submitOnItemClick: { classPropertyName: "submitOnItemClick", publicName: "submitOnItemClick", isSignal: true, isRequired: false, transformFunction: null }, grid: { classPropertyName: "grid", publicName: "grid", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, limit: { classPropertyName: "limit", publicName: "limit", isSignal: true, isRequired: false, transformFunction: null }, items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: false, transformFunction: null }, virtualized: { classPropertyName: "virtualized", publicName: "virtualized", isSignal: true, isRequired: false, transformFunction: null }, isItemEqualToValue: { classPropertyName: "isItemEqualToValue", publicName: "isItemEqualToValue", isSignal: true, isRequired: false, transformFunction: null }, itemToStringValue: { classPropertyName: "itemToStringValue", publicName: "itemToStringValue", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", open: "openChange", onValueChange: "onValueChange", onOpenChange: "onOpenChange", onItemHighlighted: "onItemHighlighted", onOpenChangeComplete: "onOpenChangeComplete" }, host: { properties: { "attr.data-disabled": "disabledState() ? \"\" : undefined" } }, providers: [
|
|
674
715
|
provideComboboxRootContext(context),
|
|
675
716
|
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxAutocompleteRoot, multi: true },
|
|
676
717
|
// New floating foundation (ADR 0015/0017) — the dismissal capability reads this shared context.
|
|
@@ -695,7 +736,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
695
736
|
'[attr.data-disabled]': 'disabledState() ? "" : undefined'
|
|
696
737
|
}
|
|
697
738
|
}]
|
|
698
|
-
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], loopFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], autoHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHighlight", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], keepHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepHighlight", required: false }] }], openOnInputClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnInputClick", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], submitOnItemClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitOnItemClick", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], virtualized: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualized", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringValue", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onItemHighlighted: [{ type: i0.Output, args: ["onItemHighlighted"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
739
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], defaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultOpen", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readOnly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readOnly", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], loopFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "loopFocus", required: false }] }], autoHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoHighlight", required: false }] }], highlightItemOnHover: [{ type: i0.Input, args: [{ isSignal: true, alias: "highlightItemOnHover", required: false }] }], keepHighlight: [{ type: i0.Input, args: [{ isSignal: true, alias: "keepHighlight", required: false }] }], openOnInputClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "openOnInputClick", required: false }] }], modal: [{ type: i0.Input, args: [{ isSignal: true, alias: "modal", required: false }] }], submitOnItemClick: [{ type: i0.Input, args: [{ isSignal: true, alias: "submitOnItemClick", required: false }] }], grid: [{ type: i0.Input, args: [{ isSignal: true, alias: "grid", required: false }] }], filter: [{ type: i0.Input, args: [{ isSignal: true, alias: "filter", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], limit: [{ type: i0.Input, args: [{ isSignal: true, alias: "limit", required: false }] }], items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], virtualized: [{ type: i0.Input, args: [{ isSignal: true, alias: "virtualized", required: false }] }], isItemEqualToValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "isItemEqualToValue", required: false }] }], itemToStringValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringValue", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onItemHighlighted: [{ type: i0.Output, args: ["onItemHighlighted"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
699
740
|
|
|
700
741
|
/**
|
|
701
742
|
* Clears the input value. Hidden when there is nothing to clear.
|
|
@@ -905,18 +946,21 @@ class RdxAutocompleteInput {
|
|
|
905
946
|
// `aria-autocomplete` tokens. Must reflect the static mechanism, not the transient inline preview.
|
|
906
947
|
this.ariaAutocomplete = computed(() => this.root.mode(), /* @ts-ignore */
|
|
907
948
|
...(ngDevMode ? [{ debugName: "ariaAutocomplete" }] : /* istanbul ignore next */ []));
|
|
908
|
-
this.invalidState = computed(() => this.invalid() || this.root.invalidState() || Boolean(this.fieldRootContext?.invalidState()), /* @ts-ignore */
|
|
909
|
-
...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
|
|
910
949
|
/**
|
|
911
950
|
* Tri-state *displayed* validity: the enclosing Field's gated `validState` when inside a `rdxFieldRoot`
|
|
912
951
|
* (so a field whose `validationMode` defers display (e.g. `onBlur`) keeps the input neutral until revealed), else the input's
|
|
913
952
|
* own binary invalidity.
|
|
914
953
|
*/
|
|
915
|
-
this.displayValid = computed(() =>
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
954
|
+
this.displayValid = computed(() => {
|
|
955
|
+
if (this.fieldRootContext) {
|
|
956
|
+
return this.fieldRootContext.validState();
|
|
957
|
+
}
|
|
958
|
+
const rootValid = this.root.validState();
|
|
959
|
+
if (rootValid === null) {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
return this.invalid() ? false : rootValid;
|
|
963
|
+
}, /* @ts-ignore */
|
|
920
964
|
...(ngDevMode ? [{ debugName: "displayValid" }] : /* istanbul ignore next */ []));
|
|
921
965
|
this.disabledState = computed(() => this.root.disabledState() || Boolean(this.fieldRootContext?.disabledState()), /* @ts-ignore */
|
|
922
966
|
...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
@@ -934,10 +978,7 @@ class RdxAutocompleteInput {
|
|
|
934
978
|
if (!this.fieldRootContext) {
|
|
935
979
|
return undefined;
|
|
936
980
|
}
|
|
937
|
-
const ids = [
|
|
938
|
-
...this.fieldRootContext.descriptionIds(),
|
|
939
|
-
...(this.fieldRootContext.invalidState() ? this.fieldRootContext.errorIds() : [])
|
|
940
|
-
];
|
|
981
|
+
const ids = [...this.fieldRootContext.descriptionIds(), ...this.fieldRootContext.errorIds()];
|
|
941
982
|
return ids.length ? ids.join(' ') : undefined;
|
|
942
983
|
}, /* @ts-ignore */
|
|
943
984
|
...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
@@ -980,11 +1021,20 @@ class RdxAutocompleteInput {
|
|
|
980
1021
|
this.commitInput(event.target.value, event);
|
|
981
1022
|
}
|
|
982
1023
|
commitInput(value, event) {
|
|
1024
|
+
// The edit itself is authoritative. Mobile keyboards, context-menu cut/paste, drag edits and
|
|
1025
|
+
// IME commits may have no useful keydown, so carrying suppression from the previous key makes
|
|
1026
|
+
// inline completion stale in either direction.
|
|
1027
|
+
const suppressInline = shouldSuppressInlineCompletion(event, value, this.root.value());
|
|
983
1028
|
// Base UI opens on input only for a non-empty trimmed value — whitespace alone won't open it.
|
|
984
1029
|
if (!this.root.open() && value.trim() !== '') {
|
|
985
1030
|
this.root.setOpen(true, 'input-change', event);
|
|
986
1031
|
}
|
|
987
1032
|
this.root.setQuery(value);
|
|
1033
|
+
// `onValueChange` is cancelable. Only attach this edit's suppression state if the requested
|
|
1034
|
+
// value actually committed; a vetoed edit must leave the previous preview semantics intact.
|
|
1035
|
+
if (this.root.value() === value) {
|
|
1036
|
+
this.root.setSuppressInline(suppressInline);
|
|
1037
|
+
}
|
|
988
1038
|
}
|
|
989
1039
|
onClick(event) {
|
|
990
1040
|
if (this.root.openOnInputClick()) {
|
|
@@ -1004,8 +1054,6 @@ class RdxAutocompleteInput {
|
|
|
1004
1054
|
if (event.isComposing || this.composing) {
|
|
1005
1055
|
return;
|
|
1006
1056
|
}
|
|
1007
|
-
// Backspace / Delete must never re-add an inline completion for the resulting edit.
|
|
1008
|
-
this.root.setSuppressInline(event.key === 'Backspace' || event.key === 'Delete');
|
|
1009
1057
|
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) {
|
|
1010
1058
|
return;
|
|
1011
1059
|
}
|
|
@@ -1100,7 +1148,7 @@ class RdxAutocompleteInput {
|
|
|
1100
1148
|
}
|
|
1101
1149
|
}
|
|
1102
1150
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxAutocompleteInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1103
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxAutocompleteInput, isStandalone: true, selector: "input[rdxAutocompleteInput]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "autocomplete": "off" }, listeners: { "input": "onInput($event)", "click": "onClick($event)", "focus": "onFocus()", "blur": "onBlur()", "keydown": "onKeydown($event)", "compositionstart": "composing = true", "compositionend": "onCompositionEnd($event)" }, properties: { "attr.aria-autocomplete": "ariaAutocomplete()", "attr.id": "id()", "attr.aria-haspopup": "root.grid() ? \"grid\" : \"listbox\"", "attr.aria-expanded": "root.open()", "attr.aria-controls": "root.listId", "attr.aria-labelledby": "root.labelId()", "attr.aria-activedescendant": "root.activeId()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "displayValid() === false ? \"true\" : undefined", "attr.aria-required": "requiredState() ? \"true\" : undefined", "attr.aria-disabled": "disabledState() ? \"true\" : undefined", "attr.disabled": "disabledState() ? \"\" : undefined", "attr.readonly": "root.readOnly() ? \"\" : undefined", "attr.required": "requiredState() ? \"\" : undefined", "value": "root.displayValue()", "attr.data-popup-open": "dataAttr(root.open())", "attr.data-list-empty": "dataAttr(root.visibleCount() === 0)", "attr.data-invalid": "dataAttr(displayValid() === false)", "attr.data-valid": "dataAttr(displayValid() === true)", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-touched": "dataAttr(touchedState())", "attr.data-dirty": "dataAttr(dirtyState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, exportAs: ["rdxAutocompleteInput"], hostDirectives: [{ directive: i1$1.RdxPopperAnchor }, { directive: i1$2.RdxFloatingInsideElement }], ngImport: i0 }); }
|
|
1151
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: RdxAutocompleteInput, isStandalone: true, selector: "input[rdxAutocompleteInput]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, invalid: { classPropertyName: "invalid", publicName: "invalid", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "autocomplete": "off" }, listeners: { "input": "onInput($event)", "click": "onClick($event)", "focus": "onFocus()", "blur": "onBlur()", "keydown": "onKeydown($event)", "compositionstart": "composing = true", "compositionend": "onCompositionEnd($event)" }, properties: { "attr.aria-autocomplete": "ariaAutocomplete()", "attr.id": "id()", "attr.aria-haspopup": "root.grid() ? \"grid\" : \"listbox\"", "attr.aria-expanded": "root.open()", "attr.aria-controls": "root.listId", "attr.aria-labelledby": "root.labelId()", "attr.aria-activedescendant": "root.activeId()", "attr.aria-describedby": "describedBy()", "attr.aria-invalid": "displayValid() === false ? \"true\" : undefined", "attr.aria-required": "requiredState() ? \"true\" : undefined", "attr.aria-disabled": "disabledState() ? \"true\" : undefined", "attr.disabled": "disabledState() ? \"\" : undefined", "attr.readonly": "root.readOnly() ? \"\" : undefined", "attr.required": "requiredState() ? \"\" : undefined", "attr.name": "root.inputOwnsFormValue() ? root.name() : undefined", "attr.form": "root.form()", "value": "root.displayValue()", "attr.data-popup-open": "dataAttr(root.open())", "attr.data-list-empty": "dataAttr(root.visibleCount() === 0)", "attr.data-invalid": "dataAttr(displayValid() === false)", "attr.data-valid": "dataAttr(displayValid() === true)", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-touched": "dataAttr(touchedState())", "attr.data-dirty": "dataAttr(dirtyState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, exportAs: ["rdxAutocompleteInput"], hostDirectives: [{ directive: i1$1.RdxPopperAnchor }, { directive: i1$2.RdxFloatingInsideElement }], ngImport: i0 }); }
|
|
1104
1152
|
}
|
|
1105
1153
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: RdxAutocompleteInput, decorators: [{
|
|
1106
1154
|
type: Directive,
|
|
@@ -1125,6 +1173,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
1125
1173
|
'[attr.disabled]': 'disabledState() ? "" : undefined',
|
|
1126
1174
|
'[attr.readonly]': 'root.readOnly() ? "" : undefined',
|
|
1127
1175
|
'[attr.required]': 'requiredState() ? "" : undefined',
|
|
1176
|
+
'[attr.name]': 'root.inputOwnsFormValue() ? root.name() : undefined',
|
|
1177
|
+
'[attr.form]': 'root.form()',
|
|
1128
1178
|
'[value]': 'root.displayValue()',
|
|
1129
1179
|
'[attr.data-popup-open]': 'dataAttr(root.open())',
|
|
1130
1180
|
'[attr.data-list-empty]': 'dataAttr(root.visibleCount() === 0)',
|
|
@@ -1146,6 +1196,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
|
|
|
1146
1196
|
}
|
|
1147
1197
|
}]
|
|
1148
1198
|
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }] } });
|
|
1199
|
+
/** Whether this concrete edit should keep inline completion hidden for its resulting value. */
|
|
1200
|
+
function shouldSuppressInlineCompletion(event, nextValue, previousValue) {
|
|
1201
|
+
// A committed composition inserts text even though CompositionEvent has no `inputType`.
|
|
1202
|
+
if (event.type === 'compositionend') {
|
|
1203
|
+
return false;
|
|
1204
|
+
}
|
|
1205
|
+
const inputType = event.inputType;
|
|
1206
|
+
if (inputType?.startsWith('delete')) {
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
if (inputType?.startsWith('insert')) {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
// Synthetic/programmatic input and history edits may omit an inputType. Length is the safest
|
|
1213
|
+
// fallback: shrinking must not immediately restore the suffix; equal/growing replacements may.
|
|
1214
|
+
return nextValue.length < previousValue.length;
|
|
1215
|
+
}
|
|
1149
1216
|
|
|
1150
1217
|
const attr = (value) => (value ? '' : undefined);
|
|
1151
1218
|
/**
|