@radix-ng/primitives 1.0.0-beta.3 → 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/README.md +1 -1
- 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 +3 -2
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +617 -659
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -1
- 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 +1305 -572
- 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 +240 -112
- 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 +3 -3
- 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 +861 -286
- 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 +144 -159
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +220 -205
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +94 -51
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +141 -173
- 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 +211 -156
- 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 +73 -110
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +10 -1
- package/types/radix-ng-primitives-accordion.d.ts +4 -3
- package/types/radix-ng-primitives-autocomplete.d.ts +217 -152
- package/types/radix-ng-primitives-calendar.d.ts +5 -3
- package/types/radix-ng-primitives-combobox.d.ts +672 -283
- 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 +77 -32
- 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-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 +186 -103
- package/types/radix-ng-primitives-navigation-menu.d.ts +37 -75
- package/types/radix-ng-primitives-popover.d.ts +59 -92
- package/types/radix-ng-primitives-popper.d.ts +39 -9
- package/types/radix-ng-primitives-preview-card.d.ts +39 -72
- 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 +145 -108
- 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 +24 -67
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Directive, inject, booleanAttribute, Injector, model, input, computed, numberAttribute, output, signal, effect, untracked, isDevMode,
|
|
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
3
|
import * as i1 from '@radix-ng/primitives/combobox';
|
|
4
|
-
import { RdxComboboxAnchor, RdxComboboxBackdrop, provideComboboxRootContext,
|
|
4
|
+
import { RdxComboboxAnchor, RdxComboboxBackdrop, useComboboxEngine, provideComboboxRootContext, injectComboboxRootContext, RdxComboboxGroup, RdxComboboxGroupLabel, RdxComboboxIcon, injectComboboxGroupContext, provideComboboxItemContext, RdxComboboxItemIndicator, RdxComboboxLabel, RdxComboboxPortal, RdxComboboxSeparator, RdxComboboxStatus, RdxComboboxTrigger } from '@radix-ng/primitives/combobox';
|
|
5
5
|
import * as i1$1 from '@radix-ng/primitives/popper';
|
|
6
|
-
import { RdxPopperArrow, RdxPopper, RdxPopperAnchor, injectPopperContentWrapperContext, RdxPopperContent
|
|
7
|
-
import * as
|
|
8
|
-
import {
|
|
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
9
|
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
10
|
-
import
|
|
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';
|
|
11
13
|
import { injectFieldRootContext } from '@radix-ng/primitives/field';
|
|
12
14
|
|
|
13
15
|
/**
|
|
@@ -68,23 +70,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
68
70
|
}]
|
|
69
71
|
}] });
|
|
70
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();
|
|
71
75
|
/**
|
|
72
76
|
* Builds the {@link RdxComboboxRootContext} the autocomplete parts consume. Autocomplete reuses the
|
|
73
|
-
* combobox parts (List, Popup, Positioner, Item, …) verbatim, so
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* parts
|
|
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}.
|
|
77
81
|
*/
|
|
78
82
|
const context = () => {
|
|
79
83
|
const root = inject(RdxAutocompleteRoot);
|
|
84
|
+
const engine = engineRegistry.get(root);
|
|
80
85
|
return {
|
|
81
|
-
listId:
|
|
82
|
-
labelId:
|
|
83
|
-
setLabelId: (id) =>
|
|
86
|
+
listId: engine.listId,
|
|
87
|
+
labelId: engine.labelId,
|
|
88
|
+
setLabelId: (id) => engine.setLabelId(id),
|
|
84
89
|
dir: root.dir,
|
|
85
90
|
value: root.value,
|
|
86
91
|
inputValue: root.value,
|
|
87
92
|
open: root.open,
|
|
93
|
+
present: root.present,
|
|
88
94
|
multiple: root.alwaysFalse,
|
|
89
95
|
selectionMode: root.noneMode,
|
|
90
96
|
disabledState: root.disabledState,
|
|
@@ -93,42 +99,52 @@ const context = () => {
|
|
|
93
99
|
openOnInputClick: root.openOnInputClick,
|
|
94
100
|
modal: root.modal,
|
|
95
101
|
virtualized: root.virtualized,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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),
|
|
107
114
|
isSelected: (value) => root.isSelectedValue(value),
|
|
108
|
-
registerItem: (item) =>
|
|
109
|
-
unregisterItem: (item) =>
|
|
110
|
-
highlight:
|
|
111
|
-
highlightNext: () =>
|
|
112
|
-
highlightPrevious: () =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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(),
|
|
118
127
|
highlightItemOnHover: root.highlightItemOnHover,
|
|
119
128
|
keepHighlight: root.keepHighlight,
|
|
120
|
-
inputElement:
|
|
121
|
-
setInputElement: (el) =>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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),
|
|
127
142
|
setInputValue: (value) => root.setQuery(value),
|
|
128
|
-
openAndHighlight: (edge) => root.openAndHighlight(edge),
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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),
|
|
132
148
|
clearSelection: () => root.clearValue(),
|
|
133
149
|
removeValue: () => undefined,
|
|
134
150
|
removeLastValue: () => undefined,
|
|
@@ -136,31 +152,91 @@ const context = () => {
|
|
|
136
152
|
focusLastChip: () => false,
|
|
137
153
|
labelFor: (value) => root.labelFor(value),
|
|
138
154
|
markAsTouched: () => root.markAsTouched()
|
|
155
|
+
// `value`/`inputValue` are the input string here, not `ComboboxValue`.
|
|
139
156
|
};
|
|
140
157
|
};
|
|
141
158
|
/**
|
|
142
159
|
* `autoHighlight` transform: pass `'always'` through verbatim, coerce everything else as a boolean
|
|
143
|
-
* attribute (so the bare `autoHighlight` attribute reads `true`).
|
|
144
|
-
*
|
|
145
|
-
* Kept as a named module-level function rather than an inline `transform` arrow: compodoc 1.2.1
|
|
146
|
-
* (the metadata source for the API contract and Storybook ArgTypes) hangs parsing an inline arrow
|
|
147
|
-
* combined with explicit generic union type arguments on `input()`. A plain function reference sidesteps it.
|
|
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()`).
|
|
148
162
|
*/
|
|
149
163
|
function coerceAutoHighlight(value) {
|
|
150
164
|
return value === 'always' ? 'always' : booleanAttribute(value);
|
|
151
165
|
}
|
|
152
166
|
/**
|
|
153
|
-
* Root of an Autocomplete — a text input with a filtered list of suggestions.
|
|
154
|
-
* engine with `selectionMode: 'none'`, so its value **is** the input
|
|
155
|
-
* item, or clearing all change a single string value.
|
|
156
|
-
* completion, and highlight-model navigation, and provides them to the combobox parts. Implements
|
|
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
|
|
157
170
|
* `ControlValueAccessor` (the form value is the input string).
|
|
158
171
|
*
|
|
159
172
|
* @group Components
|
|
160
173
|
*/
|
|
161
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
|
+
}
|
|
162
233
|
constructor() {
|
|
163
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
|
+
});
|
|
164
240
|
/** The input text. This is the form value (controlled / uncontrolled via {@link defaultValue}). */
|
|
165
241
|
this.value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
166
242
|
/** Initial input text when uncontrolled. */
|
|
@@ -172,7 +248,8 @@ class RdxAutocompleteRoot {
|
|
|
172
248
|
/** Filtering / inline-completion behavior. See {@link AutocompleteMode}. */
|
|
173
249
|
this.mode = input('list', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
174
250
|
/** Text direction. */
|
|
175
|
-
this.
|
|
251
|
+
this.dirInput = input(undefined, { ...(ngDevMode ? { debugName: "dirInput" } : /* istanbul ignore next */ {}), alias: 'dir' });
|
|
252
|
+
this.dir = injectDirection(this.dirInput);
|
|
176
253
|
/** Whether the autocomplete is disabled. */
|
|
177
254
|
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
178
255
|
/** Whether the autocomplete is read-only. */
|
|
@@ -199,17 +276,9 @@ class RdxAutocompleteRoot {
|
|
|
199
276
|
}
|
|
200
277
|
return 'off';
|
|
201
278
|
}, ...(ngDevMode ? [{ debugName: "autoHighlightMode" }] : /* istanbul ignore next */ []));
|
|
202
|
-
/**
|
|
203
|
-
* Whether moving the pointer over an item highlights it. `true` (default) paints `data-highlighted`
|
|
204
|
-
* on hover; `false` suppresses hover-driven highlight entirely, letting CSS `:hover` stay distinct
|
|
205
|
-
* from the `data-highlighted` (keyboard) state. Clicking an item still selects it.
|
|
206
|
-
*/
|
|
279
|
+
/** Whether moving the pointer over an item highlights it. */
|
|
207
280
|
this.highlightItemOnHover = input(true, { ...(ngDevMode ? { debugName: "highlightItemOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
208
|
-
/**
|
|
209
|
-
* Whether a pointer-driven highlight is kept when the cursor leaves the list. `false` (default)
|
|
210
|
-
* clears the highlight on pointer-leave; `true` retains the last hovered item highlighted. Keyboard
|
|
211
|
-
* navigation and auto-highlight are unaffected.
|
|
212
|
-
*/
|
|
281
|
+
/** Whether a pointer-driven highlight is kept when the cursor leaves the list. */
|
|
213
282
|
this.keepHighlight = input(false, { ...(ngDevMode ? { debugName: "keepHighlight" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
214
283
|
/** Whether clicking the input opens the popup. Defaults to `false` (Base UI default). */
|
|
215
284
|
this.openOnInputClick = input(false, { ...(ngDevMode ? { debugName: "openOnInputClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
@@ -221,22 +290,18 @@ class RdxAutocompleteRoot {
|
|
|
221
290
|
this.grid = input(false, { ...(ngDevMode ? { debugName: "grid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
222
291
|
/**
|
|
223
292
|
* Filter applied to items against the input query (only when {@link mode} is `'list'` / `'both'`).
|
|
224
|
-
*
|
|
225
|
-
* -
|
|
226
|
-
* - `null`: built-in filtering disabled (the consumer controls which items exist).
|
|
293
|
+
* `undefined` → locale-aware contains; a function → custom `(value, query, itemToString)`; `null` →
|
|
294
|
+
* built-in filtering disabled.
|
|
227
295
|
*/
|
|
228
296
|
this.filter = input(undefined, ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
229
297
|
/** Maximum number of matching items to show. `-1` (default) means no limit. */
|
|
230
298
|
this.limit = input(-1, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
231
|
-
/**
|
|
232
|
-
* The full set of item values, used as the source of truth for filtering and navigation in
|
|
233
|
-
* {@link virtualized} mode (where only a window of `RdxAutocompleteItem` is mounted).
|
|
234
|
-
*/
|
|
299
|
+
/** The full set of item values for {@link virtualized} mode. */
|
|
235
300
|
this.items = input(...(ngDevMode ? [undefined, { debugName: "items" }] : /* istanbul ignore next */ []));
|
|
236
301
|
/** Whether the list is externally virtualized (navigation runs over {@link items} by index). */
|
|
237
302
|
this.virtualized = input(false, { ...(ngDevMode ? { debugName: "virtualized" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
238
|
-
/** How item values are compared for equality (function or object key). */
|
|
239
|
-
this.
|
|
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 */ []));
|
|
240
305
|
/** Converts an item value to its string label (filter text + input text on selection). */
|
|
241
306
|
this.itemToStringValue = input(...(ngDevMode ? [undefined, { debugName: "itemToStringValue" }] : /* istanbul ignore next */ []));
|
|
242
307
|
/** Emits when the input value changes (typing, selection, or clear), with the reason. */
|
|
@@ -247,100 +312,54 @@ class RdxAutocompleteRoot {
|
|
|
247
312
|
this.onItemHighlighted = output();
|
|
248
313
|
/** Emits after the open/close transition (including any exit animation) finishes. */
|
|
249
314
|
this.onOpenChangeComplete = output();
|
|
250
|
-
this.transition = useTransitionStatus((open) => this.onOpenChangeComplete.emit(open));
|
|
251
|
-
/** Open/close transition phase, for `data-starting-style` / `data-ending-style`. */
|
|
252
|
-
this.transitionStatus = this.transition.status;
|
|
253
|
-
/** Registers the popup element whose animation determines transition completion. */
|
|
254
|
-
this.registerTransitionElement = this.transition.registerElement;
|
|
255
|
-
this.listId = injectId('rdx-autocomplete-list-');
|
|
256
|
-
this.labelId = signal(undefined, ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
257
|
-
this.inputElement = signal(null, ...(ngDevMode ? [{ debugName: "inputElement" }] : /* istanbul ignore next */ []));
|
|
258
315
|
/** Constant signals exposed to the combobox context (autocomplete is always single-value). */
|
|
259
316
|
this.alwaysFalse = signal(false, ...(ngDevMode ? [{ debugName: "alwaysFalse" }] : /* istanbul ignore next */ []));
|
|
260
317
|
this.noneMode = signal('none', ...(ngDevMode ? [{ debugName: "noneMode" }] : /* istanbul ignore next */ []));
|
|
261
318
|
this.cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
262
319
|
this.disabledState = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
263
320
|
this.requiredState = computed(() => this.required(), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
264
|
-
this.
|
|
265
|
-
|
|
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`). */
|
|
266
324
|
this.filteringMode = computed(() => this.mode() === 'list' || this.mode() === 'both', ...(ngDevMode ? [{ debugName: "filteringMode" }] : /* istanbul ignore next */ []));
|
|
267
|
-
/** Whether inline completion applies in the current mode. */
|
|
325
|
+
/** Whether inline completion applies in the current mode (`both` / `inline`). */
|
|
268
326
|
this.inlineMode = computed(() => this.mode() === 'both' || this.mode() === 'inline', ...(ngDevMode ? [{ debugName: "inlineMode" }] : /* istanbul ignore next */ []));
|
|
269
327
|
/**
|
|
270
328
|
* Whether the input text is a fresh user query rather than a committed selection's label. While
|
|
271
|
-
* `false` (just opened, or showing a committed selection), the list is unfiltered
|
|
272
|
-
*
|
|
329
|
+
* `false` (just opened, or showing a committed selection), the list is unfiltered; it flips `true`
|
|
330
|
+
* on the first keystroke.
|
|
273
331
|
*/
|
|
274
332
|
this.typed = signal(false, ...(ngDevMode ? [{ debugName: "typed" }] : /* istanbul ignore next */ []));
|
|
275
333
|
/** The text the user actually typed, used as the filter query. */
|
|
276
334
|
this.query = computed(() => (this.typed() ? (this.value() ?? '') : ''), ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
|
|
277
|
-
/**
|
|
278
|
-
this.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
const limit = this.limit();
|
|
300
|
-
const cap = (arr) => (limit >= 0 ? arr.slice(0, limit) : arr);
|
|
301
|
-
if (!this.filteringMode()) {
|
|
302
|
-
return cap(data);
|
|
303
|
-
}
|
|
304
|
-
const filter = this.filter();
|
|
305
|
-
if (filter === null) {
|
|
306
|
-
return cap(data);
|
|
307
|
-
}
|
|
308
|
-
const query = this.query();
|
|
309
|
-
if (!query) {
|
|
310
|
-
return cap(data);
|
|
311
|
-
}
|
|
312
|
-
const matcher = filter ?? this.defaultFilter.contains;
|
|
313
|
-
return cap(data.filter((value) => matcher(this.textFor(value), query, value)));
|
|
314
|
-
}, ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
|
|
315
|
-
this.visibleCount = computed(() => this.virtualized() ? this.filteredItems().length : this.visibleItems().length, ...(ngDevMode ? [{ debugName: "visibleCount" }] : /* istanbul ignore next */ []));
|
|
316
|
-
this.highlight = useListHighlight({
|
|
317
|
-
items: this.orderedItems,
|
|
318
|
-
isNavigable: (item) => this.isVisible(item) && !item.disabled(),
|
|
319
|
-
getId: (item) => item.id,
|
|
320
|
-
loop: this.loopFocus,
|
|
321
|
-
injector: this.injector
|
|
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)
|
|
322
356
|
});
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
this.
|
|
329
|
-
if (this.virtualized()) {
|
|
330
|
-
const index = this.highlightedIndex();
|
|
331
|
-
return index >= 0 ? this.itemId(index) : undefined;
|
|
332
|
-
}
|
|
333
|
-
return this.highlight.activeId();
|
|
334
|
-
}, ...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
335
|
-
/**
|
|
336
|
-
* What to highlight once the list has mounted (items register asynchronously after opening):
|
|
337
|
-
* an end edge, or `'first-match'` (the first item whose label starts with the query — used by
|
|
338
|
-
* auto-highlight so inline completion lands on a real prefix match even when the list is static).
|
|
339
|
-
*/
|
|
340
|
-
this.pendingHighlightEdge = signal(null, ...(ngDevMode ? [{ debugName: "pendingHighlightEdge" }] : /* istanbul ignore next */ []));
|
|
341
|
-
this.keyboardActive = false;
|
|
342
|
-
/** The trigger element, used as a focus fallback when the input lives inside the popup. */
|
|
343
|
-
this.triggerElement = null;
|
|
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));
|
|
344
363
|
// Apply uncontrolled defaults once.
|
|
345
364
|
effect(() => {
|
|
346
365
|
const initial = this.defaultValue();
|
|
@@ -353,219 +372,41 @@ class RdxAutocompleteRoot {
|
|
|
353
372
|
this.open.set(true);
|
|
354
373
|
}
|
|
355
374
|
});
|
|
356
|
-
// Emit open changes and drive the open/close transition (skip the initial run).
|
|
357
|
-
let previousOpen = untracked(this.open);
|
|
358
|
-
effect(() => {
|
|
359
|
-
const open = this.open();
|
|
360
|
-
if (open === previousOpen) {
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
previousOpen = open;
|
|
364
|
-
untracked(() => {
|
|
365
|
-
this.onOpenChange.emit(open);
|
|
366
|
-
this.transition.start(open);
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
// Emit highlight changes (skip the initial run).
|
|
370
|
-
let highlightInitialized = false;
|
|
371
|
-
effect(() => {
|
|
372
|
-
const item = this.highlightedItem();
|
|
373
|
-
const index = this.highlightedIndex();
|
|
374
|
-
if (!highlightInitialized) {
|
|
375
|
-
highlightInitialized = true;
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
|
-
untracked(() => {
|
|
379
|
-
const reason = this.highlightReason();
|
|
380
|
-
if (this.virtualized()) {
|
|
381
|
-
const value = index >= 0 ? (this.filteredItems()[index] ?? null) : null;
|
|
382
|
-
this.onItemHighlighted.emit({ value, index, reason });
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
const value = item ? item.value() : null;
|
|
386
|
-
const itemIndex = item ? this.visibleItems().indexOf(item) : -1;
|
|
387
|
-
this.onItemHighlighted.emit({ value, index: itemIndex, reason });
|
|
388
|
-
}
|
|
389
|
-
});
|
|
390
|
-
});
|
|
391
|
-
// Inline completion: mirror the active item's label into the input (with the completed suffix
|
|
392
|
-
// selected by the input directive). Recomputes whenever the highlight or query changes.
|
|
393
|
-
effect(() => {
|
|
394
|
-
const item = this.highlightedItem();
|
|
395
|
-
const query = this.query();
|
|
396
|
-
const reason = this.highlightReason();
|
|
397
|
-
untracked(() => this.recomputeInlinePreview(item, query, reason));
|
|
398
|
-
});
|
|
399
|
-
// Apply a deferred open-edge highlight once items (DOM refs) or filtered data have registered.
|
|
400
|
-
effect(() => {
|
|
401
|
-
const edge = this.pendingHighlightEdge();
|
|
402
|
-
const count = this.virtualized() ? this.filteredItems().length : this.orderedItems().length;
|
|
403
|
-
if (!this.open() || edge === null || count === 0) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
untracked(() => {
|
|
407
|
-
this.highlightReason.set('none');
|
|
408
|
-
if (this.virtualized()) {
|
|
409
|
-
this.highlightedIndex.set(this.resolveVirtualizedEdge(edge, count));
|
|
410
|
-
}
|
|
411
|
-
else if (edge === 'last') {
|
|
412
|
-
this.highlight.last();
|
|
413
|
-
}
|
|
414
|
-
else if (edge === 'first-match') {
|
|
415
|
-
this.highlight.set(this.firstMatchItem() ?? this.firstVisibleNavigable());
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
418
|
-
this.highlight.first();
|
|
419
|
-
}
|
|
420
|
-
this.pendingHighlightEdge.set(null);
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
// autoHighlight 'always': keep the first navigable item highlighted whenever the popup is open.
|
|
424
|
-
effect(() => {
|
|
425
|
-
this.orderedItems();
|
|
426
|
-
this.visibleCount();
|
|
427
|
-
if (this.autoHighlightMode() === 'always' && this.open()) {
|
|
428
|
-
untracked(() => {
|
|
429
|
-
if (this.virtualized()) {
|
|
430
|
-
const length = this.filteredItems().length;
|
|
431
|
-
const index = this.highlightedIndex();
|
|
432
|
-
if ((index < 0 || index >= length) && length > 0) {
|
|
433
|
-
this.highlightReason.set('none');
|
|
434
|
-
this.highlightedIndex.set(0);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
else if (this.highlightedItem() === null) {
|
|
438
|
-
this.highlightReason.set('none');
|
|
439
|
-
this.highlight.first();
|
|
440
|
-
}
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
// Virtualized self-heal: clear a highlight that filtering pushed out of range.
|
|
445
|
-
effect(() => {
|
|
446
|
-
if (!this.virtualized()) {
|
|
447
|
-
return;
|
|
448
|
-
}
|
|
449
|
-
const length = this.filteredItems().length;
|
|
450
|
-
untracked(() => {
|
|
451
|
-
const index = this.highlightedIndex();
|
|
452
|
-
if (index >= length && index !== -1) {
|
|
453
|
-
this.highlightReason.set('none');
|
|
454
|
-
this.highlightedIndex.set(-1);
|
|
455
|
-
}
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
375
|
// Virtualized object values can't be labelled from the DOM — warn once in dev.
|
|
459
376
|
if (isDevMode()) {
|
|
460
|
-
let warned = false;
|
|
461
377
|
effect(() => {
|
|
462
|
-
if (
|
|
378
|
+
if (!this.virtualized() || this.itemToStringValue()) {
|
|
463
379
|
return;
|
|
464
380
|
}
|
|
465
381
|
if (this.items()?.some((value) => value !== null && typeof value === 'object')) {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
'to render correct labels; falling back to a generic label.');
|
|
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');
|
|
469
384
|
}
|
|
470
385
|
});
|
|
471
386
|
}
|
|
472
387
|
}
|
|
473
|
-
recomputeInlinePreview(item, query, reason) {
|
|
474
|
-
// Pointer hover must not rewrite the input (matches Base UI); only typing / keyboard nav complete it.
|
|
475
|
-
if (!this.inlineMode() || this.suppressInline || !item || reason === 'pointer') {
|
|
476
|
-
this.inlinePreview.set(null);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
const label = item.textValue();
|
|
480
|
-
if (label && query && label.toLowerCase().startsWith(query.toLowerCase())) {
|
|
481
|
-
// Type-ahead: keep the typed prefix (preserving its casing) and complete the rest.
|
|
482
|
-
this.inlinePreview.set(query + label.slice(query.length));
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
// Keyboard navigation to an item that doesn't prefix-match the query: show its full label so the
|
|
486
|
-
// input reflects the highlighted option. Typing (reason 'none') never jumps to a non-prefix label.
|
|
487
|
-
if (reason === 'keyboard' && label) {
|
|
488
|
-
this.inlinePreview.set(label);
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
this.inlinePreview.set(null);
|
|
492
|
-
}
|
|
493
388
|
setSuppressInline(value) {
|
|
494
|
-
this.
|
|
389
|
+
this.engine.setSuppressInline(value);
|
|
495
390
|
}
|
|
496
391
|
/** Opens the popup for browsing (resets the query to "pristine" and selects the input text). */
|
|
497
|
-
openForBrowse() {
|
|
392
|
+
openForBrowse(reason = 'none', event = new Event('autocomplete.open-change')) {
|
|
498
393
|
if (!this.open()) {
|
|
499
394
|
this.typed.set(false);
|
|
500
395
|
}
|
|
501
|
-
this.setOpen(true);
|
|
502
|
-
this.selectInputText();
|
|
396
|
+
this.setOpen(true, reason, event);
|
|
397
|
+
this.engine.selectInputText();
|
|
503
398
|
if (this.autoHighlightMode() === 'always') {
|
|
504
|
-
this.
|
|
399
|
+
this.engine.setPendingHighlightEdge('first');
|
|
505
400
|
}
|
|
506
401
|
}
|
|
507
402
|
/** Opens the popup and highlights the given edge once the list mounts. */
|
|
508
|
-
openAndHighlight(edge) {
|
|
403
|
+
openAndHighlight(edge, reason = 'list-navigation', event = new Event('autocomplete.open-change')) {
|
|
509
404
|
if (!this.open()) {
|
|
510
405
|
this.typed.set(false);
|
|
511
406
|
}
|
|
512
|
-
this.setOpen(true);
|
|
513
|
-
this.selectInputText();
|
|
514
|
-
this.
|
|
515
|
-
}
|
|
516
|
-
/** Whether the item matches the active query (ignores the `limit` cap). */
|
|
517
|
-
matchesFilter(item) {
|
|
518
|
-
if (!this.filteringMode()) {
|
|
519
|
-
return true;
|
|
520
|
-
}
|
|
521
|
-
const filter = this.filter();
|
|
522
|
-
if (filter === null) {
|
|
523
|
-
return true;
|
|
524
|
-
}
|
|
525
|
-
const query = this.query();
|
|
526
|
-
const matcher = filter ?? this.defaultFilter.contains;
|
|
527
|
-
return matcher(item.textValue(), query, item.value());
|
|
528
|
-
}
|
|
529
|
-
/** Whether the item is shown in the list (matches the query and is within `limit`). */
|
|
530
|
-
isVisible(item) {
|
|
531
|
-
return this.visibleSet().has(item);
|
|
532
|
-
}
|
|
533
|
-
/** The first visible, navigable item whose label starts with the query (for inline completion). */
|
|
534
|
-
firstMatchItem() {
|
|
535
|
-
const query = this.query();
|
|
536
|
-
if (!query) {
|
|
537
|
-
return null;
|
|
538
|
-
}
|
|
539
|
-
const lower = query.toLowerCase();
|
|
540
|
-
return (this.visibleItems().find((item) => !item.disabled() && item.textValue().toLowerCase().startsWith(lower)) ??
|
|
541
|
-
null);
|
|
542
|
-
}
|
|
543
|
-
/** The first visible, navigable item (auto-highlight fallback when no prefix match exists). */
|
|
544
|
-
firstVisibleNavigable() {
|
|
545
|
-
return this.visibleItems().find((item) => !item.disabled()) ?? null;
|
|
546
|
-
}
|
|
547
|
-
/** Resolves a pending edge to a virtualized index. */
|
|
548
|
-
resolveVirtualizedEdge(edge, count) {
|
|
549
|
-
if (edge === 'last') {
|
|
550
|
-
return count - 1;
|
|
551
|
-
}
|
|
552
|
-
if (edge === 'first-match') {
|
|
553
|
-
const query = this.query();
|
|
554
|
-
if (query) {
|
|
555
|
-
const lower = query.toLowerCase();
|
|
556
|
-
const index = this.filteredItems().findIndex((value) => this.textFor(value).toLowerCase().startsWith(lower));
|
|
557
|
-
if (index >= 0) {
|
|
558
|
-
return index;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return 0;
|
|
563
|
-
}
|
|
564
|
-
isKeyboardActive() {
|
|
565
|
-
return this.keyboardActive;
|
|
566
|
-
}
|
|
567
|
-
setKeyboardActive(value) {
|
|
568
|
-
this.keyboardActive = value;
|
|
407
|
+
this.setOpen(true, reason, event);
|
|
408
|
+
this.engine.selectInputText();
|
|
409
|
+
this.engine.setPendingHighlightEdge(edge);
|
|
569
410
|
}
|
|
570
411
|
/** Whether the item's value/label matches the current input value (combobox context contract). */
|
|
571
412
|
isSelectedValue(value) {
|
|
@@ -573,27 +414,33 @@ class RdxAutocompleteRoot {
|
|
|
573
414
|
if (!current) {
|
|
574
415
|
return false;
|
|
575
416
|
}
|
|
576
|
-
return value === current || isItemEqualToValue(value, current, this.
|
|
417
|
+
return value === current || isItemEqualToValue(value, current, this.isItemEqualToValue());
|
|
577
418
|
}
|
|
578
|
-
|
|
579
|
-
this.
|
|
580
|
-
|
|
581
|
-
unregisterItem(item) {
|
|
582
|
-
this._items.update((items) => items.filter((i) => i !== item));
|
|
583
|
-
}
|
|
584
|
-
setOpen(open) {
|
|
585
|
-
if (this.disabledState() || this.readOnly()) {
|
|
586
|
-
return;
|
|
419
|
+
setOpen(open, reason = 'none', event = new Event('autocomplete.open-change')) {
|
|
420
|
+
if (open === this.open()) {
|
|
421
|
+
return true;
|
|
587
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());
|
|
588
432
|
this.open.set(open);
|
|
433
|
+
return true;
|
|
589
434
|
}
|
|
590
|
-
closePopup(revert = true) {
|
|
435
|
+
closePopup(revert = true, reason = 'none', event = new Event('autocomplete.open-change')) {
|
|
591
436
|
if (!this.open()) {
|
|
592
437
|
return;
|
|
593
438
|
}
|
|
594
|
-
this.
|
|
595
|
-
|
|
596
|
-
|
|
439
|
+
if (!this.setOpen(false, reason, event)) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
this.engine.clearHighlightState();
|
|
443
|
+
this.engine.clearInlinePreview();
|
|
597
444
|
if (revert) {
|
|
598
445
|
this.typed.set(false);
|
|
599
446
|
}
|
|
@@ -606,236 +453,169 @@ class RdxAutocompleteRoot {
|
|
|
606
453
|
// Inline modes (`both` / `inline`) implicitly highlight the first prefix match so the input can
|
|
607
454
|
// be inline-completed even without an explicit `autoHighlight`.
|
|
608
455
|
if (this.autoHighlightMode() !== 'off' || (this.inlineMode() && value.length > 0)) {
|
|
609
|
-
this.
|
|
456
|
+
this.engine.setPendingHighlightEdge('first-match');
|
|
610
457
|
}
|
|
611
458
|
}
|
|
612
|
-
/** Selects all input text so the next keystroke replaces a committed label. */
|
|
613
|
-
selectInputText() {
|
|
614
|
-
this.inputElement()?.select();
|
|
615
|
-
}
|
|
616
459
|
labelFor(value) {
|
|
617
460
|
const custom = this.itemToStringValue();
|
|
618
461
|
if (custom) {
|
|
619
462
|
return custom(value);
|
|
620
463
|
}
|
|
621
|
-
const item = this.orderedItems().find((i) => isItemEqualToValue(i.value(), value, this.
|
|
464
|
+
const item = this.engine.orderedItems().find((i) => isItemEqualToValue(i.value(), value, this.isItemEqualToValue()));
|
|
622
465
|
return item ? item.textValue() : itemToStringLabel(value);
|
|
623
466
|
}
|
|
624
|
-
/** Label text for a raw item value (virtualized mode, no DOM element to read from). */
|
|
625
|
-
textFor(value) {
|
|
626
|
-
const custom = this.itemToStringValue();
|
|
627
|
-
return custom ? custom(value) : itemToStringLabel(value);
|
|
628
|
-
}
|
|
629
|
-
/** Deterministic id for the item at `index` in virtualized mode (matches `aria-activedescendant`). */
|
|
630
467
|
itemId(index) {
|
|
631
|
-
return
|
|
468
|
+
return this.engine.itemId(index);
|
|
469
|
+
}
|
|
470
|
+
isVisible(item) {
|
|
471
|
+
return this.engine.isVisible(item);
|
|
632
472
|
}
|
|
633
|
-
|
|
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')) {
|
|
634
480
|
if (this.disabledState() || this.readOnly() || item.disabled()) {
|
|
635
481
|
return;
|
|
636
482
|
}
|
|
637
|
-
this.commitSelection(item.textValue() || this.labelFor(item.value()));
|
|
483
|
+
this.commitSelection(item.textValue() || this.labelFor(item.value()), event);
|
|
638
484
|
}
|
|
639
485
|
/** Selects the filtered item at `index` (virtualized mode). */
|
|
640
|
-
selectIndex(index) {
|
|
486
|
+
selectIndex(index, event = new Event('autocomplete.item-press')) {
|
|
641
487
|
if (this.disabledState() || this.readOnly()) {
|
|
642
488
|
return;
|
|
643
489
|
}
|
|
644
|
-
const value = this.filteredItems()[index];
|
|
490
|
+
const value = this.engine.filteredItems()[index];
|
|
645
491
|
if (value === undefined) {
|
|
646
492
|
return;
|
|
647
493
|
}
|
|
648
|
-
this.commitSelection(this.labelFor(value));
|
|
494
|
+
this.commitSelection(this.labelFor(value), event);
|
|
649
495
|
}
|
|
650
496
|
/** Commits a selection: the input value becomes the item's label, the popup closes. */
|
|
651
|
-
commitSelection(label) {
|
|
652
|
-
|
|
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();
|
|
653
502
|
this.commitValue(label, 'item-press');
|
|
654
503
|
this.typed.set(false);
|
|
655
|
-
this.
|
|
656
|
-
this.
|
|
657
|
-
this.restoreFocusAfterSelect();
|
|
504
|
+
this.closePopup(false, 'item-press', event);
|
|
505
|
+
this.engine.restoreFocusAfterSelect(activeBefore);
|
|
658
506
|
this.maybeSubmit();
|
|
659
507
|
}
|
|
660
508
|
maybeSubmit() {
|
|
661
509
|
if (this.submitOnItemClick()) {
|
|
662
|
-
this.inputElement()?.form?.requestSubmit?.();
|
|
510
|
+
this.engine.inputElement()?.form?.requestSubmit?.();
|
|
663
511
|
}
|
|
664
512
|
}
|
|
665
|
-
selectHighlighted() {
|
|
513
|
+
selectHighlighted(event = new Event('autocomplete.item-press')) {
|
|
666
514
|
if (this.virtualized()) {
|
|
667
|
-
const index = this.highlightedIndex();
|
|
515
|
+
const index = this.engine.highlightedIndex();
|
|
668
516
|
if (index >= 0) {
|
|
669
|
-
this.selectIndex(index);
|
|
517
|
+
this.selectIndex(index, event);
|
|
670
518
|
}
|
|
671
519
|
return;
|
|
672
520
|
}
|
|
673
|
-
const item = this.highlightedItem();
|
|
521
|
+
const item = this.engine.highlightedItem();
|
|
674
522
|
if (item) {
|
|
675
|
-
this.handleSelect(item);
|
|
523
|
+
this.handleSelect(item, event);
|
|
676
524
|
}
|
|
677
525
|
}
|
|
678
|
-
// ---
|
|
679
|
-
|
|
680
|
-
this.
|
|
681
|
-
if (this.
|
|
682
|
-
this.
|
|
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);
|
|
683
531
|
}
|
|
684
|
-
else if (
|
|
685
|
-
this.
|
|
532
|
+
else if (direction === 1) {
|
|
533
|
+
this.engine.highlightNext();
|
|
686
534
|
}
|
|
687
535
|
else {
|
|
688
|
-
this.
|
|
536
|
+
this.engine.highlightPrevious();
|
|
689
537
|
}
|
|
690
538
|
}
|
|
539
|
+
moveDown() {
|
|
540
|
+
this.engine.highlightNext();
|
|
541
|
+
}
|
|
691
542
|
moveUp() {
|
|
692
|
-
this.
|
|
693
|
-
if (this.virtualized()) {
|
|
694
|
-
this.stepIndex(-1);
|
|
695
|
-
}
|
|
696
|
-
else if (this.grid()) {
|
|
697
|
-
this.gridVertical(-1);
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
this.highlight.previous();
|
|
701
|
-
}
|
|
543
|
+
this.engine.highlightPrevious();
|
|
702
544
|
}
|
|
703
|
-
/** Grid: move to the next cell in DOM order. Non-grid: no-op (caret movement). */
|
|
704
545
|
moveRight() {
|
|
705
|
-
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
this.highlightReason.set('keyboard');
|
|
709
|
-
this.highlight.next();
|
|
546
|
+
this.engine.highlightNextColumn();
|
|
710
547
|
}
|
|
711
548
|
moveLeft() {
|
|
712
|
-
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
this.highlightReason.set('keyboard');
|
|
716
|
-
this.highlight.previous();
|
|
549
|
+
this.engine.highlightPreviousColumn();
|
|
717
550
|
}
|
|
718
551
|
highlightFirst(reason = 'keyboard') {
|
|
719
|
-
this.
|
|
720
|
-
if (this.virtualized()) {
|
|
721
|
-
this.highlightedIndex.set(this.filteredItems().length > 0 ? 0 : -1);
|
|
722
|
-
}
|
|
723
|
-
else {
|
|
724
|
-
this.highlight.first();
|
|
725
|
-
}
|
|
552
|
+
this.engine.highlightFirst(reason);
|
|
726
553
|
}
|
|
727
554
|
highlightLast(reason = 'keyboard') {
|
|
728
|
-
this.
|
|
729
|
-
if (this.virtualized()) {
|
|
730
|
-
const length = this.filteredItems().length;
|
|
731
|
-
this.highlightedIndex.set(length > 0 ? length - 1 : -1);
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
this.highlight.last();
|
|
735
|
-
}
|
|
555
|
+
this.engine.highlightLast(reason);
|
|
736
556
|
}
|
|
737
557
|
highlightIndex(index, reason) {
|
|
738
|
-
|
|
739
|
-
return;
|
|
740
|
-
}
|
|
741
|
-
this.highlightReason.set(reason);
|
|
742
|
-
this.highlightedIndex.set(index);
|
|
558
|
+
this.engine.highlightIndex(index, reason);
|
|
743
559
|
}
|
|
744
560
|
setHighlight(item, reason) {
|
|
745
|
-
this.
|
|
746
|
-
this.highlight.set(item);
|
|
561
|
+
this.engine.setHighlight(item, reason);
|
|
747
562
|
}
|
|
748
563
|
clearHighlightState() {
|
|
749
|
-
this.
|
|
750
|
-
this.highlightedIndex.set(-1);
|
|
564
|
+
this.engine.clearHighlightState();
|
|
751
565
|
}
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (length === 0) {
|
|
755
|
-
this.highlightedIndex.set(-1);
|
|
756
|
-
return;
|
|
757
|
-
}
|
|
758
|
-
const current = this.highlightedIndex();
|
|
759
|
-
if (current < 0) {
|
|
760
|
-
this.highlightedIndex.set(direction === 1 ? 0 : length - 1);
|
|
761
|
-
return;
|
|
762
|
-
}
|
|
763
|
-
let next = current + direction;
|
|
764
|
-
const loop = this.loopFocus();
|
|
765
|
-
if (next < 0) {
|
|
766
|
-
next = loop ? length - 1 : 0;
|
|
767
|
-
}
|
|
768
|
-
else if (next >= length) {
|
|
769
|
-
next = loop ? 0 : length - 1;
|
|
770
|
-
}
|
|
771
|
-
this.highlightedIndex.set(next);
|
|
566
|
+
isKeyboardActive() {
|
|
567
|
+
return this.engine.isKeyboardActive();
|
|
772
568
|
}
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const rows = this.gridRows();
|
|
776
|
-
if (rows.length === 0) {
|
|
777
|
-
return;
|
|
778
|
-
}
|
|
779
|
-
const current = this.highlightedItem();
|
|
780
|
-
if (!current) {
|
|
781
|
-
const row = direction === 1 ? rows[0] : rows[rows.length - 1];
|
|
782
|
-
this.highlight.set(row[0] ?? null);
|
|
783
|
-
return;
|
|
784
|
-
}
|
|
785
|
-
let rowIndex = rows.findIndex((row) => row.includes(current));
|
|
786
|
-
const col = rowIndex >= 0 ? rows[rowIndex].indexOf(current) : 0;
|
|
787
|
-
const loop = this.loopFocus();
|
|
788
|
-
rowIndex += direction;
|
|
789
|
-
if (rowIndex < 0) {
|
|
790
|
-
rowIndex = loop ? rows.length - 1 : 0;
|
|
791
|
-
}
|
|
792
|
-
else if (rowIndex >= rows.length) {
|
|
793
|
-
rowIndex = loop ? 0 : rows.length - 1;
|
|
794
|
-
}
|
|
795
|
-
const targetRow = rows[rowIndex];
|
|
796
|
-
this.highlight.set(targetRow[Math.min(col, targetRow.length - 1)] ?? null);
|
|
797
|
-
}
|
|
798
|
-
/** Visible items grouped into rows by their nearest `[rdxAutocompleteRow]` ancestor (DOM order). */
|
|
799
|
-
gridRows() {
|
|
800
|
-
const rows = new Map();
|
|
801
|
-
for (const item of this.visibleItems()) {
|
|
802
|
-
const key = item.element.closest('[rdxAutocompleteRow]');
|
|
803
|
-
const bucket = rows.get(key);
|
|
804
|
-
if (bucket) {
|
|
805
|
-
bucket.push(item);
|
|
806
|
-
}
|
|
807
|
-
else {
|
|
808
|
-
rows.set(key, [item]);
|
|
809
|
-
}
|
|
810
|
-
}
|
|
811
|
-
return [...rows.values()];
|
|
569
|
+
setKeyboardActive(value) {
|
|
570
|
+
this.engine.setKeyboardActive(value);
|
|
812
571
|
}
|
|
813
572
|
clearValue() {
|
|
573
|
+
if (this.disabledState() || this.readOnly()) {
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
814
576
|
this.commitValue('', 'input-clear');
|
|
815
577
|
this.typed.set(true);
|
|
816
|
-
this.
|
|
817
|
-
this.
|
|
578
|
+
this.engine.clearInlinePreview();
|
|
579
|
+
this.engine.clearHighlightState();
|
|
580
|
+
this.engine.focusInput();
|
|
818
581
|
}
|
|
819
582
|
focusInput() {
|
|
820
|
-
this.
|
|
821
|
-
}
|
|
822
|
-
restoreFocusAfterSelect() {
|
|
823
|
-
const input = this.inputElement();
|
|
824
|
-
if (input && !input.closest('[rdxAutocompletePopup]')) {
|
|
825
|
-
input.focus();
|
|
826
|
-
}
|
|
827
|
-
else {
|
|
828
|
-
this.triggerElement?.focus();
|
|
829
|
-
}
|
|
583
|
+
this.engine.focusInput();
|
|
830
584
|
}
|
|
831
585
|
markAsTouched() {
|
|
832
586
|
this.onTouched?.();
|
|
833
587
|
}
|
|
834
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
|
+
}
|
|
835
594
|
this.value.set(value);
|
|
836
595
|
this.onValueChange.emit({ value, reason });
|
|
837
596
|
this.onChange?.(value);
|
|
838
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
|
+
}
|
|
839
619
|
// ControlValueAccessor (the form value is the input string)
|
|
840
620
|
writeValue(value) {
|
|
841
621
|
untracked(() => this.value.set(value ?? ''));
|
|
@@ -850,9 +630,12 @@ class RdxAutocompleteRoot {
|
|
|
850
630
|
this.cvaDisabled.set(isDisabled);
|
|
851
631
|
}
|
|
852
632
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
853
|
-
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 },
|
|
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: [
|
|
854
634
|
provideComboboxRootContext(context),
|
|
855
|
-
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxAutocompleteRoot, multi: true }
|
|
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)
|
|
856
639
|
], exportAs: ["rdxAutocompleteRoot"], hostDirectives: [{ directive: i1$1.RdxPopper }], ngImport: i0 }); }
|
|
857
640
|
}
|
|
858
641
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteRoot, decorators: [{
|
|
@@ -862,25 +645,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
862
645
|
exportAs: 'rdxAutocompleteRoot',
|
|
863
646
|
providers: [
|
|
864
647
|
provideComboboxRootContext(context),
|
|
865
|
-
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxAutocompleteRoot, multi: true }
|
|
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)
|
|
866
652
|
],
|
|
867
653
|
hostDirectives: [RdxPopper],
|
|
868
654
|
host: {
|
|
869
655
|
'[attr.data-disabled]': 'disabledState() ? "" : undefined'
|
|
870
656
|
}
|
|
871
657
|
}]
|
|
872
|
-
}], 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 }] }],
|
|
873
|
-
/** DOM-order comparator for two elements (precedes → -1, follows → 1). */
|
|
874
|
-
function domOrder(a, b) {
|
|
875
|
-
const position = a.compareDocumentPosition(b);
|
|
876
|
-
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
877
|
-
return -1;
|
|
878
|
-
}
|
|
879
|
-
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
880
|
-
return 1;
|
|
881
|
-
}
|
|
882
|
-
return 0;
|
|
883
|
-
}
|
|
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"] }] } });
|
|
884
659
|
|
|
885
660
|
/**
|
|
886
661
|
* Clears the input value. Hidden when there is nothing to clear.
|
|
@@ -891,45 +666,71 @@ class RdxAutocompleteClear {
|
|
|
891
666
|
constructor() {
|
|
892
667
|
this.root = inject(RdxAutocompleteRoot);
|
|
893
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 */ []));
|
|
894
671
|
}
|
|
895
672
|
onClick() {
|
|
896
673
|
this.root.clearValue();
|
|
897
674
|
}
|
|
898
675
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteClear, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
899
|
-
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": "
|
|
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 }); }
|
|
900
677
|
}
|
|
901
678
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteClear, decorators: [{
|
|
902
679
|
type: Directive,
|
|
903
680
|
args: [{
|
|
904
681
|
selector: 'button[rdxAutocompleteClear]',
|
|
905
682
|
exportAs: 'rdxAutocompleteClear',
|
|
906
|
-
hostDirectives: [
|
|
683
|
+
hostDirectives: [RdxFloatingInsideElement],
|
|
907
684
|
host: {
|
|
908
685
|
type: 'button',
|
|
909
686
|
tabindex: '-1',
|
|
910
687
|
'aria-label': 'Clear',
|
|
911
688
|
'[hidden]': 'isEmpty()',
|
|
912
|
-
'[attr.disabled]': '
|
|
689
|
+
'[attr.disabled]': 'isDisabled() ? "" : undefined',
|
|
913
690
|
'(click)': 'onClick()'
|
|
914
691
|
}
|
|
915
692
|
}]
|
|
916
693
|
}] });
|
|
917
694
|
|
|
918
695
|
/**
|
|
919
|
-
*
|
|
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.
|
|
920
699
|
*
|
|
921
700
|
* @group Components
|
|
922
701
|
*/
|
|
923
702
|
class RdxAutocompleteEmpty {
|
|
924
|
-
|
|
925
|
-
|
|
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 }); }
|
|
926
714
|
}
|
|
927
715
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteEmpty, decorators: [{
|
|
928
|
-
type:
|
|
716
|
+
type: Component,
|
|
929
717
|
args: [{
|
|
930
718
|
selector: '[rdxAutocompleteEmpty]',
|
|
931
719
|
exportAs: 'rdxAutocompleteEmpty',
|
|
932
|
-
|
|
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
|
+
}
|
|
933
734
|
}]
|
|
934
735
|
}] });
|
|
935
736
|
|
|
@@ -989,6 +790,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
989
790
|
}]
|
|
990
791
|
}] });
|
|
991
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
|
+
|
|
992
835
|
const attr$1 = (value) => (value ? '' : undefined);
|
|
993
836
|
/**
|
|
994
837
|
* The autocomplete text input. Holds DOM focus at all times; the highlighted option is referenced via
|
|
@@ -1027,7 +870,11 @@ class RdxAutocompleteInput {
|
|
|
1027
870
|
/** Whether an IME composition is in progress (CJK). While composing, don't filter or select. */
|
|
1028
871
|
this.composing = false;
|
|
1029
872
|
this.dataAttr = attr$1;
|
|
1030
|
-
this.root.
|
|
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');
|
|
1031
878
|
afterNextRender(() => {
|
|
1032
879
|
this.fieldRootContext?.setControlId(this.id());
|
|
1033
880
|
});
|
|
@@ -1044,7 +891,7 @@ class RdxAutocompleteInput {
|
|
|
1044
891
|
});
|
|
1045
892
|
inject(DestroyRef).onDestroy(() => {
|
|
1046
893
|
if (this.root.inputElement() === this.element) {
|
|
1047
|
-
this.root.
|
|
894
|
+
this.root.setInputElement(null);
|
|
1048
895
|
}
|
|
1049
896
|
});
|
|
1050
897
|
}
|
|
@@ -1052,21 +899,22 @@ class RdxAutocompleteInput {
|
|
|
1052
899
|
if (this.composing || event.isComposing) {
|
|
1053
900
|
return;
|
|
1054
901
|
}
|
|
1055
|
-
this.commitInput(event.target.value);
|
|
902
|
+
this.commitInput(event.target.value, event);
|
|
1056
903
|
}
|
|
1057
904
|
onCompositionEnd(event) {
|
|
1058
905
|
this.composing = false;
|
|
1059
|
-
this.commitInput(event.target.value);
|
|
906
|
+
this.commitInput(event.target.value, event);
|
|
1060
907
|
}
|
|
1061
|
-
commitInput(value) {
|
|
1062
|
-
|
|
1063
|
-
|
|
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);
|
|
1064
912
|
}
|
|
1065
913
|
this.root.setQuery(value);
|
|
1066
914
|
}
|
|
1067
|
-
onClick() {
|
|
915
|
+
onClick(event) {
|
|
1068
916
|
if (this.root.openOnInputClick()) {
|
|
1069
|
-
this.root.openForBrowse();
|
|
917
|
+
this.root.openForBrowse('input-press', event);
|
|
1070
918
|
}
|
|
1071
919
|
}
|
|
1072
920
|
onFocus() {
|
|
@@ -1091,7 +939,7 @@ class RdxAutocompleteInput {
|
|
|
1091
939
|
event.preventDefault();
|
|
1092
940
|
this.root.setKeyboardActive(true);
|
|
1093
941
|
if (!open) {
|
|
1094
|
-
this.root.openAndHighlight('first');
|
|
942
|
+
this.root.openAndHighlight('first', 'list-navigation', event);
|
|
1095
943
|
}
|
|
1096
944
|
else {
|
|
1097
945
|
this.root.moveDown();
|
|
@@ -1101,7 +949,7 @@ class RdxAutocompleteInput {
|
|
|
1101
949
|
event.preventDefault();
|
|
1102
950
|
this.root.setKeyboardActive(true);
|
|
1103
951
|
if (!open) {
|
|
1104
|
-
this.root.openAndHighlight('last');
|
|
952
|
+
this.root.openAndHighlight('last', 'list-navigation', event);
|
|
1105
953
|
}
|
|
1106
954
|
else {
|
|
1107
955
|
this.root.moveUp();
|
|
@@ -1121,6 +969,22 @@ class RdxAutocompleteInput {
|
|
|
1121
969
|
this.root.moveLeft();
|
|
1122
970
|
}
|
|
1123
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;
|
|
1124
988
|
case 'Enter':
|
|
1125
989
|
if (open) {
|
|
1126
990
|
const hasHighlight = this.root.virtualized()
|
|
@@ -1128,42 +992,52 @@ class RdxAutocompleteInput {
|
|
|
1128
992
|
: this.root.highlightedItem() !== null;
|
|
1129
993
|
if (hasHighlight) {
|
|
1130
994
|
event.preventDefault();
|
|
1131
|
-
this.root.selectHighlighted();
|
|
995
|
+
this.root.selectHighlighted(event);
|
|
1132
996
|
}
|
|
1133
997
|
else if (!this.root.inlineMode()) {
|
|
1134
998
|
// Non-inline: close and let native form submission proceed. Inline modes keep the
|
|
1135
999
|
// popup open on Enter without a highlight (matches Base UI).
|
|
1136
|
-
this.root.closePopup(true);
|
|
1000
|
+
this.root.closePopup(true, 'none', event);
|
|
1137
1001
|
}
|
|
1138
1002
|
}
|
|
1139
1003
|
break;
|
|
1140
1004
|
case 'Escape':
|
|
1141
1005
|
if (open) {
|
|
1142
1006
|
event.preventDefault();
|
|
1143
|
-
this.root.closePopup(true);
|
|
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();
|
|
1144
1014
|
}
|
|
1145
1015
|
break;
|
|
1146
1016
|
case 'Tab':
|
|
1147
|
-
|
|
1148
|
-
|
|
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);
|
|
1149
1022
|
}
|
|
1150
1023
|
break;
|
|
1151
1024
|
}
|
|
1152
1025
|
}
|
|
1153
1026
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1154
|
-
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()", "focus": "onFocus()", "blur": "onBlur()", "keydown": "onKeydown($event)", "compositionstart": "composing = true", "compositionend": "onCompositionEnd($event)" }, properties: { "attr.aria-autocomplete": "ariaAutocomplete()", "attr.id": "id()", "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:
|
|
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 }); }
|
|
1155
1028
|
}
|
|
1156
1029
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteInput, decorators: [{
|
|
1157
1030
|
type: Directive,
|
|
1158
1031
|
args: [{
|
|
1159
1032
|
selector: 'input[rdxAutocompleteInput]',
|
|
1160
1033
|
exportAs: 'rdxAutocompleteInput',
|
|
1161
|
-
hostDirectives: [RdxPopperAnchor,
|
|
1034
|
+
hostDirectives: [RdxPopperAnchor, RdxFloatingInsideElement],
|
|
1162
1035
|
host: {
|
|
1163
1036
|
role: 'combobox',
|
|
1164
1037
|
autocomplete: 'off',
|
|
1165
1038
|
'[attr.aria-autocomplete]': 'ariaAutocomplete()',
|
|
1166
1039
|
'[attr.id]': 'id()',
|
|
1040
|
+
'[attr.aria-haspopup]': 'root.grid() ? "grid" : "listbox"',
|
|
1167
1041
|
'[attr.aria-expanded]': 'root.open()',
|
|
1168
1042
|
'[attr.aria-controls]': 'root.listId',
|
|
1169
1043
|
'[attr.aria-labelledby]': 'root.labelId()',
|
|
@@ -1185,7 +1059,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1185
1059
|
'[attr.data-filled]': 'dataAttr(filledState())',
|
|
1186
1060
|
'[attr.data-focused]': 'dataAttr(focusedState())',
|
|
1187
1061
|
'(input)': 'onInput($event)',
|
|
1188
|
-
'(click)': 'onClick()',
|
|
1062
|
+
'(click)': 'onClick($event)',
|
|
1189
1063
|
'(focus)': 'onFocus()',
|
|
1190
1064
|
'(blur)': 'onBlur()',
|
|
1191
1065
|
'(keydown)': 'onKeydown($event)',
|
|
@@ -1224,6 +1098,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1224
1098
|
}]
|
|
1225
1099
|
}] });
|
|
1226
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
|
+
|
|
1227
1123
|
const itemContext = () => {
|
|
1228
1124
|
const item = inject(RdxAutocompleteItem);
|
|
1229
1125
|
return {
|
|
@@ -1246,8 +1142,12 @@ class RdxAutocompleteItem {
|
|
|
1246
1142
|
this.rootContext = injectComboboxRootContext();
|
|
1247
1143
|
this.element = inject(ElementRef).nativeElement;
|
|
1248
1144
|
this.id = injectId('rdx-autocomplete-item-');
|
|
1249
|
-
/**
|
|
1250
|
-
|
|
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' });
|
|
1251
1151
|
/** Explicit text matched against the query and written to the input. Defaults to text content. */
|
|
1252
1152
|
this.textValueInput = input('', { ...(ngDevMode ? { debugName: "textValueInput" } : /* istanbul ignore next */ {}), alias: 'textValue' });
|
|
1253
1153
|
/** Whether the option is disabled. */
|
|
@@ -1257,15 +1157,31 @@ class RdxAutocompleteItem {
|
|
|
1257
1157
|
this.virtualized = this.rootContext.virtualized;
|
|
1258
1158
|
this.autoTextValue = signal('', ...(ngDevMode ? [{ debugName: "autoTextValue" }] : /* istanbul ignore next */ []));
|
|
1259
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 */ []));
|
|
1260
1169
|
this.elementId = computed(() => this.virtualized() ? this.rootContext.itemId(this.index() ?? -1) : this.id, ...(ngDevMode ? [{ debugName: "elementId" }] : /* istanbul ignore next */ []));
|
|
1261
1170
|
this.ariaSetSize = computed(() => this.virtualized() ? this.rootContext.filteredItems().length : undefined, ...(ngDevMode ? [{ debugName: "ariaSetSize" }] : /* istanbul ignore next */ []));
|
|
1262
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 */ []));
|
|
1263
1176
|
this.isVisible = computed(() => (this.virtualized() ? true : this.rootContext.isVisible(this)), ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
|
|
1264
1177
|
this.isSelected = computed(() => this.rootContext.isSelected(this.value()), ...(ngDevMode ? [{ debugName: "isSelected" }] : /* istanbul ignore next */ []));
|
|
1265
1178
|
this.isHighlighted = computed(() => this.virtualized()
|
|
1266
1179
|
? this.rootContext.highlightedIndex() === this.index()
|
|
1267
1180
|
: this.rootContext.highlightedItem() === this, ...(ngDevMode ? [{ debugName: "isHighlighted" }] : /* istanbul ignore next */ []));
|
|
1268
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;
|
|
1269
1185
|
const destroyRef = inject(DestroyRef);
|
|
1270
1186
|
afterNextRender(() => {
|
|
1271
1187
|
if (this.virtualized()) {
|
|
@@ -1293,12 +1209,45 @@ class RdxAutocompleteItem {
|
|
|
1293
1209
|
this.element.scrollIntoView({ block: 'nearest' });
|
|
1294
1210
|
}
|
|
1295
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
|
+
});
|
|
1296
1219
|
}
|
|
1297
1220
|
onPointerDown(event) {
|
|
1221
|
+
if (event.button !== 0) {
|
|
1222
|
+
return;
|
|
1223
|
+
}
|
|
1298
1224
|
event.preventDefault();
|
|
1299
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();
|
|
1300
1249
|
}
|
|
1301
|
-
|
|
1250
|
+
commitSelection() {
|
|
1302
1251
|
if (this.virtualized()) {
|
|
1303
1252
|
this.rootContext.selectIndex(this.index() ?? -1);
|
|
1304
1253
|
}
|
|
@@ -1342,7 +1291,7 @@ class RdxAutocompleteItem {
|
|
|
1342
1291
|
this.rootContext.clearHighlight();
|
|
1343
1292
|
}
|
|
1344
1293
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1345
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxAutocompleteItem, isStandalone: true, selector: "[rdxAutocompleteItem]", inputs: {
|
|
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 }); }
|
|
1346
1295
|
}
|
|
1347
1296
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteItem, decorators: [{
|
|
1348
1297
|
type: Directive,
|
|
@@ -1351,25 +1300,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1351
1300
|
exportAs: 'rdxAutocompleteItem',
|
|
1352
1301
|
providers: [provideComboboxItemContext(itemContext)],
|
|
1353
1302
|
host: {
|
|
1354
|
-
role: '
|
|
1303
|
+
'[attr.role]': 'role()',
|
|
1355
1304
|
'[attr.id]': 'elementId()',
|
|
1356
|
-
|
|
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`).
|
|
1357
1307
|
'[attr.aria-disabled]': 'disabled() ? "true" : undefined',
|
|
1358
1308
|
'[attr.aria-setsize]': 'ariaSetSize()',
|
|
1359
1309
|
'[attr.aria-posinset]': 'ariaPosInSet()',
|
|
1360
|
-
'[attr.data-selected]': 'isSelected() ? "" : undefined',
|
|
1361
1310
|
'[attr.data-highlighted]': 'isHighlighted() ? "" : undefined',
|
|
1362
1311
|
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
1363
1312
|
'[hidden]': '!isVisible()',
|
|
1364
1313
|
'[attr.data-hidden]': 'isVisible() ? undefined : ""',
|
|
1365
1314
|
'(pointerdown)': 'onPointerDown($event)',
|
|
1366
|
-
'(mousedown)': '
|
|
1367
|
-
'(
|
|
1315
|
+
'(mousedown)': 'onMouseDown($event)',
|
|
1316
|
+
'(mouseup)': 'onMouseUp($event)',
|
|
1317
|
+
'(click)': 'onClick()',
|
|
1368
1318
|
'(pointermove)': 'onPointerMove()',
|
|
1369
1319
|
'(pointerleave)': 'onPointerLeave($event)'
|
|
1370
1320
|
}
|
|
1371
1321
|
}]
|
|
1372
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
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 }] }] } });
|
|
1373
1323
|
|
|
1374
1324
|
/**
|
|
1375
1325
|
* Renders only when its item is selected (e.g. a checkmark). Reuses the combobox item indicator,
|
|
@@ -1419,8 +1369,27 @@ class RdxAutocompleteList {
|
|
|
1419
1369
|
constructor() {
|
|
1420
1370
|
this.root = inject(RdxAutocompleteRoot);
|
|
1421
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
|
+
}
|
|
1422
1391
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1423
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompleteList, isStandalone: true, selector: "[rdxAutocompleteList]", host: { properties: { "attr.role": "root.grid() ? \"grid\" : \"listbox\"", "attr.id": "root.listId" } }, exportAs: ["rdxAutocompleteList"], ngImport: i0 }); }
|
|
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 }); }
|
|
1424
1393
|
}
|
|
1425
1394
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteList, decorators: [{
|
|
1426
1395
|
type: Directive,
|
|
@@ -1428,8 +1397,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1428
1397
|
selector: '[rdxAutocompleteList]',
|
|
1429
1398
|
exportAs: 'rdxAutocompleteList',
|
|
1430
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',
|
|
1431
1403
|
'[attr.role]': 'root.grid() ? "grid" : "listbox"',
|
|
1432
|
-
'[attr.id]': 'root.listId'
|
|
1404
|
+
'[attr.id]': 'root.listId',
|
|
1405
|
+
'(keydown)': 'onKeydown($event)'
|
|
1433
1406
|
}
|
|
1434
1407
|
}]
|
|
1435
1408
|
}] });
|
|
@@ -1445,13 +1418,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1445
1418
|
class RdxAutocompletePopup {
|
|
1446
1419
|
constructor() {
|
|
1447
1420
|
this.root = inject(RdxAutocompleteRoot);
|
|
1448
|
-
this.
|
|
1421
|
+
this.floatingContext = inject(RDX_FLOATING_ROOT_CONTEXT);
|
|
1422
|
+
this.registration = inject(RDX_FLOATING_REGISTRATION, { optional: true });
|
|
1449
1423
|
this.popper = injectPopperContentWrapperContext();
|
|
1450
1424
|
this.element = inject(ElementRef).nativeElement;
|
|
1451
|
-
|
|
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
|
+
});
|
|
1452
1433
|
const unregister = this.root.registerTransitionElement(this.element);
|
|
1453
|
-
|
|
1454
|
-
this.
|
|
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
|
+
});
|
|
1455
1452
|
// For the "input inside the popup" pattern, move focus to the input once positioned. Use
|
|
1456
1453
|
// `afterRenderEffect` (not `effect`): when `isPositioned` flips true the popup's final
|
|
1457
1454
|
// position/visibility is applied in the *following* render, so a synchronous `effect` would
|
|
@@ -1463,35 +1460,57 @@ class RdxAutocompletePopup {
|
|
|
1463
1460
|
}
|
|
1464
1461
|
const input = this.root.inputElement();
|
|
1465
1462
|
if (input && input.closest('[rdxAutocompletePopup]')) {
|
|
1466
|
-
|
|
1467
|
-
input.
|
|
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
|
+
}
|
|
1468
1472
|
}
|
|
1469
1473
|
});
|
|
1470
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
|
+
}
|
|
1471
1494
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1472
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxAutocompletePopup, isStandalone: true, selector: "[rdxAutocompletePopup]", host: { properties: { "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" } },
|
|
1473
|
-
provideRdxDismissableLayerConfig(() => ({
|
|
1474
|
-
disableOutsidePointerEvents: inject(RdxAutocompleteRoot).modal
|
|
1475
|
-
}))
|
|
1476
|
-
], exportAs: ["rdxAutocompletePopup"], hostDirectives: [{ directive: i1$1.RdxPopperContent }, { directive: i2.RdxDismissableLayer }], ngImport: i0 }); }
|
|
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 }); }
|
|
1477
1496
|
}
|
|
1478
1497
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePopup, decorators: [{
|
|
1479
1498
|
type: Directive,
|
|
1480
1499
|
args: [{
|
|
1481
1500
|
selector: '[rdxAutocompletePopup]',
|
|
1482
1501
|
exportAs: 'rdxAutocompletePopup',
|
|
1483
|
-
hostDirectives: [RdxPopperContent,
|
|
1484
|
-
providers: [
|
|
1485
|
-
provideRdxDismissableLayerConfig(() => ({
|
|
1486
|
-
disableOutsidePointerEvents: inject(RdxAutocompleteRoot).modal
|
|
1487
|
-
}))
|
|
1488
|
-
],
|
|
1502
|
+
hostDirectives: [RdxPopperContent, RdxFloatingNodeRegistration],
|
|
1489
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"',
|
|
1490
1508
|
'[attr.data-state]': 'root.open() ? "open" : "closed"',
|
|
1491
1509
|
'[attr.data-open]': 'root.open() ? "" : undefined',
|
|
1492
1510
|
'[attr.data-closed]': 'root.open() ? undefined : ""',
|
|
1493
1511
|
'[attr.data-starting-style]': 'root.transitionStatus() === "starting" ? "" : undefined',
|
|
1494
|
-
'[attr.data-ending-style]': 'root.transitionStatus() === "ending" ? "" : undefined'
|
|
1512
|
+
'[attr.data-ending-style]': 'root.transitionStatus() === "ending" ? "" : undefined',
|
|
1513
|
+
'(focusin)': 'onFocusIn($event)'
|
|
1495
1514
|
}
|
|
1496
1515
|
}]
|
|
1497
1516
|
}], ctorParameters: () => [] });
|
|
@@ -1529,9 +1548,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1529
1548
|
class RdxAutocompletePortalMisuseGuard {
|
|
1530
1549
|
constructor() {
|
|
1531
1550
|
if (isDevMode()) {
|
|
1532
|
-
|
|
1551
|
+
rdxDevError('autocomplete/portal-on-element', '`rdxAutocompletePortal` is now a structural directive. ' +
|
|
1533
1552
|
'Use `*rdxAutocompletePortal` on the positioner element or `<ng-template rdxAutocompletePortal>`. ' +
|
|
1534
|
-
'rdxAutocompletePortalPresence has been removed.
|
|
1553
|
+
'rdxAutocompletePortalPresence has been removed.', 'components/autocomplete');
|
|
1535
1554
|
}
|
|
1536
1555
|
}
|
|
1537
1556
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
@@ -1545,84 +1564,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1545
1564
|
}], ctorParameters: () => [] });
|
|
1546
1565
|
|
|
1547
1566
|
/**
|
|
1548
|
-
*
|
|
1549
|
-
* content wrapper directly (the same building block the combobox positioner uses) and re-exposes its
|
|
1550
|
-
* positioning inputs.
|
|
1551
|
-
*
|
|
1552
|
-
* @group Components
|
|
1553
|
-
*/
|
|
1554
|
-
class RdxAutocompletePositioner {
|
|
1555
|
-
constructor() {
|
|
1556
|
-
this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
1557
|
-
this.sideOffset = input(4, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1558
|
-
this.align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
1559
|
-
this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1560
|
-
this.arrowPadding = input(0, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1561
|
-
this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1562
|
-
this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
|
|
1563
|
-
this.collisionPadding = input(0, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
|
|
1564
|
-
this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
|
|
1565
|
-
this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1566
|
-
this.updatePositionStrategy = input('optimized', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
|
|
1567
|
-
}
|
|
1568
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1569
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxAutocompletePositioner, isStandalone: true, selector: "[rdxAutocompletePositioner]", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, sideOffset: { classPropertyName: "sideOffset", publicName: "sideOffset", isSignal: true, isRequired: false, transformFunction: null }, align: { classPropertyName: "align", publicName: "align", isSignal: true, isRequired: false, transformFunction: null }, alignOffset: { classPropertyName: "alignOffset", publicName: "alignOffset", isSignal: true, isRequired: false, transformFunction: null }, arrowPadding: { classPropertyName: "arrowPadding", publicName: "arrowPadding", isSignal: true, isRequired: false, transformFunction: null }, avoidCollisions: { classPropertyName: "avoidCollisions", publicName: "avoidCollisions", isSignal: true, isRequired: false, transformFunction: null }, collisionBoundary: { classPropertyName: "collisionBoundary", publicName: "collisionBoundary", isSignal: true, isRequired: false, transformFunction: null }, collisionPadding: { classPropertyName: "collisionPadding", publicName: "collisionPadding", isSignal: true, isRequired: false, transformFunction: null }, sticky: { classPropertyName: "sticky", publicName: "sticky", isSignal: true, isRequired: false, transformFunction: null }, hideWhenDetached: { classPropertyName: "hideWhenDetached", publicName: "hideWhenDetached", isSignal: true, isRequired: false, transformFunction: null }, updatePositionStrategy: { classPropertyName: "updatePositionStrategy", publicName: "updatePositionStrategy", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "style": "{\n 'boxSizing': 'border-box',\n '--radix-autocomplete-content-transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-autocomplete-content-available-width': 'var(--radix-popper-available-width)',\n '--radix-autocomplete-content-available-height': 'var(--radix-popper-available-height)',\n '--radix-autocomplete-trigger-width': 'var(--radix-popper-anchor-width)',\n '--radix-autocomplete-trigger-height': 'var(--radix-popper-anchor-height)'\n }" } }, exportAs: ["rdxAutocompletePositioner"], hostDirectives: [{ directive: i1$1.RdxPopperContentWrapper, inputs: ["side", "side", "sideOffset", "sideOffset", "align", "align", "alignOffset", "alignOffset", "arrowPadding", "arrowPadding", "avoidCollisions", "avoidCollisions", "collisionBoundary", "collisionBoundary", "collisionPadding", "collisionPadding", "sticky", "sticky", "hideWhenDetached", "hideWhenDetached", "updatePositionStrategy", "updatePositionStrategy"] }], ngImport: i0 }); }
|
|
1570
|
-
}
|
|
1571
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompletePositioner, decorators: [{
|
|
1572
|
-
type: Directive,
|
|
1573
|
-
args: [{
|
|
1574
|
-
selector: '[rdxAutocompletePositioner]',
|
|
1575
|
-
exportAs: 'rdxAutocompletePositioner',
|
|
1576
|
-
hostDirectives: [
|
|
1577
|
-
{
|
|
1578
|
-
directive: RdxPopperContentWrapper,
|
|
1579
|
-
inputs: [
|
|
1580
|
-
'side',
|
|
1581
|
-
'sideOffset',
|
|
1582
|
-
'align',
|
|
1583
|
-
'alignOffset',
|
|
1584
|
-
'arrowPadding',
|
|
1585
|
-
'avoidCollisions',
|
|
1586
|
-
'collisionBoundary',
|
|
1587
|
-
'collisionPadding',
|
|
1588
|
-
'sticky',
|
|
1589
|
-
'hideWhenDetached',
|
|
1590
|
-
'updatePositionStrategy'
|
|
1591
|
-
]
|
|
1592
|
-
}
|
|
1593
|
-
],
|
|
1594
|
-
host: {
|
|
1595
|
-
'[style]': `{
|
|
1596
|
-
'boxSizing': 'border-box',
|
|
1597
|
-
'--radix-autocomplete-content-transform-origin': 'var(--radix-popper-transform-origin)',
|
|
1598
|
-
'--radix-autocomplete-content-available-width': 'var(--radix-popper-available-width)',
|
|
1599
|
-
'--radix-autocomplete-content-available-height': 'var(--radix-popper-available-height)',
|
|
1600
|
-
'--radix-autocomplete-trigger-width': 'var(--radix-popper-anchor-width)',
|
|
1601
|
-
'--radix-autocomplete-trigger-height': 'var(--radix-popper-anchor-height)'
|
|
1602
|
-
}`
|
|
1603
|
-
}
|
|
1604
|
-
}]
|
|
1605
|
-
}], propDecorators: { side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], sideOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "sideOffset", required: false }] }], align: [{ type: i0.Input, args: [{ isSignal: true, alias: "align", required: false }] }], alignOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignOffset", required: false }] }], arrowPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "arrowPadding", required: false }] }], avoidCollisions: [{ type: i0.Input, args: [{ isSignal: true, alias: "avoidCollisions", required: false }] }], collisionBoundary: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionBoundary", required: false }] }], collisionPadding: [{ type: i0.Input, args: [{ isSignal: true, alias: "collisionPadding", required: false }] }], sticky: [{ type: i0.Input, args: [{ isSignal: true, alias: "sticky", required: false }] }], hideWhenDetached: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideWhenDetached", required: false }] }], updatePositionStrategy: [{ type: i0.Input, args: [{ isSignal: true, alias: "updatePositionStrategy", required: false }] }] } });
|
|
1606
|
-
|
|
1607
|
-
/**
|
|
1608
|
-
* A row in a grid-layout autocomplete list. Groups the items in one row so the root can navigate by
|
|
1609
|
-
* row (ArrowUp / ArrowDown) and within a row (ArrowLeft / ArrowRight). Only meaningful when the root
|
|
1610
|
-
* has `grid` enabled; the root resolves an item's row from its nearest `[rdxAutocompleteRow]` ancestor.
|
|
1567
|
+
* A visual separator between groups of suggestions (`role="separator"`). Reuses the combobox separator.
|
|
1611
1568
|
*
|
|
1612
1569
|
* @group Components
|
|
1613
1570
|
*/
|
|
1614
|
-
class
|
|
1615
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1616
|
-
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type:
|
|
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 }); }
|
|
1617
1574
|
}
|
|
1618
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type:
|
|
1575
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxAutocompleteSeparator, decorators: [{
|
|
1619
1576
|
type: Directive,
|
|
1620
1577
|
args: [{
|
|
1621
|
-
selector: '[
|
|
1622
|
-
exportAs: '
|
|
1623
|
-
|
|
1624
|
-
role: 'row'
|
|
1625
|
-
}
|
|
1578
|
+
selector: '[rdxAutocompleteSeparator]',
|
|
1579
|
+
exportAs: 'rdxAutocompleteSeparator',
|
|
1580
|
+
hostDirectives: [RdxComboboxSeparator]
|
|
1626
1581
|
}]
|
|
1627
1582
|
}] });
|
|
1628
1583
|
|
|
@@ -1646,8 +1601,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1646
1601
|
}] });
|
|
1647
1602
|
|
|
1648
1603
|
/**
|
|
1649
|
-
* Toggles the autocomplete popup.
|
|
1650
|
-
*
|
|
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.
|
|
1651
1606
|
*
|
|
1652
1607
|
* @group Components
|
|
1653
1608
|
*/
|
|
@@ -1714,6 +1669,7 @@ const _importsAutocomplete = [
|
|
|
1714
1669
|
RdxAutocompleteArrow,
|
|
1715
1670
|
RdxAutocompleteList,
|
|
1716
1671
|
RdxAutocompleteRow,
|
|
1672
|
+
RdxAutocompleteSeparator,
|
|
1717
1673
|
RdxAutocompleteItem,
|
|
1718
1674
|
RdxAutocompleteItemIndicator,
|
|
1719
1675
|
RdxAutocompleteGroup,
|
|
@@ -1740,6 +1696,7 @@ class RdxAutocompleteModule {
|
|
|
1740
1696
|
RdxAutocompleteArrow,
|
|
1741
1697
|
RdxAutocompleteList,
|
|
1742
1698
|
RdxAutocompleteRow,
|
|
1699
|
+
RdxAutocompleteSeparator,
|
|
1743
1700
|
RdxAutocompleteItem,
|
|
1744
1701
|
RdxAutocompleteItemIndicator,
|
|
1745
1702
|
RdxAutocompleteGroup,
|
|
@@ -1762,6 +1719,7 @@ class RdxAutocompleteModule {
|
|
|
1762
1719
|
RdxAutocompleteArrow,
|
|
1763
1720
|
RdxAutocompleteList,
|
|
1764
1721
|
RdxAutocompleteRow,
|
|
1722
|
+
RdxAutocompleteSeparator,
|
|
1765
1723
|
RdxAutocompleteItem,
|
|
1766
1724
|
RdxAutocompleteItemIndicator,
|
|
1767
1725
|
RdxAutocompleteGroup,
|
|
@@ -1782,5 +1740,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
1782
1740
|
* Generated bundle index. Do not edit.
|
|
1783
1741
|
*/
|
|
1784
1742
|
|
|
1785
|
-
export { RdxAutocompleteAnchor, RdxAutocompleteArrow, RdxAutocompleteBackdrop, RdxAutocompleteClear, RdxAutocompleteEmpty, RdxAutocompleteGroup, RdxAutocompleteGroupLabel, RdxAutocompleteIcon, RdxAutocompleteInput, RdxAutocompleteInputGroup, RdxAutocompleteItem, RdxAutocompleteItemIndicator, RdxAutocompleteLabel, RdxAutocompleteList, RdxAutocompleteModule, RdxAutocompletePopup, RdxAutocompletePortal, RdxAutocompletePortalMisuseGuard, RdxAutocompletePositioner, RdxAutocompleteRoot, RdxAutocompleteRow, RdxAutocompleteStatus, RdxAutocompleteTrigger, RdxAutocompleteValue, _importsAutocomplete };
|
|
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 };
|
|
1786
1744
|
//# sourceMappingURL=radix-ng-primitives-autocomplete.mjs.map
|