ngx-com 0.0.1 → 0.0.4

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 (82) hide show
  1. package/fesm2022/ngx-com-components-avatar.mjs +772 -0
  2. package/fesm2022/ngx-com-components-avatar.mjs.map +1 -0
  3. package/fesm2022/ngx-com-components-badge.mjs +138 -0
  4. package/fesm2022/ngx-com-components-badge.mjs.map +1 -0
  5. package/fesm2022/ngx-com-components-button.mjs +146 -0
  6. package/fesm2022/ngx-com-components-button.mjs.map +1 -0
  7. package/fesm2022/ngx-com-components-calendar.mjs +5046 -0
  8. package/fesm2022/ngx-com-components-calendar.mjs.map +1 -0
  9. package/fesm2022/ngx-com-components-card.mjs +590 -0
  10. package/fesm2022/ngx-com-components-card.mjs.map +1 -0
  11. package/fesm2022/ngx-com-components-checkbox.mjs +344 -0
  12. package/fesm2022/ngx-com-components-checkbox.mjs.map +1 -0
  13. package/fesm2022/ngx-com-components-collapsible.mjs +612 -0
  14. package/fesm2022/ngx-com-components-collapsible.mjs.map +1 -0
  15. package/fesm2022/ngx-com-components-confirm.mjs +562 -0
  16. package/fesm2022/ngx-com-components-confirm.mjs.map +1 -0
  17. package/fesm2022/ngx-com-components-dropdown-testing.mjs +255 -0
  18. package/fesm2022/ngx-com-components-dropdown-testing.mjs.map +1 -0
  19. package/fesm2022/ngx-com-components-dropdown.mjs +2692 -0
  20. package/fesm2022/ngx-com-components-dropdown.mjs.map +1 -0
  21. package/fesm2022/ngx-com-components-empty-state.mjs +382 -0
  22. package/fesm2022/ngx-com-components-empty-state.mjs.map +1 -0
  23. package/fesm2022/ngx-com-components-form-field.mjs +924 -0
  24. package/fesm2022/ngx-com-components-form-field.mjs.map +1 -0
  25. package/fesm2022/ngx-com-components-icon.mjs +183 -0
  26. package/fesm2022/ngx-com-components-icon.mjs.map +1 -0
  27. package/fesm2022/ngx-com-components-item.mjs +578 -0
  28. package/fesm2022/ngx-com-components-item.mjs.map +1 -0
  29. package/fesm2022/ngx-com-components-menu.mjs +1200 -0
  30. package/fesm2022/ngx-com-components-menu.mjs.map +1 -0
  31. package/fesm2022/ngx-com-components-paginator.mjs +823 -0
  32. package/fesm2022/ngx-com-components-paginator.mjs.map +1 -0
  33. package/fesm2022/ngx-com-components-popover.mjs +901 -0
  34. package/fesm2022/ngx-com-components-popover.mjs.map +1 -0
  35. package/fesm2022/ngx-com-components-radio.mjs +621 -0
  36. package/fesm2022/ngx-com-components-radio.mjs.map +1 -0
  37. package/fesm2022/ngx-com-components-segmented-control.mjs +538 -0
  38. package/fesm2022/ngx-com-components-segmented-control.mjs.map +1 -0
  39. package/fesm2022/ngx-com-components-sort.mjs +368 -0
  40. package/fesm2022/ngx-com-components-sort.mjs.map +1 -0
  41. package/fesm2022/ngx-com-components-spinner.mjs +189 -0
  42. package/fesm2022/ngx-com-components-spinner.mjs.map +1 -0
  43. package/fesm2022/ngx-com-components-tabs.mjs +1522 -0
  44. package/fesm2022/ngx-com-components-tabs.mjs.map +1 -0
  45. package/fesm2022/ngx-com-components-tooltip.mjs +625 -0
  46. package/fesm2022/ngx-com-components-tooltip.mjs.map +1 -0
  47. package/fesm2022/ngx-com-components.mjs +17 -0
  48. package/fesm2022/ngx-com-components.mjs.map +1 -0
  49. package/fesm2022/ngx-com-tokens.mjs +12 -0
  50. package/fesm2022/ngx-com-tokens.mjs.map +1 -0
  51. package/fesm2022/ngx-com-utils.mjs +601 -0
  52. package/fesm2022/ngx-com-utils.mjs.map +1 -0
  53. package/fesm2022/ngx-com.mjs +9 -23
  54. package/fesm2022/ngx-com.mjs.map +1 -1
  55. package/package.json +105 -1
  56. package/types/ngx-com-components-avatar.d.ts +409 -0
  57. package/types/ngx-com-components-badge.d.ts +97 -0
  58. package/types/ngx-com-components-button.d.ts +69 -0
  59. package/types/ngx-com-components-calendar.d.ts +1665 -0
  60. package/types/ngx-com-components-card.d.ts +373 -0
  61. package/types/ngx-com-components-checkbox.d.ts +116 -0
  62. package/types/ngx-com-components-collapsible.d.ts +379 -0
  63. package/types/ngx-com-components-confirm.d.ts +160 -0
  64. package/types/ngx-com-components-dropdown-testing.d.ts +116 -0
  65. package/types/ngx-com-components-dropdown.d.ts +938 -0
  66. package/types/ngx-com-components-empty-state.d.ts +269 -0
  67. package/types/ngx-com-components-form-field.d.ts +531 -0
  68. package/types/ngx-com-components-icon.d.ts +94 -0
  69. package/types/ngx-com-components-item.d.ts +336 -0
  70. package/types/ngx-com-components-menu.d.ts +479 -0
  71. package/types/ngx-com-components-paginator.d.ts +265 -0
  72. package/types/ngx-com-components-popover.d.ts +309 -0
  73. package/types/ngx-com-components-radio.d.ts +258 -0
  74. package/types/ngx-com-components-segmented-control.d.ts +274 -0
  75. package/types/ngx-com-components-sort.d.ts +133 -0
  76. package/types/ngx-com-components-spinner.d.ts +120 -0
  77. package/types/ngx-com-components-tabs.d.ts +396 -0
  78. package/types/ngx-com-components-tooltip.d.ts +200 -0
  79. package/types/ngx-com-components.d.ts +12 -0
  80. package/types/ngx-com-tokens.d.ts +7 -0
  81. package/types/ngx-com-utils.d.ts +424 -0
  82. package/types/ngx-com.d.ts +10 -7
