1
0
Эх сурвалжийг харах

Merge pull request #619 from HugoDF/feat-passive-listeners

Feature: `.passive` modifier for passive event listeners
Caleb Porzio 5 жил өмнө
parent
commit
4a71a77b9c

+ 5 - 0
README.md

@@ -355,6 +355,11 @@ Adding `.window` to an event listener will install the listener on the global wi
 
 Adding the `.once` modifier to an event listener will ensure that the listener will only be handled once. This is useful for things you only want to do once, like fetching HTML partials and such.
 
+**`.passive` modifier**
+**Example:** `<button x-on:mousedown.passive="interactive = true"></button>`
+
+Adding the `.passive` modifier to an event listener will make the listener a passive one, which means `preventDefault()` will not work on any events being processed, this can help, for example with scroll performance on touch devices.
+
 **`.debounce` modifier**
 **Example:** `<input x-on:input.debounce="fetchSomething()">`
 

+ 7 - 7
package-lock.json

@@ -1,6 +1,6 @@
 {
     "name": "alpinejs",
-    "version": "2.3.5",
+    "version": "2.4.1",
     "lockfileVersion": 1,
     "requires": true,
     "dependencies": {
@@ -4580,12 +4580,6 @@
                 }
             }
         },
-        "custom-event-polyfill": {
-            "version": "1.0.7",
-            "resolved": "https://registry.npmjs.org/custom-event-polyfill/-/custom-event-polyfill-1.0.7.tgz",
-            "integrity": "sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==",
-            "dev": true
-        },
         "dashdash": {
             "version": "1.14.1",
             "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -4838,6 +4832,12 @@
             "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
             "dev": true
         },
+        "events-polyfill": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/events-polyfill/-/events-polyfill-2.1.2.tgz",
+            "integrity": "sha512-vx4kpGzymyD3CEjmg2wTQA6k5e0RhGTkX3ZwfC9m/Ol7+me2tbVuJ0GjSd8eIJxFioubicA0nUL0SIOAyfrgZA==",
+            "dev": true
+        },
         "exec-sh": {
             "version": "0.3.4",
             "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz",

+ 1 - 1
package.json

@@ -27,9 +27,9 @@
         "classlist-polyfill": "^1.2.0",
         "concurrently": "^5.2.0",
         "core-js": "^3.6.5",
-        "custom-event-polyfill": "^1.0.7",
         "element-closest": "^3.0.2",
         "element-remove": "^1.0.4",
+        "events-polyfill": "^2.1.2",
         "jest": "^25.5.4",
         "jsdom-simulant": "^1.1.2",
         "observable-membrane": "^0.26.1",

+ 9 - 6
src/directives/on.js

@@ -1,9 +1,12 @@
 import { kebabCase, debounce, isNumeric } from '../utils'
 
 export function registerListener(component, el, event, modifiers, expression, extraVars = {}) {
+    const options = {
+        passive: modifiers.includes('passive'),
+    };
     if (modifiers.includes('away')) {
         let handler = e => {
-            // Don't do anything if the click came form the element or within it.
+            // Don't do anything if the click came from the element or within it.
             if (el.contains(e.target)) return
 
             // Don't do anything if this element isn't currently visible.
@@ -14,12 +17,12 @@ export function registerListener(component, el, event, modifiers, expression, ex
             runListenerHandler(component, expression, e, extraVars)
 
             if (modifiers.includes('once')) {
-                document.removeEventListener(event, handler)
+                document.removeEventListener(event, handler, options)
             }
         }
 
         // Listen for this event at the root level.
-        document.addEventListener(event, handler)
+        document.addEventListener(event, handler, options)
     } else {
         let listenerTarget = modifiers.includes('window')
             ? window : (modifiers.includes('document') ? document : el)
@@ -29,7 +32,7 @@ export function registerListener(component, el, event, modifiers, expression, ex
             // has been removed. It's now stale.
             if (listenerTarget === window || listenerTarget === document) {
                 if (! document.body.contains(el)) {
-                    listenerTarget.removeEventListener(event, handler)
+                    listenerTarget.removeEventListener(event, handler, options)
                     return
                 }
             }
@@ -53,7 +56,7 @@ export function registerListener(component, el, event, modifiers, expression, ex
                     e.preventDefault()
                 } else {
                     if (modifiers.includes('once')) {
-                        listenerTarget.removeEventListener(event, handler)
+                        listenerTarget.removeEventListener(event, handler, options)
                     }
                 }
             }
@@ -65,7 +68,7 @@ export function registerListener(component, el, event, modifiers, expression, ex
             handler = debounce(handler, wait, this)
         }
 
-        listenerTarget.addEventListener(event, handler)
+        listenerTarget.addEventListener(event, handler, options)
     }
 }
 

+ 2 - 1
src/polyfills.js

@@ -5,6 +5,7 @@ import "element-closest/browser.js"
 import "element-remove"
 import "classlist-polyfill"
 import "@webcomponents/template"
-import "custom-event-polyfill"
+import "events-polyfill/src/constructors/CustomEvent"
+import "events-polyfill/src/ListenerOptions"
 
 SVGElement.prototype.contains = SVGElement.prototype.contains || HTMLElement.prototype.contains

+ 57 - 1
test/on.spec.js

@@ -42,6 +42,30 @@ test('nested data modified in event listener updates affected attribute bindings
     await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
 })
 
+test('.passive modifier should disable e.preventDefault()', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ defaultPrevented: null }">
+            <button
+                x-on:mousedown.passive="
+                    $event.preventDefault();
+                    defaultPrevented = $event.defaultPrevented;
+                "
+            >
+                <span></span>
+            </button>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('div').__x.$data.defaultPrevented).toEqual(null)
+
+    fireEvent.mouseDown(document.querySelector('button'))
+
+    await wait(() => {
+        expect(document.querySelector('div').__x.$data.defaultPrevented).toEqual(false)
+    })
+})
 
 test('.stop modifier', async () => {
     document.body.innerHTML = `
@@ -336,6 +360,39 @@ test('click away', async () => {
     await wait(() => { expect(document.querySelector('ul').classList.contains('hidden')).toEqual(false) })
 })
 
+test('.passive + .away modifier still disables e.preventDefault()', async () => {
+    // Pretend like all the elements are visible
+    Object.defineProperties(window.HTMLElement.prototype, {
+        offsetHeight: {
+            get: () => 1
+        },
+        offsetWidth: {
+            get: () => 1
+        }
+    });
+    document.body.innerHTML = `
+        <div x-data="{ defaultPrevented: null }">
+            <button
+                x-on:mousedown.away.passive="
+                    $event.preventDefault();
+                    defaultPrevented = $event.defaultPrevented;
+                "
+            ></button>
+            <span></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('div').__x.$data.defaultPrevented).toEqual(null)
+
+    fireEvent.mouseDown(document.querySelector('span'))
+
+    await wait(() => {
+        expect(document.querySelector('div').__x.$data.defaultPrevented).toEqual(false)
+    })
+})
+
 test('supports short syntax', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar' }">
@@ -353,7 +410,6 @@ test('supports short syntax', async () => {
     await wait(() => { expect(document.querySelector('span').getAttribute('foo')).toEqual('baz') })
 })
 
-
 test('event with colon', async () => {
     document.body.innerHTML = `
         <div x-data="{ foo: 'bar' }">