ng-primitives 0.93.0 → 0.94.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 (55) hide show
  1. package/a11y/index.d.ts +2 -0
  2. package/accordion/index.d.ts +22 -0
  3. package/autofill/index.d.ts +2 -0
  4. package/avatar/index.d.ts +6 -0
  5. package/breadcrumbs/index.d.ts +14 -0
  6. package/button/index.d.ts +21 -3
  7. package/checkbox/index.d.ts +14 -0
  8. package/fesm2022/ng-primitives-accordion.mjs +24 -0
  9. package/fesm2022/ng-primitives-accordion.mjs.map +1 -1
  10. package/fesm2022/ng-primitives-button.mjs +18 -4
  11. package/fesm2022/ng-primitives-button.mjs.map +1 -1
  12. package/fesm2022/ng-primitives-checkbox.mjs +18 -0
  13. package/fesm2022/ng-primitives-checkbox.mjs.map +1 -1
  14. package/fesm2022/ng-primitives-file-upload.mjs +2 -2
  15. package/fesm2022/ng-primitives-file-upload.mjs.map +1 -1
  16. package/fesm2022/ng-primitives-input.mjs +10 -2
  17. package/fesm2022/ng-primitives-input.mjs.map +1 -1
  18. package/fesm2022/ng-primitives-menu.mjs +313 -352
  19. package/fesm2022/ng-primitives-menu.mjs.map +1 -1
  20. package/fesm2022/ng-primitives-roving-focus.mjs +1 -1
  21. package/fesm2022/ng-primitives-roving-focus.mjs.map +1 -1
  22. package/fesm2022/ng-primitives-separator.mjs +15 -7
  23. package/fesm2022/ng-primitives-separator.mjs.map +1 -1
  24. package/fesm2022/ng-primitives-slider.mjs +22 -4
  25. package/fesm2022/ng-primitives-slider.mjs.map +1 -1
  26. package/fesm2022/ng-primitives-state.mjs +12 -6
  27. package/fesm2022/ng-primitives-state.mjs.map +1 -1
  28. package/fesm2022/ng-primitives-switch.mjs +12 -0
  29. package/fesm2022/ng-primitives-switch.mjs.map +1 -1
  30. package/fesm2022/ng-primitives-tabs.mjs +6 -0
  31. package/fesm2022/ng-primitives-tabs.mjs.map +1 -1
  32. package/fesm2022/ng-primitives-textarea.mjs +7 -0
  33. package/fesm2022/ng-primitives-textarea.mjs.map +1 -1
  34. package/fesm2022/ng-primitives-toggle-group.mjs +18 -0
  35. package/fesm2022/ng-primitives-toggle-group.mjs.map +1 -1
  36. package/fesm2022/ng-primitives-toggle.mjs +12 -0
  37. package/fesm2022/ng-primitives-toggle.mjs.map +1 -1
  38. package/fesm2022/ng-primitives-toolbar.mjs +2 -2
  39. package/fesm2022/ng-primitives-toolbar.mjs.map +1 -1
  40. package/file-upload/index.d.ts +12 -8
  41. package/focus-trap/index.d.ts +2 -0
  42. package/form-field/index.d.ts +8 -0
  43. package/input/index.d.ts +13 -0
  44. package/menu/index.d.ts +247 -120
  45. package/package.json +1 -1
  46. package/roving-focus/index.d.ts +82 -10
  47. package/separator/index.d.ts +1 -0
  48. package/slider/index.d.ts +20 -0
  49. package/state/index.d.ts +9 -0
  50. package/switch/index.d.ts +12 -0
  51. package/tabs/index.d.ts +12 -0
  52. package/textarea/index.d.ts +7 -0
  53. package/toggle/index.d.ts +7 -0
  54. package/toggle-group/index.d.ts +16 -0
  55. package/toolbar/index.d.ts +2 -0
@@ -1,16 +1,14 @@
1
- import { coerceOffset, createOverlay, injectOverlay } from 'ng-primitives/portal';
1
+ import { createOverlay, injectOverlay, coerceOffset } from 'ng-primitives/portal';
2
2
  export { injectOverlayContext as injectMenuContext } from 'ng-primitives/portal';
3
3
  import * as i0 from '@angular/core';
4
- import { InjectionToken, inject, Injector, ViewContainerRef, input, booleanAttribute, signal, computed, HostListener, Directive } from '@angular/core';
4
+ import { InjectionToken, inject, Injector, ViewContainerRef, signal, computed, input, booleanAttribute, Directive } from '@angular/core';
5
+ import { ngpRovingFocusItem, provideRovingFocusItemState, ngpRovingFocusGroup, provideRovingFocusGroupState } from 'ng-primitives/roving-focus';
5
6
  import { ngpButton } from 'ng-primitives/button';
6
7
  import { injectElementRef } from 'ng-primitives/internal';
7
- import * as i1 from 'ng-primitives/roving-focus';
8
- import { NgpRovingFocusItem, provideRovingFocusGroup, NgpRovingFocusGroup } from 'ng-primitives/roving-focus';
9
- import { safeTakeUntilDestroyed } from 'ng-primitives/utils';
10
- import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
11
- import * as i2 from 'ng-primitives/focus-trap';
12
- import { NgpFocusTrap } from 'ng-primitives/focus-trap';
8
+ import { createPrimitive, attrBinding, dataBinding, listener, styleBinding } from 'ng-primitives/state';
13
9
  import { Subject } from 'rxjs';
