ng-primitives 0.43.1 → 0.45.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.
Files changed (63) hide show
  1. package/a11y/active-descendant/active-descendant.d.ts +38 -0
  2. package/a11y/index.d.ts +2 -1
  3. package/combobox/README.md +3 -0
  4. package/combobox/combobox/combobox-state.d.ts +60 -0
  5. package/combobox/combobox/combobox.d.ts +182 -0
  6. package/combobox/combobox-button/combobox-button.d.ts +61 -0
  7. package/combobox/combobox-dropdown/combobox-dropdown.d.ts +65 -0
  8. package/combobox/combobox-input/combobox-input.d.ts +79 -0
  9. package/combobox/combobox-option/combobox-option.d.ts +94 -0
  10. package/combobox/combobox-portal/combobox-portal.d.ts +35 -0
  11. package/combobox/index.d.ts +7 -0
  12. package/dialog/config/dialog-config.d.ts +2 -0
  13. package/dialog/dialog/dialog-ref.d.ts +2 -0
  14. package/dialog/dialog-trigger/dialog-trigger.d.ts +8 -1
  15. package/example-theme/index.css +1 -0
  16. package/fesm2022/ng-primitives-a11y.mjs +100 -1
  17. package/fesm2022/ng-primitives-a11y.mjs.map +1 -1
  18. package/fesm2022/ng-primitives-combobox.mjs +783 -0
  19. package/fesm2022/ng-primitives-combobox.mjs.map +1 -0
  20. package/fesm2022/ng-primitives-dialog.mjs +19 -3
  21. package/fesm2022/ng-primitives-dialog.mjs.map +1 -1
  22. package/fesm2022/ng-primitives-focus-trap.mjs +9 -1
  23. package/fesm2022/ng-primitives-focus-trap.mjs.map +1 -1
  24. package/fesm2022/ng-primitives-listbox.mjs +1 -1
  25. package/fesm2022/ng-primitives-listbox.mjs.map +1 -1
  26. package/fesm2022/ng-primitives-menu.mjs +372 -63
  27. package/fesm2022/ng-primitives-menu.mjs.map +1 -1
  28. package/fesm2022/ng-primitives-popover.mjs +62 -332
  29. package/fesm2022/ng-primitives-popover.mjs.map +1 -1
  30. package/fesm2022/ng-primitives-portal.mjs +359 -2
  31. package/fesm2022/ng-primitives-portal.mjs.map +1 -1
  32. package/fesm2022/ng-primitives-resize.mjs +21 -2
  33. package/fesm2022/ng-primitives-resize.mjs.map +1 -1
  34. package/fesm2022/ng-primitives-tooltip.mjs +51 -176
  35. package/fesm2022/ng-primitives-tooltip.mjs.map +1 -1
  36. package/focus-trap/focus-trap/focus-trap-state.d.ts +1 -0
  37. package/focus-trap/focus-trap/focus-trap.d.ts +5 -0
  38. package/menu/config/menu-config.d.ts +42 -0
  39. package/menu/index.d.ts +5 -1
  40. package/menu/menu/menu.d.ts +6 -2
  41. package/menu/menu-trigger/menu-trigger-state.d.ts +37 -0
  42. package/menu/menu-trigger/menu-trigger.d.ts +87 -13
  43. package/menu/submenu-trigger/submenu-trigger-state.d.ts +56 -0
  44. package/menu/submenu-trigger/submenu-trigger.d.ts +62 -10
  45. package/package.json +17 -13
  46. package/popover/index.d.ts +1 -2
  47. package/popover/popover/popover.d.ts +2 -43
  48. package/popover/popover-trigger/popover-trigger.d.ts +13 -107
  49. package/portal/index.d.ts +2 -0
  50. package/portal/overlay-token.d.ts +12 -0
  51. package/portal/overlay.d.ts +170 -0
  52. package/resize/index.d.ts +1 -1
  53. package/resize/utils/resize.d.ts +5 -0
  54. package/schematics/ng-generate/schema.d.ts +2 -1
  55. package/schematics/ng-generate/schema.json +1 -0
  56. package/schematics/ng-generate/templates/combobox/combobox.__fileSuffix@dasherize__.ts.template +264 -0
  57. package/schematics/ng-generate/templates/listbox/listbox.__fileSuffix@dasherize__.ts.template +2 -0
  58. package/tooltip/index.d.ts +1 -1
  59. package/tooltip/tooltip/tooltip.d.ts +3 -25
  60. package/tooltip/tooltip-trigger/tooltip-trigger.d.ts +12 -63
  61. package/popover/popover/popover-token.d.ts +0 -10
  62. package/popover/utils/transform-origin.d.ts +0 -2
  63. package/tooltip/tooltip/tooltip-token.d.ts +0 -10
