Skip to content

Commit dc681a7

Browse files
committed
Fix #1412 compatibility with WebComponents
1 parent cb90113 commit dc681a7

File tree

15 files changed

+96
-41
lines changed

15 files changed

+96
-41
lines changed

packages/core/src/Viewer.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import { ViewerDynamics } from './services/ViewerDynamics';
4747
import { ViewerState } from './services/ViewerState';
4848
import {
4949
Animation,
50+
checkClosedShadowDom,
5051
checkStylesheet,
5152
exitFullscreen,
5253
getAbortError,
@@ -118,6 +119,7 @@ export class Viewer extends TypedEventTarget<ViewerEvents> {
118119
this.container.classList.add('psv-container');
119120
this.parent.appendChild(this.container);
120121

122+
checkClosedShadowDom(this.parent);
121123
checkStylesheet(this.container, 'core');
122124

123125
this.state = new ViewerState();

packages/core/src/buttons/MenuButton.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class MenuButton extends AbstractButton {
8484
content: MENU_TEMPLATE(this.viewer.navbar.collapsed, this.viewer.config.lang.menu),
8585
noMargin: true,
8686
clickHandler: (target) => {
87-
const li = target ? getClosest(target as HTMLElement, 'li') : undefined;
87+
const li = target ? getClosest(target as HTMLElement, '.psv-panel-menu-item') : undefined;
8888
const buttonId = li ? li.dataset[BUTTON_DATA] : undefined;
8989

9090
if (buttonId) {

packages/core/src/components/Panel.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { CAPTURE_EVENTS_CLASS, ICONS, KEY_CODES } from '../data/constants';
22
import { PSVError } from '../PSVError';
3-
import { toggleClass } from '../utils';
3+
import { getEventTarget, toggleClass } from '../utils';
44
import type { Viewer } from '../Viewer';
55
import { HidePanelEvent, KeypressEvent, ShowPanelEvent } from '../events';
66
import { AbstractComponent } from './AbstractComponent';
@@ -174,11 +174,11 @@ export class Panel extends AbstractComponent {
174174

175175
if (config.clickHandler) {
176176
this.state.clickHandler = (e) => {
177-
(config as PanelConfig).clickHandler(e.target as HTMLElement);
177+
(config as PanelConfig).clickHandler(getEventTarget(e));
178178
};
179179
this.state.keyHandler = (e) => {
180180
if (e.key === KEY_CODES.Enter) {
181-
(config as PanelConfig).clickHandler(e.target as HTMLElement);
181+
(config as PanelConfig).clickHandler(getEventTarget(e));
182182
}
183183
};
184184
this.content.addEventListener('click', this.state.clickHandler);

packages/core/src/model.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,11 @@ export type ClickData = {
233233
/**
234234
* Original element which received the click
235235
*/
236-
target: HTMLElement;
236+
target?: HTMLElement;
237+
/**
238+
* Original event which triggered the click
239+
*/
240+
originalEvent?: Event;
237241
/**
238242
* List of THREE scenes objects under the mouse
239243
*/

packages/core/src/services/EventsHandler.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ import {
3030
Animation,
3131
clone,
3232
distance,
33-
getClosest,
33+
getEventTarget,
34+
getMatchingTarget,
3435
getPosition,
3536
getTouchData,
36-
hasParent,
3737
isEmpty,
38-
throttle,
38+
throttle
3939
} from '../utils';
4040
import { PressHandler } from '../utils/PressHandler';
4141
import type { Viewer } from '../Viewer';
@@ -156,7 +156,7 @@ export class EventsHandler extends AbstractService {
156156
case 'fullscreenchange': this.__onFullscreenChange(); break;
157157
}
158158

159-
if (!getClosest(evt.target as HTMLElement, '.' + CAPTURE_EVENTS_CLASS)) {
159+
if (!getMatchingTarget(evt, '.' + CAPTURE_EVENTS_CLASS)) {
160160
// prettier-ignore
161161
switch (evt.type) {
162162
case 'mousedown': this.__onMouseDown(evt as MouseEvent); break;
@@ -243,7 +243,7 @@ export class EventsHandler extends AbstractService {
243243
*/
244244
private __onMouseUp(evt: MouseEvent) {
245245
if (this.step.is(Step.CLICK, Step.MOVING)) {
246-
this.__stopMove(evt.clientX, evt.clientY, evt.target, evt.button === 2);
246+
this.__stopMove(evt.clientX, evt.clientY, evt, evt.button === 2);
247247
}
248248
}
249249

@@ -271,7 +271,7 @@ export class EventsHandler extends AbstractService {
271271
if (!this.data.longtouchTimeout) {
272272
this.data.longtouchTimeout = setTimeout(() => {
273273
const touch = evt.touches[0];
274-
this.__stopMove(touch.clientX, touch.clientY, touch.target, true);
274+
this.__stopMove(touch.clientX, touch.clientY, evt, true);
275275
this.data.longtouchTimeout = null;
276276
}, LONGTOUCH_DELAY);
277277
}
@@ -301,7 +301,7 @@ export class EventsHandler extends AbstractService {
301301
this.__stopMove(this.data.mouseX, this.data.mouseY);
302302
} else if (evt.touches.length === 0) {
303303
const touch = evt.changedTouches[0];
304-
this.__stopMove(touch.clientX, touch.clientY, touch.target);
304+
this.__stopMove(touch.clientX, touch.clientY, evt);
305305
}
306306
}
307307
}
@@ -442,7 +442,7 @@ export class EventsHandler extends AbstractService {
442442
* Stops the movement
443443
* @description If the move threshold was not reached a click event is triggered, otherwise an animation is launched to simulate inertia
444444
*/
445-
private __stopMove(clientX: number, clientY: number, target?: EventTarget, rightclick = false) {
445+
private __stopMove(clientX: number, clientY: number, event?: Event, rightclick = false) {
446446
if (this.step.is(Step.MOVING)) {
447447
if (this.config.moveInertia) {
448448
this.__logMouseMove(clientX, clientY);
@@ -453,7 +453,7 @@ export class EventsHandler extends AbstractService {
453453
}
454454
} else {
455455
if (this.step.is(Step.CLICK) && !this.__moveThresholdReached(clientX, clientY)) {
456-
this.__doClick(clientX, clientY, target, rightclick);
456+
this.__doClick(clientX, clientY, event, rightclick);
457457
}
458458
this.step.remove(Step.CLICK);
459459
if (!this.step.is(Step.INERTIA)) {
@@ -518,7 +518,7 @@ export class EventsHandler extends AbstractService {
518518
/**
519519
* Triggers an event with all coordinates when a simple click is performed
520520
*/
521-
private __doClick(clientX: number, clientY: number, target: EventTarget, rightclick = false) {
521+
private __doClick(clientX: number, clientY: number, event?: Event, rightclick = false) {
522522
const boundingRect = this.viewer.container.getBoundingClientRect();
523523

524524
const viewerX = clientX - boundingRect.left;
@@ -532,7 +532,8 @@ export class EventsHandler extends AbstractService {
532532

533533
const data: ClickData = {
534534
rightclick: rightclick,
535-
target: target as HTMLElement,
535+
originalEvent: event,
536+
target: getEventTarget(event),
536537
clientX,
537538
clientY,
538539
viewerX,
@@ -576,7 +577,7 @@ export class EventsHandler extends AbstractService {
576577
* Trigger events for observed THREE objects
577578
*/
578579
private __handleObjectsEvents(evt: MouseEvent) {
579-
if (!isEmpty(this.state.objectsObservers) && hasParent(evt.target as HTMLElement, this.viewer.container)) {
580+
if (!isEmpty(this.state.objectsObservers) && evt.composedPath().includes(this.viewer.container)) {
580581
const viewerPos = getPosition(this.viewer.container);
581582

582583
const viewerPoint: Point = {

packages/core/src/utils/browser.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function hasParent(el: HTMLElement, parent: Element): boolean {
5656
}
5757

5858
/**
59-
* Gets the closest parent (can by itself)
59+
* Gets the closest parent matching the selector (can by itself)
6060
*/
6161
export function getClosest(el: HTMLElement, selector: string): HTMLElement | null {
6262
// When el is document or window, the matches does not exist
@@ -76,6 +76,29 @@ export function getClosest(el: HTMLElement, selector: string): HTMLElement | nul
7676
return null;
7777
}
7878

79+
/**
80+
* Returns the first element of the event' composedPath
81+
*/
82+
export function getEventTarget(e: Event): HTMLElement | null {
83+
return e?.composedPath()[0] as HTMLElement || null;
84+
}
85+
86+
/**
87+
* Returns the first element of the event's composedPath matching the selector
88+
*/
89+
export function getMatchingTarget(e: Event, selector: string): HTMLElement | null {
90+
if (!e) {
91+
return null;
92+
}
93+
return e.composedPath().find((el) => {
94+
if (!(el instanceof HTMLElement) && !(el instanceof SVGElement)) {
95+
return false;
96+
}
97+
98+
return el.matches(selector);
99+
}) as HTMLElement;
100+
}
101+
79102
/**
80103
* Gets the position of an element in the viewport without reflow
81104
* Will gives the same result as getBoundingClientRect() as soon as there are no CSS transforms

packages/core/src/utils/psv.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,16 @@ export function checkVersion(name: string, version: string, coreVersion: string)
446446
console.error(`PhotoSphereViewer: @photo-sphere-viewer/${name} is in version ${version} but @photo-sphere-viewer/core is in version ${coreVersion}`);
447447
}
448448
}
449+
450+
/**
451+
* Checks if the viewer is not used insude a closed shadow DOM
452+
*/
453+
export function checkClosedShadowDom(el: Node) {
454+
do {
455+
if (el instanceof ShadowRoot && el.mode === 'closed') {
456+
console.error(`PhotoSphereViewer: closed shadow DOM detected, the viewer might not work as expected`);
457+
return;
458+
}
459+
el = el.parentNode;
460+
} while (el);
461+
}

packages/gallery-plugin/src/GalleryComponent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export class GalleryComponent extends AbstractComponent {
129129
// prevent click on drag
130130
const currentMouse = this.isAboveBreakpoint ? (e as MouseEvent).clientX : (e as MouseEvent).clientY;
131131
if (Math.abs(this.state.initMouse - currentMouse) < 10) {
132-
const item = utils.getClosest(e.target as HTMLElement, `[data-${GALLERY_ITEM_DATA_KEY}]`);
132+
const item = utils.getMatchingTarget(e, `.psv-gallery-item`);
133133
if (item) {
134134
this.plugin.__click(item.dataset[GALLERY_ITEM_DATA]);
135135
}

packages/map-plugin/src/components/MapComponent.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ export class MapComponent extends AbstractComponent {
173173
}
174174

175175
handleEvent(e: Event) {
176-
if (utils.getClosest(e.target as HTMLElement, `.${CONSTANTS.CAPTURE_EVENTS_CLASS}:not(.psv-map)`)) {
176+
if (utils.getMatchingTarget(e, `.${CONSTANTS.CAPTURE_EVENTS_CLASS}:not(.psv-map)`)) {
177177
return;
178178
}
179179
switch (e.type) {
@@ -213,7 +213,7 @@ export class MapComponent extends AbstractComponent {
213213
if (this.state.mousedown) {
214214
this.__move(event.clientX, event.clientY);
215215
e.stopPropagation();
216-
} else if (e.target === this.canvas) {
216+
} else if (e.composedPath().includes(this.canvas)) {
217217
this.__handleHotspots(event.clientX, event.clientY);
218218
}
219219
break;
@@ -246,7 +246,7 @@ export class MapComponent extends AbstractComponent {
246246
this.state.mousedown = false;
247247
e.stopPropagation();
248248
}
249-
if (e.target === this.canvas) {
249+
if (e.composedPath().includes(this.canvas)) {
250250
this.__clickHotspot(mouse.clientX, mouse.clientY);
251251
}
252252
break;

packages/map-plugin/src/components/MapZoomToolbar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class MapZoomToolbar extends AbstractComponent {
3939
switch (e.type) {
4040
case 'mousedown':
4141
case 'touchstart': {
42-
const button = utils.getClosest(e.target as HTMLElement, 'svg');
42+
const button = utils.getMatchingTarget(e, 'svg[data-delta]');
4343
const delta: string = button?.dataset['delta'];
4444
if (delta) {
4545
cancelAnimationFrame(this.animation);

0 commit comments

Comments
 (0)