Skip to content

Commit 31d9931

Browse files
Merge branch 'main' into ruben/2nd-gen-chromatic
2 parents 204a3b6 + 1ec8880 commit 31d9931

File tree

11 files changed

+378
-42
lines changed

11 files changed

+378
-42
lines changed

.changeset/brave-foxes-smile.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@spectrum-web-components/menu': patch
3+
---
4+
5+
**Fixed**: Improved touch interaction handling for submenus to prevent unintended submenu closures.

.github/workflows/pr-update.yml

Lines changed: 0 additions & 28 deletions
This file was deleted.

.github/workflows/preview-release.yml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ jobs:
2929
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
3030
run: |
3131
yarn changeset version --snapshot preview-${GITHUB_SHA::8}
32-
yarn constraints --fix
33-
yarn version:update
32+
yarn genversion --source ./1st-gen/tools/base/package.json --semi --es6 --force ./2nd-gen/packages/core/shared/base/version.ts
3433
yarn install --refresh-lockfile
3534
3635
- name: Configure NPM for changeset publish
@@ -43,8 +42,8 @@ jobs:
4342
env:
4443
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
4544
run: |
45+
yarn build
4646
git add .
4747
git commit -am "chore: publish preview snapshot version"
48-
yarn prepublishOnly
4948
yarn changeset publish --no-git-tag --tag preview
5049
git reset --hard HEAD^

patches/@web+test-runner-coverage-v8+0.8.0.patch renamed to .yarn/patches/@web-test-runner-coverage-v8-npm-0.8.0-b19cf60e5f.patch

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
diff --git a/node_modules/@web/test-runner-coverage-v8/dist/index.js b/node_modules/@web/test-runner-coverage-v8/dist/index.js
1+
diff --git a/dist/index.js b/dist/index.js
22
index 997bcfc..b19cf60 100644
3-
--- a/node_modules/@web/test-runner-coverage-v8/dist/index.js
4-
+++ b/node_modules/@web/test-runner-coverage-v8/dist/index.js
3+
--- a/dist/index.js
4+
+++ b/dist/index.js
55
@@ -63,7 +63,16 @@ async function v8ToIstanbul(config, testFiles, coverage, userAgent) {
66
!path.startsWith('/__web-test-runner') &&
77
!path.startsWith('/__web-dev-server')) {

1st-gen/packages/menu/src/MenuItem.ts

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,13 @@ export class MenuItem extends LikeAnchor(
320320
*/
321321
private _closedViaPointer = false;
322322

323+
/**
324+
* Touch interaction state for submenu toggling
325+
*/
326+
private _activePointerId?: number;
327+
private _touchHandledViaPointerup = false;
328+
private _touchAbortController?: AbortController;
329+
323330
private handleClickCapture(event: Event): void | boolean {
324331
if (this.disabled) {
325332
event.preventDefault();
@@ -458,17 +465,75 @@ export class MenuItem extends LikeAnchor(
458465
}
459466

460467
private handlePointerdown(event: PointerEvent): void {
461-
if (event.target === this && this.hasSubmenu && this.open) {
468+
const path = event.composedPath();
469+
const targetIsInOverlay =
470+
this.overlayElement && path.includes(this.overlayElement);
471+
472+
if (
473+
event.pointerType === 'touch' &&
474+
this.hasSubmenu &&
475+
!targetIsInOverlay &&
476+
this._activePointerId === undefined
477+
) {
478+
this._activePointerId = event.pointerId;
479+
this._touchAbortController = new AbortController();
480+
481+
window.addEventListener(
482+
'pointerup',
483+
this.handleTouchSubmenuToggle,
484+
{ once: true, signal: this._touchAbortController.signal }
485+
);
486+
window.addEventListener('pointercancel', this.handleTouchCleanup, {
487+
once: true,
488+
signal: this._touchAbortController.signal,
489+
});
490+
}
491+
492+
if (
493+
!targetIsInOverlay &&
494+
this.hasSubmenu &&
495+
this.open &&
496+
event.pointerType !== 'touch'
497+
) {
462498
this.addEventListener('focus', this.handleSubmenuFocus, {
463499
once: true,
464500
});
465-
this.overlayElement.addEventListener(
501+
this.overlayElement?.addEventListener(
466502
'beforetoggle',
467503
this.handleBeforetoggle
468504
);
469505
}
470506
}
471507

508+
private handleTouchSubmenuToggle = (event: PointerEvent): void => {
509+
if (event.pointerId !== this._activePointerId) {
510+
return;
511+
}
512+
513+
this._touchAbortController?.abort();
514+
this._touchHandledViaPointerup = true;
515+
this._activePointerId = undefined;
516+
517+
if (this.open) {
518+
this.open = false;
519+
} else {
520+
this.openOverlay();
521+
}
522+
523+
setTimeout(() => {
524+
this._touchHandledViaPointerup = false;
525+
}, 0);
526+
};
527+
528+
private handleTouchCleanup = (event: PointerEvent): void => {
529+
if (event.pointerId !== this._activePointerId) {
530+
return;
531+
}
532+
this._touchAbortController?.abort();
533+
this._activePointerId = undefined;
534+
this._touchHandledViaPointerup = false;
535+
};
536+
472537
protected override firstUpdated(changes: PropertyValues): void {
473538
super.firstUpdated(changes);
474539
this.setAttribute('tabindex', '-1');
@@ -614,9 +679,16 @@ export class MenuItem extends LikeAnchor(
614679
}
615680

616681
protected handleSubmenuClick(event: Event): void {
682+
if (this._touchHandledViaPointerup) {
683+
event.stopPropagation();
684+
event.preventDefault();
685+
return;
686+
}
687+
617688
if (event.composedPath().includes(this.overlayElement)) {
618689
return;
619690
}
691+
620692
this.openOverlay(true);
621693
}
622694

@@ -640,7 +712,12 @@ export class MenuItem extends LikeAnchor(
640712
}
641713
};
642714

643-
protected handlePointerenter(): void {
715+
protected handlePointerenter(event: PointerEvent): void {
716+
// For touch devices, don't open on pointerenter - let click handle it
717+
if (event.pointerType === 'touch') {
718+
return;
719+
}
720+
644721
if (this.leaveTimeout) {
645722
clearTimeout(this.leaveTimeout);
646723
delete this.leaveTimeout;
@@ -654,7 +731,12 @@ export class MenuItem extends LikeAnchor(
654731
protected leaveTimeout?: ReturnType<typeof setTimeout>;
655732
protected recentlyLeftChild = false;
656733

657-
protected handlePointerleave(): void {
734+
protected handlePointerleave(event: PointerEvent): void {
735+
// For touch devices, don't close on pointerleave - let click handle it
736+
if (event.pointerType === 'touch') {
737+
return;
738+
}
739+
658740
this._closedViaPointer = true;
659741
if (this.open && !this.recentlyLeftChild) {
660742
this.leaveTimeout = setTimeout(() => {
@@ -821,6 +903,12 @@ export class MenuItem extends LikeAnchor(
821903
selectionRoot: undefined,
822904
cleanupSteps: [],
823905
};
906+
907+
// Clean up any active touch listeners
908+
this._touchAbortController?.abort();
909+
this._activePointerId = undefined;
910+
this._touchHandledViaPointerup = false;
911+
824912
super.disconnectedCallback();
825913
}
826914

0 commit comments

Comments
 (0)