@@ -1,15 +1,47 @@
1
+ import { createOverlay, injectOverlay } from 'ng-primitives/portal';
2
+ export { injectOverlayContext as injectMenuContext } from 'ng-primitives/portal';
1
3
  import * as i0 from '@angular/core';
2
- import { InjectionToken, inject, HostListener, Directive, Injector } from '@angular/core';
3
- import * as i1$1 from 'ng-primitives/button';
4
+ import { InjectionToken, inject, Injector, input, booleanAttribute, numberAttribute, signal, computed, HostListener, Directive } from '@angular/core';
5
+ import * as i1 from 'ng-primitives/button';
4
6
  import { NgpButton } from 'ng-primitives/button';
5
- import { injectElementRef } from 'ng-primitives/internal';
7
+ import { injectElementRef, provideExitAnimationManager } from 'ng-primitives/internal';
6
8
  import * as i2 from 'ng-primitives/roving-focus';
7
9
  import { NgpRovingFocusItem, provideRovingFocusGroup, NgpRovingFocusGroup } from 'ng-primitives/roving-focus';
8
10
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
9
- import * as i1 from 'ng-primitives/popover';
10
- import { injectPopoverTriggerState, NgpPopoverTrigger, NgpPopover } from 'ng-primitives/popover';
11
+ import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
12
+ import * as i1$1 from 'ng-primitives/popover';
13
+ import { NgpPopover } from 'ng-primitives/popover';
11
14
  import { Subject } from 'rxjs';
12
15
 
16
+ const defaultMenuConfig = {
17
+ offset: 4,
18
+ placement: 'bottom-start',
19
+ flip: true,
20
+ container: null,
21
+ scrollBehavior: 'block',
22
+ };
23
+ const NgpMenuConfigToken = new InjectionToken('NgpMenuConfigToken');
24
+ /**
25
+ * Provide the default Menu configuration
26
+ * @param config The Menu configuration
27
+ * @returns The provider
28
+ */
29
+ function provideMenuConfig(config) {
30
+ return [
31
+ {
32
+ provide: NgpMenuConfigToken,
33
+ useValue: { ...defaultMenuConfig, ...config },
34
+ },
35
+ ];
36
+ }
37
+ /**
38
+ * Inject the Menu configuration
39
+ * @returns The global Menu configuration
40
+ */
41
+ function injectMenuConfig() {
42
+ return inject(NgpMenuConfigToken, { optional: true }) ?? defaultMenuConfig;
43
+ }
44
+
13
45
  const NgpMenuToken = new InjectionToken('NgpMenuToken');
