@raintonic/formaui 0.2.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 +7 -0
- package/README.md +145 -0
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +806 -0
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs +86 -0
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs +1757 -0
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs +287 -0
- package/fesm2022/raintonic-formaui-cdk-virtual-scroll.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-accordion.mjs +217 -0
- package/fesm2022/raintonic-formaui-components-accordion.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-alert.mjs +161 -0
- package/fesm2022/raintonic-formaui-components-alert.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs +726 -0
- package/fesm2022/raintonic-formaui-components-autocomplete.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-avatar.mjs +92 -0
- package/fesm2022/raintonic-formaui-components-avatar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-badge.mjs +107 -0
- package/fesm2022/raintonic-formaui-components-badge.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-big-menu.mjs +68 -0
- package/fesm2022/raintonic-formaui-components-big-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs +55 -0
- package/fesm2022/raintonic-formaui-components-breadcrumb.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-button-group.mjs +103 -0
- package/fesm2022/raintonic-formaui-components-button-group.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-button.mjs +241 -0
- package/fesm2022/raintonic-formaui-components-button.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-card.mjs +270 -0
- package/fesm2022/raintonic-formaui-components-card.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-checkbox.mjs +295 -0
- package/fesm2022/raintonic-formaui-components-checkbox.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs +631 -0
- package/fesm2022/raintonic-formaui-components-data-table.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-date-picker.mjs +1331 -0
- package/fesm2022/raintonic-formaui-components-date-picker.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-divider.mjs +41 -0
- package/fesm2022/raintonic-formaui-components-divider.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-drawer.mjs +190 -0
- package/fesm2022/raintonic-formaui-components-drawer.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-dynamic-form.mjs +266 -0
- package/fesm2022/raintonic-formaui-components-dynamic-form.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs +33 -0
- package/fesm2022/raintonic-formaui-components-empty-state.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-file-upload.mjs +246 -0
- package/fesm2022/raintonic-formaui-components-file-upload.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-form-field.mjs +482 -0
- package/fesm2022/raintonic-formaui-components-form-field.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-icon.mjs +117 -0
- package/fesm2022/raintonic-formaui-components-icon.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-input.mjs +327 -0
- package/fesm2022/raintonic-formaui-components-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-list.mjs +149 -0
- package/fesm2022/raintonic-formaui-components-list.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-menu.mjs +896 -0
- package/fesm2022/raintonic-formaui-components-menu.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-number-input.mjs +345 -0
- package/fesm2022/raintonic-formaui-components-number-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-paginator.mjs +139 -0
- package/fesm2022/raintonic-formaui-components-paginator.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-password-input.mjs +306 -0
- package/fesm2022/raintonic-formaui-components-password-input.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-popover.mjs +451 -0
- package/fesm2022/raintonic-formaui-components-popover.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-progressbar.mjs +148 -0
- package/fesm2022/raintonic-formaui-components-progressbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-radio.mjs +260 -0
- package/fesm2022/raintonic-formaui-components-radio.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-select.mjs +1011 -0
- package/fesm2022/raintonic-formaui-components-select.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-side-panel.mjs +150 -0
- package/fesm2022/raintonic-formaui-components-side-panel.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-sidebar.mjs +257 -0
- package/fesm2022/raintonic-formaui-components-sidebar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs +50 -0
- package/fesm2022/raintonic-formaui-components-skeleton.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-slider.mjs +347 -0
- package/fesm2022/raintonic-formaui-components-slider.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-spinner.mjs +63 -0
- package/fesm2022/raintonic-formaui-components-spinner.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-stepper.mjs +317 -0
- package/fesm2022/raintonic-formaui-components-stepper.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs +197 -0
- package/fesm2022/raintonic-formaui-components-tab.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tag.mjs +78 -0
- package/fesm2022/raintonic-formaui-components-tag.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-time-picker.mjs +644 -0
- package/fesm2022/raintonic-formaui-components-time-picker.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs +171 -0
- package/fesm2022/raintonic-formaui-components-toggle.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-toolbar.mjs +140 -0
- package/fesm2022/raintonic-formaui-components-toolbar.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tooltip.mjs +555 -0
- package/fesm2022/raintonic-formaui-components-tooltip.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs +314 -0
- package/fesm2022/raintonic-formaui-components-tree-select.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree-table.mjs +103 -0
- package/fesm2022/raintonic-formaui-components-tree-table.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-components-tree.mjs +430 -0
- package/fesm2022/raintonic-formaui-components-tree.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-core.mjs +62 -0
- package/fesm2022/raintonic-formaui-core.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs +798 -0
- package/fesm2022/raintonic-formaui-services-dialog.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-notification.mjs +391 -0
- package/fesm2022/raintonic-formaui-services-notification.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-services-theme.mjs +248 -0
- package/fesm2022/raintonic-formaui-services-theme.mjs.map +1 -0
- package/fesm2022/raintonic-formaui-test-utils.mjs +66 -0
- package/fesm2022/raintonic-formaui-test-utils.mjs.map +1 -0
- package/fesm2022/raintonic-formaui.mjs +15 -0
- package/fesm2022/raintonic-formaui.mjs.map +1 -0
- package/llms-full.txt +1627 -0
- package/llms.txt +60 -0
- package/package.json +251 -0
- package/styles/_fonts-entry.scss +3 -0
- package/styles/fonts/dm-mono-400-latin.woff2 +0 -0
- package/styles/fonts/inter-tight-latin-italic.woff2 +0 -0
- package/styles/fonts/inter-tight-latin.woff2 +0 -0
- package/styles/index.scss +127 -0
- package/styles/partials/_constants.scss +29 -0
- package/styles/partials/_fonts.scss +36 -0
- package/styles/partials/_grid.scss +171 -0
- package/styles/partials/_mixins.scss +145 -0
- package/styles/partials/_motion.scss +252 -0
- package/styles/partials/_theme.scss +275 -0
- package/styles/partials/_typography.scss +112 -0
- package/styles/partials/_utilities.scss +480 -0
- package/styles/partials/themes/_dark.scss +254 -0
- package/styles/partials/themes/_light.scss +254 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts +196 -0
- package/types/raintonic-formaui-cdk-drag-drop.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-form-field.d.ts +62 -0
- package/types/raintonic-formaui-cdk-form-field.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts +843 -0
- package/types/raintonic-formaui-cdk-overlay.d.ts.map +1 -0
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts +112 -0
- package/types/raintonic-formaui-cdk-virtual-scroll.d.ts.map +1 -0
- package/types/raintonic-formaui-components-accordion.d.ts +124 -0
- package/types/raintonic-formaui-components-accordion.d.ts.map +1 -0
- package/types/raintonic-formaui-components-alert.d.ts +143 -0
- package/types/raintonic-formaui-components-alert.d.ts.map +1 -0
- package/types/raintonic-formaui-components-autocomplete.d.ts +193 -0
- package/types/raintonic-formaui-components-autocomplete.d.ts.map +1 -0
- package/types/raintonic-formaui-components-avatar.d.ts +52 -0
- package/types/raintonic-formaui-components-avatar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-badge.d.ts +47 -0
- package/types/raintonic-formaui-components-badge.d.ts.map +1 -0
- package/types/raintonic-formaui-components-big-menu.d.ts +62 -0
- package/types/raintonic-formaui-components-big-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-breadcrumb.d.ts +26 -0
- package/types/raintonic-formaui-components-breadcrumb.d.ts.map +1 -0
- package/types/raintonic-formaui-components-button-group.d.ts +61 -0
- package/types/raintonic-formaui-components-button-group.d.ts.map +1 -0
- package/types/raintonic-formaui-components-button.d.ts +116 -0
- package/types/raintonic-formaui-components-button.d.ts.map +1 -0
- package/types/raintonic-formaui-components-card.d.ts +191 -0
- package/types/raintonic-formaui-components-card.d.ts.map +1 -0
- package/types/raintonic-formaui-components-checkbox.d.ts +132 -0
- package/types/raintonic-formaui-components-checkbox.d.ts.map +1 -0
- package/types/raintonic-formaui-components-data-table.d.ts +368 -0
- package/types/raintonic-formaui-components-data-table.d.ts.map +1 -0
- package/types/raintonic-formaui-components-date-picker.d.ts +341 -0
- package/types/raintonic-formaui-components-date-picker.d.ts.map +1 -0
- package/types/raintonic-formaui-components-divider.d.ts +21 -0
- package/types/raintonic-formaui-components-divider.d.ts.map +1 -0
- package/types/raintonic-formaui-components-drawer.d.ts +48 -0
- package/types/raintonic-formaui-components-drawer.d.ts.map +1 -0
- package/types/raintonic-formaui-components-dynamic-form.d.ts +412 -0
- package/types/raintonic-formaui-components-dynamic-form.d.ts.map +1 -0
- package/types/raintonic-formaui-components-empty-state.d.ts +14 -0
- package/types/raintonic-formaui-components-empty-state.d.ts.map +1 -0
- package/types/raintonic-formaui-components-file-upload.d.ts +77 -0
- package/types/raintonic-formaui-components-file-upload.d.ts.map +1 -0
- package/types/raintonic-formaui-components-form-field.d.ts +271 -0
- package/types/raintonic-formaui-components-form-field.d.ts.map +1 -0
- package/types/raintonic-formaui-components-icon.d.ts +61 -0
- package/types/raintonic-formaui-components-icon.d.ts.map +1 -0
- package/types/raintonic-formaui-components-input.d.ts +149 -0
- package/types/raintonic-formaui-components-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-list.d.ts +48 -0
- package/types/raintonic-formaui-components-list.d.ts.map +1 -0
- package/types/raintonic-formaui-components-menu.d.ts +403 -0
- package/types/raintonic-formaui-components-menu.d.ts.map +1 -0
- package/types/raintonic-formaui-components-number-input.d.ts +127 -0
- package/types/raintonic-formaui-components-number-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-paginator.d.ts +37 -0
- package/types/raintonic-formaui-components-paginator.d.ts.map +1 -0
- package/types/raintonic-formaui-components-password-input.d.ts +111 -0
- package/types/raintonic-formaui-components-password-input.d.ts.map +1 -0
- package/types/raintonic-formaui-components-popover.d.ts +131 -0
- package/types/raintonic-formaui-components-popover.d.ts.map +1 -0
- package/types/raintonic-formaui-components-progressbar.d.ts +111 -0
- package/types/raintonic-formaui-components-progressbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-radio.d.ts +95 -0
- package/types/raintonic-formaui-components-radio.d.ts.map +1 -0
- package/types/raintonic-formaui-components-select.d.ts +307 -0
- package/types/raintonic-formaui-components-select.d.ts.map +1 -0
- package/types/raintonic-formaui-components-side-panel.d.ts +51 -0
- package/types/raintonic-formaui-components-side-panel.d.ts.map +1 -0
- package/types/raintonic-formaui-components-sidebar.d.ts +174 -0
- package/types/raintonic-formaui-components-sidebar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-skeleton.d.ts +20 -0
- package/types/raintonic-formaui-components-skeleton.d.ts.map +1 -0
- package/types/raintonic-formaui-components-slider.d.ts +108 -0
- package/types/raintonic-formaui-components-slider.d.ts.map +1 -0
- package/types/raintonic-formaui-components-spinner.d.ts +42 -0
- package/types/raintonic-formaui-components-spinner.d.ts.map +1 -0
- package/types/raintonic-formaui-components-stepper.d.ts +126 -0
- package/types/raintonic-formaui-components-stepper.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tab.d.ts +96 -0
- package/types/raintonic-formaui-components-tab.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tag.d.ts +34 -0
- package/types/raintonic-formaui-components-tag.d.ts.map +1 -0
- package/types/raintonic-formaui-components-time-picker.d.ts +172 -0
- package/types/raintonic-formaui-components-time-picker.d.ts.map +1 -0
- package/types/raintonic-formaui-components-toggle.d.ts +70 -0
- package/types/raintonic-formaui-components-toggle.d.ts.map +1 -0
- package/types/raintonic-formaui-components-toolbar.d.ts +128 -0
- package/types/raintonic-formaui-components-toolbar.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tooltip.d.ts +268 -0
- package/types/raintonic-formaui-components-tooltip.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-select.d.ts +80 -0
- package/types/raintonic-formaui-components-tree-select.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree-table.d.ts +90 -0
- package/types/raintonic-formaui-components-tree-table.d.ts.map +1 -0
- package/types/raintonic-formaui-components-tree.d.ts +104 -0
- package/types/raintonic-formaui-components-tree.d.ts.map +1 -0
- package/types/raintonic-formaui-core.d.ts +115 -0
- package/types/raintonic-formaui-core.d.ts.map +1 -0
- package/types/raintonic-formaui-services-dialog.d.ts +451 -0
- package/types/raintonic-formaui-services-dialog.d.ts.map +1 -0
- package/types/raintonic-formaui-services-notification.d.ts +221 -0
- package/types/raintonic-formaui-services-notification.d.ts.map +1 -0
- package/types/raintonic-formaui-services-theme.d.ts +126 -0
- package/types/raintonic-formaui-services-theme.d.ts.map +1 -0
- package/types/raintonic-formaui-test-utils.d.ts +24 -0
- package/types/raintonic-formaui-test-utils.d.ts.map +1 -0
- package/types/raintonic-formaui.d.ts +4 -0
- package/types/raintonic-formaui.d.ts.map +1 -0
|
@@ -0,0 +1,1011 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { InjectionToken, input, output, signal, inject, ElementRef, HostListener, ViewEncapsulation, ChangeDetectionStrategy, Component, computed, contentChildren, NgZone, effect, ViewChild } from '@angular/core';
|
|
3
|
+
import { Subject, Subscription, fromEvent } from 'rxjs';
|
|
4
|
+
import { NgForm, FormGroupDirective, ReactiveFormsModule, NG_VALUE_ACCESSOR } from '@angular/forms';
|
|
5
|
+
import { DOCUMENT } from '@angular/common';
|
|
6
|
+
import { filter } from 'rxjs/operators';
|
|
7
|
+
import { injectNgControl, updateErrorState, syncRequiredState, syncNgControlDisabled } from '@raintonic/formaui/cdk/form-field';
|
|
8
|
+
import { DefaultErrorStateMatcher, FUI_FORM_FIELD_CONTROL } from '@raintonic/formaui/core';
|
|
9
|
+
import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Injection token used to provide the parent select to options.
|
|
13
|
+
*/
|
|
14
|
+
const FUI_SELECT = new InjectionToken('FUI_SELECT');
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* # FuiOption Component
|
|
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
|
|
29
|
+
*
|
|
30
|
+
* ## Usage
|
|
31
|
+
*
|
|
32
|
+
* ### Basic Option
|
|
33
|
+
* ```html
|
|
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
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* ### Option with Custom Content
|
|
42
|
+
* ```html
|
|
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
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
class FuiOptionComponent {
|
|
56
|
+
static nextId = 0;
|
|
57
|
+
/**
|
|
58
|
+
* The value of the option
|
|
59
|
+
*/
|
|
60
|
+
value = input(undefined, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
61
|
+
/**
|
|
62
|
+
* Whether the option is disabled
|
|
63
|
+
* @default false
|
|
64
|
+
*/
|
|
65
|
+
disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
66
|
+
/**
|
|
67
|
+
* Event emitted when the option is selected
|
|
68
|
+
*/
|
|
69
|
+
selectionChange = output();
|
|
70
|
+
// Internal state
|
|
71
|
+
_selected = signal(false, ...(ngDevMode ? [{ debugName: "_selected" }] : /* istanbul ignore next */ []));
|
|
72
|
+
_active = signal(false, ...(ngDevMode ? [{ debugName: "_active" }] : /* istanbul ignore next */ []));
|
|
73
|
+
stateChanges = new Subject();
|
|
74
|
+
// Element reference
|
|
75
|
+
_element = inject(ElementRef);
|
|
76
|
+
// Parent select (optional - may not exist if used standalone)
|
|
77
|
+
_parentSelect = inject(FUI_SELECT, { optional: true });
|
|
78
|
+
// Unique ID
|
|
79
|
+
id = `fui-option-${FuiOptionComponent.nextId++}`;
|
|
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
|
+
}
|
|
86
|
+
ngOnDestroy() {
|
|
87
|
+
this.stateChanges.complete();
|
|
88
|
+
}
|
|
89
|
+
// View value (text content)
|
|
90
|
+
get viewValue() {
|
|
91
|
+
return (this._element.nativeElement.textContent || '').trim();
|
|
92
|
+
}
|
|
93
|
+
_handleClick(event) {
|
|
94
|
+
if (!this.disabled()) {
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
event.stopPropagation();
|
|
97
|
+
// Notify parent select
|
|
98
|
+
if (this._parentSelect) {
|
|
99
|
+
this._parentSelect._onOptionSelected(this);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
// Standalone usage - emit event
|
|
103
|
+
this._emitSelectionChangeEvent();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
_handleMouseEnter() {
|
|
108
|
+
if (!this.disabled()) {
|
|
109
|
+
this._active.set(true);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
_handleMouseLeave() {
|
|
113
|
+
this._active.set(false);
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Selects the option
|
|
117
|
+
*/
|
|
118
|
+
select() {
|
|
119
|
+
if (!this._selected()) {
|
|
120
|
+
this._selected.set(true);
|
|
121
|
+
this._updateCheckmarkVisibility();
|
|
122
|
+
this.stateChanges.next();
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Deselects the option
|
|
127
|
+
*/
|
|
128
|
+
deselect() {
|
|
129
|
+
if (this._selected()) {
|
|
130
|
+
this._selected.set(false);
|
|
131
|
+
this._updateCheckmarkVisibility();
|
|
132
|
+
this.stateChanges.next();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Sets the option as active (keyboard navigation)
|
|
137
|
+
*/
|
|
138
|
+
setActive() {
|
|
139
|
+
if (!this._active()) {
|
|
140
|
+
this._active.set(true);
|
|
141
|
+
this.stateChanges.next();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Sets the option as inactive
|
|
146
|
+
*/
|
|
147
|
+
setInactive() {
|
|
148
|
+
if (this._active()) {
|
|
149
|
+
this._active.set(false);
|
|
150
|
+
this.stateChanges.next();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Sets focus onto this option
|
|
155
|
+
*/
|
|
156
|
+
focus() {
|
|
157
|
+
const element = this._element.nativeElement;
|
|
158
|
+
if (typeof element.focus === 'function') {
|
|
159
|
+
element.focus();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Gets the label to be used when displaying the option
|
|
164
|
+
*/
|
|
165
|
+
getLabel() {
|
|
166
|
+
return this.viewValue;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Gets the host element
|
|
170
|
+
*/
|
|
171
|
+
_getHostElement() {
|
|
172
|
+
return this._element.nativeElement;
|
|
173
|
+
}
|
|
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
|
+
/** Emits the selection change event */
|
|
180
|
+
_emitSelectionChangeEvent() {
|
|
181
|
+
this.selectionChange.emit({ source: this, value: this.value() });
|
|
182
|
+
}
|
|
183
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
184
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiOptionComponent, isStandalone: true, selector: "fui-option", 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: { attributes: { "role": "option" }, listeners: { "click": "_handleClick($event)", "mouseenter": "_handleMouseEnter()", "mouseleave": "_handleMouseLeave()" }, 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" }, ngImport: i0, template: `
|
|
185
|
+
@if (_showCheckmark()) {
|
|
186
|
+
<span class="fui-option__checkmark">
|
|
187
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
188
|
+
<path d="M6.5 11.5L3 8l1-1 2.5 2.5L12 4l1 1z" />
|
|
189
|
+
</svg>
|
|
190
|
+
</span>
|
|
191
|
+
}
|
|
192
|
+
<span class="fui-option__content">
|
|
193
|
+
<ng-content></ng-content>
|
|
194
|
+
</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 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)}.fui-option{position:relative;display:flex;align-items:center;min-height:2.5rem;margin:0 var(--fui-spacing-02);padding:0 1rem;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-primary);background-color:transparent;transition:background-color,color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-surface-02)}.fui-option--selected{color:var(--fui-primary);background-color:var(--fui-primary-10)}.fui-option--selected:hover:not(.fui-option--disabled){background-color:var(--fui-primary-20)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus,.fui-option--active{background-color:var(--fui-surface-01)}.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);transition:transform,opacity var(--fui-duration-fast-02) var(--fui-ease-expressive) 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-font-size-01);font-weight:600;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}.fui-theme-dark .fui-option--selected{background-color:rgba(var(--fui-primary-rgb),.15)}.fui-theme-dark .fui-option--selected:hover:not(.fui-option--disabled){background-color:rgba(var(--fui-primary-rgb),.25)}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-primary);outline-offset:-2px}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
196
|
+
}
|
|
197
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiOptionComponent, decorators: [{
|
|
198
|
+
type: Component,
|
|
199
|
+
args: [{ selector: 'fui-option', standalone: true, imports: [], template: `
|
|
200
|
+
@if (_showCheckmark()) {
|
|
201
|
+
<span class="fui-option__checkmark">
|
|
202
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
|
|
203
|
+
<path d="M6.5 11.5L3 8l1-1 2.5 2.5L12 4l1 1z" />
|
|
204
|
+
</svg>
|
|
205
|
+
</span>
|
|
206
|
+
}
|
|
207
|
+
<span class="fui-option__content">
|
|
208
|
+
<ng-content></ng-content>
|
|
209
|
+
</span>
|
|
210
|
+
`, changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
211
|
+
class: 'fui-option',
|
|
212
|
+
'[class.fui-option--selected]': '_selected()',
|
|
213
|
+
'[class.fui-option--disabled]': 'disabled()',
|
|
214
|
+
'[class.fui-option--active]': '_active()',
|
|
215
|
+
role: 'option',
|
|
216
|
+
'[attr.id]': 'id',
|
|
217
|
+
'[attr.aria-selected]': '_selected()',
|
|
218
|
+
'[attr.aria-disabled]': 'disabled()',
|
|
219
|
+
'[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 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)}.fui-option{position:relative;display:flex;align-items:center;min-height:2.5rem;margin:0 var(--fui-spacing-02);padding:0 1rem;cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;border-radius:var(--fui-border-radius-sm);color:var(--fui-text-primary);background-color:transparent;transition:background-color,color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-option:hover:not(.fui-option--disabled){background-color:var(--fui-surface-02)}.fui-option--selected{color:var(--fui-primary);background-color:var(--fui-primary-10)}.fui-option--selected:hover:not(.fui-option--disabled){background-color:var(--fui-primary-20)}.fui-option--disabled{opacity:.5;cursor:not-allowed;color:var(--fui-text-secondary)}.fui-option:focus-visible,.fui-option:focus,.fui-option--active{background-color:var(--fui-surface-01)}.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);transition:transform,opacity var(--fui-duration-fast-02) var(--fui-ease-expressive) 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-font-size-01);font-weight:600;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}.fui-theme-dark .fui-option--selected{background-color:rgba(var(--fui-primary-rgb),.15)}.fui-theme-dark .fui-option--selected:hover:not(.fui-option--disabled){background-color:rgba(var(--fui-primary-rgb),.25)}@media(prefers-contrast:high){.fui-option--selected{outline:2px solid var(--fui-primary);outline-offset:-2px}}\n"] }]
|
|
221
|
+
}], 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: [{
|
|
222
|
+
type: HostListener,
|
|
223
|
+
args: ['click', ['$event']]
|
|
224
|
+
}], _handleMouseEnter: [{
|
|
225
|
+
type: HostListener,
|
|
226
|
+
args: ['mouseenter']
|
|
227
|
+
}], _handleMouseLeave: [{
|
|
228
|
+
type: HostListener,
|
|
229
|
+
args: ['mouseleave']
|
|
230
|
+
}] } });
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* # fui-select Component
|
|
234
|
+
*
|
|
235
|
+
* A select component designed to work seamlessly with fui-form-field.
|
|
236
|
+
* Similar to Angular Material's mat-select integration with mat-form-field.
|
|
237
|
+
* Provides full Reactive Forms support with validation and error handling.
|
|
238
|
+
*
|
|
239
|
+
* ## Features
|
|
240
|
+
* - Works inside fui-form-field like mat-select
|
|
241
|
+
* - Full Reactive Forms integration (ControlValueAccessor)
|
|
242
|
+
* - Multiple selection support
|
|
243
|
+
* - Options via projected content (fui-option)
|
|
244
|
+
* - Disabled and readonly states
|
|
245
|
+
* - Full accessibility support
|
|
246
|
+
* - Full keyboard navigation (Arrow keys, Enter, Space, Escape, Home, End)
|
|
247
|
+
* - Type-ahead search functionality
|
|
248
|
+
*
|
|
249
|
+
* ## Usage
|
|
250
|
+
*
|
|
251
|
+
* ### Basic Select with Form Field
|
|
252
|
+
* ```html
|
|
253
|
+
* <fui-form-field>
|
|
254
|
+
* <label>Country</label>
|
|
255
|
+
* <fui-select placeholder="Select a country">
|
|
256
|
+
* <fui-option value="us">United States</fui-option>
|
|
257
|
+
* <fui-option value="ca">Canada</fui-option>
|
|
258
|
+
* <fui-option value="mx">Mexico</fui-option>
|
|
259
|
+
* </fui-select>
|
|
260
|
+
* </fui-form-field>
|
|
261
|
+
* ```
|
|
262
|
+
*
|
|
263
|
+
* ### With Reactive Forms and Validation
|
|
264
|
+
* ```html
|
|
265
|
+
* <form [formGroup]="form">
|
|
266
|
+
* <fui-form-field>
|
|
267
|
+
* <label>Country</label>
|
|
268
|
+
* <fui-select formControlName="country" placeholder="Select a country">
|
|
269
|
+
* <fui-option value="us">United States</fui-option>
|
|
270
|
+
* <fui-option value="ca">Canada</fui-option>
|
|
271
|
+
* </fui-select>
|
|
272
|
+
* <fui-error *ngIf="form.get('country')?.hasError('required')">
|
|
273
|
+
* Country is required
|
|
274
|
+
* </fui-error>
|
|
275
|
+
* </fui-form-field>
|
|
276
|
+
* </form>
|
|
277
|
+
* ```
|
|
278
|
+
*
|
|
279
|
+
* ### Multiple Selection
|
|
280
|
+
* ```html
|
|
281
|
+
* <fui-form-field>
|
|
282
|
+
* <label>Skills</label>
|
|
283
|
+
* <fui-select formControlName="skills" [multiple]="true">
|
|
284
|
+
* <fui-option value="js">JavaScript</fui-option>
|
|
285
|
+
* <fui-option value="ts">TypeScript</fui-option>
|
|
286
|
+
* <fui-option value="py">Python</fui-option>
|
|
287
|
+
* </fui-select>
|
|
288
|
+
* </fui-form-field>
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
291
|
+
class FuiSelectComponent {
|
|
292
|
+
// Static properties
|
|
293
|
+
static nextId = 0;
|
|
294
|
+
controlType = 'fui-select';
|
|
295
|
+
// Inputs using new signal-based API
|
|
296
|
+
placeholderInput = input('', { ...(ngDevMode ? { debugName: "placeholderInput" } : /* istanbul ignore next */ {}), alias: 'placeholder' });
|
|
297
|
+
disabledInput = input(false, { ...(ngDevMode ? { debugName: "disabledInput" } : /* istanbul ignore next */ {}), alias: 'disabled' });
|
|
298
|
+
readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
|
|
299
|
+
multiple = input(false, ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
|
|
300
|
+
errorStateMatcher = input(null, ...(ngDevMode ? [{ debugName: "errorStateMatcher" }] : /* istanbul ignore next */ []));
|
|
301
|
+
/**
|
|
302
|
+
* Whether to compare option values using object identity or deep equality
|
|
303
|
+
*/
|
|
304
|
+
compareWith = input((o1, o2) => o1 === o2, ...(ngDevMode ? [{ debugName: "compareWith" }] : /* istanbul ignore next */ []));
|
|
305
|
+
// Outputs
|
|
306
|
+
valueChange = output();
|
|
307
|
+
selectionChange = output();
|
|
308
|
+
openedChange = output();
|
|
309
|
+
// Internal state signals
|
|
310
|
+
_value = signal(null, ...(ngDevMode ? [{ debugName: "_value" }] : /* istanbul ignore next */ []));
|
|
311
|
+
_focused = signal(false, ...(ngDevMode ? [{ debugName: "_focused" }] : /* istanbul ignore next */ []));
|
|
312
|
+
_disabled = signal(false, ...(ngDevMode ? [{ debugName: "_disabled" }] : /* istanbul ignore next */ []));
|
|
313
|
+
_readOnly = signal(false, ...(ngDevMode ? [{ debugName: "_readOnly" }] : /* istanbul ignore next */ []));
|
|
314
|
+
// FuiFormFieldControl implementation
|
|
315
|
+
stateChanges = new Subject();
|
|
316
|
+
_uid = `fui-select-${FuiSelectComponent.nextId++}`;
|
|
317
|
+
_ariaDescribedby = null;
|
|
318
|
+
// Error state
|
|
319
|
+
_errorState = signal(false, ...(ngDevMode ? [{ debugName: "_errorState" }] : /* istanbul ignore next */ []));
|
|
320
|
+
errorState = this._errorState;
|
|
321
|
+
// Form control references
|
|
322
|
+
_parentForm = inject(NgForm, { optional: true });
|
|
323
|
+
_parentFormGroup = inject(FormGroupDirective, { optional: true });
|
|
324
|
+
_defaultErrorStateMatcher = inject(DefaultErrorStateMatcher);
|
|
325
|
+
_ngControlRef = injectNgControl();
|
|
326
|
+
get ngControl() {
|
|
327
|
+
return this._ngControlRef.ngControl;
|
|
328
|
+
}
|
|
329
|
+
// Interface implementation
|
|
330
|
+
placeholder = computed(() => this.placeholderInput(), ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
|
|
331
|
+
_required = signal(false, ...(ngDevMode ? [{ debugName: "_required" }] : /* istanbul ignore next */ []));
|
|
332
|
+
required = this._required;
|
|
333
|
+
value = this._value;
|
|
334
|
+
focused = this._focused;
|
|
335
|
+
_ngControlDisabled = signal(false, ...(ngDevMode ? [{ debugName: "_ngControlDisabled" }] : /* istanbul ignore next */ []));
|
|
336
|
+
disabled = computed(() => this._disabled() || this.disabledInput() || this._ngControlDisabled(), ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
|
|
337
|
+
empty = computed(() => {
|
|
338
|
+
const val = this._value();
|
|
339
|
+
return val === null || val === undefined || (this.isArray(val) && val.length === 0);
|
|
340
|
+
}, ...(ngDevMode ? [{ debugName: "empty" }] : /* istanbul ignore next */ []));
|
|
341
|
+
id = this._uid;
|
|
342
|
+
// ViewChild for trigger and panel
|
|
343
|
+
trigger;
|
|
344
|
+
panel;
|
|
345
|
+
// ContentChildren for options
|
|
346
|
+
options = contentChildren(FuiOptionComponent, { ...(ngDevMode ? { debugName: "options" } : /* istanbul ignore next */ {}), descendants: true });
|
|
347
|
+
// Panel open/close state
|
|
348
|
+
panelOpen = signal(false, ...(ngDevMode ? [{ debugName: "panelOpen" }] : /* istanbul ignore next */ []));
|
|
349
|
+
// Active option index for keyboard navigation
|
|
350
|
+
_activeOptionIndex = signal(-1, ...(ngDevMode ? [{ debugName: "_activeOptionIndex" }] : /* istanbul ignore next */ []));
|
|
351
|
+
activeOptionIndex = this._activeOptionIndex.asReadonly();
|
|
352
|
+
// Live announcement for screen readers
|
|
353
|
+
_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
|
+
// Type-ahead search
|
|
364
|
+
_typeaheadBuffer = '';
|
|
365
|
+
_typeaheadTimeout = null;
|
|
366
|
+
TYPE_AHEAD_DEBOUNCE = 200;
|
|
367
|
+
// ControlValueAccessor callbacks
|
|
368
|
+
_onChange = () => {
|
|
369
|
+
// Intentionally empty: will be replaced by Angular forms
|
|
370
|
+
};
|
|
371
|
+
_onTouched = () => {
|
|
372
|
+
// Intentionally empty: will be replaced by Angular forms
|
|
373
|
+
};
|
|
374
|
+
// 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 */ []));
|
|
395
|
+
constructor() {
|
|
396
|
+
// Set valueAccessor after NgControl is resolved
|
|
397
|
+
void Promise.resolve().then(() => {
|
|
398
|
+
if (this._ngControlRef.ngControl) {
|
|
399
|
+
this._ngControlRef.ngControl.valueAccessor = this;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
// Effect to emit state changes
|
|
403
|
+
effect(() => {
|
|
404
|
+
// Track all reactive inputs and internal signals
|
|
405
|
+
this.placeholderInput();
|
|
406
|
+
this.readonly();
|
|
407
|
+
this.disabledInput();
|
|
408
|
+
this.multiple();
|
|
409
|
+
this.errorStateMatcher();
|
|
410
|
+
this._focused();
|
|
411
|
+
this._disabled();
|
|
412
|
+
this._value();
|
|
413
|
+
this._ngControlDisabled();
|
|
414
|
+
this._required();
|
|
415
|
+
this._errorState();
|
|
416
|
+
// Emit state change
|
|
417
|
+
this.stateChanges.next();
|
|
418
|
+
});
|
|
419
|
+
// Effect to update options selected state when value or options change
|
|
420
|
+
effect(() => {
|
|
421
|
+
const currentValue = this._value();
|
|
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
|
+
});
|
|
445
|
+
// Notify form-field that state may have changed (for display value updates)
|
|
446
|
+
this.stateChanges.next();
|
|
447
|
+
});
|
|
448
|
+
}
|
|
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
|
+
ngOnDestroy() {
|
|
457
|
+
this.stateChanges.complete();
|
|
458
|
+
this._outsideClickSub?.unsubscribe();
|
|
459
|
+
this._disposeOverlay();
|
|
460
|
+
if (this._typeaheadTimeout) {
|
|
461
|
+
clearTimeout(this._typeaheadTimeout);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
ngAfterContentInit() {
|
|
465
|
+
// Set up initial selection based on value
|
|
466
|
+
this._syncOptionsSelection();
|
|
467
|
+
}
|
|
468
|
+
// ControlValueAccessor implementation
|
|
469
|
+
writeValue(value) {
|
|
470
|
+
this._value.set(value ?? null);
|
|
471
|
+
//this._syncOptionsSelection();
|
|
472
|
+
this.stateChanges.next();
|
|
473
|
+
}
|
|
474
|
+
registerOnChange(fn) {
|
|
475
|
+
this._onChange = fn;
|
|
476
|
+
}
|
|
477
|
+
registerOnTouched(fn) {
|
|
478
|
+
this._onTouched = fn;
|
|
479
|
+
}
|
|
480
|
+
setDisabledState(isDisabled) {
|
|
481
|
+
this._disabled.set(isDisabled);
|
|
482
|
+
this.stateChanges.next();
|
|
483
|
+
}
|
|
484
|
+
// FuiFormFieldControl implementation
|
|
485
|
+
onContainerClick(_event) {
|
|
486
|
+
if (!this.disabled() && !this.readonly()) {
|
|
487
|
+
this.toggle();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
setDescribedByIds(ids) {
|
|
491
|
+
this._ariaDescribedby = ids.length ? ids.join(' ') : null;
|
|
492
|
+
}
|
|
493
|
+
setReadOnly(readOnly) {
|
|
494
|
+
this._readOnly.set(readOnly);
|
|
495
|
+
}
|
|
496
|
+
// Sync options selection state with current value
|
|
497
|
+
_syncOptionsSelection() {
|
|
498
|
+
const currentValue = this._value();
|
|
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
|
+
});
|
|
522
|
+
}
|
|
523
|
+
// Focus/blur event handlers from template
|
|
524
|
+
_onFocus() {
|
|
525
|
+
this._focused.set(true);
|
|
526
|
+
this.stateChanges.next();
|
|
527
|
+
}
|
|
528
|
+
_onBlur(event) {
|
|
529
|
+
if (this.panelOpen()) {
|
|
530
|
+
// Check if focus moved to the overlay panel (e.g. clicking an option).
|
|
531
|
+
// In that case, don't close — the user is interacting with the dropdown.
|
|
532
|
+
const relatedTarget = event?.relatedTarget;
|
|
533
|
+
const overlayElement = this._overlayRef?.overlayElement;
|
|
534
|
+
if (relatedTarget && overlayElement?.contains(relatedTarget)) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
// Focus left the select entirely (e.g. Tab) — close without restoring focus
|
|
538
|
+
this.close(false);
|
|
539
|
+
}
|
|
540
|
+
this._focused.set(false);
|
|
541
|
+
this._onTouched();
|
|
542
|
+
this.stateChanges.next();
|
|
543
|
+
}
|
|
544
|
+
// Public methods
|
|
545
|
+
focus() {
|
|
546
|
+
this.trigger?.nativeElement.focus();
|
|
547
|
+
}
|
|
548
|
+
blur() {
|
|
549
|
+
this.trigger?.nativeElement.blur();
|
|
550
|
+
}
|
|
551
|
+
// Toggle panel open/close
|
|
552
|
+
toggle() {
|
|
553
|
+
if (this.disabled())
|
|
554
|
+
return;
|
|
555
|
+
if (this.panelOpen()) {
|
|
556
|
+
this.close();
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
this.open();
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
// Open the dropdown panel
|
|
563
|
+
open() {
|
|
564
|
+
if (this.disabled() || this.readonly() || this.panelOpen())
|
|
565
|
+
return;
|
|
566
|
+
// Ensure DOM focus is on the trigger so blur/Tab work correctly
|
|
567
|
+
this.trigger?.nativeElement.focus();
|
|
568
|
+
requestAnimationFrame(() => {
|
|
569
|
+
this.panelOpen.set(true);
|
|
570
|
+
this._focused.set(true);
|
|
571
|
+
this.openedChange.emit(true);
|
|
572
|
+
// Set active option to currently selected or first option
|
|
573
|
+
this._setInitialActiveOption();
|
|
574
|
+
// Create overlay after the view updates
|
|
575
|
+
setTimeout(() => {
|
|
576
|
+
this._createOverlay();
|
|
577
|
+
this._scrollToActiveOption();
|
|
578
|
+
this._listenForOutsideClicks();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// Close the dropdown panel
|
|
583
|
+
close(restoreFocus = true) {
|
|
584
|
+
if (!this.panelOpen())
|
|
585
|
+
return;
|
|
586
|
+
this._outsideClickSub?.unsubscribe();
|
|
587
|
+
this.panelOpen.set(false);
|
|
588
|
+
this._focused.set(false);
|
|
589
|
+
this._activeOptionIndex.set(-1);
|
|
590
|
+
this._disposeOverlay();
|
|
591
|
+
this.openedChange.emit(false);
|
|
592
|
+
this._onTouched();
|
|
593
|
+
// Return focus to trigger only when explicitly requested (e.g. Escape, backdrop click).
|
|
594
|
+
// When closing via Tab, let the browser move focus naturally.
|
|
595
|
+
// Focus synchronously to avoid race conditions with Tab key presses.
|
|
596
|
+
if (restoreFocus) {
|
|
597
|
+
this.trigger?.nativeElement.focus();
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
// Handle option selection (called by FuiOptionComponent)
|
|
601
|
+
_onOptionSelected(option) {
|
|
602
|
+
const newValue = option.value();
|
|
603
|
+
if (this.multiple()) {
|
|
604
|
+
const raw = this._value();
|
|
605
|
+
const currentValue = this.isArray(raw) ? [...raw] : [];
|
|
606
|
+
const compareFn = this.compareWith();
|
|
607
|
+
const index = currentValue.findIndex((v) => compareFn(v, newValue));
|
|
608
|
+
if (index > -1) {
|
|
609
|
+
currentValue.splice(index, 1);
|
|
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}`);
|
|
624
|
+
}
|
|
625
|
+
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
|
+
this._announce(`${option.getLabel()} selected`);
|
|
640
|
+
this.close();
|
|
641
|
+
}
|
|
642
|
+
this.stateChanges.next();
|
|
643
|
+
}
|
|
644
|
+
// Get display value for trigger
|
|
645
|
+
_getDisplayValue() {
|
|
646
|
+
return this.displayValue();
|
|
647
|
+
}
|
|
648
|
+
// Handle keyboard navigation
|
|
649
|
+
_handleKeydown(event) {
|
|
650
|
+
if (this.disabled())
|
|
651
|
+
return;
|
|
652
|
+
const isOpen = this.panelOpen();
|
|
653
|
+
if (!isOpen) {
|
|
654
|
+
this._handleClosedKeydown(event);
|
|
655
|
+
}
|
|
656
|
+
else {
|
|
657
|
+
this._handleOpenKeydown(event);
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
// Handle keydown when panel is closed
|
|
661
|
+
_handleClosedKeydown(event) {
|
|
662
|
+
const key = event.key;
|
|
663
|
+
switch (key) {
|
|
664
|
+
case 'Enter':
|
|
665
|
+
case ' ':
|
|
666
|
+
case 'ArrowDown':
|
|
667
|
+
case 'ArrowUp':
|
|
668
|
+
event.preventDefault();
|
|
669
|
+
this.open();
|
|
670
|
+
break;
|
|
671
|
+
case 'Home':
|
|
672
|
+
event.preventDefault();
|
|
673
|
+
this._selectFirstOption();
|
|
674
|
+
break;
|
|
675
|
+
case 'End':
|
|
676
|
+
event.preventDefault();
|
|
677
|
+
this._selectLastOption();
|
|
678
|
+
break;
|
|
679
|
+
default:
|
|
680
|
+
// Type-ahead when closed
|
|
681
|
+
if (key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
682
|
+
this._handleTypeahead(key);
|
|
683
|
+
}
|
|
684
|
+
break;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Handle keydown when panel is open
|
|
688
|
+
_handleOpenKeydown(event) {
|
|
689
|
+
const key = event.key;
|
|
690
|
+
const opts = this._getEnabledOptions();
|
|
691
|
+
const activeIndex = this._activeOptionIndex();
|
|
692
|
+
switch (key) {
|
|
693
|
+
case 'ArrowDown':
|
|
694
|
+
event.preventDefault();
|
|
695
|
+
this._setNextActiveOption(1);
|
|
696
|
+
break;
|
|
697
|
+
case 'ArrowUp':
|
|
698
|
+
event.preventDefault();
|
|
699
|
+
this._setNextActiveOption(-1);
|
|
700
|
+
break;
|
|
701
|
+
case 'Home':
|
|
702
|
+
event.preventDefault();
|
|
703
|
+
this._setActiveOptionIndex(0);
|
|
704
|
+
break;
|
|
705
|
+
case 'End':
|
|
706
|
+
event.preventDefault();
|
|
707
|
+
this._setActiveOptionIndex(opts.length - 1);
|
|
708
|
+
break;
|
|
709
|
+
case 'Enter':
|
|
710
|
+
case ' ':
|
|
711
|
+
event.preventDefault();
|
|
712
|
+
if (activeIndex >= 0 && activeIndex < opts.length) {
|
|
713
|
+
this._onOptionSelected(opts[activeIndex]);
|
|
714
|
+
}
|
|
715
|
+
break;
|
|
716
|
+
case 'Escape':
|
|
717
|
+
event.preventDefault();
|
|
718
|
+
this.close();
|
|
719
|
+
break;
|
|
720
|
+
case 'Tab':
|
|
721
|
+
// Don't preventDefault — let browser handle Tab naturally.
|
|
722
|
+
// _onBlur will close the panel when focus leaves the trigger.
|
|
723
|
+
break;
|
|
724
|
+
default:
|
|
725
|
+
// Type-ahead when open
|
|
726
|
+
if (key.length === 1 && !event.ctrlKey && !event.metaKey) {
|
|
727
|
+
event.preventDefault();
|
|
728
|
+
this._handleTypeahead(key);
|
|
729
|
+
}
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
// Set the next active option based on delta
|
|
734
|
+
_setNextActiveOption(delta) {
|
|
735
|
+
const opts = this._getEnabledOptions();
|
|
736
|
+
if (opts.length === 0)
|
|
737
|
+
return;
|
|
738
|
+
const currentIndex = this._activeOptionIndex();
|
|
739
|
+
let newIndex = currentIndex + delta;
|
|
740
|
+
// Wrap around
|
|
741
|
+
if (newIndex < 0) {
|
|
742
|
+
newIndex = opts.length - 1;
|
|
743
|
+
}
|
|
744
|
+
else if (newIndex >= opts.length) {
|
|
745
|
+
newIndex = 0;
|
|
746
|
+
}
|
|
747
|
+
this._setActiveOptionIndex(newIndex);
|
|
748
|
+
}
|
|
749
|
+
// Set the active option index
|
|
750
|
+
_setActiveOptionIndex(index) {
|
|
751
|
+
const opts = this._getEnabledOptions();
|
|
752
|
+
if (index < 0 || index >= opts.length)
|
|
753
|
+
return;
|
|
754
|
+
// Update active state on options
|
|
755
|
+
const allOpts = this.options();
|
|
756
|
+
allOpts.forEach((opt) => {
|
|
757
|
+
opt.setInactive();
|
|
758
|
+
});
|
|
759
|
+
const activeOption = opts[index];
|
|
760
|
+
activeOption.setActive();
|
|
761
|
+
this._activeOptionIndex.set(index);
|
|
762
|
+
this._scrollToActiveOption();
|
|
763
|
+
// Announce active option for screen readers (when panel is open and using keyboard)
|
|
764
|
+
if (this.panelOpen()) {
|
|
765
|
+
const label = activeOption.getLabel();
|
|
766
|
+
const selectedState = activeOption._selected() ? ', selected' : '';
|
|
767
|
+
this._announce(`${label}${selectedState}, ${index + 1} of ${opts.length}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
// Set initial active option when opening
|
|
771
|
+
_setInitialActiveOption() {
|
|
772
|
+
const opts = this._getEnabledOptions();
|
|
773
|
+
if (opts.length === 0) {
|
|
774
|
+
this._activeOptionIndex.set(-1);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const currentValue = this._value();
|
|
778
|
+
const compareFn = this.compareWith();
|
|
779
|
+
// Find the first selected option
|
|
780
|
+
let selectedIndex = -1;
|
|
781
|
+
if (!this.multiple() && currentValue !== null && currentValue !== undefined) {
|
|
782
|
+
selectedIndex = opts.findIndex((opt) => compareFn(opt.value(), currentValue));
|
|
783
|
+
}
|
|
784
|
+
else if (this.multiple() && this.isArray(currentValue) && currentValue.length > 0) {
|
|
785
|
+
selectedIndex = opts.findIndex((opt) => currentValue.some((v) => compareFn(v, opt.value())));
|
|
786
|
+
}
|
|
787
|
+
const initialIndex = selectedIndex >= 0 ? selectedIndex : 0;
|
|
788
|
+
this._setActiveOptionIndex(initialIndex);
|
|
789
|
+
}
|
|
790
|
+
// Scroll to active option
|
|
791
|
+
_scrollToActiveOption() {
|
|
792
|
+
const opts = this._getEnabledOptions();
|
|
793
|
+
const activeIndex = this._activeOptionIndex();
|
|
794
|
+
if (activeIndex < 0 || activeIndex >= opts.length)
|
|
795
|
+
return;
|
|
796
|
+
const activeOption = opts[activeIndex];
|
|
797
|
+
const element = activeOption._getHostElement();
|
|
798
|
+
const panel = this.panel?.nativeElement;
|
|
799
|
+
if (element && panel) {
|
|
800
|
+
const optionTop = element.offsetTop;
|
|
801
|
+
const optionBottom = optionTop + element.offsetHeight;
|
|
802
|
+
const panelTop = panel.scrollTop;
|
|
803
|
+
const panelBottom = panelTop + panel.clientHeight;
|
|
804
|
+
if (optionTop < panelTop) {
|
|
805
|
+
panel.scrollTop = optionTop;
|
|
806
|
+
}
|
|
807
|
+
else if (optionBottom > panelBottom) {
|
|
808
|
+
panel.scrollTop = optionBottom - panel.clientHeight;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
// Handle type-ahead search
|
|
813
|
+
_handleTypeahead(char) {
|
|
814
|
+
// Clear timeout and add to buffer
|
|
815
|
+
if (this._typeaheadTimeout) {
|
|
816
|
+
clearTimeout(this._typeaheadTimeout);
|
|
817
|
+
}
|
|
818
|
+
this._typeaheadBuffer += char.toLowerCase();
|
|
819
|
+
// Find matching option
|
|
820
|
+
const opts = this._getEnabledOptions();
|
|
821
|
+
const matchIndex = opts.findIndex((opt) => opt.getLabel().toLowerCase().startsWith(this._typeaheadBuffer));
|
|
822
|
+
if (matchIndex >= 0) {
|
|
823
|
+
if (this.panelOpen()) {
|
|
824
|
+
this._setActiveOptionIndex(matchIndex);
|
|
825
|
+
}
|
|
826
|
+
else {
|
|
827
|
+
// Select the option when closed
|
|
828
|
+
this._onOptionSelected(opts[matchIndex]);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
// Clear buffer after debounce
|
|
832
|
+
this._typeaheadTimeout = setTimeout(() => {
|
|
833
|
+
this._typeaheadBuffer = '';
|
|
834
|
+
}, this.TYPE_AHEAD_DEBOUNCE);
|
|
835
|
+
}
|
|
836
|
+
// Select first non-disabled option
|
|
837
|
+
_selectFirstOption() {
|
|
838
|
+
const opts = this._getEnabledOptions();
|
|
839
|
+
if (opts.length > 0) {
|
|
840
|
+
this._onOptionSelected(opts[0]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
// Select last non-disabled option
|
|
844
|
+
_selectLastOption() {
|
|
845
|
+
const opts = this._getEnabledOptions();
|
|
846
|
+
if (opts.length > 0) {
|
|
847
|
+
this._onOptionSelected(opts[opts.length - 1]);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
// Get all non-disabled options
|
|
851
|
+
_getEnabledOptions() {
|
|
852
|
+
return this.options().filter((opt) => !opt.disabled());
|
|
853
|
+
}
|
|
854
|
+
// Start listening for outside clicks when panel opens
|
|
855
|
+
_listenForOutsideClicks() {
|
|
856
|
+
this._outsideClickSub?.unsubscribe();
|
|
857
|
+
// Run outside Angular zone to avoid triggering change detection on every document click
|
|
858
|
+
this._ngZone.runOutsideAngular(() => {
|
|
859
|
+
// Use setTimeout to skip the current click event that opened the panel
|
|
860
|
+
setTimeout(() => {
|
|
861
|
+
this._outsideClickSub = fromEvent(this._document, 'click')
|
|
862
|
+
.pipe(filter(() => this.panelOpen()), filter((event) => {
|
|
863
|
+
const target = event.target;
|
|
864
|
+
const triggerElement = this.trigger?.nativeElement;
|
|
865
|
+
const panelElement = this.panel?.nativeElement;
|
|
866
|
+
const overlayElement = this._overlayRef?.overlayElement;
|
|
867
|
+
return (!triggerElement?.contains(target) &&
|
|
868
|
+
!panelElement?.contains(target) &&
|
|
869
|
+
!overlayElement?.contains(target));
|
|
870
|
+
}))
|
|
871
|
+
.subscribe(() => {
|
|
872
|
+
this._ngZone.run(() => {
|
|
873
|
+
this.close();
|
|
874
|
+
});
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
// Create overlay for panel
|
|
880
|
+
_createOverlay() {
|
|
881
|
+
if (this._overlayRef || !this.panel || !this.trigger)
|
|
882
|
+
return;
|
|
883
|
+
const triggerElement = this.trigger.nativeElement;
|
|
884
|
+
const triggerWidth = triggerElement.getBoundingClientRect().width;
|
|
885
|
+
const positions = [
|
|
886
|
+
// Primary: open below
|
|
887
|
+
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: 4 },
|
|
888
|
+
// Fallback: open above if no space below
|
|
889
|
+
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -4 },
|
|
890
|
+
];
|
|
891
|
+
const positionStrategy = this._overlayService
|
|
892
|
+
.position()
|
|
893
|
+
.connectedTo(triggerElement, positions)
|
|
894
|
+
.withPush(false)
|
|
895
|
+
.withFlexibleDimensions(true)
|
|
896
|
+
.withViewportMargin(8);
|
|
897
|
+
this._overlayRef = this._overlayService.create({
|
|
898
|
+
positionStrategy,
|
|
899
|
+
scrollStrategy: this._overlayService.scrollStrategies.reposition(),
|
|
900
|
+
hasBackdrop: true,
|
|
901
|
+
backdropClass: 'fui-select-backdrop',
|
|
902
|
+
backdropClickBehavior: 'close',
|
|
903
|
+
panelClass: ['fui-select-overlay-panel'],
|
|
904
|
+
width: triggerWidth,
|
|
905
|
+
});
|
|
906
|
+
// Track overlay subscriptions for proper cleanup
|
|
907
|
+
this._overlaySubscriptions.unsubscribe();
|
|
908
|
+
this._overlaySubscriptions = new Subscription();
|
|
909
|
+
this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
|
|
910
|
+
this.close();
|
|
911
|
+
}));
|
|
912
|
+
this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
|
|
913
|
+
if (event.key === 'Escape') {
|
|
914
|
+
this.close();
|
|
915
|
+
}
|
|
916
|
+
}));
|
|
917
|
+
// Attach panel to overlay
|
|
918
|
+
const panelElement = this.panel.nativeElement;
|
|
919
|
+
this._overlayRef.attach(panelElement);
|
|
920
|
+
}
|
|
921
|
+
// Dispose overlay
|
|
922
|
+
_disposeOverlay() {
|
|
923
|
+
this._overlaySubscriptions.unsubscribe();
|
|
924
|
+
if (this._overlayRef) {
|
|
925
|
+
this._overlayRef.dispose();
|
|
926
|
+
this._overlayRef = null;
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// Get the active option's id for aria-activedescendant
|
|
930
|
+
_getActiveDescendant() {
|
|
931
|
+
const opts = this._getEnabledOptions();
|
|
932
|
+
const activeIndex = this._activeOptionIndex();
|
|
933
|
+
if (activeIndex >= 0 && activeIndex < opts.length) {
|
|
934
|
+
return opts[activeIndex].id;
|
|
935
|
+
}
|
|
936
|
+
return null;
|
|
937
|
+
}
|
|
938
|
+
/**
|
|
939
|
+
* Announces a message to screen readers via the aria-live region.
|
|
940
|
+
* Clears the message after a brief delay to allow repeated announcements.
|
|
941
|
+
*/
|
|
942
|
+
_announce(message) {
|
|
943
|
+
// Clear first to ensure repeated identical messages are announced
|
|
944
|
+
this._liveAnnouncement.set('');
|
|
945
|
+
setTimeout(() => {
|
|
946
|
+
this._liveAnnouncement.set(message);
|
|
947
|
+
}, 50);
|
|
948
|
+
}
|
|
949
|
+
// Mat-select compatibility methods
|
|
950
|
+
get selected() {
|
|
951
|
+
return this.value;
|
|
952
|
+
}
|
|
953
|
+
// Helper method to check if value is array
|
|
954
|
+
isArray(value) {
|
|
955
|
+
return Array.isArray(value);
|
|
956
|
+
}
|
|
957
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
958
|
+
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 }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, errorStateMatcher: { classPropertyName: "errorStateMatcher", publicName: "errorStateMatcher", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "compareWith", 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()" }, classAttribute: "fui-select" }, providers: [
|
|
959
|
+
{
|
|
960
|
+
provide: NG_VALUE_ACCESSOR,
|
|
961
|
+
useExisting: FuiSelectComponent,
|
|
962
|
+
multi: true,
|
|
963
|
+
},
|
|
964
|
+
{
|
|
965
|
+
provide: FUI_FORM_FIELD_CONTROL,
|
|
966
|
+
useExisting: FuiSelectComponent,
|
|
967
|
+
},
|
|
968
|
+
{
|
|
969
|
+
provide: FUI_SELECT,
|
|
970
|
+
useExisting: FuiSelectComponent,
|
|
971
|
+
},
|
|
972
|
+
], 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 }], ngImport: i0, template: "<!-- Select Trigger -->\n<div\n #trigger\n [id]=\"id\"\n class=\"fui-select__trigger\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.role]=\"'combobox'\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-label]=\"empty() ? placeholder() : null\"\n aria-autocomplete=\"none\"\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\n (keydown)=\"_handleKeydown($event)\"\n (focus)=\"_onFocus()\"\n (blur)=\"_onBlur($event)\"\n>\n <span class=\"fui-select__value\">\n @if (empty()) {\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\n } @else {\n <span class=\"fui-select__value-text\">{{ _getDisplayValue() }}</span>\n }\n </span>\n</div>\n\n<!-- Live region for screen reader announcements -->\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\n\n<!-- Dropdown Panel -->\n@if (panelOpen()) {\n <div\n #panel\n [id]=\"id + '-panel'\"\n class=\"fui-select__panel\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.aria-label]=\"placeholder()\"\n >\n <ng-content></ng-content>\n </div>\n}\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)}.fui-select{display:inline-block;width:100%;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-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-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;margin-left:var(--fui-spacing-02);color:var(--fui-text-secondary);transition:transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-select__arrow svg{width:1rem;height:1rem}.fui-select--open .fui-select__arrow{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{position:fixed;opacity:0;pointer-events:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-border-radius-md);--fui-select-panel-shadow: var(--fui-shadow-03);--fui-select-panel-bg: var(--fui-surface-00);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:1px solid var(--fui-border-color);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-02) 0;transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-surface-01);border-radius:var(--fui-border-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-color);border-radius:var(--fui-border-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;z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{transition:none}}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
|
|
973
|
+
}
|
|
974
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiSelectComponent, decorators: [{
|
|
975
|
+
type: Component,
|
|
976
|
+
args: [{ selector: 'fui-select', standalone: true, imports: [ReactiveFormsModule], changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, host: {
|
|
977
|
+
class: 'fui-select',
|
|
978
|
+
'[attr.id]': 'id',
|
|
979
|
+
'[class.fui-select--open]': 'panelOpen()',
|
|
980
|
+
'[class.fui-select--disabled]': 'disabled()',
|
|
981
|
+
'[class.fui-select--multiple]': 'multiple()',
|
|
982
|
+
'[class.fui-select--readonly]': '_readOnly()',
|
|
983
|
+
}, providers: [
|
|
984
|
+
{
|
|
985
|
+
provide: NG_VALUE_ACCESSOR,
|
|
986
|
+
useExisting: FuiSelectComponent,
|
|
987
|
+
multi: true,
|
|
988
|
+
},
|
|
989
|
+
{
|
|
990
|
+
provide: FUI_FORM_FIELD_CONTROL,
|
|
991
|
+
useExisting: FuiSelectComponent,
|
|
992
|
+
},
|
|
993
|
+
{
|
|
994
|
+
provide: FUI_SELECT,
|
|
995
|
+
useExisting: FuiSelectComponent,
|
|
996
|
+
},
|
|
997
|
+
], template: "<!-- Select Trigger -->\n<div\n #trigger\n [id]=\"id\"\n class=\"fui-select__trigger\"\n [attr.tabindex]=\"disabled() ? -1 : 0\"\n [attr.role]=\"'combobox'\"\n [attr.aria-haspopup]=\"'listbox'\"\n [attr.aria-expanded]=\"panelOpen()\"\n [attr.aria-disabled]=\"disabled()\"\n [attr.aria-invalid]=\"errorState()\"\n [attr.aria-describedby]=\"_ariaDescribedby\"\n [attr.aria-required]=\"required()\"\n [attr.aria-label]=\"empty() ? placeholder() : null\"\n aria-autocomplete=\"none\"\n [attr.aria-activedescendant]=\"panelOpen() ? _getActiveDescendant() : null\"\n [attr.aria-controls]=\"panelOpen() ? id + '-panel' : null\"\n (keydown)=\"_handleKeydown($event)\"\n (focus)=\"_onFocus()\"\n (blur)=\"_onBlur($event)\"\n>\n <span class=\"fui-select__value\">\n @if (empty()) {\n <span class=\"fui-select__placeholder\">{{ placeholder() }}</span>\n } @else {\n <span class=\"fui-select__value-text\">{{ _getDisplayValue() }}</span>\n }\n </span>\n</div>\n\n<!-- Live region for screen reader announcements -->\n<span class=\"fui-sr-only\" aria-live=\"assertive\" aria-atomic=\"true\">{{ _liveAnnouncement() }}</span>\n\n<!-- Dropdown Panel -->\n@if (panelOpen()) {\n <div\n #panel\n [id]=\"id + '-panel'\"\n class=\"fui-select__panel\"\n role=\"listbox\"\n [attr.aria-multiselectable]=\"multiple()\"\n [attr.aria-label]=\"placeholder()\"\n >\n <ng-content></ng-content>\n </div>\n}\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)}.fui-select{display:inline-block;width:100%;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-family-sans);font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-02);letter-spacing:var(--fui-letter-spacing-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;margin-left:var(--fui-spacing-02);color:var(--fui-text-secondary);transition:transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-select__arrow svg{width:1rem;height:1rem}.fui-select--open .fui-select__arrow{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{position:fixed;opacity:0;pointer-events:none}.fui-select__panel{--fui-select-panel-max-height: 16rem;--fui-select-panel-border-radius: var(--fui-border-radius-md);--fui-select-panel-shadow: var(--fui-shadow-03);--fui-select-panel-bg: var(--fui-surface-00);width:100%;max-height:var(--fui-select-panel-max-height);overflow-y:auto;overflow-x:hidden;background:var(--fui-select-panel-bg);border:1px solid var(--fui-border-color);border-radius:var(--fui-select-panel-border-radius);box-shadow:var(--fui-select-panel-shadow);padding:var(--fui-spacing-02) 0;transition:opacity,transform var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms}.fui-select__panel::-webkit-scrollbar{width:8px}.fui-select__panel::-webkit-scrollbar-track{background:var(--fui-surface-01);border-radius:var(--fui-border-radius-sm)}.fui-select__panel::-webkit-scrollbar-thumb{background:var(--fui-border-color);border-radius:var(--fui-border-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;z-index:var(--fui-z-popover, 1060)}@media(prefers-contrast:high){.fui-select__panel{border-width:2px}}@media(prefers-reduced-motion:reduce){.fui-select__panel{transition:none}}\n"] }]
|
|
998
|
+
}], ctorParameters: () => [], propDecorators: { placeholderInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabledInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], errorStateMatcher: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorStateMatcher", required: false }] }], compareWith: [{ type: i0.Input, args: [{ isSignal: true, alias: "compareWith", required: false }] }], valueChange: [{ type: i0.Output, args: ["valueChange"] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], openedChange: [{ type: i0.Output, args: ["openedChange"] }], trigger: [{
|
|
999
|
+
type: ViewChild,
|
|
1000
|
+
args: ['trigger', { static: false }]
|
|
1001
|
+
}], panel: [{
|
|
1002
|
+
type: ViewChild,
|
|
1003
|
+
args: ['panel', { static: false }]
|
|
1004
|
+
}], options: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FuiOptionComponent), { ...{ descendants: true }, isSignal: true }] }] } });
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Generated bundle index. Do not edit.
|
|
1008
|
+
*/
|
|
1009
|
+
|
|
1010
|
+
export { FUI_SELECT, FuiOptionComponent, FuiSelectComponent };
|
|
1011
|
+
//# sourceMappingURL=raintonic-formaui-components-select.mjs.map
|