diff --git a/package.json b/package.json index faad7b65..2c892f7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.18.135", + "version": "0.18.136", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/js/components/create-list-dialog.js b/src/js/components/create-list-dialog.js index aa6adb7c..d9445b64 100644 --- a/src/js/components/create-list-dialog.js +++ b/src/js/components/create-list-dialog.js @@ -426,6 +426,7 @@ class CreateListDialog extends Component { confirmDismiss: () => this.confirmClose(), onDismiss: () => this.close(), scrollContainer: this.querySelector(".form-dialog-content"), + dragHandle: this.querySelector(".form-dialog-header"), ignoreTouchTarget: (el) => !!el.closest("button") || el.tagName === "INPUT" || diff --git a/src/js/components/drafts-dialog.js b/src/js/components/drafts-dialog.js index 7c38b818..e7db1d60 100644 --- a/src/js/components/drafts-dialog.js +++ b/src/js/components/drafts-dialog.js @@ -337,6 +337,7 @@ class DraftsDialog extends Component { enableDragToDismiss(dialog, { onDismiss: () => this.close(), scrollContainer: this.querySelector(".drafts-dialog-list"), + dragHandle: this.querySelector(".drafts-dialog-header"), ignoreTouchTarget: (element) => element.closest("button") !== null, disableWhenKeyboardOpen: true, }); diff --git a/src/js/components/edit-list-details-dialog.js b/src/js/components/edit-list-details-dialog.js index 4d9ca1f5..6121a5cb 100644 --- a/src/js/components/edit-list-details-dialog.js +++ b/src/js/components/edit-list-details-dialog.js @@ -412,6 +412,7 @@ class EditListDetailsDialog extends Component { confirmDismiss: () => this.confirmClose(), onDismiss: () => this.close(), scrollContainer: this.querySelector(".form-dialog-content"), + dragHandle: this.querySelector(".form-dialog-header"), ignoreTouchTarget: (el) => !!el.closest("button") || el.tagName === "INPUT" || diff --git a/src/js/components/edit-profile-dialog.js b/src/js/components/edit-profile-dialog.js index 1583214f..b2ff5534 100644 --- a/src/js/components/edit-profile-dialog.js +++ b/src/js/components/edit-profile-dialog.js @@ -491,6 +491,7 @@ class EditProfileDialog extends Component { confirmDismiss: () => this.confirmClose(), onDismiss: () => this.close(), scrollContainer: this.querySelector(".form-dialog-content"), + dragHandle: this.querySelector(".form-dialog-header"), ignoreTouchTarget: (el) => !!el.closest("button") || el.tagName === "INPUT" || diff --git a/src/js/components/post-composer.js b/src/js/components/post-composer.js index 61b4c2d7..95e19f82 100644 --- a/src/js/components/post-composer.js +++ b/src/js/components/post-composer.js @@ -1251,6 +1251,7 @@ class PostComposer extends Component { confirmDismiss: () => this.confirmClose(), onDismiss: () => this.close(), scrollContainer: this.querySelector(".post-composer-scroll-area"), + dragHandle: this.querySelector(".post-composer-top-bar"), ignoreTouchTarget: (el) => !!el.closest("button") || el.tagName === "TEXTAREA" || diff --git a/src/js/dragHelpers.js b/src/js/dragHelpers.js index 9bde2091..f3abd55c 100644 --- a/src/js/dragHelpers.js +++ b/src/js/dragHelpers.js @@ -26,6 +26,7 @@ function trackDrag( eventSource = target, ignoreTouchTarget = () => false, scrollContainer = null, + dragHandle = null, disableWhenKeyboardOpen = false, onStart = () => {}, onMove = () => {}, @@ -67,10 +68,12 @@ function trackDrag( }; const handleTouchStart = (e) => { - if (disableWhenKeyboardOpen && isKeyboardOpen()) return; + const onHandle = !!dragHandle && dragHandle.contains(e.target); + if (!onHandle && disableWhenKeyboardOpen && isKeyboardOpen()) return; if (ignoreTouchTarget(e.target)) return; if (hasTextSelection()) return; if ( + !onHandle && scrollContainer && scrollContainer.contains(e.target) && scrollContainer.scrollTop > 0 @@ -191,6 +194,7 @@ export function enableDragToDismiss( allowOppositeStretch = false, allowOppositeTranslate = false, scrollContainer = null, + dragHandle = null, ignoreTouchTarget = () => false, disableWhenKeyboardOpen = false, eventSource = target, @@ -236,6 +240,7 @@ export function enableDragToDismiss( eventSource, ignoreTouchTarget, scrollContainer, + dragHandle, disableWhenKeyboardOpen, onStart: () => { clearTimeout(caretRestoreTimer); diff --git a/tests/e2e/specs/concerns/dragToDismiss.test.js b/tests/e2e/specs/concerns/dragToDismiss.test.js index 60314746..2cb19b87 100644 --- a/tests/e2e/specs/concerns/dragToDismiss.test.js +++ b/tests/e2e/specs/concerns/dragToDismiss.test.js @@ -461,6 +461,22 @@ test.describe("Drag-to-dismiss", () => { await expect(composer).toBeVisible(); }); + test("dismisses via the top bar handle even while the keyboard is open", async ({ + page, + }) => { + await openPostComposer(page); + await simulateKeyboardOpen(page); + await drag(page, { + eventSourceSelector: "post-composer .post-composer", + startTouchTargetSelector: "post-composer .post-composer-top-bar", + startY: 40, + endY: 200, + }); + await expect( + page.locator("post-composer .post-composer"), + ).not.toBeVisible({ timeout: 2000 }); + }); + test("does not dismiss when the body is scrolled away from the top", async ({ page, }) => { diff --git a/tests/unit/specs/dragHelpers.test.js b/tests/unit/specs/dragHelpers.test.js index 3a5f74a8..ceeb1bf5 100644 --- a/tests/unit/specs/dragHelpers.test.js +++ b/tests/unit/specs/dragHelpers.test.js @@ -199,6 +199,65 @@ describe("enableDragToDismiss", () => { await dragTouch([{ clientY: 100 }, { clientY: 250 }]); assert.equal(closeCount, 1); }); + describe("dragHandle", () => { + let dragHandle; + let originalVisualViewport; + const setKeyboardOpen = (open) => { + window.visualViewport = { + height: open ? window.innerHeight - 300 : window.innerHeight, + }; + }; + beforeEach(() => { + dragHandle = document.createElement("div"); + el.appendChild(dragHandle); + originalVisualViewport = window.visualViewport; + setKeyboardOpen(false); + }); + afterEach(() => { + window.visualViewport = originalVisualViewport; + }); + it("dismisses on a drag starting inside the handle even with the keyboard open", async () => { + setKeyboardOpen(true); + handle = enableDragToDismiss(el, { + onDismiss: () => closeCount++, + dragHandle, + disableWhenKeyboardOpen: true, + }); + dragHandle.dispatchEvent(touchEvent("touchstart", { clientY: 100 })); + el.dispatchEvent(touchEvent("touchmove", { clientY: 250 })); + el.dispatchEvent(touchEvent("touchend")); + await wait(0); + assert.equal(closeCount, 1); + }); + it("still blocks drags outside the handle when the keyboard is open", async () => { + setKeyboardOpen(true); + handle = enableDragToDismiss(el, { + onDismiss: () => closeCount++, + dragHandle, + disableWhenKeyboardOpen: true, + }); + el.dispatchEvent(touchEvent("touchstart", { clientY: 100 })); + el.dispatchEvent(touchEvent("touchmove", { clientY: 250 })); + el.dispatchEvent(touchEvent("touchend")); + await wait(0); + assert.equal(closeCount, 0); + }); + it("dismisses from the handle even when the scroll container is scrolled away from the top", async () => { + const scrollContainer = document.createElement("div"); + scrollContainer.scrollTop = 100; + el.appendChild(scrollContainer); + handle = enableDragToDismiss(el, { + onDismiss: () => closeCount++, + dragHandle, + scrollContainer, + }); + dragHandle.dispatchEvent(touchEvent("touchstart", { clientY: 100 })); + el.dispatchEvent(touchEvent("touchmove", { clientY: 250 })); + el.dispatchEvent(touchEvent("touchend")); + await wait(0); + assert.equal(closeCount, 1); + }); + }); describe("default direction (down)", () => { let originalVisualViewport; const setKeyboardOpen = (open) => {