14
46
  /**
15
47
  * Inject the Menu directive instance
@@ -24,41 +56,180 @@ function provideMenu(type) {
24
56
  return { provide: NgpMenuToken, useExisting: type };
25
57
  }
26
58
 
59
+ /**
60
+ * The state token for the MenuTrigger primitive.
61
+ */
62
+ const NgpMenuTriggerStateToken = createStateToken('MenuTrigger');
63
+ /**
64
+ * Provides the MenuTrigger state.
65
+ */
66
+ const provideMenuTriggerState = createStateProvider(NgpMenuTriggerStateToken);
67
+ /**
68
+ * Injects the MenuTrigger state.
69
+ */
70
+ const injectMenuTriggerState = createStateInjector(NgpMenuTriggerStateToken);
71
+ /**
72
+ * The MenuTrigger state registration function.
73
+ */
74
+ const menuTriggerState = createState(NgpMenuTriggerStateToken);
75
+
76
+ /**
77
+ * The state token for the SubmenuTrigger primitive.
78
+ */
79
+ const NgpSubmenuTriggerStateToken = createStateToken('SubmenuTrigger');
80
+ /**
81
+ * Provides the SubmenuTrigger state.
82
+ */
83
+ const provideSubmenuTriggerState = createStateProvider(NgpSubmenuTriggerStateToken);
84
+ /**
85
+ * Injects the SubmenuTrigger state.
86
+ */
87
+ const injectSubmenuTriggerState = createStateInjector(NgpSubmenuTriggerStateToken);
88
+ /**
89
+ * The SubmenuTrigger state registration function.
90
+ */
91
+ const submenuTriggerState = createState(NgpSubmenuTriggerStateToken);
92
+
27
93
  class NgpSubmenuTrigger {
28
94
  constructor() {
29
- /** Access the popover trigger element. */
95
+ /**
96
+ * Access the menu trigger element.
97
+ */
30
98
  this.trigger = injectElementRef();
31
- /** Access the popover trigger state */
32
- this.popoverTrigger = injectPopoverTriggerState();
99
+ /**
100
+ * Access the injector.
101
+ */
102
+ this.injector = inject(Injector);
103
+ /** Access the menu trigger state */
104
+ this.menuTrigger = injectMenuTriggerState();
33
105
  /** Access the parent menu */
34
106
  this.parentMenu = inject(NgpMenuToken, { optional: true });
35
- // by default the popover opens below and to the center of the trigger,
107
+ /**
108
+ * Access the submenu template ref.
109
+ */
110
+ this.menu = input(undefined, {
111
+ alias: 'ngpSubmenuTrigger',
112
+ });
113
+ /**
114
+ * Define if the trigger should be disabled.
115
+ * @default false
116
+ */
117
+ this.disabled = input(false, {
118
+ alias: 'ngpMenuTriggerDisabled',
119
+ transform: booleanAttribute,
120
+ });
121
+ /**
122
+ * Define the placement of the menu relative to the trigger.
123
+ * @default 'right-start'
124
+ */
125
+ this.placement = input('right-start', {
126
+ alias: 'ngpMenuTriggerPlacement',
127
+ });
128
+ /**
129
+ * Define the offset of the menu relative to the trigger.
130
+ * @default 0
131
+ */
132
+ this.offset = input(0, {
133
+ alias: 'ngpMenuTriggerOffset',
134
+ transform: numberAttribute,
135
+ });
136
+ /**
137
+ * Define whether the menu should flip when there is not enough space for the menu.
138
+ * @default true
139
+ */
140
+ this.flip = input(true, {
141
+ alias: 'ngpMenuTriggerFlip',
142
+ transform: booleanAttribute,
143
+ });
144
+ /**
145
+ * The overlay that manages the menu
146
+ * @internal
147
+ */
148
+ this.overlay = signal(null);
149
+ /**
150
+ * The open state of the menu.
151
+ * @internal
152
+ */
153
+ this.open = computed(() => this.overlay()?.isOpen() ?? false);
154
+ /**
155
+ * Access the menu trigger state.
156
+ */
157
+ this.state = submenuTriggerState(this);
158
+ // by default the menu opens below and to the center of the trigger,
36
159
  // but as this is a submenu we want to default to opening to the right
37
160
  // and to the start
38
- this.popoverTrigger().placement.set('right-start');
161
+ this.menuTrigger().placement.set('right-start');
39
162
  this.parentMenu?.closeSubmenus.pipe(takeUntilDestroyed()).subscribe(element => {
40
163
  // if the element is not the trigger, we want to close the menu
41
164
  if (element === this.trigger.nativeElement) {
42
165
  return;
43
166
  }
44
- this.closeMenu('mouse');
167
+ this.hide('mouse');
45
168
  });
46
169
  }
170
+ toggle(event) {
171
+ // if the trigger is disabled then do not toggle the menu
172
+ if (this.state.disabled()) {
173
+ return;
174
+ }
175
+ // determine the origin of the event, 0 is keyboard, 1 is mouse
176
+ const origin = event.detail === 0 ? 'keyboard' : 'mouse';
177
+ // if the menu is open then hide it
178
+ if (this.open()) {
179
+ this.hide(origin);
180
+ }
181
+ else {
182
+ this.show();
183
+ }
184
+ }
47
185
  /**
48
- * @internal
186
+ * Show the menu.
49
187
  */
50
- openMenu(origin) {
51
- // if the menu is already open, we don't want to do anything
52
- if (this.popoverTrigger().open()) {
188
+ show() {
189
+ // If the trigger is disabled, don't show the menu
190
+ if (this.state.disabled()) {
53
191
  return;
54
192
  }
55
- this.popoverTrigger().show(origin);
193
+ // Create the overlay if it doesn't exist yet
194
+ if (!this.overlay()) {
195
+ this.createOverlay();
196
+ }
197
+ // Show the overlay
198
+ this.overlay()?.show();
56
199
  }
57
200
  /**
58
201
  * @internal
202
+ * Hide the menu.
203
+ */
204
+ hide(origin = 'program') {
205
+ // If the trigger is disabled or the menu is not open, do nothing
206
+ if (this.state.disabled() || !this.open()) {
207
+ return;
208
+ }
209
+ // Hide the overlay
210
+ this.overlay()?.hide({ origin });
211
+ }
212
+ /**
213
+ * Create the overlay that will contain the menu
59
214
  */
60
- closeMenu(origin) {
61
- this.popoverTrigger().hide(origin);
215
+ createOverlay() {
216
+ const menu = this.state.menu();
217
+ if (!menu) {
218
+ throw new Error('Menu must be either a TemplateRef or a ComponentType');
219
+ }
220
+ // Create config for the overlay
221
+ const config = {
222
+ content: menu,
223
+ triggerElement: this.trigger.nativeElement,
224
+ injector: this.injector,
225
+ placement: this.state.placement(),
226
+ offset: this.state.offset(),
227
+ flip: this.state.flip(),
228
+ closeOnOutsideClick: true,
229
+ closeOnEscape: true,
230
+ restoreFocus: true,
231
+ };
232
+ this.overlay.set(createOverlay(config));
62
233
  }
63
234
  /**
64
235
  * If the user presses the right arrow key, we want to open the submenu
@@ -73,28 +244,29 @@ class NgpSubmenuTrigger {
73
244
  const isLeftArrow = event.key === 'ArrowLeft';
74
245
  if ((isRightArrow && !isRtl) || (isLeftArrow && isRtl)) {
75
246
  event.preventDefault();
76
- this.openMenu('keyboard');
247
+ this.show();
77
248
  }
78
249
  }
79
250
  /**
80
251
  * If the user hovers over the trigger, we want to open the submenu
81
252
  */
82
253
  showSubmenuOnHover() {
83
- this.openMenu('mouse');
254
+ this.show();
84
255
  }
85
256
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSubmenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
86
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpSubmenuTrigger, isStandalone: true, selector: "[ngpSubmenuTrigger]", host: { attributes: { "aria-haspopup": "true" }, listeners: { "keydown.ArrowRight": "showSubmenuOnArrow($event)", "keydown.ArrowLeft": "showSubmenuOnArrow($event)", "mouseenter": "showSubmenuOnHover()" } }, exportAs: ["ngpSubmenuTrigger"], hostDirectives: [{ directive: i1.NgpPopoverTrigger, inputs: ["ngpPopoverTrigger", "ngpSubmenuTrigger"] }], ngImport: i0 }); }
257
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpSubmenuTrigger, isStandalone: true, selector: "[ngpSubmenuTrigger]", inputs: { menu: { classPropertyName: "menu", publicName: "ngpSubmenuTrigger", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpMenuTriggerDisabled", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "ngpMenuTriggerPlacement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "ngpMenuTriggerOffset", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "ngpMenuTriggerFlip", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-haspopup": "true" }, listeners: { "click": "toggle($event)", "keydown.ArrowRight": "showSubmenuOnArrow($event)", "keydown.ArrowLeft": "showSubmenuOnArrow($event)", "mouseenter": "showSubmenuOnHover()" }, properties: { "attr.aria-expanded": "open() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"\" : null" } }, providers: [provideSubmenuTriggerState({ inherit: false }), provideExitAnimationManager()], exportAs: ["ngpSubmenuTrigger"], ngImport: i0 }); }
87
258
  }
