@lumaui/angular 0.1.4 → 0.2.1

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.
@@ -1,8 +1,12 @@
1
1
  import * as i0 from '@angular/core';
2
- import { input, computed, HostBinding, Directive, ChangeDetectionStrategy, Component } from '@angular/core';
3
- import { buttonVariants, cardVariants, cardContentVariants, cardTitleVariants, cardDescriptionVariants } from '@lumaui/core';
2
+ import { input, computed, HostBinding, Directive, ChangeDetectionStrategy, Component, output, InjectionToken, inject, ElementRef, Renderer2, effect, signal, HostListener, PLATFORM_ID, TemplateRef, ViewContainerRef, ApplicationRef, Injector, createComponent, Injectable } from '@angular/core';
3
+ import { buttonVariants, badgeVariants, cardVariants, cardContentVariants, cardTitleVariants, cardDescriptionVariants, accordionItemVariants, accordionContentWrapperVariants, accordionTriggerVariants, accordionTitleVariants, accordionIconVariants, accordionContentVariants, tooltipVariants, tabsListVariants, tabsTriggerVariants, tabsPanelVariants, tabsIndicatorVariants, modalOverlayVariants, modalContainerVariants, modalHeaderVariants, modalTitleVariants, modalContentVariants, modalFooterVariants, modalCloseVariants, toastCloseVariants, toastItemVariants, toastIconVariants, toastContentVariants, toastTitleVariants, toastMessageVariants, toastContainerVariants } from '@lumaui/core';
4
+ import { isPlatformBrowser, DOCUMENT } from '@angular/common';
5
+ import { LiveAnnouncer } from '@angular/cdk/a11y';
6
+ import { Subject, interval } from 'rxjs';
7
+ import { takeWhile, filter } from 'rxjs/operators';
4
8
 
5
- class ButtonDirective {
9
+ class LmButtonDirective {
6
10
  // Signal-based inputs with lm prefix (Angular 20+)
7
11
  lmVariant = input('primary', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
8
12
  lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
@@ -16,10 +20,10 @@ class ButtonDirective {
16
20
  get hostClasses() {
17
21
  return this.classes();
18
22
  }
19
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
20
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: ButtonDirective, isStandalone: true, selector: "button[lumaButton], a[lumaButton]", inputs: { lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null }, lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null }, lmDisabled: { classPropertyName: "lmDisabled", publicName: "lmDisabled", isSignal: true, isRequired: false, transformFunction: null }, lmType: { classPropertyName: "lmType", publicName: "lmType", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.type": "lmType()", "attr.disabled": "lmDisabled() ? \"\" : null", "class": "this.hostClasses" } }, ngImport: i0 });
23
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
24
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmButtonDirective, isStandalone: true, selector: "button[lumaButton], a[lumaButton]", inputs: { lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null }, lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null }, lmDisabled: { classPropertyName: "lmDisabled", publicName: "lmDisabled", isSignal: true, isRequired: false, transformFunction: null }, lmType: { classPropertyName: "lmType", publicName: "lmType", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.type": "lmType()", "attr.disabled": "lmDisabled() ? \"\" : null", "class": "this.hostClasses" } }, ngImport: i0 });
21
25
  }
