Skip to content

Commit

Permalink
fix(synthetic-shadow): account for both non-shadowed targets and targ…
Browse files Browse the repository at this point in the history
…ets shadowed by native shadow roots (#4050) (#4079)
  • Loading branch information
ekashida committed Mar 19, 2024
1 parent 4d62855 commit 1e8bf9b
Show file tree
Hide file tree
Showing 10 changed files with 103 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createElement } from 'lwc';
import SyntheticParent from 'x/syntheticParent';

function testSelector(name, selector) {
it(name, () => {
const elm = createElement('x-synthetic-parent', { is: SyntheticParent });
document.body.appendChild(elm);
return Promise.resolve().then(() => {
let target;
elm.addEventListener('click', (event) => {
target = event.target;
});
elm.shadowRoot.querySelector(selector).click();

expect(target).toBe(elm);
});
});
}

describe('should resolve to the outermost host element', () => {
describe('when event dispatched on an element slotted into a native shadow root', () => {
testSelector('lwc component native shadow', '.lwc-native-child-slotted-button');
testSelector('lwc component synthetic shadow', '.lwc-synthetic-child-slotted-button');
testSelector('native web component', '.native-wc-slotted-button');
});
describe('when event dispatched on an element inside a native shadow root', () => {
testSelector('lwc component native shadow', 'x-native-child');
testSelector('lwc component native shadow', 'x-synthetic-child');
testSelector('native web component', 'mixed-shadow-mode-retargeting');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<template>
<slot></slot>
<button lwc:ref="button"></button>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LightningElement, api } from 'lwc';

export default class extends LightningElement {
static shadowSupportMode = 'native';

@api click() {
this.refs.button.click();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<template>
<slot></slot>
<button lwc:ref="button"></button>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { LightningElement, api } from 'lwc';

export default class extends LightningElement {
@api click() {
this.refs.button.click();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<template>
<x-native-child>
<button class="lwc-native-child-slotted-button"></button>
</x-native-child>

<x-synthetic-child>
<button class="lwc-synthetic-child-slotted-button"></button>
</x-synthetic-child>

<mixed-shadow-mode-retargeting lwc:external>
<button class="native-wc-slotted-button"></button>
</mixed-shadow-mode-retargeting>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { LightningElement } from 'lwc';

if (!customElements.get('mixed-shadow-mode-retargeting')) {
customElements.define(
'mixed-shadow-mode-retargeting',
class extends HTMLElement {
constructor() {
super();
this._shadowRoot = this.attachShadow({ mode: 'closed' });
this._shadowRoot.innerHTML = `<slot></slot><button></button>`;
}
click() {
this._shadowRoot.querySelector('button').click();
}
}
);
}

export default class extends LightningElement {}
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ subject to an additional IP rights grant found at http://polymer.github.io/PATEN
import { isNull } from '@lwc/shared';
import { getOwnerDocument } from '../../shared/utils';
import { Node } from '../../env/node';
import { isInstanceOfNativeShadowRoot } from '../../env/shadow-root';
import { isSyntheticShadowRoot } from '../../faux-shadow/shadow-root';
import { isSyntheticOrNativeShadowRoot } from '../../shared/utils';

export function pathComposer(startNode: EventTarget, composed: boolean): EventTarget[] {
const composedPath: EventTarget[] = [];
Expand All @@ -44,11 +43,8 @@ export function pathComposer(startNode: EventTarget, composed: boolean): EventTa
} else {
current = current.parentNode;
}
} else if (
(isSyntheticShadowRoot(current) || isInstanceOfNativeShadowRoot(current)) &&
(composed || current !== startRoot)
) {
current = (current as ShadowRoot).host;
} else if (isSyntheticOrNativeShadowRoot(current) && (composed || current !== startRoot)) {
current = current.host;
} else if (current instanceof Node) {
current = current.parentNode;
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
import { isNull, isUndefined } from '@lwc/shared';
import { isSyntheticOrNativeShadowRoot } from '../../shared/utils';
import { pathComposer } from './path-composer';
import { isSyntheticShadowRoot } from './../../faux-shadow/shadow-root';

/**
@license
Expand All @@ -28,11 +28,16 @@ export function retarget(refNode: EventTarget | null, path: EventTarget[]): Even
for (let i = 0, ancestor, lastRoot, root: Window | Node, rootIdx; i < p$.length; i++) {
ancestor = p$[i];
root = ancestor instanceof Window ? ancestor : (ancestor as Node).getRootNode();
// Retarget to ancestor if ancestor is not shadowed
if (!isSyntheticOrNativeShadowRoot(root)) {
return ancestor;
}
if (root !== lastRoot) {
rootIdx = refNodePath.indexOf(root);
lastRoot = root;
}
if (!isSyntheticShadowRoot(root) || (!isUndefined(rootIdx) && rootIdx > -1)) {
// Retarget to ancestor if ancestor is shadowed by refNode's shadow root
if (!isUndefined(rootIdx) && rootIdx > -1) {
return ancestor;
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/@lwc/synthetic-shadow/src/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import { isUndefined, isTrue } from '@lwc/shared';
import { ownerDocumentGetter } from '../env/node';
import { defaultViewGetter } from '../env/document';
import { getAttribute } from '../env/element';
import { isInstanceOfNativeShadowRoot } from '../env/shadow-root';
import { isSyntheticShadowRoot } from '../faux-shadow/shadow-root';

export function isSyntheticOrNativeShadowRoot(node: unknown): node is ShadowRoot {
return isSyntheticShadowRoot(node) || isInstanceOfNativeShadowRoot(node);
}

// Helpful for tests running with jsdom
export function getOwnerDocument(node: Node): Document {
Expand Down

0 comments on commit 1e8bf9b

Please sign in to comment.