88
259
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpSubmenuTrigger, decorators: [{
89
260
  type: Directive,
90
261
  args: [{
91
262
  selector: '[ngpSubmenuTrigger]',
92
263
  exportAs: 'ngpSubmenuTrigger',
93
- hostDirectives: [
94
- { directive: NgpPopoverTrigger, inputs: ['ngpPopoverTrigger:ngpSubmenuTrigger'] },
95
- ],
264
+ providers: [provideSubmenuTriggerState({ inherit: false }), provideExitAnimationManager()],
96
265
  host: {
97
266
  'aria-haspopup': 'true',
267
+ '[attr.aria-expanded]': 'open() ? "true" : "false"',
268
+ '[attr.data-open]': 'open() ? "" : null',
269
+ '(click)': 'toggle($event)',
98
270
  },
99
271
  }]
100
272
  }], ctorParameters: () => [], propDecorators: { showSubmenuOnArrow: [{
@@ -147,7 +319,7 @@ class NgpMenuItem {
147
319
  if ((isLeftArrow && !isRtl) || (isRightArrow && isRtl)) {
148
320
  event.preventDefault();
149
321
  if (trigger) {
150
- trigger.closeMenu('keyboard');
322
+ trigger.hide('keyboard');
151
323
  }
152
324
  }
153
325
  }
@@ -158,7 +330,7 @@ class NgpMenuItem {
158
330
  this.parentMenu?.closeSubmenus.next(this.elementRef.nativeElement);
159
331
  }
160
332
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
161
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpMenuItem, isStandalone: true, selector: "[ngpMenuItem]", host: { attributes: { "role": "menuitem" }, listeners: { "click": "onClick($event)", "keydown.ArrowLeft": "handleArrowKey($event)", "keydown.ArrowRight": "handleArrowKey($event)", "mouseenter": "showSubmenuOnHover()" } }, exportAs: ["ngpMenuItem"], hostDirectives: [{ directive: i1$1.NgpButton, inputs: ["disabled", "ngpMenuItemDisabled"] }, { directive: i2.NgpRovingFocusItem }], ngImport: i0 }); }
333
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpMenuItem, isStandalone: true, selector: "[ngpMenuItem]", host: { attributes: { "role": "menuitem" }, listeners: { "click": "onClick($event)", "keydown.ArrowLeft": "handleArrowKey($event)", "keydown.ArrowRight": "handleArrowKey($event)", "mouseenter": "showSubmenuOnHover()" } }, exportAs: ["ngpMenuItem"], hostDirectives: [{ directive: i1.NgpButton, inputs: ["disabled", "ngpMenuItemDisabled"] }, { directive: i2.NgpRovingFocusItem }], ngImport: i0 }); }
162
334
  }