10
+ import { safeTakeUntilDestroyed } from 'ng-primitives/utils';
11
+ import { ngpFocusTrap, provideFocusTrapState } from 'ng-primitives/focus-trap';
14
12
 
15
13
  const defaultMenuConfig = {
16
14
  offset: 4,
@@ -41,53 +39,227 @@ function injectMenuConfig() {
41
39
  return inject(NgpMenuConfigToken, { optional: true }) ?? defaultMenuConfig;
42
40
  }
43
41
 
44
- const NgpMenuToken = new InjectionToken('NgpMenuToken');
45
- /**
46
- * Inject the Menu directive instance
47
- */
48
- function injectMenu() {
49
- return inject(NgpMenuToken);
50
- }
51
- /**
52
- * Provide the Menu directive instance
53
- */
54
- function provideMenu(type) {
55
- return { provide: NgpMenuToken, useExisting: type };
56
- }
42
+ const [NgpMenuTriggerStateToken, ngpMenuTrigger, injectMenuTriggerState, provideMenuTriggerState,] = createPrimitive('NgpMenuTrigger', ({ disabled, menu, placement = signal('bottom-start'), offset = signal(4), flip = signal(true), container, scrollBehavior, context, }) => {
43
+ const element = injectElementRef();
44
+ const injector = inject(Injector);
45
+ const viewContainerRef = inject(ViewContainerRef);
46
+ const overlay = signal(null, ...(ngDevMode ? [{ debugName: "overlay" }] : []));
47
+ const open = computed(() => overlay()?.isOpen() ?? false, ...(ngDevMode ? [{ debugName: "open" }] : []));
48
+ // Host bindings
49
+ attrBinding(element, 'aria-haspopup', 'true');
50
+ attrBinding(element, 'aria-expanded', open);
51
+ dataBinding(element, 'data-open', open);
52
+ dataBinding(element, 'data-placement', placement);
53
+ // Event listeners
54
+ listener(element, 'click', onClick);
55
+ // Methods
56
+ function onClick(event) {
57
+ if (disabled?.()) {
58
+ return;
59
+ }
60
+ toggle(event);
61
+ }
62
+ function toggle(event) {
63
+ // determine the origin of the event, 0 is keyboard, 1 is mouse
64
+ const origin = event.detail === 0 ? 'keyboard' : 'mouse';
65
+ // if the menu is open then hide it
66
+ if (open()) {
67
+ hide(origin);
68
+ }
69
+ else {
70
+ show();
71
+ }
72
+ }
73
+ function show() {
74
+ // Create the overlay if it doesn't exist yet
75
+ if (!overlay()) {
76
+ createOverlayInstance();
77
+ }
78
+ // Show the overlay
79
+ overlay()?.show();
80
+ }
81
+ function hide(origin = 'program') {
82
+ // If the trigger is disabled or the menu is not open, do nothing
83
+ if (!open()) {
84
+ return;
85
+ }
86
+ // Hide the overlay
87
+ overlay()?.hide({ origin });
88
+ }
89
+ function createOverlayInstance() {
90
+ const menuContent = menu?.();
91
+ if (!menuContent) {
92
+ throw new Error('Menu must be either a TemplateRef or a ComponentType');
93
+ }
94
+ // Create config for the overlay
95
+ const config = {
96
+ content: menuContent,
97
+ triggerElement: element.nativeElement,
98
+ viewContainerRef,
99
+ injector,
100
+ context,
101
+ container: container?.(),
102
+ placement: placement,
103
+ offset: offset(),
104
+ flip: flip(),
105
+ closeOnOutsideClick: true,
106
+ closeOnEscape: true,
107
+ restoreFocus: true,
108
+ scrollBehaviour: scrollBehavior?.() ?? 'block',
109
+ };
110
+ overlay.set(createOverlay(config));
111
+ }
112
+ return {
113
+ placement,
114
+ show,
115
+ hide,
116
+ toggle,
117
+ };
118
+ });
57
119
 
58
- /**
59
- * The state token for the SubmenuTrigger primitive.
60
- */
61
- const NgpSubmenuTriggerStateToken = createStateToken('SubmenuTrigger');
62
- /**
63
- * Provides the SubmenuTrigger state.
64
- */
65
- const provideSubmenuTriggerState = createStateProvider(NgpSubmenuTriggerStateToken);
66
- /**
67
- * Injects the SubmenuTrigger state.
68
- */
69
- const injectSubmenuTriggerState = createStateInjector(NgpSubmenuTriggerStateToken);
70
- /**
71
- * The SubmenuTrigger state registration function.
72
- */
73
- const submenuTriggerState = createState(NgpSubmenuTriggerStateToken);
120
+ const [NgpMenuStateToken, ngpMenu, injectMenuState, provideMenuState] = createPrimitive('NgpMenu', ({}) => {
121
+ const element = injectElementRef();
122
+ const overlay = injectOverlay();
123
+ const menuTrigger = injectMenuTriggerState();
124
+ const parentMenu = injectMenuState({ optional: true, skipSelf: true });
125
+ // Host bindings
126
+ attrBinding(element, 'role', 'menu');
127
+ attrBinding(element, 'data-placement', overlay.finalPlacement);
128
+ attrBinding(element, 'data-overlay', '');
129
+ styleBinding(element, 'left.px', () => overlay.position().x ?? null);
130
+ styleBinding(element, 'top.px', () => overlay.position().y ?? null);
131
+ styleBinding(element, '--ngp-menu-trigger-width.px', overlay.triggerWidth);
132
+ styleBinding(element, '--ngp-menu-transform-origin', overlay.transformOrigin);
133
+ // Subject to notify children to close submenus
134
+ const closeSubmenus = new Subject();
135
+ // Methods
136
+ function closeAllMenus(origin) {
137
+ menuTrigger().hide(origin);
138
+ parentMenu()?.closeAllMenus(origin);
139
+ }
140
+ return {
141
+ closeAllMenus,
142
+ closeSubmenus,
143
+ };
144
+ });
145
+
146
+ const [NgpSubmenuTriggerStateToken, ngpSubmenuTrigger, injectSubmenuTriggerState, provideSubmenuTriggerState,] = createPrimitive('NgpSubmenuTrigger', ({ disabled = signal(false), menu, placement = signal('right-start'), offset = signal(0), flip = signal(true), }) => {
147
+ const element = injectElementRef();
148
+ const injector = inject(Injector);
149
+ const viewContainerRef = inject(ViewContainerRef);
150
+ const parentMenu = injectMenuState({ optional: true });
151
+ const overlay = signal(null, ...(ngDevMode ? [{ debugName: "overlay" }] : []));
152
+ const open = computed(() => overlay()?.isOpen() ?? false, ...(ngDevMode ? [{ debugName: "open" }] : []));
153
+ // Subscribe to parent menu's closeSubmenus
154
+ parentMenu()
155
+ ?.closeSubmenus.pipe(safeTakeUntilDestroyed())
156
+ .subscribe(submenuElement => {
157
+ // if the element is not the trigger, we want to close the menu
158
+ if (submenuElement === element.nativeElement) {
159
+ return;
160
+ }
161
+ hide('mouse');
162
+ });
163
+ // Host bindings
164
+ attrBinding(element, 'aria-haspopup', 'true');
165
+ attrBinding(element, 'aria-expanded', open);
166
+ dataBinding(element, 'data-open', open);
167
+ // Event listeners
168
+ listener(element, 'click', onClick);
169
+ listener(element, 'keydown', handleArrowKey);
170
+ listener(element, 'pointerenter', showSubmenuOnHover);
171
+ // Methods
172
+ function onClick(event) {
173
+ if (disabled?.()) {
174
+ return;
175
+ }
176
+ toggle(event);
177
+ }
178
+ function toggle(event) {
179
+ // determine the origin of the event, 0 is keyboard, 1 is mouse
180
+ const origin = event.detail === 0 ? 'keyboard' : 'mouse';
181
+ // if the menu is open then hide it
182
+ if (open()) {
183
+ hide(origin);
184
+ }
185
+ else {
186
+ show();
187
+ }
188
+ }
189
+ function show() {
190
+ // If the trigger is disabled, don't show the menu
191
+ if (disabled?.()) {
192
+ return;
193
+ }
194
+ // Create the overlay if it doesn't exist yet
195
+ if (!overlay()) {
196
+ createOverlayInstance();
197
+ }
198
+ // Show the overlay
199
+ overlay()?.show();
200
+ }
201
+ function hide(origin = 'program') {
202
+ // If the trigger is disabled or the menu is not open, do nothing
203
+ if (disabled?.() || !open()) {
204
+ return;
205
+ }
206
+ // Hide the overlay
207
+ overlay()?.hide({ origin });
208
+ }
209
+ function createOverlayInstance() {
210
+ const menuContent = menu?.();
211
+ if (!menuContent) {
212
+ throw new Error('Menu must be either a TemplateRef or a ComponentType');
213
+ }
214
+ // Create config for the overlay
215
+ const config = {
216
+ content: menuContent,
217
+ triggerElement: element.nativeElement,
218
+ injector,
219
+ placement,
220
+ offset: offset(),
221
+ flip: flip(),
222
+ closeOnOutsideClick: true,
223
+ closeOnEscape: true,
224
+ restoreFocus: true,
225
+ viewContainerRef,
226
+ };
227
+ overlay.set(createOverlay(config));
228
+ }
229
+ function handleArrowKey(event) {
230
+ if (event instanceof KeyboardEvent === false) {
231
+ return;
232
+ }
233
+ const direction = getComputedStyle(element.nativeElement).direction;
234
+ const isRtl = direction === 'rtl';
235
+ const isRightArrow = event.key === 'ArrowRight';
236
+ const isLeftArrow = event.key === 'ArrowLeft';
237
+ if ((isRightArrow && !isRtl) || (isLeftArrow && isRtl)) {
238
+ event.preventDefault();
239
+ show();
240
+ }
241
+ }
242
+ function showSubmenuOnHover(event) {
243
+ if (event instanceof PointerEvent === false) {
244
+ return;
245
+ }
246
+ // if this was triggered by a touch event, we don't want to show the submenu
247
+ // as it will be shown by the click event - this prevents the submenu from being toggled
248
+ if (event.pointerType === 'touch') {
249
+ return;
250
+ }
251
+ show();
252
+ }
253
+ return {
254
+ placement,
255
+ show,
256
+ hide,
257
+ toggle,
258
+ };
259
+ });
74
260
 
75
261
  class NgpSubmenuTrigger {
76
262
  constructor() {
77
- /**
78
- * Access the menu trigger element.
79
- */
80
- this.trigger = injectElementRef();
81
- /**
82
- * Access the injector.
83
- */
84
- this.injector = inject(Injector);
85
- /**
86
- * Access the view container reference.
87
- */
88
- this.viewContainerRef = inject(ViewContainerRef);
89
- /** Access the parent menu */
90
- this.parentMenu = inject(NgpMenuToken, { optional: true });
91
263
  /**
92
264
  * Access the submenu template ref.
93
265
  */
@@ -129,194 +301,79 @@ class NgpSubmenuTrigger {
129
301
  alias: 'ngpSubmenuTriggerFlip',
130
302
  transform: booleanAttribute,
131
303
  }]));
