@raintonic/formaui 0.4.0 → 0.9.0
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/CHANGELOG.md +80 -35
- package/README.md +22 -26
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +39 -41
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs +207 -3
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs +19 -1
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +5 -12
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-accordion.mjs +8 -5
- package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-alert.mjs +16 -2
- package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs +255 -462
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-avatar.mjs +34 -59
- package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-badge.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +4 -4
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-button-group.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-button.mjs +15 -20
- package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-card.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-checkbox.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-chip.mjs +97 -0
- package/fesm2022/raintonic-formaui-components-chip.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs +69 -29
- package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-date-picker.mjs +223 -144
- package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-divider.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-drawer.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-dropdown-menu.mjs +888 -0
- package/fesm2022/raintonic-formaui-components-dropdown-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-dual-tier-navigation.mjs +774 -0
- package/fesm2022/raintonic-formaui-components-dual-tier-navigation.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-file-upload.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-form-field.mjs +81 -50
- package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-icon.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-input.mjs +47 -12
- package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-list.mjs +4 -4
- package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-number-input.mjs +20 -12
- package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-paginator.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-password-input.mjs +35 -110
- package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-popover.mjs +3 -2
- package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-progressbar.mjs +3 -2
- package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-radio.mjs +5 -6
- package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-select.mjs +257 -412
- package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-side-panel.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-sidebar-nav-menu.mjs +525 -0
- package/fesm2022/raintonic-formaui-components-sidebar-nav-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-slider.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-spinner.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-stepper.mjs +50 -45
- package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-strength-meter.mjs +149 -0
- package/fesm2022/raintonic-formaui-components-strength-meter.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-time-picker.mjs +194 -154
- package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-toggle-group.mjs +302 -0
- package/fesm2022/raintonic-formaui-components-toggle-group.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-toolbar.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-tooltip.mjs +10 -4
- package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-topbar.mjs +60 -0
- package/fesm2022/raintonic-formaui-components-topbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs +59 -69
- package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-tree-table.mjs +2 -2
- package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-components-tree.mjs +31 -5
- package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-core.mjs +279 -1
- package/fesm2022/raintonic-formaui-core.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-services-breakpoint.mjs +93 -0
- package/fesm2022/raintonic-formaui-services-breakpoint.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs +314 -16
- package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-services-notification.mjs +93 -29
- package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-services-theme.mjs +46 -196
- package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -1
- package/fesm2022/raintonic-formaui.mjs +1 -1
- package/fesm2022/raintonic-formaui.mjs.map +1 -1
- package/llms-full.txt +2329 -450
- package/llms.txt +36 -33
- package/package.json +42 -19
- package/styles/fonts/Geist-Bold.woff2 +0 -0
- package/styles/fonts/Geist-Italic.woff2 +0 -0
- package/styles/fonts/Geist-Light.woff2 +0 -0
- package/styles/fonts/Geist-Medium.woff2 +0 -0
- package/styles/fonts/Geist-Regular.woff2 +0 -0
- package/styles/fonts/Geist-SemiBold.woff2 +0 -0
- package/styles/fonts/GeistMono-Regular.woff2 +0 -0
- package/styles/generated/_tokens.scss +906 -0
- package/styles/index.scss +11 -10
- package/styles/partials/_brand.scss +46 -0
- package/styles/partials/_constants.scss +22 -20
- package/styles/partials/_fonts.scss +54 -10
- package/styles/partials/_grid.scss +29 -18
- package/styles/partials/_mixins.scss +69 -27
- package/styles/partials/_motion.scss +28 -33
- package/styles/partials/_theme.scss +28 -255
- package/styles/partials/_type.scss +117 -0
- package/styles/partials/_typography.scss +45 -45
- package/styles/partials/_utilities.scss +198 -98
- package/styles/partials/components/_button.scss +144 -75
- package/styles/partials/components/_dialog.scss +181 -180
- package/styles/partials/components/_overlay.scss +87 -87
- package/styles/partials/themes/_dark.scss +3 -268
- package/styles/partials/themes/_light.scss +4 -268
- package/styles/styles.css +7744 -0
- package/styles/styles.entry.scss +3 -0
- package/styles/utilities.css +4802 -0
- package/styles/utilities.entry.scss +3 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts +0 -1
- package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -1
- package/types/raintonic-formaui-cdk-form-field.d.ts +118 -2
- package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -1
- package/types/raintonic-formaui-cdk-overlay.d.ts +2 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -1
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +0 -1
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -1
- package/types/raintonic-formaui-components-accordion.d.ts +1 -1
- package/types/raintonic-formaui-components-accordion.d.ts.map +1 -1
- package/types/raintonic-formaui-components-alert.d.ts +6 -1
- package/types/raintonic-formaui-components-alert.d.ts.map +1 -1
- package/types/raintonic-formaui-components-autocomplete.d.ts +73 -116
- package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -1
- package/types/raintonic-formaui-components-avatar.d.ts +13 -31
- package/types/raintonic-formaui-components-avatar.d.ts.map +1 -1
- package/types/raintonic-formaui-components-button.d.ts +4 -10
- package/types/raintonic-formaui-components-button.d.ts.map +1 -1
- package/types/raintonic-formaui-components-chip.d.ts +43 -0
- package/types/raintonic-formaui-components-chip.d.ts.map +1 -0
- package/types/raintonic-formaui-components-data-table.d.ts +48 -11
- package/types/raintonic-formaui-components-data-table.d.ts.map +1 -1
- package/types/raintonic-formaui-components-date-picker.d.ts +59 -23
- package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -1
- package/types/raintonic-formaui-components-dropdown-menu.d.ts +394 -0
- package/types/raintonic-formaui-components-dropdown-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-dual-tier-navigation.d.ts +87 -0
- package/types/raintonic-formaui-components-dual-tier-navigation.d.ts.map +1 -0
- package/types/raintonic-formaui-components-form-field.d.ts +51 -21
- package/types/raintonic-formaui-components-form-field.d.ts.map +1 -1
- package/types/raintonic-formaui-components-input.d.ts +20 -11
- package/types/raintonic-formaui-components-input.d.ts.map +1 -1
- package/types/raintonic-formaui-components-number-input.d.ts +5 -3
- package/types/raintonic-formaui-components-number-input.d.ts.map +1 -1
- package/types/raintonic-formaui-components-password-input.d.ts +18 -32
- package/types/raintonic-formaui-components-password-input.d.ts.map +1 -1
- package/types/raintonic-formaui-components-popover.d.ts.map +1 -1
- package/types/raintonic-formaui-components-progressbar.d.ts +1 -1
- package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -1
- package/types/raintonic-formaui-components-radio.d.ts +1 -2
- package/types/raintonic-formaui-components-radio.d.ts.map +1 -1
- package/types/raintonic-formaui-components-select.d.ts +107 -76
- package/types/raintonic-formaui-components-select.d.ts.map +1 -1
- package/types/raintonic-formaui-components-sidebar-nav-menu.d.ts +223 -0
- package/types/raintonic-formaui-components-sidebar-nav-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-stepper.d.ts +4 -2
- package/types/raintonic-formaui-components-stepper.d.ts.map +1 -1
- package/types/raintonic-formaui-components-strength-meter.d.ts +78 -0
- package/types/raintonic-formaui-components-strength-meter.d.ts.map +1 -0
- package/types/raintonic-formaui-components-time-picker.d.ts +44 -24
- package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -1
- package/types/raintonic-formaui-components-toggle-group.d.ts +100 -0
- package/types/raintonic-formaui-components-toggle-group.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tooltip.d.ts +2 -1
- package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -1
- package/types/raintonic-formaui-components-topbar.d.ts +48 -0
- package/types/raintonic-formaui-components-topbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-select.d.ts +25 -9
- package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -1
- package/types/raintonic-formaui-components-tree.d.ts +12 -1
- package/types/raintonic-formaui-components-tree.d.ts.map +1 -1
- package/types/raintonic-formaui-core.d.ts +243 -5
- package/types/raintonic-formaui-core.d.ts.map +1 -1
- package/types/raintonic-formaui-services-breakpoint.d.ts +44 -0
- package/types/raintonic-formaui-services-breakpoint.d.ts.map +1 -0
- package/types/raintonic-formaui-services-dialog.d.ts +141 -2
- package/types/raintonic-formaui-services-dialog.d.ts.map +1 -1
- package/types/raintonic-formaui-services-notification.d.ts +24 -2
- package/types/raintonic-formaui-services-notification.d.ts.map +1 -1
- package/types/raintonic-formaui-services-theme.d.ts +13 -103
- package/types/raintonic-formaui-services-theme.d.ts.map +1 -1
- package/types/raintonic-formaui.d.ts +1 -1
- package/fesm2022/raintonic-formaui-components-big-menu.mjs +0 -86
- package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +0 -1
- package/fesm2022/raintonic-formaui-components-menu.mjs +0 -896
- package/fesm2022/raintonic-formaui-components-menu.mjs.map +0 -1
- package/fesm2022/raintonic-formaui-components-sidebar.mjs +0 -275
- package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +0 -1
- package/fesm2022/raintonic-formaui-components-tag.mjs +0 -95
- package/fesm2022/raintonic-formaui-components-tag.mjs.map +0 -1
- package/styles/_fonts-entry.scss +0 -3
- package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
- package/styles/fonts/inter-tight-latin.woff2 +0 -0
- package/types/raintonic-formaui-components-big-menu.d.ts +0 -73
- package/types/raintonic-formaui-components-big-menu.d.ts.map +0 -1
- package/types/raintonic-formaui-components-menu.d.ts +0 -403
- package/types/raintonic-formaui-components-menu.d.ts.map +0 -1
- package/types/raintonic-formaui-components-sidebar.d.ts +0 -185
- package/types/raintonic-formaui-components-sidebar.d.ts.map +0 -1
- package/types/raintonic-formaui-components-tag.d.ts +0 -43
- package/types/raintonic-formaui-components-tag.d.ts.map +0 -1
|
@@ -1,26 +1,25 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, InjectionToken, inject,
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import { filter } from 'rxjs/operators';
|
|
8
|
-
import { injectNgControl, updateErrorState, syncRequiredState, syncNgControlDisabled } from '@raintonic/formaui/cdk/form-field';
|
|
2
|
+
import { Injectable, InjectionToken, inject, input, booleanAttribute, output, signal, computed, contentChildren, effect, ViewChild, ViewEncapsulation, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
3
|
+
import { ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
4
|
+
import { Subject } from 'rxjs';
|
|
5
|
+
import * as i1 from '@raintonic/formaui/cdk/form-field';
|
|
6
|
+
import { FuiPopupOverlayDirective, FuiFormControlSyncDirective, injectNgControl } from '@raintonic/formaui/cdk/form-field';
|
|
9
7
|
import { FuiIconComponent } from '@raintonic/formaui/components/icon';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { FUI_FORM_FIELD, FuiFormFieldComponent, FuiPrefixDirective } from '@raintonic/formaui/components/form-field';
|
|
9
|
+
import { FuiInputDirective } from '@raintonic/formaui/components/input';
|
|
10
|
+
import { FuiIntlBase, isEmpty, noop, computeMultiDisplayValue, syncMultiOptions, applyOptionSelection, getEnabledOptions, computePagedActiveOptionIndex, computeNextActiveOptionIndex, scrollOptionIntoView, findInitialActiveOptionIndex, computeActiveDescendant, announceMessage, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
|
|
11
|
+
import { FUI_OPTION, FUI_SELECT } from '@raintonic/formaui/components/select';
|
|
14
12
|
|
|
15
13
|
class FuiAutocompleteIntl extends FuiIntlBase {
|
|
16
|
-
|
|
17
|
-
refreshButtonLabel = 'Refresh';
|
|
18
|
-
noOptionsText = 'No options found';
|
|
14
|
+
/** Placeholder text for the search input inside the panel. */
|
|
19
15
|
searchPlaceholder = 'Search...';
|
|
16
|
+
/** Aria label for the clear selection button. */
|
|
20
17
|
clearSelectionAriaLabel = 'Clear selection';
|
|
21
|
-
|
|
18
|
+
/** Text shown when the search filter has no matching options. */
|
|
19
|
+
noResultsText = 'No results found';
|
|
20
|
+
/** Announced via live region when panel opens, indicating how many results are available. */
|
|
22
21
|
resultsAvailableText(count) {
|
|
23
|
-
return `${count} results available.`;
|
|
22
|
+
return count === 1 ? '1 result available.' : `${count} results available.`;
|
|
24
23
|
}
|
|
25
24
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiAutocompleteIntl, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
|
|
26
25
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiAutocompleteIntl, providedIn: 'root' });
|
|
@@ -30,131 +29,86 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
30
29
|
args: [{ providedIn: 'root' }]
|
|
31
30
|
}] });
|
|
32
31
|
|
|
33
|
-
/**
|
|
34
|
-
* Injection token used to provide the parent autocomplete to options
|
|
35
|
-
*/
|
|
36
32
|
const FUI_AUTOCOMPLETE = new InjectionToken('FUI_AUTOCOMPLETE');
|
|
37
33
|
/**
|
|
38
34
|
* # fui-autocomplete Component
|
|
39
35
|
*
|
|
40
|
-
* An autocomplete component
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* add/refresh actions.
|
|
36
|
+
* An autocomplete component that works inside fui-form-field.
|
|
37
|
+
* Options are content-projected as `<fui-option>` children — same API as `<fui-select>`.
|
|
38
|
+
* Search filtering is always enabled, CSS-hiding non-matching options.
|
|
44
39
|
*
|
|
45
40
|
* ## Features
|
|
46
|
-
* -
|
|
47
|
-
* -
|
|
48
|
-
* -
|
|
49
|
-
* -
|
|
50
|
-
* -
|
|
51
|
-
* - Disabled and readonly states
|
|
52
|
-
* - Full accessibility support
|
|
53
|
-
* - Full keyboard navigation (Arrow keys, Enter, Escape, Home, End, PageUp, PageDown)
|
|
54
|
-
* - Uses overlay service for proper positioning
|
|
41
|
+
* - Content-projected `<fui-option>` children matching the `<fui-select>` API
|
|
42
|
+
* - Built-in search filtering — CSS-hides non-matching options
|
|
43
|
+
* - Single and multiple selection
|
|
44
|
+
* - Full keyboard navigation
|
|
45
|
+
* - Full Reactive Forms integration
|
|
55
46
|
*
|
|
56
47
|
* ## Usage
|
|
57
48
|
*
|
|
58
|
-
* ### Basic Autocomplete
|
|
49
|
+
* ### Basic Autocomplete
|
|
59
50
|
* ```html
|
|
60
51
|
* <fui-form-field>
|
|
61
|
-
* <label>
|
|
62
|
-
* <fui-autocomplete placeholder="
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
52
|
+
* <label>Framework</label>
|
|
53
|
+
* <fui-autocomplete placeholder="Select a framework" formControlName="framework">
|
|
54
|
+
* @for (fw of frameworks; track fw) {
|
|
55
|
+
* <fui-option [value]="fw">{{ fw.label }}</fui-option>
|
|
56
|
+
* }
|
|
66
57
|
* </fui-autocomplete>
|
|
67
58
|
* </fui-form-field>
|
|
68
59
|
* ```
|
|
69
60
|
*
|
|
70
|
-
* ###
|
|
71
|
-
* ```html
|
|
72
|
-
* <form [formGroup]="form">
|
|
73
|
-
* <fui-form-field>
|
|
74
|
-
* <label>Country</label>
|
|
75
|
-
* <fui-autocomplete formControlName="country" placeholder="Select a country">
|
|
76
|
-
* <fui-option value="us">United States</fui-option>
|
|
77
|
-
* <fui-option value="ca">Canada</fui-option>
|
|
78
|
-
* </fui-autocomplete>
|
|
79
|
-
* <fui-error *ngIf="form.get('country')?.hasError('required')">
|
|
80
|
-
* Country is required
|
|
81
|
-
* </fui-error>
|
|
82
|
-
* </fui-form-field>
|
|
83
|
-
* </form>
|
|
84
|
-
* ```
|
|
85
|
-
*
|
|
86
|
-
* ### With Add/Refresh Buttons
|
|
61
|
+
* ### Multiple Selection
|
|
87
62
|
* ```html
|
|
88
|
-
* <fui-
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
* [showRefreshButton]="true"
|
|
94
|
-
* (addNew)="openAddCategoryDialog()"
|
|
95
|
-
* (refresh)="refreshCategories()">
|
|
96
|
-
* @for (category of categories; track category.id) {
|
|
97
|
-
* <fui-option [value]="category.id">{{ category.name }}</fui-option>
|
|
98
|
-
* }
|
|
99
|
-
* </fui-autocomplete>
|
|
100
|
-
* </fui-form-field>
|
|
63
|
+
* <fui-autocomplete [multiple]="true" placeholder="Select tags" formControlName="tags">
|
|
64
|
+
* @for (tag of tags; track tag) {
|
|
65
|
+
* <fui-option [value]="tag">{{ tag }}</fui-option>
|
|
66
|
+
* }
|
|
67
|
+
* </fui-autocomplete>
|
|
101
68
|
* ```
|
|
102
69
|
*/
|
|
103
70
|
class FuiAutocompleteComponent {
|
|
104
|
-
// Static properties
|
|
105
71
|
static nextId = 0;
|
|
106
72
|
controlType = 'fui-autocomplete';
|
|
107
|
-
// Injected dependencies
|
|
73
|
+
// Injected dependencies
|
|
74
|
+
_popup = inject(FuiPopupOverlayDirective);
|
|
75
|
+
_formSync = inject(FuiFormControlSyncDirective);
|
|
76
|
+
_parentFormField = inject(FUI_FORM_FIELD, { optional: true });
|
|
108
77
|
intl = inject(FuiAutocompleteIntl);
|
|
109
|
-
|
|
110
|
-
// Inputs using signal-based API
|
|
78
|
+
// --- Inputs ---
|
|
111
79
|
placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
|
|
112
|
-
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
showRefreshButton = input(false, ...(ngDevMode ? [{ debugName: "showRefreshButton" }] : /* istanbul ignore next */ []));
|
|
116
|
-
addButtonLabel = input(undefined, ...(ngDevMode ? [{ debugName: "addButtonLabel" }] : /* istanbul ignore next */ []));
|
|
117
|
-
refreshButtonLabel = input(undefined, ...(ngDevMode ? [{ debugName: "refreshButtonLabel" }] : /* istanbul ignore next */ []));
|
|
118
|
-
noOptionsText = input(undefined, ...(ngDevMode ? [{ debugName: "noOptionsText" }] : /* istanbul ignore next */ []));
|
|
119
|
-
searchPlaceholder = input(undefined, ...(ngDevMode ? [{ debugName: "searchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
120
|
-
errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
121
|
-
// Resolved i18n labels: per-instance input wins, Intl service is fallback
|
|
122
|
-
resolvedAddButtonLabel = computed(() => this.addButtonLabel() ?? this.intl.addButtonLabel, ...(ngDevMode ? [{ debugName: "resolvedAddButtonLabel" }] : /* istanbul ignore next */ []));
|
|
123
|
-
resolvedRefreshButtonLabel = computed(() => this.refreshButtonLabel() ?? this.intl.refreshButtonLabel, ...(ngDevMode ? [{ debugName: "resolvedRefreshButtonLabel" }] : /* istanbul ignore next */ []));
|
|
124
|
-
resolvedNoOptionsText = computed(() => this.noOptionsText() ?? this.intl.noOptionsText, ...(ngDevMode ? [{ debugName: "resolvedNoOptionsText" }] : /* istanbul ignore next */ []));
|
|
125
|
-
resolvedSearchPlaceholder = computed(() => this.searchPlaceholder() ?? this.intl.searchPlaceholder, ...(ngDevMode ? [{ debugName: "resolvedSearchPlaceholder" }] : /* istanbul ignore next */ []));
|
|
80
|
+
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled', transform: booleanAttribute });
|
|
81
|
+
readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly', transform: booleanAttribute });
|
|
82
|
+
multiple = input(false, { ...(ngDevMode ? { debugName: "multiple" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
126
83
|
/**
|
|
127
|
-
*
|
|
84
|
+
* Comparison function for option values.
|
|
85
|
+
* Defaults to strict equality (`===`). Override when options use object
|
|
86
|
+
* values and you need to match by a property (e.g. comparing by `id`).
|
|
128
87
|
*/
|
|
129
88
|
compareWith = input((o1, o2) => o1 === o2, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
|
|
130
|
-
|
|
89
|
+
errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
90
|
+
// --- Outputs ---
|
|
131
91
|
valueChange = output();
|
|
132
92
|
selectionChange = output();
|
|
133
93
|
openedChange = output();
|
|
134
|
-
addNew = output();
|
|
135
|
-
refresh = output();
|
|
136
94
|
searchChange = output();
|
|
137
|
-
// Internal state
|
|
95
|
+
// --- Internal state ---
|
|
138
96
|
_value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
139
97
|
_focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
|
|
140
98
|
_disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
|
|
141
99
|
_readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
|
|
142
|
-
// FuiFormFieldControl implementation
|
|
143
100
|
stateChanges = new Subject();
|
|
144
101
|
_uid = `fui-autocomplete-${FuiAutocompleteComponent.nextId++}`;
|
|
145
102
|
_ariaDescribedby = null;
|
|
146
103
|
// Error state
|
|
147
104
|
_errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
|
|
148
105
|
errorState = this._errorState;
|
|
149
|
-
// Form control
|
|
150
|
-
_parentForm = inject(NgForm, { optional: true });
|
|
151
|
-
_parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
152
|
-
_defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
|
|
106
|
+
// Form control
|
|
153
107
|
_ngControlRef = injectNgControl();
|
|
154
108
|
get ngControl() {
|
|
155
109
|
return this._ngControlRef.ngControl;
|
|
156
110
|
}
|
|
157
|
-
//
|
|
111
|
+
// Computed interface properties
|
|
158
112
|
placeholder = computed(() => this.placeholderInput(), ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
159
113
|
_required = signal(false, ...(ngDevMode ? [{ debugName: "_required" }] : /* istanbul ignore next */ []));
|
|
160
114
|
required = this._required;
|
|
@@ -162,69 +116,39 @@ class FuiAutocompleteComponent {
|
|
|
162
116
|
focused = this._focused;
|
|
163
117
|
_ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
|
|
164
118
|
disabled = computed(() => this._disabled() || this.disabledInput() || this._ngControlDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
return val === null || val === undefined;
|
|
168
|
-
}, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
119
|
+
readonly = computed(() => this.readonlyInput() || this._readOnly(), ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
120
|
+
empty = computed(() => isEmpty(this._value()), ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
169
121
|
id = this._uid;
|
|
170
|
-
//
|
|
122
|
+
// --- View refs ---
|
|
171
123
|
trigger;
|
|
172
124
|
panel;
|
|
173
125
|
searchInput;
|
|
174
|
-
//
|
|
175
|
-
options = contentChildren(
|
|
176
|
-
// Panel
|
|
177
|
-
panelOpen =
|
|
178
|
-
// Search
|
|
126
|
+
// --- Content-projected options ---
|
|
127
|
+
options = contentChildren(FUI_OPTION, { ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), descendants: true });
|
|
128
|
+
// --- Panel state ---
|
|
129
|
+
panelOpen = computed(() => this._popup.panelOpen(), ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
|
|
130
|
+
// --- Search ---
|
|
179
131
|
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
180
|
-
// Active option index
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
this._liveAnnouncement.set('');
|
|
186
|
-
setTimeout(() => {
|
|
187
|
-
this._liveAnnouncement.set(message);
|
|
188
|
-
}, 50);
|
|
189
|
-
}
|
|
190
|
-
// Overlay reference
|
|
191
|
-
_overlayRef = null;
|
|
192
|
-
_overlaySubscriptions = new Subscription();
|
|
193
|
-
// Services
|
|
194
|
-
_overlayService = inject(FuiOverlayService);
|
|
195
|
-
_elementRef = inject(ElementRef);
|
|
196
|
-
_document = inject(DOCUMENT);
|
|
197
|
-
_ngZone = inject(NgZone);
|
|
198
|
-
_outsideClickSub;
|
|
199
|
-
// ControlValueAccessor callbacks
|
|
200
|
-
_onChange = () => {
|
|
201
|
-
// Intentionally empty: will be replaced by Angular forms
|
|
202
|
-
};
|
|
203
|
-
_onTouched = () => {
|
|
204
|
-
// Intentionally empty: will be replaced by Angular forms
|
|
205
|
-
};
|
|
206
|
-
// Computed properties
|
|
207
|
-
displayValue = computed(() => {
|
|
208
|
-
const currentValue = this.value();
|
|
209
|
-
const opts = this.options();
|
|
210
|
-
if (currentValue === null || currentValue === undefined) {
|
|
211
|
-
return '';
|
|
212
|
-
}
|
|
213
|
-
const selectedOption = opts.find((opt) => this.compareWith()(opt.value(), currentValue));
|
|
214
|
-
return selectedOption ? selectedOption.getLabel() : String(currentValue);
|
|
215
|
-
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
216
|
-
// Filtered options based on search term
|
|
217
|
-
filteredOptions = computed(() => {
|
|
132
|
+
// --- Active option index ---
|
|
133
|
+
activeIndex = signal(-1, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
|
|
134
|
+
// --- Visible (not filtered-out) options — single source of truth for search filtering ---
|
|
135
|
+
_visibleOptions = computed(() => {
|
|
136
|
+
const term = this.searchTerm();
|
|
218
137
|
const opts = this.options();
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
return opts.filter((opt) => !opt.disabled());
|
|
138
|
+
if (!term.trim()) {
|
|
139
|
+
return opts;
|
|
222
140
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
141
|
+
const lower = term.toLowerCase().trim();
|
|
142
|
+
return opts.filter((o) => o.getLabel().toLowerCase().includes(lower));
|
|
143
|
+
}, ...(ngDevMode ? [{ debugName: "_visibleOptions" }] : /* istanbul ignore next */ []));
|
|
144
|
+
_visibleOptionCount = computed(() => this._visibleOptions().length, ...(ngDevMode ? [{ debugName: "_visibleOptionCount" }] : /* istanbul ignore next */ []));
|
|
145
|
+
// --- Live announcement ---
|
|
146
|
+
_liveAnnouncement = signal('', ...(ngDevMode ? [{ debugName: "_liveAnnouncement" }] : /* istanbul ignore next */ []));
|
|
147
|
+
// --- ControlValueAccessor callbacks ---
|
|
148
|
+
_onChange = noop;
|
|
149
|
+
_onTouched = noop;
|
|
150
|
+
// --- Computed ---
|
|
151
|
+
displayValue = computed(() => computeMultiDisplayValue(this), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
228
152
|
constructor() {
|
|
229
153
|
// Set valueAccessor after NgControl is resolved
|
|
230
154
|
void Promise.resolve().then(() => {
|
|
@@ -232,14 +156,12 @@ class FuiAutocompleteComponent {
|
|
|
232
156
|
this._ngControlRef.ngControl.valueAccessor = this;
|
|
233
157
|
}
|
|
234
158
|
});
|
|
235
|
-
//
|
|
236
|
-
this.intl.changes.pipe(takeUntilDestroyed()).subscribe(() => { this._cdr.markForCheck(); });
|
|
237
|
-
// Effect to emit state changes
|
|
159
|
+
// State changes effect
|
|
238
160
|
effect(() => {
|
|
239
|
-
// Track all reactive inputs and internal signals
|
|
240
161
|
this.placeholderInput();
|
|
241
162
|
this.readonly();
|
|
242
163
|
this.disabledInput();
|
|
164
|
+
this.multiple();
|
|
243
165
|
this.errorStateMatcher();
|
|
244
166
|
this._focused();
|
|
245
167
|
this._disabled();
|
|
@@ -247,41 +169,76 @@ class FuiAutocompleteComponent {
|
|
|
247
169
|
this._ngControlDisabled();
|
|
248
170
|
this._required();
|
|
249
171
|
this._errorState();
|
|
250
|
-
// Emit state change
|
|
251
172
|
this.stateChanges.next();
|
|
252
173
|
});
|
|
253
|
-
//
|
|
174
|
+
// Sync option states when value or options change
|
|
254
175
|
effect(() => {
|
|
255
|
-
|
|
256
|
-
const opts = this.options();
|
|
257
|
-
const compareFn = this.compareWith();
|
|
258
|
-
opts.forEach((option) => {
|
|
259
|
-
const optValue = option.value();
|
|
260
|
-
const isSelected = compareFn(currentValue, optValue);
|
|
261
|
-
if (isSelected) {
|
|
262
|
-
option.select();
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
option.deselect();
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
// Notify form-field that state may have changed
|
|
176
|
+
syncMultiOptions(this);
|
|
269
177
|
this.stateChanges.next();
|
|
270
178
|
});
|
|
179
|
+
// Sync DOM visibility to match _visibleOptions computed signal
|
|
180
|
+
// One-way sync: computed signal is the source of truth, DOM is updated to match
|
|
181
|
+
effect(() => {
|
|
182
|
+
const term = this.searchTerm();
|
|
183
|
+
const opts = this.options();
|
|
184
|
+
const visible = this._visibleOptions();
|
|
185
|
+
const visibleSet = new Set(visible);
|
|
186
|
+
for (const opt of opts) {
|
|
187
|
+
opt._getHostElement().style.display = visibleSet.has(opt) ? '' : 'none';
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
// Announce visible results count when search term or panel state changes
|
|
191
|
+
effect(() => {
|
|
192
|
+
const term = this.searchTerm();
|
|
193
|
+
const visible = this._visibleOptions();
|
|
194
|
+
const isOpen = this._popup.panelOpen();
|
|
195
|
+
// Only announce when the panel is open and search filtering is active
|
|
196
|
+
if (isOpen && term.trim()) {
|
|
197
|
+
this._announce(this.intl.resultsAvailableText(visible.length));
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
// Focus management: move focus to search input when panel opens
|
|
201
|
+
let _panelInitialized = false;
|
|
202
|
+
effect(() => {
|
|
203
|
+
const isOpen = this._popup.panelOpen();
|
|
204
|
+
if (!_panelInitialized) {
|
|
205
|
+
_panelInitialized = true;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (isOpen) {
|
|
209
|
+
queueMicrotask(() => {
|
|
210
|
+
this.searchInput?.nativeElement.focus();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
271
214
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
215
|
+
ngAfterViewInit() {
|
|
216
|
+
this._popup.setTrigger(this.trigger ?? null);
|
|
217
|
+
this._popup.setPanel(this.panel ?? null);
|
|
218
|
+
this._popup.panelClass.set(['fui-autocomplete-overlay-panel']);
|
|
219
|
+
this._popup.backdropClass.set('fui-autocomplete-backdrop');
|
|
220
|
+
this._popup.positions.set([
|
|
221
|
+
{
|
|
222
|
+
originX: 'start',
|
|
223
|
+
originY: 'bottom',
|
|
224
|
+
overlayX: 'start',
|
|
225
|
+
overlayY: 'top',
|
|
226
|
+
offsetY: 16,
|
|
227
|
+
},
|
|
228
|
+
]);
|
|
229
|
+
if (this._parentFormField) {
|
|
230
|
+
this._popup.widthElement.set(this._parentFormField.getConnectedOverlayOrigin());
|
|
277
231
|
}
|
|
232
|
+
this._formSync.errorState.set(this._errorState);
|
|
233
|
+
this._formSync.errorStateMatcher.set(this.errorStateMatcher());
|
|
234
|
+
this._formSync.required.set(this._required);
|
|
235
|
+
this._formSync.ngControlDisabled.set(this._ngControlDisabled);
|
|
236
|
+
this._formSync.stateChanges.set(this.stateChanges);
|
|
278
237
|
}
|
|
279
238
|
ngOnDestroy() {
|
|
280
239
|
this.stateChanges.complete();
|
|
281
|
-
this._outsideClickSub?.unsubscribe();
|
|
282
|
-
this._disposeOverlay();
|
|
283
240
|
}
|
|
284
|
-
// ControlValueAccessor
|
|
241
|
+
// --- ControlValueAccessor ---
|
|
285
242
|
writeValue(value) {
|
|
286
243
|
this._value.set(value ?? null);
|
|
287
244
|
this.stateChanges.next();
|
|
@@ -296,11 +253,13 @@ class FuiAutocompleteComponent {
|
|
|
296
253
|
this._disabled.set(isDisabled);
|
|
297
254
|
this.stateChanges.next();
|
|
298
255
|
}
|
|
299
|
-
// FuiFormFieldControl
|
|
256
|
+
// --- FuiFormFieldControl ---
|
|
300
257
|
onContainerClick(_event) {
|
|
301
|
-
if (
|
|
302
|
-
|
|
303
|
-
|
|
258
|
+
if (this.disabled())
|
|
259
|
+
return;
|
|
260
|
+
if (this.multiple() && this.panelOpen())
|
|
261
|
+
return;
|
|
262
|
+
this.toggle();
|
|
304
263
|
}
|
|
305
264
|
setDescribedByIds(ids) {
|
|
306
265
|
this._ariaDescribedby = ids.length ? ids.join(' ') : null;
|
|
@@ -308,7 +267,8 @@ class FuiAutocompleteComponent {
|
|
|
308
267
|
setReadOnly(readOnly) {
|
|
309
268
|
this._readOnly.set(readOnly);
|
|
310
269
|
}
|
|
311
|
-
// Public
|
|
270
|
+
// --- Public API ---
|
|
271
|
+
/** Clears the current value. */
|
|
312
272
|
clear(event) {
|
|
313
273
|
event.stopPropagation();
|
|
314
274
|
this._value.set(null);
|
|
@@ -323,122 +283,88 @@ class FuiAutocompleteComponent {
|
|
|
323
283
|
blur() {
|
|
324
284
|
this.trigger?.nativeElement.blur();
|
|
325
285
|
}
|
|
326
|
-
|
|
327
|
-
toggle() {
|
|
328
|
-
if (this.disabled())
|
|
329
|
-
return;
|
|
330
|
-
if (this.panelOpen()) {
|
|
331
|
-
this.close();
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
this.open();
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
// Open the dropdown panel
|
|
286
|
+
/** Opens the panel. */
|
|
338
287
|
open() {
|
|
339
|
-
if (this.disabled() || this.readonly() || this.panelOpen())
|
|
288
|
+
if (this.disabled() || this.readonly() || this._popup.panelOpen())
|
|
340
289
|
return;
|
|
341
|
-
this.panelOpen.set(true);
|
|
342
290
|
this._focused.set(true);
|
|
343
291
|
this.searchTerm.set('');
|
|
344
|
-
this.openedChange.emit(true);
|
|
345
|
-
// Set active option to currently selected or first option
|
|
346
292
|
this._setInitialActiveOption();
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this._createOverlay();
|
|
293
|
+
this._popup.open();
|
|
294
|
+
if (this._popup.panelOpen()) {
|
|
350
295
|
this._scrollToActiveOption();
|
|
351
|
-
this.
|
|
352
|
-
//
|
|
353
|
-
this.
|
|
354
|
-
}
|
|
296
|
+
this.openedChange.emit(true);
|
|
297
|
+
// Announce how many results are available
|
|
298
|
+
this._announce(this.intl.resultsAvailableText(this.options().length));
|
|
299
|
+
}
|
|
355
300
|
}
|
|
356
|
-
|
|
357
|
-
close() {
|
|
358
|
-
if (!this.panelOpen())
|
|
301
|
+
/** Closes the panel. */
|
|
302
|
+
close(restoreFocus = true) {
|
|
303
|
+
if (!this._popup.panelOpen())
|
|
359
304
|
return;
|
|
360
|
-
this._outsideClickSub?.unsubscribe();
|
|
361
|
-
this.panelOpen.set(false);
|
|
362
305
|
this._focused.set(false);
|
|
363
|
-
this.
|
|
306
|
+
this.activeIndex.set(-1);
|
|
364
307
|
this.searchTerm.set('');
|
|
365
|
-
this._disposeOverlay();
|
|
366
|
-
this.openedChange.emit(false);
|
|
367
308
|
this._onTouched();
|
|
368
|
-
|
|
369
|
-
this.
|
|
370
|
-
|
|
371
|
-
});
|
|
372
|
-
// Return focus to trigger
|
|
373
|
-
setTimeout(() => {
|
|
309
|
+
this._popup.close();
|
|
310
|
+
this.openedChange.emit(false);
|
|
311
|
+
if (restoreFocus) {
|
|
374
312
|
this.trigger?.nativeElement.focus();
|
|
375
|
-
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/** Toggles the panel. */
|
|
316
|
+
toggle() {
|
|
317
|
+
if (this.disabled() || this.readonly())
|
|
318
|
+
return;
|
|
319
|
+
this._popup.toggle();
|
|
376
320
|
}
|
|
377
|
-
//
|
|
321
|
+
// --- Selection ---
|
|
322
|
+
/**
|
|
323
|
+
* Called by fui-option via FUI_SELECT._onOptionSelected.
|
|
324
|
+
* Implements FuiSelectParent.
|
|
325
|
+
*/
|
|
378
326
|
_onOptionSelected(option) {
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
const opts = this.options();
|
|
382
|
-
opts.forEach((opt) => {
|
|
383
|
-
if (opt !== option) {
|
|
384
|
-
opt.deselect();
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
option.select();
|
|
327
|
+
const result = applyOptionSelection(this, option);
|
|
328
|
+
const newValue = result.newValue;
|
|
388
329
|
this._value.set(newValue);
|
|
389
330
|
this._onChange(newValue);
|
|
390
331
|
this.valueChange.emit(newValue);
|
|
391
332
|
this.selectionChange.emit({ source: this, value: newValue });
|
|
392
|
-
this._announce(`${option.getLabel()} selected`);
|
|
393
|
-
this.close();
|
|
394
333
|
this.stateChanges.next();
|
|
334
|
+
if (result.wasSelected) {
|
|
335
|
+
this._announce(`${option.getLabel()} deselected`);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
this._announce(`${option.getLabel()} selected`);
|
|
339
|
+
}
|
|
340
|
+
if (!this.multiple()) {
|
|
341
|
+
this.close();
|
|
342
|
+
}
|
|
395
343
|
}
|
|
396
|
-
//
|
|
344
|
+
// --- Search ---
|
|
397
345
|
onSearchInput(event) {
|
|
398
346
|
const target = event.target;
|
|
399
347
|
const term = target.value;
|
|
400
348
|
this.searchTerm.set(term);
|
|
401
349
|
this.searchChange.emit(term);
|
|
402
|
-
// Reset active index
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
this._setActiveOptionIndex(0);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
this.activeOptionIndex.set(-1);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
// Handle add new action
|
|
412
|
-
onAddNew() {
|
|
413
|
-
this.close();
|
|
414
|
-
this.addNew.emit();
|
|
415
|
-
}
|
|
416
|
-
// Handle refresh action
|
|
417
|
-
onRefresh() {
|
|
418
|
-
this.refresh.emit();
|
|
419
|
-
// Keep panel open and clear search
|
|
420
|
-
this.searchTerm.set('');
|
|
421
|
-
}
|
|
422
|
-
// Get display value for trigger
|
|
423
|
-
_getDisplayValue() {
|
|
424
|
-
return this.displayValue();
|
|
350
|
+
// Reset active index to first visible enabled option
|
|
351
|
+
const enabled = getEnabledOptions(this._visibleOptions());
|
|
352
|
+
this.activeIndex.set(enabled.length > 0 ? 0 : -1);
|
|
425
353
|
}
|
|
426
|
-
//
|
|
354
|
+
// --- Keyboard Navigation ---
|
|
355
|
+
/** Entry point for trigger keydown */
|
|
427
356
|
_handleKeydown(event) {
|
|
428
357
|
if (this.disabled())
|
|
429
358
|
return;
|
|
430
|
-
|
|
431
|
-
if (!isOpen) {
|
|
359
|
+
if (!this.panelOpen()) {
|
|
432
360
|
this._handleClosedKeydown(event);
|
|
433
361
|
}
|
|
434
362
|
else {
|
|
435
|
-
this.
|
|
363
|
+
this._handlePanelKeydown(event);
|
|
436
364
|
}
|
|
437
365
|
}
|
|
438
|
-
// Handle keydown when panel is closed
|
|
439
366
|
_handleClosedKeydown(event) {
|
|
440
|
-
|
|
441
|
-
switch (key) {
|
|
367
|
+
switch (event.key) {
|
|
442
368
|
case 'Enter':
|
|
443
369
|
case ' ':
|
|
444
370
|
case 'ArrowDown':
|
|
@@ -448,254 +374,104 @@ class FuiAutocompleteComponent {
|
|
|
448
374
|
break;
|
|
449
375
|
}
|
|
450
376
|
}
|
|
451
|
-
|
|
377
|
+
/** Handle keydown inside the panel (search input or item list) */
|
|
452
378
|
_handlePanelKeydown(event) {
|
|
453
379
|
if (this.disabled())
|
|
454
380
|
return;
|
|
455
|
-
const
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
switch (key) {
|
|
381
|
+
const opts = this._getVisibleEnabledOptions();
|
|
382
|
+
const activeIdx = this.activeIndex();
|
|
383
|
+
switch (event.key) {
|
|
459
384
|
case 'ArrowDown':
|
|
460
385
|
event.preventDefault();
|
|
461
|
-
this.
|
|
386
|
+
this._setActiveOptionIndex(computeNextActiveOptionIndex(opts.length, activeIdx, 1));
|
|
462
387
|
break;
|
|
463
388
|
case 'ArrowUp':
|
|
464
389
|
event.preventDefault();
|
|
465
|
-
this.
|
|
390
|
+
this._setActiveOptionIndex(computeNextActiveOptionIndex(opts.length, activeIdx, -1));
|
|
466
391
|
break;
|
|
467
392
|
case 'Home':
|
|
468
393
|
event.preventDefault();
|
|
469
|
-
|
|
470
|
-
this._setActiveOptionIndex(0);
|
|
471
|
-
}
|
|
394
|
+
this._setActiveOptionIndex(0);
|
|
472
395
|
break;
|
|
473
396
|
case 'End':
|
|
474
397
|
event.preventDefault();
|
|
475
|
-
|
|
476
|
-
this._setActiveOptionIndex(opts.length - 1);
|
|
477
|
-
}
|
|
398
|
+
this._setActiveOptionIndex(opts.length - 1);
|
|
478
399
|
break;
|
|
479
400
|
case 'PageDown':
|
|
480
401
|
event.preventDefault();
|
|
481
402
|
if (opts.length > 0) {
|
|
482
|
-
|
|
483
|
-
const newIndex = Math.min(activeIndex + pageSize, opts.length - 1);
|
|
484
|
-
this._setActiveOptionIndex(newIndex);
|
|
403
|
+
this._setActiveOptionIndex(computePagedActiveOptionIndex(opts.length, activeIdx, 1));
|
|
485
404
|
}
|
|
486
405
|
break;
|
|
487
406
|
case 'PageUp':
|
|
488
407
|
event.preventDefault();
|
|
489
408
|
if (opts.length > 0) {
|
|
490
|
-
|
|
491
|
-
const newIndex = Math.max(activeIndex - pageSize, 0);
|
|
492
|
-
this._setActiveOptionIndex(newIndex);
|
|
409
|
+
this._setActiveOptionIndex(computePagedActiveOptionIndex(opts.length, activeIdx, -1));
|
|
493
410
|
}
|
|
494
411
|
break;
|
|
495
412
|
case 'Enter':
|
|
496
413
|
event.preventDefault();
|
|
497
|
-
if (
|
|
498
|
-
this._onOptionSelected(opts[
|
|
414
|
+
if (activeIdx >= 0 && activeIdx < opts.length) {
|
|
415
|
+
this._onOptionSelected(opts[activeIdx]);
|
|
499
416
|
}
|
|
500
417
|
break;
|
|
501
418
|
case 'Escape':
|
|
502
419
|
event.preventDefault();
|
|
420
|
+
event.stopPropagation();
|
|
503
421
|
this.close();
|
|
504
422
|
break;
|
|
505
423
|
case 'Tab':
|
|
506
|
-
// Allow tab to close and move to next element
|
|
507
424
|
this.close();
|
|
508
425
|
break;
|
|
509
426
|
}
|
|
510
427
|
}
|
|
511
|
-
// Handle keydown when panel is open (for trigger)
|
|
512
|
-
_handleOpenKeydown(event) {
|
|
513
|
-
// Delegate to panel keydown handler
|
|
514
|
-
this._handlePanelKeydown(event);
|
|
515
|
-
}
|
|
516
|
-
// Set the next active option based on delta
|
|
517
|
-
_setNextActiveOption(delta) {
|
|
518
|
-
const opts = this.filteredOptions();
|
|
519
|
-
if (opts.length === 0)
|
|
520
|
-
return;
|
|
521
|
-
let currentIndex = this.activeOptionIndex();
|
|
522
|
-
if (currentIndex < 0) {
|
|
523
|
-
currentIndex = delta > 0 ? -1 : opts.length;
|
|
524
|
-
}
|
|
525
|
-
let newIndex = currentIndex + delta;
|
|
526
|
-
// Wrap around
|
|
527
|
-
if (newIndex < 0) {
|
|
528
|
-
newIndex = opts.length - 1;
|
|
529
|
-
}
|
|
530
|
-
else if (newIndex >= opts.length) {
|
|
531
|
-
newIndex = 0;
|
|
532
|
-
}
|
|
533
|
-
this._setActiveOptionIndex(newIndex);
|
|
534
|
-
}
|
|
535
|
-
// Set the active option index
|
|
536
428
|
_setActiveOptionIndex(index) {
|
|
537
|
-
const opts = this.
|
|
429
|
+
const opts = this._getVisibleEnabledOptions();
|
|
538
430
|
if (index < 0 || index >= opts.length)
|
|
539
431
|
return;
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
allOpts.forEach((opt) => {
|
|
432
|
+
// Deactivate all visible options
|
|
433
|
+
this.options().forEach((opt) => {
|
|
543
434
|
opt.setInactive();
|
|
544
435
|
});
|
|
545
436
|
const activeOption = opts[index];
|
|
546
437
|
activeOption.setActive();
|
|
547
|
-
this.
|
|
548
|
-
|
|
549
|
-
|
|
438
|
+
this.activeIndex.set(index);
|
|
439
|
+
scrollOptionIntoView(activeOption._getHostElement(), this.panel?.nativeElement, '.fui-autocomplete__options');
|
|
440
|
+
// Announce active item
|
|
441
|
+
const label = activeOption.getLabel();
|
|
442
|
+
this._announce(`${label}, ${index + 1} of ${opts.length}`);
|
|
550
443
|
}
|
|
551
|
-
// Set initial active option when opening
|
|
552
444
|
_setInitialActiveOption() {
|
|
553
|
-
const opts = this.
|
|
445
|
+
const opts = this._getVisibleEnabledOptions();
|
|
554
446
|
if (opts.length === 0) {
|
|
555
|
-
this.
|
|
447
|
+
this.activeIndex.set(-1);
|
|
556
448
|
return;
|
|
557
449
|
}
|
|
558
|
-
const
|
|
559
|
-
const compareFn = this.compareWith();
|
|
560
|
-
// Find the selected option
|
|
561
|
-
let selectedIndex = -1;
|
|
562
|
-
if (currentValue !== null && currentValue !== undefined) {
|
|
563
|
-
selectedIndex = opts.findIndex((opt) => compareFn(opt.value(), currentValue));
|
|
564
|
-
}
|
|
565
|
-
const initialIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
450
|
+
const initialIndex = findInitialActiveOptionIndex(opts, this._value(), this.multiple(), this.compareWith());
|
|
566
451
|
this._setActiveOptionIndex(initialIndex);
|
|
567
452
|
}
|
|
568
|
-
// Scroll to active option
|
|
569
453
|
_scrollToActiveOption() {
|
|
570
|
-
const opts = this.
|
|
571
|
-
const
|
|
572
|
-
if (
|
|
454
|
+
const opts = this._getVisibleEnabledOptions();
|
|
455
|
+
const activeIdx = this.activeIndex();
|
|
456
|
+
if (activeIdx < 0 || activeIdx >= opts.length)
|
|
573
457
|
return;
|
|
574
|
-
|
|
575
|
-
const element = activeOption._getHostElement();
|
|
576
|
-
const panelElement = this.panel?.nativeElement;
|
|
577
|
-
if (element && panelElement) {
|
|
578
|
-
const optionsContainer = panelElement.querySelector('.fui-autocomplete__options');
|
|
579
|
-
if (optionsContainer) {
|
|
580
|
-
const optionTop = element.offsetTop - optionsContainer.offsetTop;
|
|
581
|
-
const optionBottom = optionTop + element.offsetHeight;
|
|
582
|
-
const containerTop = optionsContainer.scrollTop;
|
|
583
|
-
const containerBottom = containerTop + optionsContainer.clientHeight;
|
|
584
|
-
if (optionTop < containerTop) {
|
|
585
|
-
optionsContainer.scrollTop = optionTop;
|
|
586
|
-
}
|
|
587
|
-
else if (optionBottom > containerBottom) {
|
|
588
|
-
optionsContainer.scrollTop = optionBottom - optionsContainer.clientHeight;
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
}
|
|
458
|
+
scrollOptionIntoView(opts[activeIdx]._getHostElement(), this.panel?.nativeElement, '.fui-autocomplete__options');
|
|
592
459
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
this.
|
|
596
|
-
this._ngZone.runOutsideAngular(() => {
|
|
597
|
-
setTimeout(() => {
|
|
598
|
-
this._outsideClickSub = fromEvent(this._document, 'click')
|
|
599
|
-
.pipe(filter(() => this.panelOpen()), filter((event) => {
|
|
600
|
-
const target = event.target;
|
|
601
|
-
const triggerElement = this.trigger?.nativeElement;
|
|
602
|
-
const panelElement = this.panel?.nativeElement;
|
|
603
|
-
const overlayElement = this._overlayRef?.overlayElement;
|
|
604
|
-
return (!triggerElement?.contains(target) &&
|
|
605
|
-
!panelElement?.contains(target) &&
|
|
606
|
-
!overlayElement?.contains(target));
|
|
607
|
-
}))
|
|
608
|
-
.subscribe(() => {
|
|
609
|
-
this._ngZone.run(() => {
|
|
610
|
-
this.close();
|
|
611
|
-
});
|
|
612
|
-
});
|
|
613
|
-
});
|
|
614
|
-
});
|
|
460
|
+
/** Get visible (non-hidden) enabled options for keyboard navigation */
|
|
461
|
+
_getVisibleEnabledOptions() {
|
|
462
|
+
return getEnabledOptions(this._visibleOptions());
|
|
615
463
|
}
|
|
616
|
-
|
|
617
|
-
_createOverlay() {
|
|
618
|
-
if (this._overlayRef || !this.panel || !this.trigger)
|
|
619
|
-
return;
|
|
620
|
-
const triggerElement = this.trigger.nativeElement;
|
|
621
|
-
const triggerWidth = triggerElement.getBoundingClientRect().width;
|
|
622
|
-
const positions = [
|
|
623
|
-
{
|
|
624
|
-
originX: 'start',
|
|
625
|
-
originY: 'bottom',
|
|
626
|
-
overlayX: 'start',
|
|
627
|
-
overlayY: 'top',
|
|
628
|
-
offsetY: 4,
|
|
629
|
-
},
|
|
630
|
-
{
|
|
631
|
-
originX: 'start',
|
|
632
|
-
originY: 'top',
|
|
633
|
-
overlayX: 'start',
|
|
634
|
-
overlayY: 'bottom',
|
|
635
|
-
offsetY: -4,
|
|
636
|
-
},
|
|
637
|
-
];
|
|
638
|
-
const positionStrategy = this._overlayService
|
|
639
|
-
.position()
|
|
640
|
-
.connectedTo(triggerElement, positions)
|
|
641
|
-
.withPush(true)
|
|
642
|
-
.withViewportMargin(8);
|
|
643
|
-
this._overlayRef = this._overlayService.create({
|
|
644
|
-
positionStrategy,
|
|
645
|
-
scrollStrategy: this._overlayService.scrollStrategies.reposition(),
|
|
646
|
-
hasBackdrop: true,
|
|
647
|
-
backdropClass: 'fui-autocomplete-backdrop',
|
|
648
|
-
backdropClickBehavior: 'close',
|
|
649
|
-
panelClass: ['fui-autocomplete-overlay-panel'],
|
|
650
|
-
minWidth: triggerWidth,
|
|
651
|
-
});
|
|
652
|
-
// Track overlay subscriptions for proper cleanup
|
|
653
|
-
this._overlaySubscriptions.unsubscribe();
|
|
654
|
-
this._overlaySubscriptions = new Subscription();
|
|
655
|
-
this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
|
|
656
|
-
this.close();
|
|
657
|
-
}));
|
|
658
|
-
this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
|
|
659
|
-
if (event.key === 'Escape') {
|
|
660
|
-
this.close();
|
|
661
|
-
}
|
|
662
|
-
}));
|
|
663
|
-
// Attach panel to overlay
|
|
664
|
-
const panelElement = this.panel.nativeElement;
|
|
665
|
-
this._overlayRef.attach(panelElement);
|
|
666
|
-
}
|
|
667
|
-
// Dispose overlay
|
|
668
|
-
_disposeOverlay() {
|
|
669
|
-
this._overlaySubscriptions.unsubscribe();
|
|
670
|
-
if (this._overlayRef) {
|
|
671
|
-
this._overlayRef.dispose();
|
|
672
|
-
this._overlayRef = null;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
// Get the active option's id for aria-activedescendant
|
|
464
|
+
/** aria-activedescendant */
|
|
676
465
|
_getActiveDescendant() {
|
|
677
|
-
const opts = this.
|
|
678
|
-
|
|
679
|
-
if (activeIndex >= 0 && activeIndex < opts.length) {
|
|
680
|
-
return opts[activeIndex].id;
|
|
681
|
-
}
|
|
682
|
-
return null;
|
|
683
|
-
}
|
|
684
|
-
// Set active option by index (for template use)
|
|
685
|
-
setActiveOption(index, option) {
|
|
686
|
-
const allOpts = this.options();
|
|
687
|
-
allOpts.forEach((opt) => {
|
|
688
|
-
opt.setInactive();
|
|
689
|
-
});
|
|
690
|
-
option.setActive();
|
|
691
|
-
this.activeOptionIndex.set(index);
|
|
466
|
+
const opts = this._getVisibleEnabledOptions();
|
|
467
|
+
return computeActiveDescendant(opts, this.activeIndex());
|
|
692
468
|
}
|
|
693
|
-
//
|
|
694
|
-
|
|
695
|
-
|
|
469
|
+
// --- Announcement ---
|
|
470
|
+
_announce(message) {
|
|
471
|
+
announceMessage(this._liveAnnouncement, message);
|
|
696
472
|
}
|
|
697
473
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiAutocompleteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
698
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiAutocompleteComponent, isStandalone: true, selector: "fui-autocomplete", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null },
|
|
474
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiAutocompleteComponent, isStandalone: true, selector: "fui-autocomplete", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, readonlyInput: { classPropertyName: "readonlyInput", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "valueChange", selectionChange: "selectionChange", openedChange: "openedChange", searchChange: "searchChange" }, host: { properties: { "attr.id": "id", "class.fui-autocomplete--open": "panelOpen()", "class.fui-autocomplete--disabled": "disabled()", "class.fui-autocomplete--focused": "focused()", "class.fui-autocomplete--error": "errorState()", "class.fui-autocomplete--readonly": "readonly()", "class.fui-autocomplete--multiple": "multiple()", "attr.aria-readonly": "readonly() ? \"true\" : null" }, classAttribute: "fui-autocomplete" }, providers: [
|
|
699
475
|
{
|
|
700
476
|
provide: NG_VALUE_ACCESSOR,
|
|
701
477
|
useExisting: FuiAutocompleteComponent,
|
|
@@ -709,18 +485,31 @@ class FuiAutocompleteComponent {
|
|
|
709
485
|
provide: FUI_AUTOCOMPLETE,
|
|
710
486
|
useExisting: FuiAutocompleteComponent,
|
|
711
487
|
},
|
|
712
|
-
], queries: [{ propertyName: "options", predicate: FuiOptionComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }], ngImport: i0, template: "<!-- Autocomplete Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-autocomplete__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n [attr.role]=\"'combobox'\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n>\r\n <span class=\"fui-autocomplete__value\">\r\n @if (empty()) {\r\n <span class=\"fui-autocomplete__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-autocomplete__value-text\">{{ _getDisplayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!_readOnly()) {\r\n <!-- Clear button \u2014 visible only when a value is selected and not disabled -->\r\n @if (!empty() && !disabled()) {\r\n <span class=\"fui-autocomplete__clear\" role=\"button\" [attr.aria-label]=\"intl.clearSelectionAriaLabel\" (click)=\"clear($event)\">\r\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\r\n <path\r\n d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\"\r\n />\r\n </svg>\r\n </span>\r\n }\r\n\r\n <!-- Dropdown arrow icon -->\r\n <svg\r\n class=\"fui-autocomplete__arrow\"\r\n [class.fui-autocomplete__arrow--open]=\"panelOpen()\"\r\n viewBox=\"0 0 16 16\"\r\n fill=\"currentColor\"\r\n aria-hidden=\"true\"\r\n >\r\n <path d=\"M8 11L3 6l.7-.7L8 9.6l4.3-4.3L13 6z\" />\r\n </svg>\r\n }\r\n</div>\r\n\r\n<!-- Hidden container for projected options (used for data binding) -->\r\n<div class=\"fui-autocomplete__options-source\">\r\n <ng-content></ng-content>\r\n</div>\r\n\r\n<!-- Dropdown Panel -->\r\n@if (panelOpen()) {\r\n <div #panel [id]=\"id + '-panel'\" class=\"fui-autocomplete__panel\" [attr.aria-label]=\"placeholder()\">\r\n <!-- Search Input -->\r\n <div class=\"fui-autocomplete__search\">\r\n <fui-icon name=\"magnifying-glass\" size=\"sm\" class=\"fui-autocomplete__search-icon\"> </fui-icon>\r\n <input\r\n #searchInput\r\n type=\"text\"\r\n class=\"fui-autocomplete__search-input\"\r\n [placeholder]=\"resolvedSearchPlaceholder()\"\r\n [value]=\"searchTerm()\"\r\n autocomplete=\"off\"\r\n role=\"searchbox\"\r\n [attr.aria-label]=\"resolvedSearchPlaceholder()\"\r\n [attr.aria-controls]=\"id + '-panel-listbox'\"\r\n [attr.aria-activedescendant]=\"_getActiveDescendant()\"\r\n (input)=\"onSearchInput($event)\"\r\n (keydown)=\"_handlePanelKeydown($event)\"\r\n />\r\n </div>\r\n <!-- Live region for announcing result count to screen readers -->\r\n <div class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">\r\n @if (_liveAnnouncement()) {\r\n {{ _liveAnnouncement() }}\r\n } @else if (hasFilteredOptions()) {\r\n {{ intl.resultsAvailableText(filteredOptions().length) }}\r\n } @else {\r\n {{ intl.noResultsAvailableText }}\r\n }\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n @if (showAddButton() || showRefreshButton()) {\r\n <div class=\"fui-autocomplete__actions\">\r\n @if (showAddButton()) {\r\n <button\r\n type=\"button\"\r\n fuiButton\r\n variant=\"tertiary\"\r\n size=\"sm\"\r\n class=\"fui-autocomplete__action-button\"\r\n (click)=\"onAddNew()\"\r\n >\r\n <fui-icon name=\"plus\" size=\"sm\"></fui-icon>\r\n {{ resolvedAddButtonLabel() }}\r\n </button>\r\n }\r\n @if (showRefreshButton()) {\r\n <button\r\n type=\"button\"\r\n fuiButton\r\n variant=\"tertiary\"\r\n size=\"sm\"\r\n class=\"fui-autocomplete__action-button\"\r\n (click)=\"onRefresh()\"\r\n >\r\n <fui-icon name=\"refresh-cw\" size=\"sm\"></fui-icon>\r\n {{ resolvedRefreshButtonLabel() }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Options List -->\r\n <div\r\n class=\"fui-autocomplete__options\"\r\n [id]=\"id + '-panel-listbox'\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"placeholder()\"\r\n >\r\n @if (hasFilteredOptions()) {\r\n @for (option of filteredOptions(); track option.id; let i = $index) {\r\n <div\r\n class=\"fui-autocomplete__option\"\r\n [class.fui-autocomplete__option--active]=\"activeOptionIndex() === i\"\r\n [class.fui-autocomplete__option--selected]=\"option._selected()\"\r\n role=\"option\"\r\n [attr.id]=\"option.id\"\r\n [attr.aria-selected]=\"option._selected()\"\r\n (click)=\"_onOptionSelected(option)\"\r\n (mouseenter)=\"setActiveOption(i, option)\"\r\n (mouseleave)=\"option.setInactive()\"\r\n >\r\n <span class=\"fui-autocomplete__option-text\">{{ option.getLabel() }}</span>\r\n @if (option._selected()) {\r\n <fui-icon name=\"check\" size=\"sm\" class=\"fui-autocomplete__option-check\"></fui-icon>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fui-autocomplete__no-options\">\r\n {{ resolvedNoOptionsText() }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-autocomplete{--fui-autocomplete-font-size: var(--fui-font-size-02);--fui-autocomplete-panel-max-height: 24rem;--fui-autocomplete-panel-border-radius: var(--fui-border-radius-sm);--fui-autocomplete-panel-shadow: var(--fui-shadow-04);--fui-autocomplete-panel-bg: var(--fui-surface-00);--fui-autocomplete-option-padding: .5rem .75rem;--fui-autocomplete-search-padding: .75rem;position:relative;display:inline-block;width:100%}.fui-autocomplete__options-source{display:none}.fui-autocomplete__trigger{display:flex;align-items:center;width:100%;border:none;color:var(--fui-text-primary);font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);line-height:1.29;letter-spacing:.16px;cursor:pointer;outline:none;transition:border-color var(--fui-duration-fast-01) var(--fui-ease-standard),background-color var(--fui-duration-fast-01) var(--fui-ease-standard);gap:.25rem}.fui-autocomplete__trigger:hover:not([aria-disabled=true]){background-color:var(--fui-field-background-hover)}.fui-autocomplete__trigger[aria-disabled=true]{background-color:var(--fui-field-background-disabled);color:var(--fui-text-primary-disabled);cursor:not-allowed}.fui-autocomplete__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.fui-autocomplete__placeholder{color:var(--fui-text-secondary)}.fui-autocomplete__value-text{color:var(--fui-text-primary)}.fui-autocomplete__clear{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;cursor:pointer;color:var(--fui-icon-secondary);flex-shrink:0;border-radius:50%;transition:color var(--fui-duration-fast-01) var(--fui-ease-standard),background-color var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__clear svg{width:.75rem;height:.75rem}.fui-autocomplete__clear:hover{color:var(--fui-text-primary);background-color:var(--fui-background-hover)}.fui-autocomplete__arrow{width:1rem;height:1rem;flex-shrink:0;color:var(--fui-icon-primary);pointer-events:none;transition:transform var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__arrow--open{transform:rotate(180deg)}.fui-autocomplete__panel{position:absolute;width:100%;background-color:var(--fui-autocomplete-panel-bg);border:1px solid var(--fui-border-color);border-radius:var(--fui-autocomplete-panel-border-radius);box-shadow:var(--fui-autocomplete-panel-shadow);max-height:var(--fui-autocomplete-panel-max-height);overflow:hidden;display:flex;flex-direction:column}.fui-autocomplete__search{position:relative;padding:.75rem;border-bottom:1px solid var(--fui-border-color);background-color:var(--fui-field-background)}.fui-autocomplete__search-icon{position:absolute;left:1.5rem;top:50%;transform:translateY(-50%);color:var(--fui-icon-secondary);pointer-events:none}.fui-autocomplete__search-input{width:100%;padding:.5rem .75rem .5rem 2.5rem;border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-sm);background-color:var(--fui-background-primary);color:var(--fui-text-primary);font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);line-height:1.29;letter-spacing:.16px;transition:border-color var(--fui-duration-fast-01) var(--fui-ease-standard),outline var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__search-input:focus{outline:2px solid var(--fui-primary);outline-offset:-2px;border-color:var(--fui-primary)}.fui-autocomplete__search-input::placeholder{color:var(--fui-text-secondary)}.fui-autocomplete__actions{display:flex;gap:.5rem;padding:.5rem .75rem;border-bottom:1px solid var(--fui-border-color);background-color:var(--fui-field-background)}.fui-autocomplete__action-button{display:flex;align-items:center;gap:.25rem;font-size:var(--fui-font-size-01)}.fui-autocomplete__action-button fui-icon{flex-shrink:0}.fui-autocomplete__options{max-height:16rem;overflow-y:auto;padding:.25rem 0;background-color:var(--fui-surface-01);scrollbar-width:thin;scrollbar-color:var(--fui-border-color) transparent}.fui-autocomplete__options::-webkit-scrollbar{width:6px}.fui-autocomplete__options::-webkit-scrollbar-track{background:transparent}.fui-autocomplete__options::-webkit-scrollbar-thumb{background-color:var(--fui-border-color);border-radius:3px}.fui-autocomplete__options::-webkit-scrollbar-thumb:hover{background-color:var(--fui-text-secondary)}.fui-autocomplete__option{display:flex;align-items:center;justify-content:space-between;padding:.5rem .75rem;cursor:pointer;color:var(--fui-text-primary);font-size:var(--fui-font-size-02);line-height:1.29;letter-spacing:.16px;transition:background-color var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__option:hover:not(.fui-autocomplete__option--disabled){background-color:var(--fui-background-hover)}.fui-autocomplete__option--active{background-color:var(--fui-surface-05)}.fui-autocomplete__option--selected{background-color:var(--fui-surface-05);font-weight:600}.fui-autocomplete__option--disabled{color:var(--fui-text-primary-disabled);cursor:not-allowed}.fui-autocomplete__option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-autocomplete__option-check{color:var(--fui-icon-primary);flex-shrink:0;margin-left:.5rem}.fui-autocomplete__no-options{padding:1rem .75rem;text-align:center;color:var(--fui-text-secondary);font-size:var(--fui-font-size-02);font-style:italic}.fui-autocomplete--disabled .fui-autocomplete__trigger{background-color:var(--fui-field-background-disabled);color:var(--fui-text-primary-disabled);cursor:not-allowed}.fui-autocomplete--disabled .fui-autocomplete__arrow{color:var(--fui-text-primary-disabled)}.fui-autocomplete--readonly .fui-autocomplete__trigger{background-color:transparent;min-height:unset;cursor:default;color:var(--fui-text-primary)}.fui-autocomplete--readonly .fui-autocomplete__trigger[aria-disabled=true]{background-color:transparent;color:var(--fui-text-primary);cursor:default}.fui-autocomplete--readonly .fui-autocomplete__value-text{color:var(--fui-text-primary)}.fui-autocomplete--error .fui-autocomplete__trigger{border-bottom-color:var(--fui-error)}.fui-autocomplete--error .fui-autocomplete__trigger:focus{outline-color:var(--fui-error)}.fui-autocomplete-overlay-panel .fui-autocomplete__panel{border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-sm);box-shadow:var(--fui-shadow-04);transform-origin:top center;animation:fui-popover-enter var(--fui-duration-moderate-01) var(--fui-ease-entrance) both;will-change:transform,opacity}.fui-autocomplete-backdrop{background:transparent}@media(prefers-contrast:high){.fui-autocomplete__trigger,.fui-autocomplete__panel{border-width:2px}.fui-autocomplete__trigger:focus,.fui-autocomplete__search-input:focus{outline-width:3px}.fui-autocomplete__option--active,.fui-autocomplete__option--selected{outline:2px solid var(--fui-primary);outline-offset:-2px}}@media(prefers-reduced-motion:reduce){.fui-autocomplete__trigger,.fui-autocomplete__arrow,.fui-autocomplete__option,.fui-autocomplete__search-input{transition:none}.fui-autocomplete-overlay-panel .fui-autocomplete__panel{animation:none}}\n"], dependencies: [{ kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: FuiButtonDirective, selector: "button[fuiButton], a[fuiButton]", inputs: ["variant", "size", "disabled", "fullWidth", "loading", "iconOnly", "aria-label", "type"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
488
|
+
{
|
|
489
|
+
provide: FUI_SELECT,
|
|
490
|
+
useExisting: FuiAutocompleteComponent,
|
|
491
|
+
},
|
|
492
|
+
], queries: [{ propertyName: "options", predicate: FUI_OPTION, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true, static: true }, { propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true }], hostDirectives: [{ directive: i1.FuiPopupOverlayDirective, inputs: ["positions", "positions", "panelClass", "panelClass", "backdropClass", "backdropClass", "scrollStrategy", "scrollStrategy", "minWidthFromTrigger", "minWidthFromTrigger"], outputs: ["openedChange", "openedChange", "escapeKey", "escapeKey"] }, { directive: i1.FuiFormControlSyncDirective }], ngImport: i0, template: "<!-- Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-autocomplete__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n role=\"combobox\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-readonly]=\"readonly() ? 'true' : null\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n>\r\n <span class=\"fui-autocomplete__value\">\r\n @if (empty()) {\r\n <span class=\"fui-autocomplete__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-autocomplete__value-text\">{{ displayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!readonly() && !disabled()) {\r\n @if (!empty()) {\r\n <button\r\n type=\"button\"\r\n class=\"fui-autocomplete__clear\"\r\n role=\"button\"\r\n [attr.aria-label]=\"intl.clearSelectionAriaLabel\"\r\n (click)=\"clear($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\r\n <path\r\n d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n\r\n <span class=\"fui-autocomplete__arrow\" [class.fui-autocomplete__arrow--open]=\"panelOpen()\" aria-hidden=\"true\">\r\n <fui-icon name=\"caret-down\" size=\"sm\"></fui-icon>\r\n </span>\r\n }\r\n</div>\r\n\r\n<!-- Live region for screen reader announcements -->\r\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\r\n\r\n<!-- Dropdown Panel (always rendered so #panel ViewChild is always available) -->\r\n<div #panel [hidden]=\"false\" [id]=\"id + '-panel'\" class=\"fui-autocomplete__panel\">\r\n <!-- Search Input -->\r\n <div class=\"fui-autocomplete__search-input\">\r\n <fui-form-field hideSubscript>\r\n <fui-icon fuiPrefix name=\"magnifying-glass\" size=\"sm\" />\r\n <input\r\n #searchInput\r\n fuiInput\r\n type=\"text\"\r\n [placeholder]=\"intl.searchPlaceholder\"\r\n [value]=\"searchTerm()\"\r\n autocomplete=\"off\"\r\n role=\"searchbox\"\r\n aria-label=\"Search\"\r\n (input)=\"onSearchInput($event)\"\r\n (keydown)=\"_handlePanelKeydown($event)\"\r\n />\r\n </fui-form-field>\r\n </div>\r\n\r\n <!-- Options List -->\r\n <div\r\n class=\"fui-autocomplete__options\"\r\n role=\"listbox\"\r\n [attr.aria-multiselectable]=\"multiple()\"\r\n [attr.aria-label]=\"placeholder()\"\r\n >\r\n <ng-content></ng-content>\r\n\r\n <!-- No results empty state when search has no matching options -->\r\n @if (searchTerm() && _visibleOptions().length === 0) {\r\n <div class=\"fui-autocomplete__no-results\" role=\"status\">\r\n {{ intl.noResultsText }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Panel Actions (e.g., Add new option, Refresh) -->\r\n <div class=\"fui-autocomplete__panel-actions\">\r\n <ng-content select=\"[fuiAutocompletePanelActions]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-autocomplete{position:relative;display:inline-block;width:100%;min-width:8rem}.fui-autocomplete__trigger{display:flex;align-items:center;width:100%;border:none;color:var(--fui-text-primary);font-family:var(--fui-font-sans);font-size:var(--fui-text-base);line-height:1.29;letter-spacing:.16px;cursor:pointer;outline:none;gap:var(--fui-spacing-2)}.fui-autocomplete__trigger[aria-disabled=true]{color:var(--fui-text-disabled);cursor:not-allowed}.fui-autocomplete__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.fui-autocomplete__placeholder{color:var(--fui-text-secondary)}.fui-autocomplete__value-text{color:var(--fui-text-primary)}.fui-autocomplete__clear{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;cursor:pointer;color:var(--fui-text-secondary);flex-shrink:0;border-radius:50%}.fui-autocomplete__clear svg{width:.75rem;height:.75rem}.fui-autocomplete__clear:hover{color:var(--fui-text-primary);background-color:var(--fui-bg-subtle)}.fui-autocomplete__arrow{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--fui-icon-xs);height:var(--fui-icon-xs);margin-left:var(--fui-spacing-2);color:var(--fui-text-secondary);pointer-events:none;transition:color var(--fui-duration-fast) var(--fui-ease-in-out),transform var(--fui-duration-fast) var(--fui-ease-in-out)}.fui-autocomplete__arrow--open{color:var(--fui-field-border-focus);transform:rotate(180deg)}.fui-autocomplete--disabled .fui-autocomplete__trigger{color:var(--fui-text-disabled);cursor:not-allowed}.fui-autocomplete--disabled .fui-autocomplete__arrow{color:var(--fui-text-disabled)}.fui-autocomplete--readonly .fui-autocomplete__trigger{background-color:transparent;min-height:unset;cursor:default;color:var(--fui-text-primary)}.fui-autocomplete .fui-autocomplete__panel{display:none}.fui-autocomplete__panel{--fui-autocomplete-panel-max-height: 24rem;--fui-autocomplete-panel-border-radius: var(--fui-radius-sm);--fui-autocomplete-panel-shadow: var(--fui-shadow-lg);--fui-autocomplete-panel-bg: var(--fui-bg-default);width:100%;background-color:var(--fui-autocomplete-panel-bg);border:1px solid var(--fui-border-default);border-radius:var(--fui-autocomplete-panel-border-radius);box-shadow:var(--fui-autocomplete-panel-shadow);max-height:var(--fui-autocomplete-panel-max-height);overflow:hidden;display:flex;flex-direction:column;transform-origin:top center;padding:0 0 var(--fui-spacing-2) 0;animation:fui-popover-enter var(--fui-duration-base) var(--fui-ease-out) both;will-change:transform,opacity}.fui-autocomplete__panel .fui-autocomplete__search-input{padding:var(--fui-spacing-1) var(--fui-spacing-3)}.fui-autocomplete__panel .fui-autocomplete__no-results{padding:var(--fui-spacing-1) var(--fui-spacing-3);font-size:var(--fui-text-md);color:var(--fui-text-disabled)}.fui-autocomplete__options{padding-top:var(--fui-spacing-2);max-height:16rem;overflow-y:auto;background-color:var(--fui-bg-default);scrollbar-width:thin;scrollbar-color:var(--fui-border-default) transparent}.fui-autocomplete__options::-webkit-scrollbar{width:6px}.fui-autocomplete__options::-webkit-scrollbar-track{background:transparent}.fui-autocomplete__options::-webkit-scrollbar-thumb{background-color:var(--fui-border-default);border-radius:3px}.fui-autocomplete__options::-webkit-scrollbar-thumb:hover{background-color:var(--fui-text-secondary)}.fui-autocomplete__panel-actions{padding:var(--fui-spacing-4) var(--fui-spacing-4) 0 var(--fui-spacing-4)}.fui-autocomplete__panel-actions:empty{display:none}.fui-autocomplete-backdrop{background:transparent}@media(prefers-contrast:high){.fui-autocomplete__trigger{border-width:2px}.fui-autocomplete__trigger:focus{outline-width:3px}.fui-autocomplete__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-autocomplete__trigger,.fui-autocomplete__arrow{transition:none}.fui-autocomplete__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }, { kind: "component", type: FuiFormFieldComponent, selector: "fui-form-field", inputs: ["appearance", "hideRequiredMarker", "hideSubscript"] }, { kind: "directive", type: FuiInputDirective, selector: "input[fuiInput], textarea[fuiInput], select[fuiInput]", inputs: ["type", "placeholder", "readonly", "maxlength", "minlength", "pattern", "errorStateMatcher", "disabled"], outputs: ["valueChange"] }, { kind: "directive", type: FuiPrefixDirective, selector: "[fuiPrefix]" }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
713
493
|
}
|
|
714
494
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiAutocompleteComponent, decorators: [{
|
|
715
495
|
type: Component,
|
|
716
|
-
args: [{ selector: 'fui-autocomplete', standalone: true, imports: [FuiIconComponent,
|
|
496
|
+
args: [{ selector: 'fui-autocomplete', standalone: true, imports: [ReactiveFormsModule, FuiIconComponent, FuiFormFieldComponent, FuiInputDirective, FuiPrefixDirective], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, hostDirectives: [
|
|
497
|
+
{
|
|
498
|
+
directive: FuiPopupOverlayDirective,
|
|
499
|
+
inputs: ['positions', 'panelClass', 'backdropClass', 'scrollStrategy', 'minWidthFromTrigger'],
|
|
500
|
+
outputs: ['openedChange', 'escapeKey'],
|
|
501
|
+
},
|
|
502
|
+
FuiFormControlSyncDirective,
|
|
503
|
+
], host: {
|
|
717
504
|
class: 'fui-autocomplete',
|
|
718
505
|
'[attr.id]': 'id',
|
|
719
506
|
'[class.fui-autocomplete--open]': 'panelOpen()',
|
|
720
507
|
'[class.fui-autocomplete--disabled]': 'disabled()',
|
|
721
508
|
'[class.fui-autocomplete--focused]': 'focused()',
|
|
722
509
|
'[class.fui-autocomplete--error]': 'errorState()',
|
|
723
|
-
'[class.fui-autocomplete--readonly]': '
|
|
510
|
+
'[class.fui-autocomplete--readonly]': 'readonly()',
|
|
511
|
+
'[class.fui-autocomplete--multiple]': 'multiple()',
|
|
512
|
+
'[attr.aria-readonly]': 'readonly() ? "true" : null',
|
|
724
513
|
}, providers: [
|
|
725
514
|
{
|
|
726
515
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -735,17 +524,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
735
524
|
provide: FUI_AUTOCOMPLETE,
|
|
736
525
|
useExisting: FuiAutocompleteComponent,
|
|
737
526
|
},
|
|
738
|
-
], template: "<!-- Autocomplete Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-autocomplete__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n [attr.role]=\"'combobox'\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n>\r\n <span class=\"fui-autocomplete__value\">\r\n @if (empty()) {\r\n <span class=\"fui-autocomplete__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-autocomplete__value-text\">{{ _getDisplayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!_readOnly()) {\r\n <!-- Clear button \u2014 visible only when a value is selected and not disabled -->\r\n @if (!empty() && !disabled()) {\r\n <span class=\"fui-autocomplete__clear\" role=\"button\" [attr.aria-label]=\"intl.clearSelectionAriaLabel\" (click)=\"clear($event)\">\r\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\r\n <path\r\n d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\"\r\n />\r\n </svg>\r\n </span>\r\n }\r\n\r\n <!-- Dropdown arrow icon -->\r\n <svg\r\n class=\"fui-autocomplete__arrow\"\r\n [class.fui-autocomplete__arrow--open]=\"panelOpen()\"\r\n viewBox=\"0 0 16 16\"\r\n fill=\"currentColor\"\r\n aria-hidden=\"true\"\r\n >\r\n <path d=\"M8 11L3 6l.7-.7L8 9.6l4.3-4.3L13 6z\" />\r\n </svg>\r\n }\r\n</div>\r\n\r\n<!-- Hidden container for projected options (used for data binding) -->\r\n<div class=\"fui-autocomplete__options-source\">\r\n <ng-content></ng-content>\r\n</div>\r\n\r\n<!-- Dropdown Panel -->\r\n@if (panelOpen()) {\r\n <div #panel [id]=\"id + '-panel'\" class=\"fui-autocomplete__panel\" [attr.aria-label]=\"placeholder()\">\r\n <!-- Search Input -->\r\n <div class=\"fui-autocomplete__search\">\r\n <fui-icon name=\"magnifying-glass\" size=\"sm\" class=\"fui-autocomplete__search-icon\"> </fui-icon>\r\n <input\r\n #searchInput\r\n type=\"text\"\r\n class=\"fui-autocomplete__search-input\"\r\n [placeholder]=\"resolvedSearchPlaceholder()\"\r\n [value]=\"searchTerm()\"\r\n autocomplete=\"off\"\r\n role=\"searchbox\"\r\n [attr.aria-label]=\"resolvedSearchPlaceholder()\"\r\n [attr.aria-controls]=\"id + '-panel-listbox'\"\r\n [attr.aria-activedescendant]=\"_getActiveDescendant()\"\r\n (input)=\"onSearchInput($event)\"\r\n (keydown)=\"_handlePanelKeydown($event)\"\r\n />\r\n </div>\r\n <!-- Live region for announcing result count to screen readers -->\r\n <div class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">\r\n @if (_liveAnnouncement()) {\r\n {{ _liveAnnouncement() }}\r\n } @else if (hasFilteredOptions()) {\r\n {{ intl.resultsAvailableText(filteredOptions().length) }}\r\n } @else {\r\n {{ intl.noResultsAvailableText }}\r\n }\r\n </div>\r\n\r\n <!-- Action Buttons -->\r\n @if (showAddButton() || showRefreshButton()) {\r\n <div class=\"fui-autocomplete__actions\">\r\n @if (showAddButton()) {\r\n <button\r\n type=\"button\"\r\n fuiButton\r\n variant=\"tertiary\"\r\n size=\"sm\"\r\n class=\"fui-autocomplete__action-button\"\r\n (click)=\"onAddNew()\"\r\n >\r\n <fui-icon name=\"plus\" size=\"sm\"></fui-icon>\r\n {{ resolvedAddButtonLabel() }}\r\n </button>\r\n }\r\n @if (showRefreshButton()) {\r\n <button\r\n type=\"button\"\r\n fuiButton\r\n variant=\"tertiary\"\r\n size=\"sm\"\r\n class=\"fui-autocomplete__action-button\"\r\n (click)=\"onRefresh()\"\r\n >\r\n <fui-icon name=\"refresh-cw\" size=\"sm\"></fui-icon>\r\n {{ resolvedRefreshButtonLabel() }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Options List -->\r\n <div\r\n class=\"fui-autocomplete__options\"\r\n [id]=\"id + '-panel-listbox'\"\r\n role=\"listbox\"\r\n [attr.aria-label]=\"placeholder()\"\r\n >\r\n @if (hasFilteredOptions()) {\r\n @for (option of filteredOptions(); track option.id; let i = $index) {\r\n <div\r\n class=\"fui-autocomplete__option\"\r\n [class.fui-autocomplete__option--active]=\"activeOptionIndex() === i\"\r\n [class.fui-autocomplete__option--selected]=\"option._selected()\"\r\n role=\"option\"\r\n [attr.id]=\"option.id\"\r\n [attr.aria-selected]=\"option._selected()\"\r\n (click)=\"_onOptionSelected(option)\"\r\n (mouseenter)=\"setActiveOption(i, option)\"\r\n (mouseleave)=\"option.setInactive()\"\r\n >\r\n <span class=\"fui-autocomplete__option-text\">{{ option.getLabel() }}</span>\r\n @if (option._selected()) {\r\n <fui-icon name=\"check\" size=\"sm\" class=\"fui-autocomplete__option-check\"></fui-icon>\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fui-autocomplete__no-options\">\r\n {{ resolvedNoOptionsText() }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-autocomplete{--fui-autocomplete-font-size: var(--fui-font-size-02);--fui-autocomplete-panel-max-height: 24rem;--fui-autocomplete-panel-border-radius: var(--fui-border-radius-sm);--fui-autocomplete-panel-shadow: var(--fui-shadow-04);--fui-autocomplete-panel-bg: var(--fui-surface-00);--fui-autocomplete-option-padding: .5rem .75rem;--fui-autocomplete-search-padding: .75rem;position:relative;display:inline-block;width:100%}.fui-autocomplete__options-source{display:none}.fui-autocomplete__trigger{display:flex;align-items:center;width:100%;border:none;color:var(--fui-text-primary);font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);line-height:1.29;letter-spacing:.16px;cursor:pointer;outline:none;transition:border-color var(--fui-duration-fast-01) var(--fui-ease-standard),background-color var(--fui-duration-fast-01) var(--fui-ease-standard);gap:.25rem}.fui-autocomplete__trigger:hover:not([aria-disabled=true]){background-color:var(--fui-field-background-hover)}.fui-autocomplete__trigger[aria-disabled=true]{background-color:var(--fui-field-background-disabled);color:var(--fui-text-primary-disabled);cursor:not-allowed}.fui-autocomplete__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.fui-autocomplete__placeholder{color:var(--fui-text-secondary)}.fui-autocomplete__value-text{color:var(--fui-text-primary)}.fui-autocomplete__clear{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;cursor:pointer;color:var(--fui-icon-secondary);flex-shrink:0;border-radius:50%;transition:color var(--fui-duration-fast-01) var(--fui-ease-standard),background-color var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__clear svg{width:.75rem;height:.75rem}.fui-autocomplete__clear:hover{color:var(--fui-text-primary);background-color:var(--fui-background-hover)}.fui-autocomplete__arrow{width:1rem;height:1rem;flex-shrink:0;color:var(--fui-icon-primary);pointer-events:none;transition:transform var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__arrow--open{transform:rotate(180deg)}.fui-autocomplete__panel{position:absolute;width:100%;background-color:var(--fui-autocomplete-panel-bg);border:1px solid var(--fui-border-color);border-radius:var(--fui-autocomplete-panel-border-radius);box-shadow:var(--fui-autocomplete-panel-shadow);max-height:var(--fui-autocomplete-panel-max-height);overflow:hidden;display:flex;flex-direction:column}.fui-autocomplete__search{position:relative;padding:.75rem;border-bottom:1px solid var(--fui-border-color);background-color:var(--fui-field-background)}.fui-autocomplete__search-icon{position:absolute;left:1.5rem;top:50%;transform:translateY(-50%);color:var(--fui-icon-secondary);pointer-events:none}.fui-autocomplete__search-input{width:100%;padding:.5rem .75rem .5rem 2.5rem;border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-sm);background-color:var(--fui-background-primary);color:var(--fui-text-primary);font-family:var(--fui-font-family-sans);font-size:var(--fui-font-size-02);line-height:1.29;letter-spacing:.16px;transition:border-color var(--fui-duration-fast-01) var(--fui-ease-standard),outline var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__search-input:focus{outline:2px solid var(--fui-primary);outline-offset:-2px;border-color:var(--fui-primary)}.fui-autocomplete__search-input::placeholder{color:var(--fui-text-secondary)}.fui-autocomplete__actions{display:flex;gap:.5rem;padding:.5rem .75rem;border-bottom:1px solid var(--fui-border-color);background-color:var(--fui-field-background)}.fui-autocomplete__action-button{display:flex;align-items:center;gap:.25rem;font-size:var(--fui-font-size-01)}.fui-autocomplete__action-button fui-icon{flex-shrink:0}.fui-autocomplete__options{max-height:16rem;overflow-y:auto;padding:.25rem 0;background-color:var(--fui-surface-01);scrollbar-width:thin;scrollbar-color:var(--fui-border-color) transparent}.fui-autocomplete__options::-webkit-scrollbar{width:6px}.fui-autocomplete__options::-webkit-scrollbar-track{background:transparent}.fui-autocomplete__options::-webkit-scrollbar-thumb{background-color:var(--fui-border-color);border-radius:3px}.fui-autocomplete__options::-webkit-scrollbar-thumb:hover{background-color:var(--fui-text-secondary)}.fui-autocomplete__option{display:flex;align-items:center;justify-content:space-between;padding:.5rem .75rem;cursor:pointer;color:var(--fui-text-primary);font-size:var(--fui-font-size-02);line-height:1.29;letter-spacing:.16px;transition:background-color var(--fui-duration-fast-01) var(--fui-ease-standard)}.fui-autocomplete__option:hover:not(.fui-autocomplete__option--disabled){background-color:var(--fui-background-hover)}.fui-autocomplete__option--active{background-color:var(--fui-surface-05)}.fui-autocomplete__option--selected{background-color:var(--fui-surface-05);font-weight:600}.fui-autocomplete__option--disabled{color:var(--fui-text-primary-disabled);cursor:not-allowed}.fui-autocomplete__option-text{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-autocomplete__option-check{color:var(--fui-icon-primary);flex-shrink:0;margin-left:.5rem}.fui-autocomplete__no-options{padding:1rem .75rem;text-align:center;color:var(--fui-text-secondary);font-size:var(--fui-font-size-02);font-style:italic}.fui-autocomplete--disabled .fui-autocomplete__trigger{background-color:var(--fui-field-background-disabled);color:var(--fui-text-primary-disabled);cursor:not-allowed}.fui-autocomplete--disabled .fui-autocomplete__arrow{color:var(--fui-text-primary-disabled)}.fui-autocomplete--readonly .fui-autocomplete__trigger{background-color:transparent;min-height:unset;cursor:default;color:var(--fui-text-primary)}.fui-autocomplete--readonly .fui-autocomplete__trigger[aria-disabled=true]{background-color:transparent;color:var(--fui-text-primary);cursor:default}.fui-autocomplete--readonly .fui-autocomplete__value-text{color:var(--fui-text-primary)}.fui-autocomplete--error .fui-autocomplete__trigger{border-bottom-color:var(--fui-error)}.fui-autocomplete--error .fui-autocomplete__trigger:focus{outline-color:var(--fui-error)}.fui-autocomplete-overlay-panel .fui-autocomplete__panel{border:1px solid var(--fui-border-color);border-radius:var(--fui-border-radius-sm);box-shadow:var(--fui-shadow-04);transform-origin:top center;animation:fui-popover-enter var(--fui-duration-moderate-01) var(--fui-ease-entrance) both;will-change:transform,opacity}.fui-autocomplete-backdrop{background:transparent}@media(prefers-contrast:high){.fui-autocomplete__trigger,.fui-autocomplete__panel{border-width:2px}.fui-autocomplete__trigger:focus,.fui-autocomplete__search-input:focus{outline-width:3px}.fui-autocomplete__option--active,.fui-autocomplete__option--selected{outline:2px solid var(--fui-primary);outline-offset:-2px}}@media(prefers-reduced-motion:reduce){.fui-autocomplete__trigger,.fui-autocomplete__arrow,.fui-autocomplete__option,.fui-autocomplete__search-input{transition:none}.fui-autocomplete-overlay-panel .fui-autocomplete__panel{animation:none}}\n"] }]
|
|
739
|
-
|
|
527
|
+
{
|
|
528
|
+
provide: FUI_SELECT,
|
|
529
|
+
useExisting: FuiAutocompleteComponent,
|
|
530
|
+
},
|
|
531
|
+
], template: "<!-- Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-autocomplete__trigger\"\r\n [attr.tabindex]=\"disabled() ? -1 : 0\"\r\n role=\"combobox\"\r\n [attr.aria-haspopup]=\"'listbox'\"\r\n [attr.aria-expanded]=\"panelOpen()\"\r\n [attr.aria-disabled]=\"disabled()\"\r\n [attr.aria-invalid]=\"errorState()\"\r\n [attr.aria-describedby]=\"_ariaDescribedby\"\r\n [attr.aria-required]=\"required()\"\r\n [attr.aria-readonly]=\"readonly() ? 'true' : null\"\r\n aria-autocomplete=\"list\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n>\r\n <span class=\"fui-autocomplete__value\">\r\n @if (empty()) {\r\n <span class=\"fui-autocomplete__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-autocomplete__value-text\">{{ displayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!readonly() && !disabled()) {\r\n @if (!empty()) {\r\n <button\r\n type=\"button\"\r\n class=\"fui-autocomplete__clear\"\r\n role=\"button\"\r\n [attr.aria-label]=\"intl.clearSelectionAriaLabel\"\r\n (click)=\"clear($event)\"\r\n >\r\n <svg viewBox=\"0 0 16 16\" fill=\"currentColor\" aria-hidden=\"true\">\r\n <path\r\n d=\"M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z\"\r\n />\r\n </svg>\r\n </button>\r\n }\r\n\r\n <span class=\"fui-autocomplete__arrow\" [class.fui-autocomplete__arrow--open]=\"panelOpen()\" aria-hidden=\"true\">\r\n <fui-icon name=\"caret-down\" size=\"sm\"></fui-icon>\r\n </span>\r\n }\r\n</div>\r\n\r\n<!-- Live region for screen reader announcements -->\r\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\r\n\r\n<!-- Dropdown Panel (always rendered so #panel ViewChild is always available) -->\r\n<div #panel [hidden]=\"false\" [id]=\"id + '-panel'\" class=\"fui-autocomplete__panel\">\r\n <!-- Search Input -->\r\n <div class=\"fui-autocomplete__search-input\">\r\n <fui-form-field hideSubscript>\r\n <fui-icon fuiPrefix name=\"magnifying-glass\" size=\"sm\" />\r\n <input\r\n #searchInput\r\n fuiInput\r\n type=\"text\"\r\n [placeholder]=\"intl.searchPlaceholder\"\r\n [value]=\"searchTerm()\"\r\n autocomplete=\"off\"\r\n role=\"searchbox\"\r\n aria-label=\"Search\"\r\n (input)=\"onSearchInput($event)\"\r\n (keydown)=\"_handlePanelKeydown($event)\"\r\n />\r\n </fui-form-field>\r\n </div>\r\n\r\n <!-- Options List -->\r\n <div\r\n class=\"fui-autocomplete__options\"\r\n role=\"listbox\"\r\n [attr.aria-multiselectable]=\"multiple()\"\r\n [attr.aria-label]=\"placeholder()\"\r\n >\r\n <ng-content></ng-content>\r\n\r\n <!-- No results empty state when search has no matching options -->\r\n @if (searchTerm() && _visibleOptions().length === 0) {\r\n <div class=\"fui-autocomplete__no-results\" role=\"status\">\r\n {{ intl.noResultsText }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <!-- Panel Actions (e.g., Add new option, Refresh) -->\r\n <div class=\"fui-autocomplete__panel-actions\">\r\n <ng-content select=\"[fuiAutocompletePanelActions]\"></ng-content>\r\n </div>\r\n</div>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-out);transition-delay:0ms}.fui-motion-fade-out{transition-property:opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in);transition-delay:0ms}.fui-motion-slide-in-bottom{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition-property:transform;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition-property:transform,opacity;transition-duration:var(--fui-duration-base);transition-timing-function:var(--fui-ease-out);transition-delay:0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-base) var(--fui-ease-out)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}.fui-autocomplete{position:relative;display:inline-block;width:100%;min-width:8rem}.fui-autocomplete__trigger{display:flex;align-items:center;width:100%;border:none;color:var(--fui-text-primary);font-family:var(--fui-font-sans);font-size:var(--fui-text-base);line-height:1.29;letter-spacing:.16px;cursor:pointer;outline:none;gap:var(--fui-spacing-2)}.fui-autocomplete__trigger[aria-disabled=true]{color:var(--fui-text-disabled);cursor:not-allowed}.fui-autocomplete__value{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left}.fui-autocomplete__placeholder{color:var(--fui-text-secondary)}.fui-autocomplete__value-text{color:var(--fui-text-primary)}.fui-autocomplete__clear{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;cursor:pointer;color:var(--fui-text-secondary);flex-shrink:0;border-radius:50%}.fui-autocomplete__clear svg{width:.75rem;height:.75rem}.fui-autocomplete__clear:hover{color:var(--fui-text-primary);background-color:var(--fui-bg-subtle)}.fui-autocomplete__arrow{display:flex;align-items:center;justify-content:center;flex-shrink:0;width:var(--fui-icon-xs);height:var(--fui-icon-xs);margin-left:var(--fui-spacing-2);color:var(--fui-text-secondary);pointer-events:none;transition:color var(--fui-duration-fast) var(--fui-ease-in-out),transform var(--fui-duration-fast) var(--fui-ease-in-out)}.fui-autocomplete__arrow--open{color:var(--fui-field-border-focus);transform:rotate(180deg)}.fui-autocomplete--disabled .fui-autocomplete__trigger{color:var(--fui-text-disabled);cursor:not-allowed}.fui-autocomplete--disabled .fui-autocomplete__arrow{color:var(--fui-text-disabled)}.fui-autocomplete--readonly .fui-autocomplete__trigger{background-color:transparent;min-height:unset;cursor:default;color:var(--fui-text-primary)}.fui-autocomplete .fui-autocomplete__panel{display:none}.fui-autocomplete__panel{--fui-autocomplete-panel-max-height: 24rem;--fui-autocomplete-panel-border-radius: var(--fui-radius-sm);--fui-autocomplete-panel-shadow: var(--fui-shadow-lg);--fui-autocomplete-panel-bg: var(--fui-bg-default);width:100%;background-color:var(--fui-autocomplete-panel-bg);border:1px solid var(--fui-border-default);border-radius:var(--fui-autocomplete-panel-border-radius);box-shadow:var(--fui-autocomplete-panel-shadow);max-height:var(--fui-autocomplete-panel-max-height);overflow:hidden;display:flex;flex-direction:column;transform-origin:top center;padding:0 0 var(--fui-spacing-2) 0;animation:fui-popover-enter var(--fui-duration-base) var(--fui-ease-out) both;will-change:transform,opacity}.fui-autocomplete__panel .fui-autocomplete__search-input{padding:var(--fui-spacing-1) var(--fui-spacing-3)}.fui-autocomplete__panel .fui-autocomplete__no-results{padding:var(--fui-spacing-1) var(--fui-spacing-3);font-size:var(--fui-text-md);color:var(--fui-text-disabled)}.fui-autocomplete__options{padding-top:var(--fui-spacing-2);max-height:16rem;overflow-y:auto;background-color:var(--fui-bg-default);scrollbar-width:thin;scrollbar-color:var(--fui-border-default) transparent}.fui-autocomplete__options::-webkit-scrollbar{width:6px}.fui-autocomplete__options::-webkit-scrollbar-track{background:transparent}.fui-autocomplete__options::-webkit-scrollbar-thumb{background-color:var(--fui-border-default);border-radius:3px}.fui-autocomplete__options::-webkit-scrollbar-thumb:hover{background-color:var(--fui-text-secondary)}.fui-autocomplete__panel-actions{padding:var(--fui-spacing-4) var(--fui-spacing-4) 0 var(--fui-spacing-4)}.fui-autocomplete__panel-actions:empty{display:none}.fui-autocomplete-backdrop{background:transparent}@media(prefers-contrast:high){.fui-autocomplete__trigger{border-width:2px}.fui-autocomplete__trigger:focus{outline-width:3px}.fui-autocomplete__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-autocomplete__trigger,.fui-autocomplete__arrow{transition:none}.fui-autocomplete__panel{animation:none}}\n"] }]
|
|
532
|
+
}], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonlyInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], searchChange: [{ type: i0.Output, args: ["searchChange"] }], trigger: [{
|
|
740
533
|
type: ViewChild,
|
|
741
534
|
args: ['trigger', { static: false }]
|
|
742
535
|
}], panel: [{
|
|
743
536
|
type: ViewChild,
|
|
744
|
-
args: ['panel', { static:
|
|
537
|
+
args: ['panel', { static: true }]
|
|
745
538
|
}], searchInput: [{
|
|
746
539
|
type: ViewChild,
|
|
747
540
|
args: ['searchInput', { static: false }]
|
|
748
|
-
}], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() =>
|
|
541
|
+
}], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FUI_OPTION), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
749
542
|
|
|
750
543
|
/**
|
|
751
544
|
* Generated bundle index. Do not edit.
|