163
335
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpMenuItem, decorators: [{
164
336
  type: Directive,
@@ -186,60 +358,190 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
186
358
  */
187
359
  class NgpMenuTrigger {
188
360
  constructor() {
189
- /** Access the popover trigger state */
190
- this.state = injectPopoverTriggerState();
191
- /** Access any parent menus */
192
- this.parentMenu = inject(NgpMenuToken, { optional: true });
193
- // by default the popover opens below and to the center of the trigger,
194
- // but as this is a menu we want to default to opening below and to the start
195
- this.state().placement.set('bottom-start');
196
- // for menus we want to default to blocking the scroll behavior
197
- this.state().scrollBehavior.set('block');
361
+ /**
362
+ * Access the trigger element
363
+ */
364
+ this.trigger = injectElementRef();
365
+ /**
366
+ * Access the injector.
367
+ */
368
+ this.injector = inject(Injector);
369
+ /**
370
+ * Access the global menu configuration.
371
+ */
372
+ this.config = injectMenuConfig();
373
+ /**
374
+ * Access the menu template ref.
375
+ */
376
+ this.menu = input(undefined, {
377
+ alias: 'ngpMenuTrigger',
378
+ });
379
+ /**
380
+ * Define if the trigger should be disabled.
381
+ * @default false
382
+ */
383
+ this.disabled = input(false, {
384
+ alias: 'ngpMenuTriggerDisabled',
385
+ transform: booleanAttribute,
386
+ });
387
+ /**
388
+ * Define the placement of the menu relative to the trigger.
389
+ * @default 'bottom-start'
390
+ */
391
+ this.placement = input(this.config.placement, {
392
+ alias: 'ngpMenuTriggerPlacement',
393
+ });
394
+ /**
395
+ * Define the offset of the menu relative to the trigger.
396
+ * @default 0
397
+ */
398
+ this.offset = input(this.config.offset, {
399
+ alias: 'ngpMenuTriggerOffset',
400
+ transform: numberAttribute,
401
+ });
402
+ /**
403
+ * Define whether the menu should flip when there is not enough space for the menu.
404
+ * @default true
405
+ */
406
+ this.flip = input(this.config.flip, {
407
+ alias: 'ngpMenuTriggerFlip',
408
+ transform: booleanAttribute,
409
+ });
410
+ /**
411
+ * Define the container in which the menu should be attached.
412
+ * @default document.body
413
+ */
414
+ this.container = input(this.config.container, {
415
+ alias: 'ngpMenuTriggerContainer',
416
+ });
417
+ /**
418
+ * Defines how the menu behaves when the window is scrolled.
419
+ * @default 'block'
420
+ */
421
+ this.scrollBehavior = input(this.config.scrollBehavior, {
422
+ alias: 'ngpMenuTriggerScrollBehavior',
423
+ });
424
+ /**
425
+ * Provide context to the menu. This can be used to pass data to the menu content.
426
+ */
427
+ this.context = input(undefined, {
428
+ alias: 'ngpMenuTriggerContext',
429
+ });
430
+ /**
431
+ * The overlay that manages the menu
432
+ * @internal
433
+ */
434
+ this.overlay = signal(null);
435
+ /**
436
+ * The open state of the menu.
437
+ * @internal
438
+ */
439
+ this.open = computed(() => this.overlay()?.isOpen() ?? false);
440
+ /**
441
+ * The menu trigger state.
442
+ */
443
+ this.state = menuTriggerState(this);
444
+ }
445
+ ngOnDestroy() {
446
+ this.overlay()?.destroy();
447
+ }
448
+ toggle(event) {
449
+ // if the trigger is disabled then do not toggle the menu
450
+ if (this.state.disabled()) {
451
+ return;
452
+ }
453
+ // determine the origin of the event, 0 is keyboard, 1 is mouse
454
+ const origin = event.detail === 0 ? 'keyboard' : 'mouse';
455
+ // if the menu is open then hide it
456
+ if (this.open()) {
457
+ this.hide(origin);
458
+ }
459
+ else {
460
+ this.show();
461
+ }
198
462
  }
199
463
  /**
200
- * Close the menu and any parent menus
464
+ * Show the menu.
201
465
  */
202
- closeOnEscape() {
203
- this.parentMenu?.closeAllMenus('keyboard');
466
+ show() {
467
+ // If the trigger is disabled, don't show the menu
468
+ if (this.state.disabled()) {
469
+ return;
470
+ }
471
+ // Create the overlay if it doesn't exist yet
472
+ if (!this.overlay()) {
473
+ this.createOverlay();
474
+ }
475
+ // Show the overlay
476
+ this.overlay()?.show();
477
+ }
478
+ /**
479
+ * @internal
480
+ * Hide the menu.
481
+ */
482
+ hide(origin = 'program') {
483
+ // If the trigger is disabled or the menu is not open, do nothing
484
+ if (this.state.disabled() || !this.open()) {
485
+ return;
486
+ }
487
+ // Hide the overlay
488
+ this.overlay()?.hide({ origin });
489
+ }
490
+ /**
491
+ * Create the overlay that will contain the menu
492
+ */
493
+ createOverlay() {
494
+ const menu = this.state.menu();
495
+ if (!menu) {
496
+ throw new Error('Menu must be either a TemplateRef or a ComponentType');
497
+ }
498
+ // Create config for the overlay
499
+ const config = {
500
+ content: menu,
501
+ triggerElement: this.trigger.nativeElement,
502
+ injector: this.injector,
503
+ context: this.state.context(),
504
+ container: this.state.container(),
505
+ placement: this.state.placement(),
506
+ offset: this.state.offset(),
507
+ flip: this.state.flip(),
508
+ closeOnOutsideClick: true,
509
+ closeOnEscape: true,
510
+ restoreFocus: true,
511
+ scrollBehaviour: this.state.scrollBehavior(),
512
+ };
513
+ this.overlay.set(createOverlay(config));
204
514
  }
205
515
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
206
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpMenuTrigger, isStandalone: true, selector: "[ngpMenuTrigger]", host: { attributes: { "aria-haspopup": "true" }, listeners: { "document:keydown.escape": "closeOnEscape()" } }, exportAs: ["ngpMenuTrigger"], hostDirectives: [{ directive: i1.NgpPopoverTrigger, inputs: ["ngpPopoverTrigger", "ngpMenuTrigger", "ngpPopoverTriggerDisabled", "ngpMenuTriggerDisabled", "ngpPopoverTriggerPlacement", "ngpMenuTriggerPlacement", "ngpPopoverTriggerOffset", "ngpMenuTriggerOffset", "ngpPopoverTriggerShowDelay", "ngpMenuTriggerShowDelay", "ngpPopoverTriggerHideDelay", "ngpMenuTriggerHideDelay", "ngpPopoverTriggerFlip", "ngpMenuTriggerFlip", "ngpPopoverTriggerContainer", "ngpMenuTriggerContainer", "ngpPopoverTriggerScrollBehavior", "ngpMenuTriggerScrollBehavior"] }], ngImport: i0 }); }
516
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpMenuTrigger, isStandalone: true, selector: "[ngpMenuTrigger]", inputs: { menu: { classPropertyName: "menu", publicName: "ngpMenuTrigger", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpMenuTriggerDisabled", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "ngpMenuTriggerPlacement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "ngpMenuTriggerOffset", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "ngpMenuTriggerFlip", isSignal: true, isRequired: false, transformFunction: null }, container: { classPropertyName: "container", publicName: "ngpMenuTriggerContainer", isSignal: true, isRequired: false, transformFunction: null }, scrollBehavior: { classPropertyName: "scrollBehavior", publicName: "ngpMenuTriggerScrollBehavior", isSignal: true, isRequired: false, transformFunction: null }, context: { classPropertyName: "context", publicName: "ngpMenuTriggerContext", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-haspopup": "true" }, listeners: { "click": "toggle($event)" }, properties: { "attr.aria-expanded": "open() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"\" : null", "attr.data-placement": "state.placement()" } }, providers: [provideMenuTriggerState({ inherit: false }), provideExitAnimationManager()], exportAs: ["ngpMenuTrigger"], ngImport: i0 }); }
207
517
  }
