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
@@ -0,0 +1,783 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, Directive, computed, HostListener, booleanAttribute, inject, TemplateRef, Injector, signal, output, afterNextRender } from '@angular/core';
3
+ import * as i1 from 'ng-primitives/internal';
4
+ import { injectElementRef, NgpExitAnimation, setupInteractions, explicitEffect, provideExitAnimationManager } from 'ng-primitives/internal';
5
+ import { observeResize } from 'ng-primitives/resize';
6
+ import { uniqueId } from 'ng-primitives/utils';
7
+ import { createStateToken, createStateProvider, createStateInjector, createState } from 'ng-primitives/state';
8
+ import { createOverlay } from 'ng-primitives/portal';
9
+ import { activeDescendantManager } from 'ng-primitives/a11y';
10
+
11
+ /**
12
+ * The state token for the Combobox primitive.
13
+ */
14
+ const NgpComboboxStateToken = createStateToken('Combobox');
15
+ /**
16
+ * Provides the Combobox state.
17
+ */
18
+ const provideComboboxState = createStateProvider(NgpComboboxStateToken);
19
+ /**
20
+ * Injects the Combobox state.
21
+ */
22
+ const injectComboboxState = createStateInjector(NgpComboboxStateToken);
23
+ /**
24
+ * The Combobox state registration function.
25
+ */
26
+ const comboboxState = createState(NgpComboboxStateToken);
27
+
28
+ class NgpComboboxDropdown {
29
+ constructor() {
30
+ /** Access the combobox state. */
31
+ this.state = injectComboboxState();
32
+ /** The dimensions of the combobox. */
33
+ this.comboboxDimensions = observeResize(() => this.state().elementRef.nativeElement);
34
+ /** The dimensions of the combobox. */
35
+ this.inputDimensions = observeResize(() => this.state().input()?.elementRef.nativeElement);
36
+ /** Store the combobox button dimensions. */
37
+ this.buttonDimensions = observeResize(() => this.state().button()?.elementRef.nativeElement);
38
+ /**
39
+ * Access the element reference.
40
+ * @internal
41
+ */
42
+ this.elementRef = injectElementRef();
43
+ /** The id of the dropdown. */
44
+ this.id = input(uniqueId('ngp-combobox-dropdown'));
45
+ this.state().registerDropdown(this);
46
+ }
47
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxDropdown, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
48
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpComboboxDropdown, isStandalone: true, selector: "[ngpComboboxDropdown]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "listbox" }, properties: { "id": "id()", "style.left.px": "state().overlay()?.position()?.x", "style.top.px": "state().overlay()?.position()?.y", "style.--ngp-combobox-transform-origin": "state().overlay()?.transformOrigin()", "style.--ngp-combobox-width.px": "comboboxDimensions().width", "style.--ngp-combobox-input-width.px": "inputDimensions().width", "style.--ngp-combobox-button-width.px": "buttonDimensions().width" } }, exportAs: ["ngpComboboxDropdown"], hostDirectives: [{ directive: i1.NgpExitAnimation }], ngImport: i0 }); }
49
+ }
50
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxDropdown, decorators: [{
51
+ type: Directive,
52
+ args: [{
53
+ selector: '[ngpComboboxDropdown]',
54
+ exportAs: 'ngpComboboxDropdown',
55
+ hostDirectives: [NgpExitAnimation],
56
+ host: {
57
+ role: 'listbox',
58
+ '[id]': 'id()',
59
+ '[style.left.px]': 'state().overlay()?.position()?.x',
60
+ '[style.top.px]': 'state().overlay()?.position()?.y',
61
+ '[style.--ngp-combobox-transform-origin]': 'state().overlay()?.transformOrigin()',
62
+ '[style.--ngp-combobox-width.px]': 'comboboxDimensions().width',
63
+ '[style.--ngp-combobox-input-width.px]': 'inputDimensions().width',
64
+ '[style.--ngp-combobox-button-width.px]': 'buttonDimensions().width',
65
+ },
66
+ }]
67
+ }], ctorParameters: () => [] });
68
+
69
+ class NgpComboboxInput {
70
+ constructor() {
71
+ /** Access the combobox state. */
72
+ this.state = injectComboboxState();
73
+ /**
74
+ * Access the element reference.
75
+ * @internal
76
+ */
77
+ this.elementRef = injectElementRef();
78
+ /** The id of the input. */
79
+ this.id = input(uniqueId('ngp-combobox-input'));
80
+ /**
81
+ * Extract the string representation of the value.
82
+ */
83
+ this.displayWith = input((value) => {
84
+ if (typeof value === 'string') {
85
+ return value;
86
+ }
87
+ throw new Error('You must provide a displayWith function for non-string values');
88
+ });
89
+ /** The id of the dropdown. */
90
+ this.dropdownId = computed(() => this.state().dropdown()?.id());
91
+ /** The id of the active descendant. */
92
+ this.activeDescendant = computed(() => this.state().activeDescendantManager.activeDescendant());
93
+ /** Determine if the pointer was used to focus the input. */
94
+ this.pointerFocused = false;
95
+ setupInteractions({
96
+ focus: true,
97
+ hover: true,
98
+ press: true,
99
+ disabled: this.state().disabled,
100
+ });
101
+ this.state().registerInput(this);
102
+ }
103
+ /** Handle keydown events for accessibility. */
104
+ handleKeydown(event) {
105
+ switch (event.key) {
106
+ case 'ArrowDown':
107
+ if (this.state().open()) {
108
+ this.state().activateNextOption();
109
+ }
110
+ else {
111
+ this.state().openDropdown();
112
+ }
113
+ event.preventDefault();
114
+ break;
115
+ case 'ArrowUp':
116
+ if (this.state().open()) {
117
+ this.state().activatePreviousOption();
118
+ }
119
+ else {
120
+ this.state().openDropdown();
121
+ this.state().activeDescendantManager.last();
122
+ }
123
+ event.preventDefault();
124
+ break;
125
+ case 'Home':
126
+ if (this.state().open()) {
127
+ this.state().activeDescendantManager.first();
128
+ }
129
+ event.preventDefault();
130
+ break;
131
+ case 'End':
132
+ if (this.state().open()) {
133
+ this.state().activeDescendantManager.last();
134
+ }
135
+ event.preventDefault();
136
+ break;
137
+ case 'Enter':
138
+ if (this.state().open()) {
139
+ this.state().selectOption(this.state().activeDescendantManager.activeItem());
140
+ }
141
+ event.preventDefault();
142
+ break;
143
+ case 'Escape':
144
+ this.state().closeDropdown();
145
+ event.preventDefault();
146
+ break;
147
+ default:
148
+ // Ignore keys with length > 1 (e.g., 'Shift', 'ArrowLeft', 'Enter', etc.)
149
+ // Filter out control/meta key combos (e.g., Ctrl+C)
150
+ if (event.key.length > 1 || event.ctrlKey || event.metaKey || event.altKey) {
151
+ return;
152
+ }
153
+ // if this was a character key, we want to open the dropdown
154
+ this.state().openDropdown();
155
+ }
156
+ }
157
+ closeDropdown(event) {
158
+ const relatedTarget = event.relatedTarget;
159
+ // if the blur was caused by focus moving to the dropdown, don't close
160
+ if (relatedTarget &&
161
+ this.state().dropdown()?.elementRef.nativeElement.contains(relatedTarget)) {
162
+ return;
163
+ }
164
+ // if the blur was caused by focus moving to the button, don't close
165
+ if (relatedTarget && this.state().button()?.elementRef.nativeElement.contains(relatedTarget)) {
166
+ return;
167
+ }
168
+ this.state().closeDropdown();
169
+ event.preventDefault();
170
+ }
171
+ /**
172
+ * Focus the input field
173
+ * @internal
174
+ */
175
+ focus() {
176
+ this.elementRef.nativeElement.focus();
177
+ }
178
+ highlightText() {
179
+ if (this.pointerFocused) {
180
+ this.pointerFocused = false;
181
+ return;
182
+ }
183
+ // highlight the text in the input
184
+ this.elementRef.nativeElement.setSelectionRange(0, this.elementRef.nativeElement.value.length);
185
+ }
186
+ handlePointerDown() {
187
+ this.pointerFocused = true;
188
+ }
189
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxInput, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
190
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpComboboxInput, isStandalone: true, selector: "input[ngpComboboxInput]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, displayWith: { classPropertyName: "displayWith", publicName: "displayWith", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "combobox", "type": "text", "autocomplete": "off", "autocorrect": "off", "spellcheck": "false", "aria-haspopup": "listbox", "aria-autocomplete": "list" }, listeners: { "keydown": "handleKeydown($event)", "blur": "closeDropdown($event)", "focus": "highlightText($event)", "pointerdown": "handlePointerDown($event)" }, properties: { "id": "id()", "attr.aria-controls": "state().open() ? dropdownId() : undefined", "attr.aria-expanded": "state().open()", "attr.data-open": "state().open() ? \"\" : undefined", "attr.data-disabled": "state().disabled() ? \"\" : undefined", "attr.data-multiple": "state().multiple() ? \"\" : undefined", "attr.aria-activedescendant": "activeDescendant()", "disabled": "state().disabled()" } }, exportAs: ["ngpComboboxInput"], ngImport: i0 }); }
191
+ }
192
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxInput, decorators: [{
193
+ type: Directive,
194
+ args: [{
195
+ selector: 'input[ngpComboboxInput]',
196
+ exportAs: 'ngpComboboxInput',
197
+ host: {
198
+ role: 'combobox',
199
+ type: 'text',
200
+ autocomplete: 'off',
201
+ autocorrect: 'off',
202
+ spellcheck: 'false',
203
+ 'aria-haspopup': 'listbox',
204
+ 'aria-autocomplete': 'list',
205
+ '[id]': 'id()',
206
+ '[attr.aria-controls]': 'state().open() ? dropdownId() : undefined',
207
+ '[attr.aria-expanded]': 'state().open()',
208
+ '[attr.data-open]': 'state().open() ? "" : undefined',
209
+ '[attr.data-disabled]': 'state().disabled() ? "" : undefined',
210
+ '[attr.data-multiple]': 'state().multiple() ? "" : undefined',
211
+ '[attr.aria-activedescendant]': 'activeDescendant()',
212
+ '[disabled]': 'state().disabled()',
213
+ },
214
+ }]
215
+ }], ctorParameters: () => [], propDecorators: { handleKeydown: [{
216
+ type: HostListener,
217
+ args: ['keydown', ['$event']]
218
+ }], closeDropdown: [{
219
+ type: HostListener,
220
+ args: ['blur', ['$event']]
221
+ }], highlightText: [{
222
+ type: HostListener,
223
+ args: ['focus', ['$event']]
224
+ }], handlePointerDown: [{
225
+ type: HostListener,
226
+ args: ['pointerdown', ['$event']]
227
+ }] } });
228
+
229
+ class NgpComboboxOption {
230
+ constructor() {
231
+ /** Access the combobox state. */
232
+ this.state = injectComboboxState();
233
+ /**
234
+ * The element reference of the option.
235
+ * @internal
236
+ */
237
+ this.elementRef = injectElementRef();
238
+ /** The id of the option. */
239
+ this.id = input(uniqueId('ngp-combobox-option'));
240
+ /** The value of the option. */
241
+ this.value = input(undefined, {
242
+ alias: 'ngpComboboxOptionValue',
243
+ });
244
+ /** The disabled state of the option. */
245
+ this.disabled = input(false, {
246
+ alias: 'ngpComboboxOptionDisabled',
247
+ transform: booleanAttribute,
248
+ });
249
+ /**
250
+ * Whether this option is the active descendant.
251
+ * @internal
252
+ */
253
+ this.active = computed(() => this.state().activeDescendantManager.activeDescendant() === this.id());
254
+ /** Whether this option is selected. */
255
+ this.selected = computed(() => {
256
+ const value = this.value();
257
+ if (!value) {
258
+ return false;
259
+ }
260
+ if (this.state().multiple()) {
261
+ return (Array.isArray(value) && value.some(v => this.state().compareWith()(v, this.state().value())));
262
+ }
263
+ return this.state().compareWith()(value, this.state().value());
264
+ });
265
+ this.state().registerOption(this);
266
+ setupInteractions({
267
+ hover: true,
268
+ press: true,
269
+ disabled: this.disabled,
270
+ });
271
+ }
272
+ ngOnInit() {
273
+ if (this.value() === undefined) {
274
+ throw new Error('ngpComboboxOption: The value input is required. Please provide a value for the option.');
275
+ }
276
+ }
277
+ ngOnDestroy() {
278
+ this.state().unregisterOption(this);
279
+ }
280
+ /**
281
+ * Select the option.
282
+ * @internal
283
+ */
284
+ select() {
285
+ if (this.disabled()) {
286
+ return;
287
+ }
288
+ this.state().toggleOption(this);
289
+ }
290
+ /**
291
+ * Scroll the option into view.
292
+ * @internal
293
+ */
294
+ scrollIntoView() {
295
+ this.elementRef.nativeElement.scrollIntoView({ block: 'nearest' });
296
+ }
297
+ /**
298
+ * Whenever the pointer enters the option, activate it.
299
+ * @internal
300
+ */
301
+ onPointerEnter() {
302
+ this.state().activeDescendantManager.activate(this);
303
+ }
304
+ /**
305
+ * Whenever the pointer leaves the option, deactivate it.
306
+ * @internal
307
+ */
308
+ onPointerLeave() {
309
+ this.state().activeDescendantManager.activate(undefined);
310
+ }
311
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxOption, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
312
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpComboboxOption, isStandalone: true, selector: "[ngpComboboxOption]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "ngpComboboxOptionValue", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpComboboxOptionDisabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "option" }, listeners: { "click": "select()", "pointerenter": "onPointerEnter()", "pointerleave": "onPointerLeave()" }, properties: { "id": "id()", "attr.tabindex": "-1", "attr.aria-selected": "selected() ? \"true\" : undefined", "attr.data-selected": "selected() ? \"\" : undefined", "attr.data-active": "active() ? \"\" : undefined", "attr.data-disabled": "disabled() ? \"\" : undefined" } }, exportAs: ["ngpComboboxOption"], ngImport: i0 }); }
313
+ }
314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxOption, decorators: [{
315
+ type: Directive,
316
+ args: [{
317
+ selector: '[ngpComboboxOption]',
318
+ exportAs: 'ngpComboboxOption',
319
+ host: {
320
+ role: 'option',
321
+ '[id]': 'id()',
322
+ '[attr.tabindex]': '-1',
323
+ '[attr.aria-selected]': 'selected() ? "true" : undefined',
324
+ '[attr.data-selected]': 'selected() ? "" : undefined',
325
+ '[attr.data-active]': 'active() ? "" : undefined',
326
+ '[attr.data-disabled]': 'disabled() ? "" : undefined',
327
+ '(click)': 'select()',
328
+ },
329
+ }]
330
+ }], ctorParameters: () => [], propDecorators: { onPointerEnter: [{
331
+ type: HostListener,
332
+ args: ['pointerenter']
333
+ }], onPointerLeave: [{
334
+ type: HostListener,
335
+ args: ['pointerleave']
336
+ }] } });
337
+
338
+ class NgpComboboxPortal {
339
+ constructor() {
340
+ /** Access the combobox state. */
341
+ this.state = injectComboboxState();
342
+ /** Access the template reference. */
343
+ this.templateRef = inject(TemplateRef);
344
+ /** Access the injector. */
345
+ this.injector = inject(Injector);
346
+ /**
347
+ * The overlay that manages the popover
348
+ * @internal
349
+ */
350
+ this.overlay = signal(null);
351
+ this.state().registerPortal(this);
352
+ }
353
+ /** Cleanup the portal. */
354
+ ngOnDestroy() {
355
+ this.overlay()?.destroy();
356
+ }
357
+ /**
358
+ * Attach the portal.
359
+ * @internal
360
+ */
361
+ show() {
362
+ // Create the overlay if it doesn't exist yet
363
+ if (!this.overlay()) {
364
+ this.createOverlay();
365
+ }
366
+ // Show the overlay
367
+ return this.overlay().show();
368
+ }
369
+ /**
370
+ * Detach the portal.
371
+ * @internal
372
+ */
373
+ async detach() {
374
+ this.overlay()?.hide();
375
+ }
376
+ /**
377
+ * Create the overlay that will contain the dropdown
378
+ */
379
+ createOverlay() {
380
+ // Create config for the overlay
381
+ const config = {
382
+ content: this.templateRef,
383
+ triggerElement: this.state().elementRef.nativeElement,
384
+ injector: this.injector,
385
+ placement: this.state().placement(),
386
+ closeOnOutsideClick: true,
387
+ closeOnEscape: true,
388
+ restoreFocus: false,
389
+ scrollBehaviour: 'reposition',
390
+ };
391
+ this.overlay.set(createOverlay(config));
392
+ }
393
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxPortal, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
394
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.11", type: NgpComboboxPortal, isStandalone: true, selector: "[ngpComboboxPortal]", exportAs: ["ngpComboboxPortal"], ngImport: i0 }); }
395
+ }
396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxPortal, decorators: [{
397
+ type: Directive,
398
+ args: [{
399
+ selector: '[ngpComboboxPortal]',
400
+ exportAs: 'ngpComboboxPortal',
401
+ }]
402
+ }], ctorParameters: () => [] });
403
+
404
+ class NgpCombobox {
405
+ constructor() {
406
+ /** @internal Access the combobox element. */
407
+ this.elementRef = injectElementRef();
408
+ /** Access the injector. */
409
+ this.injector = inject(Injector);
410
+ /** The value of the combobox. */
411
+ this.value = input(undefined, {
412
+ alias: 'ngpComboboxValue',
413
+ });
414
+ /** Event emitted when the value changes. */
415
+ this.valueChange = output({
416
+ alias: 'ngpComboboxValueChange',
417
+ });
418
+ /** Whether the combobox is multiple selection. */
419
+ this.multiple = input(false, {
420
+ alias: 'ngpComboboxMultiple',
421
+ transform: booleanAttribute,
422
+ });
423
+ /** Whether the combobox is disabled. */
424
+ this.disabled = input(false, {
425
+ alias: 'ngpComboboxDisabled',
426
+ transform: booleanAttribute,
427
+ });
428
+ /** Emit when the dropdown open state changes. */
429
+ this.openChange = output({
430
+ alias: 'ngpComboboxOpenChange',
431
+ });
432
+ /** The comparator function used to compare options. */
433
+ this.compareWith = input(Object.is, {
434
+ alias: 'ngpComboboxCompareWith',
435
+ });
436
+ /** The position of the dropdown. */
437
+ this.placement = input('bottom', {
438
+ alias: 'ngpComboboxDropdownPlacement',
439
+ });
440
+ /**
441
+ * Store the combobox input
442
+ * @internal
443
+ */
444
+ this.input = signal(undefined);
445
+ /**
446
+ * Store the combobox button.
447
+ * @internal
448
+ */
449
+ this.button = signal(undefined);
450
+ /**
451
+ * Store the combobox portal.
452
+ * @internal
453
+ */
454
+ this.portal = signal(undefined);
455
+ /**
456
+ * Store the combobox dropdown.
457
+ * @internal
458
+ */
459
+ this.dropdown = signal(undefined);
460
+ /**
461
+ * Store the combobox options.
462
+ * @internal
463
+ */
464
+ this.options = signal([]);
465
+ /**
466
+ * Access the overlay
467
+ * @internal
468
+ */
469
+ this.overlay = computed(() => this.portal()?.overlay());
470
+ /**
471
+ * The open state of the combobox.
472
+ * @internal
473
+ */
474
+ this.open = computed(() => this.overlay()?.isOpen() ?? false);
475
+ /**
476
+ * The active key descendant manager.
477
+ * @internal
478
+ */
479
+ this.activeDescendantManager = activeDescendantManager({
480
+ // we must wrap the signal in a computed to ensure it is not used before it is defined
481
+ disabled: computed(() => this.state.disabled()),
482
+ items: this.options,
483
+ });
484
+ /** The state of the combobox. */
485
+ this.state = comboboxState(this);
486
+ // any time the active descendant changes, ensure we scroll it into view
487
+ explicitEffect([this.activeDescendantManager.activeItem], ([option]) =>
488
+ // perform after next render to ensure the DOM is updated
489
+ // e.g. the dropdown is open before the option is scrolled into view
490
+ afterNextRender({ write: () => option?.scrollIntoView?.() }, { injector: this.injector }));
491
+ }
492
+ /**
493
+ * Open the dropdown.
494
+ * @internal
495
+ */
496
+ async openDropdown() {
497
+ if (this.state.disabled() || this.open()) {
498
+ return;
499
+ }
500
+ await this.portal()?.show();
501
+ // if there is a selected option(s), set the active descendant to the first selected option
502
+ const selectedOption = this.options().find(option => this.isOptionSelected(option));
503
+ // if there is no selected option, set the active descendant to the first option
504
+ const targetOption = selectedOption ?? this.options()[0];
505
+ // if there is no target option, do nothing
506
+ if (!targetOption) {
507
+ return;
508
+ }
509
+ // activate the selected option or the first option
510
+ this.activeDescendantManager.activate(targetOption);
511
+ }
512
+ /**
513
+ * Close the dropdown.
514
+ * @internal
515
+ */
516
+ closeDropdown() {
517
+ if (!this.open()) {
518
+ return;
519
+ }
520
+ this.openChange.emit(false);
521
+ this.portal()?.detach();
522
+ // clear the active descendant
523
+ this.activeDescendantManager.reset();
524
+ }
525
+ /**
526
+ * Toggle the dropdown.
527
+ * @internal
528
+ */
529
+ toggleDropdown() {
530
+ if (this.open()) {
531
+ this.closeDropdown();
532
+ }
533
+ else {
534
+ this.openDropdown();
535
+ }
536
+ }
537
+ /**
538
+ * Select an option.
539
+ * @param option The option to select.
540
+ * @internal
541
+ */
542
+ selectOption(option) {
543
+ if (this.state.disabled()) {
544
+ return;
545
+ }
546
+ if (this.state.multiple()) {
547
+ // if the option is already selected, do nothing
548
+ if (this.isOptionSelected(option)) {
549
+ return;
550
+ }
551
+ const value = [...this.state.value(), option.value()];
552
+ // add the option to the value
553
+ this.state.value.set(value);
554
+ this.valueChange.emit(value);
555
+ }
556
+ else {
557
+ this.state.value.set(option.value());
558
+ this.valueChange.emit(option.value());
559
+ // close the dropdown on single selection
560
+ this.closeDropdown();
561
+ }
562
+ }
563
+ /**
564
+ * Deselect an option.
565
+ * @param option The option to deselect.
566
+ * @internal
567
+ */
568
+ deselectOption(option) {
569
+ // if the combobox is disabled or the option is not selected, do nothing
570
+ // if the combobox is single selection, we don't allow deselecting
571
+ if (this.state.disabled() || !this.isOptionSelected(option) || !this.state.multiple()) {
572
+ return;
573
+ }
574
+ const values = this.state.value() ?? [];
575
+ const newValue = values.filter(v => !this.state.compareWith()(v, option.value()));
576
+ // remove the option from the value
577
+ this.state.value.set(newValue);
578
+ this.valueChange.emit(newValue);
579
+ }
580
+ /**
581
+ * Toggle the selection of an option.
582
+ * @param option The option to toggle.
583
+ * @internal
584
+ */
585
+ toggleOption(option) {
586
+ if (this.state.disabled()) {
587
+ return;
588
+ }
589
+ if (this.isOptionSelected(option)) {
590
+ this.deselectOption(option);
591
+ }
592
+ else {
593
+ this.selectOption(option);
594
+ }
595
+ }
596
+ /**
597
+ * Determine if an option is selected.
598
+ * @param option The option to check.
599
+ * @internal
600
+ */
601
+ isOptionSelected(option) {
602
+ if (this.state.disabled()) {
603
+ return false;
604
+ }
605
+ const value = this.state.value();
606
+ if (!value) {
607
+ return false;
608
+ }
609
+ if (this.state.multiple()) {
610
+ return value && value.some(v => this.state.compareWith()(option.value(), v));
611
+ }
612
+ return this.state.compareWith()(option.value(), value);
613
+ }
614
+ /**
615
+ * Activate the next option in the list if there is one.
616
+ * If there is no option currently active, activate the selected option or the first option.
617
+ * @internal
618
+ */
619
+ activateNextOption() {
620
+ if (this.state.disabled()) {
621
+ return;
622
+ }
623
+ const options = this.options();
624
+ // if there are no options, do nothing
625
+ if (options.length === 0) {
626
+ return;
627
+ }
628
+ // if there is no active option, activate the first option
629
+ if (!this.activeDescendantManager.activeItem()) {
630
+ const selectedOption = options.find(option => this.isOptionSelected(option));
631
+ // if there is a selected option(s), set the active descendant to the first selected option
632
+ const targetOption = selectedOption ?? options[0];
633
+ this.activeDescendantManager.activate(targetOption);
634
+ return;
635
+ }
636
+ // otherwise activate the next option
637
+ this.activeDescendantManager.next();
638
+ }
639
+ /**
640
+ * Activate the previous option in the list if there is one.
641
+ * @internal
642
+ */
643
+ activatePreviousOption() {
644
+ if (this.state.disabled()) {
645
+ return;
646
+ }
647
+ const options = this.options();
648
+ // if there are no options, do nothing
649
+ if (options.length === 0) {
650
+ return;
651
+ }
652
+ // if there is no active option, activate the last option
653
+ if (!this.activeDescendantManager.activeItem()) {
654
+ const selectedOption = options.find(option => this.isOptionSelected(option));
655
+ // if there is a selected option(s), set the active descendant to the first selected option
656
+ const targetOption = selectedOption ?? options[options.length - 1];
657
+ this.activeDescendantManager.activate(targetOption);
658
+ return;
659
+ }
660
+ // otherwise activate the previous option
661
+ this.activeDescendantManager.previous();
662
+ }
663
+ /**
664
+ * Register the dropdown portal with the combobox.
665
+ * @param portal The dropdown portal.
666
+ * @internal
667
+ */
668
+ registerPortal(portal) {
669
+ this.portal.set(portal);
670
+ }
671
+ /**
672
+ * Register the combobox input with the combobox.
673
+ * @param input The combobox input.
674
+ * @internal
675
+ */
676
+ registerInput(input) {
677
+ this.input.set(input);
678
+ }
679
+ /**
680
+ * Register the combobox button with the combobox.
681
+ * @param button The combobox button.
682
+ * @internal
683
+ */
684
+ registerButton(button) {
685
+ this.button.set(button);
686
+ }
687
+ /**
688
+ * Register the dropdown with the combobox.
689
+ * @param dropdown The dropdown to register.
690
+ * @internal
691
+ */
692
+ registerDropdown(dropdown) {
693
+ this.dropdown.set(dropdown);
694
+ }
695
+ /**
696
+ * Register an option with the combobox.
697
+ * @param option The option to register.
698
+ * @internal
699
+ */
700
+ registerOption(option) {
701
+ this.options.update(options => [...options, option]);
702
+ }
703
+ /**
704
+ * Unregister an option from the combobox.
705
+ * @param option The option to unregister.
706
+ * @internal
707
+ */
708
+ unregisterOption(option) {
709
+ this.options.update(options => options.filter(o => o !== option));
710
+ }
711
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpCombobox, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
712
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpCombobox, isStandalone: true, selector: "[ngpCombobox]", inputs: { value: { classPropertyName: "value", publicName: "ngpComboboxValue", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "ngpComboboxMultiple", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "ngpComboboxDisabled", isSignal: true, isRequired: false, transformFunction: null }, compareWith: { classPropertyName: "compareWith", publicName: "ngpComboboxCompareWith", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "ngpComboboxDropdownPlacement", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { valueChange: "ngpComboboxValueChange", openChange: "ngpComboboxOpenChange" }, host: { properties: { "attr.data-open": "state.open() ? \"\" : undefined", "attr.data-disabled": "state.disabled() ? \"\" : undefined", "attr.data-multiple": "state.multiple() ? \"\" : undefined" } }, providers: [provideComboboxState(), provideExitAnimationManager()], exportAs: ["ngpCombobox"], ngImport: i0 }); }
713
+ }
714
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpCombobox, decorators: [{
715
+ type: Directive,
716
+ args: [{
717
+ selector: '[ngpCombobox]',
718
+ exportAs: 'ngpCombobox',
719
+ providers: [provideComboboxState(), provideExitAnimationManager()],
720
+ host: {
721
+ '[attr.data-open]': 'state.open() ? "" : undefined',
722
+ '[attr.data-disabled]': 'state.disabled() ? "" : undefined',
723
+ '[attr.data-multiple]': 'state.multiple() ? "" : undefined',
724
+ },
725
+ }]
726
+ }], ctorParameters: () => [] });
727
+
728
+ class NgpComboboxButton {
729
+ constructor() {
730
+ /** Access the combobox state. */
731
+ this.state = injectComboboxState();
732
+ /**
733
+ * Access the element reference.
734
+ * @internal
735
+ */
736
+ this.elementRef = injectElementRef();
737
+ /** The id of the button. */
738
+ this.id = input(uniqueId('ngp-combobox-button'));
739
+ /** The id of the dropdown. */
740
+ this.dropdownId = computed(() => this.state().dropdown()?.id());
741
+ setupInteractions({
742
+ hover: true,
743
+ press: true,
744
+ disabled: this.state().disabled,
745
+ });
746
+ this.state().registerButton(this);
747
+ }
748
+ toggleDropdown() {
749
+ this.state().toggleDropdown();
750
+ this.state().input()?.focus();
751
+ }
752
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxButton, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
753
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "19.2.11", type: NgpComboboxButton, isStandalone: true, selector: "[ngpComboboxButton]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button", "tabindex": "-1", "aria-haspopup": "listbox" }, listeners: { "click": "toggleDropdown()" }, properties: { "id": "id()", "attr.aria-controls": "dropdownId()", "attr.aria-expanded": "state().open()", "attr.data-open": "state().open() ? \"\" : undefined", "attr.data-disabled": "state().disabled() ? \"\" : undefined", "attr.data-multiple": "state().multiple() ? \"\" : undefined", "disabled": "state().disabled()" } }, exportAs: ["ngpComboboxButton"], ngImport: i0 }); }
754
+ }
755
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.11", ngImport: i0, type: NgpComboboxButton, decorators: [{
756
+ type: Directive,
757
+ args: [{
758
+ selector: '[ngpComboboxButton]',
759
+ exportAs: 'ngpComboboxButton',
760
+ host: {
761
+ type: 'button',
762
+ tabindex: '-1',
763
+ 'aria-haspopup': 'listbox',
764
+ '[id]': 'id()',
765
+ '[attr.aria-controls]': 'dropdownId()',
766
+ '[attr.aria-expanded]': 'state().open()',
767
+ '[attr.data-open]': 'state().open() ? "" : undefined',
768
+ '[attr.data-disabled]': 'state().disabled() ? "" : undefined',
769
+ '[attr.data-multiple]': 'state().multiple() ? "" : undefined',
770
+ '[disabled]': 'state().disabled()',
771
+ },
772
+ }]
773
+ }], ctorParameters: () => [], propDecorators: { toggleDropdown: [{
774
+ type: HostListener,
775
+ args: ['click']
776
+ }] } });
777
+
778
+ /**
779
+ * Generated bundle index. Do not edit.
780
+ */
781
+
782
+ export { NgpCombobox, NgpComboboxButton, NgpComboboxDropdown, NgpComboboxInput, NgpComboboxOption, NgpComboboxPortal, injectComboboxState, provideComboboxState };
783
+ //# sourceMappingURL=ng-primitives-combobox.mjs.map