22
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: ButtonDirective, decorators: [{
26
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmButtonDirective, decorators: [{
23
27
  type: Directive,
24
28
  args: [{
25
29
  selector: 'button[lumaButton], a[lumaButton]',
@@ -33,7 +37,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
33
37
  args: ['class']
34
38
  }] } });
35
39
 
36
- class CardComponent {
40
+ class LmBadgeDirective {
41
+ // Computed class string - layout only, no variants
42
+ classes = computed(() => badgeVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
43
+ get hostClasses() {
44
+ return this.classes();
45
+ }
46
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmBadgeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
47
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmBadgeDirective, isStandalone: true, selector: "[lumaBadge]", host: { properties: { "class": "this.hostClasses" } }, ngImport: i0 });
48
+ }
49
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmBadgeDirective, decorators: [{
50
+ type: Directive,
51
+ args: [{
52
+ selector: '[lumaBadge]',
53
+ }]
54
+ }], propDecorators: { hostClasses: [{
55
+ type: HostBinding,
56
+ args: ['class']
57
+ }] } });
58
+
59
+ class LmCardComponent {
37
60
  /**
38
61
  * Card visual style variant
39
62
  * - default: Gradient border wrapper style (default)
@@ -45,23 +68,23 @@ class CardComponent {
45
68
  // Computed class strings based on variant
46
69
  wrapperClasses = computed(() => cardVariants({ variant: this.lmVariant() }), ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
47
70
  contentClasses = computed(() => cardContentVariants({ variant: this.lmVariant() }), ...(ngDevMode ? [{ debugName: "contentClasses" }] : []));
48
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
49
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: CardComponent, isStandalone: true, selector: "luma-card", inputs: { lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div [class]=\"wrapperClasses()\">\n <div [class]=\"contentClasses()\">\n <ng-content></ng-content>\n </div>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
71
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
72
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmCardComponent, isStandalone: true, selector: "luma-card", inputs: { lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div [class]=\"wrapperClasses()\">\n <div [class]=\"contentClasses()\">\n <ng-content></ng-content>\n </div>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
50
73
  }
51
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardComponent, decorators: [{
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardComponent, decorators: [{
52
75
  type: Component,
53
76
  args: [{ selector: 'luma-card', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"wrapperClasses()\">\n <div [class]=\"contentClasses()\">\n <ng-content></ng-content>\n </div>\n</div>\n" }]
54
77
  }], propDecorators: { lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }] } });
55
78
 
56
- class CardTitleDirective {
79
+ class LmCardTitleDirective {
57
80
  // Signal-based inputs with lm prefix (Angular 20+)
58
81
  lmSize = input('normal', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
59
82
  // Computed class string
60
83
  classes = computed(() => cardTitleVariants({ size: this.lmSize() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
61
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
62
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: CardTitleDirective, isStandalone: true, selector: "[lumaCardTitle]", inputs: { lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
84
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
85
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmCardTitleDirective, isStandalone: true, selector: "[lumaCardTitle]", inputs: { lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
63
86
  }
64
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardTitleDirective, decorators: [{
87
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardTitleDirective, decorators: [{
65
88
  type: Directive,
66
89
  args: [{
67
90
  selector: '[lumaCardTitle]',
@@ -71,15 +94,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
71
94
  }]
72
95
  }], propDecorators: { lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }] } });
73
96
 
74
- class CardDescriptionDirective {
97
+ class LmCardDescriptionDirective {
75
98
  // Signal-based inputs with lm prefix (Angular 20+)
76
99
  lmSize = input('normal', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
77
100
  // Computed class string
78
101
  classes = computed(() => cardDescriptionVariants({ size: this.lmSize() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
79
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
80
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: CardDescriptionDirective, isStandalone: true, selector: "[lumaCardDescription]", inputs: { lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
102
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
103
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmCardDescriptionDirective, isStandalone: true, selector: "[lumaCardDescription]", inputs: { lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
81
104
  }
82
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardDescriptionDirective, decorators: [{
105
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardDescriptionDirective, decorators: [{
83
106
  type: Directive,
84
107
  args: [{
85
108
  selector: '[lumaCardDescription]',
@@ -89,11 +112,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
89
112
  }]
90
113
  }], propDecorators: { lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }] } });
91
114
 
92
- class CardHeaderDirective {
93
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
94
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: CardHeaderDirective, isStandalone: true, selector: "[lumaCardHeader]", host: { classAttribute: "mb-4" }, ngImport: i0 });
115
+ class LmCardHeaderDirective {
116
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
117
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmCardHeaderDirective, isStandalone: true, selector: "[lumaCardHeader]", host: { classAttribute: "mb-4" }, ngImport: i0 });
95
118
  }
96
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardHeaderDirective, decorators: [{
119
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardHeaderDirective, decorators: [{
97
120
  type: Directive,
98
121
  args: [{
99
122
  selector: '[lumaCardHeader]',
@@ -103,11 +126,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
103
126
  }]
104
127
  }] });
105
128
 
106
- class CardContentDirective {
107
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
108
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: CardContentDirective, isStandalone: true, selector: "[lumaCardContent]", ngImport: i0 });
129
+ class LmCardContentDirective {
130
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
131
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmCardContentDirective, isStandalone: true, selector: "[lumaCardContent]", ngImport: i0 });
109
132
  }
110
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: CardContentDirective, decorators: [{
133
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardContentDirective, decorators: [{
111
134
  type: Directive,
112
135
  args: [{
113
136
  selector: '[lumaCardContent]',
@@ -117,11 +140,2518 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
117
140
  }]
118
141
  }] });
119
142
 
143
+ /**
144
+ * AccordionGroupComponent
145
+ *
146
+ * Optional wrapper component that coordinates multiple accordion items.
147
+ * Supports controlled pattern for implementing business logic like:
148
+ * - Single item open at a time
149
+ * - Multiple items open
150
+ * - Always keep first/last item open
151
+ * - Maximum number of open items
152
+ *
153
+ * @example Controlled single mode
154
+ * ```html
155
+ * <luma-accordion-group [lmValue]="activeItem()" (lmValueChange)="activeItem.set($event)">
156
+ * <luma-accordion-item lmId="item-1">...</luma-accordion-item>
157
+ * <luma-accordion-item lmId="item-2">...</luma-accordion-item>
158
+ * </luma-accordion-group>
159
+ * ```
160
+ *
161
+ * @example Controlled multiple mode
162
+ * ```html
163
+ * <luma-accordion-group [lmValue]="activeItems()" (lmValueChange)="activeItems.set($event)">
164
+ * <luma-accordion-item lmId="item-1">...</luma-accordion-item>
165
+ * <luma-accordion-item lmId="item-2">...</luma-accordion-item>
166
+ * </luma-accordion-group>
167
+ * ```
168
+ */
169
+ class LmAccordionGroupComponent {
170
+ /**
171
+ * Controlled value for which items are open
172
+ * - null: uncontrolled mode (each item manages its own state)
173
+ * - string: single item mode (ID of open item)
174
+ * - string[]: multiple items mode (IDs of open items)
175
+ */
176
+ lmValue = input(null, ...(ngDevMode ? [{ debugName: "lmValue" }] : []));
177
+ /**
178
+ * Emitted when an item is toggled
179
+ * Returns the new value (string for single mode, string[] for multiple)
180
+ */
181
+ lmValueChange = output();
182
+ /**
183
+ * Force single mode even when lmValue is an array
184
+ * When true, only one item can be open at a time
185
+ */
186
+ lmSingle = input(false, ...(ngDevMode ? [{ debugName: "lmSingle" }] : []));
187
+ /**
188
+ * Toggle an item by its ID
189
+ * Called by child AccordionItemComponent when toggled
190
+ */
191
+ toggleItem(itemId) {
192
+ const current = this.lmValue();
193
+ // If uncontrolled, do nothing (items manage their own state)
194
+ if (current === null || current === undefined) {
195
+ return;
196
+ }
197
+ let newValue;
198
+ if (this.lmSingle() || typeof current === 'string') {
199
+ // Single mode: toggle between the item and empty
200
+ newValue = current === itemId ? '' : itemId;
201
+ }
202
+ else {
203
+ // Multiple mode: add/remove from array
204
+ const arr = Array.isArray(current) ? [...current] : [];
205
+ const index = arr.indexOf(itemId);
206
+ if (index >= 0) {
207
+ arr.splice(index, 1);
208
+ }
209
+ else {
210
+ arr.push(itemId);
211
+ }
212
+ newValue = arr;
213
+ }
214
+ this.lmValueChange.emit(newValue);
215
+ }
216
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
217
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmAccordionGroupComponent, isStandalone: true, selector: "luma-accordion-group", inputs: { lmValue: { classPropertyName: "lmValue", publicName: "lmValue", isSignal: true, isRequired: false, transformFunction: null }, lmSingle: { classPropertyName: "lmSingle", publicName: "lmSingle", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lmValueChange: "lmValueChange" }, host: { properties: { "style.gap": "\"var(--luma-accordion-item-gap)\"" }, classAttribute: "flex flex-col" }, ngImport: i0, template: '<ng-content></ng-content>', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
218
+ }
219
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionGroupComponent, decorators: [{
220
+ type: Component,
221
+ args: [{
222
+ selector: 'luma-accordion-group',
223
+ template: '<ng-content></ng-content>',
224
+ changeDetection: ChangeDetectionStrategy.OnPush,
225
+ host: {
226
+ class: 'flex flex-col',
227
+ '[style.gap]': '"var(--luma-accordion-item-gap)"',
228
+ },
229
+ }]
230
+ }], propDecorators: { lmValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmValue", required: false }] }], lmValueChange: [{ type: i0.Output, args: ["lmValueChange"] }], lmSingle: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSingle", required: false }] }] } });
231
+
232
+ /**
233
+ * Injection token for accordion item
234
+ * Allows child directives to access parent accordion item state
235
+ */
236
+ const ACCORDION_ITEM = new InjectionToken('AccordionItem');
237
+
238
+ /**
239
+ * AccordionItemComponent
240
+ *
241
+ * Wrapper component that contains the trigger and content.
242
+ * Can be used standalone or within an AccordionGroupComponent.
243
+ *
244
+ * @example Standalone usage
245
+ * ```html
246
+ * <luma-accordion-item>
247
+ * <button lumaAccordionTrigger>
248
+ * <span lumaAccordionTitle>Title</span>
249
+ * <svg lumaAccordionIcon>...</svg>
250
+ * </button>
251
+ * <div lumaAccordionContent>Content here...</div>
252
+ * </luma-accordion-item>
253
+ * ```
254
+ *
255
+ * @example With variants
256
+ * ```html
257
+ * <luma-accordion-item lmVariant="filled">
258
+ * ...
259
+ * </luma-accordion-item>
260
+ * ```
261
+ */
262
+ class LmAccordionItemComponent {
263
+ group = inject(LmAccordionGroupComponent, { optional: true });
264
+ el = inject(ElementRef);
265
+ renderer = inject(Renderer2);
266
+ previousClasses = [];
267
+ constructor() {
268
+ // Effect to reactively manage CVA classes without replacing user classes
269
+ effect(() => {
270
+ // Remove previous CVA classes
271
+ this.previousClasses.forEach((c) => {
272
+ this.renderer.removeClass(this.el.nativeElement, c);
273
+ });
274
+ // Add new CVA classes (preserves user-provided classes)
275
+ const newClasses = this.wrapperClasses()
276
+ .split(' ')
277
+ .filter((c) => c);
278
+ newClasses.forEach((c) => {
279
+ this.renderer.addClass(this.el.nativeElement, c);
280
+ });
281
+ this.previousClasses = newClasses;
282
+ });
283
+ }
284
+ /**
285
+ * Unique identifier for this item (required when using AccordionGroup)
286
+ */
287
+ lmId = input('', ...(ngDevMode ? [{ debugName: "lmId" }] : []));
288
+ /**
289
+ * Visual style variant
290
+ * - default: Standard with border
291
+ * - bordered: FAQ-style stacked items
292
+ * - filled: Solid background (unified trigger/content)
293
+ */
294
+ lmVariant = input('default', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
295
+ /**
296
+ * Initial/controlled open state (for standalone usage)
297
+ */
298
+ lmOpen = input(false, ...(ngDevMode ? [{ debugName: "lmOpen" }] : []));
299
+ /**
300
+ * Whether the accordion item is disabled
301
+ */
302
+ lmDisabled = input(false, ...(ngDevMode ? [{ debugName: "lmDisabled" }] : []));
303
+ /**
304
+ * Emitted when the open state changes
305
+ * Useful for tracking/analytics
306
+ */
307
+ lmOpenChange = output();
308
+ // Internal state for uncontrolled mode
309
+ _isOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isOpen" }] : []));
310
+ /**
311
+ * Computed open state
312
+ * Priority: group controlled > lmOpen input > internal state
313
+ */
314
+ isOpen = computed(() => {
315
+ // If in a controlled group, check group value
316
+ if (this.group?.lmValue() !== null && this.group?.lmValue() !== undefined) {
317
+ const value = this.group.lmValue();
318
+ const id = this.lmId();
319
+ return Array.isArray(value) ? value.includes(id) : value === id;
320
+ }
321
+ // Otherwise use input or internal state
322
+ return this.lmOpen() || this._isOpen();
323
+ }, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
324
+ // CVA classes
325
+ wrapperClasses = computed(() => accordionItemVariants({
326
+ variant: this.lmVariant(),
327
+ }), ...(ngDevMode ? [{ debugName: "wrapperClasses" }] : []));
328
+ contentWrapperClasses = computed(() => accordionContentWrapperVariants({ open: this.isOpen() }), ...(ngDevMode ? [{ debugName: "contentWrapperClasses" }] : []));
329
+ /**
330
+ * Toggle the accordion open/closed state
331
+ */
332
+ toggle() {
333
+ if (this.lmDisabled())
334
+ return;
335
+ if (this.group && this.group.lmValue() !== null) {
336
+ // Controlled by group
337
+ this.group.toggleItem(this.lmId());
338
+ }
339
+ else {
340
+ // Uncontrolled mode
341
+ this._isOpen.update((v) => !v);
342
+ }
343
+ this.lmOpenChange.emit(this.isOpen());
344
+ }
345
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
346
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmAccordionItemComponent, isStandalone: true, selector: "luma-accordion-item", inputs: { lmId: { classPropertyName: "lmId", publicName: "lmId", isSignal: true, isRequired: false, transformFunction: null }, lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null }, lmOpen: { classPropertyName: "lmOpen", publicName: "lmOpen", isSignal: true, isRequired: false, transformFunction: null }, lmDisabled: { classPropertyName: "lmDisabled", publicName: "lmDisabled", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lmOpenChange: "lmOpenChange" }, host: { properties: { "attr.data-state": "isOpen() ? \"open\" : \"closed\"", "attr.data-variant": "lmVariant()" } }, providers: [
347
+ { provide: ACCORDION_ITEM, useExisting: LmAccordionItemComponent },
348
+ ], ngImport: i0, template: "<!-- Slot for trigger button -->\n<ng-content select=\"[lumaAccordionTrigger]\"></ng-content>\n\n<!-- Content wrapper with grid-rows animation -->\n<div [class]=\"contentWrapperClasses()\">\n <div class=\"overflow-hidden\">\n <ng-content select=\"[lumaAccordionContent]\"></ng-content>\n </div>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
349
+ }
350
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionItemComponent, decorators: [{
351
+ type: Component,
352
+ args: [{ selector: 'luma-accordion-item', changeDetection: ChangeDetectionStrategy.OnPush, providers: [
353
+ { provide: ACCORDION_ITEM, useExisting: LmAccordionItemComponent },
354
+ ], host: {
355
+ '[attr.data-state]': 'isOpen() ? "open" : "closed"',
356
+ '[attr.data-variant]': 'lmVariant()',
357
+ }, template: "<!-- Slot for trigger button -->\n<ng-content select=\"[lumaAccordionTrigger]\"></ng-content>\n\n<!-- Content wrapper with grid-rows animation -->\n<div [class]=\"contentWrapperClasses()\">\n <div class=\"overflow-hidden\">\n <ng-content select=\"[lumaAccordionContent]\"></ng-content>\n </div>\n</div>\n" }]
358
+ }], ctorParameters: () => [], propDecorators: { lmId: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmId", required: false }] }], lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }], lmOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmOpen", required: false }] }], lmDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDisabled", required: false }] }], lmOpenChange: [{ type: i0.Output, args: ["lmOpenChange"] }] } });
359
+
360
+ let uniqueId$2 = 0;
361
+ /**
362
+ * AccordionTriggerDirective
363
+ *
364
+ * Applied to a div element to make it the clickable trigger for the accordion.
365
+ * Uses div instead of button for maximum layout flexibility.
366
+ * Handles ARIA attributes and keyboard navigation automatically.
367
+ *
368
+ * @example Basic usage
369
+ * ```html
370
+ * <div lumaAccordionTrigger>
371
+ * <span lumaAccordionTitle>Title</span>
372
+ * <span lumaAccordionIcon>
373
+ * <svg>...</svg>
374
+ * </span>
375
+ * </div>
376
+ * ```
377
+ *
378
+ * @example Custom layout
379
+ * ```html
380
+ * <div lumaAccordionTrigger class="grid grid-cols-[auto_1fr_auto] gap-4">
381
+ * <svg class="w-6 h-6">...</svg>
382
+ * <div>
383
+ * <span lumaAccordionTitle>Title</span>
384
+ * <p class="text-sm">Description</p>
385
+ * </div>
386
+ * <span lumaAccordionIcon>
387
+ * <svg>...</svg>
388
+ * </span>
389
+ * </div>
390
+ * ```
391
+ */
392
+ class LmAccordionTriggerDirective {
393
+ item = inject(ACCORDION_ITEM);
394
+ id = ++uniqueId$2;
395
+ triggerId = `luma-accordion-trigger-${this.id}`;
396
+ contentId = `luma-accordion-content-${this.id}`;
397
+ classes = computed(() => accordionTriggerVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
398
+ onClick(event) {
399
+ if (this.item.lmDisabled())
400
+ return;
401
+ event.preventDefault();
402
+ this.item.toggle();
403
+ }
404
+ onKeydown(event) {
405
+ if (this.item.lmDisabled())
406
+ return;
407
+ // Space and Enter don't trigger on div natively - manual handler required
408
+ if (event.code === 'Space' || event.code === 'Enter') {
409
+ event.preventDefault();
410
+ this.item.toggle();
411
+ }
412
+ }
413
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
414
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmAccordionTriggerDirective, isStandalone: true, selector: "div[lumaAccordionTrigger]", host: { attributes: { "role": "button" }, listeners: { "click": "onClick($event)", "keydown": "onKeydown($event)" }, properties: { "class": "classes()", "attr.tabindex": "item.lmDisabled() ? -1 : 0", "attr.aria-expanded": "item.isOpen()", "attr.aria-controls": "contentId", "attr.aria-disabled": "item.lmDisabled() ? \"true\" : null", "id": "triggerId" } }, ngImport: i0 });
415
+ }
416
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionTriggerDirective, decorators: [{
417
+ type: Directive,
418
+ args: [{
419
+ selector: 'div[lumaAccordionTrigger]',
420
+ host: {
421
+ '[class]': 'classes()',
422
+ role: 'button',
423
+ '[attr.tabindex]': 'item.lmDisabled() ? -1 : 0',
424
+ '[attr.aria-expanded]': 'item.isOpen()',
425
+ '[attr.aria-controls]': 'contentId',
426
+ '[attr.aria-disabled]': 'item.lmDisabled() ? "true" : null',
427
+ '[id]': 'triggerId',
428
+ },
429
+ }]
430
+ }], propDecorators: { onClick: [{
431
+ type: HostListener,
432
+ args: ['click', ['$event']]
433
+ }], onKeydown: [{
434
+ type: HostListener,
435
+ args: ['keydown', ['$event']]
436
+ }] } });
437
+
438
+ /**
439
+ * AccordionTitleDirective
440
+ *
441
+ * Applies typography styles to the accordion title.
442
+ * Supports size variants for different visual hierarchies.
443
+ *
444
+ * @example Basic usage
445
+ * ```html
446
+ * <span lumaAccordionTitle>What is Luma UI?</span>
447
+ * ```
448
+ *
449
+ * @example With size variant
450
+ * ```html
451
+ * <span lumaAccordionTitle lmSize="lg">Large Title</span>
452
+ * ```
453
+ */
454
+ class LmAccordionTitleDirective {
455
+ /**
456
+ * Size variant for the title
457
+ * - sm: Small text for compact UIs
458
+ * - md: Default size (base text)
459
+ * - lg: Large text for emphasis
460
+ */
461
+ lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
462
+ classes = computed(() => accordionTitleVariants({ size: this.lmSize() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
463
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
464
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmAccordionTitleDirective, isStandalone: true, selector: "[lumaAccordionTitle]", inputs: { lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
465
+ }
466
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionTitleDirective, decorators: [{
467
+ type: Directive,
468
+ args: [{
469
+ selector: '[lumaAccordionTitle]',
470
+ host: {
471
+ '[class]': 'classes()',
472
+ },
473
+ }]
474
+ }], propDecorators: { lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }] } });
475
+
476
+ /**
477
+ * AccordionIconDirective
478
+ *
479
+ * Applies rotation animation to the accordion icon (typically a chevron).
480
+ * Must be placed on a wrapper element (span or div), not directly on the SVG.
481
+ * Automatically rotates based on the open state of the parent accordion item.
482
+ *
483
+ * @example With span wrapper (recommended)
484
+ * ```html
485
+ * <span lumaAccordionIcon>
486
+ * <svg viewBox="0 0 24 24" class="w-4 h-4">
487
+ * <path stroke="currentColor" stroke-width="2" d="M19 9l-7 7-7-7" />
488
+ * </svg>
489
+ * </span>
490
+ * ```
491
+ *
492
+ * @example With div wrapper
493
+ * ```html
494
+ * <div lumaAccordionIcon>
495
+ * <my-chevron-icon></my-chevron-icon>
496
+ * </div>
497
+ * ```
498
+ *
499
+ * @example Customize rotation via CSS variable
500
+ * ```html
501
+ * <span lumaAccordionIcon style="--luma-accordion-icon-rotation: 90deg">
502
+ * <svg>...</svg>
503
+ * </span>
504
+ * ```
505
+ */
506
+ class LmAccordionIconDirective {
507
+ item = inject(ACCORDION_ITEM);
508
+ classes = computed(() => accordionIconVariants({ open: this.item.isOpen() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
509
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionIconDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
510
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmAccordionIconDirective, isStandalone: true, selector: "span[lumaAccordionIcon], div[lumaAccordionIcon]", host: { properties: { "class": "classes()", "attr.aria-hidden": "true" } }, ngImport: i0 });
511
+ }
512
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionIconDirective, decorators: [{
513
+ type: Directive,
514
+ args: [{
515
+ selector: 'span[lumaAccordionIcon], div[lumaAccordionIcon]',
516
+ host: {
517
+ '[class]': 'classes()',
518
+ '[attr.aria-hidden]': 'true',
519
+ },
520
+ }]
521
+ }] });
522
+
523
+ let uniqueId$1 = 0;
524
+ /**
525
+ * AccordionContentDirective
526
+ *
527
+ * Applied to the content area of the accordion.
528
+ * Handles visibility, ARIA attributes, and fade animation.
529
+ *
530
+ * @example Basic usage
531
+ * ```html
532
+ * <div lumaAccordionContent>
533
+ * <p>Your content here...</p>
534
+ * </div>
535
+ * ```
536
+ */
537
+ class LmAccordionContentDirective {
538
+ item = inject(ACCORDION_ITEM);
539
+ trigger = inject(LmAccordionTriggerDirective, { optional: true });
540
+ id = ++uniqueId$1;
541
+ contentId = `luma-accordion-content-${this.id}`;
542
+ triggerId = computed(() => this.trigger?.triggerId ?? null, ...(ngDevMode ? [{ debugName: "triggerId" }] : []));
543
+ classes = computed(() => accordionContentVariants({ open: this.item.isOpen() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
544
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
545
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmAccordionContentDirective, isStandalone: true, selector: "[lumaAccordionContent]", host: { attributes: { "role": "region" }, properties: { "class": "classes()", "id": "contentId", "attr.aria-labelledby": "triggerId()", "attr.hidden": "!item.isOpen() ? \"\" : null" } }, ngImport: i0 });
546
+ }
547
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmAccordionContentDirective, decorators: [{
548
+ type: Directive,
549
+ args: [{
550
+ selector: '[lumaAccordionContent]',
551
+ host: {
552
+ '[class]': 'classes()',
553
+ role: 'region',
554
+ '[id]': 'contentId',
555
+ '[attr.aria-labelledby]': 'triggerId()',
556
+ '[attr.hidden]': '!item.isOpen() ? "" : null',
557
+ },
558
+ }]
559
+ }] });
560
+
561
+ class LmTooltipDirective {
562
+ el = inject(ElementRef);
563
+ renderer = inject(Renderer2);
564
+ platformId = inject(PLATFORM_ID);
565
+ // Inputs with lm prefix following Lumo convention
566
+ lumaTooltip = input.required(...(ngDevMode ? [{ debugName: "lumaTooltip" }] : []));
567
+ lmPosition = input('top', ...(ngDevMode ? [{ debugName: "lmPosition" }] : []));
568
+ lmHtml = input(false, ...(ngDevMode ? [{ debugName: "lmHtml" }] : []));
569
+ lmTrigger = input('hover', ...(ngDevMode ? [{ debugName: "lmTrigger" }] : []));
570
+ lmDelay = input(0, ...(ngDevMode ? [{ debugName: "lmDelay" }] : []));
571
+ // State
572
+ isVisible = signal(false, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
573
+ actualPosition = signal('top', ...(ngDevMode ? [{ debugName: "actualPosition" }] : []));
574
+ tooltipId = `tooltip-${Math.random().toString(36).slice(2, 9)}`;
575
+ tooltipElement = null;
576
+ showTimeout = null;
577
+ classes = computed(() => tooltipVariants({
578
+ position: this.actualPosition(),
579
+ visible: this.isVisible(),
580
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
581
+ constructor() {
582
+ // Create and update tooltip element reactively
583
+ effect(() => {
584
+ if (isPlatformBrowser(this.platformId)) {
585
+ this.ensureTooltipElement();
586
+ this.updateContent();
587
+ this.updateClasses();
588
+ }
589
+ });
590
+ }
591
+ ensureTooltipElement() {
592
+ if (this.tooltipElement)
593
+ return;
594
+ this.tooltipElement = this.renderer.createElement('div');
595
+ this.renderer.setAttribute(this.tooltipElement, 'id', this.tooltipId);
596
+ this.renderer.setAttribute(this.tooltipElement, 'role', 'tooltip');
597
+ this.renderer.appendChild(this.el.nativeElement, this.tooltipElement);
598
+ }
599
+ updateContent() {
600
+ if (!this.tooltipElement)
601
+ return;
602
+ const content = this.lumaTooltip();
603
+ if (this.lmHtml()) {
604
+ this.tooltipElement.innerHTML = content;
605
+ }
606
+ else {
607
+ this.tooltipElement.textContent = content;
608
+ }
609
+ }
610
+ updateClasses() {
611
+ if (!this.tooltipElement)
612
+ return;
613
+ this.tooltipElement.className = this.classes();
614
+ }
615
+ isTouchDevice() {
616
+ if (!isPlatformBrowser(this.platformId))
617
+ return false;
618
+ return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
619
+ }
620
+ getFlippedPosition(preferred) {
621
+ if (!this.tooltipElement || !isPlatformBrowser(this.platformId)) {
622
+ return preferred;
623
+ }
624
+ const triggerRect = this.el.nativeElement.getBoundingClientRect();
625
+ const tooltipRect = this.tooltipElement.getBoundingClientRect();
626
+ const viewportWidth = window.innerWidth;
627
+ const viewportHeight = window.innerHeight;
628
+ const offset = 8; // --luma-tooltip-offset
629
+ switch (preferred) {
630
+ case 'top':
631
+ if (triggerRect.top - tooltipRect.height - offset < 0) {
632
+ return 'bottom';
633
+ }
634
+ break;
635
+ case 'bottom':
636
+ if (triggerRect.bottom + tooltipRect.height + offset > viewportHeight) {
637
+ return 'top';
638
+ }
639
+ break;
640
+ case 'left':
641
+ if (triggerRect.left - tooltipRect.width - offset < 0) {
642
+ return 'right';
643
+ }
644
+ break;
645
+ case 'right':
646
+ if (triggerRect.right + tooltipRect.width + offset > viewportWidth) {
647
+ return 'left';
648
+ }
649
+ break;
650
+ }
651
+ return preferred;
652
+ }
653
+ onMouseEnter() {
654
+ // Ignore hover on touch devices
655
+ if (this.isTouchDevice())
656
+ return;
657
+ if (this.lmTrigger() !== 'hover')
658
+ return;
659
+ this.show();
660
+ }
661
+ onMouseLeave() {
662
+ if (this.isTouchDevice())
663
+ return;
664
+ if (this.lmTrigger() !== 'hover')
665
+ return;
666
+ this.hide();
667
+ }
668
+ onClick() {
669
+ // On touch devices, click acts as toggle even with trigger='hover'
670
+ if (this.isTouchDevice() || this.lmTrigger() === 'click') {
671
+ this.toggle();
672
+ }
673
+ }
674
+ onFocus() {
675
+ if (this.lmTrigger() === 'focus' || this.lmTrigger() === 'hover') {
676
+ this.show();
677
+ }
678
+ }
679
+ onBlur() {
680
+ if (this.lmTrigger() === 'focus' || this.lmTrigger() === 'hover') {
681
+ this.hide();
682
+ }
683
+ }
684
+ onEscape() {
685
+ this.hide();
686
+ }
687
+ onDocumentClick(event) {
688
+ if (this.lmTrigger() !== 'click' && !this.isTouchDevice())
689
+ return;
690
+ if (!this.el.nativeElement.contains(event.target)) {
691
+ this.hide();
692
+ }
693
+ }
694
+ onDocumentTouch(event) {
695
+ if (!this.isVisible())
696
+ return;
697
+ if (!this.el.nativeElement.contains(event.target)) {
698
+ this.hide();
699
+ }
700
+ }
701
+ show() {
702
+ if (this.showTimeout)
703
+ clearTimeout(this.showTimeout);
704
+ const delay = this.lmDelay();
705
+ const showAction = () => {
706
+ this.isVisible.set(true);
707
+ // Calculate position with auto-flip after tooltip is visible
708
+ requestAnimationFrame(() => {
709
+ const actualPosition = this.getFlippedPosition(this.lmPosition());
710
+ this.actualPosition.set(actualPosition);
711
+ this.updateClasses();
712
+ });
713
+ };
714
+ if (delay > 0) {
715
+ this.showTimeout = setTimeout(showAction, delay);
716
+ }
717
+ else {
718
+ showAction();
719
+ }
720
+ }
721
+ hide() {
722
+ if (this.showTimeout) {
723
+ clearTimeout(this.showTimeout);
724
+ this.showTimeout = null;
725
+ }
726
+ this.isVisible.set(false);
727
+ this.updateClasses();
728
+ }
729
+ toggle() {
730
+ if (this.isVisible()) {
731
+ this.hide();
732
+ }
733
+ else {
734
+ this.show();
735
+ }
736
+ }
737
+ ngOnDestroy() {
738
+ if (this.showTimeout)
739
+ clearTimeout(this.showTimeout);
740
+ if (this.tooltipElement) {
741
+ this.renderer.removeChild(this.el.nativeElement, this.tooltipElement);
742
+ }
743
+ }
744
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
745
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmTooltipDirective, isStandalone: true, selector: "[lumaTooltip]", inputs: { lumaTooltip: { classPropertyName: "lumaTooltip", publicName: "lumaTooltip", isSignal: true, isRequired: true, transformFunction: null }, lmPosition: { classPropertyName: "lmPosition", publicName: "lmPosition", isSignal: true, isRequired: false, transformFunction: null }, lmHtml: { classPropertyName: "lmHtml", publicName: "lmHtml", isSignal: true, isRequired: false, transformFunction: null }, lmTrigger: { classPropertyName: "lmTrigger", publicName: "lmTrigger", isSignal: true, isRequired: false, transformFunction: null }, lmDelay: { classPropertyName: "lmDelay", publicName: "lmDelay", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "click": "onClick()", "focus": "onFocus()", "blur": "onBlur()", "document:keydown.escape": "onEscape()", "document:click": "onDocumentClick($event)", "document:touchstart": "onDocumentTouch($event)" }, properties: { "attr.aria-describedby": "tooltipId", "style.position": "\"relative\"" } }, ngImport: i0 });
746
+ }
747
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTooltipDirective, decorators: [{
748
+ type: Directive,
749
+ args: [{
750
+ selector: '[lumaTooltip]',
751
+ host: {
752
+ '[attr.aria-describedby]': 'tooltipId',
753
+ '[style.position]': '"relative"',
754
+ },
755
+ }]
756
+ }], ctorParameters: () => [], propDecorators: { lumaTooltip: [{ type: i0.Input, args: [{ isSignal: true, alias: "lumaTooltip", required: true }] }], lmPosition: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmPosition", required: false }] }], lmHtml: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmHtml", required: false }] }], lmTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmTrigger", required: false }] }], lmDelay: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDelay", required: false }] }], onMouseEnter: [{
757
+ type: HostListener,
758
+ args: ['mouseenter']
759
+ }], onMouseLeave: [{
760
+ type: HostListener,
761
+ args: ['mouseleave']
762
+ }], onClick: [{
763
+ type: HostListener,
764
+ args: ['click']
765
+ }], onFocus: [{
766
+ type: HostListener,
767
+ args: ['focus']
768
+ }], onBlur: [{
769
+ type: HostListener,
770
+ args: ['blur']
771
+ }], onEscape: [{
772
+ type: HostListener,
773
+ args: ['document:keydown.escape']
774
+ }], onDocumentClick: [{
775
+ type: HostListener,
776
+ args: ['document:click', ['$event']]
777
+ }], onDocumentTouch: [{
778
+ type: HostListener,
779
+ args: ['document:touchstart', ['$event']]
780
+ }] } });
781
+
782
+ /**
783
+ * Injection token for tabs group
784
+ * Allows child components to access parent tabs state
785
+ */
786
+ const TABS_GROUP = new InjectionToken('TabsGroup');
787
+ /**
788
+ * Injection token for tabs list
789
+ * Allows indicator to access trigger positions
790
+ */
791
+ const TABS_LIST = new InjectionToken('TabsList');
792
+
793
+ /**
794
+ * Tabs container component
795
+ *
796
+ * Manages tab selection state, keyboard navigation, and provides context
797
+ * to child components (TabsList, TabsTrigger, TabsPanel).
798
+ *
799
+ * @example
800
+ * ```html
801
+ * <luma-tabs [lmValue]="selectedTab()" (lmValueChange)="onSelect($event)">
802
+ * <div lumaTabsList>
803
+ * <button lumaTabsTrigger="tab-1">Tab 1</button>
804
+ * <button lumaTabsTrigger="tab-2">Tab 2</button>
805
+ * </div>
806
+ * <div lumaTabsPanel="tab-1">Content 1</div>
807
+ * <div lumaTabsPanel="tab-2">Content 2</div>
808
+ * </luma-tabs>
809
+ * ```
810
+ */
811
+ class LmTabsComponent {
812
+ /** Controlled value - currently selected tab */
813
+ lmValue = input(null, ...(ngDevMode ? [{ debugName: "lmValue" }] : []));
814
+ /** Default value for uncontrolled mode */
815
+ lmDefaultValue = input('', ...(ngDevMode ? [{ debugName: "lmDefaultValue" }] : []));
816
+ /** Visual style: underline, background, or pill */
817
+ lmVariant = input('underline', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
818
+ /** Whether to lazy load panel content */
819
+ lmLazy = input(true, ...(ngDevMode ? [{ debugName: "lmLazy" }] : []));
820
+ /** Emits when selected tab changes */
821
+ lmValueChange = output();
822
+ /** Internal state for the selected value */
823
+ value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : []));
824
+ /** Map of registered triggers for keyboard navigation */
825
+ triggers = new Map();
826
+ /** Ordered list of trigger values for navigation */
827
+ triggerOrder = [];
828
+ constructor() {
829
+ // Sync controlled value to internal state
830
+ effect(() => {
831
+ const controlled = this.lmValue();
832
+ const defaultVal = this.lmDefaultValue();
833
+ if (controlled !== null) {
834
+ this.value.set(controlled);
835
+ }
836
+ else if (this.value() === null && defaultVal) {
837
+ this.value.set(defaultVal);
838
+ }
839
+ });
840
+ }
841
+ /**
842
+ * Select a tab by value
843
+ */
844
+ select(tabValue) {
845
+ if (this.value() === tabValue)
846
+ return;
847
+ this.value.set(tabValue);
848
+ this.lmValueChange.emit(tabValue);
849
+ }
850
+ /**
851
+ * Register a trigger element for keyboard navigation
852
+ */
853
+ registerTrigger(tabValue, element) {
854
+ this.triggers.set(tabValue, element);
855
+ if (!this.triggerOrder.includes(tabValue)) {
856
+ this.triggerOrder.push(tabValue);
857
+ }
858
+ }
859
+ /**
860
+ * Unregister a trigger element
861
+ */
862
+ unregisterTrigger(tabValue) {
863
+ this.triggers.delete(tabValue);
864
+ this.triggerOrder = this.triggerOrder.filter((v) => v !== tabValue);
865
+ }
866
+ /**
867
+ * Get all registered triggers
868
+ */
869
+ getTriggers() {
870
+ return this.triggers;
871
+ }
872
+ /**
873
+ * Focus next trigger in the list
874
+ */
875
+ focusNextTrigger() {
876
+ const currentIndex = this.getCurrentTriggerIndex();
877
+ const nextIndex = (currentIndex + 1) % this.triggerOrder.length;
878
+ this.focusTriggerAtIndex(nextIndex);
879
+ }
880
+ /**
881
+ * Focus previous trigger in the list
882
+ */
883
+ focusPreviousTrigger() {
884
+ const currentIndex = this.getCurrentTriggerIndex();
885
+ const prevIndex = currentIndex <= 0 ? this.triggerOrder.length - 1 : currentIndex - 1;
886
+ this.focusTriggerAtIndex(prevIndex);
887
+ }
888
+ /**
889
+ * Focus first trigger
890
+ */
891
+ focusFirstTrigger() {
892
+ this.focusTriggerAtIndex(0);
893
+ }
894
+ /**
895
+ * Focus last trigger
896
+ */
897
+ focusLastTrigger() {
898
+ this.focusTriggerAtIndex(this.triggerOrder.length - 1);
899
+ }
900
+ getCurrentTriggerIndex() {
901
+ const currentValue = this.value();
902
+ return currentValue ? this.triggerOrder.indexOf(currentValue) : 0;
903
+ }
904
+ focusTriggerAtIndex(index) {
905
+ const value = this.triggerOrder[index];
906
+ const element = this.triggers.get(value);
907
+ if (element) {
908
+ element.focus();
909
+ this.select(value);
910
+ }
911
+ }
912
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
913
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmTabsComponent, isStandalone: true, selector: "luma-tabs", inputs: { lmValue: { classPropertyName: "lmValue", publicName: "lmValue", isSignal: true, isRequired: false, transformFunction: null }, lmDefaultValue: { classPropertyName: "lmDefaultValue", publicName: "lmDefaultValue", isSignal: true, isRequired: false, transformFunction: null }, lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null }, lmLazy: { classPropertyName: "lmLazy", publicName: "lmLazy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lmValueChange: "lmValueChange" }, host: { properties: { "class": "\"block w-full\"" } }, providers: [
914
+ {
915
+ provide: TABS_GROUP,
916
+ useExisting: LmTabsComponent,
917
+ },
918
+ ], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
919
+ }
920
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsComponent, decorators: [{
921
+ type: Component,
922
+ args: [{
923
+ selector: 'luma-tabs',
924
+ template: `<ng-content />`,
925
+ changeDetection: ChangeDetectionStrategy.OnPush,
926
+ providers: [
927
+ {
928
+ provide: TABS_GROUP,
929
+ useExisting: LmTabsComponent,
930
+ },
931
+ ],
932
+ host: {
933
+ '[class]': '"block w-full"',
934
+ },
935
+ }]
936
+ }], ctorParameters: () => [], propDecorators: { lmValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmValue", required: false }] }], lmDefaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDefaultValue", required: false }] }], lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }], lmLazy: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmLazy", required: false }] }], lmValueChange: [{ type: i0.Output, args: ["lmValueChange"] }] } });
937
+
938
+ /**
939
+ * Tabs list directive
940
+ *
941
+ * Container for tab triggers with role="tablist".
942
+ * Provides context for the indicator component.
943
+ *
944
+ * @example
945
+ * ```html
946
+ * <div lumaTabsList>
947
+ * <button lumaTabsTrigger="tab-1">Tab 1</button>
948
+ * <button lumaTabsTrigger="tab-2">Tab 2</button>
949
+ * </div>
950
+ * ```
951
+ */
952
+ class LmTabsListDirective {
953
+ elementRef = inject((ElementRef));
954
+ tabsGroup = inject(TABS_GROUP);
955
+ /** Whether horizontal scrolling is enabled */
956
+ lmScrollable = false;
957
+ classes = computed(() => tabsListVariants({
958
+ style: this.tabsGroup.lmVariant(),
959
+ scrollable: this.lmScrollable,
960
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
961
+ /**
962
+ * Get the currently active trigger element
963
+ */
964
+ getActiveTrigger() {
965
+ const currentValue = this.tabsGroup.value();
966
+ if (!currentValue)
967
+ return null;
968
+ const triggers = this.tabsGroup.getTriggers();
969
+ return triggers.get(currentValue) || null;
970
+ }
971
+ /**
972
+ * Handle mouse wheel for horizontal scroll
973
+ */
974
+ onWheel(event) {
975
+ if (this.lmScrollable) {
976
+ event.preventDefault();
977
+ this.elementRef.nativeElement.scrollLeft += event.deltaY;
978
+ }
979
+ }
980
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsListDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
981
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmTabsListDirective, isStandalone: true, selector: "[lumaTabsList]", host: { attributes: { "role": "tablist" }, listeners: { "wheel": "onWheel($event)" }, properties: { "attr.aria-orientation": "\"horizontal\"", "class": "classes()" } }, providers: [
982
+ {
983
+ provide: TABS_LIST,
984
+ useExisting: LmTabsListDirective,
985
+ },
986
+ ], ngImport: i0 });
987
+ }
988
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsListDirective, decorators: [{
989
+ type: Directive,
990
+ args: [{
991
+ selector: '[lumaTabsList]',
992
+ providers: [
993
+ {
994
+ provide: TABS_LIST,
995
+ useExisting: LmTabsListDirective,
996
+ },
997
+ ],
998
+ host: {
999
+ role: 'tablist',
1000
+ '[attr.aria-orientation]': '"horizontal"',
1001
+ '[class]': 'classes()',
1002
+ },
1003
+ }]
1004
+ }], propDecorators: { onWheel: [{
1005
+ type: HostListener,
1006
+ args: ['wheel', ['$event']]
1007
+ }] } });
1008
+
1009
+ /**
1010
+ * Tabs trigger directive
1011
+ *
1012
+ * Individual tab button with role="tab" and keyboard navigation support.
1013
+ * Follows WAI-ARIA tabs pattern with roving tabindex.
1014
+ *
1015
+ * @example
1016
+ * ```html
1017
+ * <button lumaTabsTrigger="tab-1">Tab 1</button>
1018
+ * ```
1019
+ */
1020
+ class LmTabsTriggerDirective {
1021
+ el = inject((ElementRef));
1022
+ tabsGroup = inject(TABS_GROUP);
1023
+ /** Tab value identifier */
1024
+ lumaTabsTrigger = input.required(...(ngDevMode ? [{ debugName: "lumaTabsTrigger" }] : []));
1025
+ /** Whether this trigger is disabled */
1026
+ lmDisabled = input(false, ...(ngDevMode ? [{ debugName: "lmDisabled" }] : []));
1027
+ /** Computed: whether this tab is selected */
1028
+ isSelected = computed(() => this.tabsGroup.value() === this.lumaTabsTrigger(), ...(ngDevMode ? [{ debugName: "isSelected" }] : []));
1029
+ /** Computed: ID for the trigger element */
1030
+ triggerId = computed(() => `tab-trigger-${this.lumaTabsTrigger()}`, ...(ngDevMode ? [{ debugName: "triggerId" }] : []));
1031
+ /** Computed: ID for the corresponding panel */
1032
+ panelId = computed(() => `tab-panel-${this.lumaTabsTrigger()}`, ...(ngDevMode ? [{ debugName: "panelId" }] : []));
1033
+ /** Computed: CSS classes from CVA */
1034
+ classes = computed(() => tabsTriggerVariants({
1035
+ style: this.tabsGroup.lmVariant(),
1036
+ selected: this.isSelected(),
1037
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1038
+ ngOnInit() {
1039
+ this.tabsGroup.registerTrigger(this.lumaTabsTrigger(), this.el.nativeElement);
1040
+ }
1041
+ ngOnDestroy() {
1042
+ this.tabsGroup.unregisterTrigger(this.lumaTabsTrigger());
1043
+ }
1044
+ onClick() {
1045
+ if (this.lmDisabled())
1046
+ return;
1047
+ this.tabsGroup.select(this.lumaTabsTrigger());
1048
+ }
1049
+ onKeydown(event) {
1050
+ if (this.lmDisabled())
1051
+ return;
1052
+ switch (event.key) {
1053
+ case 'ArrowRight':
1054
+ event.preventDefault();
1055
+ this.tabsGroup.focusNextTrigger();
1056
+ break;
1057
+ case 'ArrowLeft':
1058
+ event.preventDefault();
1059
+ this.tabsGroup.focusPreviousTrigger();
1060
+ break;
1061
+ case 'Home':
1062
+ event.preventDefault();
1063
+ this.tabsGroup.focusFirstTrigger();
1064
+ break;
1065
+ case 'End':
1066
+ event.preventDefault();
1067
+ this.tabsGroup.focusLastTrigger();
1068
+ break;
1069
+ case 'Enter':
1070
+ case ' ':
1071
+ event.preventDefault();
1072
+ this.tabsGroup.select(this.lumaTabsTrigger());
1073
+ break;
1074
+ }
1075
+ }
1076
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1077
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmTabsTriggerDirective, isStandalone: true, selector: "[lumaTabsTrigger]", inputs: { lumaTabsTrigger: { classPropertyName: "lumaTabsTrigger", publicName: "lumaTabsTrigger", isSignal: true, isRequired: true, transformFunction: null }, lmDisabled: { classPropertyName: "lmDisabled", publicName: "lmDisabled", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "tab" }, listeners: { "click": "onClick()", "keydown": "onKeydown($event)" }, properties: { "attr.id": "triggerId()", "attr.aria-selected": "isSelected()", "attr.aria-controls": "panelId()", "attr.tabindex": "isSelected() ? 0 : -1", "class": "classes()" } }, ngImport: i0 });
1078
+ }
1079
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsTriggerDirective, decorators: [{
1080
+ type: Directive,
1081
+ args: [{
1082
+ selector: '[lumaTabsTrigger]',
1083
+ host: {
1084
+ role: 'tab',
1085
+ '[attr.id]': 'triggerId()',
1086
+ '[attr.aria-selected]': 'isSelected()',
1087
+ '[attr.aria-controls]': 'panelId()',
1088
+ '[attr.tabindex]': 'isSelected() ? 0 : -1',
1089
+ '[class]': 'classes()',
1090
+ },
1091
+ }]
1092
+ }], propDecorators: { lumaTabsTrigger: [{ type: i0.Input, args: [{ isSignal: true, alias: "lumaTabsTrigger", required: true }] }], lmDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDisabled", required: false }] }], onClick: [{
1093
+ type: HostListener,
1094
+ args: ['click']
1095
+ }], onKeydown: [{
1096
+ type: HostListener,
1097
+ args: ['keydown', ['$event']]
1098
+ }] } });
1099
+
1100
+ /**
1101
+ * Tabs panel directive
1102
+ *
1103
+ * Content panel with role="tabpanel" and lazy loading support.
1104
+ * When lazy loading is enabled, content is only rendered after
1105
+ * the tab has been selected at least once, then cached.
1106
+ *
1107
+ * @example
1108
+ * ```html
1109
+ * <div lumaTabsPanel="tab-1">Content 1</div>
1110
+ *
1111
+ * <!-- With lazy loading (default when lmLazy=true on parent) -->
1112
+ * <ng-template lumaTabsPanel="tab-1">
1113
+ * <expensive-component />
1114
+ * </ng-template>
1115
+ * ```
1116
+ */
1117
+ class LmTabsPanelDirective {
1118
+ tabsGroup = inject(TABS_GROUP);
1119
+ templateRef = inject((TemplateRef), {
1120
+ optional: true,
1121
+ });
1122
+ viewContainer = inject(ViewContainerRef);
1123
+ /** Panel value identifier */
1124
+ lumaTabsPanel = input.required(...(ngDevMode ? [{ debugName: "lumaTabsPanel" }] : []));
1125
+ /** Track if panel has ever been selected (for lazy loading cache) */
1126
+ hasBeenSelected = signal(false, ...(ngDevMode ? [{ debugName: "hasBeenSelected" }] : []));
1127
+ /** Computed: whether this panel is currently selected */
1128
+ isSelected = computed(() => this.tabsGroup.value() === this.lumaTabsPanel(), ...(ngDevMode ? [{ debugName: "isSelected" }] : []));
1129
+ /** Computed: whether this panel should be visible/rendered */
1130
+ isVisible = computed(() => {
1131
+ const selected = this.isSelected();
1132
+ const lazy = this.tabsGroup.lmLazy();
1133
+ // If lazy loading is disabled, always show selected panel
1134
+ if (!lazy)
1135
+ return selected;
1136
+ // With lazy loading: render if ever selected, but only show if currently selected
1137
+ return this.hasBeenSelected() && selected;
1138
+ }, ...(ngDevMode ? [{ debugName: "isVisible" }] : []));
1139
+ /** Computed: whether content should be rendered (for lazy loading) */
1140
+ shouldRender = computed(() => {
1141
+ const lazy = this.tabsGroup.lmLazy();
1142
+ return lazy ? this.hasBeenSelected() : true;
1143
+ }, ...(ngDevMode ? [{ debugName: "shouldRender" }] : []));
1144
+ /** Computed: ID for the panel element */
1145
+ panelId = computed(() => `tab-panel-${this.lumaTabsPanel()}`, ...(ngDevMode ? [{ debugName: "panelId" }] : []));
1146
+ /** Computed: ID for the corresponding trigger */
1147
+ triggerId = computed(() => `tab-trigger-${this.lumaTabsPanel()}`, ...(ngDevMode ? [{ debugName: "triggerId" }] : []));
1148
+ /** Computed: CSS classes from CVA */
1149
+ classes = computed(() => tabsPanelVariants({
1150
+ visible: this.isVisible(),
1151
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1152
+ constructor() {
1153
+ // Track when panel is selected for lazy loading cache
1154
+ effect(() => {
1155
+ if (this.isSelected() && !this.hasBeenSelected()) {
1156
+ this.hasBeenSelected.set(true);
1157
+ }
1158
+ });
1159
+ // Handle template-based lazy loading
1160
+ effect(() => {
1161
+ if (this.templateRef && this.shouldRender()) {
1162
+ if (this.viewContainer.length === 0) {
1163
+ this.viewContainer.createEmbeddedView(this.templateRef);
1164
+ }
1165
+ }
1166
+ });
1167
+ }
1168
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsPanelDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1169
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmTabsPanelDirective, isStandalone: true, selector: "[lumaTabsPanel]", inputs: { lumaTabsPanel: { classPropertyName: "lumaTabsPanel", publicName: "lumaTabsPanel", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "role": "tabpanel" }, properties: { "attr.id": "panelId()", "attr.aria-labelledby": "triggerId()", "attr.tabindex": "0", "class": "classes()", "hidden": "!isVisible()" } }, ngImport: i0 });
1170
+ }
1171
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsPanelDirective, decorators: [{
1172
+ type: Directive,
1173
+ args: [{
1174
+ selector: '[lumaTabsPanel]',
1175
+ host: {
1176
+ role: 'tabpanel',
1177
+ '[attr.id]': 'panelId()',
1178
+ '[attr.aria-labelledby]': 'triggerId()',
1179
+ '[attr.tabindex]': '0',
1180
+ '[class]': 'classes()',
1181
+ '[hidden]': '!isVisible()',
1182
+ },
1183
+ }]
1184
+ }], ctorParameters: () => [], propDecorators: { lumaTabsPanel: [{ type: i0.Input, args: [{ isSignal: true, alias: "lumaTabsPanel", required: true }] }] } });
1185
+
1186
+ /**
1187
+ * Tabs indicator component
1188
+ *
1189
+ * Animated indicator that slides between tabs for the underline style.
1190
+ * Uses CSS transform for smooth, GPU-accelerated animation.
1191
+ *
1192
+ * @example
1193
+ * ```html
1194
+ * <div lumaTabsList>
1195
+ * <button lumaTabsTrigger="tab-1">Tab 1</button>
1196
+ * <button lumaTabsTrigger="tab-2">Tab 2</button>
1197
+ * <luma-tabs-indicator />
1198
+ * </div>
1199
+ * ```
1200
+ */
1201
+ class LmTabsIndicatorComponent {
1202
+ platformId = inject(PLATFORM_ID);
1203
+ tabsGroup = inject(TABS_GROUP);
1204
+ tabsList = inject(TABS_LIST);
1205
+ /** Indicator width in pixels */
1206
+ indicatorWidth = signal(0, ...(ngDevMode ? [{ debugName: "indicatorWidth" }] : []));
1207
+ /** Indicator X or Y position */
1208
+ indicatorPosition = signal(0, ...(ngDevMode ? [{ debugName: "indicatorPosition" }] : []));
1209
+ /** Resize observer for recalculating position */
1210
+ resizeObserver = null;
1211
+ /** Computed: CSS classes from CVA */
1212
+ classes = computed(() => {
1213
+ const style = this.tabsGroup.lmVariant();
1214
+ // Only show indicator for underline style
1215
+ const isVisible = style === 'underline';
1216
+ return tabsIndicatorVariants({
1217
+ visible: isVisible,
1218
+ });
1219
+ }, ...(ngDevMode ? [{ debugName: "classes" }] : []));
1220
+ /** Computed: CSS transform for positioning */
1221
+ indicatorTransform = computed(() => {
1222
+ return `translateX(${this.indicatorPosition()}px)`;
1223
+ }, ...(ngDevMode ? [{ debugName: "indicatorTransform" }] : []));
1224
+ constructor() {
1225
+ // Update indicator position when selection changes
1226
+ effect(() => {
1227
+ // Read the value to track changes
1228
+ this.tabsGroup.value();
1229
+ // Defer position calculation to ensure DOM is ready
1230
+ if (isPlatformBrowser(this.platformId)) {
1231
+ // Use setTimeout to ensure triggers are registered
1232
+ setTimeout(() => this.updateIndicatorPosition(), 0);
1233
+ }
1234
+ });
1235
+ }
1236
+ ngAfterViewInit() {
1237
+ if (!isPlatformBrowser(this.platformId))
1238
+ return;
1239
+ // Initial position calculation with delay to ensure triggers are registered
1240
+ setTimeout(() => this.updateIndicatorPosition(), 0);
1241
+ // Watch for container resize
1242
+ this.resizeObserver = new ResizeObserver(() => {
1243
+ this.updateIndicatorPosition();
1244
+ });
1245
+ this.resizeObserver.observe(this.tabsList.elementRef.nativeElement);
1246
+ }
1247
+ ngOnDestroy() {
1248
+ this.resizeObserver?.disconnect();
1249
+ }
1250
+ updateIndicatorPosition() {
1251
+ const activeTrigger = this.tabsList.getActiveTrigger();
1252
+ if (!activeTrigger) {
1253
+ this.indicatorWidth.set(0);
1254
+ return;
1255
+ }
1256
+ const triggerRect = activeTrigger.getBoundingClientRect();
1257
+ const listRect = this.tabsList.elementRef.nativeElement.getBoundingClientRect();
1258
+ // Horizontal positioning
1259
+ this.indicatorPosition.set(triggerRect.left - listRect.left);
1260
+ this.indicatorWidth.set(triggerRect.width);
1261
+ }
1262
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1263
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: LmTabsIndicatorComponent, isStandalone: true, selector: "luma-tabs-indicator", host: { properties: { "class": "classes()", "style.width.px": "indicatorWidth()", "style.transform": "indicatorTransform()" } }, ngImport: i0, template: ``, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1264
+ }
1265
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsIndicatorComponent, decorators: [{
1266
+ type: Component,
1267
+ args: [{
1268
+ selector: 'luma-tabs-indicator',
1269
+ template: ``,
1270
+ changeDetection: ChangeDetectionStrategy.OnPush,
1271
+ host: {
1272
+ '[class]': 'classes()',
1273
+ '[style.width.px]': 'indicatorWidth()',
1274
+ '[style.transform]': 'indicatorTransform()',
1275
+ },
1276
+ }]
1277
+ }], ctorParameters: () => [] });
1278
+
1279
+ /**
1280
+ * Injection token for modal context
1281
+ * Allows child components to access parent modal state
1282
+ */
1283
+ const MODAL_CONTEXT = new InjectionToken('ModalContext');
1284
+
1285
+ let uniqueId = 0;
1286
+ /**
1287
+ * Modal container component
1288
+ *
1289
+ * Manages modal open/close state, escape key handling, and provides context
1290
+ * to child components (ModalOverlay, ModalContainer, etc.).
1291
+ *
1292
+ * Supports both controlled and uncontrolled modes:
1293
+ * - Controlled: Use [lmOpen] and (lmOpenChange)
1294
+ * - Uncontrolled: Use [lmDefaultOpen] and access via template reference
1295
+ *
1296
+ * @example
1297
+ * ```html
1298
+ * <!-- Controlled mode -->
1299
+ * <luma-modal [lmOpen]="isOpen()" (lmOpenChange)="isOpen.set($event)">
1300
+ * <luma-modal-overlay>
1301
+ * <luma-modal-container>
1302
+ * <div lumaModalHeader>
1303
+ * <h2 lumaModalTitle>Title</h2>
1304
+ * <luma-modal-close />
1305
+ * </div>
1306
+ * <div lumaModalContent>Content</div>
1307
+ * <div lumaModalFooter>
1308
+ * <button lumaButton (click)="isOpen.set(false)">Close</button>
1309
+ * </div>
1310
+ * </luma-modal-container>
1311
+ * </luma-modal-overlay>
1312
+ * </luma-modal>
1313
+ *
1314
+ * <!-- Uncontrolled mode -->
1315
+ * <luma-modal #modal [lmDefaultOpen]="false">
1316
+ * ...
1317
+ * </luma-modal>
1318
+ * <button (click)="modal.open()">Open</button>
1319
+ * ```
1320
+ */
1321
+ class LmModalComponent {
1322
+ platformId = inject(PLATFORM_ID);
1323
+ document = inject(DOCUMENT);
1324
+ /** Controlled open state (null = uncontrolled mode) */
1325
+ lmOpen = input(null, ...(ngDevMode ? [{ debugName: "lmOpen" }] : []));
1326
+ /** Default open state for uncontrolled mode */
1327
+ lmDefaultOpen = input(false, ...(ngDevMode ? [{ debugName: "lmDefaultOpen" }] : []));
1328
+ /** Size variant */
1329
+ lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
1330
+ /** Close when clicking the overlay */
1331
+ lmCloseOnOverlay = input(true, ...(ngDevMode ? [{ debugName: "lmCloseOnOverlay" }] : []));
1332
+ /** Close when pressing Escape key */
1333
+ lmCloseOnEscape = input(true, ...(ngDevMode ? [{ debugName: "lmCloseOnEscape" }] : []));
1334
+ /** Emits when open state changes */
1335
+ lmOpenChange = output();
1336
+ /** Internal open state for uncontrolled mode */
1337
+ internalOpen = signal(false, ...(ngDevMode ? [{ debugName: "internalOpen" }] : []));
1338
+ /** Unique modal ID for accessibility */
1339
+ modalId = `modal-${uniqueId++}`;
1340
+ /** Previously focused element for focus restoration */
1341
+ previouslyFocused = null;
1342
+ /** Escape key handler */
1343
+ escapeHandler = null;
1344
+ /** Computed: current open state (controlled or uncontrolled) */
1345
+ isOpen = computed(() => {
1346
+ const controlled = this.lmOpen();
1347
+ return controlled !== null ? controlled : this.internalOpen();
1348
+ }, ...(ngDevMode ? [{ debugName: "isOpen" }] : []));
1349
+ /** Computed: size signal for context */
1350
+ size = computed(() => this.lmSize(), ...(ngDevMode ? [{ debugName: "size" }] : []));
1351
+ /** Computed: closeOnOverlay signal for context */
1352
+ closeOnOverlay = computed(() => this.lmCloseOnOverlay(), ...(ngDevMode ? [{ debugName: "closeOnOverlay" }] : []));
1353
+ /** Computed: closeOnEscape signal for context */
1354
+ closeOnEscape = computed(() => this.lmCloseOnEscape(), ...(ngDevMode ? [{ debugName: "closeOnEscape" }] : []));
1355
+ constructor() {
1356
+ // Initialize uncontrolled mode with default value
1357
+ effect(() => {
1358
+ if (this.lmOpen() === null) {
1359
+ this.internalOpen.set(this.lmDefaultOpen());
1360
+ }
1361
+ });
1362
+ // Handle body scroll lock and escape key
1363
+ effect(() => {
1364
+ if (!isPlatformBrowser(this.platformId))
1365
+ return;
1366
+ if (this.isOpen()) {
1367
+ this.lockBodyScroll();
1368
+ this.registerEscapeHandler();
1369
+ this.storeFocus();
1370
+ }
1371
+ else {
1372
+ // Delay scroll unlock to allow fade animation to complete (250ms)
1373
+ setTimeout(() => this.unlockBodyScroll(), 250);
1374
+ this.unregisterEscapeHandler();
1375
+ }
1376
+ });
1377
+ }
1378
+ ngOnDestroy() {
1379
+ if (isPlatformBrowser(this.platformId)) {
1380
+ this.unlockBodyScroll();
1381
+ this.unregisterEscapeHandler();
1382
+ }
1383
+ }
1384
+ /**
1385
+ * Open the modal
1386
+ */
1387
+ open() {
1388
+ if (this.lmOpen() === null) {
1389
+ this.internalOpen.set(true);
1390
+ }
1391
+ this.lmOpenChange.emit(true);
1392
+ }
1393
+ /**
1394
+ * Close the modal
1395
+ */
1396
+ close() {
1397
+ if (this.lmOpen() === null) {
1398
+ this.internalOpen.set(false);
1399
+ }
1400
+ this.lmOpenChange.emit(false);
1401
+ this.restoreFocus();
1402
+ }
1403
+ storeFocus() {
1404
+ this.previouslyFocused = this.document.activeElement;
1405
+ }
1406
+ restoreFocus() {
1407
+ if (this.previouslyFocused &&
1408
+ typeof this.previouslyFocused.focus === 'function') {
1409
+ // Use setTimeout to ensure DOM has updated
1410
+ setTimeout(() => {
1411
+ this.previouslyFocused?.focus();
1412
+ this.previouslyFocused = null;
1413
+ }, 0);
1414
+ }
1415
+ }
1416
+ lockBodyScroll() {
1417
+ const body = this.document.body;
1418
+ const scrollbarWidth = window.innerWidth - this.document.documentElement.clientWidth;
1419
+ body.style.overflow = 'hidden';
1420
+ body.style.paddingRight = `${scrollbarWidth}px`;
1421
+ }
1422
+ unlockBodyScroll() {
1423
+ const body = this.document.body;
1424
+ body.style.overflow = '';
1425
+ body.style.paddingRight = '';
1426
+ }
1427
+ registerEscapeHandler() {
1428
+ if (this.escapeHandler)
1429
+ return;
1430
+ this.escapeHandler = (event) => {
1431
+ if (event.key === 'Escape' && this.lmCloseOnEscape()) {
1432
+ event.preventDefault();
1433
+ this.close();
1434
+ }
1435
+ };
1436
+ this.document.addEventListener('keydown', this.escapeHandler);
1437
+ }
1438
+ unregisterEscapeHandler() {
1439
+ if (this.escapeHandler) {
1440
+ this.document.removeEventListener('keydown', this.escapeHandler);
1441
+ this.escapeHandler = null;
1442
+ }
1443
+ }
1444
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1445
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmModalComponent, isStandalone: true, selector: "luma-modal", inputs: { lmOpen: { classPropertyName: "lmOpen", publicName: "lmOpen", isSignal: true, isRequired: false, transformFunction: null }, lmDefaultOpen: { classPropertyName: "lmDefaultOpen", publicName: "lmDefaultOpen", isSignal: true, isRequired: false, transformFunction: null }, lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null }, lmCloseOnOverlay: { classPropertyName: "lmCloseOnOverlay", publicName: "lmCloseOnOverlay", isSignal: true, isRequired: false, transformFunction: null }, lmCloseOnEscape: { classPropertyName: "lmCloseOnEscape", publicName: "lmCloseOnEscape", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { lmOpenChange: "lmOpenChange" }, host: { properties: { "attr.data-state": "isOpen() ? \"open\" : \"closed\"" } }, providers: [
1446
+ {
1447
+ provide: MODAL_CONTEXT,
1448
+ useExisting: LmModalComponent,
1449
+ },
1450
+ ], ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1451
+ }
1452
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalComponent, decorators: [{
1453
+ type: Component,
1454
+ args: [{
1455
+ selector: 'luma-modal',
1456
+ template: `<ng-content />`,
1457
+ changeDetection: ChangeDetectionStrategy.OnPush,
1458
+ providers: [
1459
+ {
1460
+ provide: MODAL_CONTEXT,
1461
+ useExisting: LmModalComponent,
1462
+ },
1463
+ ],
1464
+ host: {
1465
+ '[attr.data-state]': 'isOpen() ? "open" : "closed"',
1466
+ },
1467
+ }]
1468
+ }], ctorParameters: () => [], propDecorators: { lmOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmOpen", required: false }] }], lmDefaultOpen: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDefaultOpen", required: false }] }], lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }], lmCloseOnOverlay: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmCloseOnOverlay", required: false }] }], lmCloseOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmCloseOnEscape", required: false }] }], lmOpenChange: [{ type: i0.Output, args: ["lmOpenChange"] }] } });
1469
+
1470
+ /**
1471
+ * Modal overlay component (backdrop)
1472
+ *
1473
+ * Provides a semi-transparent backdrop behind the modal.
1474
+ * Handles click-to-close when enabled on the parent modal.
1475
+ *
1476
+ * @example
1477
+ * ```html
1478
+ * <luma-modal [lmOpen]="isOpen()">
1479
+ * <luma-modal-overlay>
1480
+ * <luma-modal-container>...</luma-modal-container>
1481
+ * </luma-modal-overlay>
1482
+ * </luma-modal>
1483
+ * ```
1484
+ */
1485
+ class LmModalOverlayComponent {
1486
+ modal = inject(MODAL_CONTEXT);
1487
+ /** Computed classes from CVA */
1488
+ classes = computed(() => modalOverlayVariants({
1489
+ open: this.modal.isOpen(),
1490
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1491
+ /**
1492
+ * Handle click on overlay (not on children)
1493
+ */
1494
+ onOverlayClick(event) {
1495
+ // Only close if clicking directly on overlay, not on children
1496
+ if (event.target === event.currentTarget && this.modal.closeOnOverlay()) {
1497
+ this.modal.close();
1498
+ }
1499
+ }
1500
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalOverlayComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1501
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: LmModalOverlayComponent, isStandalone: true, selector: "luma-modal-overlay", host: { listeners: { "click": "onOverlayClick($event)" }, properties: { "class": "classes()" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1502
+ }
1503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalOverlayComponent, decorators: [{
1504
+ type: Component,
1505
+ args: [{
1506
+ selector: 'luma-modal-overlay',
1507
+ template: `<ng-content />`,
1508
+ changeDetection: ChangeDetectionStrategy.OnPush,
1509
+ host: {
1510
+ '[class]': 'classes()',
1511
+ '(click)': 'onOverlayClick($event)',
1512
+ },
1513
+ }]
1514
+ }] });
1515
+
1516
+ /** Focusable elements selector */
1517
+ const FOCUSABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
1518
+ /**
1519
+ * Modal container component (dialog box)
1520
+ *
1521
+ * Contains the modal content and handles:
1522
+ * - ARIA attributes for accessibility
1523
+ * - Focus trap when modal is open
1524
+ * - Size variants
1525
+ *
1526
+ * @example
1527
+ * ```html
1528
+ * <luma-modal-overlay>
1529
+ * <luma-modal-container>
1530
+ * <div lumaModalHeader>...</div>
1531
+ * <div lumaModalContent>...</div>
1532
+ * <div lumaModalFooter>...</div>
1533
+ * </luma-modal-container>
1534
+ * </luma-modal-overlay>
1535
+ * ```
1536
+ */
1537
+ class LmModalContainerComponent {
1538
+ modal = inject(MODAL_CONTEXT);
1539
+ elementRef = inject((ElementRef));
1540
+ platformId = inject(PLATFORM_ID);
1541
+ /** Focus trap keydown handler */
1542
+ focusTrapHandler = null;
1543
+ /** ID for aria-labelledby */
1544
+ titleId = computed(() => `${this.modal.modalId}-title`, ...(ngDevMode ? [{ debugName: "titleId" }] : []));
1545
+ /** Computed classes from CVA */
1546
+ classes = computed(() => modalContainerVariants({
1547
+ size: this.modal.size(),
1548
+ open: this.modal.isOpen(),
1549
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1550
+ constructor() {
1551
+ // Focus first focusable element when modal opens
1552
+ effect(() => {
1553
+ if (!isPlatformBrowser(this.platformId))
1554
+ return;
1555
+ if (this.modal.isOpen()) {
1556
+ // Use setTimeout to ensure content is rendered
1557
+ setTimeout(() => this.focusFirstElement(), 0);
1558
+ }
1559
+ });
1560
+ }
1561
+ ngAfterViewInit() {
1562
+ if (isPlatformBrowser(this.platformId)) {
1563
+ this.setupFocusTrap();
1564
+ }
1565
+ }
1566
+ ngOnDestroy() {
1567
+ if (isPlatformBrowser(this.platformId)) {
1568
+ this.removeFocusTrap();
1569
+ }
1570
+ }
1571
+ /**
1572
+ * Get all focusable elements within the modal
1573
+ */
1574
+ getFocusableElements() {
1575
+ const elements = this.elementRef.nativeElement.querySelectorAll(FOCUSABLE_SELECTOR);
1576
+ const elementsArray = Array.from(elements);
1577
+ return elementsArray.filter((el) => !el.hasAttribute('disabled') && el.getAttribute('tabindex') !== '-1');
1578
+ }
1579
+ /**
1580
+ * Focus the first focusable element
1581
+ */
1582
+ focusFirstElement() {
1583
+ const focusable = this.getFocusableElements();
1584
+ if (focusable.length > 0) {
1585
+ focusable[0].focus();
1586
+ }
1587
+ else {
1588
+ // If no focusable elements, focus the container itself
1589
+ this.elementRef.nativeElement.focus();
1590
+ }
1591
+ }
1592
+ /**
1593
+ * Setup focus trap to keep focus within modal
1594
+ */
1595
+ setupFocusTrap() {
1596
+ this.focusTrapHandler = (event) => {
1597
+ if (event.key !== 'Tab' || !this.modal.isOpen())
1598
+ return;
1599
+ const focusable = this.getFocusableElements();
1600
+ if (focusable.length === 0)
1601
+ return;
1602
+ const firstElement = focusable[0];
1603
+ const lastElement = focusable[focusable.length - 1];
1604
+ // Shift+Tab on first element -> focus last
1605
+ if (event.shiftKey && document.activeElement === firstElement) {
1606
+ event.preventDefault();
1607
+ lastElement.focus();
1608
+ }
1609
+ // Tab on last element -> focus first
1610
+ else if (!event.shiftKey && document.activeElement === lastElement) {
1611
+ event.preventDefault();
1612
+ firstElement.focus();
1613
+ }
1614
+ };
1615
+ this.elementRef.nativeElement.addEventListener('keydown', this.focusTrapHandler);
1616
+ }
1617
+ /**
1618
+ * Remove focus trap handler
1619
+ */
1620
+ removeFocusTrap() {
1621
+ if (this.focusTrapHandler) {
1622
+ this.elementRef.nativeElement.removeEventListener('keydown', this.focusTrapHandler);
1623
+ this.focusTrapHandler = null;
1624
+ }
1625
+ }
1626
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1627
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.9", type: LmModalContainerComponent, isStandalone: true, selector: "luma-modal-container", host: { attributes: { "role": "dialog" }, properties: { "attr.aria-modal": "true", "attr.aria-labelledby": "titleId()", "class": "classes()", "attr.data-state": "modal.isOpen() ? \"open\" : \"closed\"" } }, ngImport: i0, template: `<ng-content />`, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1628
+ }
1629
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalContainerComponent, decorators: [{
1630
+ type: Component,
1631
+ args: [{
1632
+ selector: 'luma-modal-container',
1633
+ template: `<ng-content />`,
1634
+ changeDetection: ChangeDetectionStrategy.OnPush,
1635
+ host: {
1636
+ role: 'dialog',
1637
+ '[attr.aria-modal]': 'true',
1638
+ '[attr.aria-labelledby]': 'titleId()',
1639
+ '[class]': 'classes()',
1640
+ '[attr.data-state]': 'modal.isOpen() ? "open" : "closed"',
1641
+ },
1642
+ }]
1643
+ }], ctorParameters: () => [] });
1644
+
1645
+ /**
1646
+ * Modal header directive
1647
+ *
1648
+ * Container for modal title and close button.
1649
+ * Provides consistent padding and border styling.
1650
+ *
1651
+ * @example
1652
+ * ```html
1653
+ * <div lumaModalHeader>
1654
+ * <h2 lumaModalTitle>Modal Title</h2>
1655
+ * <luma-modal-close />
1656
+ * </div>
1657
+ * ```
1658
+ */
1659
+ class LmModalHeaderDirective {
1660
+ /** Computed classes from CVA */
1661
+ classes = computed(() => modalHeaderVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1662
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1663
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmModalHeaderDirective, isStandalone: true, selector: "[lumaModalHeader]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
1664
+ }
1665
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalHeaderDirective, decorators: [{
1666
+ type: Directive,
1667
+ args: [{
1668
+ selector: '[lumaModalHeader]',
1669
+ host: {
1670
+ '[class]': 'classes()',
1671
+ },
1672
+ }]
1673
+ }] });
1674
+
1675
+ /**
1676
+ * Modal title directive
1677
+ *
1678
+ * Provides consistent typography for modal titles.
1679
+ * Automatically links to aria-labelledby on the modal container.
1680
+ *
1681
+ * @example
1682
+ * ```html
1683
+ * <h2 lumaModalTitle>Modal Title</h2>
1684
+ * <h2 lumaModalTitle lmSize="lg">Large Title</h2>
1685
+ * ```
1686
+ */
1687
+ class LmModalTitleDirective {
1688
+ modal = inject(MODAL_CONTEXT);
1689
+ /** Title size variant */
1690
+ lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
1691
+ /** ID for aria-labelledby connection */
1692
+ titleId = computed(() => `${this.modal.modalId}-title`, ...(ngDevMode ? [{ debugName: "titleId" }] : []));
1693
+ /** Computed classes from CVA */
1694
+ classes = computed(() => modalTitleVariants({
1695
+ size: this.lmSize(),
1696
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1697
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1698
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmModalTitleDirective, isStandalone: true, selector: "[lumaModalTitle]", inputs: { lmSize: { classPropertyName: "lmSize", publicName: "lmSize", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.id": "titleId()", "class": "classes()" } }, ngImport: i0 });
1699
+ }
1700
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalTitleDirective, decorators: [{
1701
+ type: Directive,
1702
+ args: [{
1703
+ selector: '[lumaModalTitle]',
1704
+ host: {
1705
+ '[attr.id]': 'titleId()',
1706
+ '[class]': 'classes()',
1707
+ },
1708
+ }]
1709
+ }], propDecorators: { lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }] } });
1710
+
1711
+ /**
1712
+ * Modal content directive
1713
+ *
1714
+ * Container for the main modal content.
1715
+ * Supports scrolling when content exceeds available space.
1716
+ *
1717
+ * @example
1718
+ * ```html
1719
+ * <div lumaModalContent>
1720
+ * Content that doesn't scroll
1721
+ * </div>
1722
+ *
1723
+ * <div lumaModalContent [lmScrollable]="true">
1724
+ * Long content that scrolls...
1725
+ * </div>
1726
+ * ```
1727
+ */
1728
+ class LmModalContentDirective {
1729
+ /** Enable scroll when content overflows */
1730
+ lmScrollable = input(true, ...(ngDevMode ? [{ debugName: "lmScrollable" }] : []));
1731
+ /** Computed classes from CVA */
1732
+ classes = computed(() => modalContentVariants({
1733
+ scrollable: this.lmScrollable(),
1734
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1735
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1736
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmModalContentDirective, isStandalone: true, selector: "[lumaModalContent]", inputs: { lmScrollable: { classPropertyName: "lmScrollable", publicName: "lmScrollable", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
1737
+ }
1738
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalContentDirective, decorators: [{
1739
+ type: Directive,
1740
+ args: [{
1741
+ selector: '[lumaModalContent]',
1742
+ host: {
1743
+ '[class]': 'classes()',
1744
+ },
1745
+ }]
1746
+ }], propDecorators: { lmScrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmScrollable", required: false }] }] } });
1747
+
1748
+ /**
1749
+ * Modal footer directive
1750
+ *
1751
+ * Container for modal actions (buttons, etc.).
1752
+ * Provides consistent padding and flexible alignment.
1753
+ *
1754
+ * @example
1755
+ * ```html
1756
+ * <div lumaModalFooter>
1757
+ * <button lumaButton lmVariant="ghost">Cancel</button>
1758
+ * <button lumaButton>Confirm</button>
1759
+ * </div>
1760
+ *
1761
+ * <div lumaModalFooter lmAlign="between">
1762
+ * <span>Left content</span>
1763
+ * <button lumaButton>Action</button>
1764
+ * </div>
1765
+ * ```
1766
+ */
1767
+ class LmModalFooterDirective {
1768
+ /** Alignment of footer content */
1769
+ lmAlign = input('end', ...(ngDevMode ? [{ debugName: "lmAlign" }] : []));
1770
+ /** Computed classes from CVA */
1771
+ classes = computed(() => modalFooterVariants({
1772
+ align: this.lmAlign(),
1773
+ }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1774
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalFooterDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1775
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmModalFooterDirective, isStandalone: true, selector: "[lumaModalFooter]", inputs: { lmAlign: { classPropertyName: "lmAlign", publicName: "lmAlign", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "classes()" } }, ngImport: i0 });
1776
+ }
1777
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalFooterDirective, decorators: [{
1778
+ type: Directive,
1779
+ args: [{
1780
+ selector: '[lumaModalFooter]',
1781
+ host: {
1782
+ '[class]': 'classes()',
1783
+ },
1784
+ }]
1785
+ }], propDecorators: { lmAlign: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmAlign", required: false }] }] } });
1786
+
1787
+ /**
1788
+ * Modal close button component
1789
+ *
1790
+ * Provides a styled close button with an X icon.
1791
+ * Can be customized with different content via ng-content.
1792
+ *
1793
+ * @example
1794
+ * ```html
1795
+ * <!-- Default X icon -->
1796
+ * <luma-modal-close />
1797
+ *
1798
+ * <!-- Custom aria label -->
1799
+ * <luma-modal-close lmAriaLabel="Fechar modal" />
1800
+ *
1801
+ * <!-- Custom icon -->
1802
+ * <luma-modal-close>
1803
+ * <svg>...</svg>
1804
+ * </luma-modal-close>
1805
+ * ```
1806
+ */
1807
+ class LmModalCloseComponent {
1808
+ modal = inject(MODAL_CONTEXT);
1809
+ /** Accessible label for the close button */
1810
+ lmAriaLabel = input('Close modal', ...(ngDevMode ? [{ debugName: "lmAriaLabel" }] : []));
1811
+ /** Computed aria label */
1812
+ ariaLabel = computed(() => this.lmAriaLabel(), ...(ngDevMode ? [{ debugName: "ariaLabel" }] : []));
1813
+ /** Computed classes from CVA */
1814
+ classes = computed(() => modalCloseVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1815
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalCloseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1816
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmModalCloseComponent, isStandalone: true, selector: "luma-modal-close", inputs: { lmAriaLabel: { classPropertyName: "lmAriaLabel", publicName: "lmAriaLabel", isSignal: true, isRequired: false, transformFunction: null } }, host: { classAttribute: "contents" }, ngImport: i0, template: `
1817
+ <button
1818
+ type="button"
1819
+ [attr.aria-label]="ariaLabel()"
1820
+ [class]="classes()"
1821
+ (click)="modal.close()"
1822
+ >
1823
+ <ng-content>
1824
+ <svg
1825
+ viewBox="0 0 24 24"
1826
+ class="w-4 h-4"
1827
+ fill="none"
1828
+ stroke="currentColor"
1829
+ aria-hidden="true"
1830
+ >
1831
+ <path
1832
+ stroke-linecap="round"
1833
+ stroke-linejoin="round"
1834
+ stroke-width="2"
1835
+ d="M6 18L18 6M6 6l12 12"
1836
+ />
1837
+ </svg>
1838
+ </ng-content>
1839
+ </button>
1840
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1841
+ }
1842
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalCloseComponent, decorators: [{
1843
+ type: Component,
1844
+ args: [{
1845
+ selector: 'luma-modal-close',
1846
+ template: `
1847
+ <button
1848
+ type="button"
1849
+ [attr.aria-label]="ariaLabel()"
1850
+ [class]="classes()"
1851
+ (click)="modal.close()"
1852
+ >
1853
+ <ng-content>
1854
+ <svg
1855
+ viewBox="0 0 24 24"
1856
+ class="w-4 h-4"
1857
+ fill="none"
1858
+ stroke="currentColor"
1859
+ aria-hidden="true"
1860
+ >
1861
+ <path
1862
+ stroke-linecap="round"
1863
+ stroke-linejoin="round"
1864
+ stroke-width="2"
1865
+ d="M6 18L18 6M6 6l12 12"
1866
+ />
1867
+ </svg>
1868
+ </ng-content>
1869
+ </button>
1870
+ `,
1871
+ changeDetection: ChangeDetectionStrategy.OnPush,
1872
+ host: {
1873
+ class: 'contents',
1874
+ },
1875
+ }]
1876
+ }], propDecorators: { lmAriaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmAriaLabel", required: false }] }] } });
1877
+
1878
+ // Modal Component & Directives
1879
+
1880
+ /**
1881
+ * Internal toast reference implementation
1882
+ */
1883
+ class ToastRefImpl {
1884
+ id;
1885
+ dismissFn;
1886
+ _afterDismissed = new Subject();
1887
+ constructor(id, dismissFn) {
1888
+ this.id = id;
1889
+ this.dismissFn = dismissFn;
1890
+ }
1891
+ dismiss() {
1892
+ this.dismissFn(this.id);
1893
+ }
1894
+ get afterDismissed() {
1895
+ return this._afterDismissed.asObservable();
1896
+ }
1897
+ /** @internal Called when toast is actually dismissed */
1898
+ _notifyDismissed() {
1899
+ this._afterDismissed.next();
1900
+ this._afterDismissed.complete();
1901
+ }
1902
+ }
1903
+ /**
1904
+ * Default toast configuration
1905
+ */
1906
+ const DEFAULT_TOAST_CONFIG = {
1907
+ position: 'top-right',
1908
+ duration: 5000,
1909
+ dismissible: true,
1910
+ maxVisible: 5,
1911
+ pauseOnHover: true,
1912
+ };
1913
+ /**
1914
+ * Injection token for global toast configuration
1915
+ */
1916
+ const TOAST_CONFIG = new InjectionToken('ToastConfig', {
1917
+ providedIn: 'root',
1918
+ factory: () => DEFAULT_TOAST_CONFIG,
1919
+ });
1920
+ /**
1921
+ * Provider function for custom toast configuration
1922
+ */
1923
+ function provideToastConfig(config) {
1924
+ return {
1925
+ provide: TOAST_CONFIG,
1926
+ useValue: { ...DEFAULT_TOAST_CONFIG, ...config },
1927
+ };
1928
+ }
1929
+
1930
+ /**
1931
+ * ToastCloseComponent
1932
+ *
1933
+ * Close button for toast notifications.
1934
+ * Styled according to the toast variant.
1935
+ *
1936
+ * @internal Used by ToastItemComponent
1937
+ */
1938
+ class LmToastCloseComponent {
1939
+ /** Toast variant for styling */
1940
+ lmVariant = input('info', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
1941
+ /** CSS classes */
1942
+ classes = computed(() => toastCloseVariants({ variant: this.lmVariant() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
1943
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastCloseComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1944
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.0.9", type: LmToastCloseComponent, isStandalone: true, selector: "luma-toast-close", inputs: { lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "type": "button", "aria-label": "Close notification" }, properties: { "class": "classes()" } }, ngImport: i0, template: `
1945
+ <svg
1946
+ xmlns="http://www.w3.org/2000/svg"
1947
+ viewBox="0 0 20 20"
1948
+ fill="currentColor"
1949
+ class="lm-size-toast-close"
1950
+ aria-hidden="true"
1951
+ >
1952
+ <path
1953
+ d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
1954
+ />
1955
+ </svg>
1956
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
1957
+ }
1958
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastCloseComponent, decorators: [{
1959
+ type: Component,
1960
+ args: [{
1961
+ selector: 'luma-toast-close',
1962
+ template: `
1963
+ <svg
1964
+ xmlns="http://www.w3.org/2000/svg"
1965
+ viewBox="0 0 20 20"
1966
+ fill="currentColor"
1967
+ class="lm-size-toast-close"
1968
+ aria-hidden="true"
1969
+ >
1970
+ <path
1971
+ d="M6.28 5.22a.75.75 0 00-1.06 1.06L8.94 10l-3.72 3.72a.75.75 0 101.06 1.06L10 11.06l3.72 3.72a.75.75 0 101.06-1.06L11.06 10l3.72-3.72a.75.75 0 00-1.06-1.06L10 8.94 6.28 5.22z"
1972
+ />
1973
+ </svg>
1974
+ `,
1975
+ host: {
1976
+ type: 'button',
1977
+ '[class]': 'classes()',
1978
+ 'aria-label': 'Close notification',
1979
+ },
1980
+ changeDetection: ChangeDetectionStrategy.OnPush,
1981
+ }]
1982
+ }], propDecorators: { lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }] } });
1983
+
1984
+ /**
1985
+ * ToastItemComponent
1986
+ *
1987
+ * Individual toast notification with timer and icon.
1988
+ *
1989
+ * @internal Created by ToastContainerComponent
1990
+ */
1991
+ class LmToastItemComponent {
1992
+ /** Toast data */
1993
+ toast = input.required(...(ngDevMode ? [{ debugName: "toast" }] : []));
1994
+ /** Emits when toast should be dismissed */
1995
+ dismiss = output();
1996
+ /** Timer subscription */
1997
+ timerSubscription = null;
1998
+ /** Remaining time in ms */
1999
+ remainingTime = signal(0, ...(ngDevMode ? [{ debugName: "remainingTime" }] : []));
2000
+ /** Whether timer is paused */
2001
+ isPaused = signal(false, ...(ngDevMode ? [{ debugName: "isPaused" }] : []));
2002
+ /** Animation state */
2003
+ animationState = signal('entering', ...(ngDevMode ? [{ debugName: "animationState" }] : []));
2004
+ /** Item CSS classes */
2005
+ itemClasses = computed(() => toastItemVariants({
2006
+ variant: this.toast().variant,
2007
+ state: this.toast().isExiting ? 'exiting' : this.animationState(),
2008
+ }), ...(ngDevMode ? [{ debugName: "itemClasses" }] : []));
2009
+ /** Icon CSS classes */
2010
+ iconClasses = computed(() => toastIconVariants({ variant: this.toast().variant }), ...(ngDevMode ? [{ debugName: "iconClasses" }] : []));
2011
+ /** Content CSS classes */
2012
+ contentClasses = computed(() => toastContentVariants(), ...(ngDevMode ? [{ debugName: "contentClasses" }] : []));
2013
+ /** Title CSS classes */
2014
+ titleClasses = computed(() => toastTitleVariants(), ...(ngDevMode ? [{ debugName: "titleClasses" }] : []));
2015
+ /** Message CSS classes */
2016
+ messageClasses = computed(() => toastMessageVariants(), ...(ngDevMode ? [{ debugName: "messageClasses" }] : []));
2017
+ /** Whether toast has interactive elements */
2018
+ hasInteractiveElements = computed(() => this.toast().dismissible, ...(ngDevMode ? [{ debugName: "hasInteractiveElements" }] : []));
2019
+ ngOnInit() {
2020
+ // Start animation
2021
+ requestAnimationFrame(() => {
2022
+ this.animationState.set('visible');
2023
+ });
2024
+ // Start timer if duration > 0
2025
+ const duration = this.toast().duration;
2026
+ if (duration > 0) {
2027
+ this.startTimer(duration);
2028
+ }
2029
+ }
2030
+ ngOnDestroy() {
2031
+ this.stopTimer();
2032
+ }
2033
+ /** Start auto-close timer */
2034
+ startTimer(duration) {
2035
+ this.remainingTime.set(duration);
2036
+ this.timerSubscription = interval(100)
2037
+ .pipe(takeWhile(() => this.remainingTime() > 0), filter(() => !this.isPaused()))
2038
+ .subscribe(() => {
2039
+ this.remainingTime.update((t) => t - 100);
2040
+ if (this.remainingTime() <= 0) {
2041
+ this.dismissToast();
2042
+ }
2043
+ });
2044
+ }
2045
+ /** Stop timer */
2046
+ stopTimer() {
2047
+ if (this.timerSubscription) {
2048
+ this.timerSubscription.unsubscribe();
2049
+ this.timerSubscription = null;
2050
+ }
2051
+ }
2052
+ /** Dismiss this toast */
2053
+ dismissToast() {
2054
+ this.dismiss.emit(this.toast().id);
2055
+ }
2056
+ /** Handle close button click */
2057
+ onClose() {
2058
+ this.dismissToast();
2059
+ }
2060
+ /** Pause timer on mouse enter */
2061
+ onMouseEnter() {
2062
+ if (this.toast().pauseOnHover) {
2063
+ this.isPaused.set(true);
2064
+ }
2065
+ }
2066
+ /** Resume timer on mouse leave */
2067
+ onMouseLeave() {
2068
+ this.isPaused.set(false);
2069
+ }
2070
+ /** Pause timer on focus */
2071
+ onFocus() {
2072
+ this.isPaused.set(true);
2073
+ }
2074
+ /** Resume timer on blur */
2075
+ onBlur() {
2076
+ this.isPaused.set(false);
2077
+ }
2078
+ /** Dismiss on Escape key */
2079
+ onEscapeKey() {
2080
+ if (this.toast().dismissible) {
2081
+ this.dismissToast();
2082
+ }
2083
+ }
2084
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastItemComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2085
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: LmToastItemComponent, isStandalone: true, selector: "luma-toast-item", inputs: { toast: { classPropertyName: "toast", publicName: "toast", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { dismiss: "dismiss" }, host: { listeners: { "mouseenter": "onMouseEnter()", "mouseleave": "onMouseLeave()", "focus": "onFocus()", "blur": "onBlur()", "keydown.escape": "onEscapeKey()" }, properties: { "class": "itemClasses()", "attr.role": "toast().role", "attr.aria-live": "toast().variant === \"error\" ? \"assertive\" : \"polite\"", "attr.aria-atomic": "\"true\"", "tabindex": "hasInteractiveElements() ? 0 : -1" } }, ngImport: i0, template: `
2086
+ <!-- Icon -->
2087
+ <div [class]="iconClasses()">
2088
+ @switch (toast().variant) {
2089
+ @case ('info') {
2090
+ <svg
2091
+ xmlns="http://www.w3.org/2000/svg"
2092
+ viewBox="0 0 20 20"
2093
+ fill="currentColor"
2094
+ >
2095
+ <path
2096
+ fill-rule="evenodd"
2097
+ d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
2098
+ clip-rule="evenodd"
2099
+ />
2100
+ </svg>
2101
+ }
2102
+ @case ('success') {
2103
+ <svg
2104
+ xmlns="http://www.w3.org/2000/svg"
2105
+ viewBox="0 0 20 20"
2106
+ fill="currentColor"
2107
+ >
2108
+ <path
2109
+ fill-rule="evenodd"
2110
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
2111
+ clip-rule="evenodd"
2112
+ />
2113
+ </svg>
2114
+ }
2115
+ @case ('warning') {
2116
+ <svg
2117
+ xmlns="http://www.w3.org/2000/svg"
2118
+ viewBox="0 0 20 20"
2119
+ fill="currentColor"
2120
+ >
2121
+ <path
2122
+ fill-rule="evenodd"
2123
+ d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
2124
+ clip-rule="evenodd"
2125
+ />
2126
+ </svg>
2127
+ }
2128
+ @case ('error') {
2129
+ <svg
2130
+ xmlns="http://www.w3.org/2000/svg"
2131
+ viewBox="0 0 20 20"
2132
+ fill="currentColor"
2133
+ >
2134
+ <path
2135
+ fill-rule="evenodd"
2136
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
2137
+ clip-rule="evenodd"
2138
+ />
2139
+ </svg>
2140
+ }
2141
+ }
2142
+ </div>
2143
+
2144
+ <!-- Content -->
2145
+ <div [class]="contentClasses()">
2146
+ @if (toast().title) {
2147
+ <div [class]="titleClasses()">{{ toast().title }}</div>
2148
+ }
2149
+ <div [class]="messageClasses()">{{ toast().message }}</div>
2150
+ </div>
2151
+
2152
+ <!-- Close button -->
2153
+ @if (toast().dismissible) {
2154
+ <luma-toast-close [lmVariant]="toast().variant" (click)="onClose()" />
2155
+ }
2156
+ `, isInline: true, dependencies: [{ kind: "component", type: LmToastCloseComponent, selector: "luma-toast-close", inputs: ["lmVariant"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2157
+ }
2158
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastItemComponent, decorators: [{
2159
+ type: Component,
2160
+ args: [{
2161
+ selector: 'luma-toast-item',
2162
+ template: `
2163
+ <!-- Icon -->
2164
+ <div [class]="iconClasses()">
2165
+ @switch (toast().variant) {
2166
+ @case ('info') {
2167
+ <svg
2168
+ xmlns="http://www.w3.org/2000/svg"
2169
+ viewBox="0 0 20 20"
2170
+ fill="currentColor"
2171
+ >
2172
+ <path
2173
+ fill-rule="evenodd"
2174
+ d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z"
2175
+ clip-rule="evenodd"
2176
+ />
2177
+ </svg>
2178
+ }
2179
+ @case ('success') {
2180
+ <svg
2181
+ xmlns="http://www.w3.org/2000/svg"
2182
+ viewBox="0 0 20 20"
2183
+ fill="currentColor"
2184
+ >
2185
+ <path
2186
+ fill-rule="evenodd"
2187
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
2188
+ clip-rule="evenodd"
2189
+ />
2190
+ </svg>
2191
+ }
2192
+ @case ('warning') {
2193
+ <svg
2194
+ xmlns="http://www.w3.org/2000/svg"
2195
+ viewBox="0 0 20 20"
2196
+ fill="currentColor"
2197
+ >
2198
+ <path
2199
+ fill-rule="evenodd"
2200
+ d="M8.485 2.495c.673-1.167 2.357-1.167 3.03 0l6.28 10.875c.673 1.167-.17 2.625-1.516 2.625H3.72c-1.347 0-2.189-1.458-1.515-2.625L8.485 2.495zM10 5a.75.75 0 01.75.75v3.5a.75.75 0 01-1.5 0v-3.5A.75.75 0 0110 5zm0 9a1 1 0 100-2 1 1 0 000 2z"
2201
+ clip-rule="evenodd"
2202
+ />
2203
+ </svg>
2204
+ }
2205
+ @case ('error') {
2206
+ <svg
2207
+ xmlns="http://www.w3.org/2000/svg"
2208
+ viewBox="0 0 20 20"
2209
+ fill="currentColor"
2210
+ >
2211
+ <path
2212
+ fill-rule="evenodd"
2213
+ d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.28 7.22a.75.75 0 00-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 101.06 1.06L10 11.06l1.72 1.72a.75.75 0 101.06-1.06L11.06 10l1.72-1.72a.75.75 0 00-1.06-1.06L10 8.94 8.28 7.22z"
2214
+ clip-rule="evenodd"
2215
+ />
2216
+ </svg>
2217
+ }
2218
+ }
2219
+ </div>
2220
+
2221
+ <!-- Content -->
2222
+ <div [class]="contentClasses()">
2223
+ @if (toast().title) {
2224
+ <div [class]="titleClasses()">{{ toast().title }}</div>
2225
+ }
2226
+ <div [class]="messageClasses()">{{ toast().message }}</div>
2227
+ </div>
2228
+
2229
+ <!-- Close button -->
2230
+ @if (toast().dismissible) {
2231
+ <luma-toast-close [lmVariant]="toast().variant" (click)="onClose()" />
2232
+ }
2233
+ `,
2234
+ host: {
2235
+ '[class]': 'itemClasses()',
2236
+ '[attr.role]': 'toast().role',
2237
+ '[attr.aria-live]': 'toast().variant === "error" ? "assertive" : "polite"',
2238
+ '[attr.aria-atomic]': '"true"',
2239
+ '[tabindex]': 'hasInteractiveElements() ? 0 : -1',
2240
+ '(mouseenter)': 'onMouseEnter()',
2241
+ '(mouseleave)': 'onMouseLeave()',
2242
+ '(focus)': 'onFocus()',
2243
+ '(blur)': 'onBlur()',
2244
+ '(keydown.escape)': 'onEscapeKey()',
2245
+ },
2246
+ changeDetection: ChangeDetectionStrategy.OnPush,
2247
+ imports: [LmToastCloseComponent],
2248
+ }]
2249
+ }], propDecorators: { toast: [{ type: i0.Input, args: [{ isSignal: true, alias: "toast", required: true }] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }] } });
2250
+
2251
+ /**
2252
+ * ToastContainerComponent
2253
+ *
2254
+ * Fixed-position container that renders all active toasts.
2255
+ * Supports all 6 positions simultaneously by grouping toasts by their position.
2256
+ *
2257
+ * @internal This component is created programmatically by ToastService
2258
+ */
2259
+ class LmToastContainerComponent {
2260
+ /** Toasts signal passed from ToastService */
2261
+ _toasts;
2262
+ /** Dismiss callback passed from ToastService */
2263
+ _onDismiss;
2264
+ /** Group toasts by their position */
2265
+ toastsByPosition = computed(() => {
2266
+ const groups = {
2267
+ 'top-left': [],
2268
+ 'top-center': [],
2269
+ 'top-right': [],
2270
+ 'bottom-left': [],
2271
+ 'bottom-center': [],
2272
+ 'bottom-right': [],
2273
+ };
2274
+ if (!this._toasts)
2275
+ return groups;
2276
+ for (const toast of this._toasts()) {
2277
+ const position = toast.position;
2278
+ if (groups[position]) {
2279
+ groups[position].push(toast);
2280
+ }
2281
+ }
2282
+ return groups;
2283
+ }, ...(ngDevMode ? [{ debugName: "toastsByPosition" }] : []));
2284
+ /** Get CSS classes for a specific position */
2285
+ getPositionClasses(position) {
2286
+ return toastContainerVariants({ position });
2287
+ }
2288
+ /** Handle dismiss event from toast item */
2289
+ onDismiss(id) {
2290
+ this._onDismiss?.(id);
2291
+ }
2292
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2293
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: LmToastContainerComponent, isStandalone: true, selector: "luma-toast-container", ngImport: i0, template: `
2294
+ <!-- Top Left -->
2295
+ @if (toastsByPosition()['top-left'].length) {
2296
+ <div
2297
+ role="region"
2298
+ aria-label="Top left notifications"
2299
+ [class]="getPositionClasses('top-left')"
2300
+ >
2301
+ @for (toast of toastsByPosition()['top-left']; track toast.id) {
2302
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2303
+ }
2304
+ </div>
2305
+ }
2306
+
2307
+ <!-- Top Center -->
2308
+ @if (toastsByPosition()['top-center'].length) {
2309
+ <div
2310
+ role="region"
2311
+ aria-label="Top center notifications"
2312
+ [class]="getPositionClasses('top-center')"
2313
+ >
2314
+ @for (toast of toastsByPosition()['top-center']; track toast.id) {
2315
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2316
+ }
2317
+ </div>
2318
+ }
2319
+
2320
+ <!-- Top Right -->
2321
+ @if (toastsByPosition()['top-right'].length) {
2322
+ <div
2323
+ role="region"
2324
+ aria-label="Top right notifications"
2325
+ [class]="getPositionClasses('top-right')"
2326
+ >
2327
+ @for (toast of toastsByPosition()['top-right']; track toast.id) {
2328
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2329
+ }
2330
+ </div>
2331
+ }
2332
+
2333
+ <!-- Bottom Left -->
2334
+ @if (toastsByPosition()['bottom-left'].length) {
2335
+ <div
2336
+ role="region"
2337
+ aria-label="Bottom left notifications"
2338
+ [class]="getPositionClasses('bottom-left')"
2339
+ >
2340
+ @for (toast of toastsByPosition()['bottom-left']; track toast.id) {
2341
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2342
+ }
2343
+ </div>
2344
+ }
2345
+
2346
+ <!-- Bottom Center -->
2347
+ @if (toastsByPosition()['bottom-center'].length) {
2348
+ <div
2349
+ role="region"
2350
+ aria-label="Bottom center notifications"
2351
+ [class]="getPositionClasses('bottom-center')"
2352
+ >
2353
+ @for (toast of toastsByPosition()['bottom-center']; track toast.id) {
2354
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2355
+ }
2356
+ </div>
2357
+ }
2358
+
2359
+ <!-- Bottom Right -->
2360
+ @if (toastsByPosition()['bottom-right'].length) {
2361
+ <div
2362
+ role="region"
2363
+ aria-label="Bottom right notifications"
2364
+ [class]="getPositionClasses('bottom-right')"
2365
+ >
2366
+ @for (toast of toastsByPosition()['bottom-right']; track toast.id) {
2367
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2368
+ }
2369
+ </div>
2370
+ }
2371
+ `, isInline: true, dependencies: [{ kind: "component", type: LmToastItemComponent, selector: "luma-toast-item", inputs: ["toast"], outputs: ["dismiss"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2372
+ }
2373
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastContainerComponent, decorators: [{
2374
+ type: Component,
2375
+ args: [{
2376
+ selector: 'luma-toast-container',
2377
+ template: `
2378
+ <!-- Top Left -->
2379
+ @if (toastsByPosition()['top-left'].length) {
2380
+ <div
2381
+ role="region"
2382
+ aria-label="Top left notifications"
2383
+ [class]="getPositionClasses('top-left')"
2384
+ >
2385
+ @for (toast of toastsByPosition()['top-left']; track toast.id) {
2386
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2387
+ }
2388
+ </div>
2389
+ }
2390
+
2391
+ <!-- Top Center -->
2392
+ @if (toastsByPosition()['top-center'].length) {
2393
+ <div
2394
+ role="region"
2395
+ aria-label="Top center notifications"
2396
+ [class]="getPositionClasses('top-center')"
2397
+ >
2398
+ @for (toast of toastsByPosition()['top-center']; track toast.id) {
2399
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2400
+ }
2401
+ </div>
2402
+ }
2403
+
2404
+ <!-- Top Right -->
2405
+ @if (toastsByPosition()['top-right'].length) {
2406
+ <div
2407
+ role="region"
2408
+ aria-label="Top right notifications"
2409
+ [class]="getPositionClasses('top-right')"
2410
+ >
2411
+ @for (toast of toastsByPosition()['top-right']; track toast.id) {
2412
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2413
+ }
2414
+ </div>
2415
+ }
2416
+
2417
+ <!-- Bottom Left -->
2418
+ @if (toastsByPosition()['bottom-left'].length) {
2419
+ <div
2420
+ role="region"
2421
+ aria-label="Bottom left notifications"
2422
+ [class]="getPositionClasses('bottom-left')"
2423
+ >
2424
+ @for (toast of toastsByPosition()['bottom-left']; track toast.id) {
2425
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2426
+ }
2427
+ </div>
2428
+ }
2429
+
2430
+ <!-- Bottom Center -->
2431
+ @if (toastsByPosition()['bottom-center'].length) {
2432
+ <div
2433
+ role="region"
2434
+ aria-label="Bottom center notifications"
2435
+ [class]="getPositionClasses('bottom-center')"
2436
+ >
2437
+ @for (toast of toastsByPosition()['bottom-center']; track toast.id) {
2438
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2439
+ }
2440
+ </div>
2441
+ }
2442
+
2443
+ <!-- Bottom Right -->
2444
+ @if (toastsByPosition()['bottom-right'].length) {
2445
+ <div
2446
+ role="region"
2447
+ aria-label="Bottom right notifications"
2448
+ [class]="getPositionClasses('bottom-right')"
2449
+ >
2450
+ @for (toast of toastsByPosition()['bottom-right']; track toast.id) {
2451
+ <luma-toast-item [toast]="toast" (dismiss)="onDismiss($event)" />
2452
+ }
2453
+ </div>
2454
+ }
2455
+ `,
2456
+ changeDetection: ChangeDetectionStrategy.OnPush,
2457
+ imports: [LmToastItemComponent],
2458
+ }]
2459
+ }] });
2460
+
2461
+ /**
2462
+ * ToastService
2463
+ *
2464
+ * Injectable service for showing toast notifications programmatically.
2465
+ * Provides convenience methods for info, success, warning, and error toasts.
2466
+ *
2467
+ * @example
2468
+ * ```typescript
2469
+ * private toast = inject(ToastService);
2470
+ *
2471
+ * showSuccess() {
2472
+ * this.toast.success('Changes saved successfully!');
2473
+ * }
2474
+ *
2475
+ * showError() {
2476
+ * this.toast.error('Failed to save', {
2477
+ * title: 'Error',
2478
+ * duration: 0
2479
+ * });
2480
+ * }
2481
+ * ```
2482
+ */
2483
+ class LmToastService {
2484
+ config = inject(TOAST_CONFIG);
2485
+ appRef = inject(ApplicationRef);
2486
+ injector = inject(Injector);
2487
+ document = inject(DOCUMENT);
2488
+ platformId = inject(PLATFORM_ID);
2489
+ liveAnnouncer = inject(LiveAnnouncer);
2490
+ _toasts = signal([], ...(ngDevMode ? [{ debugName: "_toasts" }] : []));
2491
+ containerRef = null;
2492
+ nextId = 0;
2493
+ toastRefs = new Map();
2494
+ /** Observable list of current toasts */
2495
+ toasts = computed(() => this._toasts(), ...(ngDevMode ? [{ debugName: "toasts" }] : []));
2496
+ ngOnDestroy() {
2497
+ this.destroyContainer();
2498
+ }
2499
+ /**
2500
+ * Show a toast notification
2501
+ * @param options - Toast configuration options
2502
+ * @returns ToastRef for programmatic control
2503
+ */
2504
+ show(options) {
2505
+ this.ensureContainer();
2506
+ const toast = {
2507
+ id: `toast-${this.nextId++}`,
2508
+ message: options.message,
2509
+ title: options.title ?? '',
2510
+ variant: options.variant ?? 'info',
2511
+ position: options.position ?? this.config.position,
2512
+ duration: options.duration ?? this.config.duration,
2513
+ dismissible: options.dismissible ?? this.config.dismissible,
2514
+ pauseOnHover: options.pauseOnHover ?? this.config.pauseOnHover,
2515
+ role: options.role ?? (options.variant === 'error' ? 'alert' : 'status'),
2516
+ createdAt: Date.now(),
2517
+ isExiting: false,
2518
+ };
2519
+ // Enforce max visible limit
2520
+ const currentToasts = this._toasts();
2521
+ if (currentToasts.length >= this.config.maxVisible) {
2522
+ // Remove oldest toast
2523
+ const oldest = currentToasts[0];
2524
+ this.dismiss(oldest.id);
2525
+ }
2526
+ this._toasts.update((toasts) => [...toasts, toast]);
2527
+ this.announceToast(toast);
2528
+ const toastRef = new ToastRefImpl(toast.id, (id) => this.dismiss(id));
2529
+ this.toastRefs.set(toast.id, toastRef);
2530
+ return toastRef;
2531
+ }
2532
+ /**
2533
+ * Show info toast
2534
+ * @param message - Toast message
2535
+ * @param options - Additional options
2536
+ */
2537
+ info(message, options) {
2538
+ return this.show({ ...options, message, variant: 'info' });
2539
+ }
2540
+ /**
2541
+ * Show success toast
2542
+ * @param message - Toast message
2543
+ * @param options - Additional options
2544
+ */
2545
+ success(message, options) {
2546
+ return this.show({ ...options, message, variant: 'success' });
2547
+ }
2548
+ /**
2549
+ * Show warning toast
2550
+ * @param message - Toast message
2551
+ * @param options - Additional options
2552
+ */
2553
+ warning(message, options) {
2554
+ return this.show({ ...options, message, variant: 'warning' });
2555
+ }
2556
+ /**
2557
+ * Show error toast
2558
+ * @param message - Toast message
2559
+ * @param options - Additional options
2560
+ */
2561
+ error(message, options) {
2562
+ return this.show({ ...options, message, variant: 'error' });
2563
+ }
2564
+ /**
2565
+ * Dismiss a specific toast
2566
+ * @param id - Toast ID to dismiss
2567
+ */
2568
+ dismiss(id) {
2569
+ // Mark as exiting for animation
2570
+ this._toasts.update((toasts) => toasts.map((t) => (t.id === id ? { ...t, isExiting: true } : t)));
2571
+ // Remove after animation completes (200ms)
2572
+ setTimeout(() => {
2573
+ this._toasts.update((toasts) => toasts.filter((t) => t.id !== id));
2574
+ // Notify ref
2575
+ const toastRef = this.toastRefs.get(id);
2576
+ if (toastRef) {
2577
+ toastRef._notifyDismissed();
2578
+ this.toastRefs.delete(id);
2579
+ }
2580
+ }, 200);
2581
+ }
2582
+ /**
2583
+ * Dismiss all toasts
2584
+ */
2585
+ dismissAll() {
2586
+ const ids = this._toasts().map((t) => t.id);
2587
+ ids.forEach((id) => this.dismiss(id));
2588
+ }
2589
+ /**
2590
+ * Ensure toast container exists in DOM
2591
+ */
2592
+ ensureContainer() {
2593
+ if (!isPlatformBrowser(this.platformId))
2594
+ return;
2595
+ if (this.containerRef)
2596
+ return;
2597
+ this.containerRef = createComponent(LmToastContainerComponent, {
2598
+ environmentInjector: this.appRef.injector,
2599
+ elementInjector: this.injector,
2600
+ });
2601
+ // Pass toasts signal to container
2602
+ this.containerRef.instance._toasts = this._toasts;
2603
+ this.containerRef.instance._onDismiss = (id) => this.dismiss(id);
2604
+ this.appRef.attachView(this.containerRef.hostView);
2605
+ this.document.body.appendChild(this.containerRef.location.nativeElement);
2606
+ }
2607
+ /**
2608
+ * Remove container from DOM
2609
+ */
2610
+ destroyContainer() {
2611
+ if (this.containerRef) {
2612
+ this.appRef.detachView(this.containerRef.hostView);
2613
+ this.containerRef.destroy();
2614
+ this.containerRef = null;
2615
+ }
2616
+ }
2617
+ /**
2618
+ * Announce toast to screen readers
2619
+ */
2620
+ announceToast(toast) {
2621
+ const prefix = this.getVariantPrefix(toast.variant);
2622
+ const message = toast.title
2623
+ ? `${prefix}: ${toast.title}. ${toast.message}`
2624
+ : `${prefix}: ${toast.message}`;
2625
+ const politeness = toast.variant === 'error' ? 'assertive' : 'polite';
2626
+ this.liveAnnouncer.announce(message, politeness);
2627
+ }
2628
+ /**
2629
+ * Get announcement prefix for variant
2630
+ */
2631
+ getVariantPrefix(variant) {
2632
+ const prefixes = {
2633
+ info: 'Information',
2634
+ success: 'Success',
2635
+ warning: 'Warning',
2636
+ error: 'Error',
2637
+ };
2638
+ return prefixes[variant];
2639
+ }
2640
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2641
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastService, providedIn: 'root' });
2642
+ }
2643
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmToastService, decorators: [{
2644
+ type: Injectable,
2645
+ args: [{ providedIn: 'root' }]
2646
+ }] });
2647
+
2648
+ // Toast public API
2649
+
120
2650
  // Button exports
121
2651
 
122
2652
  /**
123
2653
  * Generated bundle index. Do not edit.
124
2654
  */
125
2655
 
126
- export { ButtonDirective, CardComponent, CardContentDirective, CardDescriptionDirective, CardHeaderDirective, CardTitleDirective };
2656
+ export { ACCORDION_ITEM, DEFAULT_TOAST_CONFIG, LmAccordionContentDirective, LmAccordionGroupComponent, LmAccordionIconDirective, LmAccordionItemComponent, LmAccordionTitleDirective, LmAccordionTriggerDirective, LmBadgeDirective, LmButtonDirective, LmCardComponent, LmCardContentDirective, LmCardDescriptionDirective, LmCardHeaderDirective, LmCardTitleDirective, LmModalCloseComponent, LmModalComponent, LmModalContainerComponent, LmModalContentDirective, LmModalFooterDirective, LmModalHeaderDirective, LmModalOverlayComponent, LmModalTitleDirective, LmTabsComponent, LmTabsIndicatorComponent, LmTabsListDirective, LmTabsPanelDirective, LmTabsTriggerDirective, LmToastCloseComponent, LmToastContainerComponent, LmToastItemComponent, LmToastService, LmTooltipDirective, MODAL_CONTEXT, TABS_GROUP, TABS_LIST, TOAST_CONFIG, provideToastConfig };
127
2657
  //# sourceMappingURL=lumaui-angular.mjs.map