132
- /**
133
- * The overlay that manages the menu
134
- * @internal
135
- */
136
- this.overlay = signal(null, ...(ngDevMode ? [{ debugName: "overlay" }] : []));
137
- /**
138
- * The open state of the menu.
139
- * @internal
140
- */
141
- this.open = computed(() => this.overlay()?.isOpen() ?? false, ...(ngDevMode ? [{ debugName: "open" }] : []));
142
304
  /**
143
305
  * Access the menu trigger state.
144
306
  */
145
- this.state = submenuTriggerState(this);
146
- this.parentMenu?.closeSubmenus.pipe(safeTakeUntilDestroyed()).subscribe(element => {
147
- // if the element is not the trigger, we want to close the menu
148
- if (element === this.trigger.nativeElement) {
149
- return;
150
- }
151
- this.hide('mouse');
307
+ this.state = ngpSubmenuTrigger({
308
+ disabled: this.disabled,
309
+ menu: this.menu,
310
+ placement: this.placement,
311
+ offset: this.offset,
312
+ flip: this.flip,
152
313
  });
153
314
  }
154
- toggle(event) {
155
- // if the trigger is disabled then do not toggle the menu
156
- if (this.state.disabled()) {
157
- return;
158
- }
159
- // determine the origin of the event, 0 is keyboard, 1 is mouse
160
- const origin = event.detail === 0 ? 'keyboard' : 'mouse';
161
- // if the menu is open then hide it
162
- if (this.open()) {
163
- this.hide(origin);
164
- }
165
- else {
166
- this.show();
167
- }
168
- }
169
315
  /**
170
316
  * Show the menu.
171
317
  */
172
318
  show() {
173
- // If the trigger is disabled, don't show the menu
174
- if (this.state.disabled()) {
175
- return;
176
- }
177
- // Create the overlay if it doesn't exist yet
178
- if (!this.overlay()) {
179
- this.createOverlay();
180
- }
181
- // Show the overlay
182
- this.overlay()?.show();
319
+ this.state.show();
183
320
  }
184
321
  /**
185
- * @internal
186
322
  * Hide the menu.
187
323
  */
188
324
  hide(origin = 'program') {
189
- // If the trigger is disabled or the menu is not open, do nothing
190
- if (this.state.disabled() || !this.open()) {
191
- return;
192
- }
193
- // Hide the overlay
194
- this.overlay()?.hide({ origin });
195
- }
196
- /**
197
- * Create the overlay that will contain the menu
198
- */
199
- createOverlay() {
200
- const menu = this.state.menu();
201
- if (!menu) {
202
- throw new Error('Menu must be either a TemplateRef or a ComponentType');
203
- }
204
- // Create config for the overlay
205
- const config = {
206
- content: menu,
207
- triggerElement: this.trigger.nativeElement,
208
- injector: this.injector,
209
- placement: this.state.placement,
210
- offset: this.state.offset(),
211
- flip: this.state.flip(),
212
- closeOnOutsideClick: true,
213
- closeOnEscape: true,
214
- restoreFocus: true,
215
- viewContainerRef: this.viewContainerRef,
216
- };
217
- this.overlay.set(createOverlay(config));
218
- }
219
- /**
220
- * If the user presses the right arrow key, we want to open the submenu
221
- * and focus the first item in the submenu.
222
- * If the user presses the left arrow key, we want to close the submenu.
223
- * This behavior will be inverted if the direction is RTL.
224
- * @param event
225
- */
226
- handleArrowKey(event) {
227
- if (event instanceof KeyboardEvent === false) {
228
- return;
229
- }
230
- const direction = getComputedStyle(this.trigger.nativeElement).direction;
231
- const isRtl = direction === 'rtl';
232
- const isRightArrow = event.key === 'ArrowRight';
233
- const isLeftArrow = event.key === 'ArrowLeft';
234
- if ((isRightArrow && !isRtl) || (isLeftArrow && isRtl)) {
235
- event.preventDefault();
236
- this.show();
237
- }
325
+ this.state.hide(origin);
238
326
  }
239
327
  /**
240
- * If the user hovers over the trigger, we want to open the submenu
328
+ * Toggle the menu.
329
+ * @param event - The mouse event
241
330
  */
242
- showSubmenuOnHover(event) {
243
- // if this was triggered by a touch event, we don't want to show the submenu
244
- // as it will be shown by the click event - this prevents the submenu from being toggled
245
- if (event.pointerType === 'touch') {
246
- return;
247
- }
248
- this.show();
331
+ toggle(event) {
332
+ this.state.toggle(event);
249
333
  }
250
334
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpSubmenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
251
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpSubmenuTrigger, isStandalone: true, selector: "[ngpSubmenuTrigger]", inputs: { menu: { classPropertyName: "menu", publicName: "ngpSubmenuTrigger", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpSubmenuTriggerDisabled", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "ngpSubmenuTriggerPlacement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "ngpSubmenuTriggerOffset", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "ngpSubmenuTriggerFlip", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-haspopup": "true" }, listeners: { "click": "toggle($event)", "keydown.ArrowRight": "handleArrowKey($event)", "keydown.ArrowLeft": "handleArrowKey($event)", "pointerenter": "showSubmenuOnHover($event)" }, properties: { "attr.aria-expanded": "open() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"\" : null" } }, providers: [provideSubmenuTriggerState({ inherit: false })], exportAs: ["ngpSubmenuTrigger"], ngImport: i0 }); }
335
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpSubmenuTrigger, isStandalone: true, selector: "[ngpSubmenuTrigger]", inputs: { menu: { classPropertyName: "menu", publicName: "ngpSubmenuTrigger", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpSubmenuTriggerDisabled", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "ngpSubmenuTriggerPlacement", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "ngpSubmenuTriggerOffset", isSignal: true, isRequired: false, transformFunction: null }, flip: { classPropertyName: "flip", publicName: "ngpSubmenuTriggerFlip", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideSubmenuTriggerState()], exportAs: ["ngpSubmenuTrigger"], ngImport: i0 }); }
252
336
  }
253
337
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpSubmenuTrigger, decorators: [{
254
338
  type: Directive,
255
339
  args: [{
256
340
  selector: '[ngpSubmenuTrigger]',
257
341
  exportAs: 'ngpSubmenuTrigger',
258
- providers: [provideSubmenuTriggerState({ inherit: false })],
259
- host: {
260
- 'aria-haspopup': 'true',
261
- '[attr.aria-expanded]': 'open() ? "true" : "false"',
262
- '[attr.data-open]': 'open() ? "" : null',
263
- '(click)': 'toggle($event)',
264
- },
342
+ providers: [provideSubmenuTriggerState()],
265
343
  }]
266
- }], ctorParameters: () => [], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTrigger", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerDisabled", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerPlacement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerOffset", required: false }] }], flip: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerFlip", required: false }] }], handleArrowKey: [{
267
- type: HostListener,
268
- args: ['keydown.ArrowRight', ['$event']]
269
- }, {
270
- type: HostListener,
271
- args: ['keydown.ArrowLeft', ['$event']]
272
- }], showSubmenuOnHover: [{
273
- type: HostListener,
274
- args: ['pointerenter', ['$event']]
275
- }] } });
344
+ }], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTrigger", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerDisabled", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerPlacement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerOffset", required: false }] }], flip: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpSubmenuTriggerFlip", required: false }] }] } });
276
345
 