@@ -0,0 +1,901 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, computed, ChangeDetectionStrategy, Component, signal, InjectionToken, inject, ElementRef, ViewContainerRef, Injector, DestroyRef, booleanAttribute, model, output, effect, TemplateRef, Directive } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { DOCUMENT } from '@angular/common';
5
+ import { Overlay } from '@angular/cdk/overlay';
6
+ import { CdkPortalOutlet, ComponentPortal, TemplatePortal } from '@angular/cdk/portal';
7
+ import { FocusTrapFactory } from '@angular/cdk/a11y';
8
+ import { filter } from 'rxjs/operators';
9
+ import { cva } from 'class-variance-authority';
10
+ import { mergeClasses } from 'ngx-com/utils';
11
+
12
+ // ─── Popover Panel Variants ───
13
+ /**
14
+ * Popover panel styling variants.
15
+ *
16
+ * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border`, `--shadow-lg`, `--radius-popover`, `--radius-overlay`
17
+ */
18
+ const popoverPanelVariants = cva(['relative', 'bg-popover text-popover-foreground', 'border border-border', 'shadow-lg', 'overflow-hidden'], {
19
+ variants: {
20
+ variant: {
21
+ default: 'rounded-popover p-4 min-w-48 max-w-sm',
22
+ compact: 'rounded-overlay p-2 min-w-32 max-w-xs',
23
+ wide: 'rounded-popover p-5 min-w-64 max-w-lg',
24
+ flush: 'rounded-popover p-0',
25
+ },
26
+ },
27
+ defaultVariants: {
28
+ variant: 'default',
29
+ },
30
+ });
31
+ // ─── Popover Arrow Variants ───
32
+ /**
33
+ * Popover arrow positioning variants.
34
+ * The arrow is positioned on the edge of the popover pointing toward the trigger.
35
+ *
36
+ * @tokens `--color-popover`, `--color-border`
37
+ */
38
+ const popoverArrowVariants = cva('absolute z-10 text-popover', {
39
+ variants: {
40
+ side: {
41
+ top: 'left-1/2 top-full -translate-x-1/2 -translate-y-px rotate-180',
42
+ bottom: 'left-1/2 bottom-full -translate-x-1/2 translate-y-px',
43
+ left: 'top-1/2 right-0 -translate-y-1/2 translate-x-[11px] rotate-90',
44
+ right: 'top-1/2 left-0 -translate-y-1/2 -translate-x-[11px] -rotate-90',
45
+ },
46
+ },
47
+ defaultVariants: {
48
+ side: 'bottom',
49
+ },
50
+ });
51
+
52
+ let popoverIdCounter = 0;
53
+ /**
54
+ * Generate a unique ID for a popover instance.
55
+ */
56
+ function generatePopoverId() {
57
+ return `popover-${++popoverIdCounter}`;
58
+ }
59
+
60
+ /** Alignment offset classes for arrow positioning. */
61
+ const ALIGNMENT_OFFSETS = {
62
+ top: { start: 'left-4 -translate-x-0', center: '', end: 'left-auto right-4 translate-x-0' },
63
+ bottom: { start: 'left-4 -translate-x-0', center: '', end: 'left-auto right-4 translate-x-0' },
64
+ left: { start: 'top-4 -translate-y-0', center: '', end: 'top-auto bottom-4 translate-y-0' },
65
+ right: { start: 'top-4 -translate-y-0', center: '', end: 'top-auto bottom-4 translate-y-0' },
66
+ };
67
+ /**
68
+ * Internal arrow component rendered inside the popover panel.
69
+ * Points toward the trigger element based on the active position.
70
+ *
71
+ * @internal Not exported in public API
72
+ *
73
+ * @tokens `--color-popover`, `--color-border`
74
+ */
75
+ class PopoverArrowComponent {
76
+ /** Which side of the popover the arrow is on. */
77
+ side = input('top', ...(ngDevMode ? [{ debugName: "side" }] : []));
78
+ /** Alignment along the edge (for offset positioning). */
79
+ alignment = input('center', ...(ngDevMode ? [{ debugName: "alignment" }] : []));
80
+ /** Computed CSS classes for the arrow. */
81
+ arrowClasses = computed(() => mergeClasses(popoverArrowVariants({ side: this.side() }), ALIGNMENT_OFFSETS[this.side()][this.alignment()]), ...(ngDevMode ? [{ debugName: "arrowClasses" }] : []));
82
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverArrowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
83
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.0", type: PopoverArrowComponent, isStandalone: true, selector: "com-popover-arrow", inputs: { side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, alignment: { classPropertyName: "alignment", publicName: "alignment", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "pointer-events-none" }, ngImport: i0, template: `
84
+ <svg
85
+ [class]="arrowClasses()"
86
+ width="16"
87
+ height="8"
88
+ viewBox="0 0 16 8"
89
+ fill="none"
90
+ xmlns="http://www.w3.org/2000/svg"
91
+ aria-hidden="true"
92
+ >
93
+ <!-- Border stroke (rendered underneath) -->
94
+ <path
95
+ d="M0 8L8 1L16 8"
96
+ fill="none"
97
+ stroke="currentColor"
98
+ stroke-width="1"
99
+ class="text-border"
100
+ />
101
+ <!-- Fill (covers the stroke at the base) -->
102
+ <path d="M1 8L8 1.5L15 8" fill="currentColor" class="text-popover" />
103
+ </svg>
104
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
105
+ }
106
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverArrowComponent, decorators: [{
107
+ type: Component,
108
+ args: [{
109
+ selector: 'com-popover-arrow',
110
+ template: `
111
+ <svg
112
+ [class]="arrowClasses()"
113
+ width="16"
114
+ height="8"
115
+ viewBox="0 0 16 8"
116
+ fill="none"
117
+ xmlns="http://www.w3.org/2000/svg"
118
+ aria-hidden="true"
119
+ >
120
+ <!-- Border stroke (rendered underneath) -->
121
+ <path
122
+ d="M0 8L8 1L16 8"
123
+ fill="none"
124
+ stroke="currentColor"
125
+ stroke-width="1"
126
+ class="text-border"
127
+ />
128
+ <!-- Fill (covers the stroke at the base) -->
129
+ <path d="M1 8L8 1.5L15 8" fill="currentColor" class="text-popover" />
130
+ </svg>
131
+ `,
132
+ changeDetection: ChangeDetectionStrategy.OnPush,
133
+ host: {
134
+ class: 'pointer-events-none',
135
+ },
136
+ }]
137
+ }], propDecorators: { side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], alignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "alignment", required: false }] }] } });
138
+
139
+ /**
140
+ * Internal content wrapper component for the popover.
141
+ * Renders the panel styling, arrow, and consumer content.
142
+ *
143
+ * @internal Not exported in public API
144
+ *
145
+ * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border`, `--shadow-lg`, `--radius-popover`, `--radius-overlay`
146
+ */
147
+ class PopoverContentComponent {
148
+ /** The portal containing consumer content to render. */
149
+ contentPortal = signal(null, ...(ngDevMode ? [{ debugName: "contentPortal" }] : []));
150
+ /** Size/padding variant for the panel. */
151
+ variant = signal('default', ...(ngDevMode ? [{ debugName: "variant" }] : []));
152
+ /** Whether to render the arrow. */
153
+ showArrow = signal(true, ...(ngDevMode ? [{ debugName: "showArrow" }] : []));
154
+ /** Which side the popover is on relative to the trigger. */
155
+ activeSide = signal('top', ...(ngDevMode ? [{ debugName: "activeSide" }] : []));
156
+ /** Alignment along the cross-axis. */
157
+ activeAlignment = signal('center', ...(ngDevMode ? [{ debugName: "activeAlignment" }] : []));
158
+ /** Unique ID for accessibility. */
159
+ popoverId = signal('', ...(ngDevMode ? [{ debugName: "popoverId" }] : []));
160
+ /** Optional accessibility label. */
161
+ ariaLabel = signal(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
162
+ /** Animation state for enter/leave. */
163
+ animationState = signal('open', ...(ngDevMode ? [{ debugName: "animationState" }] : []));
164
+ /** Additional CSS classes for the panel. */
165
+ panelClass = signal('', ...(ngDevMode ? [{ debugName: "panelClass" }] : []));
166
+ /** Computed CSS classes for the panel. */
167
+ panelClasses = computed(() => mergeClasses(popoverPanelVariants({ variant: this.variant() }), this.panelClass()), ...(ngDevMode ? [{ debugName: "panelClasses" }] : []));
168
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverContentComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
169
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.0", type: PopoverContentComponent, isStandalone: true, selector: "com-popover-content", ngImport: i0, template: `
170
+ <div
171
+ class="relative"
172
+ [attr.data-state]="animationState()"
173
+ [attr.data-side]="activeSide()"
174
+ >
175
+ @if (showArrow()) {
176
+ <com-popover-arrow [side]="activeSide()" [alignment]="activeAlignment()" />
177
+ }
178
+ <div
179
+ [class]="panelClasses()"
180
+ role="dialog"
181
+ [attr.id]="popoverId()"
182
+ [attr.aria-label]="ariaLabel() || null"
183
+ >
184
+ <ng-template [cdkPortalOutlet]="contentPortal()" />
185
+ </div>
186
+ </div>
187
+ `, isInline: true, styles: [":host{display:contents}[data-state=open]{animation:popover-in .15s ease-out}[data-state=closed]{animation:popover-out .1s ease-in forwards}[data-side=top]{transform-origin:bottom center}[data-side=bottom]{transform-origin:top center}[data-side=left]{transform-origin:right center}[data-side=right]{transform-origin:left center}@keyframes popover-in{0%{opacity:0;transform:scale(.96) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes popover-out{0%{opacity:1}to{opacity:0}}@media(prefers-reduced-motion:reduce){[data-state=open],[data-state=closed]{animation:none}}\n"], dependencies: [{ kind: "directive", type: CdkPortalOutlet, selector: "[cdkPortalOutlet]", inputs: ["cdkPortalOutlet"], outputs: ["attached"], exportAs: ["cdkPortalOutlet"] }, { kind: "component", type: PopoverArrowComponent, selector: "com-popover-arrow", inputs: ["side", "alignment"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
188
+ }
189
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverContentComponent, decorators: [{
190
+ type: Component,
191
+ args: [{ selector: 'com-popover-content', template: `
192
+ <div
193
+ class="relative"
194
+ [attr.data-state]="animationState()"
195
+ [attr.data-side]="activeSide()"
196
+ >
197
+ @if (showArrow()) {
198
+ <com-popover-arrow [side]="activeSide()" [alignment]="activeAlignment()" />
199
+ }
200
+ <div
201
+ [class]="panelClasses()"
202
+ role="dialog"
203
+ [attr.id]="popoverId()"
204
+ [attr.aria-label]="ariaLabel() || null"
205
+ >
206
+ <ng-template [cdkPortalOutlet]="contentPortal()" />
207
+ </div>
208
+ </div>
209
+ `, imports: [CdkPortalOutlet, PopoverArrowComponent], changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:contents}[data-state=open]{animation:popover-in .15s ease-out}[data-state=closed]{animation:popover-out .1s ease-in forwards}[data-side=top]{transform-origin:bottom center}[data-side=bottom]{transform-origin:top center}[data-side=left]{transform-origin:right center}[data-side=right]{transform-origin:left center}@keyframes popover-in{0%{opacity:0;transform:scale(.96) translateY(4px)}to{opacity:1;transform:scale(1) translateY(0)}}@keyframes popover-out{0%{opacity:1}to{opacity:0}}@media(prefers-reduced-motion:reduce){[data-state=open],[data-state=closed]{animation:none}}\n"] }]
210
+ }] });
211
+
212
+ /**
213
+ * Data passed to a component rendered inside the popover.
214
+ * Inject this token to access the data provided via `popoverData` input.
215
+ *
216
+ * @example
217
+ * ```ts
218
+ * @Component({ ... })
219
+ * export class UserProfilePopover {
220
+ * readonly data = inject(POPOVER_DATA); // { userId: '123' }
221
+ * }
222
+ * ```
223
+ */
224
+ const POPOVER_DATA = new InjectionToken('POPOVER_DATA');
225
+ /**
226
+ * Reference to the popover trigger directive for closing from inside the popover.
227
+ * Inject this token to programmatically close the popover from within content.
228
+ *
229
+ * @example
230
+ * ```ts
231
+ * @Component({ ... })
232
+ * export class UserProfilePopover {
233
+ * readonly popoverRef = inject(POPOVER_REF);
234
+ *
235
+ * save(): void {
236
+ * // ... save logic
237
+ * this.popoverRef.close();
238
+ * }
239
+ * }
240
+ * ```
241
+ */
242
+ const POPOVER_REF = new InjectionToken('POPOVER_REF');
243
+
244
+ /**
245
+ * Build an ordered list of position pairs for the CDK overlay.
246
+ * The first position is the preferred position; remaining positions are fallbacks.
247
+ *
248
+ * @param position - Preferred position direction ('above', 'below', 'left', 'right', 'auto')
249
+ * @param alignment - Alignment along the cross-axis ('start', 'center', 'end')
250
+ * @param offset - Gap in pixels between trigger and popover edge (default: 8)
251
+ * @returns Array of ConnectedPosition for FlexibleConnectedPositionStrategy
252
+ */
253
+ function buildPopoverPositions(position, alignment, offset = 8) {
254
+ const allPositions = buildAllPositions(offset);
255
+ if (position === 'auto') {
256
+ // For auto, try below first, then above, then right, then left
257
+ return [
258
+ ...getPositionsForDirection('below', alignment, allPositions),
259
+ ...getPositionsForDirection('above', alignment, allPositions),
260
+ ...getPositionsForDirection('right', alignment, allPositions),
261
+ ...getPositionsForDirection('left', alignment, allPositions),
262
+ ];
263
+ }
264
+ // Start with preferred direction, then add fallbacks
265
+ const preferred = getPositionsForDirection(position, alignment, allPositions);
266
+ const fallbacks = ['below', 'above', 'left', 'right']
267
+ .filter((dir) => dir !== position)
268
+ .flatMap((dir) => getPositionsForDirection(dir, alignment, allPositions));
269
+ return [...preferred, ...fallbacks];
270
+ }
271
+ /**
272
+ * Build all 12 position combinations (4 directions × 3 alignments).
273
+ */
274
+ function buildAllPositions(offset) {
275
+ const positions = new Map();
276
+ // Below positions
277
+ positions.set('below-start', {
278
+ originX: 'start',
279
+ originY: 'bottom',
280
+ overlayX: 'start',
281
+ overlayY: 'top',
282
+ offsetY: offset,
283
+ });
284
+ positions.set('below-center', {
285
+ originX: 'center',
286
+ originY: 'bottom',
287
+ overlayX: 'center',
288
+ overlayY: 'top',
289
+ offsetY: offset,
290
+ });
291
+ positions.set('below-end', {
292
+ originX: 'end',
293
+ originY: 'bottom',
294
+ overlayX: 'end',
295
+ overlayY: 'top',
296
+ offsetY: offset,
297
+ });
298
+ // Above positions
299
+ positions.set('above-start', {
300
+ originX: 'start',
301
+ originY: 'top',
302
+ overlayX: 'start',
303
+ overlayY: 'bottom',
304
+ offsetY: -offset,
305
+ });
306
+ positions.set('above-center', {
307
+ originX: 'center',
308
+ originY: 'top',
309
+ overlayX: 'center',
310
+ overlayY: 'bottom',
311
+ offsetY: -offset,
312
+ });
313
+ positions.set('above-end', {
314
+ originX: 'end',
315
+ originY: 'top',
316
+ overlayX: 'end',
317
+ overlayY: 'bottom',
318
+ offsetY: -offset,
319
+ });
320
+ // Left positions
321
+ positions.set('left-start', {
322
+ originX: 'start',
323
+ originY: 'top',
324
+ overlayX: 'end',
325
+ overlayY: 'top',
326
+ offsetX: -offset,
327
+ });
328
+ positions.set('left-center', {
329
+ originX: 'start',
330
+ originY: 'center',
331
+ overlayX: 'end',
332
+ overlayY: 'center',
333
+ offsetX: -offset,
334
+ });
335
+ positions.set('left-end', {
336
+ originX: 'start',
337
+ originY: 'bottom',
338
+ overlayX: 'end',
339
+ overlayY: 'bottom',
340
+ offsetX: -offset,
341
+ });
342
+ // Right positions
343
+ positions.set('right-start', {
344
+ originX: 'end',
345
+ originY: 'top',
346
+ overlayX: 'start',
347
+ overlayY: 'top',
348
+ offsetX: offset,
349
+ });
350
+ positions.set('right-center', {
351
+ originX: 'end',
352
+ originY: 'center',
353
+ overlayX: 'start',
354
+ overlayY: 'center',
355
+ offsetX: offset,
356
+ });
357
+ positions.set('right-end', {
358
+ originX: 'end',
359
+ originY: 'bottom',
360
+ overlayX: 'start',
361
+ overlayY: 'bottom',
362
+ offsetX: offset,
363
+ });
364
+ return positions;
365
+ }
366
+ /**
367
+ * Get positions for a specific direction, ordered by alignment preference.
368
+ */
369
+ function getPositionsForDirection(direction, alignment, allPositions) {
370
+ if (direction === 'auto') {
371
+ return [];
372
+ }
373
+ // Order alignments with preferred first
374
+ const alignmentOrder = {
375
+ start: ['start', 'center', 'end'],
376
+ center: ['center', 'start', 'end'],
377
+ end: ['end', 'center', 'start'],
378
+ };
379
+ return alignmentOrder[alignment]
380
+ .map((align) => allPositions.get(`${direction}-${align}`))
381
+ .filter((pos) => pos !== undefined);
382
+ }
383
+ /**
384
+ * Derive which side of the trigger the popover is on from a connection pair.
385
+ * Used to position the arrow correctly.
386
+ *
387
+ * The returned side indicates where the popover sits relative to the trigger:
388
+ * - 'bottom': popover is below trigger, arrow at top pointing up
389
+ * - 'top': popover is above trigger, arrow at bottom pointing down
390
+ * - 'right': popover is right of trigger, arrow at left pointing left
391
+ * - 'left': popover is left of trigger, arrow at right pointing right
392
+ */
393
+ function deriveSideFromPosition(pair) {
394
+ const originX = pair.originX;
395
+ const originY = pair.originY;
396
+ const overlayX = pair.overlayX;
397
+ const overlayY = pair.overlayY;
398
+ if (originY === 'bottom' && overlayY === 'top')
399
+ return 'bottom';
400
+ if (originY === 'top' && overlayY === 'bottom')
401
+ return 'top';
402
+ if (originX === 'end' && overlayX === 'start')
403
+ return 'right';
404
+ if (originX === 'start' && overlayX === 'end')
405
+ return 'left';
406
+ return 'bottom';
407
+ }
408
+ /**
409
+ * Derive alignment from a connection pair.
410
+ */
411
+ function deriveAlignmentFromPosition(pair) {
412
+ const originX = pair.originX;
413
+ const originY = pair.originY;
414
+ const overlayX = pair.overlayX;
415
+ const overlayY = pair.overlayY;
416
+ // For vertical positioning (above/below), check X alignment
417
+ if (originY === 'bottom' || originY === 'top') {
418
+ if (originX === 'start' && overlayX === 'start')
419
+ return 'start';
420
+ if (originX === 'end' && overlayX === 'end')
421
+ return 'end';
422
+ return 'center';
423
+ }
424
+ // For horizontal positioning (left/right), check Y alignment
425
+ if (originY === 'top' && overlayY === 'top')
426
+ return 'start';
427
+ if (originY === 'bottom' && overlayY === 'bottom')
428
+ return 'end';
429
+ return 'center';
430
+ }
431
+
432
+ /**
433
+ * Popover trigger directive — manages the popover overlay lifecycle.
434
+ * Applied to the trigger element, it handles opening, closing, positioning,
435
+ * and accessibility for floating popover content.
436
+ *
437
+ * @tokens `--color-popover`, `--color-popover-foreground`, `--color-border`, `--shadow-lg`, `--color-ring`
438
+ *
439
+ * @example Basic usage with template
440
+ * ```html
441
+ * <button comButton [comPopoverTrigger]="helpContent">Help</button>
442
+ * <ng-template #helpContent>
443
+ * <p>This is help content.</p>
444
+ * </ng-template>
445
+ * ```
446
+ *
447
+ * @example With positioning
448
+ * ```html
449
+ * <button
450
+ * comButton
451
+ * [comPopoverTrigger]="menuContent"
452
+ * popoverPosition="below"
453
+ * popoverAlignment="start"
454
+ * [popoverShowArrow]="false"
455
+ * >
456
+ * Menu
457
+ * </button>
458
+ * ```
459
+ *
460
+ * @example With component content
461
+ * ```html
462
+ * <button
463
+ * comButton
464
+ * [comPopoverTrigger]="UserProfilePopover"
465
+ * [popoverData]="{ userId: user.id }"
466
+ * >
467
+ * Profile
468
+ * </button>
469
+ * ```
470
+ *
471
+ * @example Manual control
472
+ * ```html
473
+ * <button
474
+ * comButton
475
+ * [comPopoverTrigger]="content"
476
+ * popoverTriggerOn="manual"
477
+ * [(popoverOpen)]="isOpen"
478
+ * >
479
+ * Controlled
480
+ * </button>
481
+ * ```
482
+ */
483
+ class PopoverTriggerDirective {
484
+ overlay = inject(Overlay);
485
+ elementRef = inject(ElementRef);
486
+ viewContainerRef = inject(ViewContainerRef);
487
+ injector = inject(Injector);
488
+ destroyRef = inject(DestroyRef);
489
+ focusTrapFactory = inject(FocusTrapFactory);
490
+ document = inject(DOCUMENT);
491
+ overlayRef = null;
492
+ focusTrap = null;
493
+ scrollCleanup = null;
494
+ popoverId = generatePopoverId();
495
+ // ─── Inputs ───
496
+ /** Content to render: TemplateRef or Component class. */
497
+ comPopoverTrigger = input.required(...(ngDevMode ? [{ debugName: "comPopoverTrigger" }] : []));
498
+ /** Preferred position direction. */
499
+ popoverPosition = input('auto', ...(ngDevMode ? [{ debugName: "popoverPosition" }] : []));
500
+ /** Alignment along the cross-axis. */
501
+ popoverAlignment = input('center', ...(ngDevMode ? [{ debugName: "popoverAlignment" }] : []));
502
+ /** What interaction opens the popover. */
503
+ popoverTriggerOn = input('click', ...(ngDevMode ? [{ debugName: "popoverTriggerOn" }] : []));
504
+ /** Gap in px between trigger and popover edge. */
505
+ popoverOffset = input(8, ...(ngDevMode ? [{ debugName: "popoverOffset" }] : []));
506
+ /** Whether to render the connecting arrow. */
507
+ popoverShowArrow = input(true, { ...(ngDevMode ? { debugName: "popoverShowArrow" } : {}), transform: booleanAttribute });
508
+ /** Size/padding preset for the content panel. */
509
+ popoverVariant = input('default', ...(ngDevMode ? [{ debugName: "popoverVariant" }] : []));
510
+ /** Backdrop behavior. */
511
+ popoverBackdrop = input('transparent', ...(ngDevMode ? [{ debugName: "popoverBackdrop" }] : []));
512
+ /** Close when clicking outside the popover. */
513
+ popoverCloseOnOutside = input(true, { ...(ngDevMode ? { debugName: "popoverCloseOnOutside" } : {}), transform: booleanAttribute });
514
+ /** Close on Escape key. */
515
+ popoverCloseOnEscape = input(true, { ...(ngDevMode ? { debugName: "popoverCloseOnEscape" } : {}), transform: booleanAttribute });
516
+ /** Close when ancestor scrollable container scrolls. */
517
+ popoverCloseOnScroll = input(false, { ...(ngDevMode ? { debugName: "popoverCloseOnScroll" } : {}), transform: booleanAttribute });
518
+ /** Prevents opening when true. */
519
+ popoverDisabled = input(false, { ...(ngDevMode ? { debugName: "popoverDisabled" } : {}), transform: booleanAttribute });
520
+ /** Two-way bindable open state. */
521
+ popoverOpen = model(false, ...(ngDevMode ? [{ debugName: "popoverOpen" }] : []));
522
+ /** Arbitrary data passed to the content component/template. */
523
+ popoverData = input(undefined, ...(ngDevMode ? [{ debugName: "popoverData" }] : []));
524
+ /** Custom CSS class(es) on the overlay panel. */
525
+ popoverPanelClass = input('', ...(ngDevMode ? [{ debugName: "popoverPanelClass" }] : []));
526
+ /** Trap focus inside popover. */
527
+ popoverTrapFocus = input(false, { ...(ngDevMode ? { debugName: "popoverTrapFocus" } : {}), transform: booleanAttribute });
528
+ /** Optional accessibility label for the popover dialog. */
529
+ popoverAriaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "popoverAriaLabel" }] : []));
530
+ // ─── Outputs ───
531
+ /** Emitted after popup opens and is visible. */
532
+ popoverOpened = output();
533
+ /** Emitted after popup closes and is detached. */
534
+ popoverClosed = output();
535
+ // ─── Internal State ───
536
+ activeSide = signal('top', ...(ngDevMode ? [{ debugName: "activeSide" }] : []));
537
+ activeAlignment = signal('center', ...(ngDevMode ? [{ debugName: "activeAlignment" }] : []));
538
+ animationState = signal('open', ...(ngDevMode ? [{ debugName: "animationState" }] : []));
539
+ contentComponentRef = null;
540
+ // ─── Computed ───
541
+ hasBackdrop = computed(() => this.popoverBackdrop() !== 'none', ...(ngDevMode ? [{ debugName: "hasBackdrop" }] : []));
542
+ backdropClass = computed(() => this.popoverBackdrop() === 'dimmed' ? 'cdk-overlay-dark-backdrop' : 'cdk-overlay-transparent-backdrop', ...(ngDevMode ? [{ debugName: "backdropClass" }] : []));
543
+ ariaControls = computed(() => this.popoverOpen() ? this.popoverId : null, ...(ngDevMode ? [{ debugName: "ariaControls" }] : []));
544
+ panelClassArray = computed(() => {
545
+ const panelClass = this.popoverPanelClass();
546
+ if (Array.isArray(panelClass))
547
+ return panelClass;
548
+ return panelClass ? [panelClass] : [];
549
+ }, ...(ngDevMode ? [{ debugName: "panelClassArray" }] : []));
550
+ constructor() {
551
+ // React to external open state changes
552
+ effect(() => {
553
+ const isOpen = this.popoverOpen();
554
+ if (isOpen && !this.overlayRef?.hasAttached()) {
555
+ this.openPopover();
556
+ }
557
+ else if (!isOpen && this.overlayRef?.hasAttached()) {
558
+ this.closePopover();
559
+ }
560
+ });
561
+ // Cleanup on destroy
562
+ this.destroyRef.onDestroy(() => {
563
+ this.disposeOverlay();
564
+ });
565
+ }
566
+ // ─── Public API ───
567
+ /** Programmatically open the popover. */
568
+ open() {
569
+ if (!this.popoverDisabled() && !this.overlayRef?.hasAttached()) {
570
+ this.popoverOpen.set(true);
571
+ }
572
+ }
573
+ /** Programmatically close the popover. */
574
+ close() {
575
+ if (this.overlayRef?.hasAttached()) {
576
+ this.popoverOpen.set(false);
577
+ }
578
+ }
579
+ /** Toggle the popover open/close state. */
580
+ toggle() {
581
+ this.popoverOpen() ? this.close() : this.open();
582
+ }
583
+ /** Force recalculation of position. */
584
+ reposition() {
585
+ if (this.overlayRef) {
586
+ this.overlayRef.updatePosition();
587
+ }
588
+ }
589
+ // ─── Event Handlers ───
590
+ onTriggerClick(event) {
591
+ if (this.popoverTriggerOn() === 'click') {
592
+ event.preventDefault();
593
+ this.toggle();
594
+ }
595
+ }
596
+ onTriggerFocus() {
597
+ if (this.popoverTriggerOn() === 'focus') {
598
+ this.open();
599
+ }
600
+ }
601
+ onTriggerBlur() {
602
+ if (this.popoverTriggerOn() === 'focus') {
603
+ // Delay to allow focus to move to popover content
604
+ setTimeout(() => {
605
+ if (!this.overlayRef?.overlayElement.contains(this.document.activeElement)) {
606
+ this.close();
607
+ }
608
+ }, 0);
609
+ }
610
+ }
611
+ onEscapeKey(event) {
612
+ if (this.popoverCloseOnEscape() && this.popoverOpen()) {
613
+ event.preventDefault();
614
+ event.stopPropagation();
615
+ this.close();
616
+ this.elementRef.nativeElement.focus();
617
+ }
618
+ }
619
+ // ─── Private Methods ───
620
+ openPopover() {
621
+ if (this.popoverDisabled())
622
+ return;
623
+ this.createOverlay();
624
+ this.attachContent();
625
+ this.subscribeToCloseEvents();
626
+ this.subscribeToScrollEvents();
627
+ this.setupFocusTrap();
628
+ this.animationState.set('open');
629
+ this.popoverOpened.emit();
630
+ }
631
+ closePopover() {
632
+ this.animationState.set('closed');
633
+ this.unsubscribeFromScrollEvents();
634
+ // Wait for animation to complete before detaching
635
+ setTimeout(() => {
636
+ this.detachContent();
637
+ this.destroyFocusTrap();
638
+ this.popoverClosed.emit();
639
+ }, 100); // Match animation duration
640
+ }
641
+ subscribeToScrollEvents() {
642
+ if (this.popoverCloseOnScroll())
643
+ return;
644
+ const scrollHandler = () => this.overlayRef?.updatePosition();
645
+ this.document.addEventListener('scroll', scrollHandler, true);
646
+ this.scrollCleanup = () => this.document.removeEventListener('scroll', scrollHandler, true);
647
+ }
648
+ unsubscribeFromScrollEvents() {
649
+ this.scrollCleanup?.();
650
+ this.scrollCleanup = null;
651
+ }
652
+ createOverlay() {
653
+ if (this.overlayRef)
654
+ return;
655
+ const positionStrategy = this.overlay
656
+ .position()
657
+ .flexibleConnectedTo(this.elementRef)
658
+ .withPositions(buildPopoverPositions(this.popoverPosition(), this.popoverAlignment(), this.popoverOffset()))
659
+ .withFlexibleDimensions(false)
660
+ .withPush(true)
661
+ .withViewportMargin(8);
662
+ this.overlayRef = this.overlay.create({
663
+ positionStrategy,
664
+ scrollStrategy: this.popoverCloseOnScroll()
665
+ ? this.overlay.scrollStrategies.close()
666
+ : this.overlay.scrollStrategies.reposition(),
667
+ hasBackdrop: this.hasBackdrop(),
668
+ backdropClass: this.backdropClass(),
669
+ panelClass: ['com-popover-panel', ...this.panelClassArray()],
670
+ });
671
+ // Track position changes for arrow placement
672
+ positionStrategy.positionChanges
673
+ .pipe(takeUntilDestroyed(this.destroyRef))
674
+ .subscribe((change) => {
675
+ this.activeSide.set(deriveSideFromPosition(change.connectionPair));
676
+ this.activeAlignment.set(deriveAlignmentFromPosition(change.connectionPair));
677
+ this.updateContentComponentInputs();
678
+ });
679
+ }
680
+ attachContent() {
681
+ if (!this.overlayRef)
682
+ return;
683
+ const content = this.comPopoverTrigger();
684
+ // Create the content wrapper component
685
+ const contentInjector = Injector.create({
686
+ parent: this.injector,
687
+ providers: [
688
+ { provide: POPOVER_DATA, useValue: this.popoverData() },
689
+ { provide: POPOVER_REF, useValue: this },
690
+ ],
691
+ });
692
+ const contentPortal = new ComponentPortal(PopoverContentComponent, this.viewContainerRef, contentInjector);
693
+ const contentRef = this.overlayRef.attach(contentPortal);
694
+ this.contentComponentRef = contentRef.instance;
695
+ // Create the inner content portal (template or component)
696
+ let innerPortal;
697
+ if (content instanceof TemplateRef) {
698
+ innerPortal = new TemplatePortal(content, this.viewContainerRef, {
699
+ $implicit: this.popoverData(),
700
+ close: () => this.close(),
701
+ });
702
+ }
703
+ else {
704
+ const componentInjector = Injector.create({
705
+ parent: this.injector,
706
+ providers: [
707
+ { provide: POPOVER_DATA, useValue: this.popoverData() },
708
+ { provide: POPOVER_REF, useValue: this },
709
+ ],
710
+ });
711
+ innerPortal = new ComponentPortal(content, this.viewContainerRef, componentInjector);
712
+ }
713
+ // Set content component inputs
714
+ this.updateContentComponentInputs(innerPortal);
715
+ }
716
+ updateContentComponentInputs(innerPortal) {
717
+ const ref = this.contentComponentRef;
718
+ if (!ref)
719
+ return;
720
+ if (innerPortal) {
721
+ ref.contentPortal.set(innerPortal);
722
+ }
723
+ ref.variant.set(this.popoverVariant());
724
+ ref.showArrow.set(this.popoverShowArrow());
725
+ ref.activeSide.set(this.activeSide());
726
+ ref.activeAlignment.set(this.activeAlignment());
727
+ ref.popoverId.set(this.popoverId);
728
+ ref.ariaLabel.set(this.popoverAriaLabel());
729
+ ref.animationState.set(this.animationState());
730
+ ref.panelClass.set(mergeClasses(...this.panelClassArray()));
731
+ }
732
+ detachContent() {
733
+ if (this.overlayRef?.hasAttached()) {
734
+ this.overlayRef.detach();
735
+ }
736
+ this.contentComponentRef = null;
737
+ }
738
+ subscribeToCloseEvents() {
739
+ if (!this.overlayRef)
740
+ return;
741
+ // Close on backdrop click
742
+ if (this.popoverCloseOnOutside()) {
743
+ this.overlayRef
744
+ .backdropClick()
745
+ .pipe(takeUntilDestroyed(this.destroyRef))
746
+ .subscribe(() => this.close());
747
+ }
748
+ // Close on escape key (handled by overlay)
749
+ if (this.popoverCloseOnEscape()) {
750
+ this.overlayRef
751
+ .keydownEvents()
752
+ .pipe(filter((event) => event.key === 'Escape'), takeUntilDestroyed(this.destroyRef))
753
+ .subscribe((event) => {
754
+ event.preventDefault();
755
+ this.close();
756
+ this.elementRef.nativeElement.focus();
757
+ });
758
+ }
759
+ // Handle detachment
760
+ this.overlayRef
761
+ .detachments()
762
+ .pipe(takeUntilDestroyed(this.destroyRef))
763
+ .subscribe(() => {
764
+ this.popoverOpen.set(false);
765
+ });
766
+ }
767
+ setupFocusTrap() {
768
+ if (this.popoverTrapFocus() && this.overlayRef) {
769
+ this.focusTrap = this.focusTrapFactory.create(this.overlayRef.overlayElement);
770
+ this.focusTrap.focusInitialElementWhenReady();
771
+ }
772
+ }
773
+ destroyFocusTrap() {
774
+ if (this.focusTrap) {
775
+ this.focusTrap.destroy();
776
+ this.focusTrap = null;
777
+ }
778
+ }
779
+ disposeOverlay() {
780
+ this.destroyFocusTrap();
781
+ if (this.overlayRef) {
782
+ this.overlayRef.dispose();
783
+ this.overlayRef = null;
784
+ }
785
+ this.contentComponentRef = null;
786
+ }
787
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
788
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: PopoverTriggerDirective, isStandalone: true, selector: "[comPopoverTrigger]", inputs: { comPopoverTrigger: { classPropertyName: "comPopoverTrigger", publicName: "comPopoverTrigger", isSignal: true, isRequired: true, transformFunction: null }, popoverPosition: { classPropertyName: "popoverPosition", publicName: "popoverPosition", isSignal: true, isRequired: false, transformFunction: null }, popoverAlignment: { classPropertyName: "popoverAlignment", publicName: "popoverAlignment", isSignal: true, isRequired: false, transformFunction: null }, popoverTriggerOn: { classPropertyName: "popoverTriggerOn", publicName: "popoverTriggerOn", isSignal: true, isRequired: false, transformFunction: null }, popoverOffset: { classPropertyName: "popoverOffset", publicName: "popoverOffset", isSignal: true, isRequired: false, transformFunction: null }, popoverShowArrow: { classPropertyName: "popoverShowArrow", publicName: "popoverShowArrow", isSignal: true, isRequired: false, transformFunction: null }, popoverVariant: { classPropertyName: "popoverVariant", publicName: "popoverVariant", isSignal: true, isRequired: false, transformFunction: null }, popoverBackdrop: { classPropertyName: "popoverBackdrop", publicName: "popoverBackdrop", isSignal: true, isRequired: false, transformFunction: null }, popoverCloseOnOutside: { classPropertyName: "popoverCloseOnOutside", publicName: "popoverCloseOnOutside", isSignal: true, isRequired: false, transformFunction: null }, popoverCloseOnEscape: { classPropertyName: "popoverCloseOnEscape", publicName: "popoverCloseOnEscape", isSignal: true, isRequired: false, transformFunction: null }, popoverCloseOnScroll: { classPropertyName: "popoverCloseOnScroll", publicName: "popoverCloseOnScroll", isSignal: true, isRequired: false, transformFunction: null }, popoverDisabled: { classPropertyName: "popoverDisabled", publicName: "popoverDisabled", isSignal: true, isRequired: false, transformFunction: null }, popoverOpen: { classPropertyName: "popoverOpen", publicName: "popoverOpen", isSignal: true, isRequired: false, transformFunction: null }, popoverData: { classPropertyName: "popoverData", publicName: "popoverData", isSignal: true, isRequired: false, transformFunction: null }, popoverPanelClass: { classPropertyName: "popoverPanelClass", publicName: "popoverPanelClass", isSignal: true, isRequired: false, transformFunction: null }, popoverTrapFocus: { classPropertyName: "popoverTrapFocus", publicName: "popoverTrapFocus", isSignal: true, isRequired: false, transformFunction: null }, popoverAriaLabel: { classPropertyName: "popoverAriaLabel", publicName: "popoverAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { popoverOpen: "popoverOpenChange", popoverOpened: "popoverOpened", popoverClosed: "popoverClosed" }, host: { listeners: { "click": "onTriggerClick($event)", "focus": "onTriggerFocus()", "blur": "onTriggerBlur()", "keydown.escape": "onEscapeKey($event)" }, properties: { "attr.aria-haspopup": "\"dialog\"", "attr.aria-expanded": "popoverOpen()", "attr.aria-controls": "ariaControls()" } }, exportAs: ["comPopoverTrigger"], ngImport: i0 });
789
+ }
790
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverTriggerDirective, decorators: [{
791
+ type: Directive,
792
+ args: [{
793
+ selector: '[comPopoverTrigger]',
794
+ exportAs: 'comPopoverTrigger',
795
+ host: {
796
+ '[attr.aria-haspopup]': '"dialog"',
797
+ '[attr.aria-expanded]': 'popoverOpen()',
798
+ '[attr.aria-controls]': 'ariaControls()',
799
+ '(click)': 'onTriggerClick($event)',
800
+ '(focus)': 'onTriggerFocus()',
801
+ '(blur)': 'onTriggerBlur()',
802
+ '(keydown.escape)': 'onEscapeKey($event)',
803
+ },
804
+ }]
805
+ }], ctorParameters: () => [], propDecorators: { comPopoverTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "comPopoverTrigger", required: true }] }], popoverPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverPosition", required: false }] }], popoverAlignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverAlignment", required: false }] }], popoverTriggerOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverTriggerOn", required: false }] }], popoverOffset: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverOffset", required: false }] }], popoverShowArrow: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverShowArrow", required: false }] }], popoverVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverVariant", required: false }] }], popoverBackdrop: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverBackdrop", required: false }] }], popoverCloseOnOutside: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverCloseOnOutside", required: false }] }], popoverCloseOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverCloseOnEscape", required: false }] }], popoverCloseOnScroll: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverCloseOnScroll", required: false }] }], popoverDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverDisabled", required: false }] }], popoverOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverOpen", required: false }] }, { type: i0.Output, args: ["popoverOpenChange"] }], popoverData: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverData", required: false }] }], popoverPanelClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverPanelClass", required: false }] }], popoverTrapFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverTrapFocus", required: false }] }], popoverAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "popoverAriaLabel", required: false }] }], popoverOpened: [{ type: i0.Output, args: ["popoverOpened"] }], popoverClosed: [{ type: i0.Output, args: ["popoverClosed"] }] } });
806
+
807
+ /**
808
+ * Convenience directive that closes the parent popover when clicked.
809
+ * Applied to elements inside the popover that should dismiss it.
810
+ *
811
+ * @example
812
+ * ```html
813
+ * <ng-template #confirmPop>
814
+ * <div class="space-y-3">
815
+ * <p>Are you sure?</p>
816
+ * <div class="flex gap-2">
817
+ * <button comButton variant="ghost" comPopoverClose>Cancel</button>
818
+ * <button comButton (click)="confirm()" comPopoverClose>Confirm</button>
819
+ * </div>
820
+ * </div>
821
+ * </ng-template>
822
+ * ```
823
+ *
824
+ * @example With a result value
825
+ * ```html
826
+ * <button [comPopoverClose]="'confirmed'" (click)="onConfirm()">Yes</button>
827
+ * <button [comPopoverClose]="'cancelled'">No</button>
828
+ * ```
829
+ */
830
+ class PopoverCloseDirective {
831
+ popoverRef = inject(POPOVER_REF, { optional: true });
832
+ /**
833
+ * Optional result value to pass when closing.
834
+ * This value is emitted via the trigger's close event.
835
+ */
836
+ comPopoverClose = input(undefined, ...(ngDevMode ? [{ debugName: "comPopoverClose" }] : []));
837
+ closePopover() {
838
+ this.popoverRef?.close();
839
+ }
840
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverCloseDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
841
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.0", type: PopoverCloseDirective, isStandalone: true, selector: "[comPopoverClose]", inputs: { comPopoverClose: { classPropertyName: "comPopoverClose", publicName: "comPopoverClose", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "click": "closePopover()" }, properties: { "attr.type": "\"button\"" } }, exportAs: ["comPopoverClose"], ngImport: i0 });
842
+ }
843
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverCloseDirective, decorators: [{
844
+ type: Directive,
845
+ args: [{
846
+ selector: '[comPopoverClose]',
847
+ exportAs: 'comPopoverClose',
848
+ host: {
849
+ '(click)': 'closePopover()',
850
+ '[attr.type]': '"button"',
851
+ },
852
+ }]
853
+ }], propDecorators: { comPopoverClose: [{ type: i0.Input, args: [{ isSignal: true, alias: "comPopoverClose", required: false }] }] } });
854
+
855
+ /**
856
+ * Marker directive for lazy popover content templates.
857
+ * Applied to `<ng-template>` to indicate content that should be
858
+ * lazily instantiated when the popover opens.
859
+ *
860
+ * This directive is primarily semantic — it marks the template as popover content
861
+ * and provides access to the TemplateRef for potential content queries.
862
+ *
863
+ * @example
864
+ * ```html
865
+ * <button [comPopoverTrigger]="helpContent">Help</button>
866
+ * <ng-template comPopoverTemplate #helpContent>
867
+ * <p>This is help content.</p>
868
+ * </ng-template>
869
+ * ```
870
+ *
871
+ * Note: You can also pass a template reference directly without this directive:
872
+ * ```html
873
+ * <button [comPopoverTrigger]="helpContent">Help</button>
874
+ * <ng-template #helpContent>
875
+ * <p>This also works.</p>
876
+ * </ng-template>
877
+ * ```
878
+ */
879
+ class PopoverTemplateDirective {
880
+ /** Reference to the template for rendering. */
881
+ templateRef = inject(TemplateRef);
882
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
883
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.0", type: PopoverTemplateDirective, isStandalone: true, selector: "[comPopoverTemplate]", exportAs: ["comPopoverTemplate"], ngImport: i0 });
884
+ }
885
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.0", ngImport: i0, type: PopoverTemplateDirective, decorators: [{
886
+ type: Directive,
887
+ args: [{
888
+ selector: '[comPopoverTemplate]',
889
+ exportAs: 'comPopoverTemplate',
890
+ }]
891
+ }] });
892
+
893
+ // Public API for the popover component
894
+ // Main directive
895
+
896
+ /**
897
+ * Generated bundle index. Do not edit.
898
+ */
899
+
900
+ export { POPOVER_DATA, POPOVER_REF, PopoverCloseDirective, PopoverTemplateDirective, PopoverTriggerDirective, buildPopoverPositions, deriveAlignmentFromPosition, deriveSideFromPosition, popoverArrowVariants, popoverPanelVariants };
901
+ //# sourceMappingURL=ngx-com-components-popover.mjs.map