Skip to content

Commit ca2daf7

Browse files
committed
refactor(tour): drop usage of overlay helper
The previous `getOverlay` function has some serious flaws. Replacing it with direct cdk usage. See #808
1 parent 2d921cb commit ca2daf7

File tree

3 files changed

+66
-29
lines changed

3 files changed

+66
-29
lines changed
Lines changed: 2 additions & 2 deletions
Loading
Lines changed: 1 addition & 1 deletion
Loading

projects/element-ng/tour/si-tour.service.ts

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@ import {
66
FlexibleConnectedPositionStrategy,
77
Overlay,
88
OverlayOutsideClickDispatcher,
9-
OverlayRef,
10-
PositionStrategy
9+
OverlayRef
1110
} from '@angular/cdk/overlay';
1211
import { 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';
12+
import { DOCUMENT, ElementRef, inject, Injectable, Injector, signal } from '@angular/core';
13+
import { isRTL } from '@siemens/element-ng/common';
1514
import { ResizeObserverService } from '@siemens/element-ng/resize-observer';
1615
import { map, merge, Subject, Subscription, tap, throttleTime } from 'rxjs';
1716

@@ -145,7 +144,11 @@ export class SiTourService {
145144
? new ElementRef(anchorElement!)
146145
: undefined;
147146

148-
this.makeOverlay(anchorElementRef);
147+
if (anchorElementRef) {
148+
this.attachOverlay(anchorElementRef);
149+
} else {
150+
this.centerOverlay();
151+
}
149152
this.handleResizeSubscription(anchorElementRef);
150153

151154
this.tourToken.currentStep.next({
@@ -179,12 +182,57 @@ export class SiTourService {
179182
}
180183
}
181184

182-
private makeOverlay(anchorElement: ElementRef<HTMLElement> | undefined): void {
183-
const strategy = makePositionStrategy(anchorElement, this.overlay, 'auto');
184-
this.handlePositionChangeSubscription(strategy, anchorElement);
185+
private centerOverlay(): void {
186+
this.positionChangeSub?.unsubscribe();
187+
this.createOverlays();
188+
189+
const positionStrategy = this.overlayRef!.getConfig().positionStrategy;
190+
if (!positionStrategy || positionStrategy instanceof FlexibleConnectedPositionStrategy) {
191+
this.overlayRef!.updatePositionStrategy(
192+
this.overlay.position().global().centerHorizontally().centerVertically()
193+
);
194+
}
195+
this.tourToken.positionChange.next(undefined);
196+
}
197+
198+
private attachOverlay(anchor: ElementRef<HTMLElement>): void {
199+
this.createOverlays();
200+
201+
const positionStrategy = this.overlayRef!.getConfig().positionStrategy;
202+
if (positionStrategy && positionStrategy instanceof FlexibleConnectedPositionStrategy) {
203+
positionStrategy.setOrigin(anchor);
204+
} else {
205+
this.createFlexiblePositionStrategy(anchor);
206+
}
207+
}
185208

209+
private createFlexiblePositionStrategy(anchor: ElementRef<HTMLElement>): void {
210+
const positionStrategy = this.overlay
211+
.position()
212+
.flexibleConnectedTo(anchor)
213+
.withGrowAfterOpen(true)
214+
.withPositions([
215+
// On top
216+
{ originX: 'center', overlayX: 'center', originY: 'top', overlayY: 'bottom' },
217+
{ originX: 'start', overlayX: 'start', originY: 'top', overlayY: 'bottom' },
218+
{ originX: 'end', overlayX: 'end', originY: 'top', overlayY: 'bottom' },
219+
// On bottom
220+
{ originX: 'center', overlayX: 'center', originY: 'bottom', overlayY: 'top' },
221+
{ originX: 'start', overlayX: 'start', originY: 'bottom', overlayY: 'top' },
222+
{ originX: 'end', overlayX: 'end', originY: 'bottom', overlayY: 'top' },
223+
// Left and right
224+
{ originX: 'start', overlayX: 'end', originY: 'center', overlayY: 'center' },
225+
{ originX: 'end', overlayX: 'start', originY: 'center', overlayY: 'center' }
226+
]);
227+
this.overlayRef!.updatePositionStrategy(positionStrategy);
228+
this.positionChangeSub = positionStrategy.positionChanges
229+
.pipe(map(change => ({ change, anchor })))
230+
// We only want to forward the next channel, as the positionChanges completes when setting a new origin.
231+
.subscribe(value => this.tourToken.positionChange.next(value));
232+
}
233+
234+
private createOverlays(): void {
186235
if (this.overlayRef) {
187-
this.overlayRef.updatePositionStrategy(strategy);
188236
// This moves the dispatcher to the top, allowing it to catch other open overlays.
189237
// Much lighter than to detach and re-attach the portal, not re-creating the si-tour
190238
// component for each step
@@ -207,10 +255,13 @@ export class SiTourService {
207255

208256
// then the dialog
209257
this.portal = new ComponentPortal(SiTourComponent, undefined, componentInjector);
210-
this.overlayRef = makeOverlay(strategy, this.overlay, true);
258+
this.overlayRef = this.overlay.create({
259+
scrollStrategy: this.overlay.scrollStrategies.reposition(),
260+
direction: isRTL() ? 'rtl' : 'ltr'
261+
});
262+
this.overlayRef.attach(this.portal);
211263
// needs a subscriber, otherwise events will be ignored and the .backdrop CSS hack doesn't help
212264
this.overlayRef.outsidePointerEvents().subscribe();
213-
this.overlayRef.attach(this.portal);
214265
}
215266

216267
private handleResizeSubscription(anchorElement: ElementRef<HTMLElement> | undefined): void {
@@ -228,7 +279,7 @@ export class SiTourService {
228279
tap(() => {
229280
if (!this.isElementVisible(anchorElement?.nativeElement)) {
230281
// repositions to center if anchor disappears
231-
this.makeOverlay(undefined);
282+
this.centerOverlay();
232283
} else {
233284
this.overlayRef?.updatePosition();
234285
}
@@ -243,20 +294,6 @@ export class SiTourService {
243294
return !!rect?.width && !!rect.height;
244295
}
245296

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-
260297
private getElement(
261298
selectorOrElement?: string | HTMLElement | (() => string | HTMLElement)
262299
): HTMLElement | undefined {

0 commit comments

Comments
 (0)