277
- /**
278
- * The `NgpMenuItem` directive represents a menu item.
279
- */
280
- class NgpMenuItem {
281
- constructor() {
282
- /** Access the injector */
283
- this.injector = inject(Injector);
284
- /** Access the button element */
285
- this.elementRef = injectElementRef();
286
- /** Access the parent menu */
287
- this.parentMenu = injectMenu();
288
- /** Whether the menu item is disabled */
289
- this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", alias: 'ngpMenuItemDisabled',
290
- transform: booleanAttribute }] : [{
291
- alias: 'ngpMenuItemDisabled',
292
- transform: booleanAttribute,
293
- }]));
294
- ngpButton({ disabled: this.disabled });
295
- }
296
- /** Close the menu when the item is clicked */
297
- onClick(event) {
346
+ const [NgpMenuItemStateToken, ngpMenuItem, injectMenuItemState, provideMenuItemState] = createPrimitive('NgpMenuItem', ({ disabled = signal(false) }) => {
347
+ const element = injectElementRef();
348
+ const injector = inject(Injector);
349
+ const parentMenu = injectMenuState({ optional: true });
350
+ ngpButton({ disabled });
351
+ // Host bindings
352
+ attrBinding(element, 'role', 'menuitem');
353
+ // Event listeners
354
+ listener(element, 'click', onClick);
355
+ listener(element, 'keydown', handleArrowKey);
356
+ listener(element, 'mouseenter', showSubmenuOnHover);
357
+ // Methods
358
+ function onClick(event) {
298
359
  // we do this here to avoid circular dependency issues
299
- const trigger = this.injector.get(NgpSubmenuTrigger, null, { self: true, optional: true });
360
+ const trigger = injector.get(NgpSubmenuTrigger, null, { self: true, optional: true });
300
361
  const origin = event.detail === 0 ? 'keyboard' : 'mouse';
301
362
  // if this is a submenu trigger, we don't want to close the menu, we want to open the submenu
302
363
  if (!trigger) {
303
- this.parentMenu?.closeAllMenus(origin);
364
+ parentMenu()?.closeAllMenus(origin);
304
365
  }
305
366
  }
306
- /**
307
- * If the user presses the left arrow key (in LTR) and there is a parent menu,
308
- * we want to close the menu and focus the parent menu item.
309
- */
310
- handleArrowKey(event) {
367
+ function handleArrowKey(event) {
311
368
  if (event instanceof KeyboardEvent === false) {
312
369
  return;
313
370
  }
314
371
  // if there is no parent menu, we don't want to do anything
315
- const trigger = this.injector.get(NgpSubmenuTrigger, null, { optional: true, skipSelf: true });
372
+ const trigger = injector.get(NgpSubmenuTrigger, null, { optional: true, skipSelf: true });
316
373
  if (!trigger) {
317
374
  return;
318
375
  }
319
- const direction = getComputedStyle(this.elementRef.nativeElement).direction;
376
+ const direction = getComputedStyle(element.nativeElement).direction;
320
377
  const isRtl = direction === 'rtl';
321
378
  const isLeftArrow = event.key === 'ArrowLeft';
322
379
  const isRightArrow = event.key === 'ArrowRight';
@@ -327,69 +384,43 @@ class NgpMenuItem {
327
384
  }
328
385
  }
329
386
  }
330
- /**
331
- * If the user hovers over the trigger, we want to open the submenu
332
- */
333
- showSubmenuOnHover() {
334
- this.parentMenu?.closeSubmenus.next(this.elementRef.nativeElement);
387
+ function showSubmenuOnHover() {
388
+ parentMenu()?.closeSubmenus.next(element.nativeElement);
389
+ }
390
+ return {};
391
+ });
392
+
393
+ /**
394
+ * The `NgpMenuItem` directive represents a menu item.
395
+ */
396
+ class NgpMenuItem {
397
+ constructor() {
398
+ /** Whether the menu item is disabled */
399
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled", alias: 'ngpMenuItemDisabled',
400
+ transform: booleanAttribute }] : [{
401
+ alias: 'ngpMenuItemDisabled',
402
+ transform: booleanAttribute,
403
+ }]));
404
+ ngpMenuItem({ disabled: this.disabled });
405
+ ngpRovingFocusItem({ disabled: this.disabled });
335
406
  }