208
518
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpMenuTrigger, decorators: [{
209
519
  type: Directive,
210
520
  args: [{
211
521
  selector: '[ngpMenuTrigger]',
212
522
  exportAs: 'ngpMenuTrigger',
213
- hostDirectives: [
214
- {
215
- directive: NgpPopoverTrigger,
216
- inputs: [
217
- 'ngpPopoverTrigger:ngpMenuTrigger',
218
- 'ngpPopoverTriggerDisabled:ngpMenuTriggerDisabled',
219
- 'ngpPopoverTriggerPlacement:ngpMenuTriggerPlacement',
220
- 'ngpPopoverTriggerOffset:ngpMenuTriggerOffset',
221
- 'ngpPopoverTriggerShowDelay:ngpMenuTriggerShowDelay',
222
- 'ngpPopoverTriggerHideDelay:ngpMenuTriggerHideDelay',
223
- 'ngpPopoverTriggerFlip:ngpMenuTriggerFlip',
224
- 'ngpPopoverTriggerContainer:ngpMenuTriggerContainer',
225
- 'ngpPopoverTriggerScrollBehavior:ngpMenuTriggerScrollBehavior',
226
- ],
227
- },
228
- ],
523
+ providers: [provideMenuTriggerState({ inherit: false }), provideExitAnimationManager()],
229
524
  host: {
230
525
  'aria-haspopup': 'true',
231
- '(document:keydown.escape)': 'closeOnEscape()',
526
+ '[attr.aria-expanded]': 'open() ? "true" : "false"',
527
+ '[attr.data-open]': 'open() ? "" : null',
528
+ '[attr.data-placement]': 'state.placement()',
529
+ '(click)': 'toggle($event)',
232
530
  },
233
531
  }]
234
- }], ctorParameters: () => [] });
532
+ }] });
235
533
 
