@radix-ng/primitives 1.0.0-beta.1 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +76 -6
- package/fesm2022/radix-ng-primitives-accordion.mjs +2 -2
- package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs +30 -24
- package/fesm2022/radix-ng-primitives-alert-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-autocomplete.mjs +1786 -0
- package/fesm2022/radix-ng-primitives-autocomplete.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-calendar.mjs +14 -1
- package/fesm2022/radix-ng-primitives-calendar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-checkbox.mjs +2 -2
- package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs +1 -1
- package/fesm2022/radix-ng-primitives-collapsible.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-combobox.mjs +1983 -0
- package/fesm2022/radix-ng-primitives-combobox.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-context-menu.mjs +1 -1
- package/fesm2022/radix-ng-primitives-context-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-core.mjs +480 -469
- package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-cropper.mjs +1 -1
- package/fesm2022/radix-ng-primitives-cropper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-date-field.mjs +11 -0
- package/fesm2022/radix-ng-primitives-date-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-dialog.mjs +44 -46
- package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-drawer.mjs +154 -64
- package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-editable.mjs +1 -1
- package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-field.mjs +86 -6
- package/fesm2022/radix-ng-primitives-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-fieldset.mjs +1 -1
- package/fesm2022/radix-ng-primitives-fieldset.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs +1 -1
- package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-form.mjs +207 -0
- package/fesm2022/radix-ng-primitives-form.mjs.map +1 -0
- package/fesm2022/radix-ng-primitives-input.mjs +85 -4
- package/fesm2022/radix-ng-primitives-input.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menu.mjs +44 -24
- package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs +1 -1
- package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs +1 -1
- package/fesm2022/radix-ng-primitives-meter.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs +39 -55
- package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-number-field.mjs +2 -2
- package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popover.mjs +36 -51
- package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-popper.mjs +12 -6
- package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-portal.mjs +107 -17
- package/fesm2022/radix-ng-primitives-portal.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-presence.mjs +262 -79
- package/fesm2022/radix-ng-primitives-presence.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-preview-card.mjs +37 -51
- package/fesm2022/radix-ng-primitives-preview-card.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs +1 -1
- package/fesm2022/radix-ng-primitives-progress.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs +1 -1
- package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-scroll-area.mjs +3 -3
- package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-select.mjs +469 -258
- package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs +1 -1
- package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-switch.mjs +3 -2
- package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs +1 -1
- package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-time-field.mjs +27 -3
- package/fesm2022/radix-ng-primitives-time-field.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs +1 -1
- package/fesm2022/radix-ng-primitives-toast.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs +1 -1
- package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-toolbar.mjs +2 -2
- package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
- package/fesm2022/radix-ng-primitives-tooltip.mjs +39 -42
- package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
- package/package.json +13 -1
- package/schematics/ng-add/index.js +57 -0
- package/schematics/ng-add/index.js.map +1 -1
- package/schematics/ng-add/schema.d.ts +1 -0
- package/schematics/ng-add/schema.json +6 -0
- package/types/radix-ng-primitives-alert-dialog.d.ts +17 -11
- package/types/radix-ng-primitives-autocomplete.d.ts +596 -0
- package/types/radix-ng-primitives-combobox.d.ts +1310 -0
- package/types/radix-ng-primitives-core.d.ts +148 -56
- package/types/radix-ng-primitives-dialog.d.ts +32 -25
- package/types/radix-ng-primitives-drawer.d.ts +49 -22
- package/types/radix-ng-primitives-field.d.ts +71 -2
- package/types/radix-ng-primitives-form.d.ts +124 -0
- package/types/radix-ng-primitives-input.d.ts +75 -5
- package/types/radix-ng-primitives-menu.d.ts +19 -10
- package/types/radix-ng-primitives-navigation-menu.d.ts +24 -26
- package/types/radix-ng-primitives-popover.d.ts +23 -23
- package/types/radix-ng-primitives-popper.d.ts +7 -1
- package/types/radix-ng-primitives-portal.d.ts +53 -8
- package/types/radix-ng-primitives-presence.d.ts +98 -17
- package/types/radix-ng-primitives-preview-card.d.ts +24 -23
- package/types/radix-ng-primitives-select.d.ts +294 -137
- package/types/radix-ng-primitives-tooltip.d.ts +26 -19
|
@@ -0,0 +1,1983 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Directive, inject, booleanAttribute, Injector, model, input, computed, numberAttribute, output, signal, effect, untracked, isDevMode, ElementRef, DestroyRef, afterNextRender, afterRenderEffect, NgModule } from '@angular/core';
|
|
3
|
+
import * as i1 from '@radix-ng/primitives/popper';
|
|
4
|
+
import { RdxPopperAnchor, RdxPopperArrow, RdxPopper, injectPopperContentWrapperContext, RdxPopperContent, RdxPopperContentWrapper } from '@radix-ng/primitives/popper';
|
|
5
|
+
import { NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
|
+
import { createContext, useTransitionStatus, injectId, useFilter, useListHighlight, isItemEqualToValue, isNullish, itemToStringLabel, useScrollLock } from '@radix-ng/primitives/core';
|
|
7
|
+
import * as i1$1 from '@radix-ng/primitives/dismissable-layer';
|
|
8
|
+
import { RdxDismissableLayerBranch, RdxDismissableLayer, provideRdxDismissableLayerConfig } from '@radix-ng/primitives/dismissable-layer';
|
|
9
|
+
import { injectFieldRootContext } from '@radix-ng/primitives/field';
|
|
10
|
+
import * as i1$2 from '@radix-ng/primitives/portal';
|
|
11
|
+
import { RdxPortalPresence } from '@radix-ng/primitives/portal';
|
|
12
|
+
import { provideRdxPresenceContext } from '@radix-ng/primitives/presence';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Optional positioning anchor for the popup. Put it on the element the popup should align to — for
|
|
16
|
+
* example a control that wraps chips + input in `multiple` mode. When present it takes precedence
|
|
17
|
+
* over the input (the popper resolves the first `RdxPopperAnchor` in DOM order, and this part sits
|
|
18
|
+
* above the input). When absent, the input itself is the anchor, which is ideal when the input fills
|
|
19
|
+
* the control.
|
|
20
|
+
*
|
|
21
|
+
* @group Components
|
|
22
|
+
*/
|
|
23
|
+
class RdxComboboxAnchor {
|
|
24
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxAnchor, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
25
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxAnchor, isStandalone: true, selector: "[rdxComboboxAnchor]", exportAs: ["rdxComboboxAnchor"], hostDirectives: [{ directive: i1.RdxPopperAnchor }], ngImport: i0 }); }
|
|
26
|
+
}
|
|
27
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxAnchor, decorators: [{
|
|
28
|
+
type: Directive,
|
|
29
|
+
args: [{
|
|
30
|
+
selector: '[rdxComboboxAnchor]',
|
|
31
|
+
exportAs: 'rdxComboboxAnchor',
|
|
32
|
+
hostDirectives: [RdxPopperAnchor]
|
|
33
|
+
}]
|
|
34
|
+
}] });
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An optional arrow that points from the popup to the anchor. Composes the popper arrow, which keeps
|
|
38
|
+
* it aligned as the popup flips sides.
|
|
39
|
+
*
|
|
40
|
+
* @group Components
|
|
41
|
+
*/
|
|
42
|
+
class RdxComboboxArrow {
|
|
43
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxArrow, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
44
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxArrow, isStandalone: true, selector: "[rdxComboboxArrow]", exportAs: ["rdxComboboxArrow"], hostDirectives: [{ directive: i1.RdxPopperArrow, inputs: ["width", "width", "height", "height"] }], ngImport: i0 }); }
|
|
45
|
+
}
|
|
46
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxArrow, decorators: [{
|
|
47
|
+
type: Directive,
|
|
48
|
+
args: [{
|
|
49
|
+
selector: '[rdxComboboxArrow]',
|
|
50
|
+
exportAs: 'rdxComboboxArrow',
|
|
51
|
+
hostDirectives: [{ directive: RdxPopperArrow, inputs: ['width', 'height'] }]
|
|
52
|
+
}]
|
|
53
|
+
}] });
|
|
54
|
+
|
|
55
|
+
const context = () => {
|
|
56
|
+
const root = inject(RdxComboboxRoot);
|
|
57
|
+
return {
|
|
58
|
+
listId: root.listId,
|
|
59
|
+
labelId: root.labelId,
|
|
60
|
+
setLabelId: (id) => root.labelId.set(id),
|
|
61
|
+
dir: root.dir,
|
|
62
|
+
value: root.value,
|
|
63
|
+
inputValue: root.inputValue,
|
|
64
|
+
open: root.open,
|
|
65
|
+
multiple: root.multiple,
|
|
66
|
+
selectionMode: root.mode,
|
|
67
|
+
disabledState: root.disabledState,
|
|
68
|
+
readonly: root.readonly,
|
|
69
|
+
requiredState: root.requiredState,
|
|
70
|
+
openOnInputClick: root.openOnInputClick,
|
|
71
|
+
modal: root.modal,
|
|
72
|
+
virtualized: root.virtualized,
|
|
73
|
+
filteredItems: root.filteredItems,
|
|
74
|
+
highlightedItem: root.highlightedItem,
|
|
75
|
+
highlightedIndex: root.highlightedIndex.asReadonly(),
|
|
76
|
+
activeId: root.activeId,
|
|
77
|
+
itemId: (index) => root.itemId(index),
|
|
78
|
+
isKeyboardActive: () => root.isKeyboardActive(),
|
|
79
|
+
setKeyboardActive: (value) => root.setKeyboardActive(value),
|
|
80
|
+
transitionStatus: root.transitionStatus,
|
|
81
|
+
registerTransitionElement: root.registerTransitionElement,
|
|
82
|
+
visibleCount: root.visibleCount,
|
|
83
|
+
isVisible: (item) => root.isVisible(item),
|
|
84
|
+
isSelected: (value) => root.isSelected(value),
|
|
85
|
+
registerItem: (item) => root.registerItem(item),
|
|
86
|
+
unregisterItem: (item) => root.unregisterItem(item),
|
|
87
|
+
highlight: root.highlight,
|
|
88
|
+
highlightNext: () => root.highlightNext('keyboard'),
|
|
89
|
+
highlightPrevious: () => root.highlightPrevious('keyboard'),
|
|
90
|
+
highlightFirst: () => root.highlightFirst('keyboard'),
|
|
91
|
+
highlightLast: () => root.highlightLast('keyboard'),
|
|
92
|
+
highlightIndex: (index, reason) => root.highlightIndex(index, reason),
|
|
93
|
+
setHighlight: (item, reason) => root.setHighlight(item, reason),
|
|
94
|
+
clearHighlight: () => root.clearHighlightState(),
|
|
95
|
+
highlightItemOnHover: root.highlightItemOnHover,
|
|
96
|
+
keepHighlight: root.keepHighlight,
|
|
97
|
+
inputElement: root.inputElement.asReadonly(),
|
|
98
|
+
setInputElement: (el) => root.inputElement.set(el),
|
|
99
|
+
registerTrigger: (el) => (root.triggerElement = el),
|
|
100
|
+
focusInput: () => root.focusInput(),
|
|
101
|
+
openPopup: () => root.setOpen(true),
|
|
102
|
+
openForBrowse: () => root.openForBrowse(),
|
|
103
|
+
closePopup: (revert = true) => root.closePopup(revert),
|
|
104
|
+
setInputValue: (value) => root.setInputValue(value),
|
|
105
|
+
openAndHighlight: (edge) => root.openAndHighlight(edge),
|
|
106
|
+
select: (item) => root.handleSelect(item),
|
|
107
|
+
selectIndex: (index) => root.selectIndex(index),
|
|
108
|
+
selectHighlighted: () => root.selectHighlighted(),
|
|
109
|
+
clearSelection: () => root.clearSelection(),
|
|
110
|
+
removeValue: (value) => root.removeValue(value),
|
|
111
|
+
removeLastValue: () => root.removeLastValue(),
|
|
112
|
+
registerChipsNav: (fn) => root.registerChipsNav(fn),
|
|
113
|
+
focusLastChip: () => root.focusLastChip(),
|
|
114
|
+
labelFor: (value) => root.labelFor(value),
|
|
115
|
+
markAsTouched: () => root.markAsTouched()
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const [injectComboboxRootContext, provideComboboxRootContext] = createContext('RdxComboboxRootContext', 'components/combobox');
|
|
119
|
+
/**
|
|
120
|
+
* `autoHighlight` transform: pass the `'always'` / `'input-change'` string modes through verbatim,
|
|
121
|
+
* coerce everything else as a boolean attribute (so the bare `autoHighlight` attribute reads `true`).
|
|
122
|
+
*
|
|
123
|
+
* Kept as a named module-level function rather than an inline `transform` arrow: compodoc 1.2.1
|
|
124
|
+
* (the metadata source for the API contract and Storybook ArgTypes) hangs parsing an inline arrow
|
|
125
|
+
* combined with explicit generic union type arguments on `input()`. A plain function reference sidesteps it.
|
|
126
|
+
*/
|
|
127
|
+
function coerceAutoHighlight(value) {
|
|
128
|
+
return value === 'always' || value === 'input-change' ? value : booleanAttribute(value);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Root of a Combobox — a filterable select. Owns selection, input text, open state, filtering, and
|
|
132
|
+
* highlight-model navigation, and exposes them to the parts through {@link RdxComboboxRootContext}.
|
|
133
|
+
* Implements `ControlValueAccessor` for forms.
|
|
134
|
+
*
|
|
135
|
+
* @group Components
|
|
136
|
+
*/
|
|
137
|
+
class RdxComboboxRoot {
|
|
138
|
+
constructor() {
|
|
139
|
+
this.injector = inject(Injector);
|
|
140
|
+
/** Selected value(s). A single value in single mode, an array in `multiple` mode. */
|
|
141
|
+
this.value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
142
|
+
/** Initial value when uncontrolled. */
|
|
143
|
+
this.defaultValue = input(...(ngDevMode ? [undefined, { debugName: "defaultValue" }] : /* istanbul ignore next */ []));
|
|
144
|
+
/** The text currently in the input. */
|
|
145
|
+
this.inputValue = model('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
|
|
146
|
+
/** Whether the popup is open. */
|
|
147
|
+
this.open = model(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
148
|
+
/** Initial open state when uncontrolled. */
|
|
149
|
+
this.defaultOpen = input(false, { ...(ngDevMode ? { debugName: "defaultOpen" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
150
|
+
/** Whether multiple values can be selected. Shorthand for `selectionMode="multiple"`. */
|
|
151
|
+
this.multipleInput = input(false, { ...(ngDevMode ? { debugName: "multipleInput" } : /* istanbul ignore next */ {}), alias: 'multiple', transform: booleanAttribute });
|
|
152
|
+
/**
|
|
153
|
+
* Selection behavior. `'single'` / `'multiple'` commit a value; `'none'` filters without
|
|
154
|
+
* selecting (a pure search/command UI). Defaults from the `multiple` shorthand.
|
|
155
|
+
*/
|
|
156
|
+
this.selectionMode = input(...(ngDevMode ? [undefined, { debugName: "selectionMode" }] : /* istanbul ignore next */ []));
|
|
157
|
+
/** Resolved selection mode. */
|
|
158
|
+
this.mode = computed(() => this.selectionMode() ?? (this.multipleInput() ? 'multiple' : 'single'), ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
159
|
+
/** Whether the combobox is in multiple-selection mode. */
|
|
160
|
+
this.multiple = computed(() => this.mode() === 'multiple', ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
161
|
+
/** In `'none'` mode, whether pressing an item fills the input with its label. */
|
|
162
|
+
this.fillInputOnItemPress = input(true, { ...(ngDevMode ? { debugName: "fillInputOnItemPress" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
163
|
+
/** Text direction. */
|
|
164
|
+
this.dir = input('ltr', ...(ngDevMode ? [{ debugName: "dir" }] : /* istanbul ignore next */ []));
|
|
165
|
+
/** Whether the combobox is disabled. */
|
|
166
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
167
|
+
/** Whether the combobox is read-only. */
|
|
168
|
+
this.readonly = input(false, { ...(ngDevMode ? { debugName: "readonly" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
169
|
+
/** Whether a value is required (for forms). */
|
|
170
|
+
this.required = input(false, { ...(ngDevMode ? { debugName: "required" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
171
|
+
/** Whether keyboard navigation wraps at the list boundaries. */
|
|
172
|
+
this.loopFocus = input(true, { ...(ngDevMode ? { debugName: "loopFocus" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
173
|
+
/**
|
|
174
|
+
* Auto-highlight behavior:
|
|
175
|
+
* - `false` (default): never auto-highlight;
|
|
176
|
+
* - `true` (also the bare `autoHighlight` attribute) / `'input-change'`: highlight the first match as the query changes;
|
|
177
|
+
* - `'always'`: keep the first navigable item highlighted whenever the popup is open.
|
|
178
|
+
*/
|
|
179
|
+
this.autoHighlight = input(false, { ...(ngDevMode ? { debugName: "autoHighlight" } : /* istanbul ignore next */ {}), transform: coerceAutoHighlight });
|
|
180
|
+
/** Resolved auto-highlight mode. */
|
|
181
|
+
this.autoHighlightMode = computed(() => {
|
|
182
|
+
const value = this.autoHighlight();
|
|
183
|
+
if (value === 'always') {
|
|
184
|
+
return 'always';
|
|
185
|
+
}
|
|
186
|
+
if (value === true || value === 'input-change') {
|
|
187
|
+
return 'input-change';
|
|
188
|
+
}
|
|
189
|
+
return 'off';
|
|
190
|
+
}, ...(ngDevMode ? [{ debugName: "autoHighlightMode" }] : /* istanbul ignore next */ []));
|
|
191
|
+
/**
|
|
192
|
+
* Whether moving the pointer over an item highlights it. `true` (default) paints `data-highlighted`
|
|
193
|
+
* on hover; `false` suppresses hover-driven highlight entirely, letting CSS `:hover` stay distinct
|
|
194
|
+
* from the `data-highlighted` (keyboard) state. Clicking an item still selects it.
|
|
195
|
+
*/
|
|
196
|
+
this.highlightItemOnHover = input(true, { ...(ngDevMode ? { debugName: "highlightItemOnHover" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
197
|
+
/**
|
|
198
|
+
* Whether a pointer-driven highlight is kept when the cursor leaves the list. `false` (default)
|
|
199
|
+
* clears the highlight on pointer-leave; `true` retains the last hovered item highlighted. Keyboard
|
|
200
|
+
* navigation and auto-highlight are unaffected.
|
|
201
|
+
*/
|
|
202
|
+
this.keepHighlight = input(false, { ...(ngDevMode ? { debugName: "keepHighlight" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
203
|
+
/** Whether clicking the input opens the popup. */
|
|
204
|
+
this.openOnInputClick = input(true, { ...(ngDevMode ? { debugName: "openOnInputClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
205
|
+
/** Whether the popup is modal: locks page scroll and makes outside content inert while open. */
|
|
206
|
+
this.modal = input(false, { ...(ngDevMode ? { debugName: "modal" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
207
|
+
/** Whether selecting an item requests submit of the closest form. */
|
|
208
|
+
this.submitOnItemClick = input(false, { ...(ngDevMode ? { debugName: "submitOnItemClick" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
209
|
+
/**
|
|
210
|
+
* Filter applied to items against the input query.
|
|
211
|
+
* - `undefined` (default): locale-aware "contains" via {@link useFilter};
|
|
212
|
+
* - a function: custom matching;
|
|
213
|
+
* - `null`: built-in filtering disabled (the consumer controls which items exist).
|
|
214
|
+
*/
|
|
215
|
+
this.filter = input(undefined, ...(ngDevMode ? [{ debugName: "filter" }] : /* istanbul ignore next */ []));
|
|
216
|
+
/** Maximum number of matching items to show. `-1` (default) means no limit. */
|
|
217
|
+
this.limit = input(-1, { ...(ngDevMode ? { debugName: "limit" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
218
|
+
/**
|
|
219
|
+
* The full set of item values, used as the source of truth for filtering and navigation in
|
|
220
|
+
* {@link virtualized} mode (where only a window of `RdxComboboxItem` is mounted). The filtered
|
|
221
|
+
* subset is exposed as {@link filteredItems} for an external virtualizer to render.
|
|
222
|
+
*/
|
|
223
|
+
this.items = input(...(ngDevMode ? [undefined, { debugName: "items" }] : /* istanbul ignore next */ []));
|
|
224
|
+
/**
|
|
225
|
+
* Whether the list is externally virtualized — only a window of items is rendered, so navigation
|
|
226
|
+
* runs over {@link items}/{@link filteredItems} by index instead of over mounted DOM elements.
|
|
227
|
+
* Requires {@link items}, and {@link itemToStringLabel} for selection labels/filter text. Disabled
|
|
228
|
+
* items outside the rendered window are not skipped by keyboard navigation.
|
|
229
|
+
*/
|
|
230
|
+
this.virtualized = input(false, { ...(ngDevMode ? { debugName: "virtualized" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
231
|
+
/** How item values are compared for equality (function or object key). */
|
|
232
|
+
this.by = input(...(ngDevMode ? [undefined, { debugName: "by" }] : /* istanbul ignore next */ []));
|
|
233
|
+
/** Converts a value to its display label. Defaults to the matching item's text. */
|
|
234
|
+
this.itemToStringLabel = input(...(ngDevMode ? [undefined, { debugName: "itemToStringLabel" }] : /* istanbul ignore next */ []));
|
|
235
|
+
/** Emits when the selection changes. */
|
|
236
|
+
this.onValueChange = output();
|
|
237
|
+
/** Emits when the input text changes. */
|
|
238
|
+
this.onInputValueChange = output();
|
|
239
|
+
/** Emits when the popup opens or closes. */
|
|
240
|
+
this.onOpenChange = output();
|
|
241
|
+
/**
|
|
242
|
+
* Emits as the highlight moves, with the item's value, its index in {@link filteredItems}, and the
|
|
243
|
+
* reason. In virtualized mode, use `index` to call the virtualizer's `scrollToIndex`.
|
|
244
|
+
*/
|
|
245
|
+
this.onItemHighlighted = output();
|
|
246
|
+
/** Emits after the open/close transition (including any exit animation) finishes. */
|
|
247
|
+
this.onOpenChangeComplete = output();
|
|
248
|
+
this.transition = useTransitionStatus((open) => this.onOpenChangeComplete.emit(open));
|
|
249
|
+
/** Open/close transition phase, for `data-starting-style` / `data-ending-style`. */
|
|
250
|
+
this.transitionStatus = this.transition.status;
|
|
251
|
+
/** Registers the popup element whose animation determines transition completion. */
|
|
252
|
+
this.registerTransitionElement = this.transition.registerElement;
|
|
253
|
+
this.listId = injectId('rdx-combobox-list-');
|
|
254
|
+
this.labelId = signal(undefined, ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
255
|
+
this.inputElement = signal(null, ...(ngDevMode ? [{ debugName: "inputElement" }] : /* istanbul ignore next */ []));
|
|
256
|
+
this.cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
|
|
257
|
+
this.disabledState = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
258
|
+
this.requiredState = computed(() => this.required(), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
259
|
+
this.defaultFilter = useFilter();
|
|
260
|
+
/**
|
|
261
|
+
* Whether the input text is a fresh user query rather than the current selection's label. While
|
|
262
|
+
* `false` (just opened, or showing a selected label), the list is unfiltered so the user can
|
|
263
|
+
* browse; it flips `true` on the first keystroke.
|
|
264
|
+
*/
|
|
265
|
+
this.typed = signal(false, ...(ngDevMode ? [{ debugName: "typed" }] : /* istanbul ignore next */ []));
|
|
266
|
+
this._items = signal([], ...(ngDevMode ? [{ debugName: "_items" }] : /* istanbul ignore next */ []));
|
|
267
|
+
/** Registered items, sorted into DOM order. */
|
|
268
|
+
this.orderedItems = computed(() => {
|
|
269
|
+
const items = [...this._items()];
|
|
270
|
+
return items.sort((a, b) => {
|
|
271
|
+
const position = a.element.compareDocumentPosition(b.element);
|
|
272
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
273
|
+
return -1;
|
|
274
|
+
}
|
|
275
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
276
|
+
return 1;
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
});
|
|
280
|
+
}, ...(ngDevMode ? [{ debugName: "orderedItems" }] : /* istanbul ignore next */ []));
|
|
281
|
+
/** Matching items in DOM order, capped at `limit`. The set of items the list shows. */
|
|
282
|
+
this.visibleItems = computed(() => {
|
|
283
|
+
const matching = this.orderedItems().filter((item) => this.matchesFilter(item));
|
|
284
|
+
const limit = this.limit();
|
|
285
|
+
return limit >= 0 ? matching.slice(0, limit) : matching;
|
|
286
|
+
}, ...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
|
|
287
|
+
this.visibleSet = computed(() => new Set(this.visibleItems()), ...(ngDevMode ? [{ debugName: "visibleSet" }] : /* istanbul ignore next */ []));
|
|
288
|
+
/**
|
|
289
|
+
* The filtered item values an external virtualizer should render (the analogue of Base UI's
|
|
290
|
+
* `useFilteredItems`). Driven by {@link items} when provided (virtualized mode), capped by
|
|
291
|
+
* {@link limit}; otherwise mirrors the values of the mounted {@link visibleItems}.
|
|
292
|
+
*/
|
|
293
|
+
this.filteredItems = computed(() => {
|
|
294
|
+
const data = this.items();
|
|
295
|
+
if (data === undefined) {
|
|
296
|
+
return this.visibleItems().map((item) => item.value());
|
|
297
|
+
}
|
|
298
|
+
const limit = this.limit();
|
|
299
|
+
const cap = (arr) => (limit >= 0 ? arr.slice(0, limit) : arr);
|
|
300
|
+
const filter = this.filter();
|
|
301
|
+
if (filter === null) {
|
|
302
|
+
return cap(data);
|
|
303
|
+
}
|
|
304
|
+
const query = this.typed() ? (this.inputValue() ?? '') : '';
|
|
305
|
+
if (!query) {
|
|
306
|
+
return cap(data);
|
|
307
|
+
}
|
|
308
|
+
const matcher = filter ?? this.defaultFilter.contains;
|
|
309
|
+
return cap(data.filter((value) => matcher(this.textFor(value), query)));
|
|
310
|
+
}, ...(ngDevMode ? [{ debugName: "filteredItems" }] : /* istanbul ignore next */ []));
|
|
311
|
+
this.visibleCount = computed(() => this.virtualized() ? this.filteredItems().length : this.visibleItems().length, ...(ngDevMode ? [{ debugName: "visibleCount" }] : /* istanbul ignore next */ []));
|
|
312
|
+
this.highlight = useListHighlight({
|
|
313
|
+
items: this.orderedItems,
|
|
314
|
+
isNavigable: (item) => this.isVisible(item) && !item.disabled(),
|
|
315
|
+
getId: (item) => item.id,
|
|
316
|
+
loop: this.loopFocus,
|
|
317
|
+
injector: this.injector
|
|
318
|
+
});
|
|
319
|
+
this.highlightedItem = this.highlight.highlightedItem;
|
|
320
|
+
/** Highlighted index into {@link filteredItems} in virtualized mode (`-1` when cleared). */
|
|
321
|
+
this.highlightedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "highlightedIndex" }] : /* istanbul ignore next */ []));
|
|
322
|
+
/** Why the highlight last moved; read when emitting {@link onItemHighlighted}. */
|
|
323
|
+
this.highlightReason = signal('none', ...(ngDevMode ? [{ debugName: "highlightReason" }] : /* istanbul ignore next */ []));
|
|
324
|
+
this.activeId = computed(() => {
|
|
325
|
+
if (this.virtualized()) {
|
|
326
|
+
const index = this.highlightedIndex();
|
|
327
|
+
return index >= 0 ? this.itemId(index) : undefined;
|
|
328
|
+
}
|
|
329
|
+
return this.highlight.activeId();
|
|
330
|
+
}, ...(ngDevMode ? [{ debugName: "activeId" }] : /* istanbul ignore next */ []));
|
|
331
|
+
/** Edge to highlight once the list has mounted (items register asynchronously after opening). */
|
|
332
|
+
this.pendingHighlightEdge = signal(null, ...(ngDevMode ? [{ debugName: "pendingHighlightEdge" }] : /* istanbul ignore next */ []));
|
|
333
|
+
// Tracks whether the last interaction was the keyboard, so the highlight doesn't jump to an item
|
|
334
|
+
// the cursor happens to rest on when arrow-key navigation scrolls the list under a still pointer.
|
|
335
|
+
this.keyboardActive = false;
|
|
336
|
+
/** The trigger element, used as a focus fallback when the input lives inside the popup. */
|
|
337
|
+
this.triggerElement = null;
|
|
338
|
+
this.chipsFocusLast = null;
|
|
339
|
+
// Apply uncontrolled defaults once.
|
|
340
|
+
effect(() => {
|
|
341
|
+
const initial = this.defaultValue();
|
|
342
|
+
if (initial !== undefined && untracked(this.value) === null) {
|
|
343
|
+
this.value.set(initial);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
effect(() => {
|
|
347
|
+
if (untracked(this.open) === false && this.defaultOpen()) {
|
|
348
|
+
this.open.set(true);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
// Emit open changes and drive the open/close transition (skip the initial run).
|
|
352
|
+
let previousOpen = untracked(this.open);
|
|
353
|
+
effect(() => {
|
|
354
|
+
const open = this.open();
|
|
355
|
+
if (open === previousOpen) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
previousOpen = open;
|
|
359
|
+
untracked(() => {
|
|
360
|
+
this.onOpenChange.emit(open);
|
|
361
|
+
this.transition.start(open);
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
// Emit highlight changes (skip the initial run). Tracks both the DOM-ref highlight and the
|
|
365
|
+
// virtualized index; only one is active per mode, so the other never fires spuriously.
|
|
366
|
+
let highlightInitialized = false;
|
|
367
|
+
effect(() => {
|
|
368
|
+
const item = this.highlightedItem();
|
|
369
|
+
const index = this.highlightedIndex();
|
|
370
|
+
if (!highlightInitialized) {
|
|
371
|
+
highlightInitialized = true;
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
untracked(() => {
|
|
375
|
+
const reason = this.highlightReason();
|
|
376
|
+
if (this.virtualized()) {
|
|
377
|
+
const value = index >= 0 ? (this.filteredItems()[index] ?? null) : null;
|
|
378
|
+
this.onItemHighlighted.emit({ value, index, reason });
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
const value = item ? item.value() : null;
|
|
382
|
+
const itemIndex = item ? this.visibleItems().indexOf(item) : -1;
|
|
383
|
+
this.onItemHighlighted.emit({ value, index: itemIndex, reason });
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
// Apply a deferred open-edge highlight once items (DOM refs) or filtered data have registered.
|
|
388
|
+
effect(() => {
|
|
389
|
+
const edge = this.pendingHighlightEdge();
|
|
390
|
+
const count = this.virtualized() ? this.filteredItems().length : this.orderedItems().length;
|
|
391
|
+
if (!this.open() || edge === null || count === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
untracked(() => {
|
|
395
|
+
// Programmatic move — reset the reason in both modes so the emit reports 'none', not a
|
|
396
|
+
// stale 'keyboard'/'pointer' left by the previous user interaction.
|
|
397
|
+
this.highlightReason.set('none');
|
|
398
|
+
if (this.virtualized()) {
|
|
399
|
+
this.highlightedIndex.set(edge === 'first' ? 0 : count - 1);
|
|
400
|
+
}
|
|
401
|
+
else if (edge === 'first') {
|
|
402
|
+
this.highlight.first();
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
this.highlight.last();
|
|
406
|
+
}
|
|
407
|
+
this.pendingHighlightEdge.set(null);
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
// autoHighlight 'always': keep the first navigable item highlighted whenever the popup is
|
|
411
|
+
// open. `visibleCount` re-runs this when filtering changes; the current highlight is read
|
|
412
|
+
// untracked to re-establish a highlight only after the self-heal clears it (no loop).
|
|
413
|
+
effect(() => {
|
|
414
|
+
this.orderedItems();
|
|
415
|
+
this.visibleCount();
|
|
416
|
+
if (this.autoHighlightMode() === 'always' && this.open()) {
|
|
417
|
+
untracked(() => {
|
|
418
|
+
if (this.virtualized()) {
|
|
419
|
+
// Re-seed when the index is cleared OR has fallen out of range, so this works
|
|
420
|
+
// regardless of whether the self-heal effect ran first (no ordering dependency).
|
|
421
|
+
const length = this.filteredItems().length;
|
|
422
|
+
const index = this.highlightedIndex();
|
|
423
|
+
if ((index < 0 || index >= length) && length > 0) {
|
|
424
|
+
this.highlightReason.set('none');
|
|
425
|
+
this.highlightedIndex.set(0);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
else if (this.highlightedItem() === null) {
|
|
429
|
+
this.highlightReason.set('none');
|
|
430
|
+
this.highlight.first();
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
// Virtualized self-heal: clear a highlight that filtering has pushed out of range, so
|
|
436
|
+
// `activeId` never references an index past the end of the filtered list.
|
|
437
|
+
effect(() => {
|
|
438
|
+
if (!this.virtualized()) {
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
const length = this.filteredItems().length;
|
|
442
|
+
untracked(() => {
|
|
443
|
+
const index = this.highlightedIndex();
|
|
444
|
+
if (index >= length && index !== -1) {
|
|
445
|
+
this.highlightReason.set('none');
|
|
446
|
+
this.highlightedIndex.set(-1);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
// Virtualized object values can't be labelled from the DOM (items aren't registered) — without
|
|
451
|
+
// `itemToStringLabel`, selection/revert fall back to a generic label. Warn once in dev.
|
|
452
|
+
if (isDevMode()) {
|
|
453
|
+
let warned = false;
|
|
454
|
+
effect(() => {
|
|
455
|
+
if (warned || !this.virtualized() || this.itemToStringLabel()) {
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
if (this.items()?.some((value) => value !== null && typeof value === 'object')) {
|
|
459
|
+
warned = true;
|
|
460
|
+
console.warn('[rdxComboboxRoot] `virtualized` with object item values needs `itemToStringLabel` ' +
|
|
461
|
+
'to render correct selection labels; falling back to a generic label.');
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
/** Opens the popup for browsing (resets the query to "pristine" and selects the input text). */
|
|
467
|
+
openForBrowse() {
|
|
468
|
+
if (!this.open()) {
|
|
469
|
+
this.typed.set(false);
|
|
470
|
+
}
|
|
471
|
+
this.setOpen(true);
|
|
472
|
+
this.selectInputText();
|
|
473
|
+
if (this.autoHighlightMode() === 'always') {
|
|
474
|
+
this.pendingHighlightEdge.set('first');
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/** Opens the popup and highlights the given edge once the list mounts. */
|
|
478
|
+
openAndHighlight(edge) {
|
|
479
|
+
if (!this.open()) {
|
|
480
|
+
this.typed.set(false);
|
|
481
|
+
}
|
|
482
|
+
this.setOpen(true);
|
|
483
|
+
this.selectInputText();
|
|
484
|
+
this.pendingHighlightEdge.set(edge);
|
|
485
|
+
}
|
|
486
|
+
/** Whether the item matches the active query (ignores the `limit` cap). */
|
|
487
|
+
matchesFilter(item) {
|
|
488
|
+
const filter = this.filter();
|
|
489
|
+
if (filter === null) {
|
|
490
|
+
return true;
|
|
491
|
+
}
|
|
492
|
+
// Until the user types a fresh query, show the whole list (the input may still hold the
|
|
493
|
+
// selected item's label, which must not filter everything down to just that item).
|
|
494
|
+
const query = this.typed() ? (this.inputValue() ?? '') : '';
|
|
495
|
+
const matcher = filter ?? this.defaultFilter.contains;
|
|
496
|
+
return matcher(item.textValue(), query);
|
|
497
|
+
}
|
|
498
|
+
/** Whether the item is shown in the list (matches the query and is within `limit`). */
|
|
499
|
+
isVisible(item) {
|
|
500
|
+
return this.visibleSet().has(item);
|
|
501
|
+
}
|
|
502
|
+
isKeyboardActive() {
|
|
503
|
+
return this.keyboardActive;
|
|
504
|
+
}
|
|
505
|
+
setKeyboardActive(value) {
|
|
506
|
+
this.keyboardActive = value;
|
|
507
|
+
}
|
|
508
|
+
isSelected(value) {
|
|
509
|
+
if (this.mode() === 'none') {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
const current = this.value();
|
|
513
|
+
if (this.multiple()) {
|
|
514
|
+
return Array.isArray(current) && current.some((v) => isItemEqualToValue(v, value, this.by()));
|
|
515
|
+
}
|
|
516
|
+
return !isNullish(current) && isItemEqualToValue(current, value, this.by());
|
|
517
|
+
}
|
|
518
|
+
registerItem(item) {
|
|
519
|
+
this._items.update((items) => [...items, item]);
|
|
520
|
+
}
|
|
521
|
+
unregisterItem(item) {
|
|
522
|
+
this._items.update((items) => items.filter((i) => i !== item));
|
|
523
|
+
}
|
|
524
|
+
setOpen(open) {
|
|
525
|
+
if (this.disabledState() || this.readonly()) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
this.open.set(open);
|
|
529
|
+
}
|
|
530
|
+
closePopup(revert = true) {
|
|
531
|
+
if (!this.open()) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
this.open.set(false);
|
|
535
|
+
this.clearHighlightState();
|
|
536
|
+
if (revert) {
|
|
537
|
+
this.revertInputValue();
|
|
538
|
+
}
|
|
539
|
+
this.markAsTouched();
|
|
540
|
+
}
|
|
541
|
+
/** Updates the input text from user typing (marks the query as a fresh user query). */
|
|
542
|
+
setInputValue(value) {
|
|
543
|
+
this.inputValue.set(value);
|
|
544
|
+
this.typed.set(true);
|
|
545
|
+
this.onInputValueChange.emit(value);
|
|
546
|
+
// Auto-highlight the first match as the query changes (deferred so it lands after items mount).
|
|
547
|
+
if (this.autoHighlightMode() !== 'off') {
|
|
548
|
+
this.pendingHighlightEdge.set('first');
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
/** Sets the input text programmatically (a selection label / revert) — not a user query. */
|
|
552
|
+
setLabel(value) {
|
|
553
|
+
this.inputValue.set(value);
|
|
554
|
+
this.typed.set(false);
|
|
555
|
+
this.onInputValueChange.emit(value);
|
|
556
|
+
}
|
|
557
|
+
/** Selects all input text so the next keystroke replaces a stale selection label. */
|
|
558
|
+
selectInputText() {
|
|
559
|
+
this.inputElement()?.select();
|
|
560
|
+
}
|
|
561
|
+
/** Resets the input text to the current selection's label (single mode) or empty. */
|
|
562
|
+
revertInputValue() {
|
|
563
|
+
if (this.multiple()) {
|
|
564
|
+
this.setLabel('');
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const current = this.value();
|
|
568
|
+
this.setLabel(isNullish(current) ? '' : this.labelFor(current));
|
|
569
|
+
}
|
|
570
|
+
labelFor(value) {
|
|
571
|
+
const custom = this.itemToStringLabel();
|
|
572
|
+
if (custom) {
|
|
573
|
+
return custom(value);
|
|
574
|
+
}
|
|
575
|
+
const item = this.orderedItems().find((i) => isItemEqualToValue(i.value(), value, this.by()));
|
|
576
|
+
return item ? item.textValue() : itemToStringLabel(value);
|
|
577
|
+
}
|
|
578
|
+
/** Filter/label text for a raw item value (virtualized mode, no DOM element to read from). */
|
|
579
|
+
textFor(value) {
|
|
580
|
+
const custom = this.itemToStringLabel();
|
|
581
|
+
return custom ? custom(value) : itemToStringLabel(value);
|
|
582
|
+
}
|
|
583
|
+
/** Deterministic id for the item at `index` in virtualized mode (matches `aria-activedescendant`). */
|
|
584
|
+
itemId(index) {
|
|
585
|
+
return `${this.listId}-item-${index}`;
|
|
586
|
+
}
|
|
587
|
+
handleSelect(item) {
|
|
588
|
+
if (this.disabledState() || this.readonly() || item.disabled()) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
this.handleSelectValue(item.value(), item.textValue() || this.labelFor(item.value()));
|
|
592
|
+
}
|
|
593
|
+
/** Selects the filtered item at `index` (virtualized mode). The label comes from {@link labelFor}. */
|
|
594
|
+
selectIndex(index) {
|
|
595
|
+
if (this.disabledState() || this.readonly()) {
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const value = this.filteredItems()[index];
|
|
599
|
+
if (value === undefined) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this.handleSelectValue(value, this.labelFor(value));
|
|
603
|
+
}
|
|
604
|
+
/** Commits a selection from a resolved value/label, independent of whether a DOM item exists. */
|
|
605
|
+
handleSelectValue(value, textValue) {
|
|
606
|
+
if (this.mode() === 'none') {
|
|
607
|
+
// No value is committed; `onValueChange` fires as a pointer/keyboard activation signal so
|
|
608
|
+
// command-palette consumers can react. Optionally fill the input, then close.
|
|
609
|
+
this.onValueChange.emit(value);
|
|
610
|
+
if (this.fillInputOnItemPress()) {
|
|
611
|
+
this.setLabel(textValue);
|
|
612
|
+
}
|
|
613
|
+
this.open.set(false);
|
|
614
|
+
this.clearHighlightState();
|
|
615
|
+
this.restoreFocusAfterSelect();
|
|
616
|
+
this.maybeSubmit();
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (this.multiple()) {
|
|
620
|
+
const current = Array.isArray(this.value()) ? [...this.value()] : [];
|
|
621
|
+
const index = current.findIndex((v) => isItemEqualToValue(v, value, this.by()));
|
|
622
|
+
if (index === -1) {
|
|
623
|
+
current.push(value);
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
current.splice(index, 1);
|
|
627
|
+
}
|
|
628
|
+
this.commitValue(current);
|
|
629
|
+
this.setLabel('');
|
|
630
|
+
this.focusInput();
|
|
631
|
+
}
|
|
632
|
+
else {
|
|
633
|
+
this.commitValue(value);
|
|
634
|
+
this.setLabel(textValue);
|
|
635
|
+
this.open.set(false);
|
|
636
|
+
this.clearHighlightState();
|
|
637
|
+
this.restoreFocusAfterSelect();
|
|
638
|
+
}
|
|
639
|
+
this.maybeSubmit();
|
|
640
|
+
}
|
|
641
|
+
/** Requests submit of the closest form when `submitOnItemClick` is enabled. */
|
|
642
|
+
maybeSubmit() {
|
|
643
|
+
if (this.submitOnItemClick()) {
|
|
644
|
+
this.inputElement()?.form?.requestSubmit?.();
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
selectHighlighted() {
|
|
648
|
+
if (this.virtualized()) {
|
|
649
|
+
const index = this.highlightedIndex();
|
|
650
|
+
if (index >= 0) {
|
|
651
|
+
this.selectIndex(index);
|
|
652
|
+
}
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
const item = this.highlightedItem();
|
|
656
|
+
if (item) {
|
|
657
|
+
this.handleSelect(item);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// --- Highlight navigation facade (mode-aware: index-based when virtualized, else DOM-ref) ---
|
|
661
|
+
highlightNext(reason = 'keyboard') {
|
|
662
|
+
this.highlightReason.set(reason);
|
|
663
|
+
if (this.virtualized()) {
|
|
664
|
+
this.stepIndex(1);
|
|
665
|
+
}
|
|
666
|
+
else {
|
|
667
|
+
this.highlight.next();
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
highlightPrevious(reason = 'keyboard') {
|
|
671
|
+
this.highlightReason.set(reason);
|
|
672
|
+
if (this.virtualized()) {
|
|
673
|
+
this.stepIndex(-1);
|
|
674
|
+
}
|
|
675
|
+
else {
|
|
676
|
+
this.highlight.previous();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
highlightFirst(reason = 'keyboard') {
|
|
680
|
+
this.highlightReason.set(reason);
|
|
681
|
+
if (this.virtualized()) {
|
|
682
|
+
this.highlightedIndex.set(this.filteredItems().length > 0 ? 0 : -1);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
this.highlight.first();
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
highlightLast(reason = 'keyboard') {
|
|
689
|
+
this.highlightReason.set(reason);
|
|
690
|
+
if (this.virtualized()) {
|
|
691
|
+
const length = this.filteredItems().length;
|
|
692
|
+
this.highlightedIndex.set(length > 0 ? length - 1 : -1);
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
this.highlight.last();
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/** Highlights a specific index in virtualized mode (e.g. pointer hover). Ignored if out of range. */
|
|
699
|
+
highlightIndex(index, reason) {
|
|
700
|
+
if (index < 0 || index >= this.filteredItems().length) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
this.highlightReason.set(reason);
|
|
704
|
+
this.highlightedIndex.set(index);
|
|
705
|
+
}
|
|
706
|
+
/** Highlights a DOM-ref item (non-virtualized pointer hover). */
|
|
707
|
+
setHighlight(item, reason) {
|
|
708
|
+
this.highlightReason.set(reason);
|
|
709
|
+
this.highlight.set(item);
|
|
710
|
+
}
|
|
711
|
+
/** Clears whichever highlight model is active. */
|
|
712
|
+
clearHighlightState() {
|
|
713
|
+
this.highlight.clear();
|
|
714
|
+
this.highlightedIndex.set(-1);
|
|
715
|
+
}
|
|
716
|
+
/** Steps the virtualized highlight index by `direction`, wrapping when {@link loopFocus}. */
|
|
717
|
+
stepIndex(direction) {
|
|
718
|
+
const length = this.filteredItems().length;
|
|
719
|
+
if (length === 0) {
|
|
720
|
+
this.highlightedIndex.set(-1);
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
const current = this.highlightedIndex();
|
|
724
|
+
if (current < 0) {
|
|
725
|
+
this.highlightedIndex.set(direction === 1 ? 0 : length - 1);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
let next = current + direction;
|
|
729
|
+
const loop = this.loopFocus();
|
|
730
|
+
if (next < 0) {
|
|
731
|
+
next = loop ? length - 1 : 0;
|
|
732
|
+
}
|
|
733
|
+
else if (next >= length) {
|
|
734
|
+
next = loop ? 0 : length - 1;
|
|
735
|
+
}
|
|
736
|
+
this.highlightedIndex.set(next);
|
|
737
|
+
}
|
|
738
|
+
clearSelection() {
|
|
739
|
+
this.commitValue(this.multiple() ? [] : null);
|
|
740
|
+
this.setLabel('');
|
|
741
|
+
this.focusInput();
|
|
742
|
+
}
|
|
743
|
+
removeValue(value) {
|
|
744
|
+
if (!this.multiple() || !Array.isArray(this.value())) {
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
const next = this.value().filter((v) => !isItemEqualToValue(v, value, this.by()));
|
|
748
|
+
this.commitValue(next);
|
|
749
|
+
}
|
|
750
|
+
removeLastValue() {
|
|
751
|
+
if (!this.multiple() || !Array.isArray(this.value())) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
const current = this.value();
|
|
755
|
+
if (current.length > 0) {
|
|
756
|
+
this.commitValue(current.slice(0, -1));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
focusInput() {
|
|
760
|
+
this.inputElement()?.focus();
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Restores focus after a selection closes the popup, so the keyboard can reopen it. When the
|
|
764
|
+
* input lives inside the popup it is about to unmount, so focus goes to the trigger instead;
|
|
765
|
+
* otherwise it returns to the input. Done synchronously while the input is still in the DOM.
|
|
766
|
+
*/
|
|
767
|
+
restoreFocusAfterSelect() {
|
|
768
|
+
const input = this.inputElement();
|
|
769
|
+
if (input && !input.closest('[rdxComboboxPopup]')) {
|
|
770
|
+
input.focus();
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
this.triggerElement?.focus();
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
/** Registered by `RdxComboboxChips` so the input can hand keyboard focus to the chips. */
|
|
777
|
+
registerChipsNav(fn) {
|
|
778
|
+
this.chipsFocusLast = fn;
|
|
779
|
+
}
|
|
780
|
+
/** Moves focus to the last chip, if any. Returns whether a chip was focused. */
|
|
781
|
+
focusLastChip() {
|
|
782
|
+
return this.chipsFocusLast?.() ?? false;
|
|
783
|
+
}
|
|
784
|
+
markAsTouched() {
|
|
785
|
+
this.onTouched?.();
|
|
786
|
+
}
|
|
787
|
+
commitValue(value) {
|
|
788
|
+
this.value.set(value);
|
|
789
|
+
this.onValueChange.emit(value);
|
|
790
|
+
this.onChange?.(value);
|
|
791
|
+
}
|
|
792
|
+
// ControlValueAccessor
|
|
793
|
+
writeValue(value) {
|
|
794
|
+
untracked(() => this.value.set(value));
|
|
795
|
+
}
|
|
796
|
+
registerOnChange(fn) {
|
|
797
|
+
this.onChange = fn;
|
|
798
|
+
}
|
|
799
|
+
registerOnTouched(fn) {
|
|
800
|
+
this.onTouched = fn;
|
|
801
|
+
}
|
|
802
|
+
setDisabledState(isDisabled) {
|
|
803
|
+
this.cvaDisabled.set(isDisabled);
|
|
804
|
+
}
|
|
805
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
806
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxRoot, isStandalone: true, selector: "[rdxComboboxRoot]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, inputValue: { classPropertyName: "inputValue", publicName: "inputValue", 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 }, multipleInput: { classPropertyName: "multipleInput", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, selectionMode: { classPropertyName: "selectionMode", publicName: "selectionMode", isSignal: true, isRequired: false, transformFunction: null }, fillInputOnItemPress: { classPropertyName: "fillInputOnItemPress", publicName: "fillInputOnItemPress", isSignal: true, isRequired: false, transformFunction: null }, dir: { classPropertyName: "dir", 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 }, 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 }, by: { classPropertyName: "by", publicName: "by", isSignal: true, isRequired: false, transformFunction: null }, itemToStringLabel: { classPropertyName: "itemToStringLabel", publicName: "itemToStringLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", inputValue: "inputValueChange", open: "openChange", onValueChange: "onValueChange", onInputValueChange: "onInputValueChange", onOpenChange: "onOpenChange", onItemHighlighted: "onItemHighlighted", onOpenChangeComplete: "onOpenChangeComplete" }, host: { properties: { "attr.data-disabled": "disabledState() ? \"\" : undefined" } }, providers: [
|
|
807
|
+
provideComboboxRootContext(context),
|
|
808
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxComboboxRoot, multi: true }
|
|
809
|
+
], exportAs: ["rdxComboboxRoot"], hostDirectives: [{ directive: i1.RdxPopper }], ngImport: i0 }); }
|
|
810
|
+
}
|
|
811
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxRoot, decorators: [{
|
|
812
|
+
type: Directive,
|
|
813
|
+
args: [{
|
|
814
|
+
selector: '[rdxComboboxRoot]',
|
|
815
|
+
exportAs: 'rdxComboboxRoot',
|
|
816
|
+
providers: [
|
|
817
|
+
provideComboboxRootContext(context),
|
|
818
|
+
{ provide: NG_VALUE_ACCESSOR, useExisting: RdxComboboxRoot, multi: true }
|
|
819
|
+
],
|
|
820
|
+
hostDirectives: [RdxPopper],
|
|
821
|
+
host: {
|
|
822
|
+
'[attr.data-disabled]': 'disabledState() ? "" : undefined'
|
|
823
|
+
}
|
|
824
|
+
}]
|
|
825
|
+
}], 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 }] }], inputValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "inputValue", required: false }] }, { type: i0.Output, args: ["inputValueChange"] }], 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 }] }], multipleInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], selectionMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectionMode", required: false }] }], fillInputOnItemPress: [{ type: i0.Input, args: [{ isSignal: true, alias: "fillInputOnItemPress", required: false }] }], dir: [{ 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 }] }], 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 }] }], by: [{ type: i0.Input, args: [{ isSignal: true, alias: "by", required: false }] }], itemToStringLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemToStringLabel", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onInputValueChange: [{ type: i0.Output, args: ["onInputValueChange"] }], onOpenChange: [{ type: i0.Output, args: ["onOpenChange"] }], onItemHighlighted: [{ type: i0.Output, args: ["onItemHighlighted"] }], onOpenChangeComplete: [{ type: i0.Output, args: ["onOpenChangeComplete"] }] } });
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* An overlay rendered beneath the popup in `modal` mode. Place it inside the portal/presence; style
|
|
829
|
+
* it `position: fixed; inset: 0`. Give it `pointer-events: auto` so a click on it dismisses the popup
|
|
830
|
+
* (with `modal`, the rest of the page is inert). Exposes `data-open` / `data-closed` for animation.
|
|
831
|
+
*
|
|
832
|
+
* @group Components
|
|
833
|
+
*/
|
|
834
|
+
class RdxComboboxBackdrop {
|
|
835
|
+
constructor() {
|
|
836
|
+
this.rootContext = injectComboboxRootContext();
|
|
837
|
+
}
|
|
838
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxBackdrop, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
839
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxBackdrop, isStandalone: true, selector: "[rdxComboboxBackdrop]", host: { attributes: { "aria-hidden": "true" }, properties: { "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-open": "rootContext.open() ? \"\" : undefined", "attr.data-closed": "rootContext.open() ? undefined : \"\"" } }, exportAs: ["rdxComboboxBackdrop"], ngImport: i0 }); }
|
|
840
|
+
}
|
|
841
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxBackdrop, decorators: [{
|
|
842
|
+
type: Directive,
|
|
843
|
+
args: [{
|
|
844
|
+
selector: '[rdxComboboxBackdrop]',
|
|
845
|
+
exportAs: 'rdxComboboxBackdrop',
|
|
846
|
+
host: {
|
|
847
|
+
'aria-hidden': 'true',
|
|
848
|
+
'[attr.data-state]': 'rootContext.open() ? "open" : "closed"',
|
|
849
|
+
'[attr.data-open]': 'rootContext.open() ? "" : undefined',
|
|
850
|
+
'[attr.data-closed]': 'rootContext.open() ? undefined : ""'
|
|
851
|
+
}
|
|
852
|
+
}]
|
|
853
|
+
}] });
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* Container for the selected-value chips in `multiple` mode. Sits before the input and coordinates
|
|
857
|
+
* arrow-key navigation across the chips (the chips themselves handle the key events).
|
|
858
|
+
*
|
|
859
|
+
* @group Components
|
|
860
|
+
*/
|
|
861
|
+
class RdxComboboxChips {
|
|
862
|
+
constructor() {
|
|
863
|
+
this.host = inject(ElementRef).nativeElement;
|
|
864
|
+
this.rootContext = injectComboboxRootContext();
|
|
865
|
+
this.rootContext.registerChipsNav(() => this.focusLast());
|
|
866
|
+
inject(DestroyRef).onDestroy(() => this.rootContext.registerChipsNav(null));
|
|
867
|
+
}
|
|
868
|
+
/** The chip elements in DOM order. */
|
|
869
|
+
getChips() {
|
|
870
|
+
return Array.from(this.host.querySelectorAll('[rdxComboboxChip]'));
|
|
871
|
+
}
|
|
872
|
+
/** Focuses the last chip. Returns whether there was one. */
|
|
873
|
+
focusLast() {
|
|
874
|
+
const chips = this.getChips();
|
|
875
|
+
if (chips.length === 0) {
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
chips[chips.length - 1].focus();
|
|
879
|
+
return true;
|
|
880
|
+
}
|
|
881
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxChips, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
882
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxChips, isStandalone: true, selector: "[rdxComboboxChips]", host: { attributes: { "role": "list" } }, exportAs: ["rdxComboboxChips"], hostDirectives: [{ directive: i1$1.RdxDismissableLayerBranch }], ngImport: i0 }); }
|
|
883
|
+
}
|
|
884
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxChips, decorators: [{
|
|
885
|
+
type: Directive,
|
|
886
|
+
args: [{
|
|
887
|
+
selector: '[rdxComboboxChips]',
|
|
888
|
+
exportAs: 'rdxComboboxChips',
|
|
889
|
+
hostDirectives: [RdxDismissableLayerBranch],
|
|
890
|
+
host: {
|
|
891
|
+
role: 'list'
|
|
892
|
+
}
|
|
893
|
+
}]
|
|
894
|
+
}], ctorParameters: () => [] });
|
|
895
|
+
|
|
896
|
+
const chipContext = () => {
|
|
897
|
+
const chip = inject(RdxComboboxChip);
|
|
898
|
+
return { value: chip.value };
|
|
899
|
+
};
|
|
900
|
+
const [injectComboboxChipContext, provideComboboxChipContext] = createContext('RdxComboboxChipContext', 'components/combobox');
|
|
901
|
+
/**
|
|
902
|
+
* A single selected-value chip. Provide its `value` so {@link RdxComboboxChipRemove} can deselect it.
|
|
903
|
+
* Chips are focusable and navigable with the arrow keys; Backspace / Delete removes the chip.
|
|
904
|
+
*
|
|
905
|
+
* @group Components
|
|
906
|
+
*/
|
|
907
|
+
class RdxComboboxChip {
|
|
908
|
+
constructor() {
|
|
909
|
+
this.element = inject(ElementRef).nativeElement;
|
|
910
|
+
this.chips = inject(RdxComboboxChips, { optional: true });
|
|
911
|
+
this.rootContext = injectComboboxRootContext();
|
|
912
|
+
/** The value this chip represents. */
|
|
913
|
+
this.value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
914
|
+
}
|
|
915
|
+
onKeydown(event) {
|
|
916
|
+
const list = this.chips?.getChips() ?? [];
|
|
917
|
+
const index = list.indexOf(this.element);
|
|
918
|
+
switch (event.key) {
|
|
919
|
+
case 'ArrowLeft':
|
|
920
|
+
if (index > 0) {
|
|
921
|
+
event.preventDefault();
|
|
922
|
+
list[index - 1].focus();
|
|
923
|
+
}
|
|
924
|
+
break;
|
|
925
|
+
case 'ArrowRight':
|
|
926
|
+
event.preventDefault();
|
|
927
|
+
if (index < list.length - 1) {
|
|
928
|
+
list[index + 1].focus();
|
|
929
|
+
}
|
|
930
|
+
else {
|
|
931
|
+
this.rootContext.focusInput();
|
|
932
|
+
}
|
|
933
|
+
break;
|
|
934
|
+
case 'Home':
|
|
935
|
+
if (list.length) {
|
|
936
|
+
event.preventDefault();
|
|
937
|
+
list[0].focus();
|
|
938
|
+
}
|
|
939
|
+
break;
|
|
940
|
+
case 'End':
|
|
941
|
+
event.preventDefault();
|
|
942
|
+
this.rootContext.focusInput();
|
|
943
|
+
break;
|
|
944
|
+
case 'Backspace':
|
|
945
|
+
case 'Delete': {
|
|
946
|
+
event.preventDefault();
|
|
947
|
+
// The @for tracks by value, so the previous/next chip nodes survive this removal.
|
|
948
|
+
const prev = list[index - 1];
|
|
949
|
+
const next = list[index + 1];
|
|
950
|
+
this.rootContext.removeValue(this.value());
|
|
951
|
+
if (prev) {
|
|
952
|
+
prev.focus();
|
|
953
|
+
}
|
|
954
|
+
else if (next) {
|
|
955
|
+
next.focus();
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
this.rootContext.focusInput();
|
|
959
|
+
}
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
case 'Escape':
|
|
963
|
+
this.rootContext.focusInput();
|
|
964
|
+
break;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxChip, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
968
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxChip, isStandalone: true, selector: "[rdxComboboxChip]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "listitem", "tabindex": "-1" }, listeners: { "keydown": "onKeydown($event)" } }, providers: [provideComboboxChipContext(chipContext)], exportAs: ["rdxComboboxChip"], ngImport: i0 }); }
|
|
969
|
+
}
|
|
970
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxChip, decorators: [{
|
|
971
|
+
type: Directive,
|
|
972
|
+
args: [{
|
|
973
|
+
selector: '[rdxComboboxChip]',
|
|
974
|
+
exportAs: 'rdxComboboxChip',
|
|
975
|
+
providers: [provideComboboxChipContext(chipContext)],
|
|
976
|
+
host: {
|
|
977
|
+
role: 'listitem',
|
|
978
|
+
tabindex: '-1',
|
|
979
|
+
'(keydown)': 'onKeydown($event)'
|
|
980
|
+
}
|
|
981
|
+
}]
|
|
982
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }] } });
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Removes its chip's value from the selection, keeping focus in the input.
|
|
986
|
+
*
|
|
987
|
+
* @group Components
|
|
988
|
+
*/
|
|
989
|
+
class RdxComboboxChipRemove {
|
|
990
|
+
constructor() {
|
|
991
|
+
this.rootContext = injectComboboxRootContext();
|
|
992
|
+
this.chipContext = injectComboboxChipContext();
|
|
993
|
+
}
|
|
994
|
+
onClick() {
|
|
995
|
+
this.rootContext.removeValue(this.chipContext.value());
|
|
996
|
+
this.rootContext.focusInput();
|
|
997
|
+
}
|
|
998
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxChipRemove, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
999
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxChipRemove, isStandalone: true, selector: "button[rdxComboboxChipRemove]", host: { attributes: { "type": "button", "tabindex": "-1", "aria-label": "Remove" }, listeners: { "click": "onClick()" } }, exportAs: ["rdxComboboxChipRemove"], ngImport: i0 }); }
|
|
1000
|
+
}
|
|
1001
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxChipRemove, decorators: [{
|
|
1002
|
+
type: Directive,
|
|
1003
|
+
args: [{
|
|
1004
|
+
selector: 'button[rdxComboboxChipRemove]',
|
|
1005
|
+
exportAs: 'rdxComboboxChipRemove',
|
|
1006
|
+
host: {
|
|
1007
|
+
type: 'button',
|
|
1008
|
+
tabindex: '-1',
|
|
1009
|
+
'aria-label': 'Remove',
|
|
1010
|
+
'(click)': 'onClick()'
|
|
1011
|
+
}
|
|
1012
|
+
}]
|
|
1013
|
+
}] });
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* Clears the current selection and the input text. Hidden when there is nothing to clear.
|
|
1017
|
+
*
|
|
1018
|
+
* @group Components
|
|
1019
|
+
*/
|
|
1020
|
+
class RdxComboboxClear {
|
|
1021
|
+
constructor() {
|
|
1022
|
+
this.rootContext = injectComboboxRootContext();
|
|
1023
|
+
this.isEmpty = computed(() => {
|
|
1024
|
+
const value = this.rootContext.value();
|
|
1025
|
+
if (Array.isArray(value)) {
|
|
1026
|
+
return value.length === 0;
|
|
1027
|
+
}
|
|
1028
|
+
return value === null || value === undefined;
|
|
1029
|
+
}, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
1030
|
+
}
|
|
1031
|
+
onClick() {
|
|
1032
|
+
this.rootContext.clearSelection();
|
|
1033
|
+
}
|
|
1034
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxClear, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1035
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxClear, isStandalone: true, selector: "button[rdxComboboxClear]", host: { attributes: { "type": "button", "tabindex": "-1", "aria-label": "Clear" }, listeners: { "click": "onClick()" }, properties: { "hidden": "isEmpty()", "attr.disabled": "rootContext.disabledState() ? \"\" : undefined" } }, exportAs: ["rdxComboboxClear"], hostDirectives: [{ directive: i1$1.RdxDismissableLayerBranch }], ngImport: i0 }); }
|
|
1036
|
+
}
|
|
1037
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxClear, decorators: [{
|
|
1038
|
+
type: Directive,
|
|
1039
|
+
args: [{
|
|
1040
|
+
selector: 'button[rdxComboboxClear]',
|
|
1041
|
+
exportAs: 'rdxComboboxClear',
|
|
1042
|
+
hostDirectives: [RdxDismissableLayerBranch],
|
|
1043
|
+
host: {
|
|
1044
|
+
type: 'button',
|
|
1045
|
+
tabindex: '-1',
|
|
1046
|
+
'aria-label': 'Clear',
|
|
1047
|
+
'[hidden]': 'isEmpty()',
|
|
1048
|
+
'[attr.disabled]': 'rootContext.disabledState() ? "" : undefined',
|
|
1049
|
+
'(click)': 'onClick()'
|
|
1050
|
+
}
|
|
1051
|
+
}]
|
|
1052
|
+
}] });
|
|
1053
|
+
|
|
1054
|
+
/**
|
|
1055
|
+
* Shown only when no items match the current query.
|
|
1056
|
+
*
|
|
1057
|
+
* @group Components
|
|
1058
|
+
*/
|
|
1059
|
+
class RdxComboboxEmpty {
|
|
1060
|
+
constructor() {
|
|
1061
|
+
this.rootContext = injectComboboxRootContext();
|
|
1062
|
+
}
|
|
1063
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxEmpty, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1064
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxEmpty, isStandalone: true, selector: "[rdxComboboxEmpty]", host: { attributes: { "role": "presentation" }, properties: { "hidden": "rootContext.visibleCount() > 0" } }, exportAs: ["rdxComboboxEmpty"], ngImport: i0 }); }
|
|
1065
|
+
}
|
|
1066
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxEmpty, decorators: [{
|
|
1067
|
+
type: Directive,
|
|
1068
|
+
args: [{
|
|
1069
|
+
selector: '[rdxComboboxEmpty]',
|
|
1070
|
+
exportAs: 'rdxComboboxEmpty',
|
|
1071
|
+
host: {
|
|
1072
|
+
role: 'presentation',
|
|
1073
|
+
'[hidden]': 'rootContext.visibleCount() > 0'
|
|
1074
|
+
}
|
|
1075
|
+
}]
|
|
1076
|
+
}] });
|
|
1077
|
+
|
|
1078
|
+
const groupContext = () => {
|
|
1079
|
+
const group = inject(RdxComboboxGroup);
|
|
1080
|
+
return {
|
|
1081
|
+
labelId: group.labelId,
|
|
1082
|
+
registerItem: (item) => group.registerItem(item),
|
|
1083
|
+
unregisterItem: (item) => group.unregisterItem(item)
|
|
1084
|
+
};
|
|
1085
|
+
};
|
|
1086
|
+
const [injectComboboxGroupContext, provideComboboxGroupContext] = createContext('RdxComboboxGroupContext', 'components/combobox');
|
|
1087
|
+
/**
|
|
1088
|
+
* Groups related options under a shared label. Hides itself when all of its items are filtered out,
|
|
1089
|
+
* so a group heading never lingers above an empty section.
|
|
1090
|
+
*
|
|
1091
|
+
* @group Components
|
|
1092
|
+
*/
|
|
1093
|
+
class RdxComboboxGroup {
|
|
1094
|
+
constructor() {
|
|
1095
|
+
this.rootContext = injectComboboxRootContext();
|
|
1096
|
+
this.labelId = signal(undefined, ...(ngDevMode ? [{ debugName: "labelId" }] : /* istanbul ignore next */ []));
|
|
1097
|
+
this.items = signal([], ...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
|
|
1098
|
+
this.hasItems = computed(() => this.items().length > 0, ...(ngDevMode ? [{ debugName: "hasItems" }] : /* istanbul ignore next */ []));
|
|
1099
|
+
this.hasVisibleItems = computed(() => this.items().some((item) => this.rootContext.isVisible(item)), ...(ngDevMode ? [{ debugName: "hasVisibleItems" }] : /* istanbul ignore next */ []));
|
|
1100
|
+
}
|
|
1101
|
+
registerItem(item) {
|
|
1102
|
+
this.items.update((items) => [...items, item]);
|
|
1103
|
+
}
|
|
1104
|
+
unregisterItem(item) {
|
|
1105
|
+
this.items.update((items) => items.filter((i) => i !== item));
|
|
1106
|
+
}
|
|
1107
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1108
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxGroup, isStandalone: true, selector: "[rdxComboboxGroup]", host: { attributes: { "role": "group" }, properties: { "attr.aria-labelledby": "labelId()", "hidden": "hasItems() && !hasVisibleItems()" } }, providers: [provideComboboxGroupContext(groupContext)], exportAs: ["rdxComboboxGroup"], ngImport: i0 }); }
|
|
1109
|
+
}
|
|
1110
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxGroup, decorators: [{
|
|
1111
|
+
type: Directive,
|
|
1112
|
+
args: [{
|
|
1113
|
+
selector: '[rdxComboboxGroup]',
|
|
1114
|
+
exportAs: 'rdxComboboxGroup',
|
|
1115
|
+
providers: [provideComboboxGroupContext(groupContext)],
|
|
1116
|
+
host: {
|
|
1117
|
+
role: 'group',
|
|
1118
|
+
'[attr.aria-labelledby]': 'labelId()',
|
|
1119
|
+
'[hidden]': 'hasItems() && !hasVisibleItems()'
|
|
1120
|
+
}
|
|
1121
|
+
}]
|
|
1122
|
+
}] });
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Accessible label for a {@link RdxComboboxGroup}. Wires itself up via `aria-labelledby`.
|
|
1126
|
+
*
|
|
1127
|
+
* @group Components
|
|
1128
|
+
*/
|
|
1129
|
+
class RdxComboboxGroupLabel {
|
|
1130
|
+
constructor() {
|
|
1131
|
+
this.groupContext = injectComboboxGroupContext();
|
|
1132
|
+
this.id = injectId('rdx-combobox-group-label-');
|
|
1133
|
+
this.groupContext.labelId.set(this.id);
|
|
1134
|
+
}
|
|
1135
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxGroupLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1136
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxGroupLabel, isStandalone: true, selector: "[rdxComboboxGroupLabel]", host: { properties: { "attr.id": "id" } }, exportAs: ["rdxComboboxGroupLabel"], ngImport: i0 }); }
|
|
1137
|
+
}
|
|
1138
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxGroupLabel, decorators: [{
|
|
1139
|
+
type: Directive,
|
|
1140
|
+
args: [{
|
|
1141
|
+
selector: '[rdxComboboxGroupLabel]',
|
|
1142
|
+
exportAs: 'rdxComboboxGroupLabel',
|
|
1143
|
+
host: {
|
|
1144
|
+
'[attr.id]': 'id'
|
|
1145
|
+
}
|
|
1146
|
+
}]
|
|
1147
|
+
}], ctorParameters: () => [] });
|
|
1148
|
+
|
|
1149
|
+
/**
|
|
1150
|
+
* Decorative icon inside the trigger. Hidden from assistive technology.
|
|
1151
|
+
*
|
|
1152
|
+
* @group Components
|
|
1153
|
+
*/
|
|
1154
|
+
class RdxComboboxIcon {
|
|
1155
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxIcon, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1156
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxIcon, isStandalone: true, selector: "[rdxComboboxIcon]", host: { attributes: { "aria-hidden": "true" } }, exportAs: ["rdxComboboxIcon"], ngImport: i0 }); }
|
|
1157
|
+
}
|
|
1158
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxIcon, decorators: [{
|
|
1159
|
+
type: Directive,
|
|
1160
|
+
args: [{
|
|
1161
|
+
selector: '[rdxComboboxIcon]',
|
|
1162
|
+
exportAs: 'rdxComboboxIcon',
|
|
1163
|
+
host: {
|
|
1164
|
+
'aria-hidden': 'true'
|
|
1165
|
+
}
|
|
1166
|
+
}]
|
|
1167
|
+
}] });
|
|
1168
|
+
|
|
1169
|
+
const attr = (value) => (value ? '' : undefined);
|
|
1170
|
+
/**
|
|
1171
|
+
* The combobox text input. Holds DOM focus at all times; the highlighted option is referenced via
|
|
1172
|
+
* `aria-activedescendant`. Integrates with Field for labeling, description, and validation state.
|
|
1173
|
+
*
|
|
1174
|
+
* @group Components
|
|
1175
|
+
*/
|
|
1176
|
+
class RdxComboboxInput {
|
|
1177
|
+
constructor() {
|
|
1178
|
+
this.rootContext = injectComboboxRootContext();
|
|
1179
|
+
this.element = inject(ElementRef).nativeElement;
|
|
1180
|
+
this.fieldRootContext = injectFieldRootContext(true);
|
|
1181
|
+
/** The input id; Field labels and descriptions reference it for accessible relationships. */
|
|
1182
|
+
this.id = input(injectId('rdx-combobox-input-'), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
|
|
1183
|
+
/** Marks the input as invalid independently of any Field state. */
|
|
1184
|
+
this.invalid = input(false, { ...(ngDevMode ? { debugName: "invalid" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1185
|
+
this.invalidState = computed(() => this.invalid() || Boolean(this.fieldRootContext?.invalidState()), ...(ngDevMode ? [{ debugName: "invalidState" }] : /* istanbul ignore next */ []));
|
|
1186
|
+
this.disabledState = computed(() => this.rootContext.disabledState() || Boolean(this.fieldRootContext?.disabledState()), ...(ngDevMode ? [{ debugName: "disabledState" }] : /* istanbul ignore next */ []));
|
|
1187
|
+
this.requiredState = computed(() => this.rootContext.requiredState() || Boolean(this.fieldRootContext?.requiredState()), ...(ngDevMode ? [{ debugName: "requiredState" }] : /* istanbul ignore next */ []));
|
|
1188
|
+
this.filledState = computed(() => !this.isEmptyValue() || Boolean(this.fieldRootContext?.filledState()), ...(ngDevMode ? [{ debugName: "filledState" }] : /* istanbul ignore next */ []));
|
|
1189
|
+
this.focusedState = computed(() => Boolean(this.fieldRootContext?.focusedState()), ...(ngDevMode ? [{ debugName: "focusedState" }] : /* istanbul ignore next */ []));
|
|
1190
|
+
this.isEmptyValue = computed(() => {
|
|
1191
|
+
const value = this.rootContext.value();
|
|
1192
|
+
if (Array.isArray(value)) {
|
|
1193
|
+
return value.length === 0;
|
|
1194
|
+
}
|
|
1195
|
+
return value === null || value === undefined;
|
|
1196
|
+
}, ...(ngDevMode ? [{ debugName: "isEmptyValue" }] : /* istanbul ignore next */ []));
|
|
1197
|
+
this.describedBy = computed(() => {
|
|
1198
|
+
if (!this.fieldRootContext) {
|
|
1199
|
+
return undefined;
|
|
1200
|
+
}
|
|
1201
|
+
const ids = [
|
|
1202
|
+
...this.fieldRootContext.descriptionIds(),
|
|
1203
|
+
...(this.fieldRootContext.invalidState() ? this.fieldRootContext.errorIds() : [])
|
|
1204
|
+
];
|
|
1205
|
+
return ids.length ? ids.join(' ') : undefined;
|
|
1206
|
+
}, ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
|
|
1207
|
+
/** Whether an IME composition is in progress (CJK). While composing, don't filter or select. */
|
|
1208
|
+
this.composing = false;
|
|
1209
|
+
this.dataAttr = attr;
|
|
1210
|
+
this.rootContext.setInputElement(this.element);
|
|
1211
|
+
afterNextRender(() => {
|
|
1212
|
+
this.fieldRootContext?.setControlId(this.id());
|
|
1213
|
+
});
|
|
1214
|
+
inject(DestroyRef).onDestroy(() => {
|
|
1215
|
+
if (this.rootContext.inputElement() === this.element) {
|
|
1216
|
+
this.rootContext.setInputElement(null);
|
|
1217
|
+
}
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
onInput(event) {
|
|
1221
|
+
// Defer filtering until the composition ends so intermediate IME text doesn't filter/select.
|
|
1222
|
+
if (this.composing || event.isComposing) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
this.commitInput(event.target.value);
|
|
1226
|
+
}
|
|
1227
|
+
onCompositionEnd(event) {
|
|
1228
|
+
this.composing = false;
|
|
1229
|
+
this.commitInput(event.target.value);
|
|
1230
|
+
}
|
|
1231
|
+
commitInput(value) {
|
|
1232
|
+
if (!this.rootContext.open()) {
|
|
1233
|
+
this.rootContext.openPopup();
|
|
1234
|
+
}
|
|
1235
|
+
// setInputValue applies any autoHighlight (deferred until items mount).
|
|
1236
|
+
this.rootContext.setInputValue(value);
|
|
1237
|
+
}
|
|
1238
|
+
onClick() {
|
|
1239
|
+
if (this.rootContext.openOnInputClick()) {
|
|
1240
|
+
this.rootContext.openForBrowse();
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
onFocus() {
|
|
1244
|
+
this.fieldRootContext?.setFocused(true);
|
|
1245
|
+
}
|
|
1246
|
+
onBlur() {
|
|
1247
|
+
this.fieldRootContext?.setFocused(false);
|
|
1248
|
+
this.fieldRootContext?.setTouched(true);
|
|
1249
|
+
}
|
|
1250
|
+
onKeydown(event) {
|
|
1251
|
+
// Don't interfere with IME composition or text-editing shortcuts / range selection. Home/End
|
|
1252
|
+
// and Shift+Arrows must keep moving the caret, Ctrl/Meta combos stay browser shortcuts.
|
|
1253
|
+
if (event.isComposing || this.composing) {
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (event.ctrlKey || event.metaKey || event.altKey || event.shiftKey) {
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const open = this.rootContext.open();
|
|
1260
|
+
switch (event.key) {
|
|
1261
|
+
case 'ArrowDown':
|
|
1262
|
+
event.preventDefault();
|
|
1263
|
+
this.rootContext.setKeyboardActive(true);
|
|
1264
|
+
if (!open) {
|
|
1265
|
+
this.rootContext.openAndHighlight('first');
|
|
1266
|
+
}
|
|
1267
|
+
else {
|
|
1268
|
+
this.rootContext.highlightNext();
|
|
1269
|
+
}
|
|
1270
|
+
break;
|
|
1271
|
+
case 'ArrowUp':
|
|
1272
|
+
event.preventDefault();
|
|
1273
|
+
this.rootContext.setKeyboardActive(true);
|
|
1274
|
+
if (!open) {
|
|
1275
|
+
this.rootContext.openAndHighlight('last');
|
|
1276
|
+
}
|
|
1277
|
+
else {
|
|
1278
|
+
this.rootContext.highlightPrevious();
|
|
1279
|
+
}
|
|
1280
|
+
break;
|
|
1281
|
+
case 'Enter':
|
|
1282
|
+
if (open) {
|
|
1283
|
+
const hasHighlight = this.rootContext.virtualized()
|
|
1284
|
+
? this.rootContext.highlightedIndex() >= 0
|
|
1285
|
+
: this.rootContext.highlightedItem() !== null;
|
|
1286
|
+
if (hasHighlight) {
|
|
1287
|
+
// Select the highlighted item (and prevent an accidental form submit).
|
|
1288
|
+
event.preventDefault();
|
|
1289
|
+
this.rootContext.selectHighlighted();
|
|
1290
|
+
}
|
|
1291
|
+
else {
|
|
1292
|
+
// Nothing highlighted: just close, and let the form submit.
|
|
1293
|
+
this.rootContext.closePopup(true);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
break;
|
|
1297
|
+
case 'Escape':
|
|
1298
|
+
// Just close the popup (reverting the in-progress query); never clear the selection.
|
|
1299
|
+
if (open) {
|
|
1300
|
+
event.preventDefault();
|
|
1301
|
+
this.rootContext.closePopup(true);
|
|
1302
|
+
}
|
|
1303
|
+
break;
|
|
1304
|
+
case 'Tab':
|
|
1305
|
+
if (open) {
|
|
1306
|
+
this.rootContext.closePopup(true);
|
|
1307
|
+
}
|
|
1308
|
+
break;
|
|
1309
|
+
case 'ArrowLeft':
|
|
1310
|
+
// From the very start of the input in multiple mode, step into the chips.
|
|
1311
|
+
if (this.rootContext.multiple() &&
|
|
1312
|
+
this.element.selectionStart === 0 &&
|
|
1313
|
+
this.element.selectionEnd === 0 &&
|
|
1314
|
+
this.rootContext.focusLastChip()) {
|
|
1315
|
+
event.preventDefault();
|
|
1316
|
+
}
|
|
1317
|
+
break;
|
|
1318
|
+
case 'Backspace':
|
|
1319
|
+
if (this.rootContext.multiple() && this.element.value === '') {
|
|
1320
|
+
this.rootContext.removeLastValue();
|
|
1321
|
+
}
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1326
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxInput, isStandalone: true, selector: "input[rdxComboboxInput]", 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", "aria-autocomplete": "list" }, listeners: { "input": "onInput($event)", "click": "onClick()", "focus": "onFocus()", "blur": "onBlur()", "keydown": "onKeydown($event)", "compositionstart": "composing = true", "compositionend": "onCompositionEnd($event)" }, properties: { "attr.id": "id()", "attr.aria-expanded": "rootContext.open()", "attr.aria-controls": "rootContext.listId", "attr.aria-labelledby": "rootContext.labelId()", "attr.aria-activedescendant": "rootContext.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": "rootContext.readonly() ? \"\" : undefined", "attr.required": "requiredState() ? \"\" : undefined", "value": "rootContext.inputValue()", "attr.data-popup-open": "dataAttr(rootContext.open())", "attr.data-list-empty": "dataAttr(rootContext.visibleCount() === 0)", "attr.data-placeholder": "dataAttr(isEmptyValue())", "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: ["rdxComboboxInput"], hostDirectives: [{ directive: i1.RdxPopperAnchor }, { directive: i1$1.RdxDismissableLayerBranch }], ngImport: i0 }); }
|
|
1327
|
+
}
|
|
1328
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxInput, decorators: [{
|
|
1329
|
+
type: Directive,
|
|
1330
|
+
args: [{
|
|
1331
|
+
selector: 'input[rdxComboboxInput]',
|
|
1332
|
+
exportAs: 'rdxComboboxInput',
|
|
1333
|
+
hostDirectives: [RdxPopperAnchor, RdxDismissableLayerBranch],
|
|
1334
|
+
host: {
|
|
1335
|
+
role: 'combobox',
|
|
1336
|
+
autocomplete: 'off',
|
|
1337
|
+
'aria-autocomplete': 'list',
|
|
1338
|
+
'[attr.id]': 'id()',
|
|
1339
|
+
'[attr.aria-expanded]': 'rootContext.open()',
|
|
1340
|
+
'[attr.aria-controls]': 'rootContext.listId',
|
|
1341
|
+
'[attr.aria-labelledby]': 'rootContext.labelId()',
|
|
1342
|
+
'[attr.aria-activedescendant]': 'rootContext.activeId()',
|
|
1343
|
+
'[attr.aria-describedby]': 'describedBy()',
|
|
1344
|
+
'[attr.aria-invalid]': 'invalidState() ? "true" : undefined',
|
|
1345
|
+
'[attr.aria-required]': 'requiredState() ? "true" : undefined',
|
|
1346
|
+
'[attr.aria-disabled]': 'disabledState() ? "true" : undefined',
|
|
1347
|
+
'[attr.disabled]': 'disabledState() ? "" : undefined',
|
|
1348
|
+
'[attr.readonly]': 'rootContext.readonly() ? "" : undefined',
|
|
1349
|
+
'[attr.required]': 'requiredState() ? "" : undefined',
|
|
1350
|
+
'[value]': 'rootContext.inputValue()',
|
|
1351
|
+
'[attr.data-popup-open]': 'dataAttr(rootContext.open())',
|
|
1352
|
+
'[attr.data-list-empty]': 'dataAttr(rootContext.visibleCount() === 0)',
|
|
1353
|
+
'[attr.data-placeholder]': 'dataAttr(isEmptyValue())',
|
|
1354
|
+
'[attr.data-invalid]': 'dataAttr(invalidState())',
|
|
1355
|
+
'[attr.data-valid]': 'dataAttr(!invalidState())',
|
|
1356
|
+
'[attr.data-disabled]': 'dataAttr(disabledState())',
|
|
1357
|
+
'[attr.data-required]': 'dataAttr(requiredState())',
|
|
1358
|
+
'[attr.data-filled]': 'dataAttr(filledState())',
|
|
1359
|
+
'[attr.data-focused]': 'dataAttr(focusedState())',
|
|
1360
|
+
'(input)': 'onInput($event)',
|
|
1361
|
+
'(click)': 'onClick()',
|
|
1362
|
+
'(focus)': 'onFocus()',
|
|
1363
|
+
'(blur)': 'onBlur()',
|
|
1364
|
+
'(keydown)': 'onKeydown($event)',
|
|
1365
|
+
'(compositionstart)': 'composing = true',
|
|
1366
|
+
'(compositionend)': 'onCompositionEnd($event)'
|
|
1367
|
+
}
|
|
1368
|
+
}]
|
|
1369
|
+
}], ctorParameters: () => [], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], invalid: [{ type: i0.Input, args: [{ isSignal: true, alias: "invalid", required: false }] }] } });
|
|
1370
|
+
|
|
1371
|
+
const itemContext = () => {
|
|
1372
|
+
const item = inject(RdxComboboxItem);
|
|
1373
|
+
return {
|
|
1374
|
+
isSelected: item.isSelected,
|
|
1375
|
+
isHighlighted: item.isHighlighted,
|
|
1376
|
+
disabled: item.disabled,
|
|
1377
|
+
value: item.value
|
|
1378
|
+
};
|
|
1379
|
+
};
|
|
1380
|
+
const [injectComboboxItemContext, provideComboboxItemContext] = createContext('RdxComboboxItemContext', 'components/combobox');
|
|
1381
|
+
/**
|
|
1382
|
+
* A selectable option. Registers itself with the root for filtering and navigation. Highlight is
|
|
1383
|
+
* virtual (`data-highlighted` + `aria-activedescendant` on the input) — items never take DOM focus.
|
|
1384
|
+
*
|
|
1385
|
+
* @group Components
|
|
1386
|
+
*/
|
|
1387
|
+
class RdxComboboxItem {
|
|
1388
|
+
constructor() {
|
|
1389
|
+
this.rootContext = injectComboboxRootContext();
|
|
1390
|
+
this.element = inject(ElementRef).nativeElement;
|
|
1391
|
+
this.id = injectId('rdx-combobox-item-');
|
|
1392
|
+
/** The option's value. */
|
|
1393
|
+
this.value = input.required(...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
1394
|
+
/** Explicit text matched against the query. Defaults to the element's text content. */
|
|
1395
|
+
this.textValueInput = input('', { ...(ngDevMode ? { debugName: "textValueInput" } : /* istanbul ignore next */ {}), alias: 'textValue' });
|
|
1396
|
+
/** Whether the option is disabled. */
|
|
1397
|
+
this.disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1398
|
+
/**
|
|
1399
|
+
* The option's index in the list. Required in virtualized mode — it drives the deterministic id
|
|
1400
|
+
* (for `aria-activedescendant`), highlight matching, and selection when only a window is mounted.
|
|
1401
|
+
*/
|
|
1402
|
+
this.index = input(...(ngDevMode ? [undefined, { debugName: "index" }] : /* istanbul ignore next */ []));
|
|
1403
|
+
this.virtualized = this.rootContext.virtualized;
|
|
1404
|
+
this.autoTextValue = signal('', ...(ngDevMode ? [{ debugName: "autoTextValue" }] : /* istanbul ignore next */ []));
|
|
1405
|
+
this.textValue = computed(() => this.textValueInput() || this.autoTextValue(), ...(ngDevMode ? [{ debugName: "textValue" }] : /* istanbul ignore next */ []));
|
|
1406
|
+
/** Host id: deterministic per-index in virtualized mode, otherwise a generated unique id. */
|
|
1407
|
+
this.elementId = computed(() => this.virtualized() ? this.rootContext.itemId(this.index() ?? -1) : this.id, ...(ngDevMode ? [{ debugName: "elementId" }] : /* istanbul ignore next */ []));
|
|
1408
|
+
this.ariaSetSize = computed(() => this.virtualized() ? this.rootContext.filteredItems().length : undefined, ...(ngDevMode ? [{ debugName: "ariaSetSize" }] : /* istanbul ignore next */ []));
|
|
1409
|
+
this.ariaPosInSet = computed(() => (this.virtualized() ? (this.index() ?? -1) + 1 : undefined), ...(ngDevMode ? [{ debugName: "ariaPosInSet" }] : /* istanbul ignore next */ []));
|
|
1410
|
+
// Virtualized items are always rendered (the consumer only mounts the filtered window).
|
|
1411
|
+
this.isVisible = computed(() => (this.virtualized() ? true : this.rootContext.isVisible(this)), ...(ngDevMode ? [{ debugName: "isVisible" }] : /* istanbul ignore next */ []));
|
|
1412
|
+
this.isSelected = computed(() => this.rootContext.isSelected(this.value()), ...(ngDevMode ? [{ debugName: "isSelected" }] : /* istanbul ignore next */ []));
|
|
1413
|
+
this.isHighlighted = computed(() => this.virtualized()
|
|
1414
|
+
? this.rootContext.highlightedIndex() === this.index()
|
|
1415
|
+
: this.rootContext.highlightedItem() === this, ...(ngDevMode ? [{ debugName: "isHighlighted" }] : /* istanbul ignore next */ []));
|
|
1416
|
+
this.group = injectComboboxGroupContext(true);
|
|
1417
|
+
const destroyRef = inject(DestroyRef);
|
|
1418
|
+
afterNextRender(() => {
|
|
1419
|
+
// Virtualized items are not registered: the root navigates over `items` data by index, and
|
|
1420
|
+
// a windowed item mounting/unmounting must not churn the DOM-ordered registry or `labelFor`.
|
|
1421
|
+
if (this.virtualized()) {
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
if (!this.textValueInput()) {
|
|
1425
|
+
// Derive the match/label text from the element, excluding decorative indicator text
|
|
1426
|
+
// (e.g. a checkmark) so it doesn't pollute filtering or the input label.
|
|
1427
|
+
const clone = this.element.cloneNode(true);
|
|
1428
|
+
clone.querySelectorAll('[rdxComboboxItemIndicator]').forEach((node) => node.remove());
|
|
1429
|
+
this.autoTextValue.set(clone.textContent?.trim() ?? '');
|
|
1430
|
+
}
|
|
1431
|
+
this.rootContext.registerItem(this);
|
|
1432
|
+
this.group?.registerItem(this);
|
|
1433
|
+
});
|
|
1434
|
+
destroyRef.onDestroy(() => {
|
|
1435
|
+
if (this.virtualized()) {
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
this.rootContext.unregisterItem(this);
|
|
1439
|
+
this.group?.unregisterItem(this);
|
|
1440
|
+
});
|
|
1441
|
+
// Keep the highlighted option in view while navigating a scrollable popup. `block: 'nearest'`
|
|
1442
|
+
// makes hover a no-op (the item is already visible) and only scrolls on keyboard navigation.
|
|
1443
|
+
afterRenderEffect(() => {
|
|
1444
|
+
if (!this.virtualized() && this.isHighlighted()) {
|
|
1445
|
+
this.element.scrollIntoView({ block: 'nearest' });
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
onPointerDown(event) {
|
|
1450
|
+
// Keep focus on the input; prevent the item from stealing focus on pointer/mouse down.
|
|
1451
|
+
event.preventDefault();
|
|
1452
|
+
this.rootContext.setKeyboardActive(false);
|
|
1453
|
+
}
|
|
1454
|
+
onPointerUp() {
|
|
1455
|
+
if (this.virtualized()) {
|
|
1456
|
+
this.rootContext.selectIndex(this.index() ?? -1);
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.rootContext.select(this);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
onPointerMove() {
|
|
1463
|
+
// Hover highlighting disabled: leave `data-highlighted` to keyboard/auto-highlight only.
|
|
1464
|
+
if (!this.rootContext.highlightItemOnHover()) {
|
|
1465
|
+
return;
|
|
1466
|
+
}
|
|
1467
|
+
// Ignore the first move after keyboard navigation: arrow keys scroll the list under a still
|
|
1468
|
+
// cursor, and the resulting pointer event must not yank the highlight off the keyboard target.
|
|
1469
|
+
if (this.rootContext.isKeyboardActive()) {
|
|
1470
|
+
this.rootContext.setKeyboardActive(false);
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
if (this.disabled()) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (this.virtualized()) {
|
|
1477
|
+
this.rootContext.highlightIndex(this.index() ?? -1, 'pointer');
|
|
1478
|
+
}
|
|
1479
|
+
else if (this.isVisible()) {
|
|
1480
|
+
this.rootContext.setHighlight(this, 'pointer');
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
// Clear a pointer-driven highlight when the cursor leaves the list (unless `keepHighlight`).
|
|
1484
|
+
// Moving to another element inside the list keeps it (the next item's move re-highlights).
|
|
1485
|
+
onPointerLeave(event) {
|
|
1486
|
+
if (event.pointerType === 'touch' ||
|
|
1487
|
+
!this.rootContext.open() ||
|
|
1488
|
+
!this.rootContext.highlightItemOnHover() ||
|
|
1489
|
+
this.rootContext.keepHighlight()) {
|
|
1490
|
+
return;
|
|
1491
|
+
}
|
|
1492
|
+
const related = event.relatedTarget;
|
|
1493
|
+
const list = related && document.getElementById(this.rootContext.listId);
|
|
1494
|
+
if (list && list.contains(related)) {
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
this.rootContext.clearHighlight();
|
|
1498
|
+
}
|
|
1499
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1500
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxItem, isStandalone: true, selector: "[rdxComboboxItem]", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: true, 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: { attributes: { "role": "option" }, listeners: { "pointerdown": "onPointerDown($event)", "mousedown": "onPointerDown($event)", "pointerup": "onPointerUp()", "pointermove": "onPointerMove()", "pointerleave": "onPointerLeave($event)" }, properties: { "attr.id": "elementId()", "attr.aria-selected": "isSelected()", "attr.aria-disabled": "disabled() ? \"true\" : undefined", "attr.aria-setsize": "ariaSetSize()", "attr.aria-posinset": "ariaPosInSet()", "attr.data-selected": "isSelected() ? \"\" : undefined", "attr.data-highlighted": "isHighlighted() ? \"\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined", "hidden": "!isVisible()", "attr.data-hidden": "isVisible() ? undefined : \"\"" } }, providers: [provideComboboxItemContext(itemContext)], exportAs: ["rdxComboboxItem"], ngImport: i0 }); }
|
|
1501
|
+
}
|
|
1502
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxItem, decorators: [{
|
|
1503
|
+
type: Directive,
|
|
1504
|
+
args: [{
|
|
1505
|
+
selector: '[rdxComboboxItem]',
|
|
1506
|
+
exportAs: 'rdxComboboxItem',
|
|
1507
|
+
providers: [provideComboboxItemContext(itemContext)],
|
|
1508
|
+
host: {
|
|
1509
|
+
role: 'option',
|
|
1510
|
+
'[attr.id]': 'elementId()',
|
|
1511
|
+
'[attr.aria-selected]': 'isSelected()',
|
|
1512
|
+
'[attr.aria-disabled]': 'disabled() ? "true" : undefined',
|
|
1513
|
+
'[attr.aria-setsize]': 'ariaSetSize()',
|
|
1514
|
+
'[attr.aria-posinset]': 'ariaPosInSet()',
|
|
1515
|
+
'[attr.data-selected]': 'isSelected() ? "" : undefined',
|
|
1516
|
+
'[attr.data-highlighted]': 'isHighlighted() ? "" : undefined',
|
|
1517
|
+
'[attr.data-disabled]': 'disabled() ? "" : undefined',
|
|
1518
|
+
'[hidden]': '!isVisible()',
|
|
1519
|
+
'[attr.data-hidden]': 'isVisible() ? undefined : ""',
|
|
1520
|
+
'(pointerdown)': 'onPointerDown($event)',
|
|
1521
|
+
'(mousedown)': 'onPointerDown($event)',
|
|
1522
|
+
'(pointerup)': 'onPointerUp()',
|
|
1523
|
+
'(pointermove)': 'onPointerMove()',
|
|
1524
|
+
'(pointerleave)': 'onPointerLeave($event)'
|
|
1525
|
+
}
|
|
1526
|
+
}]
|
|
1527
|
+
}], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], 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 }] }] } });
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Renders only when its item is selected (e.g. a checkmark).
|
|
1531
|
+
*
|
|
1532
|
+
* @group Components
|
|
1533
|
+
*/
|
|
1534
|
+
class RdxComboboxItemIndicator {
|
|
1535
|
+
constructor() {
|
|
1536
|
+
this.itemContext = injectComboboxItemContext();
|
|
1537
|
+
}
|
|
1538
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxItemIndicator, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1539
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxItemIndicator, isStandalone: true, selector: "[rdxComboboxItemIndicator]", host: { attributes: { "aria-hidden": "true" }, properties: { "hidden": "!itemContext.isSelected()" } }, exportAs: ["rdxComboboxItemIndicator"], ngImport: i0 }); }
|
|
1540
|
+
}
|
|
1541
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxItemIndicator, decorators: [{
|
|
1542
|
+
type: Directive,
|
|
1543
|
+
args: [{
|
|
1544
|
+
selector: '[rdxComboboxItemIndicator]',
|
|
1545
|
+
exportAs: 'rdxComboboxItemIndicator',
|
|
1546
|
+
host: {
|
|
1547
|
+
'aria-hidden': 'true',
|
|
1548
|
+
'[hidden]': '!itemContext.isSelected()'
|
|
1549
|
+
}
|
|
1550
|
+
}]
|
|
1551
|
+
}] });
|
|
1552
|
+
|
|
1553
|
+
/**
|
|
1554
|
+
* An accessible label for the combobox. Registers its id so the input (and trigger) reference it via
|
|
1555
|
+
* `aria-labelledby`.
|
|
1556
|
+
*
|
|
1557
|
+
* @group Components
|
|
1558
|
+
*/
|
|
1559
|
+
class RdxComboboxLabel {
|
|
1560
|
+
constructor() {
|
|
1561
|
+
this.rootContext = injectComboboxRootContext();
|
|
1562
|
+
this.id = injectId('rdx-combobox-label-');
|
|
1563
|
+
this.rootContext.setLabelId(this.id);
|
|
1564
|
+
inject(DestroyRef).onDestroy(() => this.rootContext.setLabelId(undefined));
|
|
1565
|
+
}
|
|
1566
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxLabel, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1567
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxLabel, isStandalone: true, selector: "[rdxComboboxLabel]", host: { properties: { "attr.id": "id" } }, exportAs: ["rdxComboboxLabel"], ngImport: i0 }); }
|
|
1568
|
+
}
|
|
1569
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxLabel, decorators: [{
|
|
1570
|
+
type: Directive,
|
|
1571
|
+
args: [{
|
|
1572
|
+
selector: '[rdxComboboxLabel]',
|
|
1573
|
+
exportAs: 'rdxComboboxLabel',
|
|
1574
|
+
host: {
|
|
1575
|
+
'[attr.id]': 'id'
|
|
1576
|
+
}
|
|
1577
|
+
}]
|
|
1578
|
+
}], ctorParameters: () => [] });
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* The listbox container for options. Carries the id referenced by the input's `aria-controls`.
|
|
1582
|
+
*
|
|
1583
|
+
* @group Components
|
|
1584
|
+
*/
|
|
1585
|
+
class RdxComboboxList {
|
|
1586
|
+
constructor() {
|
|
1587
|
+
this.rootContext = injectComboboxRootContext();
|
|
1588
|
+
}
|
|
1589
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxList, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1590
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxList, isStandalone: true, selector: "[rdxComboboxList]", host: { attributes: { "role": "listbox" }, properties: { "attr.id": "rootContext.listId", "attr.aria-multiselectable": "rootContext.multiple() ? \"true\" : undefined" } }, exportAs: ["rdxComboboxList"], ngImport: i0 }); }
|
|
1591
|
+
}
|
|
1592
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxList, decorators: [{
|
|
1593
|
+
type: Directive,
|
|
1594
|
+
args: [{
|
|
1595
|
+
selector: '[rdxComboboxList]',
|
|
1596
|
+
exportAs: 'rdxComboboxList',
|
|
1597
|
+
host: {
|
|
1598
|
+
role: 'listbox',
|
|
1599
|
+
'[attr.id]': 'rootContext.listId',
|
|
1600
|
+
'[attr.aria-multiselectable]': 'rootContext.multiple() ? "true" : undefined'
|
|
1601
|
+
}
|
|
1602
|
+
}]
|
|
1603
|
+
}] });
|
|
1604
|
+
|
|
1605
|
+
/**
|
|
1606
|
+
* The popup surface. Composes the popper content (for `data-side` / `data-align`) and a dismissable
|
|
1607
|
+
* layer for outside-dismiss. It does **not** trap focus — focus stays in the input throughout.
|
|
1608
|
+
*
|
|
1609
|
+
* @group Components
|
|
1610
|
+
*/
|
|
1611
|
+
class RdxComboboxPopup {
|
|
1612
|
+
constructor() {
|
|
1613
|
+
this.rootContext = injectComboboxRootContext();
|
|
1614
|
+
this.dismissableLayer = inject(RdxDismissableLayer);
|
|
1615
|
+
this.popper = injectPopperContentWrapperContext();
|
|
1616
|
+
this.element = inject(ElementRef).nativeElement;
|
|
1617
|
+
// The popup mounts only while open, so locking on `modal` locks scroll for as long as a modal
|
|
1618
|
+
// popup is open and releases it on close.
|
|
1619
|
+
useScrollLock(this.rootContext.modal);
|
|
1620
|
+
// The popup's animation determines when the open/close transition (onOpenChangeComplete) is done.
|
|
1621
|
+
const unregister = this.rootContext.registerTransitionElement(this.element);
|
|
1622
|
+
inject(DestroyRef).onDestroy(unregister);
|
|
1623
|
+
// The input keeps focus while the popup is open; it is registered as a layer branch, so
|
|
1624
|
+
// focus/pointer interactions on it don't count as "outside" and won't self-dismiss. Escape
|
|
1625
|
+
// is handled by the input (which calls preventDefault), so the layer won't dismiss for it.
|
|
1626
|
+
this.dismissableLayer.dismiss.subscribe(() => this.rootContext.closePopup(true));
|
|
1627
|
+
// For the "input inside the popup" pattern, move focus to the input once the popup is
|
|
1628
|
+
// positioned. Use `afterRenderEffect` (not `effect`): when `isPositioned` flips true the
|
|
1629
|
+
// popup's final position/visibility is applied in the *following* render, so a synchronous
|
|
1630
|
+
// `effect` would call `focus()` while the element is still unfocusable and silently no-op.
|
|
1631
|
+
afterRenderEffect(() => {
|
|
1632
|
+
if (!this.popper.isPositioned() || !this.rootContext.open()) {
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
const input = this.rootContext.inputElement();
|
|
1636
|
+
if (input && input.closest('[rdxComboboxPopup]')) {
|
|
1637
|
+
input.focus();
|
|
1638
|
+
input.select();
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPopup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1643
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxPopup, isStandalone: true, selector: "[rdxComboboxPopup]", host: { properties: { "attr.data-state": "rootContext.open() ? \"open\" : \"closed\"", "attr.data-open": "rootContext.open() ? \"\" : undefined", "attr.data-closed": "rootContext.open() ? undefined : \"\"", "attr.data-starting-style": "rootContext.transitionStatus() === \"starting\" ? \"\" : undefined", "attr.data-ending-style": "rootContext.transitionStatus() === \"ending\" ? \"\" : undefined" } }, providers: [
|
|
1644
|
+
// In modal mode, make content outside the popup inert (Base UI's `modal`).
|
|
1645
|
+
provideRdxDismissableLayerConfig(() => ({ disableOutsidePointerEvents: injectComboboxRootContext().modal }))
|
|
1646
|
+
], exportAs: ["rdxComboboxPopup"], hostDirectives: [{ directive: i1.RdxPopperContent }, { directive: i1$1.RdxDismissableLayer }], ngImport: i0 }); }
|
|
1647
|
+
}
|
|
1648
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPopup, decorators: [{
|
|
1649
|
+
type: Directive,
|
|
1650
|
+
args: [{
|
|
1651
|
+
selector: '[rdxComboboxPopup]',
|
|
1652
|
+
exportAs: 'rdxComboboxPopup',
|
|
1653
|
+
hostDirectives: [RdxPopperContent, RdxDismissableLayer],
|
|
1654
|
+
providers: [
|
|
1655
|
+
// In modal mode, make content outside the popup inert (Base UI's `modal`).
|
|
1656
|
+
provideRdxDismissableLayerConfig(() => ({ disableOutsidePointerEvents: injectComboboxRootContext().modal }))
|
|
1657
|
+
],
|
|
1658
|
+
host: {
|
|
1659
|
+
'[attr.data-state]': 'rootContext.open() ? "open" : "closed"',
|
|
1660
|
+
'[attr.data-open]': 'rootContext.open() ? "" : undefined',
|
|
1661
|
+
'[attr.data-closed]': 'rootContext.open() ? undefined : ""',
|
|
1662
|
+
'[attr.data-starting-style]': 'rootContext.transitionStatus() === "starting" ? "" : undefined',
|
|
1663
|
+
'[attr.data-ending-style]': 'rootContext.transitionStatus() === "ending" ? "" : undefined'
|
|
1664
|
+
}
|
|
1665
|
+
}]
|
|
1666
|
+
}], ctorParameters: () => [] });
|
|
1667
|
+
|
|
1668
|
+
/**
|
|
1669
|
+
* Structural directive that teleports the combobox popup into a container (default `document.body`)
|
|
1670
|
+
* while the combobox is open, and keeps it mounted until any CSS exit `@keyframes` finishes.
|
|
1671
|
+
*
|
|
1672
|
+
* Apply it with the `*` microsyntax on the positioner —
|
|
1673
|
+
* `<div *rdxComboboxPortal rdxComboboxPositioner>` — or as an explicit `<ng-template rdxComboboxPortal>`.
|
|
1674
|
+
* For a custom container use the explicit form with `[container]`.
|
|
1675
|
+
*
|
|
1676
|
+
* @group Components
|
|
1677
|
+
*/
|
|
1678
|
+
class RdxComboboxPortal {
|
|
1679
|
+
constructor() {
|
|
1680
|
+
/**
|
|
1681
|
+
* Optional container to portal the content into. Defaults to `document.body`. Declared here (and
|
|
1682
|
+
* forwarded to the composed {@link RdxPortalPresence}) so the autocomplete portal can re-expose it
|
|
1683
|
+
* through its own `hostDirectives`.
|
|
1684
|
+
*/
|
|
1685
|
+
this.container = input(...(ngDevMode ? [undefined, { debugName: "container" }] : /* istanbul ignore next */ []));
|
|
1686
|
+
}
|
|
1687
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1688
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxPortal, isStandalone: true, selector: "ng-template[rdxComboboxPortal]", inputs: { container: { classPropertyName: "container", publicName: "container", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideRdxPresenceContext(() => ({ present: injectComboboxRootContext().open }))], exportAs: ["rdxComboboxPortal"], hostDirectives: [{ directive: i1$2.RdxPortalPresence, inputs: ["container", "container"] }], ngImport: i0 }); }
|
|
1689
|
+
}
|
|
1690
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPortal, decorators: [{
|
|
1691
|
+
type: Directive,
|
|
1692
|
+
args: [{
|
|
1693
|
+
selector: 'ng-template[rdxComboboxPortal]',
|
|
1694
|
+
exportAs: 'rdxComboboxPortal',
|
|
1695
|
+
hostDirectives: [{ directive: RdxPortalPresence, inputs: ['container'] }],
|
|
1696
|
+
providers: [provideRdxPresenceContext(() => ({ present: injectComboboxRootContext().open }))]
|
|
1697
|
+
}]
|
|
1698
|
+
}], propDecorators: { container: [{ type: i0.Input, args: [{ isSignal: true, alias: "container", required: false }] }] } });
|
|
1699
|
+
/**
|
|
1700
|
+
* Dev-mode guard: `rdxComboboxPortal` used to be an attribute directive on a `<div>`. It is now
|
|
1701
|
+
* structural, so the old `<div rdxComboboxPortal>` markup would silently stop portaling — fail loudly
|
|
1702
|
+
* instead.
|
|
1703
|
+
*
|
|
1704
|
+
* @group Components
|
|
1705
|
+
*/
|
|
1706
|
+
class RdxComboboxPortalMisuseGuard {
|
|
1707
|
+
constructor() {
|
|
1708
|
+
if (isDevMode()) {
|
|
1709
|
+
throw new Error('[rdxComboboxPortal] is now a structural directive. ' +
|
|
1710
|
+
'Use `*rdxComboboxPortal` on the positioner element or `<ng-template rdxComboboxPortal>`. ' +
|
|
1711
|
+
'rdxComboboxPortalPresence has been removed. See https://radix-ng.com/components/combobox.md');
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPortalMisuseGuard, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1715
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxPortalMisuseGuard, isStandalone: true, selector: "[rdxComboboxPortal]:not(ng-template)", ngImport: i0 }); }
|
|
1716
|
+
}
|
|
1717
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPortalMisuseGuard, decorators: [{
|
|
1718
|
+
type: Directive,
|
|
1719
|
+
args: [{
|
|
1720
|
+
selector: '[rdxComboboxPortal]:not(ng-template)'
|
|
1721
|
+
}]
|
|
1722
|
+
}], ctorParameters: () => [] });
|
|
1723
|
+
|
|
1724
|
+
/**
|
|
1725
|
+
* Positions the popup relative to the input anchor using the popper engine. Re-exposes the popper
|
|
1726
|
+
* positioning inputs.
|
|
1727
|
+
*
|
|
1728
|
+
* @group Components
|
|
1729
|
+
*/
|
|
1730
|
+
class RdxComboboxPositioner {
|
|
1731
|
+
constructor() {
|
|
1732
|
+
this.side = input('bottom', ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
|
|
1733
|
+
this.sideOffset = input(4, { ...(ngDevMode ? { debugName: "sideOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1734
|
+
this.align = input('start', ...(ngDevMode ? [{ debugName: "align" }] : /* istanbul ignore next */ []));
|
|
1735
|
+
this.alignOffset = input(0, { ...(ngDevMode ? { debugName: "alignOffset" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1736
|
+
this.arrowPadding = input(0, { ...(ngDevMode ? { debugName: "arrowPadding" } : /* istanbul ignore next */ {}), transform: numberAttribute });
|
|
1737
|
+
this.avoidCollisions = input(true, { ...(ngDevMode ? { debugName: "avoidCollisions" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1738
|
+
this.collisionBoundary = input(...(ngDevMode ? [undefined, { debugName: "collisionBoundary" }] : /* istanbul ignore next */ []));
|
|
1739
|
+
this.collisionPadding = input(0, ...(ngDevMode ? [{ debugName: "collisionPadding" }] : /* istanbul ignore next */ []));
|
|
1740
|
+
this.sticky = input('partial', ...(ngDevMode ? [{ debugName: "sticky" }] : /* istanbul ignore next */ []));
|
|
1741
|
+
this.hideWhenDetached = input(false, { ...(ngDevMode ? { debugName: "hideWhenDetached" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
1742
|
+
this.updatePositionStrategy = input('optimized', ...(ngDevMode ? [{ debugName: "updatePositionStrategy" }] : /* istanbul ignore next */ []));
|
|
1743
|
+
}
|
|
1744
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPositioner, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1745
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxPositioner, isStandalone: true, selector: "[rdxComboboxPositioner]", 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-combobox-content-transform-origin': 'var(--radix-popper-transform-origin)',\n '--radix-combobox-content-available-width': 'var(--radix-popper-available-width)',\n '--radix-combobox-content-available-height': 'var(--radix-popper-available-height)',\n '--radix-combobox-trigger-width': 'var(--radix-popper-anchor-width)',\n '--radix-combobox-trigger-height': 'var(--radix-popper-anchor-height)'\n }" } }, exportAs: ["rdxComboboxPositioner"], hostDirectives: [{ directive: i1.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 }); }
|
|
1746
|
+
}
|
|
1747
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxPositioner, decorators: [{
|
|
1748
|
+
type: Directive,
|
|
1749
|
+
args: [{
|
|
1750
|
+
selector: '[rdxComboboxPositioner]',
|
|
1751
|
+
exportAs: 'rdxComboboxPositioner',
|
|
1752
|
+
hostDirectives: [
|
|
1753
|
+
{
|
|
1754
|
+
directive: RdxPopperContentWrapper,
|
|
1755
|
+
inputs: [
|
|
1756
|
+
'side',
|
|
1757
|
+
'sideOffset',
|
|
1758
|
+
'align',
|
|
1759
|
+
'alignOffset',
|
|
1760
|
+
'arrowPadding',
|
|
1761
|
+
'avoidCollisions',
|
|
1762
|
+
'collisionBoundary',
|
|
1763
|
+
'collisionPadding',
|
|
1764
|
+
'sticky',
|
|
1765
|
+
'hideWhenDetached',
|
|
1766
|
+
'updatePositionStrategy'
|
|
1767
|
+
]
|
|
1768
|
+
}
|
|
1769
|
+
],
|
|
1770
|
+
host: {
|
|
1771
|
+
'[style]': `{
|
|
1772
|
+
'boxSizing': 'border-box',
|
|
1773
|
+
'--radix-combobox-content-transform-origin': 'var(--radix-popper-transform-origin)',
|
|
1774
|
+
'--radix-combobox-content-available-width': 'var(--radix-popper-available-width)',
|
|
1775
|
+
'--radix-combobox-content-available-height': 'var(--radix-popper-available-height)',
|
|
1776
|
+
'--radix-combobox-trigger-width': 'var(--radix-popper-anchor-width)',
|
|
1777
|
+
'--radix-combobox-trigger-height': 'var(--radix-popper-anchor-height)'
|
|
1778
|
+
}`
|
|
1779
|
+
}
|
|
1780
|
+
}]
|
|
1781
|
+
}], 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 }] }] } });
|
|
1782
|
+
|
|
1783
|
+
/**
|
|
1784
|
+
* A polite live region for async status (loading, result counts) announced without moving focus.
|
|
1785
|
+
*
|
|
1786
|
+
* @group Components
|
|
1787
|
+
*/
|
|
1788
|
+
class RdxComboboxStatus {
|
|
1789
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxStatus, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1790
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxStatus, isStandalone: true, selector: "[rdxComboboxStatus]", host: { attributes: { "role": "status", "aria-live": "polite" } }, exportAs: ["rdxComboboxStatus"], ngImport: i0 }); }
|
|
1791
|
+
}
|
|
1792
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxStatus, decorators: [{
|
|
1793
|
+
type: Directive,
|
|
1794
|
+
args: [{
|
|
1795
|
+
selector: '[rdxComboboxStatus]',
|
|
1796
|
+
exportAs: 'rdxComboboxStatus',
|
|
1797
|
+
host: {
|
|
1798
|
+
role: 'status',
|
|
1799
|
+
'aria-live': 'polite'
|
|
1800
|
+
}
|
|
1801
|
+
}]
|
|
1802
|
+
}] });
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Toggles the combobox popup. Carries `tabindex="-1"` so it never steals focus from the input.
|
|
1806
|
+
*
|
|
1807
|
+
* @group Components
|
|
1808
|
+
*/
|
|
1809
|
+
class RdxComboboxTrigger {
|
|
1810
|
+
constructor() {
|
|
1811
|
+
this.rootContext = injectComboboxRootContext();
|
|
1812
|
+
this.element = inject(ElementRef).nativeElement;
|
|
1813
|
+
this.rootContext.registerTrigger(this.element);
|
|
1814
|
+
inject(DestroyRef).onDestroy(() => this.rootContext.registerTrigger(null));
|
|
1815
|
+
}
|
|
1816
|
+
onClick() {
|
|
1817
|
+
if (this.rootContext.open()) {
|
|
1818
|
+
this.rootContext.closePopup(true);
|
|
1819
|
+
}
|
|
1820
|
+
else {
|
|
1821
|
+
this.rootContext.focusInput();
|
|
1822
|
+
this.rootContext.openForBrowse();
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1826
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: RdxComboboxTrigger, isStandalone: true, selector: "button[rdxComboboxTrigger]", host: { attributes: { "type": "button", "tabindex": "-1", "aria-label": "Open" }, listeners: { "click": "onClick()" }, properties: { "attr.aria-expanded": "rootContext.open()", "attr.aria-controls": "rootContext.listId", "attr.aria-labelledby": "rootContext.labelId()", "attr.disabled": "rootContext.disabledState() ? \"\" : undefined", "attr.data-popup-open": "rootContext.open() ? \"\" : undefined", "attr.data-disabled": "rootContext.disabledState() ? \"\" : undefined" } }, exportAs: ["rdxComboboxTrigger"], hostDirectives: [{ directive: i1$1.RdxDismissableLayerBranch }], ngImport: i0 }); }
|
|
1827
|
+
}
|
|
1828
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxTrigger, decorators: [{
|
|
1829
|
+
type: Directive,
|
|
1830
|
+
args: [{
|
|
1831
|
+
selector: 'button[rdxComboboxTrigger]',
|
|
1832
|
+
exportAs: 'rdxComboboxTrigger',
|
|
1833
|
+
hostDirectives: [RdxDismissableLayerBranch],
|
|
1834
|
+
host: {
|
|
1835
|
+
type: 'button',
|
|
1836
|
+
tabindex: '-1',
|
|
1837
|
+
'aria-label': 'Open',
|
|
1838
|
+
'[attr.aria-expanded]': 'rootContext.open()',
|
|
1839
|
+
'[attr.aria-controls]': 'rootContext.listId',
|
|
1840
|
+
'[attr.aria-labelledby]': 'rootContext.labelId()',
|
|
1841
|
+
'[attr.disabled]': 'rootContext.disabledState() ? "" : undefined',
|
|
1842
|
+
'[attr.data-popup-open]': 'rootContext.open() ? "" : undefined',
|
|
1843
|
+
'[attr.data-disabled]': 'rootContext.disabledState() ? "" : undefined',
|
|
1844
|
+
'(click)': 'onClick()'
|
|
1845
|
+
}
|
|
1846
|
+
}]
|
|
1847
|
+
}], ctorParameters: () => [] });
|
|
1848
|
+
|
|
1849
|
+
/**
|
|
1850
|
+
* Renders the current selection's label(s) — typically inside a {@link RdxComboboxTrigger} for a
|
|
1851
|
+
* select-like control. Read `slotText()` in the template, or compose your own from `selectedLabels()`.
|
|
1852
|
+
* Exposes `data-placeholder` while nothing is selected.
|
|
1853
|
+
*
|
|
1854
|
+
* @example
|
|
1855
|
+
* <span #value="rdxComboboxValue" rdxComboboxValue placeholder="Select…">{{ value.slotText() }}</span>
|
|
1856
|
+
*
|
|
1857
|
+
* @group Components
|
|
1858
|
+
*/
|
|
1859
|
+
class RdxComboboxValue {
|
|
1860
|
+
constructor() {
|
|
1861
|
+
this.rootContext = injectComboboxRootContext();
|
|
1862
|
+
/** Text shown when there is no selection. */
|
|
1863
|
+
this.placeholder = input(...(ngDevMode ? [undefined, { debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
1864
|
+
/** The label(s) of the current selection, resolved via the root's `itemToStringLabel`/items. */
|
|
1865
|
+
this.selectedLabels = computed(() => {
|
|
1866
|
+
const value = this.rootContext.value();
|
|
1867
|
+
if (Array.isArray(value)) {
|
|
1868
|
+
return value.map((v) => this.rootContext.labelFor(v)).filter(Boolean);
|
|
1869
|
+
}
|
|
1870
|
+
return isNullish(value) ? [] : [this.rootContext.labelFor(value)].filter(Boolean);
|
|
1871
|
+
}, ...(ngDevMode ? [{ debugName: "selectedLabels" }] : /* istanbul ignore next */ []));
|
|
1872
|
+
this.isEmpty = computed(() => this.selectedLabels().length === 0, ...(ngDevMode ? [{ debugName: "isEmpty" }] : /* istanbul ignore next */ []));
|
|
1873
|
+
/** The selection joined for display, or the `placeholder` when empty. */
|
|
1874
|
+
this.slotText = computed(() => {
|
|
1875
|
+
const labels = this.selectedLabels();
|
|
1876
|
+
return labels.length ? labels.join(', ') : (this.placeholder() ?? '');
|
|
1877
|
+
}, ...(ngDevMode ? [{ debugName: "slotText" }] : /* istanbul ignore next */ []));
|
|
1878
|
+
}
|
|
1879
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxValue, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
|
|
1880
|
+
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxComboboxValue, isStandalone: true, selector: "[rdxComboboxValue]", inputs: { placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.data-placeholder": "isEmpty() ? \"\" : undefined" } }, exportAs: ["rdxComboboxValue"], ngImport: i0 }); }
|
|
1881
|
+
}
|
|
1882
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxValue, decorators: [{
|
|
1883
|
+
type: Directive,
|
|
1884
|
+
args: [{
|
|
1885
|
+
selector: '[rdxComboboxValue]',
|
|
1886
|
+
exportAs: 'rdxComboboxValue',
|
|
1887
|
+
host: {
|
|
1888
|
+
'[attr.data-placeholder]': 'isEmpty() ? "" : undefined'
|
|
1889
|
+
}
|
|
1890
|
+
}]
|
|
1891
|
+
}], propDecorators: { placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }] } });
|
|
1892
|
+
|
|
1893
|
+
const _importsCombobox = [
|
|
1894
|
+
RdxComboboxRoot,
|
|
1895
|
+
RdxComboboxAnchor,
|
|
1896
|
+
RdxComboboxLabel,
|
|
1897
|
+
RdxComboboxInput,
|
|
1898
|
+
RdxComboboxValue,
|
|
1899
|
+
RdxComboboxTrigger,
|
|
1900
|
+
RdxComboboxIcon,
|
|
1901
|
+
RdxComboboxClear,
|
|
1902
|
+
RdxComboboxPortal,
|
|
1903
|
+
RdxComboboxPortalMisuseGuard,
|
|
1904
|
+
RdxComboboxBackdrop,
|
|
1905
|
+
RdxComboboxPositioner,
|
|
1906
|
+
RdxComboboxPopup,
|
|
1907
|
+
RdxComboboxArrow,
|
|
1908
|
+
RdxComboboxList,
|
|
1909
|
+
RdxComboboxItem,
|
|
1910
|
+
RdxComboboxItemIndicator,
|
|
1911
|
+
RdxComboboxGroup,
|
|
1912
|
+
RdxComboboxGroupLabel,
|
|
1913
|
+
RdxComboboxEmpty,
|
|
1914
|
+
RdxComboboxStatus,
|
|
1915
|
+
RdxComboboxChips,
|
|
1916
|
+
RdxComboboxChip,
|
|
1917
|
+
RdxComboboxChipRemove
|
|
1918
|
+
];
|
|
1919
|
+
class RdxComboboxModule {
|
|
1920
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
|
|
1921
|
+
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxModule, imports: [RdxComboboxRoot,
|
|
1922
|
+
RdxComboboxAnchor,
|
|
1923
|
+
RdxComboboxLabel,
|
|
1924
|
+
RdxComboboxInput,
|
|
1925
|
+
RdxComboboxValue,
|
|
1926
|
+
RdxComboboxTrigger,
|
|
1927
|
+
RdxComboboxIcon,
|
|
1928
|
+
RdxComboboxClear,
|
|
1929
|
+
RdxComboboxPortal,
|
|
1930
|
+
RdxComboboxPortalMisuseGuard,
|
|
1931
|
+
RdxComboboxBackdrop,
|
|
1932
|
+
RdxComboboxPositioner,
|
|
1933
|
+
RdxComboboxPopup,
|
|
1934
|
+
RdxComboboxArrow,
|
|
1935
|
+
RdxComboboxList,
|
|
1936
|
+
RdxComboboxItem,
|
|
1937
|
+
RdxComboboxItemIndicator,
|
|
1938
|
+
RdxComboboxGroup,
|
|
1939
|
+
RdxComboboxGroupLabel,
|
|
1940
|
+
RdxComboboxEmpty,
|
|
1941
|
+
RdxComboboxStatus,
|
|
1942
|
+
RdxComboboxChips,
|
|
1943
|
+
RdxComboboxChip,
|
|
1944
|
+
RdxComboboxChipRemove], exports: [RdxComboboxRoot,
|
|
1945
|
+
RdxComboboxAnchor,
|
|
1946
|
+
RdxComboboxLabel,
|
|
1947
|
+
RdxComboboxInput,
|
|
1948
|
+
RdxComboboxValue,
|
|
1949
|
+
RdxComboboxTrigger,
|
|
1950
|
+
RdxComboboxIcon,
|
|
1951
|
+
RdxComboboxClear,
|
|
1952
|
+
RdxComboboxPortal,
|
|
1953
|
+
RdxComboboxPortalMisuseGuard,
|
|
1954
|
+
RdxComboboxBackdrop,
|
|
1955
|
+
RdxComboboxPositioner,
|
|
1956
|
+
RdxComboboxPopup,
|
|
1957
|
+
RdxComboboxArrow,
|
|
1958
|
+
RdxComboboxList,
|
|
1959
|
+
RdxComboboxItem,
|
|
1960
|
+
RdxComboboxItemIndicator,
|
|
1961
|
+
RdxComboboxGroup,
|
|
1962
|
+
RdxComboboxGroupLabel,
|
|
1963
|
+
RdxComboboxEmpty,
|
|
1964
|
+
RdxComboboxStatus,
|
|
1965
|
+
RdxComboboxChips,
|
|
1966
|
+
RdxComboboxChip,
|
|
1967
|
+
RdxComboboxChipRemove] }); }
|
|
1968
|
+
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxModule }); }
|
|
1969
|
+
}
|
|
1970
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxComboboxModule, decorators: [{
|
|
1971
|
+
type: NgModule,
|
|
1972
|
+
args: [{
|
|
1973
|
+
imports: [..._importsCombobox],
|
|
1974
|
+
exports: [..._importsCombobox]
|
|
1975
|
+
}]
|
|
1976
|
+
}] });
|
|
1977
|
+
|
|
1978
|
+
/**
|
|
1979
|
+
* Generated bundle index. Do not edit.
|
|
1980
|
+
*/
|
|
1981
|
+
|
|
1982
|
+
export { RdxComboboxAnchor, RdxComboboxArrow, RdxComboboxBackdrop, RdxComboboxChip, RdxComboboxChipRemove, RdxComboboxChips, RdxComboboxClear, RdxComboboxEmpty, RdxComboboxGroup, RdxComboboxGroupLabel, RdxComboboxIcon, RdxComboboxInput, RdxComboboxItem, RdxComboboxItemIndicator, RdxComboboxLabel, RdxComboboxList, RdxComboboxModule, RdxComboboxPopup, RdxComboboxPortal, RdxComboboxPortalMisuseGuard, RdxComboboxPositioner, RdxComboboxRoot, RdxComboboxStatus, RdxComboboxTrigger, RdxComboboxValue, _importsCombobox, injectComboboxChipContext, injectComboboxGroupContext, injectComboboxItemContext, injectComboboxRootContext, provideComboboxChipContext, provideComboboxGroupContext, provideComboboxItemContext, provideComboboxRootContext };
|
|
1983
|
+
//# sourceMappingURL=radix-ng-primitives-combobox.mjs.map
|