336
407
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpMenuItem, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
337
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpMenuItem, isStandalone: true, selector: "[ngpMenuItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "ngpMenuItemDisabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "menuitem" }, listeners: { "click": "onClick($event)", "keydown.ArrowLeft": "handleArrowKey($event)", "keydown.ArrowRight": "handleArrowKey($event)", "mouseenter": "showSubmenuOnHover()" } }, exportAs: ["ngpMenuItem"], hostDirectives: [{ directive: i1.NgpRovingFocusItem, inputs: ["ngpRovingFocusItemDisabled", "ngpMenuItemDisabled"] }], ngImport: i0 }); }
408
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", type: NgpMenuItem, isStandalone: true, selector: "[ngpMenuItem]", inputs: { disabled: { classPropertyName: "disabled", publicName: "ngpMenuItemDisabled", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideMenuItemState(), provideRovingFocusItemState()], exportAs: ["ngpMenuItem"], ngImport: i0 }); }
338
409
  }
339
410
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpMenuItem, decorators: [{
340
411
  type: Directive,
341
412
  args: [{
342
413
  selector: '[ngpMenuItem]',
343
414
  exportAs: 'ngpMenuItem',
344
- hostDirectives: [
345
- { directive: NgpRovingFocusItem, inputs: ['ngpRovingFocusItemDisabled: ngpMenuItemDisabled'] },
346
- ],
347
- host: {
348
- role: 'menuitem',
349
- '(click)': 'onClick($event)',
350
- '(keydown.ArrowLeft)': 'handleArrowKey($event)',
351
- '(keydown.ArrowRight)': 'handleArrowKey($event)',
352
- },
415
+ providers: [provideMenuItemState(), provideRovingFocusItemState()],
353
416
  }]
354
- }], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuItemDisabled", required: false }] }], showSubmenuOnHover: [{
355
- type: HostListener,
356
- args: ['mouseenter']
357
- }] } });
358
-
359
- /**
360
- * The state token for the MenuTrigger primitive.
361
- */
362
- const NgpMenuTriggerStateToken = createStateToken('MenuTrigger');
363
- /**
364
- * Provides the MenuTrigger state.
365
- */
366
- const provideMenuTriggerState = createStateProvider(NgpMenuTriggerStateToken);
367
- /**
368
- * Injects the MenuTrigger state.
369
- */
370
- const injectMenuTriggerState = createStateInjector(NgpMenuTriggerStateToken);
371
- /**
372
- * The MenuTrigger state registration function.
373
- */
374
- const menuTriggerState = createState(NgpMenuTriggerStateToken);
417
+ }], ctorParameters: () => [], propDecorators: { disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuItemDisabled", required: false }] }] } });
375
418
 
376
419
  /**
377
420
  * The `NgpMenuTrigger` directive allows you to turn an element into a menu trigger.
378
421
  */
379
422
  class NgpMenuTrigger {
380
423
  constructor() {
381
- /**
382
- * Access the trigger element
383
- */
384
- this.trigger = injectElementRef();
385
- /**
386
- * Access the injector.
387
- */
388
- this.injector = inject(Injector);
389
- /**
390
- * Access the view container reference.
391
- */
392
- this.viewContainerRef = inject(ViewContainerRef);
393
424
  /**
394
425
  * Access the global menu configuration.
395
426
  */
@@ -455,106 +486,50 @@ class NgpMenuTrigger {
455
486
  this.context = input(undefined, ...(ngDevMode ? [{ debugName: "context", alias: 'ngpMenuTriggerContext' }] : [{
456
487
  alias: 'ngpMenuTriggerContext',
457
488
  }]));
458
- /**
459
- * The overlay that manages the menu
460
- * @internal
461
- */
462
- this.overlay = signal(null, ...(ngDevMode ? [{ debugName: "overlay" }] : []));
463
- /**
464
- * The open state of the menu.
465
- * @internal
466
- */
467
- this.open = computed(() => this.overlay()?.isOpen() ?? false, ...(ngDevMode ? [{ debugName: "open" }] : []));
468
489
  /**
469
490
  * The menu trigger state.
470
491
  */
471
- this.state = menuTriggerState(this);
472
- }
473
- ngOnDestroy() {
474
- this.overlay()?.destroy();
475
- }
476
- onClick(event) {
477
- if (this.state.disabled()) {
478
- return;
479
- }
480
- this.toggle(event);
481
- }
482
- toggle(event) {
483
- // determine the origin of the event, 0 is keyboard, 1 is mouse
484
- const origin = event.detail === 0 ? 'keyboard' : 'mouse';
485
- // if the menu is open then hide it
486
- if (this.open()) {
487
- this.hide(origin);
488
- }
489
- else {
490
- this.show();
491
- }
492
+ this.state = ngpMenuTrigger({
493
+ disabled: this.disabled,
494
+ menu: this.menu,
495
+ placement: this.placement,
496
+ offset: this.offset,
497
+ flip: this.flip,
498
+ container: this.container,
499
+ scrollBehavior: this.scrollBehavior,
500
+ context: this.context,
501
+ });
492
502
  }
493
503
  /**
494
504
  * Show the menu.
495
505
  */
496
506
  show() {
497
- // Create the overlay if it doesn't exist yet
498
- if (!this.overlay()) {
499
- this.createOverlay();
500
- }
501
- // Show the overlay
502
- this.overlay()?.show();
507
+ this.state.show();
503
508
  }
504
509
  /**
505
- * @internal
506
510
  * Hide the menu.
511
+ * @param origin - The focus origin
512
+ * @internal
507
513
  */
508
- hide(origin = 'program') {
509
- // If the trigger is disabled or the menu is not open, do nothing
510
- if (!this.open()) {
511
- return;
512
- }
513
- // Hide the overlay
514
- this.overlay()?.hide({ origin });
514
+ hide(origin) {
515
+ this.state.hide(origin);
515
516
  }
516
517
  /**
517
- * Create the overlay that will contain the menu
518
+ * Toggle the menu.
519
+ * @param event - The mouse event
518
520
  */
519
- createOverlay() {
520
- const menu = this.state.menu();
521
- if (!menu) {
522
- throw new Error('Menu must be either a TemplateRef or a ComponentType');
523
- }
524
- // Create config for the overlay
525
- const config = {
526
- content: menu,
527
- triggerElement: this.trigger.nativeElement,
528
- viewContainerRef: this.viewContainerRef,
529
- injector: this.injector,
530
- context: this.state.context,
531
- container: this.state.container(),
532
- placement: this.state.placement,
533
- offset: this.state.offset(),
534
- flip: this.state.flip(),
535
- closeOnOutsideClick: true,
536
- closeOnEscape: true,
537
- restoreFocus: true,
538
- scrollBehaviour: this.state.scrollBehavior(),
539
- };
540
- this.overlay.set(createOverlay(config));
521
+ toggle(event) {
522
+ this.state.toggle(event);
541
523
  }
542
524
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpMenuTrigger, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
543
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", 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": "onClick($event)" }, properties: { "attr.aria-expanded": "open() ? \"true\" : \"false\"", "attr.data-open": "open() ? \"\" : null", "attr.data-placement": "state.placement()" } }, providers: [provideMenuTriggerState({ inherit: false })], exportAs: ["ngpMenuTrigger"], ngImport: i0 }); }
525
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "20.3.9", 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 } }, providers: [provideMenuTriggerState()], exportAs: ["ngpMenuTrigger"], ngImport: i0 }); }
544
526
  }