236
534
  /**
237
535
  * The `NgpMenu` is a container for menu items.
238
536
  */
239
537
  class NgpMenu {
240
538
  constructor() {
241
- /** Access the popover trigger state */
242
- this.popover = injectPopoverTriggerState();
539
+ /**
540
+ * Access the overlay.
541
+ */
542
+ this.overlay = injectOverlay();
543
+ /** Access the menu trigger state */
544
+ this.menuTrigger = injectMenuTriggerState();
243
545
  /** Access any parent menus */
244
546
  this.parentMenu = inject(NgpMenuToken, { optional: true, skipSelf: true });
245
547
  /** @internal Whether we should close submenus */
@@ -247,15 +549,15 @@ class NgpMenu {
247
549
  }
248
550
  /** Close the menu and any parent menus */
249
551
  closeAllMenus(origin) {
250
- this.popover().hide(origin);
552
+ this.menuTrigger().hide(origin);
251
553
  this.parentMenu?.closeAllMenus(origin);
252
554
  }
253
555
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
254
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpMenu, isStandalone: true, selector: "[ngpMenu]", providers: [
556
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpMenu, isStandalone: true, selector: "[ngpMenu]", host: { attributes: { "role": "menu" }, properties: { "style.left.px": "overlay.position().x", "style.top.px": "overlay.position().y", "style.--ngp-menu-trigger-width.px": "overlay.triggerWidth()", "style.--ngp-menu-transform-origin": "overlay.transformOrigin()" } }, providers: [
255
557
  // ensure we don't inherit the focus group from the parent menu if there is one
256
558
  provideRovingFocusGroup(NgpRovingFocusGroup, { inherit: false }),
257
559
  provideMenu(NgpMenu),
258
- ], exportAs: ["ngpMenu"], hostDirectives: [{ directive: i1.NgpPopover }, { directive: i2.NgpRovingFocusGroup }], ngImport: i0 }); }
560
+ ], exportAs: ["ngpMenu"], hostDirectives: [{ directive: i1$1.NgpPopover }, { directive: i2.NgpRovingFocusGroup }], ngImport: i0 }); }
259
561
  }
