From 669bd51734f31da1c209d85ad7db581dc17316c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Scandella?= Date: Sat, 5 Apr 2025 18:49:53 +0200 Subject: [PATCH] Add support for slotted elements --- .../__snapshots__/nested_slots.test.js.snap | 41 ++++++++++ integration/__snapshots__/slot.test.js.snap | 31 ++++++++ integration/nested_slots.html | 76 +++++++++++++++++++ integration/nested_slots.test.js | 37 +++++++++ integration/slot.html | 49 ++++++++++++ integration/slot.test.js | 34 +++++++++ src/index.ts | 4 +- 7 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 integration/__snapshots__/nested_slots.test.js.snap create mode 100644 integration/__snapshots__/slot.test.js.snap create mode 100644 integration/nested_slots.html create mode 100644 integration/nested_slots.test.js create mode 100644 integration/slot.html create mode 100644 integration/slot.test.js diff --git a/integration/__snapshots__/nested_slots.test.js.snap b/integration/__snapshots__/nested_slots.test.js.snap new file mode 100644 index 00000000..7939bd79 --- /dev/null +++ b/integration/__snapshots__/nested_slots.test.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`target in nested slots into scrollable container in shadow DOM block: "nearest" 1`] = ` +[ + { + "el": "div.second-inner-container", + "left": 0, + "top": 100, + }, + { + "el": "div.first-inner-container", + "left": 0, + "top": 600, + }, + { + "el": "html", + "left": 0, + "top": 0, + }, +] +`; + +exports[`target in nested slots into scrollable container in shadow DOM block: "start" 1`] = ` +[ + { + "el": "div.second-inner-container", + "left": 0, + "top": 600, + }, + { + "el": "div.first-inner-container", + "left": 0, + "top": 600, + }, + { + "el": "html", + "left": 0, + "top": 0, + }, +] +`; diff --git a/integration/__snapshots__/slot.test.js.snap b/integration/__snapshots__/slot.test.js.snap new file mode 100644 index 00000000..ca371ded --- /dev/null +++ b/integration/__snapshots__/slot.test.js.snap @@ -0,0 +1,31 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`target slotted into scrollable container in shadow DOM block: "nearest" 1`] = ` +[ + { + "el": "div.inner-container", + "left": 0, + "top": 100, + }, + { + "el": "html", + "left": 0, + "top": 0, + }, +] +`; + +exports[`target slotted into scrollable container in shadow DOM block: "start" 1`] = ` +[ + { + "el": "div.inner-container", + "left": 0, + "top": 600, + }, + { + "el": "html", + "left": 0, + "top": 0, + }, +] +`; diff --git a/integration/nested_slots.html b/integration/nested_slots.html new file mode 100644 index 00000000..9e3d92b7 --- /dev/null +++ b/integration/nested_slots.html @@ -0,0 +1,76 @@ + + + + + +
+ +
+
diff --git a/integration/nested_slots.test.js b/integration/nested_slots.test.js new file mode 100644 index 00000000..dbd71761 --- /dev/null +++ b/integration/nested_slots.test.js @@ -0,0 +1,37 @@ +beforeAll(async () => { + await page.goto('http://localhost:3000/integration/nested_slots') +}) + +describe('target in nested slots into scrollable container in shadow DOM', () => { + test('block: "nearest"', async () => { + expect.assertions(4) + const windowHeight = await page.evaluate(() => window.innerHeight) + const actual = await page.evaluate(() => { + return window + .computeScrollIntoView(document.querySelector('.target'), { + block: 'nearest', + }) + .map(window.mapActions) + }) + expect(actual).toHaveLength(3) + expect(actual[0]).toMatchObject({ el: 'div.second-inner-container', left: 0, top: 100 }) + expect(actual[1]).toMatchObject({ el: 'div.first-inner-container', left: 0, top: windowHeight }) + expect(actual).toMatchSnapshot() + }) + + test('block: "start"', async () => { + expect.assertions(4) + const windowHeight = await page.evaluate(() => window.innerHeight) + const actual = await page.evaluate(() => { + return window + .computeScrollIntoView(document.querySelector('.target'), { + block: 'start', + }) + .map(window.mapActions) + }) + expect(actual).toHaveLength(3) + expect(actual[0]).toMatchObject({ el: 'div.second-inner-container', left: 0, top: windowHeight }) + expect(actual[1]).toMatchObject({ el: 'div.first-inner-container', left: 0, top: windowHeight }) + expect(actual).toMatchSnapshot() + }) +}) \ No newline at end of file diff --git a/integration/slot.html b/integration/slot.html new file mode 100644 index 00000000..2e760e97 --- /dev/null +++ b/integration/slot.html @@ -0,0 +1,49 @@ + + + + + +
+ +
+
diff --git a/integration/slot.test.js b/integration/slot.test.js new file mode 100644 index 00000000..24f7a6be --- /dev/null +++ b/integration/slot.test.js @@ -0,0 +1,34 @@ +beforeAll(async () => { + await page.goto('http://localhost:3000/integration/slot') +}) + +describe('target slotted into scrollable container in shadow DOM', () => { + test('block: "nearest"', async () => { + expect.assertions(3) + const actual = await page.evaluate(() => { + return window + .computeScrollIntoView(document.querySelector('.target'), { + block: 'nearest', + }) + .map(window.mapActions) + }) + expect(actual).toHaveLength(2) + expect(actual[0]).toMatchObject({ el: 'div.inner-container', left: 0, top: 100 }) + expect(actual).toMatchSnapshot() + }) + + test('block: "start"', async () => { + expect.assertions(3) + const windowHeight = await page.evaluate(() => window.innerHeight); + const actual = await page.evaluate(() => { + return window + .computeScrollIntoView(document.querySelector('.target'), { + block: 'start', + }) + .map(window.mapActions) + }) + expect(actual).toHaveLength(2) + expect(actual[0]).toMatchObject({ el: 'div.inner-container', left: 0, top: windowHeight }) + expect(actual).toMatchSnapshot() + }) +}) \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index ff43f78f..d6021330 100644 --- a/src/index.ts +++ b/src/index.ts @@ -267,7 +267,9 @@ const alignNearest = ( } const getParentElement = (element: Node): Element | null => { - const parent = element.parentElement + const parent = isElement(element) + ? (element.assignedSlot ?? element.parentElement) + : element.parentElement if (parent == null) { return (element.getRootNode() as ShadowRoot).host || null }