Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nasty-clouds-win.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@lynx-js/web-elements": patch
---

feat: add wheel event handling and corresponding tests for x-foldview-ng
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class XFoldviewSlotNgTouchEventsHandler
constructor(dom: XFoldviewSlotNg) {
this.#dom = dom;

this.#dom.addEventListener('touchmove', this.#scroller, {
this.#dom.addEventListener('touchmove', this.#handleTouch, {
passive: false,
});

Expand All @@ -32,6 +32,9 @@ export class XFoldviewSlotNgTouchEventsHandler
this.#dom.addEventListener('touchend', this.#touchEnd, {
passive: true,
});
this.#dom.addEventListener('wheel', this.#handleWheel, {
passive: false,
});
}

#getTheMostScrollableKid(delta: number) {
Expand Down Expand Up @@ -61,8 +64,11 @@ export class XFoldviewSlotNgTouchEventsHandler
scrollableKid.scrollTop = targetKidScrollDistance;
}

#scroller = (event: TouchEvent) => {
#handleTouch = (event: TouchEvent) => {
const parentElement = this.#getParentElement();
if (!parentElement) {
return;
}
const touch = event.touches.item(0)!;
const { pageY, pageX } = touch;
const deltaY = this.#previousPageY! - pageY;
Expand All @@ -73,35 +79,41 @@ export class XFoldviewSlotNgTouchEventsHandler
if (this.#scrollingVertically === false) {
return;
}
const scrollableKidY = this.#getTheMostScrollableKid(deltaY);
if (
parentElement
) {
if (event.cancelable) {
event.preventDefault();
}
if (
(parentElement[isHeaderShowing] && deltaY > 0
|| (deltaY < 0 && !scrollableKidY))
// deltaY > 0: swipe up (folding header)
// scroll the foldview if its scrollable
|| (!parentElement[isHeaderShowing] && !scrollableKidY)
// all sub doms are scrolled
) {
parentElement.scrollBy({
top: deltaY,
behavior: 'smooth',
});
this.#parentScrollTop += deltaY;
parentElement.scrollTop = this.#parentScrollTop;
this.#currentScrollingElement = parentElement;
} else if (scrollableKidY) {
this.#currentScrollingElement = scrollableKidY;
this.#scrollKid(scrollableKidY, deltaY);
}
if (event.cancelable) {
event.preventDefault();
}
this.#handleScrollDelta(deltaY, parentElement);
this.#previousPageY = pageY;
this.#deltaY = deltaY;
};

#handleWheel = (event: WheelEvent) => {
const parentElement = this.#getParentElement();
if (!parentElement) {
return;
}
if (Math.abs(event.deltaY) <= Math.abs(event.deltaX)) {
return;
}
const pathElements = event.composedPath().filter((
element,
): element is Element =>
element instanceof Element && this.#dom.contains(element)
);
const { clientX, clientY } = event;
const pointElements = document.elementsFromPoint(clientX, clientY).filter(
e => this.#dom.contains(e),
);
this.#elements = [...new Set([...pathElements, ...pointElements])];
this.#parentScrollTop = parentElement.scrollTop;
if (this.#elements) {
for (const element of this.#elements) {
this.#childrenElemsntsScrollTop.set(element, element.scrollTop);
}
}
if (event.cancelable) {
event.preventDefault();
}
this.#handleScrollDelta(event.deltaY, parentElement);
};

#getParentElement(): XFoldviewNg | void {
Expand Down Expand Up @@ -142,4 +154,31 @@ export class XFoldviewSlotNgTouchEventsHandler
});
}
};

