@raintonic/formaui 0.4.0 → 0.9.2
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,58 +1,43 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, input, output, signal, inject, ElementRef, HostListener, ViewEncapsulation, ChangeDetectionStrategy, Component,
|
|
3
|
-
import { Subject
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
2
|
+
import { InjectionToken, input, output, signal, inject, ElementRef, HostListener, Directive, computed, ViewEncapsulation, ChangeDetectionStrategy, Component, contentChildren, effect, ViewChild } from '@angular/core';
|
|
3
|
+
import { Subject } from 'rxjs';
|
|
4
|
+
import { FuiCheckboxComponent } from '@raintonic/formaui/components/checkbox';
|
|
5
|
+
import { ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
6
|
+
import * as i1 from '@raintonic/formaui/cdk/form-field';
|
|
7
|
+
import { FuiPopupOverlayDirective, FuiFormControlSyncDirective, injectNgControl } from '@raintonic/formaui/cdk/form-field';
|
|
8
|
+
import { isEmpty, computeMultiDisplayValue, syncMultiOptions, applyOptionSelection, computePagedActiveOptionIndex, computeNextActiveOptionIndex, scrollOptionIntoView, findInitialActiveOptionIndex, getEnabledOptions, computeActiveDescendant, announceMessage, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
|
|
9
|
+
import { FuiIconComponent } from '@raintonic/formaui/components/icon';
|
|
10
|
+
import { FUI_FORM_FIELD } from '@raintonic/formaui/components/form-field';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Injection token used to provide the parent select to options.
|
|
13
14
|
*/
|
|
14
15
|
const FUI_SELECT = new InjectionToken('FUI_SELECT');
|
|
16
|
+
/**
|
|
17
|
+
* Vertical offset (px) between the select trigger bottom and overlay panel top.
|
|
18
|
+
* The default popup positions use 4px, but the select needs 16px for better
|
|
19
|
+
* visual spacing between the trigger and the dropdown panel.
|
|
20
|
+
*/
|
|
21
|
+
const FUI_SELECT_OVERLAY_OFFSET = 16;
|
|
15
22
|
|
|
16
23
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* Individual option component for use within fui-select.
|
|
20
|
-
* Works like Angular Material's mat-option with full accessibility support.
|
|
21
|
-
*
|
|
22
|
-
* ## Features
|
|
23
|
-
* - Disabled state support
|
|
24
|
-
* - Selection state management
|
|
25
|
-
* - Full accessibility support (ARIA attributes)
|
|
26
|
-
* - Keyboard navigation support
|
|
27
|
-
* - Custom content projection
|
|
28
|
-
* - Smooth hover animations
|
|
24
|
+
* Injection token for querying all option-like components
|
|
25
|
+
* via contentChildren from parent components.
|
|
29
26
|
*
|
|
30
|
-
*
|
|
27
|
+
* FuiOptionComponent provides this token so a single
|
|
28
|
+
* contentChildren(FUI_OPTION) query captures all options.
|
|
29
|
+
*/
|
|
30
|
+
const FUI_OPTION = new InjectionToken('FUI_OPTION');
|
|
31
|
+
/**
|
|
32
|
+
* Abstract base class for FuiOptionComponent.
|
|
31
33
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* <fui-select placeholder="Select a status">
|
|
35
|
-
* <fui-option value="active">Active</fui-option>
|
|
36
|
-
* <fui-option value="inactive">Inactive</fui-option>
|
|
37
|
-
* <fui-option value="pending" [disabled]="true">Pending (Disabled)</fui-option>
|
|
38
|
-
* </fui-select>
|
|
39
|
-
* ```
|
|
34
|
+
* Defines the contract that parent components rely on.
|
|
35
|
+
* FuiOptionComponent provides FUI_OPTION for DI-based querying.
|
|
40
36
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* <fui-select placeholder="Select a country">
|
|
44
|
-
* <fui-option value="us">
|
|
45
|
-
* <fui-icon name="flag-us"></fui-icon>
|
|
46
|
-
* United States
|
|
47
|
-
* </fui-option>
|
|
48
|
-
* <fui-option value="ca">
|
|
49
|
-
* <fui-icon name="flag-ca"></fui-icon>
|
|
50
|
-
* Canada
|
|
51
|
-
* </fui-option>
|
|
52
|
-
* </fui-select>
|
|
53
|
-
* ```
|
|
37
|
+
* Uses @Directive() so Angular recognises input()/output()/signal()/HostListener
|
|
38
|
+
* calls as valid (they are only valid on @Component or @Directive classes).
|
|
54
39
|
*/
|
|
55
|
-
class
|
|
40
|
+
class FuiOptionBase {
|
|
56
41
|
static nextId = 0;
|
|
57
42
|
/**
|
|
58
43
|
* The value of the option
|
|
@@ -73,16 +58,10 @@ class FuiOptionComponent {
|
|
|
73
58
|
stateChanges = new Subject();
|
|
74
59
|
// Element reference
|
|
75
60
|
_element = inject(ElementRef);
|
|
76
|
-
// Parent select (optional
|
|
61
|
+
// Parent select (optional — may not exist if used standalone)
|
|
77
62
|
_parentSelect = inject(FUI_SELECT, { optional: true });
|
|
78
63
|
// Unique ID
|
|
79
|
-
id = `fui-option-${
|
|
80
|
-
// Show checkmark only in multiple mode when selected
|
|
81
|
-
_showCheckmark = signal(false, ...(ngDevMode ? [{ debugName: "_showCheckmark" }] : /* istanbul ignore next */ []));
|
|
82
|
-
ngAfterViewInit() {
|
|
83
|
-
// Update checkmark visibility based on parent select's multiple mode
|
|
84
|
-
this._updateCheckmarkVisibility();
|
|
85
|
-
}
|
|
64
|
+
id = `fui-option-${FuiOptionBase.nextId++}`;
|
|
86
65
|
ngOnDestroy() {
|
|
87
66
|
this.stateChanges.complete();
|
|
88
67
|
}
|
|
@@ -99,7 +78,7 @@ class FuiOptionComponent {
|
|
|
99
78
|
this._parentSelect._onOptionSelected(this);
|
|
100
79
|
}
|
|
101
80
|
else {
|
|
102
|
-
// Standalone usage
|
|
81
|
+
// Standalone usage — emit event
|
|
103
82
|
this._emitSelectionChangeEvent();
|
|
104
83
|
}
|
|
105
84
|
}
|
|
@@ -118,7 +97,6 @@ class FuiOptionComponent {
|
|
|
118
97
|
select() {
|
|
119
98
|
if (!this._selected()) {
|
|
120
99
|
this._selected.set(true);
|
|
121
|
-
this._updateCheckmarkVisibility();
|
|
122
100
|
this.stateChanges.next();
|
|
123
101
|
}
|
|
124
102
|
}
|
|
@@ -128,7 +106,6 @@ class FuiOptionComponent {
|
|
|
128
106
|
deselect() {
|
|
129
107
|
if (this._selected()) {
|
|
130
108
|
this._selected.set(false);
|
|
131
|
-
this._updateCheckmarkVisibility();
|
|
132
109
|
this.stateChanges.next();
|
|
133
110
|
}
|
|
134
111
|
}
|
|
@@ -171,43 +148,121 @@ class FuiOptionComponent {
|
|
|
171
148
|
_getHostElement() {
|
|
172
149
|
return this._element.nativeElement;
|
|
173
150
|
}
|
|
174
|
-
/** Update checkmark visibility based on selection and multiple mode */
|
|
175
|
-
_updateCheckmarkVisibility() {
|
|
176
|
-
const isMultiple = this._parentSelect?.multiple() ?? false;
|
|
177
|
-
this._showCheckmark.set(isMultiple && this._selected());
|
|
178
|
-
}
|
|
179
151
|
/** Emits the selection change event */
|
|
180
152
|
_emitSelectionChangeEvent() {
|
|
181
153
|
this.selectionChange.emit({ source: this, value: this.value() });
|
|
182
154
|
}
|
|
183
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type:
|
|
184
|
-
static
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
155
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionBase, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
156
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.6", type: FuiOptionBase, isStandalone: true, inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selectionChange: "selectionChange" }, host: { listeners: { "click": "_handleClick($event)", "mouseenter": "_handleMouseEnter()", "mouseleave": "_handleMouseLeave()" } }, ngImport: i0 });
|
|
157
|
+
}
|
|
158
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionBase, decorators: [{
|
|
159
|
+
type: Directive
|
|
160
|
+
}], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], _handleClick: [{
|
|
161
|
+
type: HostListener,
|
|
162
|
+
args: ['click', ['$event']]
|
|
163
|
+
}], _handleMouseEnter: [{
|
|
164
|
+
type: HostListener,
|
|
165
|
+
args: ['mouseenter']
|
|
166
|
+
}], _handleMouseLeave: [{
|
|
167
|
+
type: HostListener,
|
|
168
|
+
args: ['mouseleave']
|
|
169
|
+
}] } });
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* # FuiOption Component
|
|
173
|
+
*
|
|
174
|
+
* Individual option component for use within fui-select.
|
|
175
|
+
* Works like Angular Material's mat-option with full accessibility support.
|
|
176
|
+
*
|
|
177
|
+
* ## Features
|
|
178
|
+
* - Disabled state support
|
|
179
|
+
* - Selection state management
|
|
180
|
+
* - Full accessibility support (ARIA attributes)
|
|
181
|
+
* - Keyboard navigation support
|
|
182
|
+
* - Custom content projection
|
|
183
|
+
* - Smooth hover animations
|
|
184
|
+
*
|
|
185
|
+
* ## Usage
|
|
186
|
+
*
|
|
187
|
+
* ### Basic Option
|
|
188
|
+
* ```html
|
|
189
|
+
* <fui-select placeholder="Select a status">
|
|
190
|
+
* <fui-option value="active">Active</fui-option>
|
|
191
|
+
* <fui-option value="inactive">Inactive</fui-option>
|
|
192
|
+
* <fui-option value="pending" [disabled]="true">Pending (Disabled)</fui-option>
|
|
193
|
+
* </fui-select>
|
|
194
|
+
* ```
|
|
195
|
+
*
|
|
196
|
+
* ### Option with Custom Content
|
|
197
|
+
* ```html
|
|
198
|
+
* <fui-select placeholder="Select a country">
|
|
199
|
+
* <fui-option value="us">
|
|
200
|
+
* <fui-icon name="flag-us"></fui-icon>
|
|
201
|
+
* United States
|
|
202
|
+
* </fui-option>
|
|
203
|
+
* <fui-option value="ca">
|
|
204
|
+
* <fui-icon name="flag-ca"></fui-icon>
|
|
205
|
+
* Canada
|
|
206
|
+
* </fui-option>
|
|
207
|
+
* </fui-select>
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* ### Multi-Select Options
|
|
211
|
+
* When `fui-select` has `[multiple]="true"`, `<fui-option>` automatically renders
|
|
212
|
+
* a checkbox indicator:
|
|
213
|
+
* ```html
|
|
214
|
+
* <fui-select [multiple]="true" placeholder="Select skills">
|
|
215
|
+
* <fui-option value="js">JavaScript</fui-option>
|
|
216
|
+
* <fui-option value="ts">TypeScript</fui-option>
|
|
217
|
+
* <fui-option value="py">Python</fui-option>
|
|
218
|
+
* </fui-select>
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
class FuiOptionComponent extends FuiOptionBase {
|
|
222
|
+
/** Whether the parent select is in multi-select mode */
|
|
223
|
+
_isMultiParent = computed(() => this._parentSelect?.multiple() ?? false, ...(ngDevMode ? [{ debugName: "_isMultiParent" }] : /* istanbul ignore next */ []));
|
|
224
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
|
|
225
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiOptionComponent, isStandalone: true, selector: "fui-option", host: { attributes: { "role": "option" }, properties: { "class.fui-option--selected": "_selected()", "class.fui-option--disabled": "disabled()", "class.fui-option--active": "_active()", "attr.id": "id", "attr.aria-selected": "_selected()", "attr.aria-disabled": "disabled()", "attr.tabindex": "-1" }, classAttribute: "fui-option" }, providers: [
|
|
226
|
+
{
|
|
227
|
+
provide: FUI_OPTION,
|
|
228
|
+
useExisting: FuiOptionComponent,
|
|
229
|
+
},
|
|
230
|
+
], usesInheritance: true, ngImport: i0, template: `
|
|
231
|
+
@if (_isMultiParent()) {
|
|
232
|
+
<fui-checkbox
|
|
233
|
+
class="fui-option__checkbox"
|
|
234
|
+
[checked]="_selected()"
|
|
235
|
+
[disabled]="disabled()"
|
|
236
|
+
[tabIndex]="-1"
|
|
237
|
+
aria-hidden="true"
|
|
238
|
+
/>
|
|
239
|
+
} @else if (_selected()) {}
|
|
192
240
|
<span class="fui-option__content">
|
|
193
241
|
<ng-content></ng-content>
|
|
194
242
|
</span>
|
|
195
|
-
`, isInline: true, 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
|
|
243
|
+
`, isInline: true, 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-option{position:relative;display:flex;font-size:var(--fui-text-base);align-items:center;margin:0 var(--fui-spacing-2);padding:var(--fui-spacing-3) var(--fui-spacing-4);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-radius-xs);color:var(--fui-text-primary);background-color:transparent;transition-property:background-color,color;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus{background-color:var(--fui-bg-default)}.fui-option--active:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--selected:not(.fui-option--disabled){background-color:var(--fui-bg-subtle)}.fui-option--selected:not(.fui-option--disabled):hover{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option.fui-option--selected.fui-option--active{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option__checkmark{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-right:.5rem;flex-shrink:0;color:var(--fui-primary-fg);transition-property:transform,opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-emphasized);transition-delay:0ms}.fui-option__checkmark svg{width:100%;height:100%}.fui-option__content{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-option--group-header{font-size:var(--fui-text-sm);font-weight:var(--fui-weight-semibold);text-transform:uppercase;letter-spacing:.05em;color:var(--fui-text-secondary);padding:.5rem 1rem;min-height:2rem;cursor:default}.fui-option--group-header:hover{background-color:transparent}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-border-primary);outline-offset:-2px}}\n"], dependencies: [{ kind: "component", type: FuiCheckboxComponent, selector: "fui-checkbox", inputs: ["readonly", "checked", "disabled", "indeterminate", "required", "labelPosition", "name", "value", "aria-label", "aria-labelledby", "aria-describedby", "disableRipple", "tabIndex", "errorStateMatcher"], outputs: ["change", "indeterminateChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
196
244
|
}
|
|
197
245
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, decorators: [{
|
|
198
246
|
type: Component,
|
|
199
|
-
args: [{ selector: 'fui-option', standalone: true, imports: [], template: `
|
|
200
|
-
@if (
|
|
201
|
-
<
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
247
|
+
args: [{ selector: 'fui-option', standalone: true, imports: [FuiCheckboxComponent], template: `
|
|
248
|
+
@if (_isMultiParent()) {
|
|
249
|
+
<fui-checkbox
|
|
250
|
+
class="fui-option__checkbox"
|
|
251
|
+
[checked]="_selected()"
|
|
252
|
+
[disabled]="disabled()"
|
|
253
|
+
[tabIndex]="-1"
|
|
254
|
+
aria-hidden="true"
|
|
255
|
+
/>
|
|
256
|
+
} @else if (_selected()) {}
|
|
207
257
|
<span class="fui-option__content">
|
|
208
258
|
<ng-content></ng-content>
|
|
209
259
|
</span>
|
|
210
|
-
`,
|
|
260
|
+
`, providers: [
|
|
261
|
+
{
|
|
262
|
+
provide: FUI_OPTION,
|
|
263
|
+
useExisting: FuiOptionComponent,
|
|
264
|
+
},
|
|
265
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
211
266
|
class: 'fui-option',
|
|
212
267
|
'[class.fui-option--selected]': '_selected()',
|
|
213
268
|
'[class.fui-option--disabled]': 'disabled()',
|
|
@@ -217,17 +272,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
217
272
|
'[attr.aria-selected]': '_selected()',
|
|
218
273
|
'[attr.aria-disabled]': 'disabled()',
|
|
219
274
|
'[attr.tabindex]': '-1',
|
|
220
|
-
}, 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
|
|
221
|
-
}]
|
|
222
|
-
type: HostListener,
|
|
223
|
-
args: ['click', ['$event']]
|
|
224
|
-
}], _handleMouseEnter: [{
|
|
225
|
-
type: HostListener,
|
|
226
|
-
args: ['mouseenter']
|
|
227
|
-
}], _handleMouseLeave: [{
|
|
228
|
-
type: HostListener,
|
|
229
|
-
args: ['mouseleave']
|
|
230
|
-
}] } });
|
|
275
|
+
}, 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-option{position:relative;display:flex;font-size:var(--fui-text-base);align-items:center;margin:0 var(--fui-spacing-2);padding:var(--fui-spacing-3) var(--fui-spacing-4);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-radius-xs);color:var(--fui-text-primary);background-color:transparent;transition-property:background-color,color;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus{background-color:var(--fui-bg-default)}.fui-option--active:not(.fui-option--disabled){background-color:var(--fui-primary-muted)}.fui-option--selected:not(.fui-option--disabled){background-color:var(--fui-bg-subtle)}.fui-option--selected:not(.fui-option--disabled):hover{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option.fui-option--selected.fui-option--active{background-color:color-mix(in srgb,var(--fui-bg-subtle) 100%,var(--fui-primary-muted) 100%)}.fui-option__checkmark{display:flex;align-items:center;justify-content:center;width:1rem;height:1rem;margin-right:.5rem;flex-shrink:0;color:var(--fui-primary-fg);transition-property:transform,opacity;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-emphasized);transition-delay:0ms}.fui-option__checkmark svg{width:100%;height:100%}.fui-option__content{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-option--group-header{font-size:var(--fui-text-sm);font-weight:var(--fui-weight-semibold);text-transform:uppercase;letter-spacing:.05em;color:var(--fui-text-secondary);padding:.5rem 1rem;min-height:2rem;cursor:default}.fui-option--group-header:hover{background-color:transparent}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-border-primary);outline-offset:-2px}}\n"] }]
|
|
276
|
+
}] });
|
|
231
277
|
|
|
232
278
|
/**
|
|
233
279
|
* # fui-select Component
|
|
@@ -292,16 +338,23 @@ class FuiSelectComponent {
|
|
|
292
338
|
// Static properties
|
|
293
339
|
static nextId = 0;
|
|
294
340
|
controlType = 'fui-select';
|
|
341
|
+
// Injected host directives
|
|
342
|
+
_popup = inject(FuiPopupOverlayDirective);
|
|
343
|
+
_formSync = inject(FuiFormControlSyncDirective);
|
|
344
|
+
// Optional parent form-field (null when used standalone)
|
|
345
|
+
_parentFormField = inject(FUI_FORM_FIELD, { optional: true });
|
|
295
346
|
// Inputs using new signal-based API
|
|
296
347
|
placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
|
|
297
348
|
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
|
|
298
|
-
|
|
349
|
+
readonlyInput = input(false, { ...(ngDevMode ? { debugName: "readonlyInput" } : /* istanbul ignore next */ {}), alias: 'readonly' });
|
|
299
350
|
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
300
|
-
errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
301
351
|
/**
|
|
302
|
-
*
|
|
352
|
+
* Comparison function for option values.
|
|
353
|
+
* Defaults to strict equality (`===`). Override when options use object
|
|
354
|
+
* values and you need to match by a property (e.g. comparing by `id`).
|
|
303
355
|
*/
|
|
304
356
|
compareWith = input((o1, o2) => o1 === o2, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
|
|
357
|
+
errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
305
358
|
// Outputs
|
|
306
359
|
valueChange = output();
|
|
307
360
|
selectionChange = output();
|
|
@@ -310,7 +363,6 @@ class FuiSelectComponent {
|
|
|
310
363
|
_value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
311
364
|
_focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
|
|
312
365
|
_disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
|
|
313
|
-
_readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
|
|
314
366
|
// FuiFormFieldControl implementation
|
|
315
367
|
stateChanges = new Subject();
|
|
316
368
|
_uid = `fui-select-${FuiSelectComponent.nextId++}`;
|
|
@@ -319,9 +371,6 @@ class FuiSelectComponent {
|
|
|
319
371
|
_errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
|
|
320
372
|
errorState = this._errorState;
|
|
321
373
|
// Form control references
|
|
322
|
-
_parentForm = inject(NgForm, { optional: true });
|
|
323
|
-
_parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
324
|
-
_defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
|
|
325
374
|
_ngControlRef = injectNgControl();
|
|
326
375
|
get ngControl() {
|
|
327
376
|
return this._ngControlRef.ngControl;
|
|
@@ -334,35 +383,24 @@ class FuiSelectComponent {
|
|
|
334
383
|
focused = this._focused;
|
|
335
384
|
_ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
|
|
336
385
|
disabled = computed(() => this._disabled() || this.disabledInput() || this._ngControlDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
return val === null || val === undefined || (this.isArray(val) && val.length === 0);
|
|
340
|
-
}, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
386
|
+
readonly = computed(() => this.readonlyInput(), ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
387
|
+
empty = computed(() => isEmpty(this._value()), ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
341
388
|
id = this._uid;
|
|
342
389
|
// ViewChild for trigger and panel
|
|
343
390
|
trigger;
|
|
344
391
|
panel;
|
|
345
392
|
// ContentChildren for options
|
|
346
|
-
options = contentChildren(
|
|
347
|
-
// Panel open/close state
|
|
348
|
-
panelOpen =
|
|
393
|
+
options = contentChildren(FUI_OPTION, { ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), descendants: true });
|
|
394
|
+
// Panel open/close state — projected from the popup overlay directive
|
|
395
|
+
panelOpen = computed(() => this._popup.panelOpen(), ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
|
|
349
396
|
// Active option index for keyboard navigation
|
|
350
397
|
_activeOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeOptionIndex" }] : /* istanbul ignore next */ []));
|
|
351
398
|
activeOptionIndex = this._activeOptionIndex.asReadonly();
|
|
352
399
|
// Live announcement for screen readers
|
|
353
400
|
_liveAnnouncement = signal('', ...(ngDevMode ? [{ debugName: "_liveAnnouncement" }] : /* istanbul ignore next */ []));
|
|
354
|
-
// Overlay reference
|
|
355
|
-
_overlayRef = null;
|
|
356
|
-
_overlaySubscriptions = new Subscription();
|
|
357
|
-
// Services
|
|
358
|
-
_overlayService = inject(FuiOverlayService);
|
|
359
|
-
_elementRef = inject(ElementRef);
|
|
360
|
-
_document = inject(DOCUMENT);
|
|
361
|
-
_ngZone = inject(NgZone);
|
|
362
|
-
_outsideClickSub;
|
|
363
401
|
// Type-ahead search
|
|
364
|
-
_typeaheadBuffer = '';
|
|
365
|
-
|
|
402
|
+
_typeaheadBuffer = signal('', ...(ngDevMode ? [{ debugName: "_typeaheadBuffer" }] : /* istanbul ignore next */ []));
|
|
403
|
+
_typeaheadResetTimer = null;
|
|
366
404
|
TYPE_AHEAD_DEBOUNCE = 200;
|
|
367
405
|
// ControlValueAccessor callbacks
|
|
368
406
|
_onChange = () => {
|
|
@@ -372,26 +410,7 @@ class FuiSelectComponent {
|
|
|
372
410
|
// Intentionally empty: will be replaced by Angular forms
|
|
373
411
|
};
|
|
374
412
|
// Computed properties
|
|
375
|
-
displayValue = computed(() => {
|
|
376
|
-
const currentValue = this.value();
|
|
377
|
-
const opts = this.options();
|
|
378
|
-
if (currentValue === null || currentValue === undefined) {
|
|
379
|
-
return '';
|
|
380
|
-
}
|
|
381
|
-
// Filter out options that don't have a value set yet
|
|
382
|
-
const validOpts = opts.filter((opt) => opt.value() !== undefined);
|
|
383
|
-
if (this.multiple()) {
|
|
384
|
-
if (this.isArray(currentValue)) {
|
|
385
|
-
const selectedOptions = validOpts.filter((opt) => currentValue.some((v) => this.compareWith()(v, opt.value())));
|
|
386
|
-
return selectedOptions.map((opt) => opt.getLabel()).join(', ');
|
|
387
|
-
}
|
|
388
|
-
return '';
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
const selectedOption = validOpts.find((opt) => this.compareWith()(opt.value(), currentValue));
|
|
392
|
-
return selectedOption ? selectedOption.getLabel() : String(currentValue);
|
|
393
|
-
}
|
|
394
|
-
}, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
413
|
+
displayValue = computed(() => computeMultiDisplayValue(this), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
|
|
395
414
|
constructor() {
|
|
396
415
|
// Set valueAccessor after NgControl is resolved
|
|
397
416
|
void Promise.resolve().then(() => {
|
|
@@ -418,107 +437,84 @@ class FuiSelectComponent {
|
|
|
418
437
|
});
|
|
419
438
|
// Effect to update options selected state when value or options change
|
|
420
439
|
effect(() => {
|
|
421
|
-
|
|
422
|
-
const opts = this.options();
|
|
423
|
-
const isMultiple = this.multiple();
|
|
424
|
-
const compareFn = this.compareWith();
|
|
425
|
-
opts.forEach((option) => {
|
|
426
|
-
const optValue = option.value();
|
|
427
|
-
// Skip options that don't have a value set yet
|
|
428
|
-
if (optValue === undefined) {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
let isSelected = false;
|
|
432
|
-
if (isMultiple && this.isArray(currentValue)) {
|
|
433
|
-
isSelected = currentValue.some((v) => compareFn(v, optValue));
|
|
434
|
-
}
|
|
435
|
-
else {
|
|
436
|
-
isSelected = compareFn(currentValue, optValue);
|
|
437
|
-
}
|
|
438
|
-
if (isSelected) {
|
|
439
|
-
option.select();
|
|
440
|
-
}
|
|
441
|
-
else {
|
|
442
|
-
option.deselect();
|
|
443
|
-
}
|
|
444
|
-
});
|
|
440
|
+
syncMultiOptions(this);
|
|
445
441
|
// Notify form-field that state may have changed (for display value updates)
|
|
446
442
|
this.stateChanges.next();
|
|
447
443
|
});
|
|
448
444
|
}
|
|
449
|
-
ngDoCheck() {
|
|
450
|
-
if (this.ngControl) {
|
|
451
|
-
updateErrorState(this.ngControl, this._errorState, this.errorStateMatcher(), this._defaultErrorStateMatcher, this._parentForm, this._parentFormGroup, this.stateChanges);
|
|
452
|
-
syncRequiredState(this.ngControl, this._required, this.stateChanges);
|
|
453
|
-
syncNgControlDisabled(this.ngControl, this._ngControlDisabled, this.stateChanges);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
445
|
ngOnDestroy() {
|
|
457
446
|
this.stateChanges.complete();
|
|
458
|
-
this.
|
|
459
|
-
|
|
460
|
-
if (this._typeaheadTimeout) {
|
|
461
|
-
clearTimeout(this._typeaheadTimeout);
|
|
447
|
+
if (this._typeaheadResetTimer) {
|
|
448
|
+
clearTimeout(this._typeaheadResetTimer);
|
|
462
449
|
}
|
|
463
450
|
}
|
|
464
451
|
ngAfterContentInit() {
|
|
465
452
|
// Set up initial selection based on value
|
|
466
453
|
this._syncOptionsSelection();
|
|
467
454
|
}
|
|
455
|
+
ngAfterViewInit() {
|
|
456
|
+
// Wire popup-overlay directive. Both trigger and panel use static:true so refs are
|
|
457
|
+
// resolved here regardless of panel open state (panel is always rendered via [hidden]).
|
|
458
|
+
this._popup.setTrigger(this.trigger ?? null);
|
|
459
|
+
this._popup.setPanel(this.panel ?? null);
|
|
460
|
+
this._popup.panelClass.set(['fui-select-overlay-panel']);
|
|
461
|
+
this._popup.backdropClass.set('fui-select-backdrop');
|
|
462
|
+
// Position the overlay panel 16px below the form-field wrapper (or trigger).
|
|
463
|
+
// This gives visual breathing room between the trigger and the dropdown.
|
|
464
|
+
this._popup.positions.set([
|
|
465
|
+
{
|
|
466
|
+
originX: 'start',
|
|
467
|
+
originY: 'bottom',
|
|
468
|
+
overlayX: 'start',
|
|
469
|
+
overlayY: 'top',
|
|
470
|
+
offsetY: FUI_SELECT_OVERLAY_OFFSET,
|
|
471
|
+
},
|
|
472
|
+
]);
|
|
473
|
+
// When inside a form-field, use the form-field wrapper as the width reference for
|
|
474
|
+
// the overlay panel so the dropdown spans the full width (including prefix/suffix).
|
|
475
|
+
if (this._parentFormField) {
|
|
476
|
+
this._popup.widthElement.set(this._parentFormField.getConnectedOverlayOrigin());
|
|
477
|
+
}
|
|
478
|
+
// Wire form-control-sync directive signals
|
|
479
|
+
this._formSync.errorState.set(this._errorState);
|
|
480
|
+
this._formSync.errorStateMatcher.set(this.errorStateMatcher());
|
|
481
|
+
this._formSync.required.set(this._required);
|
|
482
|
+
this._formSync.ngControlDisabled.set(this._ngControlDisabled);
|
|
483
|
+
this._formSync.stateChanges.set(this.stateChanges);
|
|
484
|
+
}
|
|
468
485
|
// ControlValueAccessor implementation
|
|
486
|
+
/** Sets the select value from the form model. Null/undefined coerces to null. */
|
|
469
487
|
writeValue(value) {
|
|
470
488
|
this._value.set(value ?? null);
|
|
471
|
-
//this._syncOptionsSelection();
|
|
472
489
|
this.stateChanges.next();
|
|
473
490
|
}
|
|
491
|
+
/** Registers the callback Angular calls when the value should propagate to the model. */
|
|
474
492
|
registerOnChange(fn) {
|
|
475
493
|
this._onChange = fn;
|
|
476
494
|
}
|
|
495
|
+
/** Registers the callback Angular calls when the control should be marked as touched. */
|
|
477
496
|
registerOnTouched(fn) {
|
|
478
497
|
this._onTouched = fn;
|
|
479
498
|
}
|
|
499
|
+
/** Enables or disables the control programmatically; mirrors the `disabled` form-control state. */
|
|
480
500
|
setDisabledState(isDisabled) {
|
|
481
501
|
this._disabled.set(isDisabled);
|
|
482
502
|
this.stateChanges.next();
|
|
483
503
|
}
|
|
484
504
|
// FuiFormFieldControl implementation
|
|
505
|
+
/** Opens the panel when the form-field container is clicked, unless disabled or readonly. */
|
|
485
506
|
onContainerClick(_event) {
|
|
486
507
|
if (!this.disabled() && !this.readonly()) {
|
|
487
508
|
this.toggle();
|
|
488
509
|
}
|
|
489
510
|
}
|
|
511
|
+
/** Stores the space-separated list of IDs for the aria-describedby attribute. */
|
|
490
512
|
setDescribedByIds(ids) {
|
|
491
513
|
this._ariaDescribedby = ids.length ? ids.join(' ') : null;
|
|
492
514
|
}
|
|
493
|
-
setReadOnly(readOnly) {
|
|
494
|
-
this._readOnly.set(readOnly);
|
|
495
|
-
}
|
|
496
515
|
// Sync options selection state with current value
|
|
497
516
|
_syncOptionsSelection() {
|
|
498
|
-
|
|
499
|
-
const opts = this.options();
|
|
500
|
-
const isMultiple = this.multiple();
|
|
501
|
-
const compareFn = this.compareWith();
|
|
502
|
-
opts.forEach((option) => {
|
|
503
|
-
const optValue = option.value();
|
|
504
|
-
// Skip options that don't have a value set yet
|
|
505
|
-
if (optValue === undefined) {
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
let isSelected = false;
|
|
509
|
-
if (isMultiple && this.isArray(currentValue)) {
|
|
510
|
-
isSelected = currentValue.some((v) => compareFn(v, optValue));
|
|
511
|
-
}
|
|
512
|
-
else if (currentValue !== null && currentValue !== undefined) {
|
|
513
|
-
isSelected = compareFn(currentValue, optValue);
|
|
514
|
-
}
|
|
515
|
-
if (isSelected) {
|
|
516
|
-
option.select();
|
|
517
|
-
}
|
|
518
|
-
else {
|
|
519
|
-
option.deselect();
|
|
520
|
-
}
|
|
521
|
-
});
|
|
517
|
+
syncMultiOptions(this);
|
|
522
518
|
}
|
|
523
519
|
// Focus/blur event handlers from template
|
|
524
520
|
_onFocus() {
|
|
@@ -530,7 +526,7 @@ class FuiSelectComponent {
|
|
|
530
526
|
// Check if focus moved to the overlay panel (e.g. clicking an option).
|
|
531
527
|
// In that case, don't close — the user is interacting with the dropdown.
|
|
532
528
|
const relatedTarget = event?.relatedTarget;
|
|
533
|
-
const overlayElement = this.
|
|
529
|
+
const overlayElement = this._popup.overlayRef()?.overlayElement;
|
|
534
530
|
if (relatedTarget && overlayElement?.contains(relatedTarget)) {
|
|
535
531
|
return;
|
|
536
532
|
}
|
|
@@ -542,54 +538,48 @@ class FuiSelectComponent {
|
|
|
542
538
|
this.stateChanges.next();
|
|
543
539
|
}
|
|
544
540
|
// Public methods
|
|
541
|
+
/** Focuses the select trigger element. */
|
|
545
542
|
focus() {
|
|
546
543
|
this.trigger?.nativeElement.focus();
|
|
547
544
|
}
|
|
545
|
+
/** Blurs the select trigger element. */
|
|
548
546
|
blur() {
|
|
549
547
|
this.trigger?.nativeElement.blur();
|
|
550
548
|
}
|
|
551
|
-
|
|
549
|
+
/** Toggles the select panel. No-op if disabled. */
|
|
552
550
|
toggle() {
|
|
553
551
|
if (this.disabled())
|
|
554
552
|
return;
|
|
555
|
-
if (this.panelOpen()) {
|
|
553
|
+
if (this._popup.panelOpen()) {
|
|
556
554
|
this.close();
|
|
557
555
|
}
|
|
558
556
|
else {
|
|
559
557
|
this.open();
|
|
560
558
|
}
|
|
561
559
|
}
|
|
562
|
-
|
|
560
|
+
/** Opens the select panel. No-op if disabled, readonly, or already open. */
|
|
563
561
|
open() {
|
|
564
|
-
if (this.disabled() || this.readonly() || this.panelOpen())
|
|
562
|
+
if (this.disabled() || this.readonly() || this._popup.panelOpen())
|
|
565
563
|
return;
|
|
566
564
|
// Ensure DOM focus is on the trigger so blur/Tab work correctly
|
|
567
565
|
this.trigger?.nativeElement.focus();
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
566
|
+
this._focused.set(true);
|
|
567
|
+
this._setInitialActiveOption();
|
|
568
|
+
this._popup.open();
|
|
569
|
+
if (this._popup.panelOpen()) {
|
|
570
|
+
this._scrollToActiveOption();
|
|
571
571
|
this.openedChange.emit(true);
|
|
572
|
-
|
|
573
|
-
this._setInitialActiveOption();
|
|
574
|
-
// Create overlay after the view updates
|
|
575
|
-
setTimeout(() => {
|
|
576
|
-
this._createOverlay();
|
|
577
|
-
this._scrollToActiveOption();
|
|
578
|
-
this._listenForOutsideClicks();
|
|
579
|
-
});
|
|
580
|
-
});
|
|
572
|
+
}
|
|
581
573
|
}
|
|
582
|
-
|
|
574
|
+
/** Closes the select panel and restores focus to the trigger unless `restoreFocus` is false. No-op if already closed. */
|
|
583
575
|
close(restoreFocus = true) {
|
|
584
|
-
if (!this.panelOpen())
|
|
576
|
+
if (!this._popup.panelOpen())
|
|
585
577
|
return;
|
|
586
|
-
this._outsideClickSub?.unsubscribe();
|
|
587
|
-
this.panelOpen.set(false);
|
|
588
578
|
this._focused.set(false);
|
|
589
579
|
this._activeOptionIndex.set(-1);
|
|
590
|
-
this._disposeOverlay();
|
|
591
|
-
this.openedChange.emit(false);
|
|
592
580
|
this._onTouched();
|
|
581
|
+
this._popup.close();
|
|
582
|
+
this.openedChange.emit(false);
|
|
593
583
|
// Return focus to trigger only when explicitly requested (e.g. Escape, backdrop click).
|
|
594
584
|
// When closing via Tab, let the browser move focus naturally.
|
|
595
585
|
// Focus synchronously to avoid race conditions with Tab key presses.
|
|
@@ -599,51 +589,21 @@ class FuiSelectComponent {
|
|
|
599
589
|
}
|
|
600
590
|
// Handle option selection (called by FuiOptionComponent)
|
|
601
591
|
_onOptionSelected(option) {
|
|
602
|
-
const
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
option.deselect();
|
|
611
|
-
}
|
|
612
|
-
else {
|
|
613
|
-
currentValue.push(newValue);
|
|
614
|
-
option.select();
|
|
615
|
-
}
|
|
616
|
-
this._value.set(currentValue);
|
|
617
|
-
this._onChange(currentValue);
|
|
618
|
-
this.valueChange.emit(currentValue);
|
|
619
|
-
this.selectionChange.emit({ source: this, value: currentValue });
|
|
620
|
-
// Announce for screen readers
|
|
621
|
-
const label = option.getLabel();
|
|
622
|
-
const action = index > -1 ? 'deselected' : 'selected';
|
|
623
|
-
this._announce(`${label} ${action}`);
|
|
592
|
+
const result = applyOptionSelection(this, option);
|
|
593
|
+
this._value.set(result.newValue);
|
|
594
|
+
this._onChange(result.newValue);
|
|
595
|
+
this.valueChange.emit(result.newValue);
|
|
596
|
+
this.selectionChange.emit({ source: this, value: result.newValue });
|
|
597
|
+
this.stateChanges.next();
|
|
598
|
+
if (result.wasSelected) {
|
|
599
|
+
this._announce(`${option.getLabel()} deselected`);
|
|
624
600
|
}
|
|
625
601
|
else {
|
|
626
|
-
// Deselect previous option
|
|
627
|
-
const opts = this.options();
|
|
628
|
-
opts.forEach((opt) => {
|
|
629
|
-
if (opt !== option) {
|
|
630
|
-
opt.deselect();
|
|
631
|
-
}
|
|
632
|
-
});
|
|
633
|
-
option.select();
|
|
634
|
-
this._value.set(newValue);
|
|
635
|
-
this._onChange(newValue);
|
|
636
|
-
this.valueChange.emit(newValue);
|
|
637
|
-
this.selectionChange.emit({ source: this, value: newValue });
|
|
638
|
-
// Announce for screen readers
|
|
639
602
|
this._announce(`${option.getLabel()} selected`);
|
|
603
|
+
}
|
|
604
|
+
if (!this.multiple()) {
|
|
640
605
|
this.close();
|
|
641
606
|
}
|
|
642
|
-
this.stateChanges.next();
|
|
643
|
-
}
|
|
644
|
-
// Get display value for trigger
|
|
645
|
-
_getDisplayValue() {
|
|
646
|
-
return this.displayValue();
|
|
647
607
|
}
|
|
648
608
|
// Handle keyboard navigation
|
|
649
609
|
_handleKeydown(event) {
|
|
@@ -686,7 +646,6 @@ class FuiSelectComponent {
|
|
|
686
646
|
}
|
|
687
647
|
// Handle keydown when panel is open
|
|
688
648
|
_handleOpenKeydown(event) {
|
|
689
|
-
const PAGE_SIZE = 5;
|
|
690
649
|
const key = event.key;
|
|
691
650
|
const opts = this._getEnabledOptions();
|
|
692
651
|
const activeIndex = this._activeOptionIndex();
|
|
@@ -710,15 +669,13 @@ class FuiSelectComponent {
|
|
|
710
669
|
case 'PageDown':
|
|
711
670
|
event.preventDefault();
|
|
712
671
|
if (opts.length > 0) {
|
|
713
|
-
|
|
714
|
-
this._setActiveOptionIndex(newIndex);
|
|
672
|
+
this._setActiveOptionIndex(computePagedActiveOptionIndex(opts.length, activeIndex, 1));
|
|
715
673
|
}
|
|
716
674
|
break;
|
|
717
675
|
case 'PageUp':
|
|
718
676
|
event.preventDefault();
|
|
719
677
|
if (opts.length > 0) {
|
|
720
|
-
|
|
721
|
-
this._setActiveOptionIndex(newIndex);
|
|
678
|
+
this._setActiveOptionIndex(computePagedActiveOptionIndex(opts.length, activeIndex, -1));
|
|
722
679
|
}
|
|
723
680
|
break;
|
|
724
681
|
case 'Enter':
|
|
@@ -730,6 +687,7 @@ class FuiSelectComponent {
|
|
|
730
687
|
break;
|
|
731
688
|
case 'Escape':
|
|
732
689
|
event.preventDefault();
|
|
690
|
+
event.stopPropagation();
|
|
733
691
|
this.close();
|
|
734
692
|
break;
|
|
735
693
|
case 'Tab':
|
|
@@ -750,16 +708,7 @@ class FuiSelectComponent {
|
|
|
750
708
|
const opts = this._getEnabledOptions();
|
|
751
709
|
if (opts.length === 0)
|
|
752
710
|
return;
|
|
753
|
-
|
|
754
|
-
let newIndex = currentIndex + delta;
|
|
755
|
-
// Wrap around
|
|
756
|
-
if (newIndex < 0) {
|
|
757
|
-
newIndex = opts.length - 1;
|
|
758
|
-
}
|
|
759
|
-
else if (newIndex >= opts.length) {
|
|
760
|
-
newIndex = 0;
|
|
761
|
-
}
|
|
762
|
-
this._setActiveOptionIndex(newIndex);
|
|
711
|
+
this._setActiveOptionIndex(computeNextActiveOptionIndex(opts.length, this._activeOptionIndex(), delta));
|
|
763
712
|
}
|
|
764
713
|
// Set the active option index
|
|
765
714
|
_setActiveOptionIndex(index) {
|
|
@@ -774,11 +723,11 @@ class FuiSelectComponent {
|
|
|
774
723
|
const activeOption = opts[index];
|
|
775
724
|
activeOption.setActive();
|
|
776
725
|
this._activeOptionIndex.set(index);
|
|
777
|
-
|
|
726
|
+
scrollOptionIntoView(activeOption._getHostElement(), this.panel?.nativeElement);
|
|
778
727
|
// Announce active option for screen readers (when panel is open and using keyboard)
|
|
779
728
|
if (this.panelOpen()) {
|
|
780
729
|
const label = activeOption.getLabel();
|
|
781
|
-
const selectedState = activeOption._selected() ? ', selected' : '';
|
|
730
|
+
const selectedState = activeOption._selected ? (activeOption._selected() ? ', selected' : '') : '';
|
|
782
731
|
this._announce(`${label}${selectedState}, ${index + 1} of ${opts.length}`);
|
|
783
732
|
}
|
|
784
733
|
}
|
|
@@ -789,17 +738,7 @@ class FuiSelectComponent {
|
|
|
789
738
|
this._activeOptionIndex.set(-1);
|
|
790
739
|
return;
|
|
791
740
|
}
|
|
792
|
-
const
|
|
793
|
-
const compareFn = this.compareWith();
|
|
794
|
-
// Find the first selected option
|
|
795
|
-
let selectedIndex = -1;
|
|
796
|
-
if (!this.multiple() && currentValue !== null && currentValue !== undefined) {
|
|
797
|
-
selectedIndex = opts.findIndex((opt) => compareFn(opt.value(), currentValue));
|
|
798
|
-
}
|
|
799
|
-
else if (this.multiple() && this.isArray(currentValue) && currentValue.length > 0) {
|
|
800
|
-
selectedIndex = opts.findIndex((opt) => currentValue.some((v) => compareFn(v, opt.value())));
|
|
801
|
-
}
|
|
802
|
-
const initialIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
741
|
+
const initialIndex = findInitialActiveOptionIndex(opts, this._value(), this.multiple(), this.compareWith());
|
|
803
742
|
this._setActiveOptionIndex(initialIndex);
|
|
804
743
|
}
|
|
805
744
|
// Scroll to active option
|
|
@@ -808,32 +747,18 @@ class FuiSelectComponent {
|
|
|
808
747
|
const activeIndex = this._activeOptionIndex();
|
|
809
748
|
if (activeIndex < 0 || activeIndex >= opts.length)
|
|
810
749
|
return;
|
|
811
|
-
|
|
812
|
-
const element = activeOption._getHostElement();
|
|
813
|
-
const panel = this.panel?.nativeElement;
|
|
814
|
-
if (element && panel) {
|
|
815
|
-
const optionTop = element.offsetTop;
|
|
816
|
-
const optionBottom = optionTop + element.offsetHeight;
|
|
817
|
-
const panelTop = panel.scrollTop;
|
|
818
|
-
const panelBottom = panelTop + panel.clientHeight;
|
|
819
|
-
if (optionTop < panelTop) {
|
|
820
|
-
panel.scrollTop = optionTop;
|
|
821
|
-
}
|
|
822
|
-
else if (optionBottom > panelBottom) {
|
|
823
|
-
panel.scrollTop = optionBottom - panel.clientHeight;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
750
|
+
scrollOptionIntoView(opts[activeIndex]._getHostElement(), this.panel?.nativeElement);
|
|
826
751
|
}
|
|
827
752
|
// Handle type-ahead search
|
|
828
753
|
_handleTypeahead(char) {
|
|
829
|
-
// Clear
|
|
830
|
-
if (this.
|
|
831
|
-
clearTimeout(this.
|
|
754
|
+
// Clear timer and append char to buffer
|
|
755
|
+
if (this._typeaheadResetTimer) {
|
|
756
|
+
clearTimeout(this._typeaheadResetTimer);
|
|
832
757
|
}
|
|
833
|
-
this._typeaheadBuffer
|
|
758
|
+
this._typeaheadBuffer.update((b) => b + char.toLowerCase());
|
|
834
759
|
// Find matching option
|
|
835
760
|
const opts = this._getEnabledOptions();
|
|
836
|
-
const matchIndex = opts.findIndex((opt) => opt.getLabel().toLowerCase().startsWith(this._typeaheadBuffer));
|
|
761
|
+
const matchIndex = opts.findIndex((opt) => opt.getLabel().toLowerCase().startsWith(this._typeaheadBuffer()));
|
|
837
762
|
if (matchIndex >= 0) {
|
|
838
763
|
if (this.panelOpen()) {
|
|
839
764
|
this._setActiveOptionIndex(matchIndex);
|
|
@@ -844,8 +769,8 @@ class FuiSelectComponent {
|
|
|
844
769
|
}
|
|
845
770
|
}
|
|
846
771
|
// Clear buffer after debounce
|
|
847
|
-
this.
|
|
848
|
-
this._typeaheadBuffer
|
|
772
|
+
this._typeaheadResetTimer = setTimeout(() => {
|
|
773
|
+
this._typeaheadBuffer.set('');
|
|
849
774
|
}, this.TYPE_AHEAD_DEBOUNCE);
|
|
850
775
|
}
|
|
851
776
|
// Select first non-disabled option
|
|
@@ -864,113 +789,25 @@ class FuiSelectComponent {
|
|
|
864
789
|
}
|
|
865
790
|
// Get all non-disabled options
|
|
866
791
|
_getEnabledOptions() {
|
|
867
|
-
return this.options()
|
|
868
|
-
}
|
|
869
|
-
// Start listening for outside clicks when panel opens
|
|
870
|
-
_listenForOutsideClicks() {
|
|
871
|
-
this._outsideClickSub?.unsubscribe();
|
|
872
|
-
// Run outside Angular zone to avoid triggering change detection on every document click
|
|
873
|
-
this._ngZone.runOutsideAngular(() => {
|
|
874
|
-
// Use setTimeout to skip the current click event that opened the panel
|
|
875
|
-
setTimeout(() => {
|
|
876
|
-
this._outsideClickSub = fromEvent(this._document, 'click')
|
|
877
|
-
.pipe(filter(() => this.panelOpen()), filter((event) => {
|
|
878
|
-
const target = event.target;
|
|
879
|
-
const triggerElement = this.trigger?.nativeElement;
|
|
880
|
-
const panelElement = this.panel?.nativeElement;
|
|
881
|
-
const overlayElement = this._overlayRef?.overlayElement;
|
|
882
|
-
return (!triggerElement?.contains(target) &&
|
|
883
|
-
!panelElement?.contains(target) &&
|
|
884
|
-
!overlayElement?.contains(target));
|
|
885
|
-
}))
|
|
886
|
-
.subscribe(() => {
|
|
887
|
-
this._ngZone.run(() => {
|
|
888
|
-
this.close();
|
|
889
|
-
});
|
|
890
|
-
});
|
|
891
|
-
});
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
// Create overlay for panel
|
|
895
|
-
_createOverlay() {
|
|
896
|
-
if (this._overlayRef || !this.panel || !this.trigger)
|
|
897
|
-
return;
|
|
898
|
-
const triggerElement = this.trigger.nativeElement;
|
|
899
|
-
const triggerWidth = triggerElement.getBoundingClientRect().width;
|
|
900
|
-
const positions = [
|
|
901
|
-
// Primary: open below
|
|
902
|
-
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
903
|
-
// Fallback: open above if no space below
|
|
904
|
-
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
905
|
-
];
|
|
906
|
-
const positionStrategy = this._overlayService
|
|
907
|
-
.position()
|
|
908
|
-
.connectedTo(triggerElement, positions)
|
|
909
|
-
.withPush(false)
|
|
910
|
-
.withFlexibleDimensions(true)
|
|
911
|
-
.withViewportMargin(8);
|
|
912
|
-
this._overlayRef = this._overlayService.create({
|
|
913
|
-
positionStrategy,
|
|
914
|
-
scrollStrategy: this._overlayService.scrollStrategies.reposition(),
|
|
915
|
-
hasBackdrop: true,
|
|
916
|
-
backdropClass: 'fui-select-backdrop',
|
|
917
|
-
backdropClickBehavior: 'close',
|
|
918
|
-
panelClass: ['fui-select-overlay-panel'],
|
|
919
|
-
width: triggerWidth,
|
|
920
|
-
});
|
|
921
|
-
// Track overlay subscriptions for proper cleanup
|
|
922
|
-
this._overlaySubscriptions.unsubscribe();
|
|
923
|
-
this._overlaySubscriptions = new Subscription();
|
|
924
|
-
this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
|
|
925
|
-
this.close();
|
|
926
|
-
}));
|
|
927
|
-
this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
|
|
928
|
-
if (event.key === 'Escape') {
|
|
929
|
-
this.close();
|
|
930
|
-
}
|
|
931
|
-
}));
|
|
932
|
-
// Attach panel to overlay
|
|
933
|
-
const panelElement = this.panel.nativeElement;
|
|
934
|
-
this._overlayRef.attach(panelElement);
|
|
935
|
-
}
|
|
936
|
-
// Dispose overlay
|
|
937
|
-
_disposeOverlay() {
|
|
938
|
-
this._overlaySubscriptions.unsubscribe();
|
|
939
|
-
if (this._overlayRef) {
|
|
940
|
-
this._overlayRef.dispose();
|
|
941
|
-
this._overlayRef = null;
|
|
942
|
-
}
|
|
792
|
+
return getEnabledOptions(this.options());
|
|
943
793
|
}
|
|
944
794
|
// Get the active option's id for aria-activedescendant
|
|
945
795
|
_getActiveDescendant() {
|
|
946
|
-
|
|
947
|
-
const activeIndex = this._activeOptionIndex();
|
|
948
|
-
if (activeIndex >= 0 && activeIndex < opts.length) {
|
|
949
|
-
return opts[activeIndex].id;
|
|
950
|
-
}
|
|
951
|
-
return null;
|
|
796
|
+
return computeActiveDescendant(this._getEnabledOptions(), this._activeOptionIndex());
|
|
952
797
|
}
|
|
953
|
-
/**
|
|
954
|
-
* Announces a message to screen readers via the aria-live region.
|
|
955
|
-
* Clears the message after a brief delay to allow repeated announcements.
|
|
956
|
-
*/
|
|
957
798
|
_announce(message) {
|
|
958
|
-
|
|
959
|
-
this._liveAnnouncement.set('');
|
|
960
|
-
setTimeout(() => {
|
|
961
|
-
this._liveAnnouncement.set(message);
|
|
962
|
-
}, 50);
|
|
799
|
+
announceMessage(this._liveAnnouncement, message);
|
|
963
800
|
}
|
|
964
801
|
// Mat-select compatibility methods
|
|
965
802
|
get selected() {
|
|
966
803
|
return this.value;
|
|
967
804
|
}
|
|
968
|
-
|
|
805
|
+
/** @internal Helper to check if a value is an array. Used by tests. */
|
|
969
806
|
isArray(value) {
|
|
970
807
|
return Array.isArray(value);
|
|
971
808
|
}
|
|
972
809
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
973
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiSelectComponent, isStandalone: true, selector: "fui-select", inputs: { placeholderInput: { classPropertyName: "placeholderInput", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabledInput: { classPropertyName: "disabledInput", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null },
|
|
810
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiSelectComponent, isStandalone: true, selector: "fui-select", 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" }, host: { properties: { "attr.id": "id", "class.fui-select--open": "panelOpen()", "class.fui-select--disabled": "disabled()", "class.fui-select--multiple": "multiple()", "class.fui-select--readonly": "readonly()", "attr.aria-readonly": "readonly() ? \"true\" : null" }, classAttribute: "fui-select" }, providers: [
|
|
974
811
|
{
|
|
975
812
|
provide: NG_VALUE_ACCESSOR,
|
|
976
813
|
useExisting: FuiSelectComponent,
|
|
@@ -984,17 +821,25 @@ class FuiSelectComponent {
|
|
|
984
821
|
provide: FUI_SELECT,
|
|
985
822
|
useExisting: FuiSelectComponent,
|
|
986
823
|
},
|
|
987
|
-
], queries: [{ propertyName: "options", predicate:
|
|
824
|
+
], queries: [{ propertyName: "options", predicate: FUI_OPTION, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "trigger", first: true, predicate: ["trigger"], descendants: true, static: true }, { propertyName: "panel", first: true, predicate: ["panel"], descendants: true, static: 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: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__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 [attr.aria-readonly]=\"readonly() ? 'true' : null\"\r\n [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{ displayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!readonly() && !disabled()) {\r\n <span class=\"fui-select__arrow\" 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; hidden when closed so #panel ViewChild is always available) -->\r\n<div\r\n #panel\r\n [hidden]=\"false\"\r\n [id]=\"id + '-panel'\"\r\n class=\"fui-select__panel\"\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</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-select{display:inline-block;width:100%;min-width:8rem;position:relative}.fui-select__trigger{width:100%;min-height:100%;display:flex;align-items:center;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;background:transparent;border:none;font-family:var(--fui-font-sans);font-size:var(--fui-text-base);font-weight:var(--fui-weight-regular);line-height:var(--fui-leading-normal);letter-spacing:var(--fui-tracking-normal);color:var(--fui-text-primary)}.fui-select__trigger:focus{outline:none}.fui-select__trigger:focus-visible{outline:none}.fui-select__trigger[aria-disabled=true]{cursor:not-allowed;opacity:.5}.fui-select__value{flex:1;display:flex;align-items:center;overflow:hidden;min-width:0}.fui-select__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-property:color,transform;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-select--open .fui-select__arrow{color:var(--fui-field-border-focus);transform:rotate(180deg)}.fui-select--disabled{pointer-events:none}.fui-select--disabled .fui-select__trigger{cursor:not-allowed;opacity:.5}.fui-select--disabled .fui-select__arrow{color:var(--fui-text-disabled)}.fui-select--readonly{pointer-events:none}.fui-select--readonly .fui-select__trigger{opacity:1;cursor:default;color:var(--fui-text-primary)}.fui-select--readonly .fui-select__trigger[aria-disabled=true]{opacity:1;cursor:default}.fui-select--readonly .fui-select__value-text{color:var(--fui-text-primary)}.fui-select__value-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select__placeholder{color:var(--fui-text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select .fui-select__panel{display:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-radius-md);--fui-select-panel-shadow: var(--fui-shadow-lg);--fui-select-panel-bg: var(--fui-bg-default);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:var(--fui-border-width-sm) solid var(--fui-border-default);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-2) 0;transform-origin:top center;animation:fui-popover-enter var(--fui-duration-base) var(--fui-ease-out) both;will-change:transform,opacity}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-bg-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb:hover{background:var(--fui-text-secondary)}.fui-select-backdrop{background:transparent}.fui-select-overlay-panel{max-width:32rem}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{animation:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "component", type: FuiIconComponent, selector: "fui-icon", inputs: ["name", "size", "weight", "color", "ariaLabel", "spin", "pulse"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
988
825
|
}
|
|
989
826
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, decorators: [{
|
|
990
827
|
type: Component,
|
|
991
|
-
args: [{ selector: 'fui-select', standalone: true, imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None,
|
|
828
|
+
args: [{ selector: 'fui-select', standalone: true, imports: [ReactiveFormsModule, FuiIconComponent], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, hostDirectives: [
|
|
829
|
+
{
|
|
830
|
+
directive: FuiPopupOverlayDirective,
|
|
831
|
+
inputs: ['positions', 'panelClass', 'backdropClass', 'scrollStrategy', 'minWidthFromTrigger'],
|
|
832
|
+
outputs: ['openedChange', 'escapeKey'],
|
|
833
|
+
},
|
|
834
|
+
FuiFormControlSyncDirective,
|
|
835
|
+
], host: {
|
|
992
836
|
class: 'fui-select',
|
|
993
837
|
'[attr.id]': 'id',
|
|
994
838
|
'[class.fui-select--open]': 'panelOpen()',
|
|
995
839
|
'[class.fui-select--disabled]': 'disabled()',
|
|
996
840
|
'[class.fui-select--multiple]': 'multiple()',
|
|
997
|
-
'[class.fui-select--readonly]': '
|
|
841
|
+
'[class.fui-select--readonly]': 'readonly()',
|
|
842
|
+
'[attr.aria-readonly]': 'readonly() ? "true" : null',
|
|
998
843
|
}, providers: [
|
|
999
844
|
{
|
|
1000
845
|
provide: NG_VALUE_ACCESSOR,
|
|
@@ -1009,18 +854,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImpor
|
|
|
1009
854
|
provide: FUI_SELECT,
|
|
1010
855
|
useExisting: FuiSelectComponent,
|
|
1011
856
|
},
|
|
1012
|
-
], template: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__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 [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{
|
|
1013
|
-
}], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }],
|
|
857
|
+
], template: "<!-- Select Trigger -->\r\n<div\r\n #trigger\r\n [id]=\"id\"\r\n class=\"fui-select__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 [attr.aria-readonly]=\"readonly() ? 'true' : null\"\r\n [attr.aria-label]=\"empty() ? placeholder() : null\"\r\n aria-autocomplete=\"none\"\r\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\r\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\r\n (keydown)=\"_handleKeydown($event)\"\r\n (focus)=\"_onFocus()\"\r\n (blur)=\"_onBlur($event)\"\r\n>\r\n <span class=\"fui-select__value\">\r\n @if (empty()) {\r\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\r\n } @else {\r\n <span class=\"fui-select__value-text\">{{ displayValue() }}</span>\r\n }\r\n </span>\r\n\r\n @if (!readonly() && !disabled()) {\r\n <span class=\"fui-select__arrow\" 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; hidden when closed so #panel ViewChild is always available) -->\r\n<div\r\n #panel\r\n [hidden]=\"false\"\r\n [id]=\"id + '-panel'\"\r\n class=\"fui-select__panel\"\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</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-select{display:inline-block;width:100%;min-width:8rem;position:relative}.fui-select__trigger{width:100%;min-height:100%;display:flex;align-items:center;cursor:pointer;outline:none;-webkit-user-select:none;user-select:none;background:transparent;border:none;font-family:var(--fui-font-sans);font-size:var(--fui-text-base);font-weight:var(--fui-weight-regular);line-height:var(--fui-leading-normal);letter-spacing:var(--fui-tracking-normal);color:var(--fui-text-primary)}.fui-select__trigger:focus{outline:none}.fui-select__trigger:focus-visible{outline:none}.fui-select__trigger[aria-disabled=true]{cursor:not-allowed;opacity:.5}.fui-select__value{flex:1;display:flex;align-items:center;overflow:hidden;min-width:0}.fui-select__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-property:color,transform;transition-duration:var(--fui-duration-fast);transition-timing-function:var(--fui-ease-in-out);transition-delay:0ms}.fui-select--open .fui-select__arrow{color:var(--fui-field-border-focus);transform:rotate(180deg)}.fui-select--disabled{pointer-events:none}.fui-select--disabled .fui-select__trigger{cursor:not-allowed;opacity:.5}.fui-select--disabled .fui-select__arrow{color:var(--fui-text-disabled)}.fui-select--readonly{pointer-events:none}.fui-select--readonly .fui-select__trigger{opacity:1;cursor:default;color:var(--fui-text-primary)}.fui-select--readonly .fui-select__trigger[aria-disabled=true]{opacity:1;cursor:default}.fui-select--readonly .fui-select__value-text{color:var(--fui-text-primary)}.fui-select__value-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select__placeholder{color:var(--fui-text-disabled);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fui-select .fui-select__panel{display:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-radius-md);--fui-select-panel-shadow: var(--fui-shadow-lg);--fui-select-panel-bg: var(--fui-bg-default);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:var(--fui-border-width-sm) solid var(--fui-border-default);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-2) 0;transform-origin:top center;animation:fui-popover-enter var(--fui-duration-base) var(--fui-ease-out) both;will-change:transform,opacity}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-bg-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-default);border-radius:var(--fui-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb:hover{background:var(--fui-text-secondary)}.fui-select-backdrop{background:transparent}.fui-select-overlay-panel{max-width:32rem}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{animation:none}}\n"] }]
|
|
858
|
+
}], 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"] }], trigger: [{
|
|
1014
859
|
type: ViewChild,
|
|
1015
|
-
args: ['trigger', { static:
|
|
860
|
+
args: ['trigger', { static: true }]
|
|
1016
861
|
}], panel: [{
|
|
1017
862
|
type: ViewChild,
|
|
1018
|
-
args: ['panel', { static:
|
|
1019
|
-
}], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() =>
|
|
863
|
+
args: ['panel', { static: true }]
|
|
864
|
+
}], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FUI_OPTION), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
1020
865
|
|
|
1021
866
|
/**
|
|
1022
867
|
* Generated bundle index. Do not edit.
|
|
1023
868
|
*/
|
|
1024
869
|
|
|
1025
|
-
export { FUI_SELECT, FuiOptionComponent, FuiSelectComponent };
|
|
870
|
+
export { FUI_OPTION, FUI_SELECT, FuiOptionBase, FuiOptionComponent, FuiSelectComponent };
|
|
1026
871
|
//# sourceMappingURL=raintonic-formaui-components-select.mjs.map
|