22 * Copyright (c) Siemens 2016 - 2025
33 * SPDX-License-Identifier: MIT
44 */
5- import {
6- FlexibleConnectedPositionStrategy ,
7- Overlay ,
8- OverlayOutsideClickDispatcher ,
9- OverlayRef ,
10- PositionStrategy
11- } from '@angular/cdk/overlay' ;
5+ import { FlexibleConnectedPositionStrategy , Overlay , OverlayRef } from '@angular/cdk/overlay' ;
126import { ComponentPortal } from '@angular/cdk/portal' ;
13- import { ElementRef , inject , Injectable , Injector , signal , DOCUMENT } from '@angular/core' ;
14- import { makeOverlay , makePositionStrategy } from '@siemens/element-ng/common' ;
7+ import { DOCUMENT , ElementRef , inject , Injectable , Injector , signal } from '@angular/core' ;
8+ import { isRTL } from '@siemens/element-ng/common' ;
159import { ResizeObserverService } from '@siemens/element-ng/resize-observer' ;
1610import { map , merge , Subject , Subscription , tap , throttleTime } from 'rxjs' ;
1711
@@ -25,7 +19,6 @@ export class SiTourService {
2519 private injector = inject ( Injector ) ;
2620 private resizeObserver = inject ( ResizeObserverService ) ;
2721 private overlay = inject ( Overlay ) ;
28- private outsideClickDispatcher = inject ( OverlayOutsideClickDispatcher ) ;
2922 private overlayRefHighlight ?: OverlayRef ;
3023 private overlayRef ?: OverlayRef ;
3124 private portal ?: ComponentPortal < SiTourComponent > ;
@@ -145,7 +138,11 @@ export class SiTourService {
145138 ? new ElementRef ( anchorElement ! )
146139 : undefined ;
147140
148- this . makeOverlay ( anchorElementRef ) ;
141+ if ( anchorElementRef ) {
142+ this . attachOverlay ( anchorElementRef ) ;
143+ } else {
144+ this . centerOverlay ( ) ;
145+ }
149146 this . handleResizeSubscription ( anchorElementRef ) ;
150147
151148 this . tourToken . currentStep . next ( {
@@ -179,17 +176,57 @@ export class SiTourService {
179176 }
180177 }
181178
182- private makeOverlay ( anchorElement : ElementRef < HTMLElement > | undefined ) : void {
183- const strategy = makePositionStrategy ( anchorElement , this . overlay , 'auto' ) ;
184- this . handlePositionChangeSubscription ( strategy , anchorElement ) ;
179+ private centerOverlay ( ) : void {
180+ this . positionChangeSub ?. unsubscribe ( ) ;
181+ this . createOverlays ( ) ;
182+
183+ const positionStrategy = this . overlayRef ! . getConfig ( ) . positionStrategy ;
184+ if ( ! positionStrategy || positionStrategy instanceof FlexibleConnectedPositionStrategy ) {
185+ this . overlayRef ! . updatePositionStrategy (
186+ this . overlay . position ( ) . global ( ) . centerHorizontally ( ) . centerVertically ( )
187+ ) ;
188+ }
189+ this . tourToken . positionChange . next ( undefined ) ;
190+ }
191+
192+ private attachOverlay ( anchor : ElementRef < HTMLElement > ) : void {
193+ this . createOverlays ( ) ;
185194
195+ const positionStrategy = this . overlayRef ! . getConfig ( ) . positionStrategy ;
196+ if ( positionStrategy && positionStrategy instanceof FlexibleConnectedPositionStrategy ) {
197+ positionStrategy . setOrigin ( anchor ) ;
198+ } else {
199+ this . createFlexiblePositionStrategy ( anchor ) ;
200+ }
201+ }
202+
203+ private createFlexiblePositionStrategy ( anchor : ElementRef < HTMLElement > ) : void {
204+ const positionStrategy = this . overlay
205+ . position ( )
206+ . flexibleConnectedTo ( anchor )
207+ . withGrowAfterOpen ( true )
208+ . withPositions ( [
209+ // On top
210+ { originX : 'center' , overlayX : 'center' , originY : 'top' , overlayY : 'bottom' } ,
211+ { originX : 'start' , overlayX : 'start' , originY : 'top' , overlayY : 'bottom' } ,
212+ { originX : 'end' , overlayX : 'end' , originY : 'top' , overlayY : 'bottom' } ,
213+ // On bottom
214+ { originX : 'center' , overlayX : 'center' , originY : 'bottom' , overlayY : 'top' } ,
215+ { originX : 'start' , overlayX : 'start' , originY : 'bottom' , overlayY : 'top' } ,
216+ { originX : 'end' , overlayX : 'end' , originY : 'bottom' , overlayY : 'top' } ,
217+ // Left and right
218+ { originX : 'start' , overlayX : 'end' , originY : 'center' , overlayY : 'center' } ,
219+ { originX : 'end' , overlayX : 'start' , originY : 'center' , overlayY : 'center' }
220+ ] ) ;
221+ this . overlayRef ! . updatePositionStrategy ( positionStrategy ) ;
222+ this . positionChangeSub = positionStrategy . positionChanges
223+ . pipe ( map ( change => ( { change, anchor } ) ) )
224+ // We only want to forward the next channel, as the positionChanges completes when setting a new origin.
225+ . subscribe ( value => this . tourToken . positionChange . next ( value ) ) ;
226+ }
227+
228+ private createOverlays ( ) : void {
186229 if ( this . overlayRef ) {
187- this . overlayRef . updatePositionStrategy ( strategy ) ;
188- // This moves the dispatcher to the top, allowing it to catch other open overlays.
189- // Much lighter than to detach and re-attach the portal, not re-creating the si-tour
190- // component for each step
191- this . outsideClickDispatcher . remove ( this . overlayRef ) ;
192- this . outsideClickDispatcher . add ( this . overlayRef ) ;
193230 return ;
194231 }
195232
@@ -207,9 +244,10 @@ export class SiTourService {
207244
208245 // then the dialog
209246 this . portal = new ComponentPortal ( SiTourComponent , undefined , componentInjector ) ;
210- this . overlayRef = makeOverlay ( strategy , this . overlay , true ) ;
211- // needs a subscriber, otherwise events will be ignored and the .backdrop CSS hack doesn't help
212- this . overlayRef . outsidePointerEvents ( ) . subscribe ( ) ;
247+ this . overlayRef = this . overlay . create ( {
248+ scrollStrategy : this . overlay . scrollStrategies . reposition ( ) ,
249+ direction : isRTL ( ) ? 'rtl' : 'ltr'
250+ } ) ;
213251 this . overlayRef . attach ( this . portal ) ;
214252 }
215253
@@ -228,7 +266,7 @@ export class SiTourService {
228266 tap ( ( ) => {
229267 if ( ! this . isElementVisible ( anchorElement ?. nativeElement ) ) {
230268 // repositions to center if anchor disappears
231- this . makeOverlay ( undefined ) ;
269+ this . centerOverlay ( ) ;
232270 } else {
233271 this . overlayRef ?. updatePosition ( ) ;
234272 }
@@ -243,20 +281,6 @@ export class SiTourService {
243281 return ! ! rect ?. width && ! ! rect . height ;
244282 }
245283
246- private handlePositionChangeSubscription (
247- strategy : PositionStrategy ,
248- anchor ?: ElementRef < HTMLElement >
249- ) : void {
250- this . positionChangeSub ?. unsubscribe ( ) ;
251- if ( anchor && strategy instanceof FlexibleConnectedPositionStrategy ) {
252- this . positionChangeSub = strategy . positionChanges
253- . pipe ( map ( change => ( { change, anchor } ) ) )
254- . subscribe ( this . tourToken . positionChange ) ;
255- } else {
256- this . tourToken . positionChange . next ( undefined ) ;
257- }
258- }
259-
260284 private getElement (
261285 selectorOrElement ?: string | HTMLElement | ( ( ) => string | HTMLElement )
262286 ) : HTMLElement | undefined {
0 commit comments