545
527
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpMenuTrigger, decorators: [{
546
528
  type: Directive,
547
529
  args: [{
548
530
  selector: '[ngpMenuTrigger]',
549
531
  exportAs: 'ngpMenuTrigger',
550
- providers: [provideMenuTriggerState({ inherit: false })],
551
- host: {
552
- 'aria-haspopup': 'true',
553
- '[attr.aria-expanded]': 'open() ? "true" : "false"',
554
- '[attr.data-open]': 'open() ? "" : null',
555
- '[attr.data-placement]': 'state.placement()',
556
- '(click)': 'onClick($event)',
557
- },
532
+ providers: [provideMenuTriggerState()],
558
533
  }]
559
534
  }], propDecorators: { menu: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTrigger", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerDisabled", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerPlacement", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerOffset", required: false }] }], flip: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerFlip", required: false }] }], container: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerContainer", required: false }] }], scrollBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerScrollBehavior", required: false }] }], context: [{ type: i0.Input, args: [{ isSignal: true, alias: "ngpMenuTriggerContext", required: false }] }] } });
560
535
 
@@ -563,53 +538,39 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImpor
563
538
  */
564
539
  class NgpMenu {
565
540
  constructor() {
566
- /** Access the overlay. */
567
- this.overlay = injectOverlay();
568
- /** Access the menu trigger state */
569
- this.menuTrigger = injectMenuTriggerState();
570
- /** Access any parent menus */
571
- this.parentMenu = inject(NgpMenuToken, { optional: true, skipSelf: true });
572
- /** @internal Whether we should close submenus */
573
- this.closeSubmenus = new Subject();
541
+ this.state = ngpMenu({});
542
+ ngpRovingFocusGroup({ inherit: false });
543
+ ngpFocusTrap({});
574
544
  }
575
545
  /** @internal Close the menu and any parent menus */
576
546
  closeAllMenus(origin) {
577
- this.menuTrigger().hide(origin);
578
- this.parentMenu?.closeAllMenus(origin);
547
+ this.state.closeAllMenus(origin);
579
548
  }
580
549
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpMenu, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
581
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: NgpMenu, isStandalone: true, selector: "[ngpMenu]", host: { attributes: { "role": "menu", "data-overlay": "" }, 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()", "attr.data-placement": "overlay.finalPlacement()" } }, providers: [
550
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.9", type: NgpMenu, isStandalone: true, selector: "[ngpMenu]", providers: [
582
551
  // ensure we don't inherit the focus group from the parent menu if there is one
583
- provideRovingFocusGroup(NgpRovingFocusGroup, { inherit: false }),
584
- provideMenu(NgpMenu),
585
- ], exportAs: ["ngpMenu"], hostDirectives: [{ directive: i1.NgpRovingFocusGroup }, { directive: i2.NgpFocusTrap }], ngImport: i0 }); }
552
+ provideRovingFocusGroupState({ inherit: false }),
553
+ provideMenuState({ inherit: false }),
554
+ provideFocusTrapState(),
555
+ ], exportAs: ["ngpMenu"], ngImport: i0 }); }
586
556
  }
