]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-dom): support event options (#149)
authorStanislav Lashmanov <stasvarenkin@gmail.com>
Wed, 9 Oct 2019 19:05:21 +0000 (22:05 +0300)
committerEvan You <yyx990803@gmail.com>
Wed, 9 Oct 2019 19:05:21 +0000 (15:05 -0400)
packages/runtime-dom/__tests__/events.spec.ts [new file with mode: 0644]
packages/runtime-dom/src/modules/events.ts

diff --git a/packages/runtime-dom/__tests__/events.spec.ts b/packages/runtime-dom/__tests__/events.spec.ts
new file mode 100644 (file)
index 0000000..12b0ae9
--- /dev/null
@@ -0,0 +1,101 @@
+import { patchEvent } from '../src/modules/events'
+
+describe(`events`, () => {
+  it('should assign event handler', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const fn = jest.fn()
+    patchEvent(el, 'click', null, fn, null)
+    el.dispatchEvent(event)
+    el.dispatchEvent(event)
+    el.dispatchEvent(event)
+    expect(fn).toHaveBeenCalledTimes(3)
+  })
+
+  it('should update event handler', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const prevFn = jest.fn()
+    const nextFn = jest.fn()
+    patchEvent(el, 'click', null, prevFn, null)
+    el.dispatchEvent(event)
+    patchEvent(el, 'click', prevFn, nextFn, null)
+    el.dispatchEvent(event)
+    el.dispatchEvent(event)
+    expect(prevFn).toHaveBeenCalledTimes(1)
+    expect(nextFn).toHaveBeenCalledTimes(2)
+  })
+
+  it('should support multiple event handlers', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const fn1 = jest.fn()
+    const fn2 = jest.fn()
+    patchEvent(el, 'click', null, [fn1, fn2], null)
+    el.dispatchEvent(event)
+    expect(fn1).toHaveBeenCalledTimes(1)
+    expect(fn2).toHaveBeenCalledTimes(1)
+  })
+
+  it('should unassign event handler', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const fn = jest.fn()
+    patchEvent(el, 'click', null, fn, null)
+    patchEvent(el, 'click', fn, null, null)
+    el.dispatchEvent(event)
+    expect(fn).not.toHaveBeenCalled()
+  })
+
+  it('should support event options', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const fn = jest.fn()
+    const nextValue = {
+      handler: fn,
+      options: {
+        once: true
+      }
+    }
+    patchEvent(el, 'click', null, nextValue, null)
+    el.dispatchEvent(event)
+    el.dispatchEvent(event)
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
+
+  it('should support varying event options', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const prevFn = jest.fn()
+    const nextFn = jest.fn()
+    const nextValue = {
+      handler: nextFn,
+      options: {
+        once: true
+      }
+    }
+    patchEvent(el, 'click', null, prevFn, null)
+    patchEvent(el, 'click', prevFn, nextValue, null)
+    el.dispatchEvent(event)
+    el.dispatchEvent(event)
+    expect(prevFn).not.toHaveBeenCalled()
+    expect(nextFn).toHaveBeenCalledTimes(1)
+  })
+
+  it('should unassign event handler with options', () => {
+    const el = document.createElement('div')
+    const event = new Event('click')
+    const fn = jest.fn()
+    const nextValue = {
+      handler: fn,
+      options: {
+        once: true
+      }
+    }
+    patchEvent(el, 'click', null, nextValue, null)
+    patchEvent(el, 'click', nextValue, null, null)
+    el.dispatchEvent(event)
+    el.dispatchEvent(event)
+    expect(fn).not.toHaveBeenCalled()
+  })
+})
index 7d572e4910a8bfcf56531835b29ca8b773fd01fe..d24ec3886b426938210d044086847d46bbc84bd8 100644 (file)
@@ -1,4 +1,4 @@
-import { isArray } from '@vue/shared'
+import { isArray, EMPTY_OBJ } from '@vue/shared'
 import {
   ComponentInternalInstance,
   callWithAsyncErrorHandling
@@ -14,6 +14,13 @@ type EventValue = (Function | Function[]) & {
   invoker?: Invoker | null
 }
 
+type EventValueWithOptions = {
+  handler: EventValue
+  options: AddEventListenerOptions
+  persistent?: boolean
+  invoker?: Invoker | null
+}
+
 // Async edge case fix requires storing an event listener's attach timestamp.
 let _getNow: () => number = Date.now
 
@@ -43,22 +50,53 @@ const getNow = () => cachedNow || (p.then(reset), (cachedNow = _getNow()))
 export function patchEvent(
   el: Element,
   name: string,
-  prevValue: EventValue | null,
-  nextValue: EventValue | null,
+  prevValue: EventValueWithOptions | EventValue | null,
+  nextValue: EventValueWithOptions | EventValue | null,
   instance: ComponentInternalInstance | null = null
 ) {
+  const prevOptions = prevValue && 'options' in prevValue && prevValue.options
+  const nextOptions = nextValue && 'options' in nextValue && nextValue.options
   const invoker = prevValue && prevValue.invoker
-  if (nextValue) {
+  const value =
+    nextValue && 'handler' in nextValue ? nextValue.handler : nextValue
+  const persistent =
+    nextValue && 'persistent' in nextValue && nextValue.persistent
+
+  if (!persistent && (prevOptions || nextOptions)) {
+    const prev = prevOptions || EMPTY_OBJ
+    const next = nextOptions || EMPTY_OBJ
+    if (
+      prev.capture !== next.capture ||
+      prev.passive !== next.passive ||
+      prev.once !== next.once
+    ) {
+      if (invoker) {
+        el.removeEventListener(name, invoker as any, prevOptions as any)
+      }
+      if (nextValue && value) {
+        const invoker = createInvoker(value, instance)
+        nextValue.invoker = invoker
+        el.addEventListener(name, invoker, nextOptions as any)
+      }
+      return
+    }
+  }
+
+  if (nextValue && value) {
     if (invoker) {
       ;(prevValue as EventValue).invoker = null
-      invoker.value = nextValue
+      invoker.value = value
       nextValue.invoker = invoker
       invoker.lastUpdated = getNow()
     } else {
-      el.addEventListener(name, createInvoker(nextValue, instance))
+      el.addEventListener(
+        name,
+        createInvoker(value, instance),
+        nextOptions as any
+      )
     }
   } else if (invoker) {
-    el.removeEventListener(name, invoker)
+    el.removeEventListener(name, invoker, prevOptions as any)
   }
 }
 
@@ -73,7 +111,7 @@ function createInvoker(
     // the solution is simple: we save the timestamp when a handler is attached,
     // and the handler would only fire if the event passed to it was fired
     // AFTER it was attached.
-    if (e.timeStamp >= invoker.lastUpdated) {
+    if (e.timeStamp >= invoker.lastUpdated - 1) {
       const args = [e]
       const value = invoker.value
       if (isArray(value)) {