@radix-ng/primitives 1.0.0-beta.2 → 1.0.0-beta.4
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/LICENSE +1 -1
- package/README.md +76 -6
- package/fesm2022/radix-ng-primitives-accordion.mjs +5 -3
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +31 -24
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +1744 -0
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1399 -606
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-config.mjs +13 -4
- package/fesm2022/radix-ng-primitives-config.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs +51 -10
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +1345 -64
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +5 -3
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +271 -145
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-direction-provider.mjs +70 -0
- package/fesm2022/radix-ng-primitives-direction-provider.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs +519 -184
- package/fesm2022/radix-ng-primitives-dismissable-layer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +154 -64
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +3 -2
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +517 -0
- package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +296 -70
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +894 -299
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +32 -4
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +176 -207
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +250 -250
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +94 -45
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +107 -17
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +262 -79
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +172 -218
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +4 -2
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +5 -4
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +303 -234
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +5 -3
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-stepper.mjs +5 -3
- package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +5 -3
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs +15 -36
- package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +5 -3
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +5 -3
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +105 -145
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +14 -1
- package/types/radix-ng-primitives-accordion.d.ts +4 -3
- package/types/radix-ng-primitives-alert-dialog.d.ts +17 -11
- package/types/radix-ng-primitives-autocomplete.d.ts +661 -0
- package/types/radix-ng-primitives-calendar.d.ts +5 -3
- package/types/radix-ng-primitives-combobox.d.ts +727 -293
- package/types/radix-ng-primitives-config.d.ts +1 -1
- package/types/radix-ng-primitives-context-menu.d.ts +15 -5
- package/types/radix-ng-primitives-core.d.ts +762 -14
- package/types/radix-ng-primitives-date-field.d.ts +3 -2
- package/types/radix-ng-primitives-dialog.d.ts +107 -55
- package/types/radix-ng-primitives-direction-provider.d.ts +41 -0
- package/types/radix-ng-primitives-dismissable-layer.d.ts +147 -99
- package/types/radix-ng-primitives-drawer.d.ts +49 -22
- package/types/radix-ng-primitives-field.d.ts +1 -0
- package/types/radix-ng-primitives-floating-focus-manager.d.ts +175 -0
- package/types/radix-ng-primitives-focus-scope.d.ts +132 -1
- package/types/radix-ng-primitives-menu.d.ts +204 -112
- package/types/radix-ng-primitives-navigation-menu.d.ts +61 -101
- package/types/radix-ng-primitives-popover.d.ts +82 -115
- package/types/radix-ng-primitives-popper.d.ts +46 -10
- package/types/radix-ng-primitives-portal.d.ts +53 -8
- package/types/radix-ng-primitives-presence.d.ts +98 -17
- package/types/radix-ng-primitives-preview-card.d.ts +63 -95
- package/types/radix-ng-primitives-roving-focus.d.ts +7 -6
- package/types/radix-ng-primitives-scroll-area.d.ts +2 -2
- package/types/radix-ng-primitives-select.d.ts +192 -158
- package/types/radix-ng-primitives-slider.d.ts +5 -4
- package/types/radix-ng-primitives-stepper.d.ts +4 -3
- package/types/radix-ng-primitives-time-field.d.ts +3 -2
- package/types/radix-ng-primitives-toast.d.ts +7 -7
- package/types/radix-ng-primitives-toggle-group.d.ts +5 -4
- package/types/radix-ng-primitives-toolbar.d.ts +3 -2
- package/types/radix-ng-primitives-tooltip.d.ts +48 -84
|
@@ -0,0 +1,1744 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Directive, inject, booleanAttribute, Injector, ElementRef, model, input, computed, numberAttribute, output, signal, effect, untracked, isDevMode, ChangeDetectionStrategy, Component, afterNextRender, afterRenderEffect, DestroyRef, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@radix-ng/primitives/combobox';
|
|
4
|
+
import { RdxComboboxAnchor, RdxComboboxBackdrop, useComboboxEngine, provideComboboxRootContext, injectComboboxRootContext, RdxComboboxGroup, RdxComboboxGroupLabel, RdxComboboxIcon, injectComboboxGroupContext, provideComboboxItemContext, RdxComboboxItemIndicator, RdxComboboxLabel, RdxComboboxPortal, RdxComboboxSeparator, RdxComboboxStatus, RdxComboboxTrigger } from '@radix-ng/primitives/combobox';
|
|
5
|
+
import * as i1$1 from '@radix-ng/primitives/popper';
|
|
6
|
+
import { RdxPopperArrow, RdxPopper, RdxPopperContentWrapper, provideRdxPopperContentWrapper, provideRdxPopperContentConfig, RdxPopperAnchor, injectPopperContentWrapperContext, RdxPopperContent } from '@radix-ng/primitives/popper';
|
|
7
|
+
import * as i1$2 from '@radix-ng/primitives/dismissable-layer';
|
|
8
|
+
import { RdxFloatingInsideElement, RdxDismiss } from '@radix-ng/primitives/dismissable-layer';
|
|
9
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
10
|
+
import * as i2 from '@radix-ng/primitives/core';
|
|
11
|
+
import { createFloatingRootContext, rdxDevWarning, isItemEqualToValue, itemToStringLabel, createCancelableChangeEventDetails, provideFloatingTree, provideFloatingRootContext, setupInternalBackdrop, injectId, RDX_FLOATING_ROOT_CONTEXT, RDX_FLOATING_REGISTRATION, useAnchoredScrollLock, RdxFloatingNodeRegistration, rdxDevError } from '@radix-ng/primitives/core';
|
|
12
|
+
import { injectDirection } from '@radix-ng/primitives/direction-provider';
|
|
13
|
+
import { injectFieldRootContext } from '@radix-ng/primitives/field';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Optional positioning anchor for the popup. Put it on the element the popup should align to — for
|
|
17
|
+
* example a wrapper that holds the input plus an icon/clear button. When present it takes precedence
|
|
18
|
+
* over the input. Reuses the combobox anchor behavior.
|
|
19
|
+
*
|
|
20
|
+
* @group Components
|
|
21
|
+
*/
|
|
22
|
+
class RdxAutocompleteAnchor {
|
|
23
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteAnchor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
24
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteAnchor, isStandalone: true, selector: "[rdxAutocompleteAnchor]", exportAs: ["rdxAutocompleteAnchor"], hostDirectives: [{ directive: i1.RdxComboboxAnchor }], ngImport: i0 }); }
|
|
25
|
+
}
|
|
26
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteAnchor, decorators: [{
|
|
27
|
+
type: Directive,
|
|
28
|
+
args: [{
|
|
29
|
+
selector: '[rdxAutocompleteAnchor]',
|
|
30
|
+
exportAs: 'rdxAutocompleteAnchor',
|
|
31
|
+
hostDirectives: [RdxComboboxAnchor]
|
|
32
|
+
}]
|
|
33
|
+
}] });
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* An optional arrow that points from the popup to the anchor. Composes the popper arrow directly
|
|
37
|
+
* (the same building block the combobox arrow uses).
|
|
38
|
+
*
|
|
39
|
+
* @group Components
|
|
40
|
+
*/
|
|
41
|
+
class RdxAutocompleteArrow {
|
|
42
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteArrow, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
43
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteArrow, isStandalone: true, selector: "[rdxAutocompleteArrow]", exportAs: ["rdxAutocompleteArrow"], hostDirectives: [{ directive: i1$1.RdxPopperArrow, inputs: ["width", "width", "height", "height"] }], ngImport: i0 }); }
|
|
44
|
+
}
|
|
45
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteArrow, decorators: [{
|
|
46
|
+
type: Directive,
|
|
47
|
+
args: [{
|
|
48
|
+
selector: '[rdxAutocompleteArrow]',
|
|
49
|
+
exportAs: 'rdxAutocompleteArrow',
|
|
50
|
+
hostDirectives: [{ directive: RdxPopperArrow, inputs: ['width', 'height'] }]
|
|
51
|
+
}]
|
|
52
|
+
}] });
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* An overlay rendered beneath the popup in `modal` mode. Exposes `data-open` / `data-closed` for
|
|
56
|
+
* animation. Reuses the combobox backdrop.
|
|
57
|
+
*
|
|
58
|
+
* @group Components
|
|
59
|
+
*/
|
|
60
|
+
class RdxAutocompleteBackdrop {
|
|
61
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
62
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteBackdrop, isStandalone: true, selector: "[rdxAutocompleteBackdrop]", exportAs: ["rdxAutocompleteBackdrop"], hostDirectives: [{ directive: i1.RdxComboboxBackdrop }], ngImport: i0 }); }
|
|
63
|
+
}
|
|
64
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteBackdrop, decorators: [{
|
|
65
|
+
type: Directive,
|
|
66
|
+
args: [{
|
|
67
|
+
selector: '[rdxAutocompleteBackdrop]',
|
|
68
|
+
exportAs: 'rdxAutocompleteBackdrop',
|
|
69
|
+
hostDirectives: [RdxComboboxBackdrop]
|
|
70
|
+
}]
|
|
71
|
+
}] });
|
|
72
|
+
|
|
73
|
+
// The engine stays private to the root; the context factory (a free function) reads it through this registry.
|
|
74
|
+
const engineRegistry = new WeakMap();
|
|
75
|
+
/**
|
|
76
|
+
* Builds the {@link RdxComboboxRootContext} the autocomplete parts consume. Autocomplete reuses the
|
|
77
|
+
* combobox parts (List, Popup, Positioner, Item, …) verbatim, so it provides the exact combobox context
|
|
78
|
+
* shape — configured for a single-value, `selectionMode: 'none'` control whose value **is** the input
|
|
79
|
+
* string. The shared engine supplies registry / filtering / highlight / inline; the root supplies the
|
|
80
|
+
* string-value semantics. Autocomplete-specific parts (mode, inline, grid) inject {@link RdxAutocompleteRoot}.
|
|
81
|
+
*/
|
|
82
|
+
const context = () => {
|
|
83
|
+
const root = inject(RdxAutocompleteRoot);
|
|
84
|
+
const engine = engineRegistry.get(root);
|
|
85
|
+
return {
|
|
86
|
+
listId: engine.listId,
|
|
87
|
+
labelId: engine.labelId,
|
|
88
|
+
setLabelId: (id) => engine.setLabelId(id),
|
|
89
|
+
dir: root.dir,
|
|
90
|
+
value: root.value,
|
|
91
|
+
inputValue: root.value,
|
|
92
|
+
open: root.open,
|
|
93
|
+
present: root.present,
|
|
94
|
+
multiple: root.alwaysFalse,
|
|
95
|
+
selectionMode: root.noneMode,
|
|
96
|
+
disabledState: root.disabledState,
|
|
97
|
+
readonly: root.readOnly,
|
|
98
|
+
requiredState: root.requiredState,
|
|
99
|
+
openOnInputClick: root.openOnInputClick,
|
|
100
|
+
modal: root.modal,
|
|
101
|
+
virtualized: root.virtualized,
|
|
102
|
+
grid: root.grid,
|
|
103
|
+
filteredItems: engine.filteredItems,
|
|
104
|
+
highlightedItem: engine.highlightedItem,
|
|
105
|
+
highlightedIndex: engine.highlightedIndex,
|
|
106
|
+
activeId: engine.activeId,
|
|
107
|
+
itemId: (index) => engine.itemId(index),
|
|
108
|
+
isKeyboardActive: () => engine.isKeyboardActive(),
|
|
109
|
+
setKeyboardActive: (value) => engine.setKeyboardActive(value),
|
|
110
|
+
transitionStatus: engine.transitionStatus,
|
|
111
|
+
registerTransitionElement: engine.registerTransitionElement,
|
|
112
|
+
visibleCount: engine.visibleCount,
|
|
113
|
+
isVisible: (item) => engine.isVisible(item),
|
|
114
|
+
isSelected: (value) => root.isSelectedValue(value),
|
|
115
|
+
registerItem: (item) => engine.registerItem(item),
|
|
116
|
+
unregisterItem: (item) => engine.unregisterItem(item),
|
|
117
|
+
highlight: engine.highlight,
|
|
118
|
+
highlightNext: () => engine.highlightNext('keyboard'),
|
|
119
|
+
highlightPrevious: () => engine.highlightPrevious('keyboard'),
|
|
120
|
+
highlightNextColumn: () => engine.highlightNextColumn('keyboard'),
|
|
121
|
+
highlightPreviousColumn: () => engine.highlightPreviousColumn('keyboard'),
|
|
122
|
+
highlightFirst: () => engine.highlightFirst('keyboard'),
|
|
123
|
+
highlightLast: () => engine.highlightLast('keyboard'),
|
|
124
|
+
highlightIndex: (index, reason) => engine.highlightIndex(index, reason),
|
|
125
|
+
setHighlight: (item, reason) => engine.setHighlight(item, reason),
|
|
126
|
+
clearHighlight: () => engine.clearHighlightState(),
|
|
127
|
+
highlightItemOnHover: root.highlightItemOnHover,
|
|
128
|
+
keepHighlight: root.keepHighlight,
|
|
129
|
+
inputElement: engine.inputElement,
|
|
130
|
+
setInputElement: (el) => engine.setInputElement(el),
|
|
131
|
+
inputLayout: engine.inputLayout,
|
|
132
|
+
setInputLayout: (layout) => engine.setInputLayout(layout),
|
|
133
|
+
openedByTouch: engine.openedByTouch,
|
|
134
|
+
setOpenedByTouch: (value) => engine.setOpenedByTouch(value),
|
|
135
|
+
popupMounted: engine.popupMounted,
|
|
136
|
+
setPopupMounted: (value) => engine.setPopupMounted(value),
|
|
137
|
+
registerTrigger: (el) => engine.setTrigger(el),
|
|
138
|
+
focusInput: () => engine.focusInput(),
|
|
139
|
+
openPopup: (reason, event) => root.setOpen(true, reason, event),
|
|
140
|
+
openForBrowse: (reason, event) => root.openForBrowse(reason, event),
|
|
141
|
+
closePopup: (revert = true, reason, event) => root.closePopup(revert, reason, event),
|
|
142
|
+
setInputValue: (value) => root.setQuery(value),
|
|
143
|
+
openAndHighlight: (edge, reason, event) => root.openAndHighlight(edge, reason, event),
|
|
144
|
+
navigateByKeyboard: (direction, event) => root.navigateByKeyboard(direction, event),
|
|
145
|
+
select: (item, event) => root.handleSelect(item, event),
|
|
146
|
+
selectIndex: (index, event) => root.selectIndex(index, event),
|
|
147
|
+
selectHighlighted: (event) => root.selectHighlighted(event),
|
|
148
|
+
clearSelection: () => root.clearValue(),
|
|
149
|
+
removeValue: () => undefined,
|
|
150
|
+
removeLastValue: () => undefined,
|
|
151
|
+
registerChipsNav: () => undefined,
|
|
152
|
+
focusLastChip: () => false,
|
|
153
|
+
labelFor: (value) => root.labelFor(value),
|
|
154
|
+
markAsTouched: () => root.markAsTouched()
|
|
155
|
+
// `value`/`inputValue` are the input string here, not `ComboboxValue`.
|
|
156
|
+
};
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* `autoHighlight` transform: pass `'always'` through verbatim, coerce everything else as a boolean
|
|
160
|
+
* attribute (so the bare `autoHighlight` attribute reads `true`). Kept as a named function (compodoc
|
|
161
|
+
* hangs on an inline arrow combined with union-generic `input()`).
|
|
162
|
+
*/
|
|
163
|
+
function coerceAutoHighlight(value) {
|
|
164
|
+
return value === 'always' ? 'always' : booleanAttribute(value);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Root of an Autocomplete — a text input with a filtered list of suggestions. A thin configuration over
|
|
168
|
+
* the shared combobox engine (ADR 0014) with `selectionMode: 'none'`, so its value **is** the input
|
|
169
|
+
* string: typing, selecting an item, or clearing all change a single string value. Implements
|
|
170
|
+
* `ControlValueAccessor` (the form value is the input string).
|
|
171
|
+
*
|
|
172
|
+
* @group Components
|
|
173
|
+
*/
|
|
174
|
+
class RdxAutocompleteRoot {
|
|
175
|
+
// --- engine-backed surface read by the parts / tests ---
|
|
176
|
+
get listId() {
|
|
177
|
+
return this.engine.listId;
|
|
178
|
+
}
|
|
179
|
+
get labelId() {
|
|
180
|
+
return this.engine.labelId;
|
|
181
|
+
}
|
|
182
|
+
get inputElement() {
|
|
183
|
+
return this.engine.inputElement;
|
|
184
|
+
}
|
|
185
|
+
setInputElement(el) {
|
|
186
|
+
this.engine.setInputElement(el);
|
|
187
|
+
}
|
|
188
|
+
setInputLayout(layout) {
|
|
189
|
+
this.engine.setInputLayout(layout);
|
|
190
|
+
}
|
|
191
|
+
setPopupMounted(value) {
|
|
192
|
+
this.engine.setPopupMounted(value);
|
|
193
|
+
}
|
|
194
|
+
get inputLayout() {
|
|
195
|
+
return this.engine.inputLayout;
|
|
196
|
+
}
|
|
197
|
+
get openedByTouch() {
|
|
198
|
+
return this.engine.openedByTouch;
|
|
199
|
+
}
|
|
200
|
+
get popupMounted() {
|
|
201
|
+
return this.engine.popupMounted;
|
|
202
|
+
}
|
|
203
|
+
get highlight() {
|
|
204
|
+
return this.engine.highlight;
|
|
205
|
+
}
|
|
206
|
+
get highlightedItem() {
|
|
207
|
+
return this.engine.highlightedItem;
|
|
208
|
+
}
|
|
209
|
+
get highlightedIndex() {
|
|
210
|
+
return this.engine.highlightedIndex;
|
|
211
|
+
}
|
|
212
|
+
get activeId() {
|
|
213
|
+
return this.engine.activeId;
|
|
214
|
+
}
|
|
215
|
+
get filteredItems() {
|
|
216
|
+
return this.engine.filteredItems;
|
|
217
|
+
}
|
|
218
|
+
get visibleCount() {
|
|
219
|
+
return this.engine.visibleCount;
|
|
220
|
+
}
|
|
221
|
+
get inlinePreview() {
|
|
222
|
+
return this.engine.inlinePreview;
|
|
223
|
+
}
|
|
224
|
+
get transitionStatus() {
|
|
225
|
+
return this.engine.transitionStatus;
|
|
226
|
+
}
|
|
227
|
+
get registerTransitionElement() {
|
|
228
|
+
return this.engine.registerTransitionElement;
|
|
229
|
+
}
|
|
230
|
+
get triggerElement() {
|
|
231
|
+
return this.engine.triggerElement;
|
|
232
|
+
}
|
|
233
|
+
constructor() {
|
|
234
|
+
this.injector = inject(Injector);
|
|
235
|
+
/** Per-popup floating root context (ADR 0015) — `open` / `triggers` / reference for the dismissal engine. */
|
|
236
|
+
this.floatingContext = createFloatingRootContext({
|
|
237
|
+
ownerDocument: inject(ElementRef).nativeElement.ownerDocument,
|
|
238
|
+
open: () => this.open()
|
|
239
|
+
});
|
|
240
|
+
/** The input text. This is the form value (controlled / uncontrolled via {@link defaultValue}). */
|
|
241
|
+
this.value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
242
|
+
/** Initial input text when uncontrolled. */
|
|
243
|
+
this.defaultValue = input(...(ngDevMode ? [undefined, { debugName: "defaultValue" }] : /* istanbul ignore next */ []));
|
|
244
|
+
/** Whether the popup is open. */
|
|
245
|
+
this.open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
246
|
+
/** Initial open state when uncontrolled. */
|
|
247
|
+
this.defaultOpen = input(false, { ...(ngDevMode ? { debugName: "defaultOpen" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
248
|
+
/** Filtering / inline-completion behavior. See {@link AutocompleteMode}. */
|
|
249
|
+
this.mode = input('list', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
250
|
+
/** Text direction. */
|
|
251
|
+
this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
252
|
+
this.dir = injectDirection(this.dirInput);
|
|
253
|
+
/** Whether the autocomplete is disabled. */
|
|
254
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
255
|
+
/** Whether the autocomplete is read-only. */
|
|
256
|
+
this.readOnly = input(false, { ...(ngDevMode ? { debugName: "readOnly" } : /* istanbul ignore next */ {}), alias: 'readOnly', transform: booleanAttribute });
|
|
257
|
+
/** Whether a value is required (for forms). */
|
|
258
|
+
this.required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
259
|
+
/** Whether keyboard navigation wraps at the list boundaries. */
|
|
260
|
+
this.loopFocus = input(true, { ...(ngDevMode ? { debugName: "loopFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
261
|
+
/**
|
|
262
|
+
* Auto-highlight behavior (matches Base UI):
|
|
263
|
+
* - `false` (default): never auto-highlight;
|
|
264
|
+
* - `true` (also the bare `autoHighlight` attribute): highlight the first match as the query changes;
|
|
265
|
+
* - `'always'`: keep the first navigable item highlighted whenever the popup is open.
|
|
266
|
+
*/
|
|
267
|
+
this.autoHighlight = input(false, { ...(ngDevMode ? { debugName: "autoHighlight" } : /* istanbul ignore next */ {}), transform: coerceAutoHighlight });
|
|
268
|
+
/** Resolved auto-highlight mode. */
|
|
269
|
+
this.autoHighlightMode = computed(() => {
|
|
270
|
+
const value = this.autoHighlight();
|
|
271
|
+
if (value === 'always') {
|
|
272
|
+
return 'always';
|
|
273
|
+
}
|
|
274
|
+
if (value === true) {
|
|
275
|
+
return 'input-change';
|
|
276
|
+
}
|
|
277
|
+
return 'off';
|
|
278
|
+
}, ...(ngDevMode ? [{ debugName: "autoHighlightMode" }] : /* istanbul ignore next */ []));
|
|
279
|
+
/** Whether moving the pointer over an item highlights it. */
|
|
280
|
+
this.highlightItemOnHover = input(true, { ...(ngDevMode ? { debugName: "highlightItemOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
281
|
+
/** Whether a pointer-driven highlight is kept when the cursor leaves the list. */
|
|
282
|
+
this.keepHighlight = input(false, { ...(ngDevMode ? { debugName: "keepHighlight" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
283
|
+
/** Whether clicking the input opens the popup. Defaults to `false` (Base UI default). */
|
|
284
|
+
this.openOnInputClick = input(false, { ...(ngDevMode ? { debugName: "openOnInputClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
285
|
+
/** Whether the popup is modal: locks page scroll and makes outside content inert while open. */
|
|
286
|
+
this.modal = input(false, { ...(ngDevMode ? { debugName: "modal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
287
|
+
/** Whether selecting an item requests submit of the closest form. */
|
|
288
|
+
this.submitOnItemClick = input(false, { ...(ngDevMode ? { debugName: "submitOnItemClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
289
|
+
/** Whether the list is laid out as a 2D grid (enables row/column arrow navigation). */
|
|
290
|
+
this.grid = input(false, { ...(ngDevMode ? { debugName: "grid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
291
|
+
/**
|
|
292
|
+
* Filter applied to items against the input query (only when {@link mode} is `'list'` / `'both'`).
|
|
293
|
+
* `undefined` → locale-aware contains; a function → custom `(value, query, itemToString)`; `null` →
|
|
294
|
+
* built-in filtering disabled.
|
|
295
|
+
*/
|
|
296
|
+
this.filter = input(undefined, ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
297
|
+
/** Maximum number of matching items to show. `-1` (default) means no limit. */
|
|
298
|
+
this.limit = input(-1, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
299
|
+
/** The full set of item values for {@link virtualized} mode. */
|
|
300
|
+
this.items = input(...(ngDevMode ? [undefined, { debugName: "items" }] : /* istanbul ignore next */ []));
|
|
301
|
+
/** Whether the list is externally virtualized (navigation runs over {@link items} by index). */
|
|
302
|
+
this.virtualized = input(false, { ...(ngDevMode ? { debugName: "virtualized" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
303
|
+
/** How item values are compared for equality (a comparator function or an object key). Base UI prop name. */
|
|
304
|
+
this.isItemEqualToValue = input(...(ngDevMode ? [undefined, { debugName: "isItemEqualToValue" }] : /* istanbul ignore next */ []));
|
|
305
|
+
/** Converts an item value to its string label (filter text + input text on selection). */
|
|
306
|
+
this.itemToStringValue = input(...(ngDevMode ? [undefined, { debugName: "itemToStringValue" }] : /* istanbul ignore next */ []));
|
|
307
|
+
/** Emits when the input value changes (typing, selection, or clear), with the reason. */
|
|
308
|
+
this.onValueChange = output();
|
|
309
|
+
/** Emits when the popup opens or closes. */
|
|
310
|
+
this.onOpenChange = output();
|
|
311
|
+
/** Emits as the highlight moves, with the item's value, its index in {@link filteredItems}, and the reason. */
|
|
312
|
+
this.onItemHighlighted = output();
|
|
313
|
+
/** Emits after the open/close transition (including any exit animation) finishes. */
|
|
314
|
+
this.onOpenChangeComplete = output();
|
|
315
|
+
/** Constant signals exposed to the combobox context (autocomplete is always single-value). */
|
|
316
|
+
this.alwaysFalse = signal(false, ...(ngDevMode ? [{ debugName: "alwaysFalse" }] : /* istanbul ignore next */ []));
|
|
317
|
+
this.noneMode = signal('none', ...(ngDevMode ? [{ debugName: "noneMode" }] : /* istanbul ignore next */ []));
|
|
318
|
+
this.cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
319
|
+
this.disabledState = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
320
|
+
this.requiredState = computed(() => this.required(), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
321
|
+
this.preventUnmountOnClose = signal(false, ...(ngDevMode ? [{ debugName: "preventUnmountOnClose" }] : /* istanbul ignore next */ []));
|
|
322
|
+
this.present = computed(() => this.open() || this.preventUnmountOnClose(), ...(ngDevMode ? [{ debugName: "present" }] : /* istanbul ignore next */ []));
|
|
323
|
+
/** Whether built-in filtering applies in the current mode (`list` / `both`). */
|
|
324
|
+
this.filteringMode = computed(() => this.mode() === 'list' || this.mode() === 'both', ...(ngDevMode ? [{ debugName: "filteringMode" }] : /* istanbul ignore next */ []));
|
|
325
|
+
/** Whether inline completion applies in the current mode (`both` / `inline`). */
|
|
326
|
+
this.inlineMode = computed(() => this.mode() === 'both' || this.mode() === 'inline', ...(ngDevMode ? [{ debugName: "inlineMode" }] : /* istanbul ignore next */ []));
|
|
327
|
+
/**
|
|
328
|
+
* Whether the input text is a fresh user query rather than a committed selection's label. While
|
|
329
|
+
* `false` (just opened, or showing a committed selection), the list is unfiltered; it flips `true`
|
|
330
|
+
* on the first keystroke.
|
|
331
|
+
*/
|
|
332
|
+
this.typed = signal(false, ...(ngDevMode ? [{ debugName: "typed" }] : /* istanbul ignore next */ []));
|
|
333
|
+
/** The text the user actually typed, used as the filter query. */
|
|
334
|
+
this.query = computed(() => (this.typed() ? (this.value() ?? '') : ''), ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
335
|
+
/** The shared engine: item registry, filtering, highlight navigation (grid), inline, transition. */
|
|
336
|
+
this.engine = useComboboxEngine({
|
|
337
|
+
injector: this.injector,
|
|
338
|
+
listIdPrefix: 'rdx-autocomplete-list-',
|
|
339
|
+
popupSelector: '[rdxAutocompletePopup]',
|
|
340
|
+
open: this.open,
|
|
341
|
+
query: this.query,
|
|
342
|
+
filteringEnabled: this.filteringMode,
|
|
343
|
+
loopFocus: this.loopFocus,
|
|
344
|
+
autoHighlightMode: this.autoHighlightMode,
|
|
345
|
+
virtualized: this.virtualized,
|
|
346
|
+
items: this.items,
|
|
347
|
+
filter: this.filter,
|
|
348
|
+
limit: this.limit,
|
|
349
|
+
grid: this.grid,
|
|
350
|
+
rowOf: (element) => element.closest('[rdxAutocompleteRow]'),
|
|
351
|
+
inlineMode: this.inlineMode,
|
|
352
|
+
itemToString: (value) => this.labelFor(value),
|
|
353
|
+
onItemHighlighted: (details) => this.onItemHighlighted.emit(details),
|
|
354
|
+
onOpenChange: () => { },
|
|
355
|
+
onOpenChangeComplete: (open) => this.onOpenChangeComplete.emit(open)
|
|
356
|
+
});
|
|
357
|
+
/** What the input element displays: the inline preview if any, else the committed value. */
|
|
358
|
+
this.displayValue = computed(() => this.engine.inlinePreview() ?? this.value() ?? '', ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
359
|
+
engineRegistry.set(this, this.engine);
|
|
360
|
+
// Keep the dismissal reference in sync with the input (the anchor) so a press / focus on it counts
|
|
361
|
+
// as "inside" and never dismisses (ADR 0015).
|
|
362
|
+
effect(() => this.floatingContext.setReferenceElement(this.engine.inputElement() ?? null));
|
|
363
|
+
// Apply uncontrolled defaults once.
|
|
364
|
+
effect(() => {
|
|
365
|
+
const initial = this.defaultValue();
|
|
366
|
+
if (initial !== undefined && untracked(this.value) === '') {
|
|
367
|
+
this.value.set(initial);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
effect(() => {
|
|
371
|
+
if (untracked(this.open) === false && this.defaultOpen()) {
|
|
372
|
+
this.open.set(true);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
// Virtualized object values can't be labelled from the DOM — warn once in dev.
|
|
376
|
+
if (isDevMode()) {
|
|
377
|
+
effect(() => {
|
|
378
|
+
if (!this.virtualized() || this.itemToStringValue()) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (this.items()?.some((value) => value !== null && typeof value === 'object')) {
|
|
382
|
+
rdxDevWarning('autocomplete/virtualized-item-label', '`rdxAutocompleteRoot` `virtualized` with object item values needs `itemToStringValue` ' +
|
|
383
|
+
'to render correct labels; falling back to a generic label.', 'components/autocomplete');
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
setSuppressInline(value) {
|
|
389
|
+
this.engine.setSuppressInline(value);
|
|
390
|
+
}
|
|
391
|
+
/** Opens the popup for browsing (resets the query to "pristine" and selects the input text). */
|
|
392
|
+
openForBrowse(reason = 'none', event = new Event('autocomplete.open-change')) {
|
|
393
|
+
if (!this.open()) {
|
|
394
|
+
this.typed.set(false);
|
|
395
|
+
}
|
|
396
|
+
this.setOpen(true, reason, event);
|
|
397
|
+
this.engine.selectInputText();
|
|
398
|
+
if (this.autoHighlightMode() === 'always') {
|
|
399
|
+
this.engine.setPendingHighlightEdge('first');
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/** Opens the popup and highlights the given edge once the list mounts. */
|
|
403
|
+
openAndHighlight(edge, reason = 'list-navigation', event = new Event('autocomplete.open-change')) {
|
|
404
|
+
if (!this.open()) {
|
|
405
|
+
this.typed.set(false);
|
|
406
|
+
}
|
|
407
|
+
this.setOpen(true, reason, event);
|
|
408
|
+
this.engine.selectInputText();
|
|
409
|
+
this.engine.setPendingHighlightEdge(edge);
|
|
410
|
+
}
|
|
411
|
+
/** Whether the item's value/label matches the current input value (combobox context contract). */
|
|
412
|
+
isSelectedValue(value) {
|
|
413
|
+
const current = this.value();
|
|
414
|
+
if (!current) {
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
return value === current || isItemEqualToValue(value, current, this.isItemEqualToValue());
|
|
418
|
+
}
|
|
419
|
+
setOpen(open, reason = 'none', event = new Event('autocomplete.open-change')) {
|
|
420
|
+
if (open === this.open()) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
if (open && (this.disabledState() || this.readOnly())) {
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
const change = this.createOpenChangeEvent(open, reason, event);
|
|
427
|
+
this.onOpenChange.emit(change.payload);
|
|
428
|
+
if (change.eventDetails.isCanceled()) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
this.preventUnmountOnClose.set(open ? false : change.shouldPreventUnmountOnClose());
|
|
432
|
+
this.open.set(open);
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
closePopup(revert = true, reason = 'none', event = new Event('autocomplete.open-change')) {
|
|
436
|
+
if (!this.open()) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (!this.setOpen(false, reason, event)) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
this.engine.clearHighlightState();
|
|
443
|
+
this.engine.clearInlinePreview();
|
|
444
|
+
if (revert) {
|
|
445
|
+
this.typed.set(false);
|
|
446
|
+
}
|
|
447
|
+
this.markAsTouched();
|
|
448
|
+
}
|
|
449
|
+
/** Updates the input text from user typing (marks it a fresh query, emits change). */
|
|
450
|
+
setQuery(value) {
|
|
451
|
+
this.commitValue(value, 'input-change');
|
|
452
|
+
this.typed.set(true);
|
|
453
|
+
// Inline modes (`both` / `inline`) implicitly highlight the first prefix match so the input can
|
|
454
|
+
// be inline-completed even without an explicit `autoHighlight`.
|
|
455
|
+
if (this.autoHighlightMode() !== 'off' || (this.inlineMode() && value.length > 0)) {
|
|
456
|
+
this.engine.setPendingHighlightEdge('first-match');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
labelFor(value) {
|
|
460
|
+
const custom = this.itemToStringValue();
|
|
461
|
+
if (custom) {
|
|
462
|
+
return custom(value);
|
|
463
|
+
}
|
|
464
|
+
const item = this.engine.orderedItems().find((i) => isItemEqualToValue(i.value(), value, this.isItemEqualToValue()));
|
|
465
|
+
return item ? item.textValue() : itemToStringLabel(value);
|
|
466
|
+
}
|
|
467
|
+
itemId(index) {
|
|
468
|
+
return this.engine.itemId(index);
|
|
469
|
+
}
|
|
470
|
+
isVisible(item) {
|
|
471
|
+
return this.engine.isVisible(item);
|
|
472
|
+
}
|
|
473
|
+
registerItem(item) {
|
|
474
|
+
this.engine.registerItem(item);
|
|
475
|
+
}
|
|
476
|
+
unregisterItem(item) {
|
|
477
|
+
this.engine.unregisterItem(item);
|
|
478
|
+
}
|
|
479
|
+
handleSelect(item, event = new Event('autocomplete.item-press')) {
|
|
480
|
+
if (this.disabledState() || this.readOnly() || item.disabled()) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.commitSelection(item.textValue() || this.labelFor(item.value()), event);
|
|
484
|
+
}
|
|
485
|
+
/** Selects the filtered item at `index` (virtualized mode). */
|
|
486
|
+
selectIndex(index, event = new Event('autocomplete.item-press')) {
|
|
487
|
+
if (this.disabledState() || this.readOnly()) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const value = this.engine.filteredItems()[index];
|
|
491
|
+
if (value === undefined) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
this.commitSelection(this.labelFor(value), event);
|
|
495
|
+
}
|
|
496
|
+
/** Commits a selection: the input value becomes the item's label, the popup closes. */
|
|
497
|
+
commitSelection(label, event = new Event('autocomplete.item-press')) {
|
|
498
|
+
// Capture focus before `commitValue` emits `onValueChange`, so restoration is skipped when the
|
|
499
|
+
// consumer moves focus in that callback (e.g. focusing a message field after an emoji press).
|
|
500
|
+
const activeBefore = typeof document !== 'undefined' ? document.activeElement : null;
|
|
501
|
+
this.engine.clearInlinePreview();
|
|
502
|
+
this.commitValue(label, 'item-press');
|
|
503
|
+
this.typed.set(false);
|
|
504
|
+
this.closePopup(false, 'item-press', event);
|
|
505
|
+
this.engine.restoreFocusAfterSelect(activeBefore);
|
|
506
|
+
this.maybeSubmit();
|
|
507
|
+
}
|
|
508
|
+
maybeSubmit() {
|
|
509
|
+
if (this.submitOnItemClick()) {
|
|
510
|
+
this.engine.inputElement()?.form?.requestSubmit?.();
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
selectHighlighted(event = new Event('autocomplete.item-press')) {
|
|
514
|
+
if (this.virtualized()) {
|
|
515
|
+
const index = this.engine.highlightedIndex();
|
|
516
|
+
if (index >= 0) {
|
|
517
|
+
this.selectIndex(index, event);
|
|
518
|
+
}
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const item = this.engine.highlightedItem();
|
|
522
|
+
if (item) {
|
|
523
|
+
this.handleSelect(item, event);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
// --- highlight navigation facade (delegates to the engine; grid-aware) ---
|
|
527
|
+
navigateByKeyboard(direction, event = new Event('autocomplete.open-change')) {
|
|
528
|
+
this.engine.setKeyboardActive(true);
|
|
529
|
+
if (!this.open()) {
|
|
530
|
+
this.openAndHighlight(direction === 1 ? 'first' : 'last', 'list-navigation', event);
|
|
531
|
+
}
|
|
532
|
+
else if (direction === 1) {
|
|
533
|
+
this.engine.highlightNext();
|
|
534
|
+
}
|
|
535
|
+
else {
|
|
536
|
+
this.engine.highlightPrevious();
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
moveDown() {
|
|
540
|
+
this.engine.highlightNext();
|
|
541
|
+
}
|
|
542
|
+
moveUp() {
|
|
543
|
+
this.engine.highlightPrevious();
|
|
544
|
+
}
|
|
545
|
+
moveRight() {
|
|
546
|
+
this.engine.highlightNextColumn();
|
|
547
|
+
}
|
|
548
|
+
moveLeft() {
|
|
549
|
+
this.engine.highlightPreviousColumn();
|
|
550
|
+
}
|
|
551
|
+
highlightFirst(reason = 'keyboard') {
|
|
552
|
+
this.engine.highlightFirst(reason);
|
|
553
|
+
}
|
|
554
|
+
highlightLast(reason = 'keyboard') {
|
|
555
|
+
this.engine.highlightLast(reason);
|
|
556
|
+
}
|
|
557
|
+
highlightIndex(index, reason) {
|
|
558
|
+
this.engine.highlightIndex(index, reason);
|
|
559
|
+
}
|
|
560
|
+
setHighlight(item, reason) {
|
|
561
|
+
this.engine.setHighlight(item, reason);
|
|
562
|
+
}
|
|
563
|
+
clearHighlightState() {
|
|
564
|
+
this.engine.clearHighlightState();
|
|
565
|
+
}
|
|
566
|
+
isKeyboardActive() {
|
|
567
|
+
return this.engine.isKeyboardActive();
|
|
568
|
+
}
|
|
569
|
+
setKeyboardActive(value) {
|
|
570
|
+
this.engine.setKeyboardActive(value);
|
|
571
|
+
}
|
|
572
|
+
clearValue() {
|
|
573
|
+
if (this.disabledState() || this.readOnly()) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
this.commitValue('', 'input-clear');
|
|
577
|
+
this.typed.set(true);
|
|
578
|
+
this.engine.clearInlinePreview();
|
|
579
|
+
this.engine.clearHighlightState();
|
|
580
|
+
this.engine.focusInput();
|
|
581
|
+
}
|
|
582
|
+
focusInput() {
|
|
583
|
+
this.engine.focusInput();
|
|
584
|
+
}
|
|
585
|
+
markAsTouched() {
|
|
586
|
+
this.onTouched?.();
|
|
587
|
+
}
|
|
588
|
+
commitValue(value, reason) {
|
|
589
|
+
// Mirror combobox's guarded commit: never mutate the value while disabled or read-only
|
|
590
|
+
// (the input is the form value here, so Clear / item-press / typing must all be inert).
|
|
591
|
+
if (this.disabledState() || this.readOnly()) {
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
this.value.set(value);
|
|
595
|
+
this.onValueChange.emit({ value, reason });
|
|
596
|
+
this.onChange?.(value);
|
|
597
|
+
}
|
|
598
|
+
createOpenChangeEvent(open, reason, event) {
|
|
599
|
+
const change = createCancelableChangeEventDetails(reason, event, this.resolveOpenChangeTrigger(event));
|
|
600
|
+
return {
|
|
601
|
+
payload: {
|
|
602
|
+
open,
|
|
603
|
+
reason,
|
|
604
|
+
event: change.eventDetails.event,
|
|
605
|
+
trigger: change.eventDetails.trigger,
|
|
606
|
+
eventDetails: change.eventDetails
|
|
607
|
+
},
|
|
608
|
+
eventDetails: change.eventDetails,
|
|
609
|
+
shouldPreventUnmountOnClose: change.shouldPreventUnmountOnClose
|
|
610
|
+
};
|
|
611
|
+
}
|
|
612
|
+
resolveOpenChangeTrigger(event) {
|
|
613
|
+
const target = event.target;
|
|
614
|
+
if (target instanceof HTMLElement) {
|
|
615
|
+
return target;
|
|
616
|
+
}
|
|
617
|
+
return this.engine.triggerElement ?? this.engine.inputElement() ?? undefined;
|
|
618
|
+
}
|
|
619
|
+
// ControlValueAccessor (the form value is the input string)
|
|
620
|
+
writeValue(value) {
|
|
621
|
+
untracked(() => this.value.set(value ?? ''));
|
|
622
|
+
}
|
|
623
|
+
registerOnChange(fn) {
|
|
624
|
+
this.onChange = fn;
|
|
625
|
+
}
|
|
626
|
+
registerOnTouched(fn) {
|
|
627
|
+
this.onTouched = fn;
|
|
628
|
+
}
|
|
629
|
+
setDisabledState(isDisabled) {
|
|
630
|
+
this.cvaDisabled.set(isDisabled);
|
|
631
|
+
}
|
|
632
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
633
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", 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: [
|
|
634
|
+
provideComboboxRootContext(context),
|
|
635
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxAutocompleteRoot, multi: true },
|
|
636
|
+
// New floating foundation (ADR 0015/0017) — the dismissal capability reads this shared context.
|
|
637
|
+
provideFloatingTree(),
|
|
638
|
+
provideFloatingRootContext(() => inject(RdxAutocompleteRoot).floatingContext)
|
|
639
|
+
], exportAs: ["rdxAutocompleteRoot"], hostDirectives: [{ directive: i1$1.RdxPopper }], ngImport: i0 }); }
|
|
640
|
+
}
|
|
641
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteRoot, decorators: [{
|
|
642
|
+
type: Directive,
|
|
643
|
+
args: [{
|
|
644
|
+
selector: '[rdxAutocompleteRoot]',
|
|
645
|
+
exportAs: 'rdxAutocompleteRoot',
|
|
646
|
+
providers: [
|
|
647
|
+
provideComboboxRootContext(context),
|
|
648
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxAutocompleteRoot, multi: true },
|
|
649
|
+
// New floating foundation (ADR 0015/0017) — the dismissal capability reads this shared context.
|
|
650
|
+
provideFloatingTree(),
|
|
651
|
+
provideFloatingRootContext(() => inject(RdxAutocompleteRoot).floatingContext)
|
|
652
|
+
],
|
|
653
|
+
hostDirectives: [RdxPopper],
|
|
654
|
+
host: {
|
|
655
|
+
'[attr.data-disabled]': 'disabledState() ? "" : undefined'
|
|
656
|
+
}
|
|
657
|
+
}]
|
|
658
|
+
}], 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"] }] } });
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Clears the input value. Hidden when there is nothing to clear.
|
|
662
|
+
*
|
|
663
|
+
* @group Components
|
|
664
|
+
*/
|
|
665
|
+
class RdxAutocompleteClear {
|
|
666
|
+
constructor() {
|
|
667
|
+
this.root = inject(RdxAutocompleteRoot);
|
|
668
|
+
this.isEmpty = computed(() => !this.root.value(), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
669
|
+
/** Disabled when the control is disabled or read-only (clearing is a mutation). */
|
|
670
|
+
this.isDisabled = computed(() => this.root.disabledState() || this.root.readOnly(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
|
|
671
|
+
}
|
|
672
|
+
onClick() {
|
|
673
|
+
this.root.clearValue();
|
|
674
|
+
}
|
|
675
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteClear, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
676
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteClear, isStandalone: true, selector: "button[rdxAutocompleteClear]", host: { attributes: { "type": "button", "tabindex": "-1", "aria-label": "Clear" }, listeners: { "click": "onClick()" }, properties: { "hidden": "isEmpty()", "attr.disabled": "isDisabled() ? \"\" : undefined" } }, exportAs: ["rdxAutocompleteClear"], hostDirectives: [{ directive: i1$2.RdxFloatingInsideElement }], ngImport: i0 }); }
|
|
677
|
+
}
|
|
678
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteClear, decorators: [{
|
|
679
|
+
type: Directive,
|
|
680
|
+
args: [{
|
|
681
|
+
selector: 'button[rdxAutocompleteClear]',
|
|
682
|
+
exportAs: 'rdxAutocompleteClear',
|
|
683
|
+
hostDirectives: [RdxFloatingInsideElement],
|
|
684
|
+
host: {
|
|
685
|
+
type: 'button',
|
|
686
|
+
tabindex: '-1',
|
|
687
|
+
'aria-label': 'Clear',
|
|
688
|
+
'[hidden]': 'isEmpty()',
|
|
689
|
+
'[attr.disabled]': 'isDisabled() ? "" : undefined',
|
|
690
|
+
'(click)': 'onClick()'
|
|
691
|
+
}
|
|
692
|
+
}]
|
|
693
|
+
}] });
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* A polite, atomic live region announcing the "no results" message. Like the combobox empty part, the
|
|
697
|
+
* element stays mounted and visible at all times (never hidden/unmounted) so the transition to empty is
|
|
698
|
+
* announced; only its projected content is rendered conditionally.
|
|
699
|
+
*
|
|
700
|
+
* @group Components
|
|
701
|
+
*/
|
|
702
|
+
class RdxAutocompleteEmpty {
|
|
703
|
+
constructor() {
|
|
704
|
+
this.rootContext = injectComboboxRootContext();
|
|
705
|
+
/** Whether no items match the current query (drives projection of the message). */
|
|
706
|
+
this.isEmpty = computed(() => this.rootContext.visibleCount() === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
707
|
+
}
|
|
708
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteEmpty, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
709
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: RdxAutocompleteEmpty, isStandalone: true, selector: "[rdxAutocompleteEmpty]", host: { attributes: { "role": "status", "aria-live": "polite", "aria-atomic": "true" }, properties: { "attr.data-empty": "isEmpty() ? \"\" : undefined" } }, exportAs: ["rdxAutocompleteEmpty"], ngImport: i0, template: `
|
|
710
|
+
@if (isEmpty()) {
|
|
711
|
+
<ng-content />
|
|
712
|
+
}
|
|
713
|
+
`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
714
|
+
}
|
|
715
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteEmpty, decorators: [{
|
|
716
|
+
type: Component,
|
|
717
|
+
args: [{
|
|
718
|
+
selector: '[rdxAutocompleteEmpty]',
|
|
719
|
+
exportAs: 'rdxAutocompleteEmpty',
|
|
720
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
721
|
+
template: `
|
|
722
|
+
@if (isEmpty()) {
|
|
723
|
+
<ng-content />
|
|
724
|
+
}
|
|
725
|
+
`,
|
|
726
|
+
host: {
|
|
727
|
+
role: 'status',
|
|
728
|
+
'aria-live': 'polite',
|
|
729
|
+
'aria-atomic': 'true',
|
|
730
|
+
// Present only while the message is shown. Lets consumers collapse the always-mounted region
|
|
731
|
+
// (e.g. `data-[empty]:py-6`) without `display:none`/`hidden`, which would break the announcement.
|
|
732
|
+
'[attr.data-empty]': 'isEmpty() ? "" : undefined'
|
|
733
|
+
}
|
|
734
|
+
}]
|
|
735
|
+
}] });
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Groups related options under a shared label. Hides itself when all of its items are filtered out.
|
|
739
|
+
* Reuses the combobox group (and exposes the combobox group context for the label + items).
|
|
740
|
+
*
|
|
741
|
+
* @group Components
|
|
742
|
+
*/
|
|
743
|
+
class RdxAutocompleteGroup {
|
|
744
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
745
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteGroup, isStandalone: true, selector: "[rdxAutocompleteGroup]", exportAs: ["rdxAutocompleteGroup"], hostDirectives: [{ directive: i1.RdxComboboxGroup }], ngImport: i0 }); }
|
|
746
|
+
}
|
|
747
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteGroup, decorators: [{
|
|
748
|
+
type: Directive,
|
|
749
|
+
args: [{
|
|
750
|
+
selector: '[rdxAutocompleteGroup]',
|
|
751
|
+
exportAs: 'rdxAutocompleteGroup',
|
|
752
|
+
hostDirectives: [RdxComboboxGroup]
|
|
753
|
+
}]
|
|
754
|
+
}] });
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Accessible label for a {@link RdxAutocompleteGroup}. Wires itself up via `aria-labelledby`. Reuses
|
|
758
|
+
* the combobox group label.
|
|
759
|
+
*
|
|
760
|
+
* @group Components
|
|
761
|
+
*/
|
|
762
|
+
class RdxAutocompleteGroupLabel {
|
|
763
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteGroupLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
764
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteGroupLabel, isStandalone: true, selector: "[rdxAutocompleteGroupLabel]", exportAs: ["rdxAutocompleteGroupLabel"], hostDirectives: [{ directive: i1.RdxComboboxGroupLabel }], ngImport: i0 }); }
|
|
765
|
+
}
|
|
766
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteGroupLabel, decorators: [{
|
|
767
|
+
type: Directive,
|
|
768
|
+
args: [{
|
|
769
|
+
selector: '[rdxAutocompleteGroupLabel]',
|
|
770
|
+
exportAs: 'rdxAutocompleteGroupLabel',
|
|
771
|
+
hostDirectives: [RdxComboboxGroupLabel]
|
|
772
|
+
}]
|
|
773
|
+
}] });
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Decorative icon inside the trigger. Hidden from assistive technology. Reuses the combobox icon.
|
|
777
|
+
*
|
|
778
|
+
* @group Components
|
|
779
|
+
*/
|
|
780
|
+
class RdxAutocompleteIcon {
|
|
781
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
782
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteIcon, isStandalone: true, selector: "[rdxAutocompleteIcon]", exportAs: ["rdxAutocompleteIcon"], hostDirectives: [{ directive: i1.RdxComboboxIcon }], ngImport: i0 }); }
|
|
783
|
+
}
|
|
784
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteIcon, decorators: [{
|
|
785
|
+
type: Directive,
|
|
786
|
+
args: [{
|
|
787
|
+
selector: '[rdxAutocompleteIcon]',
|
|
788
|
+
exportAs: 'rdxAutocompleteIcon',
|
|
789
|
+
hostDirectives: [RdxComboboxIcon]
|
|
790
|
+
}]
|
|
791
|
+
}] });
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Positions the autocomplete popup relative to the input anchor using the popper engine.
|
|
795
|
+
*
|
|
796
|
+
* A "thin" positioner (ADR 0012): it inherits the full popper positioning surface — the inputs
|
|
797
|
+
* (`side`, `sideOffset`, `align`, …), the `placed` output, and the host bindings — from
|
|
798
|
+
* {@link RdxPopperContentWrapper}, and only declares autocomplete's Base UI-aligned defaults through
|
|
799
|
+
* the config provider (the same building block the combobox positioner uses).
|
|
800
|
+
*
|
|
801
|
+
* @group Components
|
|
802
|
+
*/
|
|
803
|
+
class RdxAutocompletePositioner extends RdxPopperContentWrapper {
|
|
804
|
+
constructor() {
|
|
805
|
+
super();
|
|
806
|
+
const root = inject(RdxAutocompleteRoot);
|
|
807
|
+
const injector = inject(Injector);
|
|
808
|
+
const host = inject(ElementRef).nativeElement;
|
|
809
|
+
// A modal autocomplete isolates the background with an internal backdrop (Base UI); the input stays
|
|
810
|
+
// clickable through a cutout. (Autocomplete is non-modal by default — usually no backdrop.)
|
|
811
|
+
afterNextRender(() => setupInternalBackdrop(host, injector, {
|
|
812
|
+
isOpen: () => root.open(),
|
|
813
|
+
shouldRender: () => root.modal(),
|
|
814
|
+
cutout: () => root.inputElement() ?? null
|
|
815
|
+
}));
|
|
816
|
+
}
|
|
817
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
818
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompletePositioner, isStandalone: true, selector: "[rdxAutocompletePositioner]", providers: [
|
|
819
|
+
...provideRdxPopperContentWrapper(RdxAutocompletePositioner),
|
|
820
|
+
provideRdxPopperContentConfig({ sideOffset: 4, align: 'start' })
|
|
821
|
+
], exportAs: ["rdxAutocompletePositioner"], usesInheritance: true, ngImport: i0 }); }
|
|
822
|
+
}
|
|
823
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePositioner, decorators: [{
|
|
824
|
+
type: Directive,
|
|
825
|
+
args: [{
|
|
826
|
+
selector: '[rdxAutocompletePositioner]',
|
|
827
|
+
exportAs: 'rdxAutocompletePositioner',
|
|
828
|
+
providers: [
|
|
829
|
+
...provideRdxPopperContentWrapper(RdxAutocompletePositioner),
|
|
830
|
+
provideRdxPopperContentConfig({ sideOffset: 4, align: 'start' })
|
|
831
|
+
]
|
|
832
|
+
}]
|
|
833
|
+
}], ctorParameters: () => [] });
|
|
834
|
+
|
|
835
|
+
const attr$1 = (value) => (value ? '' : undefined);
|
|
836
|
+
/**
|
|
837
|
+
* The autocomplete text input. Holds DOM focus at all times; the highlighted option is referenced via
|
|
838
|
+
* `aria-activedescendant`. In `both` / `inline` modes it shows the active item's inline completion
|
|
839
|
+
* with the completed suffix selected. Integrates with Field for labeling and validation state.
|
|
840
|
+
*
|
|
841
|
+
* @group Components
|
|
842
|
+
*/
|
|
843
|
+
class RdxAutocompleteInput {
|
|
844
|
+
constructor() {
|
|
845
|
+
this.root = inject(RdxAutocompleteRoot);
|
|
846
|
+
this.element = inject(ElementRef).nativeElement;
|
|
847
|
+
this.fieldRootContext = injectFieldRootContext(true);
|
|
848
|
+
/** The input id; Field labels and descriptions reference it for accessible relationships. */
|
|
849
|
+
this.id = input(injectId('rdx-autocomplete-input-'), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
850
|
+
/** Marks the input as invalid independently of any Field state. */
|
|
851
|
+
this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
852
|
+
// Mirrors the configured mode (matches Base UI): 'list' | 'both' | 'inline' | 'none' — all valid
|
|
853
|
+
// `aria-autocomplete` tokens. Must reflect the static mechanism, not the transient inline preview.
|
|
854
|
+
this.ariaAutocomplete = computed(() => this.root.mode(), ...(ngDevMode ? [{ debugName: "ariaAutocomplete" }] : /* istanbul ignore next */ []));
|
|
855
|
+
this.invalidState = computed(() => this.invalid() || Boolean(this.fieldRootContext?.invalidState()), ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
|
|
856
|
+
this.disabledState = computed(() => this.root.disabledState() || Boolean(this.fieldRootContext?.disabledState()), ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
857
|
+
this.requiredState = computed(() => this.root.requiredState() || Boolean(this.fieldRootContext?.requiredState()), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
858
|
+
this.filledState = computed(() => Boolean(this.root.value()) || Boolean(this.fieldRootContext?.filledState()), ...(ngDevMode ? [{ debugName: "filledState" }] : /* istanbul ignore next */ []));
|
|
859
|
+
this.focusedState = computed(() => Boolean(this.fieldRootContext?.focusedState()), ...(ngDevMode ? [{ debugName: "focusedState" }] : /* istanbul ignore next */ []));
|
|
860
|
+
this.describedBy = computed(() => {
|
|
861
|
+
if (!this.fieldRootContext) {
|
|
862
|
+
return undefined;
|
|
863
|
+
}
|
|
864
|
+
const ids = [
|
|
865
|
+
...this.fieldRootContext.descriptionIds(),
|
|
866
|
+
...(this.fieldRootContext.invalidState() ? this.fieldRootContext.errorIds() : [])
|
|
867
|
+
];
|
|
868
|
+
return ids.length ? ids.join(' ') : undefined;
|
|
869
|
+
}, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
870
|
+
/** Whether an IME composition is in progress (CJK). While composing, don't filter or select. */
|
|
871
|
+
this.composing = false;
|
|
872
|
+
this.dataAttr = attr$1;
|
|
873
|
+
this.root.setInputElement(this.element);
|
|
874
|
+
// Report the layout (Base UI's `inputInsidePopup`): a positioner ancestor means the input lives
|
|
875
|
+
// inside the popup, so the Trigger becomes the focusable `role="combobox"`; otherwise the input
|
|
876
|
+
// is the tab stop and the Trigger is a `tabindex="-1"` toggle.
|
|
877
|
+
this.root.setInputLayout(inject(RdxAutocompletePositioner, { optional: true }) ? 'inside' : 'outside');
|
|
878
|
+
afterNextRender(() => {
|
|
879
|
+
this.fieldRootContext?.setControlId(this.id());
|
|
880
|
+
});
|
|
881
|
+
// Select the completed part of an inline preview so the next keystroke replaces it: the suffix
|
|
882
|
+
// after the typed prefix when the label prefix-matches, otherwise the whole navigated label.
|
|
883
|
+
afterRenderEffect(() => {
|
|
884
|
+
const preview = this.root.inlinePreview();
|
|
885
|
+
if (preview === null) {
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
const query = this.root.query();
|
|
889
|
+
const start = query && preview.toLowerCase().startsWith(query.toLowerCase()) ? query.length : 0;
|
|
890
|
+
this.element.setSelectionRange(start, preview.length);
|
|
891
|
+
});
|
|
892
|
+
inject(DestroyRef).onDestroy(() => {
|
|
893
|
+
if (this.root.inputElement() === this.element) {
|
|
894
|
+
this.root.setInputElement(null);
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
onInput(event) {
|
|
899
|
+
if (this.composing || event.isComposing) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
this.commitInput(event.target.value, event);
|
|
903
|
+
}
|
|
904
|
+
onCompositionEnd(event) {
|
|
905
|
+
this.composing = false;
|
|
906
|
+
this.commitInput(event.target.value, event);
|
|
907
|
+
}
|
|
908
|
+
commitInput(value, event) {
|
|
909
|
+
// Base UI opens on input only for a non-empty trimmed value — whitespace alone won't open it.
|
|
910
|
+
if (!this.root.open() && value.trim() !== '') {
|
|
911
|
+
this.root.setOpen(true, 'input-change', event);
|
|
912
|
+
}
|
|
913
|
+
this.root.setQuery(value);
|
|
914
|
+
}
|
|
915
|
+
onClick(event) {
|
|
916
|
+
if (this.root.openOnInputClick()) {
|
|
917
|
+
this.root.openForBrowse('input-press', event);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
onFocus() {
|
|
921
|
+
this.fieldRootContext?.setFocused(true);
|
|
922
|
+
}
|
|
923
|
+
onBlur() {
|
|
924
|
+
this.fieldRootContext?.setFocused(false);
|
|
925
|
+
this.fieldRootContext?.setTouched(true);
|
|
926
|
+
}
|
|
927
|
+
onKeydown(event) {
|
|
928
|
+
if (event.isComposing || this.composing) {
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
// Backspace / Delete must never re-add an inline completion for the resulting edit.
|
|
932
|
+
this.root.setSuppressInline(event.key === 'Backspace' || event.key === 'Delete');
|
|
933
|
+
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) {
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const open = this.root.open();
|
|
937
|
+
switch (event.key) {
|
|
938
|
+
case 'ArrowDown':
|
|
939
|
+
event.preventDefault();
|
|
940
|
+
this.root.setKeyboardActive(true);
|
|
941
|
+
if (!open) {
|
|
942
|
+
this.root.openAndHighlight('first', 'list-navigation', event);
|
|
943
|
+
}
|
|
944
|
+
else {
|
|
945
|
+
this.root.moveDown();
|
|
946
|
+
}
|
|
947
|
+
break;
|
|
948
|
+
case 'ArrowUp':
|
|
949
|
+
event.preventDefault();
|
|
950
|
+
this.root.setKeyboardActive(true);
|
|
951
|
+
if (!open) {
|
|
952
|
+
this.root.openAndHighlight('last', 'list-navigation', event);
|
|
953
|
+
}
|
|
954
|
+
else {
|
|
955
|
+
this.root.moveUp();
|
|
956
|
+
}
|
|
957
|
+
break;
|
|
958
|
+
case 'ArrowRight':
|
|
959
|
+
if (open && this.root.grid()) {
|
|
960
|
+
event.preventDefault();
|
|
961
|
+
this.root.setKeyboardActive(true);
|
|
962
|
+
this.root.moveRight();
|
|
963
|
+
}
|
|
964
|
+
break;
|
|
965
|
+
case 'ArrowLeft':
|
|
966
|
+
if (open && this.root.grid()) {
|
|
967
|
+
event.preventDefault();
|
|
968
|
+
this.root.setKeyboardActive(true);
|
|
969
|
+
this.root.moveLeft();
|
|
970
|
+
}
|
|
971
|
+
break;
|
|
972
|
+
case 'Home':
|
|
973
|
+
// In a grid the search box is a filter, so Home/End jump to the first/last cell rather
|
|
974
|
+
// than moving the caret (outside a grid they keep their native text-editing behavior).
|
|
975
|
+
if (open && this.root.grid()) {
|
|
976
|
+
event.preventDefault();
|
|
977
|
+
this.root.setKeyboardActive(true);
|
|
978
|
+
this.root.highlightFirst();
|
|
979
|
+
}
|
|
980
|
+
break;
|
|
981
|
+
case 'End':
|
|
982
|
+
if (open && this.root.grid()) {
|
|
983
|
+
event.preventDefault();
|
|
984
|
+
this.root.setKeyboardActive(true);
|
|
985
|
+
this.root.highlightLast();
|
|
986
|
+
}
|
|
987
|
+
break;
|
|
988
|
+
case 'Enter':
|
|
989
|
+
if (open) {
|
|
990
|
+
const hasHighlight = this.root.virtualized()
|
|
991
|
+
? this.root.highlightedIndex() >= 0
|
|
992
|
+
: this.root.highlightedItem() !== null;
|
|
993
|
+
if (hasHighlight) {
|
|
994
|
+
event.preventDefault();
|
|
995
|
+
this.root.selectHighlighted(event);
|
|
996
|
+
}
|
|
997
|
+
else if (!this.root.inlineMode()) {
|
|
998
|
+
// Non-inline: close and let native form submission proceed. Inline modes keep the
|
|
999
|
+
// popup open on Enter without a highlight (matches Base UI).
|
|
1000
|
+
this.root.closePopup(true, 'none', event);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
break;
|
|
1004
|
+
case 'Escape':
|
|
1005
|
+
if (open) {
|
|
1006
|
+
event.preventDefault();
|
|
1007
|
+
this.root.closePopup(true, 'escape-key', event);
|
|
1008
|
+
}
|
|
1009
|
+
else if (!this.root.popupMounted()) {
|
|
1010
|
+
// Base UI: Escape on a closed autocomplete clears the input value (a no-op while
|
|
1011
|
+
// read-only / disabled). Guard on `popupMounted` so the same Escape that just closed
|
|
1012
|
+
// an open popup (the `open` branch above) doesn't also clear.
|
|
1013
|
+
this.root.clearValue();
|
|
1014
|
+
}
|
|
1015
|
+
break;
|
|
1016
|
+
case 'Tab':
|
|
1017
|
+
// Tab dismisses a real popup and lets focus move on. With no popup (the always-open,
|
|
1018
|
+
// inline "command palette" layout), Tab must NOT close — it just moves focus within the
|
|
1019
|
+
// surrounding dialog. Guard on `popupMounted` so closing doesn't tear down that dialog.
|
|
1020
|
+
if (open && this.root.popupMounted()) {
|
|
1021
|
+
this.root.closePopup(true, 'none', event);
|
|
1022
|
+
}
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1027
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", 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": "invalidState() ? \"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(invalidState())", "attr.data-valid": "dataAttr(!invalidState())", "attr.data-disabled": "dataAttr(disabledState())", "attr.data-required": "dataAttr(requiredState())", "attr.data-filled": "dataAttr(filledState())", "attr.data-focused": "dataAttr(focusedState())" } }, exportAs: ["rdxAutocompleteInput"], hostDirectives: [{ directive: i1$1.RdxPopperAnchor }, { directive: i1$2.RdxFloatingInsideElement }], ngImport: i0 }); }
|
|
1028
|
+
}
|
|
1029
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteInput, decorators: [{
|
|
1030
|
+
type: Directive,
|
|
1031
|
+
args: [{
|
|
1032
|
+
selector: 'input[rdxAutocompleteInput]',
|
|
1033
|
+
exportAs: 'rdxAutocompleteInput',
|
|
1034
|
+
hostDirectives: [RdxPopperAnchor, RdxFloatingInsideElement],
|
|
1035
|
+
host: {
|
|
1036
|
+
role: 'combobox',
|
|
1037
|
+
autocomplete: 'off',
|
|
1038
|
+
'[attr.aria-autocomplete]': 'ariaAutocomplete()',
|
|
1039
|
+
'[attr.id]': 'id()',
|
|
1040
|
+
'[attr.aria-haspopup]': 'root.grid() ? "grid" : "listbox"',
|
|
1041
|
+
'[attr.aria-expanded]': 'root.open()',
|
|
1042
|
+
'[attr.aria-controls]': 'root.listId',
|
|
1043
|
+
'[attr.aria-labelledby]': 'root.labelId()',
|
|
1044
|
+
'[attr.aria-activedescendant]': 'root.activeId()',
|
|
1045
|
+
'[attr.aria-describedby]': 'describedBy()',
|
|
1046
|
+
'[attr.aria-invalid]': 'invalidState() ? "true" : undefined',
|
|
1047
|
+
'[attr.aria-required]': 'requiredState() ? "true" : undefined',
|
|
1048
|
+
'[attr.aria-disabled]': 'disabledState() ? "true" : undefined',
|
|
1049
|
+
'[attr.disabled]': 'disabledState() ? "" : undefined',
|
|
1050
|
+
'[attr.readonly]': 'root.readOnly() ? "" : undefined',
|
|
1051
|
+
'[attr.required]': 'requiredState() ? "" : undefined',
|
|
1052
|
+
'[value]': 'root.displayValue()',
|
|
1053
|
+
'[attr.data-popup-open]': 'dataAttr(root.open())',
|
|
1054
|
+
'[attr.data-list-empty]': 'dataAttr(root.visibleCount() === 0)',
|
|
1055
|
+
'[attr.data-invalid]': 'dataAttr(invalidState())',
|
|
1056
|
+
'[attr.data-valid]': 'dataAttr(!invalidState())',
|
|
1057
|
+
'[attr.data-disabled]': 'dataAttr(disabledState())',
|
|
1058
|
+
'[attr.data-required]': 'dataAttr(requiredState())',
|
|
1059
|
+
'[attr.data-filled]': 'dataAttr(filledState())',
|
|
1060
|
+
'[attr.data-focused]': 'dataAttr(focusedState())',
|
|
1061
|
+
'(input)': 'onInput($event)',
|
|
1062
|
+
'(click)': 'onClick($event)',
|
|
1063
|
+
'(focus)': 'onFocus()',
|
|
1064
|
+
'(blur)': 'onBlur()',
|
|
1065
|
+
'(keydown)': 'onKeydown($event)',
|
|
1066
|
+
'(compositionstart)': 'composing = true',
|
|
1067
|
+
'(compositionend)': 'onCompositionEnd($event)'
|
|
1068
|
+
}
|
|
1069
|
+
}]
|
|
1070
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }] } });
|
|
1071
|
+
|
|
1072
|
+
const attr = (value) => (value ? '' : undefined);
|
|
1073
|
+
/**
|
|
1074
|
+
* Optional wrapper around the input and its adornments (icon, clear, trigger). Mirrors the input's
|
|
1075
|
+
* state via `data-*` so the whole group can be styled together (focus ring, disabled, etc.).
|
|
1076
|
+
*
|
|
1077
|
+
* @group Components
|
|
1078
|
+
*/
|
|
1079
|
+
class RdxAutocompleteInputGroup {
|
|
1080
|
+
constructor() {
|
|
1081
|
+
this.root = inject(RdxAutocompleteRoot);
|
|
1082
|
+
this.dataAttr = attr;
|
|
1083
|
+
}
|
|
1084
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteInputGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1085
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteInputGroup, isStandalone: true, selector: "[rdxAutocompleteInputGroup]", host: { properties: { "attr.data-popup-open": "dataAttr(root.open())", "attr.data-disabled": "dataAttr(root.disabledState())", "attr.data-required": "dataAttr(root.requiredState())", "attr.data-filled": "dataAttr(!!root.value())" } }, exportAs: ["rdxAutocompleteInputGroup"], ngImport: i0 }); }
|
|
1086
|
+
}
|
|
1087
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteInputGroup, decorators: [{
|
|
1088
|
+
type: Directive,
|
|
1089
|
+
args: [{
|
|
1090
|
+
selector: '[rdxAutocompleteInputGroup]',
|
|
1091
|
+
exportAs: 'rdxAutocompleteInputGroup',
|
|
1092
|
+
host: {
|
|
1093
|
+
'[attr.data-popup-open]': 'dataAttr(root.open())',
|
|
1094
|
+
'[attr.data-disabled]': 'dataAttr(root.disabledState())',
|
|
1095
|
+
'[attr.data-required]': 'dataAttr(root.requiredState())',
|
|
1096
|
+
'[attr.data-filled]': 'dataAttr(!!root.value())'
|
|
1097
|
+
}
|
|
1098
|
+
}]
|
|
1099
|
+
}] });
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* A row in a grid-layout autocomplete list. Groups the items in one row so the root can navigate by
|
|
1103
|
+
* row (ArrowUp / ArrowDown) and within a row (ArrowLeft / ArrowRight). Only meaningful when the root
|
|
1104
|
+
* has `grid` enabled; the root resolves an item's row from its nearest `[rdxAutocompleteRow]` ancestor.
|
|
1105
|
+
*
|
|
1106
|
+
* @group Components
|
|
1107
|
+
*/
|
|
1108
|
+
class RdxAutocompleteRow {
|
|
1109
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteRow, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1110
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteRow, isStandalone: true, selector: "[rdxAutocompleteRow]", host: { attributes: { "role": "row" } }, exportAs: ["rdxAutocompleteRow"], ngImport: i0 }); }
|
|
1111
|
+
}
|
|
1112
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteRow, decorators: [{
|
|
1113
|
+
type: Directive,
|
|
1114
|
+
args: [{
|
|
1115
|
+
selector: '[rdxAutocompleteRow]',
|
|
1116
|
+
exportAs: 'rdxAutocompleteRow',
|
|
1117
|
+
host: {
|
|
1118
|
+
role: 'row'
|
|
1119
|
+
}
|
|
1120
|
+
}]
|
|
1121
|
+
}] });
|
|
1122
|
+
|
|
1123
|
+
const itemContext = () => {
|
|
1124
|
+
const item = inject(RdxAutocompleteItem);
|
|
1125
|
+
return {
|
|
1126
|
+
isSelected: item.isSelected,
|
|
1127
|
+
isHighlighted: item.isHighlighted,
|
|
1128
|
+
disabled: item.disabled,
|
|
1129
|
+
value: item.value
|
|
1130
|
+
};
|
|
1131
|
+
};
|
|
1132
|
+
/**
|
|
1133
|
+
* A selectable suggestion. Registers itself with the root for filtering and navigation. Highlight is
|
|
1134
|
+
* virtual (`data-highlighted` + `aria-activedescendant` on the input) — items never take DOM focus.
|
|
1135
|
+
* Selecting an item writes its text into the input. Unlike the combobox item, the `value` is optional;
|
|
1136
|
+
* when omitted, selection falls back to the option's text content (autocomplete's value is the input string).
|
|
1137
|
+
*
|
|
1138
|
+
* @group Components
|
|
1139
|
+
*/
|
|
1140
|
+
class RdxAutocompleteItem {
|
|
1141
|
+
constructor() {
|
|
1142
|
+
this.rootContext = injectComboboxRootContext();
|
|
1143
|
+
this.element = inject(ElementRef).nativeElement;
|
|
1144
|
+
this.id = injectId('rdx-autocomplete-item-');
|
|
1145
|
+
/**
|
|
1146
|
+
* The explicit `[value]`, or `undefined` when omitted. Read the resolved {@link value} instead —
|
|
1147
|
+
* it falls back to the text content only when no value was bound (so explicit falsy values like
|
|
1148
|
+
* `0` / `''` / `null` are preserved for the filter and selection).
|
|
1149
|
+
*/
|
|
1150
|
+
this.valueInput = input(undefined, { ...(ngDevMode ? { debugName: "valueInput" } : /* istanbul ignore next */ {}), alias: 'value' });
|
|
1151
|
+
/** Explicit text matched against the query and written to the input. Defaults to text content. */
|
|
1152
|
+
this.textValueInput = input('', { ...(ngDevMode ? { debugName: "textValueInput" } : /* istanbul ignore next */ {}), alias: 'textValue' });
|
|
1153
|
+
/** Whether the option is disabled. */
|
|
1154
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1155
|
+
/** The option's index in the list. Required in virtualized mode. */
|
|
1156
|
+
this.index = input(...(ngDevMode ? [undefined, { debugName: "index" }] : /* istanbul ignore next */ []));
|
|
1157
|
+
this.virtualized = this.rootContext.virtualized;
|
|
1158
|
+
this.autoTextValue = signal('', ...(ngDevMode ? [{ debugName: "autoTextValue" }] : /* istanbul ignore next */ []));
|
|
1159
|
+
this.textValue = computed(() => this.textValueInput() || this.autoTextValue(), ...(ngDevMode ? [{ debugName: "textValue" }] : /* istanbul ignore next */ []));
|
|
1160
|
+
/**
|
|
1161
|
+
* The option's effective value: the explicit `[value]` if bound (preserving `0` / `''` / `null`),
|
|
1162
|
+
* otherwise the text content (autocomplete's value is the input string). Only an absent input —
|
|
1163
|
+
* `undefined` — falls back, so a custom filter still sees the real `itemValue` for falsy values.
|
|
1164
|
+
*/
|
|
1165
|
+
this.value = computed(() => {
|
|
1166
|
+
const bound = this.valueInput();
|
|
1167
|
+
return bound === undefined ? this.textValue() : bound;
|
|
1168
|
+
}, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1169
|
+
this.elementId = computed(() => this.virtualized() ? this.rootContext.itemId(this.index() ?? -1) : this.id, ...(ngDevMode ? [{ debugName: "elementId" }] : /* istanbul ignore next */ []));
|
|
1170
|
+
this.ariaSetSize = computed(() => this.virtualized() ? this.rootContext.filteredItems().length : undefined, ...(ngDevMode ? [{ debugName: "ariaSetSize" }] : /* istanbul ignore next */ []));
|
|
1171
|
+
this.ariaPosInSet = computed(() => (this.virtualized() ? (this.index() ?? -1) + 1 : undefined), ...(ngDevMode ? [{ debugName: "ariaPosInSet" }] : /* istanbul ignore next */ []));
|
|
1172
|
+
/** The nearest enclosing grid row, if any (drives the `gridcell` role). */
|
|
1173
|
+
this.row = inject(RdxAutocompleteRow, { optional: true });
|
|
1174
|
+
/** `gridcell` only when actually inside a `RdxAutocompleteRow` of a grid list; otherwise `option`. */
|
|
1175
|
+
this.role = computed(() => (this.rootContext.grid() && this.row ? 'gridcell' : 'option'), ...(ngDevMode ? [{ debugName: "role" }] : /* istanbul ignore next */ []));
|
|
1176
|
+
this.isVisible = computed(() => (this.virtualized() ? true : this.rootContext.isVisible(this)), ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
|
|
1177
|
+
this.isSelected = computed(() => this.rootContext.isSelected(this.value()), ...(ngDevMode ? [{ debugName: "isSelected" }] : /* istanbul ignore next */ []));
|
|
1178
|
+
this.isHighlighted = computed(() => this.virtualized()
|
|
1179
|
+
? this.rootContext.highlightedIndex() === this.index()
|
|
1180
|
+
: this.rootContext.highlightedItem() === this, ...(ngDevMode ? [{ debugName: "isHighlighted" }] : /* istanbul ignore next */ []));
|
|
1181
|
+
this.group = injectComboboxGroupContext(true);
|
|
1182
|
+
// Whether a primary-button pointerdown started on **this** item. A normal press+release here is
|
|
1183
|
+
// committed by `click`; `mouseup` is only the drag-end fallback for a press that began *elsewhere*.
|
|
1184
|
+
this.pointerDownStarted = false;
|
|
1185
|
+
const destroyRef = inject(DestroyRef);
|
|
1186
|
+
afterNextRender(() => {
|
|
1187
|
+
if (this.virtualized()) {
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
if (!this.textValueInput()) {
|
|
1191
|
+
const clone = this.element.cloneNode(true);
|
|
1192
|
+
clone.querySelectorAll('[rdxAutocompleteItemIndicator]').forEach((node) => node.remove());
|
|
1193
|
+
this.autoTextValue.set(clone.textContent?.trim() ?? '');
|
|
1194
|
+
}
|
|
1195
|
+
this.rootContext.registerItem(this);
|
|
1196
|
+
this.group?.registerItem(this);
|
|
1197
|
+
});
|
|
1198
|
+
destroyRef.onDestroy(() => {
|
|
1199
|
+
if (this.virtualized()) {
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
this.rootContext.unregisterItem(this);
|
|
1203
|
+
this.group?.unregisterItem(this);
|
|
1204
|
+
});
|
|
1205
|
+
// Keep the highlighted option in view while navigating a scrollable popup. `block: 'nearest'`
|
|
1206
|
+
// makes hover a no-op (the item is already visible) and only scrolls on keyboard navigation.
|
|
1207
|
+
afterRenderEffect(() => {
|
|
1208
|
+
if (!this.virtualized() && this.isHighlighted()) {
|
|
1209
|
+
this.element.scrollIntoView({ block: 'nearest' });
|
|
1210
|
+
}
|
|
1211
|
+
});
|
|
1212
|
+
// Reset the press flag whenever the popup closes (matches Base UI), so a later drag-end onto
|
|
1213
|
+
// this item isn't blocked by a stale press from an earlier interaction.
|
|
1214
|
+
effect(() => {
|
|
1215
|
+
if (!this.rootContext.open()) {
|
|
1216
|
+
this.pointerDownStarted = false;
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
onPointerDown(event) {
|
|
1221
|
+
if (event.button !== 0) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1224
|
+
event.preventDefault();
|
|
1225
|
+
this.rootContext.setKeyboardActive(false);
|
|
1226
|
+
this.pointerDownStarted = true;
|
|
1227
|
+
}
|
|
1228
|
+
onMouseDown(event) {
|
|
1229
|
+
// Belt-and-suspenders for keeping focus on the input (and iOS Safari blur on tap).
|
|
1230
|
+
if (event.button === 0) {
|
|
1231
|
+
event.preventDefault();
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
onMouseUp(event) {
|
|
1235
|
+
// Read-and-reset the press flag first (matches Base UI), so a press+release here doesn't leave
|
|
1236
|
+
// it set and block a later drag-end onto this same item. Drag-end: commit when the primary
|
|
1237
|
+
// button is released over the highlighted item while the press began on a *different* element
|
|
1238
|
+
// (so `click` won't fire here). A press that began on this item is committed by `click` instead.
|
|
1239
|
+
const startedHere = this.pointerDownStarted;
|
|
1240
|
+
this.pointerDownStarted = false;
|
|
1241
|
+
if (event.button !== 0 || startedHere || !this.isHighlighted()) {
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
this.commitSelection();
|
|
1245
|
+
}
|
|
1246
|
+
onClick() {
|
|
1247
|
+
// Primary selection trigger; also fires for programmatic `.click()`.
|
|
1248
|
+
this.commitSelection();
|
|
1249
|
+
}
|
|
1250
|
+
commitSelection() {
|
|
1251
|
+
if (this.virtualized()) {
|
|
1252
|
+
this.rootContext.selectIndex(this.index() ?? -1);
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
this.rootContext.select(this);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
onPointerMove() {
|
|
1259
|
+
// Hover highlighting disabled: leave `data-highlighted` to keyboard/auto-highlight only.
|
|
1260
|
+
if (!this.rootContext.highlightItemOnHover()) {
|
|
1261
|
+
return;
|
|
1262
|
+
}
|
|
1263
|
+
if (this.rootContext.isKeyboardActive()) {
|
|
1264
|
+
this.rootContext.setKeyboardActive(false);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
if (this.disabled()) {
|
|
1268
|
+
return;
|
|
1269
|
+
}
|
|
1270
|
+
if (this.virtualized()) {
|
|
1271
|
+
this.rootContext.highlightIndex(this.index() ?? -1, 'pointer');
|
|
1272
|
+
}
|
|
1273
|
+
else if (this.isVisible()) {
|
|
1274
|
+
this.rootContext.setHighlight(this, 'pointer');
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
// Clear a pointer-driven highlight when the cursor leaves the list (unless `keepHighlight`).
|
|
1278
|
+
// Moving to another element inside the list keeps it (the next item's move re-highlights).
|
|
1279
|
+
onPointerLeave(event) {
|
|
1280
|
+
if (event.pointerType === 'touch' ||
|
|
1281
|
+
!this.rootContext.open() ||
|
|
1282
|
+
!this.rootContext.highlightItemOnHover() ||
|
|
1283
|
+
this.rootContext.keepHighlight()) {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
const related = event.relatedTarget;
|
|
1287
|
+
const list = related && document.getElementById(this.rootContext.listId);
|
|
1288
|
+
if (list && list.contains(related)) {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
this.rootContext.clearHighlight();
|
|
1292
|
+
}
|
|
1293
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1294
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxAutocompleteItem, isStandalone: true, selector: "[rdxAutocompleteItem]", inputs: { valueInput: { classPropertyName: "valueInput", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, textValueInput: { classPropertyName: "textValueInput", publicName: "textValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "pointerdown": "onPointerDown($event)", "mousedown": "onMouseDown($event)", "mouseup": "onMouseUp($event)", "click": "onClick()", "pointermove": "onPointerMove()", "pointerleave": "onPointerLeave($event)" }, properties: { "attr.role": "role()", "attr.id": "elementId()", "attr.aria-disabled": "disabled() ? \"true\" : undefined", "attr.aria-setsize": "ariaSetSize()", "attr.aria-posinset": "ariaPosInSet()", "attr.data-highlighted": "isHighlighted() ? \"\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined", "hidden": "!isVisible()", "attr.data-hidden": "isVisible() ? undefined : \"\"" } }, providers: [provideComboboxItemContext(itemContext)], exportAs: ["rdxAutocompleteItem"], ngImport: i0 }); }
|
|
1295
|
+
}
|
|
1296
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteItem, decorators: [{
|
|
1297
|
+
type: Directive,
|
|
1298
|
+
args: [{
|
|
1299
|
+
selector: '[rdxAutocompleteItem]',
|
|
1300
|
+
exportAs: 'rdxAutocompleteItem',
|
|
1301
|
+
providers: [provideComboboxItemContext(itemContext)],
|
|
1302
|
+
host: {
|
|
1303
|
+
'[attr.role]': 'role()',
|
|
1304
|
+
'[attr.id]': 'elementId()',
|
|
1305
|
+
// Autocomplete is always `selectionMode="none"`, so options carry no selection state: Base UI
|
|
1306
|
+
// omits `aria-selected` / `data-selected` here entirely (rather than rendering `false`).
|
|
1307
|
+
'[attr.aria-disabled]': 'disabled() ? "true" : undefined',
|
|
1308
|
+
'[attr.aria-setsize]': 'ariaSetSize()',
|
|
1309
|
+
'[attr.aria-posinset]': 'ariaPosInSet()',
|
|
1310
|
+
'[attr.data-highlighted]': 'isHighlighted() ? "" : undefined',
|
|
1311
|
+
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
1312
|
+
'[hidden]': '!isVisible()',
|
|
1313
|
+
'[attr.data-hidden]': 'isVisible() ? undefined : ""',
|
|
1314
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
1315
|
+
'(mousedown)': 'onMouseDown($event)',
|
|
1316
|
+
'(mouseup)': 'onMouseUp($event)',
|
|
1317
|
+
'(click)': 'onClick()',
|
|
1318
|
+
'(pointermove)': 'onPointerMove()',
|
|
1319
|
+
'(pointerleave)': 'onPointerLeave($event)'
|
|
1320
|
+
}
|
|
1321
|
+
}]
|
|
1322
|
+
}], ctorParameters: () => [], propDecorators: { valueInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], textValueInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "textValue", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }] } });
|
|
1323
|
+
|
|
1324
|
+
/**
|
|
1325
|
+
* Renders only when its item is selected (e.g. a checkmark). Reuses the combobox item indicator,
|
|
1326
|
+
* which reads the item context provided by {@link RdxAutocompleteItem}.
|
|
1327
|
+
*
|
|
1328
|
+
* @group Components
|
|
1329
|
+
*/
|
|
1330
|
+
class RdxAutocompleteItemIndicator {
|
|
1331
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteItemIndicator, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1332
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteItemIndicator, isStandalone: true, selector: "[rdxAutocompleteItemIndicator]", exportAs: ["rdxAutocompleteItemIndicator"], hostDirectives: [{ directive: i1.RdxComboboxItemIndicator }], ngImport: i0 }); }
|
|
1333
|
+
}
|
|
1334
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteItemIndicator, decorators: [{
|
|
1335
|
+
type: Directive,
|
|
1336
|
+
args: [{
|
|
1337
|
+
selector: '[rdxAutocompleteItemIndicator]',
|
|
1338
|
+
exportAs: 'rdxAutocompleteItemIndicator',
|
|
1339
|
+
hostDirectives: [RdxComboboxItemIndicator]
|
|
1340
|
+
}]
|
|
1341
|
+
}] });
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* An accessible label for the autocomplete. Registers its id so the input (and trigger) reference it
|
|
1345
|
+
* via `aria-labelledby`. Reuses the combobox label behavior.
|
|
1346
|
+
*
|
|
1347
|
+
* @group Components
|
|
1348
|
+
*/
|
|
1349
|
+
class RdxAutocompleteLabel {
|
|
1350
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1351
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteLabel, isStandalone: true, selector: "[rdxAutocompleteLabel]", exportAs: ["rdxAutocompleteLabel"], hostDirectives: [{ directive: i1.RdxComboboxLabel }], ngImport: i0 }); }
|
|
1352
|
+
}
|
|
1353
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteLabel, decorators: [{
|
|
1354
|
+
type: Directive,
|
|
1355
|
+
args: [{
|
|
1356
|
+
selector: '[rdxAutocompleteLabel]',
|
|
1357
|
+
exportAs: 'rdxAutocompleteLabel',
|
|
1358
|
+
hostDirectives: [RdxComboboxLabel]
|
|
1359
|
+
}]
|
|
1360
|
+
}] });
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* The listbox (or grid) container for suggestions. Carries the id referenced by the input's
|
|
1364
|
+
* `aria-controls`, and switches its role to `grid` when the root has `grid` enabled.
|
|
1365
|
+
*
|
|
1366
|
+
* @group Components
|
|
1367
|
+
*/
|
|
1368
|
+
class RdxAutocompleteList {
|
|
1369
|
+
constructor() {
|
|
1370
|
+
this.root = inject(RdxAutocompleteRoot);
|
|
1371
|
+
}
|
|
1372
|
+
onKeydown(event) {
|
|
1373
|
+
if (event.key !== 'Enter') {
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
// Base UI bails early when disabled / read-only — don't swallow Enter (e.g. a form submit).
|
|
1377
|
+
if (this.root.disabledState() || this.root.readOnly()) {
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
const hasHighlight = this.root.virtualized()
|
|
1381
|
+
? this.root.highlightedIndex() >= 0
|
|
1382
|
+
: this.root.highlightedItem() !== null;
|
|
1383
|
+
if (hasHighlight) {
|
|
1384
|
+
// Base UI `stopEvent`: also stop propagation so a parent keydown handler doesn't re-handle
|
|
1385
|
+
// Enter after the selection.
|
|
1386
|
+
event.preventDefault();
|
|
1387
|
+
event.stopPropagation();
|
|
1388
|
+
this.root.selectHighlighted();
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1392
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteList, isStandalone: true, selector: "[rdxAutocompleteList]", host: { attributes: { "tabindex": "-1" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "attr.role": "root.grid() ? \"grid\" : \"listbox\"", "attr.id": "root.listId" } }, exportAs: ["rdxAutocompleteList"], ngImport: i0 }); }
|
|
1393
|
+
}
|
|
1394
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteList, decorators: [{
|
|
1395
|
+
type: Directive,
|
|
1396
|
+
args: [{
|
|
1397
|
+
selector: '[rdxAutocompleteList]',
|
|
1398
|
+
exportAs: 'rdxAutocompleteList',
|
|
1399
|
+
host: {
|
|
1400
|
+
// Base UI: the list is a programmatic focus target (`tabindex="-1"`) and selects the highlighted
|
|
1401
|
+
// item on Enter, for custom layouts that move focus onto the list rather than the input.
|
|
1402
|
+
tabindex: '-1',
|
|
1403
|
+
'[attr.role]': 'root.grid() ? "grid" : "listbox"',
|
|
1404
|
+
'[attr.id]': 'root.listId',
|
|
1405
|
+
'(keydown)': 'onKeydown($event)'
|
|
1406
|
+
}
|
|
1407
|
+
}]
|
|
1408
|
+
}] });
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* The popup surface. Composes the popper content (for `data-side` / `data-align`) and a dismissable
|
|
1412
|
+
* layer for outside-dismiss. It does **not** trap focus — focus stays in the input throughout. When
|
|
1413
|
+
* the input lives **inside** the popup (the "input in popup" / emoji-picker pattern), it moves focus
|
|
1414
|
+
* to the input once the popup is positioned.
|
|
1415
|
+
*
|
|
1416
|
+
* @group Components
|
|
1417
|
+
*/
|
|
1418
|
+
class RdxAutocompletePopup {
|
|
1419
|
+
constructor() {
|
|
1420
|
+
this.root = inject(RdxAutocompleteRoot);
|
|
1421
|
+
this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
|
|
1422
|
+
this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
|
|
1423
|
+
this.popper = injectPopperContentWrapperContext();
|
|
1424
|
+
this.element = inject(ElementRef).nativeElement;
|
|
1425
|
+
// Activation policy (ADR 0016 §2 + §3): lock page scroll while a modal popup is OPEN, gated on
|
|
1426
|
+
// `open` (not mounted) so the lock releases at close-start. For a **touch** open the anchored
|
|
1427
|
+
// helper only locks when the popup is effectively viewport-width (a small dropdown stays
|
|
1428
|
+
// swipe-to-dismissable on mobile, §3).
|
|
1429
|
+
useAnchoredScrollLock(computed(() => this.root.open() && this.root.modal()), {
|
|
1430
|
+
touchOpen: () => this.root.openedByTouch(),
|
|
1431
|
+
element: () => this.element
|
|
1432
|
+
});
|
|
1433
|
+
const unregister = this.root.registerTransitionElement(this.element);
|
|
1434
|
+
// Track mounted state so Escape can tell "closing this open popup" from "already closed".
|
|
1435
|
+
this.root.setPopupMounted(true);
|
|
1436
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1437
|
+
unregister();
|
|
1438
|
+
this.root.setPopupMounted(false);
|
|
1439
|
+
});
|
|
1440
|
+
// The popup is this layer's floating element (the inside surface for containment checks).
|
|
1441
|
+
this.floatingContext.setFloatingElement(this.element);
|
|
1442
|
+
// Dismissal (ADR 0015): an outside press, or focus leaving everything, closes the autocomplete. The
|
|
1443
|
+
// input / trigger / clear are registered as "inside" (RdxFloatingInsideElement), so the input keeping
|
|
1444
|
+
// focus — or a press on those parts — never self-dismisses. Escape is owned by the input (it
|
|
1445
|
+
// preventDefaults + closes), so the capability does not handle it (`escapeKey: false`).
|
|
1446
|
+
new RdxDismiss(this.floatingContext, () => this.registration?.node() ?? null, {
|
|
1447
|
+
escapeKey: () => false,
|
|
1448
|
+
outsidePress: () => true,
|
|
1449
|
+
focusOutside: () => true,
|
|
1450
|
+
onDismiss: (reason, event) => this.root.closePopup(true, reason === 'focus-outside' ? 'focus-out' : 'outside-press', event)
|
|
1451
|
+
});
|
|
1452
|
+
// For the "input inside the popup" pattern, move focus to the input once positioned. Use
|
|
1453
|
+
// `afterRenderEffect` (not `effect`): when `isPositioned` flips true the popup's final
|
|
1454
|
+
// position/visibility is applied in the *following* render, so a synchronous `effect` would
|
|
1455
|
+
// call `focus()` while the element is still unfocusable and silently no-op. Running after the
|
|
1456
|
+
// render guarantees the input is focusable.
|
|
1457
|
+
afterRenderEffect(() => {
|
|
1458
|
+
if (!this.popper.isPositioned() || !this.root.open()) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
const input = this.root.inputElement();
|
|
1462
|
+
if (input && input.closest('[rdxAutocompletePopup]')) {
|
|
1463
|
+
// Base UI: a touch-open focuses the popup itself so Android keeps the virtual keyboard
|
|
1464
|
+
// closed; mouse/keyboard opens focus (and select) the search input as usual.
|
|
1465
|
+
if (this.root.openedByTouch()) {
|
|
1466
|
+
this.element.focus();
|
|
1467
|
+
}
|
|
1468
|
+
else {
|
|
1469
|
+
input.focus();
|
|
1470
|
+
input.select();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Base UI focus handoff: if focus lands on the popup or the list (the `tabindex="-1"` programmatic
|
|
1477
|
+
* focus targets), hand it back to the input so arrow-key navigation (`aria-activedescendant`) keeps
|
|
1478
|
+
* working. Skipped for a touch interaction, where focus is parked on the popup to keep the Android
|
|
1479
|
+
* virtual keyboard closed.
|
|
1480
|
+
*/
|
|
1481
|
+
onFocusIn(event) {
|
|
1482
|
+
if (this.root.openedByTouch()) {
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
const input = this.root.inputElement();
|
|
1486
|
+
const target = event.target;
|
|
1487
|
+
if (!input || !target || target === input) {
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (target === this.element || target.matches('[rdxAutocompleteList]')) {
|
|
1491
|
+
input.focus();
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1495
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompletePopup, isStandalone: true, selector: "[rdxAutocompletePopup]", host: { attributes: { "tabindex": "-1" }, listeners: { "focusin": "onFocusIn($event)" }, properties: { "attr.role": "root.inputLayout() === \"inside\" ? \"dialog\" : \"presentation\"", "attr.data-state": "root.open() ? \"open\" : \"closed\"", "attr.data-open": "root.open() ? \"\" : undefined", "attr.data-closed": "root.open() ? undefined : \"\"", "attr.data-starting-style": "root.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "root.transitionStatus() === \"ending\" ? \"\" : undefined" } }, exportAs: ["rdxAutocompletePopup"], hostDirectives: [{ directive: i1$1.RdxPopperContent }, { directive: i2.RdxFloatingNodeRegistration }], ngImport: i0 }); }
|
|
1496
|
+
}
|
|
1497
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePopup, decorators: [{
|
|
1498
|
+
type: Directive,
|
|
1499
|
+
args: [{
|
|
1500
|
+
selector: '[rdxAutocompletePopup]',
|
|
1501
|
+
exportAs: 'rdxAutocompletePopup',
|
|
1502
|
+
hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration],
|
|
1503
|
+
host: {
|
|
1504
|
+
// Base UI: a `dialog` (focusable, tabindex -1) when the input lives inside the popup, otherwise
|
|
1505
|
+
// a presentational wrapper around the `listbox` (the List part owns the listbox role).
|
|
1506
|
+
tabindex: '-1',
|
|
1507
|
+
'[attr.role]': 'root.inputLayout() === "inside" ? "dialog" : "presentation"',
|
|
1508
|
+
'[attr.data-state]': 'root.open() ? "open" : "closed"',
|
|
1509
|
+
'[attr.data-open]': 'root.open() ? "" : undefined',
|
|
1510
|
+
'[attr.data-closed]': 'root.open() ? undefined : ""',
|
|
1511
|
+
'[attr.data-starting-style]': 'root.transitionStatus() === "starting" ? "" : undefined',
|
|
1512
|
+
'[attr.data-ending-style]': 'root.transitionStatus() === "ending" ? "" : undefined',
|
|
1513
|
+
'(focusin)': 'onFocusIn($event)'
|
|
1514
|
+
}
|
|
1515
|
+
}]
|
|
1516
|
+
}], ctorParameters: () => [] });
|
|
1517
|
+
|
|
1518
|
+
/**
|
|
1519
|
+
* Structural directive that teleports the autocomplete popup into a container (default
|
|
1520
|
+
* `document.body`) while the autocomplete is open, keeping it mounted until any CSS exit `@keyframes`
|
|
1521
|
+
* finishes. Composes the structural {@link RdxComboboxPortal} (which reads the open state from the
|
|
1522
|
+
* combobox root context provided by `RdxAutocompleteRoot`).
|
|
1523
|
+
*
|
|
1524
|
+
* Apply it with the `*` microsyntax on the positioner —
|
|
1525
|
+
* `<div *rdxAutocompletePortal rdxAutocompletePositioner>` — or as an explicit
|
|
1526
|
+
* `<ng-template rdxAutocompletePortal>`. For a custom container use the explicit form with `[container]`.
|
|
1527
|
+
*
|
|
1528
|
+
* @group Components
|
|
1529
|
+
*/
|
|
1530
|
+
class RdxAutocompletePortal {
|
|
1531
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1532
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompletePortal, isStandalone: true, selector: "ng-template[rdxAutocompletePortal]", exportAs: ["rdxAutocompletePortal"], hostDirectives: [{ directive: i1.RdxComboboxPortal, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
1533
|
+
}
|
|
1534
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePortal, decorators: [{
|
|
1535
|
+
type: Directive,
|
|
1536
|
+
args: [{
|
|
1537
|
+
selector: 'ng-template[rdxAutocompletePortal]',
|
|
1538
|
+
exportAs: 'rdxAutocompletePortal',
|
|
1539
|
+
hostDirectives: [{ directive: RdxComboboxPortal, inputs: ['container'] }]
|
|
1540
|
+
}]
|
|
1541
|
+
}] });
|
|
1542
|
+
/**
|
|
1543
|
+
* Dev-mode guard: `rdxAutocompletePortal` is now structural, so the old `<div rdxAutocompletePortal>`
|
|
1544
|
+
* markup would silently stop portaling — fail loudly instead.
|
|
1545
|
+
*
|
|
1546
|
+
* @group Components
|
|
1547
|
+
*/
|
|
1548
|
+
class RdxAutocompletePortalMisuseGuard {
|
|
1549
|
+
constructor() {
|
|
1550
|
+
if (isDevMode()) {
|
|
1551
|
+
rdxDevError('autocomplete/portal-on-element', '`rdxAutocompletePortal` is now a structural directive. ' +
|
|
1552
|
+
'Use `*rdxAutocompletePortal` on the positioner element or `<ng-template rdxAutocompletePortal>`. ' +
|
|
1553
|
+
'rdxAutocompletePortalPresence has been removed.', 'components/autocomplete');
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1557
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompletePortalMisuseGuard, isStandalone: true, selector: "[rdxAutocompletePortal]:not(ng-template)", ngImport: i0 }); }
|
|
1558
|
+
}
|
|
1559
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePortalMisuseGuard, decorators: [{
|
|
1560
|
+
type: Directive,
|
|
1561
|
+
args: [{
|
|
1562
|
+
selector: '[rdxAutocompletePortal]:not(ng-template)'
|
|
1563
|
+
}]
|
|
1564
|
+
}], ctorParameters: () => [] });
|
|
1565
|
+
|
|
1566
|
+
/**
|
|
1567
|
+
* A visual separator between groups of suggestions (`role="separator"`). Reuses the combobox separator.
|
|
1568
|
+
*
|
|
1569
|
+
* @group Components
|
|
1570
|
+
*/
|
|
1571
|
+
class RdxAutocompleteSeparator {
|
|
1572
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteSeparator, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1573
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteSeparator, isStandalone: true, selector: "[rdxAutocompleteSeparator]", exportAs: ["rdxAutocompleteSeparator"], hostDirectives: [{ directive: i1.RdxComboboxSeparator }], ngImport: i0 }); }
|
|
1574
|
+
}
|
|
1575
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteSeparator, decorators: [{
|
|
1576
|
+
type: Directive,
|
|
1577
|
+
args: [{
|
|
1578
|
+
selector: '[rdxAutocompleteSeparator]',
|
|
1579
|
+
exportAs: 'rdxAutocompleteSeparator',
|
|
1580
|
+
hostDirectives: [RdxComboboxSeparator]
|
|
1581
|
+
}]
|
|
1582
|
+
}] });
|
|
1583
|
+
|
|
1584
|
+
/**
|
|
1585
|
+
* A polite live region for async status (loading, result counts) announced without moving focus.
|
|
1586
|
+
* Reuses the combobox status region.
|
|
1587
|
+
*
|
|
1588
|
+
* @group Components
|
|
1589
|
+
*/
|
|
1590
|
+
class RdxAutocompleteStatus {
|
|
1591
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteStatus, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1592
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteStatus, isStandalone: true, selector: "[rdxAutocompleteStatus]", exportAs: ["rdxAutocompleteStatus"], hostDirectives: [{ directive: i1.RdxComboboxStatus }], ngImport: i0 }); }
|
|
1593
|
+
}
|
|
1594
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteStatus, decorators: [{
|
|
1595
|
+
type: Directive,
|
|
1596
|
+
args: [{
|
|
1597
|
+
selector: '[rdxAutocompleteStatus]',
|
|
1598
|
+
exportAs: 'rdxAutocompleteStatus',
|
|
1599
|
+
hostDirectives: [RdxComboboxStatus]
|
|
1600
|
+
}]
|
|
1601
|
+
}] });
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Toggles the autocomplete popup. Reuses the combobox trigger: a `tabindex="-1"` toggle when the input
|
|
1605
|
+
* sits outside the popup, or the focusable `role="combobox"` control when the input is inside it.
|
|
1606
|
+
*
|
|
1607
|
+
* @group Components
|
|
1608
|
+
*/
|
|
1609
|
+
class RdxAutocompleteTrigger {
|
|
1610
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1611
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteTrigger, isStandalone: true, selector: "button[rdxAutocompleteTrigger]", exportAs: ["rdxAutocompleteTrigger"], hostDirectives: [{ directive: i1.RdxComboboxTrigger }], ngImport: i0 }); }
|
|
1612
|
+
}
|
|
1613
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteTrigger, decorators: [{
|
|
1614
|
+
type: Directive,
|
|
1615
|
+
args: [{
|
|
1616
|
+
selector: 'button[rdxAutocompleteTrigger]',
|
|
1617
|
+
exportAs: 'rdxAutocompleteTrigger',
|
|
1618
|
+
hostDirectives: [RdxComboboxTrigger]
|
|
1619
|
+
}]
|
|
1620
|
+
}] });
|
|
1621
|
+
|
|
1622
|
+
/**
|
|
1623
|
+
* Renders the current input value as text. Useful for a read-only display of the committed value.
|
|
1624
|
+
* Read `slotText()` in the template. Exposes `data-placeholder` while the value is empty.
|
|
1625
|
+
*
|
|
1626
|
+
* @example
|
|
1627
|
+
* <span #value="rdxAutocompleteValue" rdxAutocompleteValue placeholder="Empty">{{ value.slotText() }}</span>
|
|
1628
|
+
*
|
|
1629
|
+
* @group Components
|
|
1630
|
+
*/
|
|
1631
|
+
class RdxAutocompleteValue {
|
|
1632
|
+
constructor() {
|
|
1633
|
+
this.root = inject(RdxAutocompleteRoot);
|
|
1634
|
+
/** Text shown when the value is empty. */
|
|
1635
|
+
this.placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
1636
|
+
this.isEmpty = computed(() => !this.root.value(), ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
1637
|
+
/** The current input value, or the `placeholder` when empty. */
|
|
1638
|
+
this.slotText = computed(() => this.root.value() || (this.placeholder() ?? ''), ...(ngDevMode ? [{ debugName: "slotText" }] : /* istanbul ignore next */ []));
|
|
1639
|
+
}
|
|
1640
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteValue, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1641
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxAutocompleteValue, isStandalone: true, selector: "[rdxAutocompleteValue]", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-placeholder": "isEmpty() ? \"\" : undefined" } }, exportAs: ["rdxAutocompleteValue"], ngImport: i0 }); }
|
|
1642
|
+
}
|
|
1643
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteValue, decorators: [{
|
|
1644
|
+
type: Directive,
|
|
1645
|
+
args: [{
|
|
1646
|
+
selector: '[rdxAutocompleteValue]',
|
|
1647
|
+
exportAs: 'rdxAutocompleteValue',
|
|
1648
|
+
host: {
|
|
1649
|
+
'[attr.data-placeholder]': 'isEmpty() ? "" : undefined'
|
|
1650
|
+
}
|
|
1651
|
+
}]
|
|
1652
|
+
}], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
1653
|
+
|
|
1654
|
+
const _importsAutocomplete = [
|
|
1655
|
+
RdxAutocompleteRoot,
|
|
1656
|
+
RdxAutocompleteAnchor,
|
|
1657
|
+
RdxAutocompleteLabel,
|
|
1658
|
+
RdxAutocompleteInputGroup,
|
|
1659
|
+
RdxAutocompleteInput,
|
|
1660
|
+
RdxAutocompleteValue,
|
|
1661
|
+
RdxAutocompleteTrigger,
|
|
1662
|
+
RdxAutocompleteIcon,
|
|
1663
|
+
RdxAutocompleteClear,
|
|
1664
|
+
RdxAutocompletePortal,
|
|
1665
|
+
RdxAutocompletePortalMisuseGuard,
|
|
1666
|
+
RdxAutocompleteBackdrop,
|
|
1667
|
+
RdxAutocompletePositioner,
|
|
1668
|
+
RdxAutocompletePopup,
|
|
1669
|
+
RdxAutocompleteArrow,
|
|
1670
|
+
RdxAutocompleteList,
|
|
1671
|
+
RdxAutocompleteRow,
|
|
1672
|
+
RdxAutocompleteSeparator,
|
|
1673
|
+
RdxAutocompleteItem,
|
|
1674
|
+
RdxAutocompleteItemIndicator,
|
|
1675
|
+
RdxAutocompleteGroup,
|
|
1676
|
+
RdxAutocompleteGroupLabel,
|
|
1677
|
+
RdxAutocompleteEmpty,
|
|
1678
|
+
RdxAutocompleteStatus
|
|
1679
|
+
];
|
|
1680
|
+
class RdxAutocompleteModule {
|
|
1681
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1682
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteModule, imports: [RdxAutocompleteRoot,
|
|
1683
|
+
RdxAutocompleteAnchor,
|
|
1684
|
+
RdxAutocompleteLabel,
|
|
1685
|
+
RdxAutocompleteInputGroup,
|
|
1686
|
+
RdxAutocompleteInput,
|
|
1687
|
+
RdxAutocompleteValue,
|
|
1688
|
+
RdxAutocompleteTrigger,
|
|
1689
|
+
RdxAutocompleteIcon,
|
|
1690
|
+
RdxAutocompleteClear,
|
|
1691
|
+
RdxAutocompletePortal,
|
|
1692
|
+
RdxAutocompletePortalMisuseGuard,
|
|
1693
|
+
RdxAutocompleteBackdrop,
|
|
1694
|
+
RdxAutocompletePositioner,
|
|
1695
|
+
RdxAutocompletePopup,
|
|
1696
|
+
RdxAutocompleteArrow,
|
|
1697
|
+
RdxAutocompleteList,
|
|
1698
|
+
RdxAutocompleteRow,
|
|
1699
|
+
RdxAutocompleteSeparator,
|
|
1700
|
+
RdxAutocompleteItem,
|
|
1701
|
+
RdxAutocompleteItemIndicator,
|
|
1702
|
+
RdxAutocompleteGroup,
|
|
1703
|
+
RdxAutocompleteGroupLabel,
|
|
1704
|
+
RdxAutocompleteEmpty,
|
|
1705
|
+
RdxAutocompleteStatus], exports: [RdxAutocompleteRoot,
|
|
1706
|
+
RdxAutocompleteAnchor,
|
|
1707
|
+
RdxAutocompleteLabel,
|
|
1708
|
+
RdxAutocompleteInputGroup,
|
|
1709
|
+
RdxAutocompleteInput,
|
|
1710
|
+
RdxAutocompleteValue,
|
|
1711
|
+
RdxAutocompleteTrigger,
|
|
1712
|
+
RdxAutocompleteIcon,
|
|
1713
|
+
RdxAutocompleteClear,
|
|
1714
|
+
RdxAutocompletePortal,
|
|
1715
|
+
RdxAutocompletePortalMisuseGuard,
|
|
1716
|
+
RdxAutocompleteBackdrop,
|
|
1717
|
+
RdxAutocompletePositioner,
|
|
1718
|
+
RdxAutocompletePopup,
|
|
1719
|
+
RdxAutocompleteArrow,
|
|
1720
|
+
RdxAutocompleteList,
|
|
1721
|
+
RdxAutocompleteRow,
|
|
1722
|
+
RdxAutocompleteSeparator,
|
|
1723
|
+
RdxAutocompleteItem,
|
|
1724
|
+
RdxAutocompleteItemIndicator,
|
|
1725
|
+
RdxAutocompleteGroup,
|
|
1726
|
+
RdxAutocompleteGroupLabel,
|
|
1727
|
+
RdxAutocompleteEmpty,
|
|
1728
|
+
RdxAutocompleteStatus] }); }
|
|
1729
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteModule }); }
|
|
1730
|
+
}
|
|
1731
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteModule, decorators: [{
|
|
1732
|
+
type: NgModule,
|
|
1733
|
+
args: [{
|
|
1734
|
+
imports: [..._importsAutocomplete],
|
|
1735
|
+
exports: [..._importsAutocomplete]
|
|
1736
|
+
}]
|
|
1737
|
+
}] });
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* Generated bundle index. Do not edit.
|
|
1741
|
+
*/
|
|
1742
|
+
|
|
1743
|
+
export { RdxAutocompleteAnchor, RdxAutocompleteArrow, RdxAutocompleteBackdrop, RdxAutocompleteClear, RdxAutocompleteEmpty, RdxAutocompleteGroup, RdxAutocompleteGroupLabel, RdxAutocompleteIcon, RdxAutocompleteInput, RdxAutocompleteInputGroup, RdxAutocompleteItem, RdxAutocompleteItemIndicator, RdxAutocompleteLabel, RdxAutocompleteList, RdxAutocompleteModule, RdxAutocompletePopup, RdxAutocompletePortal, RdxAutocompletePortalMisuseGuard, RdxAutocompletePositioner, RdxAutocompleteRoot, RdxAutocompleteRow, RdxAutocompleteSeparator, RdxAutocompleteStatus, RdxAutocompleteTrigger, RdxAutocompleteValue, _importsAutocomplete };
|
|
1744
|
+
//# sourceMappingURL=radix-ng-primitives-autocomplete.mjs.map
|