Update menu swipe (#13277)

Update menu swipe (#13277)

  • DEV: replace swipe events to use translate rather than left/right

translate is better for animations. also use native css animations for opening and closing.

  • a11y: respect prefers reduced motion on mobile timeline

  • DEV: reduce jquery usage

  • DEV: add tests for menu swipe events

test is run in 50% zoom/transform which means offsets and x of touch events need to be halved

Refactor test window to use a transform rather than non-standard zoom property

Co-authored-by: Penar Musaraj pmusaraj@gmail.com

diff --git a/app/assets/javascripts/discourse/app/components/site-header.js b/app/assets/javascripts/discourse/app/components/site-header.js
index eac5366..b037a30 100644
--- a/app/assets/javascripts/discourse/app/components/site-header.js
+++ b/app/assets/javascripts/discourse/app/components/site-header.js
@@ -1,6 +1,5 @@
 import PanEvents, {
   SWIPE_DISTANCE_THRESHOLD,
-  SWIPE_VELOCITY,
   SWIPE_VELOCITY_THRESHOLD,
 } from "discourse/mixins/pan-events";
 import { cancel, later, schedule } from "@ember/runloop";
@@ -23,7 +22,6 @@ const SiteHeaderComponent = MountWidget.extend(
     _isPanning: false,
     _panMenuOrigin: "right",
     _panMenuOffset: 0,
-    _scheduledMovingAnimation: null,
     _scheduledRemoveAnimate: null,
     _topic: null,
     _mousetrap: null,
@@ -37,26 +35,44 @@ const SiteHeaderComponent = MountWidget.extend(
       this.queueRerender();
     },
 
-    _animateOpening($panel) {
-      $panel.css({ right: "", left: "" });
+    _animateOpening(panel) {
+      const headerCloak = document.querySelector(".header-cloak");
+      panel.classList.add("animate");
+      headerCloak.classList.add("animate");
+      this._scheduledRemoveAnimate = later(() => {
+        panel.classList.remove("animate");
+        headerCloak.classList.remove("animate");
+      }, 200);
+      panel.style.setProperty("--offset", 0);
+      headerCloak.style.setProperty("--opacity", 0.5);
       this._panMenuOffset = 0;
     },
 
-    _animateClosing($panel, menuOrigin, windowWidth) {
-      $panel.css(menuOrigin, -windowWidth);
+    _animateClosing(panel, menuOrigin) {
+      const windowWidth = document.body.offsetWidth;
       this._animate = true;
-      schedule("afterRender", () => {
-        this.eventDispatched("dom:clean", "header");
-        this._panMenuOffset = 0;
-      });
+      const headerCloak = document.querySelector(".header-cloak");
+      panel.classList.add("animate");
+      headerCloak.classList.add("animate");
+      const offsetDirection = menuOrigin === "left" ? -1 : 1;
+      panel.style.setProperty("--offset", `${offsetDirection * windowWidth}px`);
+      headerCloak.style.setProperty("--opacity", 0);
+      this._scheduledRemoveAnimate = later(() => {
+        panel.classList.remove("animate");
+        headerCloak.classList.remove("animate");
+        schedule("afterRender", () => {
+          this.eventDispatched("dom:clean", "header");
+          this._panMenuOffset = 0;
+        });
+      }, 200);
     },
 
     _isRTL() {
-      return $("html").css("direction") === "rtl";
+      return document.querySelector("html").classList["direction"] === "rtl";
     },
 
     _leftMenuClass() {
-      return this._isRTL() ? ".user-menu" : ".hamburger-panel";
+      return this._isRTL() ? "user-menu" : "hamburger-panel";
     },
 
     _leftMenuAction() {
@@ -67,28 +83,14 @@ const SiteHeaderComponent = MountWidget.extend(
       return this._isRTL() ? "toggleHamburger" : "toggleUserMenu";
     },
 
-    _handlePanDone(offset, event) {
-      const $window = $(window);
-      const windowWidth = $window.width();
-      const $menuPanels = $(".menu-panel");
+    _handlePanDone(event) {
+      const menuPanels = document.querySelectorAll(".menu-panel");
       const menuOrigin = this._panMenuOrigin;
-      this._shouldMenuClose(event, menuOrigin)
-        ? (offset += SWIPE_VELOCITY)
-        : (offset -= SWIPE_VELOCITY);
-      $menuPanels.each((idx, panel) => {
-        const $panel = $(panel);
-        const $headerCloak = $(".header-cloak");
-        $panel.css(menuOrigin, -offset);
-        $headerCloak.css("opacity", Math.min(0.5, (300 - offset) / 600));
-        if (offset > windowWidth) {
-          this._animateClosing($panel, menuOrigin, windowWidth);
-        } else if (offset <= 0) {
-          this._animateOpening($panel);
+      menuPanels.forEach((panel) => {
+        if (this._shouldMenuClose(event, menuOrigin)) {
+          this._animateClosing(panel, menuOrigin);
         } else {
-          //continue to open or close menu
-          this._scheduledMovingAnimation = window.requestAnimationFrame(() =>
-            this._handlePanDone(offset, event)
-          );
+          this._animateOpening(panel);
         }
       });
     },
@@ -114,11 +116,15 @@ const SiteHeaderComponent = MountWidget.extend(
 
     panStart(e) {
       const center = e.center;
-      const $centeredElement = $(document.elementFromPoint(center.x, center.y));
+      const panOverValidElement = document
+        .elementsFromPoint(center.x, center.y)
+        .some(
+          (ele) =>
+            ele.classList.contains("panel-body") ||
+            ele.classList.contains("header-cloak")
+        );
       if (
-        ($centeredElement.hasClass("panel-body") ||
-          $centeredElement.hasClass("header-cloak") ||
-          $centeredElement.parents(".panel-body").length) &&
+        panOverValidElement &&
         (e.direction === "left" || e.direction === "right")
       ) {
         e.originalEvent.preventDefault();
@@ -133,57 +139,51 @@ const SiteHeaderComponent = MountWidget.extend(
         return;
       }
       this._isPanning = false;
-      $(".menu-panel").each((idx, panel) => {
-        const $panel = $(panel);
-        let offset = $panel.css("right");
-        if (this._panMenuOrigin === "left") {
-          offset = $panel.css("left");
-        }
-        offset = Math.abs(parseInt(offset, 10));
-        this._handlePanDone(offset, e);
-      });
+      this._handlePanDone(e);
     },
 
     panMove(e) {
       if (!this._isPanning) {
         return;
       }
-      const $menuPanels = $(".menu-panel");
-      $menuPanels.each((idx, panel) => {
-        const $panel = $(panel);
-        const $headerCloak = $(".header-cloak");
-        if (this._panMenuOrigin === "right") {
-          const pxClosed = Math.min(0, -e.deltaX + this._panMenuOffset);
-          $panel.css("right", pxClosed);
-          $headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
-        } else {
-          const pxClosed = Math.min(0, e.deltaX + this._panMenuOffset);
-          $panel.css("left", pxClosed);
-          $headerCloak.css("opacity", Math.min(0.5, (300 + pxClosed) / 600));
-        }
-      });
+      const panel = document.querySelector(".menu-panel");
+      const headerCloak = document.querySelector(".header-cloak");
+      if (this._panMenuOrigin === "right") {
+        const pxClosed = Math.min(0, -e.deltaX + this._panMenuOffset);
+        panel.style.setProperty("--offset", `${-pxClosed}px`);
+        headerCloak.style.setProperty(
+          "--opacity",
+          Math.min(0.5, (300 + pxClosed) / 600)
+        );
+      } else {
+        const pxClosed = Math.min(0, e.deltaX + this._panMenuOffset);
+        panel.style.setProperty("--offset", `${pxClosed}px`);
+        headerCloak.style.setProperty(
+          "--opacity",
+          Math.min(0.5, (300 + pxClosed) / 600)
+        );
+      }
     },
 
     dockCheck(info) {
-      const $header = $("header.d-header");
+      const header = document.querySelector("header.d-header");
 
       if (this.docAt === null) {
-        if (!($header && $header.length === 1)) {
+        if (!header) {
           return;
         }
-        this.docAt = $header.offset().top;
+        this.docAt = header.offsetTop;
       }
 
-      const $body = $("body");
       const offset = info.offset();
       if (offset >= this.docAt) {
         if (!this.dockedHeader) {
-          $body.addClass("docked");
+          document.body.classList.add("docked");
           this.dockedHeader = true;
         }
       } else {
         if (this.dockedHeader) {
-          $body.removeClass("docked");
+          document.body.classList.remove("docked");
           this.dockedHeader = false;
         }
       }
@@ -197,13 +197,14 @@ const SiteHeaderComponent = MountWidget.extend(
 
     willRender() {
       if (this.get("currentUser.staff")) {
-        $("body").addClass("staff");

[... diff too long, it was truncated ...]

GitHub sha: 7c12ca89

This commit appears in #13277 which was approved by pmusaraj and eviltrout. It was merged by featheredtoast.