#handleScrollDelta(
deltaY: number,
parentElement: XFoldviewNg,
) {
const scrollableKidY = this.#getTheMostScrollableKid(deltaY);
if (
(parentElement[isHeaderShowing] && deltaY > 0
|| (deltaY < 0 && !scrollableKidY))
// deltaY > 0: swipe up (folding header)
// scroll the foldview if its scrollable
|| (!parentElement[isHeaderShowing] && !scrollableKidY)
// all sub doms are scrolled
) {
parentElement.scrollBy({
top: deltaY,
behavior: 'smooth',
});
this.#parentScrollTop += deltaY;
parentElement.scrollTop = this.#parentScrollTop;
this.#currentScrollingElement = parentElement;
} else if (scrollableKidY) {
this.#currentScrollingElement = scrollableKidY;
this.#scrollKid(scrollableKidY, deltaY);
}
this.#deltaY = deltaY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<!DOCTYPE html>
<!-- Copyright 2024 The Lynx Authors. All rights reserved.
Licensed under the Apache License Version 2.0 that can be found in the
LICENSE file in the root directory of this source tree. -->
<html>
<head>
<meta charset="utf-8">
<title>web playground</title>
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="default" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no" name="format-detection">
<meta content="portrait" name="screen-orientation">
<meta
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"
name="viewport"
>
<meta content="portrait" name="x5-orientation" />
<link
href="/main.css"
rel="stylesheet"
/>
<style>
x-view {
width: 100%;
height: 120px;
}
</style>
</head>

<body>
<script src="/main.js" defer></script>
<x-view
style="height: 100vh; width: 100vw; display: flex; --lynx-display: flex; --lynx-display-toggle: var(--lynx-display-flex); flex-direction: row"
>
<x-foldview-ng
id="foldview"
style="width: 80%; height: 400px; background-color: wheat; display: flex; --lynx-display: flex; --lynx-display-toggle: var(--lynx-display-flex); flex-direction: column"
>
<x-foldview-header-ng
style="position: absolute; width: 100%; height: 200px"
>
<x-view
style="background-color: aqua; width: 100%; height: 300px"
></x-view>
</x-foldview-header-ng>
<x-foldview-slot-ng id="slot" style="width: 100%; height: 400px">
<scroll-view
id="inner-scroll"
style="width: 80%; height: 100%"
scroll-y
>
<x-view style="background-color: orange"> </x-view>
<x-view style="background-color: tomato"> </x-view>
<x-view style="background-color: thistle"> </x-view>
<x-view style="background-color: forestgreen"> </x-view>
<x-view style="background-color: firebrick"> </x-view>
<x-view style="background-color: silver"> </x-view>
<x-view style="background-color: seashell"> </x-view>
<x-view style="background-color: plum"> </x-view>
<x-view style="background-color: lightsteelblue"> </x-view>
<x-view style="background-color: khaki"> </x-view>
</scroll-view>
</x-foldview-slot-ng>
</x-foldview-ng>
</x-view>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<!-- Copyright 2024 The Lynx Authors. All rights reserved.
Licensed under the Apache License Version 2.0 that can be found in the
LICENSE file in the root directory of this source tree. -->
<html>
<head>
<meta charset="utf-8">
<title>web playground</title>
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="default" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no" name="format-detection">
<meta content="portrait" name="screen-orientation">
<meta
content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no"
name="viewport"
>
<meta content="portrait" name="x5-orientation" />
<link
href="/main.css"
rel="stylesheet"
/>
<style>
x-view {
width: 100%;
height: 140px;
}
</style>
</head>

<body>
<script src="/main.js" defer></script>
<x-view
style="height: 100vh; width: 100vw; display: flex; --lynx-display: flex; --lynx-display-toggle: var(--lynx-display-flex); flex-direction: row"
>
<x-foldview-ng
id="foldview"
style="width: 80%; height: 420px; background-color: wheat; display: flex; --lynx-display: flex; --lynx-display-toggle: var(--lynx-display-flex); flex-direction: column"
>
<x-foldview-header-ng
style="position: absolute; width: 100%; height: 220px"
>
<x-view
style="background-color: aqua; width: 100%; height: 320px"
></x-view>
</x-foldview-header-ng>
<x-foldview-slot-ng id="slot" style="width: 100%; height: 420px">
<scroll-view
id="inner-scroll"
style="width: 80%; height: 100%"
scroll-y
>
<x-view style="background-color: orange"> </x-view>
<x-view style="background-color: tomato"> </x-view>
<x-view style="background-color: thistle"> </x-view>
<x-view style="background-color: forestgreen"> </x-view>
<x-view style="background-color: firebrick"> </x-view>
<x-view style="background-color: silver"> </x-view>
<x-view style="background-color: seashell"> </x-view>
<x-view style="background-color: plum"> </x-view>
<x-view style="background-color: lightsteelblue"> </x-view>
<x-view style="background-color: khaki"> </x-view>
<x-view style="background-color: lightcoral"> </x-view>
<x-view style="background-color: mediumaquamarine"> </x-view>
</scroll-view>
</x-foldview-slot-ng>
</x-foldview-ng>
</x-view>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2024 The Lynx Authors. All rights reserved.
// Licensed under the Apache License Version 2.0 that can be found in the
// LICENSE file in the root directory of this source tree.
import { test, expect } from '@lynx-js/playwright-fixtures';
import type { Page } from '@playwright/test';

const wait = async (ms: number) => {
await new Promise((resolve) => {
setTimeout(resolve, ms);
});
};

const goto = async (page: Page, fixtureName: string) => {
await page.goto(`/tests/fixtures/${fixtureName}.html`, {
waitUntil: 'load',
});
await page.evaluate(() => document.fonts.ready);
};

test.describe('x-foldview-ng wheel', () => {
test('x-foldview-ng/wheel-parent-first', async ({ page, browserName }, {
title,
}) => {
test.skip(browserName === 'webkit', 'mouse wheel unsupported on webkit');
await goto(page, title);
const foldview = page.locator('#foldview');
const scrollview = page.locator('#inner-scroll');
await page.locator('#inner-scroll').hover();

await foldview.evaluate((dom: HTMLElement) => {
dom.scrollTop = 0;
});
await scrollview.evaluate((dom: HTMLElement) => {
dom.scrollTop = 0;
});

const foldviewInitial = await foldview.evaluate((dom: HTMLElement) =>
dom.scrollTop
);
const scrollViewInitial = await scrollview.evaluate((dom: HTMLElement) =>
dom.scrollTop
);

await page.mouse.wheel(0, 120);
await wait(200);

expect(
await foldview.evaluate((dom: HTMLElement) => dom.scrollTop),
'wheel-outer-scrolls-first',
).toBeGreaterThan(foldviewInitial);
expect(
await scrollview.evaluate((dom: HTMLElement) => dom.scrollTop),
'wheel-inner-not-scrolled-before-header',
).toBe(scrollViewInitial);
});

test('x-foldview-ng/wheel-smooth-continue', async ({ page, browserName }, {
title,
}) => {
test.skip(browserName === 'webkit', 'mouse wheel unsupported on webkit');
await goto(page, title);
const foldview = page.locator('#foldview');
const scrollview = page.locator('#inner-scroll');
await page.locator('#inner-scroll').hover();

await foldview.evaluate((dom: HTMLElement) => {
dom.scrollTop = 0;
});
await scrollview.evaluate((dom: HTMLElement) => {
dom.scrollTop = 0;
});

await foldview.evaluate((dom: HTMLElement) => {
dom.scrollTop = dom.scrollHeight;
});
await wait(100);

const foldviewBeforeInner = await foldview.evaluate((dom: HTMLElement) =>
dom.scrollTop
);
await page.mouse.wheel(0, 200);
await wait(200);

expect(
await scrollview.evaluate((dom: HTMLElement) => dom.scrollTop),
'wheel-continues-to-inner-scroll',
).toBeGreaterThan(0);
expect(
await foldview.evaluate((dom: HTMLElement) => dom.scrollTop),
'wheel-outer-stays-at-end',
).toBeGreaterThanOrEqual(foldviewBeforeInner);
});
});
Loading