1
1
import {
2
- AfterViewInit ,
3
2
booleanAttribute ,
4
3
computed ,
5
4
Directive ,
6
- DoCheck ,
5
+ effect ,
7
6
ElementRef ,
8
- Input ,
7
+ inject ,
9
8
input ,
10
- OnChanges ,
11
9
OnDestroy ,
12
10
output ,
13
11
Renderer2 ,
14
- SimpleChanges
12
+ signal
15
13
} from '@angular/core' ;
16
14
import { AnimationBuilder , AnimationPlayer , useAnimation } from '@angular/animations' ;
17
15
@@ -22,19 +20,31 @@ import {
22
20
expandHorizontalAnimation
23
21
} from './collapse.animations' ;
24
22
25
- // todo
26
23
@Directive ( {
27
24
selector : '[cCollapse]' ,
28
25
exportAs : 'cCollapse' ,
29
26
standalone : true ,
30
- host : { '[class]' : 'hostClasses()' }
27
+ host : { '[class]' : 'hostClasses()' , '[style]' : '{display: "none"}' }
31
28
} )
32
- export class CollapseDirective implements OnDestroy , AfterViewInit , DoCheck , OnChanges {
29
+ export class CollapseDirective implements OnDestroy {
30
+ readonly #hostElement = inject ( ElementRef ) ;
31
+ readonly #renderer = inject ( Renderer2 ) ;
32
+ readonly #animationBuilder = inject ( AnimationBuilder ) ;
33
+ #player: AnimationPlayer | undefined = undefined ;
34
+
33
35
/**
34
36
* @ignore
35
- * todo: 'animate' input signal for navbar
36
37
*/
37
- @Input ( { transform : booleanAttribute } ) animate : boolean = true ;
38
+ readonly animateInput = input ( true , { transform : booleanAttribute , alias : 'animate' } ) ;
39
+
40
+ readonly animate = signal ( true ) ;
41
+
42
+ readonly animateInputEffect = effect (
43
+ ( ) => {
44
+ this . animate . set ( this . animateInput ( ) ) ;
45
+ } ,
46
+ { allowSignalWrites : true }
47
+ ) ;
38
48
39
49
/**
40
50
* Set horizontal collapsing to transition the width instead of height.
@@ -47,18 +57,31 @@ export class CollapseDirective implements OnDestroy, AfterViewInit, DoCheck, OnC
47
57
* Toggle the visibility of collapsible element.
48
58
* @type boolean
49
59
* @default false
50
- * todo: 'visible' input signal
51
60
*/
52
- @Input ( { transform : booleanAttribute } )
53
- set visible ( value ) {
54
- this . _visible = value ;
55
- }
61
+ readonly visibleInput = input ( false , { transform : booleanAttribute , alias : 'visible' } ) ;
56
62
57
- get visible ( ) : boolean {
58
- return this . _visible ;
59
- }
63
+ readonly visibleChange = output < boolean > ( ) ;
64
+
65
+ readonly visibleInputEffect = effect (
66
+ ( ) => {
67
+ this . visible . set ( this . visibleInput ( ) ) ;
68
+ } ,
69
+ { allowSignalWrites : true }
70
+ ) ;
71
+
72
+ readonly visible = signal < boolean > ( false ) ;
60
73
61
- private _visible : boolean = false ;
74
+ #init = false ;
75
+
76
+ readonly visibleEffect = effect (
77
+ ( ) => {
78
+ const visible = this . visible ( ) ;
79
+
80
+ ( this . #init || visible ) && this . createPlayer ( visible ) ;
81
+ this . #init = true ;
82
+ } ,
83
+ { allowSignalWrites : true }
84
+ ) ;
62
85
63
86
/**
64
87
* Add `navbar` prop for grouping and hiding navbar contents by a parent breakpoint.
@@ -83,71 +106,38 @@ export class CollapseDirective implements OnDestroy, AfterViewInit, DoCheck, OnC
83
106
*/
84
107
readonly collapseChange = output < string > ( ) ;
85
108
86
- private player ! : AnimationPlayer ;
87
- private readonly host : HTMLElement ;
88
- // private scrollHeight!: number;
89
- private scrollWidth ! : number ;
90
- private collapsing : boolean = false ;
91
-
92
- constructor (
93
- private readonly hostElement : ElementRef ,
94
- private readonly renderer : Renderer2 ,
95
- private readonly animationBuilder : AnimationBuilder
96
- ) {
97
- this . host = this . hostElement . nativeElement ;
98
- this . renderer . setStyle ( this . host , 'display' , 'none' ) ;
99
- }
100
-
101
109
readonly hostClasses = computed ( ( ) => {
102
110
return {
103
111
'navbar-collapse' : this . navbar ( ) ,
104
112
'collapse-horizontal' : this . horizontal ( )
105
113
} as Record < string , boolean > ;
106
114
} ) ;
107
115
108
- ngAfterViewInit ( ) : void {
109
- if ( this . visible ) {
110
- this . toggle ( ) ;
111
- }
112
- }
113
-
114
116
ngOnDestroy ( ) : void {
115
117
this . destroyPlayer ( ) ;
116
118
}
117
119
118
- ngOnChanges ( changes : SimpleChanges ) : void {
119
- if ( changes [ 'visible' ] ) {
120
- if ( ! changes [ 'visible' ] . firstChange || ! changes [ 'visible' ] . currentValue ) {
121
- this . toggle ( changes [ 'visible' ] . currentValue ) ;
122
- }
123
- }
124
- }
125
-
126
- ngDoCheck ( ) : void {
127
- if ( this . _visible !== this . visible ) {
128
- this . toggle ( ) ;
129
- }
130
- }
131
-
132
- toggle ( visible = this . visible ) : void {
133
- this . createPlayer ( visible ) ;
134
- this . player ?. play ( ) ;
120
+ toggle ( visible = ! this . visible ( ) ) : void {
121
+ this . visible . set ( visible ) ;
135
122
}
136
123
137
124
destroyPlayer ( ) : void {
138
- this . player ?. destroy ( ) ;
125
+ this . #player?. destroy ( ) ;
126
+ this . #player = undefined ;
139
127
}
140
128
141
- createPlayer ( visible : boolean = this . visible ) : void {
142
- if ( this . player ?. hasStarted ( ) ) {
129
+ createPlayer ( visible : boolean = this . visible ( ) ) : void {
130
+ if ( this . # player?. hasStarted ( ) ) {
143
131
this . destroyPlayer ( ) ;
144
132
}
145
133
134
+ const host : HTMLElement = this . #hostElement. nativeElement ;
135
+
146
136
if ( visible ) {
147
- this . renderer . removeStyle ( this . host , 'display' ) ;
137
+ this . # renderer. removeStyle ( host , 'display' ) ;
148
138
}
149
139
150
- const duration = this . animate ? this . duration ( ) : '0ms' ;
140
+ const duration = this . animate ( ) ? this . duration ( ) : '0ms' ;
151
141
152
142
const expand = this . horizontal ( ) ? expandHorizontalAnimation : expandAnimation ;
153
143
const collapse = this . horizontal ( ) ? collapseHorizontalAnimation : collapseAnimation ;
@@ -156,53 +146,48 @@ export class CollapseDirective implements OnDestroy, AfterViewInit, DoCheck, OnC
156
146
const capitalizedDimension = dimension [ 0 ] . toUpperCase ( ) + dimension . slice ( 1 ) ;
157
147
const scrollSize = `scroll${ capitalizedDimension } ` ;
158
148
159
- const animationFactory = this . animationBuilder ?. build (
149
+ const animationFactory = this . # animationBuilder?. build (
160
150
useAnimation ( visible ? expand : collapse , { params : { time : duration , easing : this . transition ( ) } } )
161
151
) ;
162
152
163
- this . player = animationFactory . create ( this . host ) ;
153
+ this . # player = animationFactory . create ( host ) ;
164
154
165
- this . renderer . setStyle ( this . host , dimension , visible ? 0 : ` ${ this . host . getBoundingClientRect ( ) [ dimension ] } px` ) ;
155
+ ! visible && host . offsetHeight && host . style [ dimension ] && host . scrollHeight ;
166
156
167
- ! visible && this . host . offsetHeight ;
157
+ this . #renderer . setStyle ( host , dimension , visible ? 0 : ` ${ host . getBoundingClientRect ( ) [ dimension ] } px` ) ;
168
158
169
- this . player . onStart ( ( ) => {
159
+ this . # player. onStart ( ( ) => {
170
160
this . setMaxSize ( ) ;
171
- this . renderer . removeClass ( this . host , 'collapse' ) ;
172
- this . renderer . addClass ( this . host , 'collapsing' ) ;
173
- this . renderer . removeClass ( this . host , 'show' ) ;
174
- this . collapsing = true ;
175
- if ( visible ) {
176
- this . renderer . setStyle ( this . host , dimension , `${ this . hostElement . nativeElement [ scrollSize ] } px` ) ;
177
- } else {
178
- this . renderer . setStyle ( this . host , dimension , '' ) ;
179
- }
180
- this . collapseChange . emit ( visible ? 'opening' : 'collapsing' ) ;
161
+ this . #renderer. removeClass ( host , 'collapse' ) ;
162
+ this . #renderer. addClass ( host , 'collapsing' ) ;
163
+ this . #renderer. removeClass ( host , 'show' ) ;
164
+ this . #renderer. setStyle ( host , dimension , visible ? `${ ( host as any ) [ scrollSize ] } px` : '' ) ;
165
+ this . collapseChange ?. emit ( visible ? 'opening' : 'collapsing' ) ;
181
166
} ) ;
182
- this . player . onDone ( ( ) => {
183
- this . visible = visible ;
184
- this . collapsing = false ;
185
- this . renderer . removeClass ( this . host , 'collapsing' ) ;
186
- this . renderer . addClass ( this . host , 'collapse' ) ;
167
+
168
+ this . #player. onDone ( ( ) => {
169
+ this . #renderer. removeClass ( host , 'collapsing' ) ;
170
+ this . #renderer. addClass ( host , 'collapse' ) ;
187
171
if ( visible ) {
188
- this . renderer . addClass ( this . host , 'show' ) ;
189
- this . renderer . setStyle ( this . host , dimension , '' ) ;
172
+ this . # renderer. addClass ( host , 'show' ) ;
173
+ this . # renderer. setStyle ( host , dimension , '' ) ;
190
174
} else {
191
- this . renderer . removeClass ( this . host , 'show' ) ;
175
+ this . # renderer. removeClass ( host , 'show' ) ;
192
176
}
193
- this . collapseChange . emit ( visible ? 'open' : 'collapsed' ) ;
177
+ this . collapseChange ?. emit ( visible ? 'open' : 'collapsed' ) ;
178
+ this . destroyPlayer ( ) ;
179
+ this . visibleChange . emit ( visible ) ;
194
180
} ) ;
181
+
182
+ this . #player?. play ( ) ;
195
183
}
196
184
197
185
setMaxSize ( ) {
198
- // setTimeout(() => {
186
+ const host = this . #hostElement . nativeElement ;
199
187
if ( this . horizontal ( ) ) {
200
- this . scrollWidth = this . host . scrollWidth ;
201
- this . scrollWidth > 0 && this . renderer . setStyle ( this . host , 'maxWidth' , `${ this . scrollWidth } px` ) ;
188
+ host . scrollWidth > 0 && this . #renderer. setStyle ( host , 'maxWidth' , `${ host . scrollWidth } px` ) ;
202
189
// } else {
203
- // this.scrollHeight = this.host.scrollHeight;
204
- // this.scrollHeight > 0 && this.renderer.setStyle(this.host, 'maxHeight', `${this.scrollHeight}px`);
190
+ // host.scrollHeight > 0 && this.#renderer.setStyle(host, 'maxHeight', `${host.scrollHeight}px`);
205
191
}
206
- // });
207
192
}
208
193
}
0 commit comments