@raintonic/formaui 0.3.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +80 -35
- package/README.md +22 -26
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs +39 -41
- package/fesm2022/raintonic-formaui-cdk-drag-drop.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs +207 -3
- package/fesm2022/raintonic-formaui-cdk-form-field.mjs.map +1 -1
- package/fesm2022/raintonic-formaui-cdk-overlay.mjs +27 -2
- 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,896 +0,0 @@
|
|
|
1
|
-
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, booleanAttribute, output, signal, computed, inject, ElementRef, Renderer2, HostListener, Component, NgZone, contentChildren, effect, ViewChild, Directive } from '@angular/core';
|
|
3
|
-
import { DOCUMENT } from '@angular/common';
|
|
4
|
-
import { Subscription, fromEvent } from 'rxjs';
|
|
5
|
-
import { filter } from 'rxjs/operators';
|
|
6
|
-
import { FuiOverlayService } from '@raintonic/formaui/cdk/overlay';
|
|
7
|
-
|
|
8
|
-
const MENU_ITEM_VARIANTS = ['default', 'danger'];
|
|
9
|
-
/**
|
|
10
|
-
* # FuiMenuItem Component
|
|
11
|
-
*
|
|
12
|
-
* A menu item component designed to be used within fui-menu.
|
|
13
|
-
* Provides consistent styling and behavior for menu options.
|
|
14
|
-
*
|
|
15
|
-
* ## Features
|
|
16
|
-
* - Default and danger variants
|
|
17
|
-
* - Full accessibility support (ARIA attributes, keyboard navigation)
|
|
18
|
-
* - Icon support with proper spacing
|
|
19
|
-
* - Disabled state support
|
|
20
|
-
* - Hover and focus states
|
|
21
|
-
* - Keyboard activation (Enter and Space)
|
|
22
|
-
*
|
|
23
|
-
* ## Usage
|
|
24
|
-
*
|
|
25
|
-
* ### Basic Menu Item
|
|
26
|
-
* ```html
|
|
27
|
-
* <fui-menu-item>Profile</fui-menu-item>
|
|
28
|
-
* ```
|
|
29
|
-
*
|
|
30
|
-
* ### Menu Item with Icon
|
|
31
|
-
* ```html
|
|
32
|
-
* <fui-menu-item>
|
|
33
|
-
* <fui-icon name="user" fuiPrefix></fui-icon>
|
|
34
|
-
* Profile
|
|
35
|
-
* </fui-menu-item>
|
|
36
|
-
* ```
|
|
37
|
-
*
|
|
38
|
-
* ### Danger Menu Item
|
|
39
|
-
* ```html
|
|
40
|
-
* <fui-menu-item variant="danger">
|
|
41
|
-
* <fui-icon name="trash" fuiPrefix></fui-icon>
|
|
42
|
-
* Delete Account
|
|
43
|
-
* </fui-menu-item>
|
|
44
|
-
* ```
|
|
45
|
-
*
|
|
46
|
-
* ### Disabled Menu Item
|
|
47
|
-
* ```html
|
|
48
|
-
* <fui-menu-item [disabled]="true">
|
|
49
|
-
* Unavailable Option
|
|
50
|
-
* </fui-menu-item>
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```typescript
|
|
55
|
-
* import { FuiMenuItemComponent } from '@raintonic/formaui/components/menu';
|
|
56
|
-
*
|
|
57
|
-
* @Component({
|
|
58
|
-
* standalone: true,
|
|
59
|
-
* imports: [FuiMenuItemComponent],
|
|
60
|
-
* templateUrl: './my-component.component.html',
|
|
61
|
-
* styleUrl: './my-component.component.scss'
|
|
62
|
-
* })
|
|
63
|
-
* export class MyComponent {
|
|
64
|
-
* onItemClick(event: Event) {
|
|
65
|
-
* console.log('Menu item clicked:', event);
|
|
66
|
-
* }
|
|
67
|
-
* }
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
class FuiMenuItemComponent {
|
|
71
|
-
/**
|
|
72
|
-
* Menu item variant that determines the visual style
|
|
73
|
-
* @default 'default'
|
|
74
|
-
*/
|
|
75
|
-
variant = input('default', { ...(ngDevMode ? { debugName: "variant" } : /* istanbul ignore next */ {}), transform: (v) => (MENU_ITEM_VARIANTS.includes(v) ? v : 'default') });
|
|
76
|
-
/**
|
|
77
|
-
* Whether the menu item is disabled
|
|
78
|
-
* @default false
|
|
79
|
-
*/
|
|
80
|
-
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
81
|
-
/**
|
|
82
|
-
* Emitted when the menu item is clicked or activated
|
|
83
|
-
*/
|
|
84
|
-
selected = output();
|
|
85
|
-
/**
|
|
86
|
-
* Internal tabindex for roving tabindex pattern.
|
|
87
|
-
* Managed by the parent FuiMenuComponent.
|
|
88
|
-
* @internal
|
|
89
|
-
*/
|
|
90
|
-
tabIndex = signal('-1', ...(ngDevMode ? [{ debugName: "tabIndex" }] : /* istanbul ignore next */ []));
|
|
91
|
-
// Computed properties
|
|
92
|
-
computedClasses = computed(() => {
|
|
93
|
-
const classes = ['fui-menu-item', `fui-menu-item--${this.variant()}`];
|
|
94
|
-
if (this.disabled()) {
|
|
95
|
-
classes.push('fui-menu-item--disabled');
|
|
96
|
-
}
|
|
97
|
-
return classes.join(' ');
|
|
98
|
-
}, ...(ngDevMode ? [{ debugName: "computedClasses" }] : /* istanbul ignore next */ []));
|
|
99
|
-
/** @internal */ _elementRef = inject(ElementRef);
|
|
100
|
-
_renderer = inject(Renderer2);
|
|
101
|
-
onClick(event) {
|
|
102
|
-
if (this.disabled()) {
|
|
103
|
-
event.preventDefault();
|
|
104
|
-
event.stopPropagation();
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
this.selected.emit(event);
|
|
108
|
-
}
|
|
109
|
-
onKeydown(event) {
|
|
110
|
-
if (this.disabled()) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
switch (event.key) {
|
|
114
|
-
case 'Enter':
|
|
115
|
-
case ' ':
|
|
116
|
-
event.preventDefault();
|
|
117
|
-
// Dispatch a synthetic click so the parent menu's click handler
|
|
118
|
-
// can detect the activation and close the menu.
|
|
119
|
-
this._elementRef.nativeElement.click();
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Focuses the menu item
|
|
125
|
-
*/
|
|
126
|
-
focus() {
|
|
127
|
-
this._elementRef.nativeElement.focus();
|
|
128
|
-
}
|
|
129
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiMenuItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
130
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.6", type: FuiMenuItemComponent, isStandalone: true, selector: "fui-menu-item", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "class": "computedClasses()", "attr.role": "\"menuitem\"", "attr.tabindex": "disabled() ? \"-1\" : tabIndex()", "attr.aria-disabled": "disabled() ? \"true\" : null" } }, ngImport: i0, template: "<ng-content></ng-content>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}:host.fui-menu-item{transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms;display:flex;align-items:center;gap:var(--fui-spacing-03);width:100%;padding:var(--fui-spacing-03) var(--fui-spacing-04);margin:0;background-color:transparent;border:none;border-radius:var(--fui-border-radius-sm);font-family:var(--fui-font-family-sans);letter-spacing:.56px;font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-body);text-align:left;text-decoration:none;color:var(--fui-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none}:host.fui-menu-item:focus-visible{background-color:var(--fui-surface-02);outline:2px solid var(--fui-primary);outline-offset:-2px}:host.fui-menu-item:hover:not(.fui-menu-item--disabled){background-color:var(--fui-surface-02)}:host.fui-menu-item:active:not(.fui-menu-item--disabled){background-color:var(--fui-surface-05)}:host.fui-menu-item--danger{color:var(--fui-danger-100)}:host.fui-menu-item--danger:hover:not(.fui-menu-item--disabled){background-color:var(--fui-danger-10);color:var(--fui-danger-70)}:host.fui-menu-item--danger:active:not(.fui-menu-item--disabled){background-color:var(--fui-danger-20);color:var(--fui-danger-80)}:host.fui-menu-item--danger:focus-visible{background-color:var(--fui-danger-10);outline-color:var(--fui-danger-60)}:host.fui-menu-item--disabled{color:var(--fui-text-secondary);cursor:not-allowed;opacity:var(--fui-opacity-disabled)}:host.fui-menu-item--disabled:hover,:host.fui-menu-item--disabled:active,:host.fui-menu-item--disabled:focus{background-color:transparent}:host.fui-menu-item .fui-icon{flex-shrink:0;width:var(--fui-icon-size-sm);height:var(--fui-icon-size-sm);font-size:var(--fui-icon-size-md)}:host.fui-menu-item .fui-icon[fuiPrefix]{margin-right:var(--fui-spacing-02)}:host.fui-menu-item .fui-icon[fuiSuffix]{margin-left:auto;margin-right:0}:host.fui-menu-item .fui-menu-item__shortcut{margin-left:auto;font-size:var(--fui-font-size-01);color:var(--fui-text-secondary);font-family:var(--fui-font-family-mono)}@media(prefers-contrast:more){:host.fui-menu-item:focus-visible{outline-width:3px}:host.fui-menu-item--danger:focus-visible{outline-width:3px}}@media(prefers-reduced-motion:reduce){:host.fui-menu-item{transition:none}}\n"] });
|
|
131
|
-
}
|
|
132
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiMenuItemComponent, decorators: [{
|
|
133
|
-
type: Component,
|
|
134
|
-
args: [{ selector: 'fui-menu-item', standalone: true, imports: [], host: {
|
|
135
|
-
'[class]': 'computedClasses()',
|
|
136
|
-
'[attr.role]': '"menuitem"',
|
|
137
|
-
'[attr.tabindex]': 'disabled() ? "-1" : tabIndex()',
|
|
138
|
-
'[attr.aria-disabled]': 'disabled() ? "true" : null',
|
|
139
|
-
}, template: "<ng-content></ng-content>\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}:host.fui-menu-item{transition:background-color var(--fui-duration-fast-02) var(--fui-ease-standard) 0ms;display:flex;align-items:center;gap:var(--fui-spacing-03);width:100%;padding:var(--fui-spacing-03) var(--fui-spacing-04);margin:0;background-color:transparent;border:none;border-radius:var(--fui-border-radius-sm);font-family:var(--fui-font-family-sans);letter-spacing:.56px;font-size:var(--fui-font-size-02);font-weight:var(--fui-font-weight-regular);line-height:var(--fui-line-height-body);text-align:left;text-decoration:none;color:var(--fui-text-secondary);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none}:host.fui-menu-item:focus-visible{background-color:var(--fui-surface-02);outline:2px solid var(--fui-primary);outline-offset:-2px}:host.fui-menu-item:hover:not(.fui-menu-item--disabled){background-color:var(--fui-surface-02)}:host.fui-menu-item:active:not(.fui-menu-item--disabled){background-color:var(--fui-surface-05)}:host.fui-menu-item--danger{color:var(--fui-danger-100)}:host.fui-menu-item--danger:hover:not(.fui-menu-item--disabled){background-color:var(--fui-danger-10);color:var(--fui-danger-70)}:host.fui-menu-item--danger:active:not(.fui-menu-item--disabled){background-color:var(--fui-danger-20);color:var(--fui-danger-80)}:host.fui-menu-item--danger:focus-visible{background-color:var(--fui-danger-10);outline-color:var(--fui-danger-60)}:host.fui-menu-item--disabled{color:var(--fui-text-secondary);cursor:not-allowed;opacity:var(--fui-opacity-disabled)}:host.fui-menu-item--disabled:hover,:host.fui-menu-item--disabled:active,:host.fui-menu-item--disabled:focus{background-color:transparent}:host.fui-menu-item .fui-icon{flex-shrink:0;width:var(--fui-icon-size-sm);height:var(--fui-icon-size-sm);font-size:var(--fui-icon-size-md)}:host.fui-menu-item .fui-icon[fuiPrefix]{margin-right:var(--fui-spacing-02)}:host.fui-menu-item .fui-icon[fuiSuffix]{margin-left:auto;margin-right:0}:host.fui-menu-item .fui-menu-item__shortcut{margin-left:auto;font-size:var(--fui-font-size-01);color:var(--fui-text-secondary);font-family:var(--fui-font-family-mono)}@media(prefers-contrast:more){:host.fui-menu-item:focus-visible{outline-width:3px}:host.fui-menu-item--danger:focus-visible{outline-width:3px}}@media(prefers-reduced-motion:reduce){:host.fui-menu-item{transition:none}}\n"] }]
|
|
140
|
-
}], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], onClick: [{
|
|
141
|
-
type: HostListener,
|
|
142
|
-
args: ['click', ['$event']]
|
|
143
|
-
}], onKeydown: [{
|
|
144
|
-
type: HostListener,
|
|
145
|
-
args: ['keydown', ['$event']]
|
|
146
|
-
}] } });
|
|
147
|
-
|
|
148
|
-
const RT_MENU_POSITIONS = [
|
|
149
|
-
'top-start',
|
|
150
|
-
'top',
|
|
151
|
-
'top-end',
|
|
152
|
-
'bottom-start',
|
|
153
|
-
'bottom',
|
|
154
|
-
'bottom-end',
|
|
155
|
-
'left-start',
|
|
156
|
-
'left',
|
|
157
|
-
'left-end',
|
|
158
|
-
'right-start',
|
|
159
|
-
'right',
|
|
160
|
-
'right-end',
|
|
161
|
-
];
|
|
162
|
-
const RT_MENU_SIZES = ['sm', 'md', 'lg'];
|
|
163
|
-
/**
|
|
164
|
-
* # FuiMenu Component
|
|
165
|
-
*
|
|
166
|
-
* A dropdown menu component that provides a list of options or actions.
|
|
167
|
-
* Designed to work with external triggers using the fuiMenuTrigger directive.
|
|
168
|
-
*
|
|
169
|
-
* ## Features
|
|
170
|
-
* - Multiple positioning options relative to trigger
|
|
171
|
-
* - Keyboard navigation (Arrow keys, Enter, Escape)
|
|
172
|
-
* - Click outside to close
|
|
173
|
-
* - Full accessibility support (ARIA attributes, focus management)
|
|
174
|
-
* - Customizable size variants
|
|
175
|
-
* - Auto-positioning with collision detection
|
|
176
|
-
* - Portal attachment to document body to avoid clipping issues
|
|
177
|
-
*
|
|
178
|
-
* ## Usage
|
|
179
|
-
*
|
|
180
|
-
* ### Basic Menu with External Trigger
|
|
181
|
-
* ```html
|
|
182
|
-
* <button fuiButton fuiMenuTrigger [fuiMenuTriggerFor]="menu">
|
|
183
|
-
* Open Menu
|
|
184
|
-
* </button>
|
|
185
|
-
* <fui-menu #menu>
|
|
186
|
-
* <fui-menu-item>Option 1</fui-menu-item>
|
|
187
|
-
* <fui-menu-item>Option 2</fui-menu-item>
|
|
188
|
-
* </fui-menu>
|
|
189
|
-
* ```
|
|
190
|
-
*
|
|
191
|
-
* ### Menu with Custom Position
|
|
192
|
-
* ```html
|
|
193
|
-
* <button fuiButton fuiMenuTrigger [fuiMenuTriggerFor]="menu">
|
|
194
|
-
* Open Menu
|
|
195
|
-
* </button>
|
|
196
|
-
* <fui-menu #menu position="top-start" size="lg">
|
|
197
|
-
* <fui-menu-item>Profile</fui-menu-item>
|
|
198
|
-
* <fui-menu-item variant="danger">Logout</fui-menu-item>
|
|
199
|
-
* </fui-menu>
|
|
200
|
-
* ```
|
|
201
|
-
*
|
|
202
|
-
* ### Menu without Portal (for special cases)
|
|
203
|
-
* ```html
|
|
204
|
-
* <button fuiButton fuiMenuTrigger [fuiMenuTriggerFor]="menu">
|
|
205
|
-
* Open Menu
|
|
206
|
-
* </button>
|
|
207
|
-
* <fui-menu #menu [attachToBody]="false">
|
|
208
|
-
* <fui-menu-item>Option 1</fui-menu-item>
|
|
209
|
-
* <fui-menu-item>Option 2</fui-menu-item>
|
|
210
|
-
* </fui-menu>
|
|
211
|
-
* ```
|
|
212
|
-
*
|
|
213
|
-
* ### Menu with Data Passed from Trigger
|
|
214
|
-
* ```html
|
|
215
|
-
* <button fuiButton fuiMenuTrigger
|
|
216
|
-
* [fuiMenuTriggerFor]="dynamicMenu"
|
|
217
|
-
* [menuTriggerData]="{ user: currentUser, items: menuItems }">
|
|
218
|
-
* Open Menu
|
|
219
|
-
* </button>
|
|
220
|
-
* <fui-menu #dynamicMenu>
|
|
221
|
-
* <!-- Access data in component using menu.menuData() -->
|
|
222
|
-
* </fui-menu>
|
|
223
|
-
* ```
|
|
224
|
-
*
|
|
225
|
-
* ```typescript
|
|
226
|
-
* @Component({
|
|
227
|
-
* template: `
|
|
228
|
-
* <fui-menu #menu>
|
|
229
|
-
* <fui-menu-item *ngFor="let item of menu.menuData()?.items">
|
|
230
|
-
* {{ item.label }}
|
|
231
|
-
* </fui-menu-item>
|
|
232
|
-
* </fui-menu>
|
|
233
|
-
* `
|
|
234
|
-
* })
|
|
235
|
-
* export class MyComponent { }
|
|
236
|
-
* ```
|
|
237
|
-
*/
|
|
238
|
-
class FuiMenuComponent {
|
|
239
|
-
/**
|
|
240
|
-
* Menu position relative to the trigger element
|
|
241
|
-
* @default 'bottom-start'
|
|
242
|
-
*/
|
|
243
|
-
position = input('bottom-start', ...(ngDevMode ? [{ debugName: "position" }] : /* istanbul ignore next */ []));
|
|
244
|
-
/**
|
|
245
|
-
* Menu size variant
|
|
246
|
-
* @default 'md'
|
|
247
|
-
*/
|
|
248
|
-
size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
|
|
249
|
-
/**
|
|
250
|
-
* Whether the menu should close when clicking outside
|
|
251
|
-
* @default true
|
|
252
|
-
*/
|
|
253
|
-
closeOnClickOutside = input(true, { ...(ngDevMode ? { debugName: "closeOnClickOutside" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
254
|
-
/**
|
|
255
|
-
* Whether the menu should close when pressing Escape
|
|
256
|
-
* @default true
|
|
257
|
-
*/
|
|
258
|
-
closeOnEscape = input(true, { ...(ngDevMode ? { debugName: "closeOnEscape" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
259
|
-
/**
|
|
260
|
-
* Whether the menu is disabled
|
|
261
|
-
* @default false
|
|
262
|
-
*/
|
|
263
|
-
disabled = input(false, { ...(ngDevMode ? { debugName: "disabled" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
264
|
-
/**
|
|
265
|
-
* Whether to attach the menu panel to the document body to avoid clipping issues
|
|
266
|
-
* @default true
|
|
267
|
-
*/
|
|
268
|
-
attachToBody = input(true, { ...(ngDevMode ? { debugName: "attachToBody" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
269
|
-
/**
|
|
270
|
-
* Emitted when the menu open state changes
|
|
271
|
-
*/
|
|
272
|
-
openChange = output();
|
|
273
|
-
/**
|
|
274
|
-
* Emitted when a menu item is selected
|
|
275
|
-
*/
|
|
276
|
-
itemSelected = output();
|
|
277
|
-
// Internal state
|
|
278
|
-
_isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : /* istanbul ignore next */ []));
|
|
279
|
-
_animationState = signal('void', ...(ngDevMode ? [{ debugName: "_animationState" }] : /* istanbul ignore next */ []));
|
|
280
|
-
_triggerElement = signal(null, ...(ngDevMode ? [{ debugName: "_triggerElement" }] : /* istanbul ignore next */ []));
|
|
281
|
-
_menuData = signal(null, ...(ngDevMode ? [{ debugName: "_menuData" }] : /* istanbul ignore next */ []));
|
|
282
|
-
_previousFocusedElement = null;
|
|
283
|
-
_overlayRef = null;
|
|
284
|
-
_overlaySubscriptions = new Subscription();
|
|
285
|
-
_closeAnimationTimeout = null;
|
|
286
|
-
// Computed properties
|
|
287
|
-
computedClasses = computed(() => {
|
|
288
|
-
const classes = ['fui-menu', `fui-menu--${this.position()}`, `fui-menu--${this.size()}`];
|
|
289
|
-
if (this.disabled()) {
|
|
290
|
-
classes.push('fui-menu--disabled');
|
|
291
|
-
}
|
|
292
|
-
return classes.join(' ');
|
|
293
|
-
}, ...(ngDevMode ? [{ debugName: "computedClasses" }] : /* istanbul ignore next */ []));
|
|
294
|
-
// Injected dependencies
|
|
295
|
-
_elementRef = inject(ElementRef);
|
|
296
|
-
_overlayService = inject(FuiOverlayService);
|
|
297
|
-
_document = inject(DOCUMENT);
|
|
298
|
-
_ngZone = inject(NgZone);
|
|
299
|
-
_outsideClickSub;
|
|
300
|
-
// View references
|
|
301
|
-
menuPanel;
|
|
302
|
-
// Content children for roving tabindex management
|
|
303
|
-
_menuItems = contentChildren(FuiMenuItemComponent, { ...(ngDevMode ? { debugName: "_menuItems" } : /* istanbul ignore next */ {}), descendants: true });
|
|
304
|
-
constructor() {
|
|
305
|
-
// Handle open state changes with animation
|
|
306
|
-
effect(() => {
|
|
307
|
-
if (this._isOpen()) {
|
|
308
|
-
requestAnimationFrame(() => {
|
|
309
|
-
this._animationState.set('enter');
|
|
310
|
-
this._openMenu();
|
|
311
|
-
});
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
this._startCloseAnimation();
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
// Emit open change events
|
|
318
|
-
effect(() => {
|
|
319
|
-
this.openChange.emit(this._isOpen());
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
/**
|
|
323
|
-
* Whether the menu is currently open
|
|
324
|
-
*/
|
|
325
|
-
isOpen() {
|
|
326
|
-
return this._isOpen();
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Opens the menu
|
|
330
|
-
*/
|
|
331
|
-
open() {
|
|
332
|
-
if (this.disabled())
|
|
333
|
-
return;
|
|
334
|
-
this._isOpen.set(true);
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Closes the menu
|
|
338
|
-
*/
|
|
339
|
-
close() {
|
|
340
|
-
this._isOpen.set(false);
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Toggles the menu open/closed state
|
|
344
|
-
*/
|
|
345
|
-
toggle() {
|
|
346
|
-
if (this._isOpen()) {
|
|
347
|
-
this.close();
|
|
348
|
-
}
|
|
349
|
-
else {
|
|
350
|
-
this.open();
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
/**
|
|
354
|
-
* Sets the trigger element for positioning (called by trigger directive)
|
|
355
|
-
*/
|
|
356
|
-
setTriggerElement(element) {
|
|
357
|
-
this._triggerElement.set(element);
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Gets the menu data passed from the trigger
|
|
361
|
-
* Returns a signal containing the data
|
|
362
|
-
*/
|
|
363
|
-
menuData() {
|
|
364
|
-
return this._menuData();
|
|
365
|
-
}
|
|
366
|
-
/**
|
|
367
|
-
* Sets the menu data (called by trigger directive)
|
|
368
|
-
* @param data The data to pass to the menu
|
|
369
|
-
*/
|
|
370
|
-
setMenuData(data) {
|
|
371
|
-
this._menuData.set(data);
|
|
372
|
-
}
|
|
373
|
-
// Start listening for outside clicks when menu opens
|
|
374
|
-
_listenForOutsideClicks() {
|
|
375
|
-
this._outsideClickSub?.unsubscribe();
|
|
376
|
-
if (!this.closeOnClickOutside())
|
|
377
|
-
return;
|
|
378
|
-
this._ngZone.runOutsideAngular(() => {
|
|
379
|
-
setTimeout(() => {
|
|
380
|
-
this._outsideClickSub = fromEvent(this._document, 'click')
|
|
381
|
-
.pipe(filter(() => this._isOpen()), filter((event) => {
|
|
382
|
-
const target = event.target;
|
|
383
|
-
const menuElement = this._elementRef.nativeElement;
|
|
384
|
-
const triggerElement = this._triggerElement();
|
|
385
|
-
return !menuElement.contains(target) && !triggerElement?.contains(target);
|
|
386
|
-
}))
|
|
387
|
-
.subscribe(() => {
|
|
388
|
-
this._ngZone.run(() => {
|
|
389
|
-
this.close();
|
|
390
|
-
});
|
|
391
|
-
});
|
|
392
|
-
});
|
|
393
|
-
});
|
|
394
|
-
}
|
|
395
|
-
onDocumentKeydown(event) {
|
|
396
|
-
if (!this._isOpen() || !this._isTopmostOverlay()) {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
switch (event.key) {
|
|
400
|
-
case 'Escape':
|
|
401
|
-
if (this.closeOnEscape()) {
|
|
402
|
-
event.preventDefault();
|
|
403
|
-
this.close();
|
|
404
|
-
}
|
|
405
|
-
break;
|
|
406
|
-
case 'ArrowDown':
|
|
407
|
-
event.preventDefault();
|
|
408
|
-
this._focusNextItem();
|
|
409
|
-
break;
|
|
410
|
-
case 'ArrowUp':
|
|
411
|
-
event.preventDefault();
|
|
412
|
-
this._focusPreviousItem();
|
|
413
|
-
break;
|
|
414
|
-
case 'Home':
|
|
415
|
-
event.preventDefault();
|
|
416
|
-
this._focusFirstItem();
|
|
417
|
-
break;
|
|
418
|
-
case 'End':
|
|
419
|
-
event.preventDefault();
|
|
420
|
-
this._focusLastItem();
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
onMenuClick(event) {
|
|
425
|
-
// Check if the click was on a menu item
|
|
426
|
-
const target = event.target;
|
|
427
|
-
const menuItem = target.closest('fui-menu-item');
|
|
428
|
-
if (menuItem && this._isOpen()) {
|
|
429
|
-
// Emit the itemSelected event and close the menu
|
|
430
|
-
this.itemSelected.emit(event);
|
|
431
|
-
this.close();
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
ngOnDestroy() {
|
|
435
|
-
this._clearCloseTimeout();
|
|
436
|
-
this._outsideClickSub?.unsubscribe();
|
|
437
|
-
this._restoreFocus();
|
|
438
|
-
this._disposeOverlay();
|
|
439
|
-
}
|
|
440
|
-
_openMenu() {
|
|
441
|
-
// Create overlay after the view updates
|
|
442
|
-
requestAnimationFrame(() => {
|
|
443
|
-
this._createOverlay();
|
|
444
|
-
this._listenForOutsideClicks();
|
|
445
|
-
const triggerElement = this._triggerElement();
|
|
446
|
-
if (triggerElement) {
|
|
447
|
-
triggerElement.setAttribute('aria-expanded', 'true');
|
|
448
|
-
this._previousFocusedElement = document.activeElement;
|
|
449
|
-
}
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
_closeMenu() {
|
|
453
|
-
this._outsideClickSub?.unsubscribe();
|
|
454
|
-
const triggerElement = this._triggerElement();
|
|
455
|
-
if (triggerElement) {
|
|
456
|
-
triggerElement.setAttribute('aria-expanded', 'false');
|
|
457
|
-
}
|
|
458
|
-
// Reset roving tabindex on all items
|
|
459
|
-
this._resetRovingTabindex();
|
|
460
|
-
this._disposeOverlay();
|
|
461
|
-
this._restoreFocus();
|
|
462
|
-
}
|
|
463
|
-
_createOverlay() {
|
|
464
|
-
if (this._overlayRef || !this.menuPanel) {
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
const triggerElement = this._triggerElement();
|
|
468
|
-
if (!triggerElement) {
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
// Create overlay with positioning strategy
|
|
472
|
-
const positions = this._getPositionsForMenuPosition(this.position());
|
|
473
|
-
const positionStrategy = this._overlayService
|
|
474
|
-
.position()
|
|
475
|
-
.connectedTo(triggerElement, positions)
|
|
476
|
-
.withPush(true)
|
|
477
|
-
.withViewportMargin(8);
|
|
478
|
-
// Create overlay
|
|
479
|
-
this._overlayRef = this._overlayService.create({
|
|
480
|
-
positionStrategy,
|
|
481
|
-
scrollStrategy: this.closeOnClickOutside()
|
|
482
|
-
? this._overlayService.scrollStrategies.close()
|
|
483
|
-
: this._overlayService.scrollStrategies.reposition(),
|
|
484
|
-
hasBackdrop: this.closeOnClickOutside(),
|
|
485
|
-
backdropClass: 'fui-menu-backdrop',
|
|
486
|
-
backdropClickBehavior: 'close',
|
|
487
|
-
panelClass: ['fui-menu-panel', `fui-menu-panel--${this.size()}`],
|
|
488
|
-
});
|
|
489
|
-
// Track overlay subscriptions for proper cleanup
|
|
490
|
-
this._overlaySubscriptions.unsubscribe();
|
|
491
|
-
this._overlaySubscriptions = new Subscription();
|
|
492
|
-
if (this.closeOnClickOutside()) {
|
|
493
|
-
this._overlaySubscriptions.add(this._overlayRef.backdropClick.subscribe(() => {
|
|
494
|
-
this.close();
|
|
495
|
-
}));
|
|
496
|
-
}
|
|
497
|
-
if (this.closeOnEscape()) {
|
|
498
|
-
this._overlaySubscriptions.add(this._overlayRef.keydownEvents.subscribe((event) => {
|
|
499
|
-
if (event.key === 'Escape') {
|
|
500
|
-
this.close();
|
|
501
|
-
}
|
|
502
|
-
}));
|
|
503
|
-
}
|
|
504
|
-
// Attach menu panel to overlay
|
|
505
|
-
const menuElement = this.menuPanel.nativeElement;
|
|
506
|
-
this._overlayRef.attach(menuElement);
|
|
507
|
-
// Focus first item after attachment
|
|
508
|
-
setTimeout(() => {
|
|
509
|
-
this._focusFirstItem();
|
|
510
|
-
}, 0);
|
|
511
|
-
}
|
|
512
|
-
_getPositionsForMenuPosition(position) {
|
|
513
|
-
const offset = 4;
|
|
514
|
-
switch (position) {
|
|
515
|
-
case 'bottom-start':
|
|
516
|
-
return [
|
|
517
|
-
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: offset },
|
|
518
|
-
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -offset },
|
|
519
|
-
];
|
|
520
|
-
case 'bottom':
|
|
521
|
-
return [
|
|
522
|
-
{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: offset },
|
|
523
|
-
{ originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -offset },
|
|
524
|
-
];
|
|
525
|
-
case 'bottom-end':
|
|
526
|
-
return [
|
|
527
|
-
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: offset },
|
|
528
|
-
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -offset },
|
|
529
|
-
];
|
|
530
|
-
case 'top-start':
|
|
531
|
-
return [
|
|
532
|
-
{ originX: 'start', originY: 'top', overlayX: 'start', overlayY: 'bottom', offsetY: -offset },
|
|
533
|
-
{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: offset },
|
|
534
|
-
];
|
|
535
|
-
case 'top':
|
|
536
|
-
return [
|
|
537
|
-
{ originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -offset },
|
|
538
|
-
{ originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: offset },
|
|
539
|
-
];
|
|
540
|
-
case 'top-end':
|
|
541
|
-
return [
|
|
542
|
-
{ originX: 'end', originY: 'top', overlayX: 'end', overlayY: 'bottom', offsetY: -offset },
|
|
543
|
-
{ originX: 'end', originY: 'bottom', overlayX: 'end', overlayY: 'top', offsetY: offset },
|
|
544
|
-
];
|
|
545
|
-
case 'right-start':
|
|
546
|
-
return [
|
|
547
|
-
{ originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: offset },
|
|
548
|
-
{ originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -offset },
|
|
549
|
-
];
|
|
550
|
-
case 'right':
|
|
551
|
-
return [
|
|
552
|
-
{ originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: offset },
|
|
553
|
-
{ originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -offset },
|
|
554
|
-
];
|
|
555
|
-
case 'right-end':
|
|
556
|
-
return [
|
|
557
|
-
{ originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: offset },
|
|
558
|
-
{ originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: -offset },
|
|
559
|
-
];
|
|
560
|
-
case 'left-start':
|
|
561
|
-
return [
|
|
562
|
-
{ originX: 'start', originY: 'top', overlayX: 'end', overlayY: 'top', offsetX: -offset },
|
|
563
|
-
{ originX: 'end', originY: 'top', overlayX: 'start', overlayY: 'top', offsetX: offset },
|
|
564
|
-
];
|
|
565
|
-
case 'left':
|
|
566
|
-
return [
|
|
567
|
-
{ originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -offset },
|
|
568
|
-
{ originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: offset },
|
|
569
|
-
];
|
|
570
|
-
case 'left-end':
|
|
571
|
-
return [
|
|
572
|
-
{ originX: 'start', originY: 'bottom', overlayX: 'end', overlayY: 'bottom', offsetX: -offset },
|
|
573
|
-
{ originX: 'end', originY: 'bottom', overlayX: 'start', overlayY: 'bottom', offsetX: offset },
|
|
574
|
-
];
|
|
575
|
-
default:
|
|
576
|
-
return [{ originX: 'start', originY: 'bottom', overlayX: 'start', overlayY: 'top', offsetY: offset }];
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
_restoreFocus() {
|
|
580
|
-
// Prefer the trigger element for focus restoration
|
|
581
|
-
const triggerElement = this._triggerElement();
|
|
582
|
-
if (triggerElement) {
|
|
583
|
-
triggerElement.focus();
|
|
584
|
-
}
|
|
585
|
-
else if (this._previousFocusedElement) {
|
|
586
|
-
this._previousFocusedElement.focus();
|
|
587
|
-
}
|
|
588
|
-
this._previousFocusedElement = null;
|
|
589
|
-
}
|
|
590
|
-
_getMenuItems() {
|
|
591
|
-
const menuElement = this.menuPanel?.nativeElement;
|
|
592
|
-
if (!menuElement)
|
|
593
|
-
return [];
|
|
594
|
-
return Array.from(menuElement.querySelectorAll('fui-menu-item:not([aria-disabled="true"])'));
|
|
595
|
-
}
|
|
596
|
-
/** @internal Called by FuiMenuTriggerDirective */
|
|
597
|
-
_focusFirstItem() {
|
|
598
|
-
const items = this._getMenuItems();
|
|
599
|
-
if (items.length > 0) {
|
|
600
|
-
this._focusItem(items[0]);
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
/** @internal Called by FuiMenuTriggerDirective */
|
|
604
|
-
_focusLastItem() {
|
|
605
|
-
const items = this._getMenuItems();
|
|
606
|
-
if (items.length > 0) {
|
|
607
|
-
this._focusItem(items[items.length - 1]);
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
_focusNextItem() {
|
|
611
|
-
const items = this._getMenuItems();
|
|
612
|
-
if (items.length === 0) {
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
615
|
-
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
|
616
|
-
const nextIndex = currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
617
|
-
this._focusItem(items[nextIndex]);
|
|
618
|
-
}
|
|
619
|
-
_focusPreviousItem() {
|
|
620
|
-
const items = this._getMenuItems();
|
|
621
|
-
if (items.length === 0) {
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
const currentIndex = items.findIndex((item) => item === document.activeElement);
|
|
625
|
-
const prevIndex = currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
626
|
-
this._focusItem(items[prevIndex]);
|
|
627
|
-
}
|
|
628
|
-
/**
|
|
629
|
-
* Focuses a menu item and updates roving tabindex across all items.
|
|
630
|
-
*/
|
|
631
|
-
_focusItem(element) {
|
|
632
|
-
this._updateRovingTabindex(element);
|
|
633
|
-
element.focus();
|
|
634
|
-
}
|
|
635
|
-
/**
|
|
636
|
-
* Resets all items to tabindex="-1" when the menu closes.
|
|
637
|
-
*/
|
|
638
|
-
_resetRovingTabindex() {
|
|
639
|
-
const items = this._menuItems();
|
|
640
|
-
for (const item of items) {
|
|
641
|
-
item.tabIndex.set('-1');
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
/**
|
|
645
|
-
* Updates roving tabindex: sets tabindex="0" on the target item
|
|
646
|
-
* and tabindex="-1" on all other items.
|
|
647
|
-
*/
|
|
648
|
-
_updateRovingTabindex(targetElement) {
|
|
649
|
-
const items = this._menuItems();
|
|
650
|
-
for (const item of items) {
|
|
651
|
-
if (item._elementRef.nativeElement === targetElement) {
|
|
652
|
-
item.tabIndex.set('0');
|
|
653
|
-
}
|
|
654
|
-
else {
|
|
655
|
-
item.tabIndex.set('-1');
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
_disposeOverlay() {
|
|
660
|
-
this._overlaySubscriptions.unsubscribe();
|
|
661
|
-
if (this._overlayRef) {
|
|
662
|
-
this._overlayRef.dispose();
|
|
663
|
-
this._overlayRef = null;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
_startCloseAnimation() {
|
|
667
|
-
// Clear any existing timeout
|
|
668
|
-
this._clearCloseTimeout();
|
|
669
|
-
// Trigger leave animation
|
|
670
|
-
this._animationState.set('leave');
|
|
671
|
-
// Wait for animation to complete before closing (matches CSS transition duration)
|
|
672
|
-
this._closeAnimationTimeout = window.setTimeout(() => {
|
|
673
|
-
this._closeMenu();
|
|
674
|
-
this._animationState.set('void');
|
|
675
|
-
}, 150); // Match the CSS transition duration
|
|
676
|
-
}
|
|
677
|
-
_clearCloseTimeout() {
|
|
678
|
-
if (this._closeAnimationTimeout !== null) {
|
|
679
|
-
clearTimeout(this._closeAnimationTimeout);
|
|
680
|
-
this._closeAnimationTimeout = null;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
/**
|
|
684
|
-
* Checks if this menu's overlay is the topmost (most recently opened) overlay.
|
|
685
|
-
* This ensures that only the topmost menu responds to keyboard events when
|
|
686
|
-
* multiple menus are open (e.g., nested menus).
|
|
687
|
-
*/
|
|
688
|
-
_isTopmostOverlay() {
|
|
689
|
-
if (!this._overlayRef) {
|
|
690
|
-
return false;
|
|
691
|
-
}
|
|
692
|
-
const activeOverlays = this._overlayService.getActiveOverlays();
|
|
693
|
-
// The last overlay in the array is the most recently created (topmost)
|
|
694
|
-
const topmostOverlay = activeOverlays[activeOverlays.length - 1];
|
|
695
|
-
return topmostOverlay === this._overlayRef;
|
|
696
|
-
}
|
|
697
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiMenuComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
698
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.6", type: FuiMenuComponent, isStandalone: true, selector: "fui-menu", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, closeOnClickOutside: { classPropertyName: "closeOnClickOutside", publicName: "closeOnClickOutside", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, attachToBody: { classPropertyName: "attachToBody", publicName: "attachToBody", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openChange: "openChange", itemSelected: "itemSelected" }, host: { listeners: { "document:keydown": "onDocumentKeydown($event)", "click": "onMenuClick($event)" }, properties: { "class": "computedClasses()", "attr.data-open": "isOpen() ? \"true\" : null" } }, queries: [{ propertyName: "_menuItems", predicate: FuiMenuItemComponent, descendants: true, isSignal: true }], viewQueries: [{ propertyName: "menuPanel", first: true, predicate: ["menuPanel"], descendants: true }], ngImport: i0, template: "@if (_isOpen() || _animationState() === 'leave') {\r\n <div class=\"fui-menu__panel\" #menuPanel role=\"menu\" [attr.data-animation-state]=\"_animationState()\">\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}::ng-deep .fui-menu{position:absolute}::ng-deep .fui-menu--disabled{opacity:var(--fui-opacity-disabled);pointer-events:none}::ng-deep .fui-menu__panel{--fui-menu-min-width: 150px;--fui-menu-max-width: 500px;--fui-menu-border-radius: var(--fui-border-radius-sm);--fui-menu-padding: var(--fui-spacing-02);--fui-menu-bg: var(--fui-surface-00);--fui-menu-shadow: var(--fui-shadow-04);z-index:var(--fui-z-popover);min-width:var(--fui-menu-min-width);max-width:var(--fui-menu-max-width);background-color:var(--fui-menu-bg);border-radius:var(--fui-menu-border-radius);padding:var(--fui-menu-padding);box-shadow:var(--fui-menu-shadow);overflow:hidden;transform-origin:top center;will-change:transform,opacity;opacity:0;transform:translateY(-14px);transition:opacity var(--fui-duration-moderate-02) var(--fui-ease-exit),transform var(--fui-duration-moderate-02) var(--fui-ease-exit)}::ng-deep .fui-menu__panel:focus{outline:none}::ng-deep .fui-menu__panel[data-animation-state=enter]{opacity:1;transform:translateY(0);transition:opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance),transform var(--fui-duration-moderate-01) var(--fui-ease-entrance)}::ng-deep .fui-menu__panel[data-animation-state=leave]{opacity:0;transform:translateY(-14px)}::ng-deep .fui-menu-panel--sm .fui-menu__panel{min-width:8rem;font-size:var(--fui-font-size-01)}::ng-deep .fui-menu-panel--md .fui-menu__panel{min-width:12rem;font-size:var(--fui-font-size-02)}::ng-deep .fui-menu-panel--lg .fui-menu__panel{min-width:16rem;font-size:var(--fui-font-size-03)}@media(prefers-contrast:more){::ng-deep .fui-menu__panel{border-width:2px;box-shadow:var(--fui-shadow-04)}}@media(prefers-reduced-motion:reduce){::ng-deep .fui-menu__panel{transition:none}}@media print{::ng-deep .fui-menu__panel{display:none!important}}\n"] });
|
|
699
|
-
}
|
|
700
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiMenuComponent, decorators: [{
|
|
701
|
-
type: Component,
|
|
702
|
-
args: [{ selector: 'fui-menu', standalone: true, imports: [], host: {
|
|
703
|
-
'[class]': 'computedClasses()',
|
|
704
|
-
'[attr.data-open]': 'isOpen() ? "true" : null',
|
|
705
|
-
}, template: "@if (_isOpen() || _animationState() === 'leave') {\r\n <div class=\"fui-menu__panel\" #menuPanel role=\"menu\" [attr.data-animation-state]=\"_animationState()\">\r\n <ng-content></ng-content>\r\n </div>\r\n}\r\n", styles: ["@keyframes fui-skeleton-pulse{0%{opacity:1}50%{opacity:.4}to{opacity:1}}@keyframes fui-spin{to{transform:rotate(360deg)}}@keyframes fui-shake{0%,to{transform:translate(0)}10%,30%,50%,70%,90%{transform:translate(-2px)}20%,40%,60%,80%{transform:translate(2px)}}.fui-motion-fade-in{transition:opacity var(--fui-duration-fast-02) var(--fui-ease-entrance) 0ms}.fui-motion-fade-out{transition:opacity var(--fui-duration-fast-01) var(--fui-ease-exit) 0ms}.fui-motion-slide-in-bottom{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-bottom.fui-motion-entering{transform:translateY(1rem)}.fui-motion-slide-in-top{transition:transform var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:translateY(0)}.fui-motion-slide-in-top.fui-motion-entering{transform:translateY(-1rem)}.fui-motion-scale-in{transition:transform,opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance) 0ms;transform:scale(1);opacity:1}.fui-motion-scale-in.fui-motion-entering{transform:scale(.95);opacity:0}.fui-no-motion{transition:none!important;animation:none!important}@media(prefers-reduced-motion:reduce){*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}}@keyframes fui-pulse{0%{transform:scale(1);opacity:1}50%{transform:scale(1.05)}to{transform:scale(1);opacity:1}}@keyframes fui-slide-in{0%{transform:translate(120%)}to{transform:translate(0)}}.fui-slide-in{animation:fui-slide-in var(--fui-duration-moderate-01) var(--fui-ease-entrance)}@keyframes fui-popover-enter{0%{opacity:0;transform:translateY(-14px)}60%{opacity:1}to{opacity:1;transform:translateY(0)}}::ng-deep .fui-menu{position:absolute}::ng-deep .fui-menu--disabled{opacity:var(--fui-opacity-disabled);pointer-events:none}::ng-deep .fui-menu__panel{--fui-menu-min-width: 150px;--fui-menu-max-width: 500px;--fui-menu-border-radius: var(--fui-border-radius-sm);--fui-menu-padding: var(--fui-spacing-02);--fui-menu-bg: var(--fui-surface-00);--fui-menu-shadow: var(--fui-shadow-04);z-index:var(--fui-z-popover);min-width:var(--fui-menu-min-width);max-width:var(--fui-menu-max-width);background-color:var(--fui-menu-bg);border-radius:var(--fui-menu-border-radius);padding:var(--fui-menu-padding);box-shadow:var(--fui-menu-shadow);overflow:hidden;transform-origin:top center;will-change:transform,opacity;opacity:0;transform:translateY(-14px);transition:opacity var(--fui-duration-moderate-02) var(--fui-ease-exit),transform var(--fui-duration-moderate-02) var(--fui-ease-exit)}::ng-deep .fui-menu__panel:focus{outline:none}::ng-deep .fui-menu__panel[data-animation-state=enter]{opacity:1;transform:translateY(0);transition:opacity var(--fui-duration-moderate-01) var(--fui-ease-entrance),transform var(--fui-duration-moderate-01) var(--fui-ease-entrance)}::ng-deep .fui-menu__panel[data-animation-state=leave]{opacity:0;transform:translateY(-14px)}::ng-deep .fui-menu-panel--sm .fui-menu__panel{min-width:8rem;font-size:var(--fui-font-size-01)}::ng-deep .fui-menu-panel--md .fui-menu__panel{min-width:12rem;font-size:var(--fui-font-size-02)}::ng-deep .fui-menu-panel--lg .fui-menu__panel{min-width:16rem;font-size:var(--fui-font-size-03)}@media(prefers-contrast:more){::ng-deep .fui-menu__panel{border-width:2px;box-shadow:var(--fui-shadow-04)}}@media(prefers-reduced-motion:reduce){::ng-deep .fui-menu__panel{transition:none}}@media print{::ng-deep .fui-menu__panel{display:none!important}}\n"] }]
|
|
706
|
-
}], ctorParameters: () => [], propDecorators: { position: [{ type: i0.Input, args: [{ isSignal: true, alias: "position", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], closeOnClickOutside: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnClickOutside", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], attachToBody: [{ type: i0.Input, args: [{ isSignal: true, alias: "attachToBody", required: false }] }], openChange: [{ type: i0.Output, args: ["openChange"] }], itemSelected: [{ type: i0.Output, args: ["itemSelected"] }], menuPanel: [{
|
|
707
|
-
type: ViewChild,
|
|
708
|
-
args: ['menuPanel', { static: false }]
|
|
709
|
-
}], _menuItems: [{ type: i0.ContentChildren, args: [i0.forwardRef(() => FuiMenuItemComponent), { ...{ descendants: true }, isSignal: true }] }], onDocumentKeydown: [{
|
|
710
|
-
type: HostListener,
|
|
711
|
-
args: ['document:keydown', ['$event']]
|
|
712
|
-
}], onMenuClick: [{
|
|
713
|
-
type: HostListener,
|
|
714
|
-
args: ['click', ['$event']]
|
|
715
|
-
}] } });
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* # fuiMenuTrigger Directive
|
|
719
|
-
*
|
|
720
|
-
* A directive that marks an element as a menu trigger, similar to Angular Material's matMenuTriggerFor.
|
|
721
|
-
* This directive should be used in conjunction with FuiMenuComponent.
|
|
722
|
-
*
|
|
723
|
-
* ## Usage
|
|
724
|
-
*
|
|
725
|
-
* ### Basic Usage
|
|
726
|
-
* ```html
|
|
727
|
-
* <button fuiMenuTrigger [menuTriggerFor]="menu">Open Menu</button>
|
|
728
|
-
* <fui-menu #menu>
|
|
729
|
-
* <fui-menu-item>Option 1</fui-menu-item>
|
|
730
|
-
* <fui-menu-item>Option 2</fui-menu-item>
|
|
731
|
-
* </fui-menu>
|
|
732
|
-
* ```
|
|
733
|
-
*
|
|
734
|
-
* ### With Menu Reference
|
|
735
|
-
* ```html
|
|
736
|
-
* <button fuiMenuTrigger [menuTriggerFor]="userMenu">
|
|
737
|
-
* <fui-icon name="user"></fui-icon>
|
|
738
|
-
* User Menu
|
|
739
|
-
* </button>
|
|
740
|
-
*
|
|
741
|
-
* <fui-menu #userMenu position="bottom-end">
|
|
742
|
-
* <fui-menu-item>Profile</fui-menu-item>
|
|
743
|
-
* <fui-menu-item>Settings</fui-menu-item>
|
|
744
|
-
* <fui-menu-item variant="danger">Logout</fui-menu-item>
|
|
745
|
-
* </fui-menu>
|
|
746
|
-
* ```
|
|
747
|
-
*
|
|
748
|
-
* ### Passing Data to Menu
|
|
749
|
-
* ```html
|
|
750
|
-
* <button fuiMenuTrigger
|
|
751
|
-
* [menuTriggerFor]="dynamicMenu"
|
|
752
|
-
* [menuTriggerData]="{ user: currentUser, role: 'admin' }">
|
|
753
|
-
* Open Menu
|
|
754
|
-
* </button>
|
|
755
|
-
*
|
|
756
|
-
* <fui-menu #dynamicMenu>
|
|
757
|
-
* <!-- Access menu data in your component via menu.menuData() -->
|
|
758
|
-
* </fui-menu>
|
|
759
|
-
* ```
|
|
760
|
-
*
|
|
761
|
-
* @example
|
|
762
|
-
* ```typescript
|
|
763
|
-
* import { FuiMenuTriggerDirective, FuiMenuComponent, FuiMenuItemComponent } from '@raintonic/formaui/components/menu';
|
|
764
|
-
*
|
|
765
|
-
* @Component({
|
|
766
|
-
* standalone: true,
|
|
767
|
-
* imports: [FuiMenuTriggerDirective, FuiMenuComponent, FuiMenuItemComponent],
|
|
768
|
-
* template: `
|
|
769
|
-
* <button fuiMenuTrigger [menuTriggerFor]="menu">Open Menu</button>
|
|
770
|
-
* <fui-menu #menu>
|
|
771
|
-
* <fui-menu-item>Option 1</fui-menu-item>
|
|
772
|
-
* <fui-menu-item>Option 2</fui-menu-item>
|
|
773
|
-
* </fui-menu>
|
|
774
|
-
* `
|
|
775
|
-
* })
|
|
776
|
-
* export class MyComponent { }
|
|
777
|
-
* ```
|
|
778
|
-
*/
|
|
779
|
-
class FuiMenuTriggerDirective {
|
|
780
|
-
_elementRef = inject((ElementRef));
|
|
781
|
-
/** The menu instance that this trigger should open */
|
|
782
|
-
menuTriggerFor = input(...(ngDevMode ? [undefined, { debugName: "menuTriggerFor" }] : /* istanbul ignore next */ []));
|
|
783
|
-
/**
|
|
784
|
-
* Data to be passed to the menu.
|
|
785
|
-
* Can be accessed in the menu component or menu items.
|
|
786
|
-
* Similar to Angular Material's matMenuTriggerData.
|
|
787
|
-
*/
|
|
788
|
-
menuTriggerData = input(...(ngDevMode ? [undefined, { debugName: "menuTriggerData" }] : /* istanbul ignore next */ []));
|
|
789
|
-
/** The menu instance that this trigger is associated with */
|
|
790
|
-
menu = null;
|
|
791
|
-
constructor() {
|
|
792
|
-
// Set up the menu reference when fuiMenuTriggerFor changes
|
|
793
|
-
effect(() => {
|
|
794
|
-
const menuRef = this.menuTriggerFor();
|
|
795
|
-
if (menuRef) {
|
|
796
|
-
this.menu = menuRef;
|
|
797
|
-
// Set the trigger element on the menu for positioning
|
|
798
|
-
menuRef.setTriggerElement(this._elementRef.nativeElement);
|
|
799
|
-
}
|
|
800
|
-
});
|
|
801
|
-
}
|
|
802
|
-
ngAfterViewInit() {
|
|
803
|
-
// Ensure the menu reference is set after view initialization
|
|
804
|
-
const menuRef = this.menuTriggerFor();
|
|
805
|
-
if (menuRef) {
|
|
806
|
-
this.menu = menuRef;
|
|
807
|
-
menuRef.setTriggerElement(this._elementRef.nativeElement);
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
onClick(event) {
|
|
811
|
-
if (this.menu) {
|
|
812
|
-
event.preventDefault();
|
|
813
|
-
// Update trigger element to ensure correct positioning when multiple triggers exist
|
|
814
|
-
this.menu.setTriggerElement(this._elementRef.nativeElement);
|
|
815
|
-
// Pass data to menu before opening/toggling
|
|
816
|
-
const data = this.menuTriggerData();
|
|
817
|
-
if (data !== undefined && this.menu.setMenuData) {
|
|
818
|
-
this.menu.setMenuData(data);
|
|
819
|
-
}
|
|
820
|
-
this.menu.toggle();
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
onKeydown(event) {
|
|
824
|
-
const menu = this.menu;
|
|
825
|
-
if (!menu)
|
|
826
|
-
return;
|
|
827
|
-
// Update trigger element and pass data to menu before opening
|
|
828
|
-
const prepareMenu = () => {
|
|
829
|
-
// Update trigger element to ensure correct positioning when multiple triggers exist
|
|
830
|
-
menu.setTriggerElement(this._elementRef.nativeElement);
|
|
831
|
-
// Pass data to menu
|
|
832
|
-
const data = this.menuTriggerData();
|
|
833
|
-
if (data !== undefined) {
|
|
834
|
-
menu.setMenuData(data);
|
|
835
|
-
}
|
|
836
|
-
};
|
|
837
|
-
switch (event.key) {
|
|
838
|
-
case 'Enter':
|
|
839
|
-
case ' ':
|
|
840
|
-
event.preventDefault();
|
|
841
|
-
prepareMenu();
|
|
842
|
-
menu.toggle();
|
|
843
|
-
break;
|
|
844
|
-
case 'ArrowDown':
|
|
845
|
-
event.preventDefault();
|
|
846
|
-
prepareMenu();
|
|
847
|
-
menu.open();
|
|
848
|
-
// Focus first item after menu opens
|
|
849
|
-
setTimeout(() => {
|
|
850
|
-
menu._focusFirstItem();
|
|
851
|
-
}, 0);
|
|
852
|
-
break;
|
|
853
|
-
case 'ArrowUp':
|
|
854
|
-
event.preventDefault();
|
|
855
|
-
prepareMenu();
|
|
856
|
-
menu.open();
|
|
857
|
-
// Focus last item after menu opens
|
|
858
|
-
setTimeout(() => {
|
|
859
|
-
menu._focusLastItem();
|
|
860
|
-
}, 0);
|
|
861
|
-
break;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
/** Gets the trigger element */
|
|
865
|
-
getElement() {
|
|
866
|
-
return this._elementRef.nativeElement;
|
|
867
|
-
}
|
|
868
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiMenuTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
869
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.6", type: FuiMenuTriggerDirective, isStandalone: true, selector: "[fuiMenuTrigger]", inputs: { menuTriggerFor: { classPropertyName: "menuTriggerFor", publicName: "menuTriggerFor", isSignal: true, isRequired: false, transformFunction: null }, menuTriggerData: { classPropertyName: "menuTriggerData", publicName: "menuTriggerData", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "attr.aria-haspopup": "\"true\"", "attr.aria-expanded": "menu?.isOpen() ? \"true\" : \"false\"" } }, ngImport: i0 });
|
|
870
|
-
}
|
|
871
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.6", ngImport: i0, type: FuiMenuTriggerDirective, decorators: [{
|
|
872
|
-
type: Directive,
|
|
873
|
-
args: [{
|
|
874
|
-
selector: '[fuiMenuTrigger]',
|
|
875
|
-
standalone: true,
|
|
876
|
-
host: {
|
|
877
|
-
'[attr.aria-haspopup]': '"true"',
|
|
878
|
-
'[attr.aria-expanded]': 'menu?.isOpen() ? "true" : "false"',
|
|
879
|
-
},
|
|
880
|
-
}]
|
|
881
|
-
}], ctorParameters: () => [], propDecorators: { menuTriggerFor: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuTriggerFor", required: false }] }], menuTriggerData: [{ type: i0.Input, args: [{ isSignal: true, alias: "menuTriggerData", required: false }] }], onClick: [{
|
|
882
|
-
type: HostListener,
|
|
883
|
-
args: ['click', ['$event']]
|
|
884
|
-
}], onKeydown: [{
|
|
885
|
-
type: HostListener,
|
|
886
|
-
args: ['keydown', ['$event']]
|
|
887
|
-
}] } });
|
|
888
|
-
|
|
889
|
-
// Public API for menu components
|
|
890
|
-
|
|
891
|
-
/**
|
|
892
|
-
* Generated bundle index. Do not edit.
|
|
893
|
-
*/
|
|
894
|
-
|
|
895
|
-
export { FuiMenuComponent, FuiMenuItemComponent, FuiMenuTriggerDirective, MENU_ITEM_VARIANTS, RT_MENU_POSITIONS, RT_MENU_SIZES };
|
|
896
|
-
//# sourceMappingURL=raintonic-formaui-components-menu.mjs.map
|