260
562
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpMenu, decorators: [{
261
563
  type: Directive,
@@ -268,6 +570,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
268
570
  provideRovingFocusGroup(NgpRovingFocusGroup, { inherit: false }),
269
571
  provideMenu(NgpMenu),
270
572
  ],
573
+ host: {
574
+ role: 'menu',
575
+ '[style.left.px]': 'overlay.position().x',
576
+ '[style.top.px]': 'overlay.position().y',
577
+ '[style.--ngp-menu-trigger-width.px]': 'overlay.triggerWidth()',
578
+ '[style.--ngp-menu-transform-origin]': 'overlay.transformOrigin()',
579
+ },
271
580
  }]
272
581
  }] });
273
582
 
@@ -275,5 +584,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImpo
275
584
  * Generated bundle index. Do not edit.
276
585
  */
277
586
 
278
- export { NgpMenu, NgpMenuItem, NgpMenuToken, NgpMenuTrigger, NgpSubmenuTrigger, injectMenu };
587
+ export { NgpMenu, NgpMenuItem, NgpMenuToken, NgpMenuTrigger, NgpSubmenuTrigger, injectMenu, injectMenuTriggerState, injectSubmenuTriggerState, provideMenuConfig, provideMenuTriggerState, provideSubmenuTriggerState };
279
588
  //# sourceMappingURL=ng-primitives-menu.mjs.map