@lumaui/angular 0.2.3 → 0.3.3
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.
- package/fesm2022/lumaui-angular.mjs +439 -77
- package/fesm2022/lumaui-angular.mjs.map +1 -1
- package/package.json +3 -3
- package/types/lumaui-angular.d.ts +85 -17
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/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,
|
|
4
|
-
import {
|
|
2
|
+
import { input, computed, HostBinding, Directive, ChangeDetectionStrategy, Component, output, InjectionToken, inject, ElementRef, Renderer2, effect, signal, HostListener, PLATFORM_ID, NgZone, viewChild, TemplateRef, ViewContainerRef, ApplicationRef, Injector, createComponent, Injectable } from '@angular/core';
|
|
3
|
+
import { buttonVariants, badgeVariants, cardVariants, cardTitleVariants, cardDescriptionVariants, cardHeaderVariants, cardContentVariants, accordionItemVariants, accordionContentWrapperVariants, accordionTriggerVariants, accordionTitleVariants, accordionIconVariants, accordionContentVariants, tooltipVariants, tabsListVariants, tabsScrollArrowVariants, tabsTriggerVariants, tabsPanelVariants, tabsIndicatorVariants, modalOverlayVariants, modalContainerVariants, modalHeaderVariants, modalTitleVariants, modalContentVariants, modalFooterVariants, modalCloseVariants, toastCloseVariants, toastItemVariants, toastIconVariants, toastContentVariants, toastTitleVariants, toastMessageVariants, toastContainerVariants } from '@lumaui/core';
|
|
4
|
+
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
|
5
5
|
import { Subject, interval } from 'rxjs';
|
|
6
6
|
import { takeWhile, filter } from 'rxjs/operators';
|
|
7
7
|
|
|
@@ -9,18 +9,20 @@ class LmButtonDirective {
|
|
|
9
9
|
// Signal-based inputs with lm prefix (Angular 20+)
|
|
10
10
|
lmVariant = input('primary', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
|
|
11
11
|
lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
|
|
12
|
+
lmRadius = input('default', ...(ngDevMode ? [{ debugName: "lmRadius" }] : []));
|
|
12
13
|
lmDisabled = input(false, ...(ngDevMode ? [{ debugName: "lmDisabled" }] : []));
|
|
13
14
|
lmType = input('button', ...(ngDevMode ? [{ debugName: "lmType" }] : []));
|
|
14
15
|
// Computed class string
|
|
15
16
|
classes = computed(() => buttonVariants({
|
|
16
17
|
variant: this.lmVariant(),
|
|
17
18
|
size: this.lmSize(),
|
|
19
|
+
radius: this.lmRadius(),
|
|
18
20
|
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
19
21
|
get hostClasses() {
|
|
20
22
|
return this.classes();
|
|
21
23
|
}
|
|
22
24
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmButtonDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
23
|
-
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 });
|
|
25
|
+
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 }, lmRadius: { classPropertyName: "lmRadius", publicName: "lmRadius", 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 });
|
|
24
26
|
}
|
|
25
27
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmButtonDirective, decorators: [{
|
|
26
28
|
type: Directive,
|
|
@@ -31,26 +33,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
31
33
|
'[attr.disabled]': 'lmDisabled() ? "" : null',
|
|
32
34
|
},
|
|
33
35
|
}]
|
|
34
|
-
}], propDecorators: { lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }], lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }], lmDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDisabled", required: false }] }], lmType: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmType", required: false }] }], hostClasses: [{
|
|
36
|
+
}], propDecorators: { lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }], lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }], lmRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmRadius", required: false }] }], lmDisabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmDisabled", required: false }] }], lmType: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmType", required: false }] }], hostClasses: [{
|
|
35
37
|
type: HostBinding,
|
|
36
38
|
args: ['class']
|
|
37
39
|
}] } });
|
|
38
40
|
|
|
39
41
|
class LmBadgeDirective {
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
+
// Signal-based inputs with lm prefix (Angular 20+)
|
|
43
|
+
lmVariant = input('default', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
|
|
44
|
+
lmRadius = input('default', ...(ngDevMode ? [{ debugName: "lmRadius" }] : []));
|
|
45
|
+
// Computed class string
|
|
46
|
+
classes = computed(() => badgeVariants({
|
|
47
|
+
variant: this.lmVariant(),
|
|
48
|
+
radius: this.lmRadius(),
|
|
49
|
+
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
42
50
|
get hostClasses() {
|
|
43
51
|
return this.classes();
|
|
44
52
|
}
|
|
45
53
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmBadgeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
46
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "
|
|
54
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.0.9", type: LmBadgeDirective, isStandalone: true, selector: "[lumaBadge]", inputs: { lmVariant: { classPropertyName: "lmVariant", publicName: "lmVariant", isSignal: true, isRequired: false, transformFunction: null }, lmRadius: { classPropertyName: "lmRadius", publicName: "lmRadius", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "this.hostClasses" } }, ngImport: i0 });
|
|
47
55
|
}
|
|
48
56
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmBadgeDirective, decorators: [{
|
|
49
57
|
type: Directive,
|
|
50
58
|
args: [{
|
|
51
59
|
selector: '[lumaBadge]',
|
|
52
60
|
}]
|
|
53
|
-
}], propDecorators: { hostClasses: [{
|
|
61
|
+
}], propDecorators: { lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }], lmRadius: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmRadius", required: false }] }], hostClasses: [{
|
|
54
62
|
type: HostBinding,
|
|
55
63
|
args: ['class']
|
|
56
64
|
}] } });
|
|
@@ -58,26 +66,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
58
66
|
class LmCardComponent {
|
|
59
67
|
/**
|
|
60
68
|
* Card visual style variant
|
|
61
|
-
* - default:
|
|
62
|
-
* -
|
|
63
|
-
* -
|
|
64
|
-
* - preview: For documentation examples
|
|
69
|
+
* - default: Simple border card
|
|
70
|
+
* - elevated: Card with shadow elevation
|
|
71
|
+
* - subtle: Muted background card
|
|
65
72
|
*/
|
|
66
73
|
lmVariant = input('default', ...(ngDevMode ? [{ debugName: "lmVariant" }] : []));
|
|
67
|
-
// Computed class
|
|
68
|
-
|
|
69
|
-
contentClasses = computed(() => cardContentVariants({ variant: this.lmVariant() }), ...(ngDevMode ? [{ debugName: "contentClasses" }] : []));
|
|
74
|
+
// Computed class string based on variant
|
|
75
|
+
classes = computed(() => cardVariants({ variant: this.lmVariant() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
70
76
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
71
|
-
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]=\"
|
|
77
|
+
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]=\"classes()\">\n <ng-content></ng-content>\n</div>\n", changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
72
78
|
}
|
|
73
79
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardComponent, decorators: [{
|
|
74
80
|
type: Component,
|
|
75
|
-
args: [{ selector: 'luma-card', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"
|
|
81
|
+
args: [{ selector: 'luma-card', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div [class]=\"classes()\">\n <ng-content></ng-content>\n</div>\n" }]
|
|
76
82
|
}], propDecorators: { lmVariant: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmVariant", required: false }] }] } });
|
|
77
83
|
|
|
78
84
|
class LmCardTitleDirective {
|
|
79
85
|
// Signal-based inputs with lm prefix (Angular 20+)
|
|
80
|
-
lmSize = input('
|
|
86
|
+
lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
|
|
81
87
|
// Computed class string
|
|
82
88
|
classes = computed(() => cardTitleVariants({ size: this.lmSize() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
83
89
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -95,7 +101,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
95
101
|
|
|
96
102
|
class LmCardDescriptionDirective {
|
|
97
103
|
// Signal-based inputs with lm prefix (Angular 20+)
|
|
98
|
-
lmSize = input('
|
|
104
|
+
lmSize = input('md', ...(ngDevMode ? [{ debugName: "lmSize" }] : []));
|
|
99
105
|
// Computed class string
|
|
100
106
|
classes = computed(() => cardDescriptionVariants({ size: this.lmSize() }), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
101
107
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardDescriptionDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
@@ -112,29 +118,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
112
118
|
}], propDecorators: { lmSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmSize", required: false }] }] } });
|
|
113
119
|
|
|
114
120
|
class LmCardHeaderDirective {
|
|
121
|
+
// Computed class string
|
|
122
|
+
classes = computed(() => cardHeaderVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
115
123
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardHeaderDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
116
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmCardHeaderDirective, isStandalone: true, selector: "[lumaCardHeader]", host: {
|
|
124
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmCardHeaderDirective, isStandalone: true, selector: "[lumaCardHeader]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
117
125
|
}
|
|
118
126
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardHeaderDirective, decorators: [{
|
|
119
127
|
type: Directive,
|
|
120
128
|
args: [{
|
|
121
129
|
selector: '[lumaCardHeader]',
|
|
122
130
|
host: {
|
|
123
|
-
class: '
|
|
131
|
+
'[class]': 'classes()',
|
|
124
132
|
},
|
|
125
133
|
}]
|
|
126
134
|
}] });
|
|
127
135
|
|
|
128
136
|
class LmCardContentDirective {
|
|
137
|
+
// Computed class string
|
|
138
|
+
classes = computed(() => cardContentVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
129
139
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
130
|
-
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmCardContentDirective, isStandalone: true, selector: "[lumaCardContent]", ngImport: i0 });
|
|
140
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.0.9", type: LmCardContentDirective, isStandalone: true, selector: "[lumaCardContent]", host: { properties: { "class": "classes()" } }, ngImport: i0 });
|
|
131
141
|
}
|
|
132
142
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmCardContentDirective, decorators: [{
|
|
133
143
|
type: Directive,
|
|
134
144
|
args: [{
|
|
135
145
|
selector: '[lumaCardContent]',
|
|
136
146
|
host: {
|
|
137
|
-
class: '',
|
|
147
|
+
'[class]': 'classes()',
|
|
138
148
|
},
|
|
139
149
|
}]
|
|
140
150
|
}] });
|
|
@@ -557,10 +567,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
557
567
|
}]
|
|
558
568
|
}] });
|
|
559
569
|
|
|
570
|
+
const TOOLTIP_GAP = 8;
|
|
560
571
|
class LmTooltipDirective {
|
|
561
572
|
el = inject(ElementRef);
|
|
562
573
|
renderer = inject(Renderer2);
|
|
563
574
|
platformId = inject(PLATFORM_ID);
|
|
575
|
+
document = inject(DOCUMENT);
|
|
576
|
+
ngZone = inject(NgZone);
|
|
564
577
|
// Inputs with lm prefix following Lumo convention
|
|
565
578
|
lumaTooltip = input.required(...(ngDevMode ? [{ debugName: "lumaTooltip" }] : []));
|
|
566
579
|
lmPosition = input('top', ...(ngDevMode ? [{ debugName: "lmPosition" }] : []));
|
|
@@ -573,8 +586,10 @@ class LmTooltipDirective {
|
|
|
573
586
|
tooltipId = `tooltip-${Math.random().toString(36).slice(2, 9)}`;
|
|
574
587
|
tooltipElement = null;
|
|
575
588
|
showTimeout = null;
|
|
589
|
+
scrollListener = null;
|
|
590
|
+
resizeListener = null;
|
|
591
|
+
captureScrollHandler = null;
|
|
576
592
|
classes = computed(() => tooltipVariants({
|
|
577
|
-
position: this.actualPosition(),
|
|
578
593
|
visible: this.isVisible(),
|
|
579
594
|
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
580
595
|
constructor() {
|
|
@@ -593,7 +608,8 @@ class LmTooltipDirective {
|
|
|
593
608
|
this.tooltipElement = this.renderer.createElement('div');
|
|
594
609
|
this.renderer.setAttribute(this.tooltipElement, 'id', this.tooltipId);
|
|
595
610
|
this.renderer.setAttribute(this.tooltipElement, 'role', 'tooltip');
|
|
596
|
-
|
|
611
|
+
// Portal: append to document.body to avoid overflow clipping
|
|
612
|
+
this.renderer.appendChild(this.document.body, this.tooltipElement);
|
|
597
613
|
}
|
|
598
614
|
updateContent() {
|
|
599
615
|
if (!this.tooltipElement)
|
|
@@ -611,6 +627,68 @@ class LmTooltipDirective {
|
|
|
611
627
|
return;
|
|
612
628
|
this.tooltipElement.className = this.classes();
|
|
613
629
|
}
|
|
630
|
+
updatePosition() {
|
|
631
|
+
if (!this.tooltipElement || !isPlatformBrowser(this.platformId))
|
|
632
|
+
return;
|
|
633
|
+
const triggerRect = this.el.nativeElement.getBoundingClientRect();
|
|
634
|
+
const position = this.actualPosition();
|
|
635
|
+
let top;
|
|
636
|
+
let left;
|
|
637
|
+
let transform;
|
|
638
|
+
switch (position) {
|
|
639
|
+
case 'top':
|
|
640
|
+
left = triggerRect.left + triggerRect.width / 2;
|
|
641
|
+
top = triggerRect.top - TOOLTIP_GAP;
|
|
642
|
+
transform = 'translate(-50%, -100%)';
|
|
643
|
+
break;
|
|
644
|
+
case 'bottom':
|
|
645
|
+
left = triggerRect.left + triggerRect.width / 2;
|
|
646
|
+
top = triggerRect.bottom + TOOLTIP_GAP;
|
|
647
|
+
transform = 'translate(-50%, 0%)';
|
|
648
|
+
break;
|
|
649
|
+
case 'left':
|
|
650
|
+
left = triggerRect.left - TOOLTIP_GAP;
|
|
651
|
+
top = triggerRect.top + triggerRect.height / 2;
|
|
652
|
+
transform = 'translate(-100%, -50%)';
|
|
653
|
+
break;
|
|
654
|
+
case 'right':
|
|
655
|
+
left = triggerRect.right + TOOLTIP_GAP;
|
|
656
|
+
top = triggerRect.top + triggerRect.height / 2;
|
|
657
|
+
transform = 'translate(0%, -50%)';
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
this.renderer.setStyle(this.tooltipElement, 'top', `${top}px`);
|
|
661
|
+
this.renderer.setStyle(this.tooltipElement, 'left', `${left}px`);
|
|
662
|
+
this.renderer.setStyle(this.tooltipElement, 'transform', transform);
|
|
663
|
+
}
|
|
664
|
+
addPositionListeners() {
|
|
665
|
+
if (!isPlatformBrowser(this.platformId))
|
|
666
|
+
return;
|
|
667
|
+
// Run outside Angular zone for performance — no change detection needed
|
|
668
|
+
this.ngZone.runOutsideAngular(() => {
|
|
669
|
+
const onReposition = () => this.updatePosition();
|
|
670
|
+
this.scrollListener = this.renderer.listen('window', 'scroll', onReposition);
|
|
671
|
+
// Capture phase catches scroll events on any ancestor
|
|
672
|
+
this.document.addEventListener('scroll', onReposition, true);
|
|
673
|
+
this.captureScrollHandler = onReposition;
|
|
674
|
+
this.resizeListener = this.renderer.listen('window', 'resize', onReposition);
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
removePositionListeners() {
|
|
678
|
+
if (this.scrollListener) {
|
|
679
|
+
this.scrollListener();
|
|
680
|
+
this.scrollListener = null;
|
|
681
|
+
}
|
|
682
|
+
if (this.resizeListener) {
|
|
683
|
+
this.resizeListener();
|
|
684
|
+
this.resizeListener = null;
|
|
685
|
+
}
|
|
686
|
+
// Remove capture-phase scroll listener
|
|
687
|
+
if (this.captureScrollHandler) {
|
|
688
|
+
this.document.removeEventListener('scroll', this.captureScrollHandler, true);
|
|
689
|
+
this.captureScrollHandler = null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
614
692
|
isTouchDevice() {
|
|
615
693
|
if (!isPlatformBrowser(this.platformId))
|
|
616
694
|
return false;
|
|
@@ -624,25 +702,26 @@ class LmTooltipDirective {
|
|
|
624
702
|
const tooltipRect = this.tooltipElement.getBoundingClientRect();
|
|
625
703
|
const viewportWidth = window.innerWidth;
|
|
626
704
|
const viewportHeight = window.innerHeight;
|
|
627
|
-
const offset = 8; // --luma-tooltip-offset
|
|
628
705
|
switch (preferred) {
|
|
629
706
|
case 'top':
|
|
630
|
-
if (triggerRect.top - tooltipRect.height -
|
|
707
|
+
if (triggerRect.top - tooltipRect.height - TOOLTIP_GAP < 0) {
|
|
631
708
|
return 'bottom';
|
|
632
709
|
}
|
|
633
710
|
break;
|
|
634
711
|
case 'bottom':
|
|
635
|
-
if (triggerRect.bottom + tooltipRect.height +
|
|
712
|
+
if (triggerRect.bottom + tooltipRect.height + TOOLTIP_GAP >
|
|
713
|
+
viewportHeight) {
|
|
636
714
|
return 'top';
|
|
637
715
|
}
|
|
638
716
|
break;
|
|
639
717
|
case 'left':
|
|
640
|
-
if (triggerRect.left - tooltipRect.width -
|
|
718
|
+
if (triggerRect.left - tooltipRect.width - TOOLTIP_GAP < 0) {
|
|
641
719
|
return 'right';
|
|
642
720
|
}
|
|
643
721
|
break;
|
|
644
722
|
case 'right':
|
|
645
|
-
if (triggerRect.right + tooltipRect.width +
|
|
723
|
+
if (triggerRect.right + tooltipRect.width + TOOLTIP_GAP >
|
|
724
|
+
viewportWidth) {
|
|
646
725
|
return 'left';
|
|
647
726
|
}
|
|
648
727
|
break;
|
|
@@ -686,14 +765,19 @@ class LmTooltipDirective {
|
|
|
686
765
|
onDocumentClick(event) {
|
|
687
766
|
if (this.lmTrigger() !== 'click' && !this.isTouchDevice())
|
|
688
767
|
return;
|
|
689
|
-
|
|
768
|
+
const target = event.target;
|
|
769
|
+
// Check both trigger and portal tooltip (tooltip is now in body, not trigger)
|
|
770
|
+
if (!this.el.nativeElement.contains(target) &&
|
|
771
|
+
!this.tooltipElement?.contains(target)) {
|
|
690
772
|
this.hide();
|
|
691
773
|
}
|
|
692
774
|
}
|
|
693
775
|
onDocumentTouch(event) {
|
|
694
776
|
if (!this.isVisible())
|
|
695
777
|
return;
|
|
696
|
-
|
|
778
|
+
const target = event.target;
|
|
779
|
+
if (!this.el.nativeElement.contains(target) &&
|
|
780
|
+
!this.tooltipElement?.contains(target)) {
|
|
697
781
|
this.hide();
|
|
698
782
|
}
|
|
699
783
|
}
|
|
@@ -702,12 +786,15 @@ class LmTooltipDirective {
|
|
|
702
786
|
clearTimeout(this.showTimeout);
|
|
703
787
|
const delay = this.lmDelay();
|
|
704
788
|
const showAction = () => {
|
|
789
|
+
// Calculate position with auto-flip
|
|
790
|
+
const actualPosition = this.getFlippedPosition(this.lmPosition());
|
|
791
|
+
this.actualPosition.set(actualPosition);
|
|
705
792
|
this.isVisible.set(true);
|
|
706
|
-
|
|
793
|
+
this.updateClasses();
|
|
794
|
+
// Position after DOM paint so tooltip dimensions are available
|
|
707
795
|
requestAnimationFrame(() => {
|
|
708
|
-
|
|
709
|
-
this.
|
|
710
|
-
this.updateClasses();
|
|
796
|
+
this.updatePosition();
|
|
797
|
+
this.addPositionListeners();
|
|
711
798
|
});
|
|
712
799
|
};
|
|
713
800
|
if (delay > 0) {
|
|
@@ -724,6 +811,7 @@ class LmTooltipDirective {
|
|
|
724
811
|
}
|
|
725
812
|
this.isVisible.set(false);
|
|
726
813
|
this.updateClasses();
|
|
814
|
+
this.removePositionListeners();
|
|
727
815
|
}
|
|
728
816
|
toggle() {
|
|
729
817
|
if (this.isVisible()) {
|
|
@@ -736,12 +824,13 @@ class LmTooltipDirective {
|
|
|
736
824
|
ngOnDestroy() {
|
|
737
825
|
if (this.showTimeout)
|
|
738
826
|
clearTimeout(this.showTimeout);
|
|
827
|
+
this.removePositionListeners();
|
|
739
828
|
if (this.tooltipElement) {
|
|
740
|
-
this.renderer.removeChild(this.
|
|
829
|
+
this.renderer.removeChild(this.document.body, this.tooltipElement);
|
|
741
830
|
}
|
|
742
831
|
}
|
|
743
832
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTooltipDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
744
|
-
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"
|
|
833
|
+
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" } }, ngImport: i0 });
|
|
745
834
|
}
|
|
746
835
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTooltipDirective, decorators: [{
|
|
747
836
|
type: Directive,
|
|
@@ -749,7 +838,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
749
838
|
selector: '[lumaTooltip]',
|
|
750
839
|
host: {
|
|
751
840
|
'[attr.aria-describedby]': 'tooltipId',
|
|
752
|
-
'[style.position]': '"relative"',
|
|
753
841
|
},
|
|
754
842
|
}]
|
|
755
843
|
}], 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: [{
|
|
@@ -935,10 +1023,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
935
1023
|
}], 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"] }] } });
|
|
936
1024
|
|
|
937
1025
|
/**
|
|
938
|
-
* Tabs list
|
|
1026
|
+
* Tabs list component
|
|
939
1027
|
*
|
|
940
1028
|
* Container for tab triggers with role="tablist".
|
|
941
1029
|
* Provides context for the indicator component.
|
|
1030
|
+
* Supports horizontal scrolling with arrow navigation.
|
|
942
1031
|
*
|
|
943
1032
|
* @example
|
|
944
1033
|
* ```html
|
|
@@ -947,16 +1036,125 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
947
1036
|
* <button lumaTabsTrigger="tab-2">Tab 2</button>
|
|
948
1037
|
* </div>
|
|
949
1038
|
* ```
|
|
1039
|
+
*
|
|
1040
|
+
* @example
|
|
1041
|
+
* ```html
|
|
1042
|
+
* <div lumaTabsList [lmScrollable]="true">
|
|
1043
|
+
* <button lumaTabsTrigger="tab-1">Tab 1</button>
|
|
1044
|
+
* <!-- ... many more tabs -->
|
|
1045
|
+
* </div>
|
|
1046
|
+
* ```
|
|
950
1047
|
*/
|
|
951
1048
|
class LmTabsListDirective {
|
|
952
1049
|
elementRef = inject((ElementRef));
|
|
953
1050
|
tabsGroup = inject(TABS_GROUP);
|
|
1051
|
+
platformId = inject(PLATFORM_ID);
|
|
1052
|
+
/** Reference to the scroll container */
|
|
1053
|
+
scrollContainerRef = viewChild('scrollContainer', ...(ngDevMode ? [{ debugName: "scrollContainerRef" }] : []));
|
|
954
1054
|
/** Whether horizontal scrolling is enabled */
|
|
955
|
-
lmScrollable = false;
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1055
|
+
lmScrollable = input(false, ...(ngDevMode ? [{ debugName: "lmScrollable" }] : []));
|
|
1056
|
+
/** Whether to show scroll arrows */
|
|
1057
|
+
showArrows = signal(false, ...(ngDevMode ? [{ debugName: "showArrows" }] : []));
|
|
1058
|
+
showLeftArrow = signal(false, ...(ngDevMode ? [{ debugName: "showLeftArrow" }] : []));
|
|
1059
|
+
showRightArrow = signal(false, ...(ngDevMode ? [{ debugName: "showRightArrow" }] : []));
|
|
1060
|
+
/** ResizeObserver for overflow detection */
|
|
1061
|
+
resizeObserver;
|
|
1062
|
+
/** Debounce timeout for ResizeObserver */
|
|
1063
|
+
resizeTimeout;
|
|
1064
|
+
/** Throttle timeout for scroll events */
|
|
1065
|
+
scrollTimeout;
|
|
1066
|
+
/** Host element classes */
|
|
1067
|
+
hostClasses = computed(() => {
|
|
1068
|
+
if (this.lmScrollable()) {
|
|
1069
|
+
// Scrollable: host is a structural flex wrapper (no role, no variant styling)
|
|
1070
|
+
return 'flex items-center gap-2 w-full';
|
|
1071
|
+
}
|
|
1072
|
+
// Non-scrollable: host is the tablist with variant styling
|
|
1073
|
+
return tabsListVariants({
|
|
1074
|
+
variant: this.tabsGroup.lmVariant(),
|
|
1075
|
+
});
|
|
1076
|
+
}, ...(ngDevMode ? [{ debugName: "hostClasses" }] : []));
|
|
1077
|
+
/** Scroll container classes */
|
|
1078
|
+
scrollContainerClasses = computed(() => {
|
|
1079
|
+
const baseClasses = ['flex', 'items-center'];
|
|
1080
|
+
if (this.lmScrollable()) {
|
|
1081
|
+
const scrollClasses = [
|
|
1082
|
+
...baseClasses,
|
|
1083
|
+
'flex-1', // Fill available space in flex wrapper
|
|
1084
|
+
'relative', // For indicator absolute positioning
|
|
1085
|
+
'overflow-x-auto',
|
|
1086
|
+
'scrollbar-none',
|
|
1087
|
+
'scroll-smooth',
|
|
1088
|
+
'-webkit-overflow-scrolling-touch',
|
|
1089
|
+
];
|
|
1090
|
+
// Move variant visual styling from host to scroll container
|
|
1091
|
+
const variant = this.tabsGroup.lmVariant();
|
|
1092
|
+
if (variant === 'underline') {
|
|
1093
|
+
scrollClasses.push('border-b', 'border-border');
|
|
1094
|
+
}
|
|
1095
|
+
else if (variant === 'pills') {
|
|
1096
|
+
scrollClasses.push('p-1', 'bg-muted', 'rounded-lg');
|
|
1097
|
+
}
|
|
1098
|
+
return scrollClasses;
|
|
1099
|
+
}
|
|
1100
|
+
// Non-scrollable: apply w-full and variant-specific classes
|
|
1101
|
+
const variant = this.tabsGroup.lmVariant();
|
|
1102
|
+
if (variant === 'underline') {
|
|
1103
|
+
return [...baseClasses, 'w-full', 'gap-4'];
|
|
1104
|
+
}
|
|
1105
|
+
else if (variant === 'pills') {
|
|
1106
|
+
return [...baseClasses, 'w-full', 'gap-1'];
|
|
1107
|
+
}
|
|
1108
|
+
return [...baseClasses, 'w-full'];
|
|
1109
|
+
}, ...(ngDevMode ? [{ debugName: "scrollContainerClasses" }] : []));
|
|
1110
|
+
/** Left arrow button classes */
|
|
1111
|
+
leftArrowClasses = computed(() => tabsScrollArrowVariants({ direction: 'left' }), ...(ngDevMode ? [{ debugName: "leftArrowClasses" }] : []));
|
|
1112
|
+
/** Right arrow button classes */
|
|
1113
|
+
rightArrowClasses = computed(() => tabsScrollArrowVariants({ direction: 'right' }), ...(ngDevMode ? [{ debugName: "rightArrowClasses" }] : []));
|
|
1114
|
+
constructor() {
|
|
1115
|
+
// Update arrows when scrollable changes
|
|
1116
|
+
effect(() => {
|
|
1117
|
+
if (this.lmScrollable()) {
|
|
1118
|
+
this.updateArrowsVisibility();
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
this.showArrows.set(false);
|
|
1122
|
+
}
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
ngAfterViewInit() {
|
|
1126
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const container = this.scrollContainerRef()?.nativeElement;
|
|
1130
|
+
if (!container)
|
|
1131
|
+
return;
|
|
1132
|
+
// Set up ResizeObserver with debouncing to prevent infinite feedback loop
|
|
1133
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
1134
|
+
// Debounce: wait 250ms after last resize before updating
|
|
1135
|
+
if (this.resizeTimeout !== undefined) {
|
|
1136
|
+
clearTimeout(this.resizeTimeout);
|
|
1137
|
+
}
|
|
1138
|
+
this.resizeTimeout = window.setTimeout(() => {
|
|
1139
|
+
this.updateArrowsVisibility();
|
|
1140
|
+
this.resizeTimeout = undefined;
|
|
1141
|
+
}, 250);
|
|
1142
|
+
});
|
|
1143
|
+
// Only observe the container, NOT children (prevents cascading updates)
|
|
1144
|
+
this.resizeObserver.observe(container);
|
|
1145
|
+
// Initial check
|
|
1146
|
+
this.updateArrowsVisibility();
|
|
1147
|
+
}
|
|
1148
|
+
ngOnDestroy() {
|
|
1149
|
+
this.resizeObserver?.disconnect();
|
|
1150
|
+
// Clear timeouts to prevent memory leaks
|
|
1151
|
+
if (this.resizeTimeout !== undefined) {
|
|
1152
|
+
clearTimeout(this.resizeTimeout);
|
|
1153
|
+
}
|
|
1154
|
+
if (this.scrollTimeout !== undefined) {
|
|
1155
|
+
clearTimeout(this.scrollTimeout);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
960
1158
|
/**
|
|
961
1159
|
* Get the currently active trigger element
|
|
962
1160
|
*/
|
|
@@ -967,25 +1165,157 @@ class LmTabsListDirective {
|
|
|
967
1165
|
const triggers = this.tabsGroup.getTriggers();
|
|
968
1166
|
return triggers.get(currentValue) || null;
|
|
969
1167
|
}
|
|
1168
|
+
/**
|
|
1169
|
+
* Get the element that acts as the semantic tablist container.
|
|
1170
|
+
* When scrollable, this is the inner scroll container (which has role="tablist").
|
|
1171
|
+
* When non-scrollable, this is the host element itself.
|
|
1172
|
+
*/
|
|
1173
|
+
getListContainer() {
|
|
1174
|
+
if (this.lmScrollable()) {
|
|
1175
|
+
return (this.scrollContainerRef()?.nativeElement ??
|
|
1176
|
+
this.elementRef.nativeElement);
|
|
1177
|
+
}
|
|
1178
|
+
return this.elementRef.nativeElement;
|
|
1179
|
+
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Scroll to the next set of tabs (85% of container width)
|
|
1182
|
+
*/
|
|
1183
|
+
scrollNext() {
|
|
1184
|
+
const container = this.scrollContainerRef()?.nativeElement;
|
|
1185
|
+
if (!container)
|
|
1186
|
+
return;
|
|
1187
|
+
const scrollAmount = container.clientWidth * 0.85;
|
|
1188
|
+
container.scrollBy({ left: scrollAmount, behavior: 'smooth' });
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Scroll to the previous set of tabs (85% of container width)
|
|
1192
|
+
*/
|
|
1193
|
+
scrollPrevious() {
|
|
1194
|
+
const container = this.scrollContainerRef()?.nativeElement;
|
|
1195
|
+
if (!container)
|
|
1196
|
+
return;
|
|
1197
|
+
const scrollAmount = container.clientWidth * 0.85;
|
|
1198
|
+
container.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Handle scroll event to update arrow states (throttled)
|
|
1202
|
+
*/
|
|
1203
|
+
onScroll() {
|
|
1204
|
+
if (!this.lmScrollable())
|
|
1205
|
+
return;
|
|
1206
|
+
// Throttle: only update after 100ms of no scrolling
|
|
1207
|
+
if (this.scrollTimeout !== undefined) {
|
|
1208
|
+
clearTimeout(this.scrollTimeout);
|
|
1209
|
+
}
|
|
1210
|
+
this.scrollTimeout = window.setTimeout(() => {
|
|
1211
|
+
this.updateArrowsVisibility();
|
|
1212
|
+
this.scrollTimeout = undefined;
|
|
1213
|
+
}, 100);
|
|
1214
|
+
}
|
|
970
1215
|
/**
|
|
971
1216
|
* Handle mouse wheel for horizontal scroll
|
|
972
1217
|
*/
|
|
973
1218
|
onWheel(event) {
|
|
974
|
-
if (this.lmScrollable)
|
|
975
|
-
|
|
976
|
-
|
|
1219
|
+
if (!this.lmScrollable())
|
|
1220
|
+
return;
|
|
1221
|
+
const container = this.scrollContainerRef()?.nativeElement;
|
|
1222
|
+
if (!container)
|
|
1223
|
+
return;
|
|
1224
|
+
event.preventDefault();
|
|
1225
|
+
container.scrollLeft += event.deltaY;
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Update arrow visibility and enabled states based on scroll position
|
|
1229
|
+
* Optimized to only update signals when values actually change
|
|
1230
|
+
*/
|
|
1231
|
+
updateArrowsVisibility() {
|
|
1232
|
+
if (!isPlatformBrowser(this.platformId)) {
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
const container = this.scrollContainerRef()?.nativeElement;
|
|
1236
|
+
if (!container)
|
|
1237
|
+
return;
|
|
1238
|
+
// Check if content overflows container
|
|
1239
|
+
const hasOverflow = container.scrollWidth > container.clientWidth;
|
|
1240
|
+
// Only update if value actually changed
|
|
1241
|
+
if (this.showArrows() !== hasOverflow) {
|
|
1242
|
+
this.showArrows.set(hasOverflow);
|
|
1243
|
+
}
|
|
1244
|
+
if (hasOverflow) {
|
|
1245
|
+
// Update individual arrow states based on scroll position
|
|
1246
|
+
const isAtStart = container.scrollLeft <= 1;
|
|
1247
|
+
const isAtEnd = container.scrollLeft >=
|
|
1248
|
+
container.scrollWidth - container.clientWidth - 1;
|
|
1249
|
+
const newShowLeft = !isAtStart;
|
|
1250
|
+
const newShowRight = !isAtEnd;
|
|
1251
|
+
// Only update if values changed
|
|
1252
|
+
if (this.showLeftArrow() !== newShowLeft) {
|
|
1253
|
+
this.showLeftArrow.set(newShowLeft);
|
|
1254
|
+
}
|
|
1255
|
+
if (this.showRightArrow() !== newShowRight) {
|
|
1256
|
+
this.showRightArrow.set(newShowRight);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
else {
|
|
1260
|
+
// Reset arrow states when no overflow
|
|
1261
|
+
if (this.showLeftArrow() !== false) {
|
|
1262
|
+
this.showLeftArrow.set(false);
|
|
1263
|
+
}
|
|
1264
|
+
if (this.showRightArrow() !== false) {
|
|
1265
|
+
this.showRightArrow.set(false);
|
|
1266
|
+
}
|
|
977
1267
|
}
|
|
978
1268
|
}
|
|
979
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsListDirective, deps: [], target: i0.ɵɵFactoryTarget.
|
|
980
|
-
static
|
|
1269
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsListDirective, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1270
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.0.9", type: LmTabsListDirective, isStandalone: true, selector: "[lumaTabsList]", inputs: { lmScrollable: { classPropertyName: "lmScrollable", publicName: "lmScrollable", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "attr.role": "lmScrollable() ? null : \"tablist\"", "attr.aria-orientation": "lmScrollable() ? null : \"horizontal\"", "class": "hostClasses()" } }, providers: [
|
|
981
1271
|
{
|
|
982
1272
|
provide: TABS_LIST,
|
|
983
1273
|
useExisting: LmTabsListDirective,
|
|
984
1274
|
},
|
|
985
|
-
], ngImport: i0
|
|
1275
|
+
], viewQueries: [{ propertyName: "scrollContainerRef", first: true, predicate: ["scrollContainer"], descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
1276
|
+
@if (lmScrollable()) {
|
|
1277
|
+
<!-- LEFT ARROW - outside tablist for correct ARIA -->
|
|
1278
|
+
<button
|
|
1279
|
+
type="button"
|
|
1280
|
+
[class]="leftArrowClasses()"
|
|
1281
|
+
[disabled]="!showLeftArrow()"
|
|
1282
|
+
[style.visibility]="showArrows() ? 'visible' : 'hidden'"
|
|
1283
|
+
(click)="scrollPrevious()"
|
|
1284
|
+
aria-label="Scroll to previous tabs"
|
|
1285
|
+
>
|
|
1286
|
+
‹
|
|
1287
|
+
</button>
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
<!-- SINGLE scroll container - always rendered to avoid dual ng-content -->
|
|
1291
|
+
<div
|
|
1292
|
+
#scrollContainer
|
|
1293
|
+
[attr.role]="lmScrollable() ? 'tablist' : null"
|
|
1294
|
+
[attr.aria-orientation]="lmScrollable() ? 'horizontal' : null"
|
|
1295
|
+
[class]="scrollContainerClasses()"
|
|
1296
|
+
(scroll)="onScroll()"
|
|
1297
|
+
(wheel)="onWheel($event)"
|
|
1298
|
+
>
|
|
1299
|
+
<ng-content />
|
|
1300
|
+
</div>
|
|
1301
|
+
|
|
1302
|
+
@if (lmScrollable()) {
|
|
1303
|
+
<!-- RIGHT ARROW - outside tablist for correct ARIA -->
|
|
1304
|
+
<button
|
|
1305
|
+
type="button"
|
|
1306
|
+
[class]="rightArrowClasses()"
|
|
1307
|
+
[disabled]="!showRightArrow()"
|
|
1308
|
+
[style.visibility]="showArrows() ? 'visible' : 'hidden'"
|
|
1309
|
+
(click)="scrollNext()"
|
|
1310
|
+
aria-label="Scroll to next tabs"
|
|
1311
|
+
>
|
|
1312
|
+
›
|
|
1313
|
+
</button>
|
|
1314
|
+
}
|
|
1315
|
+
`, isInline: true });
|
|
986
1316
|
}
|
|
987
1317
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsListDirective, decorators: [{
|
|
988
|
-
type:
|
|
1318
|
+
type: Component,
|
|
989
1319
|
args: [{
|
|
990
1320
|
selector: '[lumaTabsList]',
|
|
991
1321
|
providers: [
|
|
@@ -995,15 +1325,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
995
1325
|
},
|
|
996
1326
|
],
|
|
997
1327
|
host: {
|
|
998
|
-
role: 'tablist',
|
|
999
|
-
'[attr.aria-orientation]': '"horizontal"',
|
|
1000
|
-
'[class]': '
|
|
1328
|
+
'[attr.role]': 'lmScrollable() ? null : "tablist"',
|
|
1329
|
+
'[attr.aria-orientation]': 'lmScrollable() ? null : "horizontal"',
|
|
1330
|
+
'[class]': 'hostClasses()',
|
|
1001
1331
|
},
|
|
1332
|
+
template: `
|
|
1333
|
+
@if (lmScrollable()) {
|
|
1334
|
+
<!-- LEFT ARROW - outside tablist for correct ARIA -->
|
|
1335
|
+
<button
|
|
1336
|
+
type="button"
|
|
1337
|
+
[class]="leftArrowClasses()"
|
|
1338
|
+
[disabled]="!showLeftArrow()"
|
|
1339
|
+
[style.visibility]="showArrows() ? 'visible' : 'hidden'"
|
|
1340
|
+
(click)="scrollPrevious()"
|
|
1341
|
+
aria-label="Scroll to previous tabs"
|
|
1342
|
+
>
|
|
1343
|
+
‹
|
|
1344
|
+
</button>
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
<!-- SINGLE scroll container - always rendered to avoid dual ng-content -->
|
|
1348
|
+
<div
|
|
1349
|
+
#scrollContainer
|
|
1350
|
+
[attr.role]="lmScrollable() ? 'tablist' : null"
|
|
1351
|
+
[attr.aria-orientation]="lmScrollable() ? 'horizontal' : null"
|
|
1352
|
+
[class]="scrollContainerClasses()"
|
|
1353
|
+
(scroll)="onScroll()"
|
|
1354
|
+
(wheel)="onWheel($event)"
|
|
1355
|
+
>
|
|
1356
|
+
<ng-content />
|
|
1357
|
+
</div>
|
|
1358
|
+
|
|
1359
|
+
@if (lmScrollable()) {
|
|
1360
|
+
<!-- RIGHT ARROW - outside tablist for correct ARIA -->
|
|
1361
|
+
<button
|
|
1362
|
+
type="button"
|
|
1363
|
+
[class]="rightArrowClasses()"
|
|
1364
|
+
[disabled]="!showRightArrow()"
|
|
1365
|
+
[style.visibility]="showArrows() ? 'visible' : 'hidden'"
|
|
1366
|
+
(click)="scrollNext()"
|
|
1367
|
+
aria-label="Scroll to next tabs"
|
|
1368
|
+
>
|
|
1369
|
+
›
|
|
1370
|
+
</button>
|
|
1371
|
+
}
|
|
1372
|
+
`,
|
|
1002
1373
|
}]
|
|
1003
|
-
}], propDecorators: {
|
|
1004
|
-
type: HostListener,
|
|
1005
|
-
args: ['wheel', ['$event']]
|
|
1006
|
-
}] } });
|
|
1374
|
+
}], ctorParameters: () => [], propDecorators: { scrollContainerRef: [{ type: i0.ViewChild, args: ['scrollContainer', { isSignal: true }] }], lmScrollable: [{ type: i0.Input, args: [{ isSignal: true, alias: "lmScrollable", required: false }] }] } });
|
|
1007
1375
|
|
|
1008
1376
|
/**
|
|
1009
1377
|
* Tabs trigger directive
|
|
@@ -1031,8 +1399,7 @@ class LmTabsTriggerDirective {
|
|
|
1031
1399
|
panelId = computed(() => `tab-panel-${this.lumaTabsTrigger()}`, ...(ngDevMode ? [{ debugName: "panelId" }] : []));
|
|
1032
1400
|
/** Computed: CSS classes from CVA */
|
|
1033
1401
|
classes = computed(() => tabsTriggerVariants({
|
|
1034
|
-
|
|
1035
|
-
selected: this.isSelected(),
|
|
1402
|
+
variant: this.tabsGroup.lmVariant(),
|
|
1036
1403
|
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
1037
1404
|
ngOnInit() {
|
|
1038
1405
|
this.tabsGroup.registerTrigger(this.lumaTabsTrigger(), this.el.nativeElement);
|
|
@@ -1073,7 +1440,7 @@ class LmTabsTriggerDirective {
|
|
|
1073
1440
|
}
|
|
1074
1441
|
}
|
|
1075
1442
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1076
|
-
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 });
|
|
1443
|
+
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", "attr.data-state": "isSelected() ? \"active\" : \"inactive\"", "class": "classes()" } }, ngImport: i0 });
|
|
1077
1444
|
}
|
|
1078
1445
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsTriggerDirective, decorators: [{
|
|
1079
1446
|
type: Directive,
|
|
@@ -1085,6 +1452,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
1085
1452
|
'[attr.aria-selected]': 'isSelected()',
|
|
1086
1453
|
'[attr.aria-controls]': 'panelId()',
|
|
1087
1454
|
'[attr.tabindex]': 'isSelected() ? 0 : -1',
|
|
1455
|
+
'[attr.data-state]': 'isSelected() ? "active" : "inactive"',
|
|
1088
1456
|
'[class]': 'classes()',
|
|
1089
1457
|
},
|
|
1090
1458
|
}]
|
|
@@ -1241,7 +1609,7 @@ class LmTabsIndicatorComponent {
|
|
|
1241
1609
|
this.resizeObserver = new ResizeObserver(() => {
|
|
1242
1610
|
this.updateIndicatorPosition();
|
|
1243
1611
|
});
|
|
1244
|
-
this.resizeObserver.observe(this.tabsList.
|
|
1612
|
+
this.resizeObserver.observe(this.tabsList.getListContainer());
|
|
1245
1613
|
}
|
|
1246
1614
|
ngOnDestroy() {
|
|
1247
1615
|
this.resizeObserver?.disconnect();
|
|
@@ -1253,9 +1621,10 @@ class LmTabsIndicatorComponent {
|
|
|
1253
1621
|
return;
|
|
1254
1622
|
}
|
|
1255
1623
|
const triggerRect = activeTrigger.getBoundingClientRect();
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1624
|
+
const listContainer = this.tabsList.getListContainer();
|
|
1625
|
+
const listRect = listContainer.getBoundingClientRect();
|
|
1626
|
+
// Horizontal positioning (account for scroll offset in scrollable mode)
|
|
1627
|
+
this.indicatorPosition.set(triggerRect.left - listRect.left + listContainer.scrollLeft);
|
|
1259
1628
|
this.indicatorWidth.set(triggerRect.width);
|
|
1260
1629
|
}
|
|
1261
1630
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmTabsIndicatorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
@@ -1484,9 +1853,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.9", ngImpor
|
|
|
1484
1853
|
class LmModalOverlayComponent {
|
|
1485
1854
|
modal = inject(MODAL_CONTEXT);
|
|
1486
1855
|
/** Computed classes from CVA */
|
|
1487
|
-
classes = computed(() => modalOverlayVariants({
|
|
1488
|
-
open: this.modal.isOpen(),
|
|
1489
|
-
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
1856
|
+
classes = computed(() => modalOverlayVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
1490
1857
|
/**
|
|
1491
1858
|
* Handle click on overlay (not on children)
|
|
1492
1859
|
*/
|
|
@@ -1544,7 +1911,6 @@ class LmModalContainerComponent {
|
|
|
1544
1911
|
/** Computed classes from CVA */
|
|
1545
1912
|
classes = computed(() => modalContainerVariants({
|
|
1546
1913
|
size: this.modal.size(),
|
|
1547
|
-
open: this.modal.isOpen(),
|
|
1548
1914
|
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
1549
1915
|
constructor() {
|
|
1550
1916
|
// Focus first focusable element when modal opens
|
|
@@ -1690,9 +2056,7 @@ class LmModalTitleDirective {
|
|
|
1690
2056
|
/** ID for aria-labelledby connection */
|
|
1691
2057
|
titleId = computed(() => `${this.modal.modalId}-title`, ...(ngDevMode ? [{ debugName: "titleId" }] : []));
|
|
1692
2058
|
/** Computed classes from CVA */
|
|
1693
|
-
classes = computed(() => modalTitleVariants({
|
|
1694
|
-
size: this.lmSize(),
|
|
1695
|
-
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
2059
|
+
classes = computed(() => modalTitleVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
1696
2060
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalTitleDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1697
2061
|
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 });
|
|
1698
2062
|
}
|
|
@@ -1728,9 +2092,7 @@ class LmModalContentDirective {
|
|
|
1728
2092
|
/** Enable scroll when content overflows */
|
|
1729
2093
|
lmScrollable = input(true, ...(ngDevMode ? [{ debugName: "lmScrollable" }] : []));
|
|
1730
2094
|
/** Computed classes from CVA */
|
|
1731
|
-
classes = computed(() => modalContentVariants({
|
|
1732
|
-
scrollable: this.lmScrollable(),
|
|
1733
|
-
}), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
2095
|
+
classes = computed(() => modalContentVariants(), ...(ngDevMode ? [{ debugName: "classes" }] : []));
|
|
1734
2096
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.9", ngImport: i0, type: LmModalContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1735
2097
|
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 });
|
|
1736
2098
|
}
|