587
557
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.9", ngImport: i0, type: NgpMenu, decorators: [{
588
558
  type: Directive,
589
559
  args: [{
590
560
  selector: '[ngpMenu]',
591
561
  exportAs: 'ngpMenu',
592
- hostDirectives: [NgpRovingFocusGroup, NgpFocusTrap],
593
562
  providers: [
594
563
  // ensure we don't inherit the focus group from the parent menu if there is one
595
- provideRovingFocusGroup(NgpRovingFocusGroup, { inherit: false }),
596
- provideMenu(NgpMenu),
564
+ provideRovingFocusGroupState({ inherit: false }),
565
+ provideMenuState({ inherit: false }),
566
+ provideFocusTrapState(),
597
567
  ],
598
- host: {
599
- role: 'menu',
600
- '[style.left.px]': 'overlay.position().x',
601
- '[style.top.px]': 'overlay.position().y',
602
- '[style.--ngp-menu-trigger-width.px]': 'overlay.triggerWidth()',
603
- '[style.--ngp-menu-transform-origin]': 'overlay.transformOrigin()',
604
- '[attr.data-placement]': 'overlay.finalPlacement()',
605
- 'data-overlay': '',
606
- },
607
568
  }]
608
- }] });
569
+ }], ctorParameters: () => [] });
609
570
 
610
571
  /**
611
572
  * Generated bundle index. Do not edit.
612
573
  */
613
574
 
614
- export { NgpMenu, NgpMenuItem, NgpMenuToken, NgpMenuTrigger, NgpSubmenuTrigger, injectMenu, injectMenuTriggerState, injectSubmenuTriggerState, provideMenuConfig, provideMenuTriggerState, provideSubmenuTriggerState };
575
+ export { NgpMenu, NgpMenuItem, NgpMenuTrigger, NgpSubmenuTrigger, injectMenuItemState, injectMenuState, injectMenuTriggerState, injectSubmenuTriggerState, provideMenuConfig, provideMenuItemState, provideMenuState, provideMenuTriggerState, provideSubmenuTriggerState };
615
576
  //# sourceMappingURL=ng-primitives-menu.mjs.map