feat: delegate event for vapor

closes #136
This commit is contained in:
三咲智子 Kevin Deng 2024-02-25 00:40:00 +08:00
parent e91dde5d22
commit 669fec8dad
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
12 changed files with 305 additions and 157 deletions

View File

@ -45,7 +45,7 @@ export * from './runtimeHelpers'
export { getBaseTransformPreset, type TransformPreset } from './compile' export { getBaseTransformPreset, type TransformPreset } from './compile'
export { transformModel } from './transforms/vModel' export { transformModel } from './transforms/vModel'
export { transformOn } from './transforms/vOn' export { transformOn, fnExpRE } from './transforms/vOn'
export { transformBind } from './transforms/vBind' export { transformBind } from './transforms/vBind'
export { noopDirectiveTransform } from './transforms/noopDirectiveTransform' export { noopDirectiveTransform } from './transforms/noopDirectiveTransform'
export { processIf } from './transforms/vIf' export { processIf } from './transforms/vIf'

View File

@ -16,7 +16,7 @@ import { validateBrowserExpression } from '../validateExpression'
import { hasScopeRef, isMemberExpression } from '../utils' import { hasScopeRef, isMemberExpression } from '../utils'
import { TO_HANDLER_KEY } from '../runtimeHelpers' import { TO_HANDLER_KEY } from '../runtimeHelpers'
const fnExpRE = export const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/ /^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
export interface VOnDirectiveNode extends DirectiveNode { export interface VOnDirectiveNode extends DirectiveNode {

View File

@ -146,12 +146,13 @@ export function render(_ctx) {
`; `;
exports[`compile > dynamic root nodes and interpolation 1`] = ` exports[`compile > dynamic root nodes and interpolation 1`] = `
"import { on as _on, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, renderEffect as _renderEffect, setText as _setText, setDynamicProp as _setDynamicProp, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<button></button>") const t0 = _template("<button></button>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _ctx.handleClick) _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.handleClick))
_renderEffect(() => _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count)) _renderEffect(() => _setText(n0, _ctx.count, "foo", _ctx.count, "foo", _ctx.count))
_renderEffect(() => _setDynamicProp(n0, "id", _ctx.count)) _renderEffect(() => _setDynamicProp(n0, "id", _ctx.count))
return n0 return n0

View File

@ -1,13 +1,14 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-for > basic v-for 1`] = ` exports[`compiler: v-for > basic v-for 1`] = `
"import { on as _on, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = _createFor(() => (_ctx.items), (_block) => { const n0 = _createFor(() => (_ctx.items), (_block) => {
const n2 = t0() const n2 = t0()
_on(n2, "click", () => $event => (_ctx.remove(_block.s[0]))) _recordMetadata(n2, "events", "click", _eventHandler(() => $event => (_ctx.remove(_block.s[0]))))
const _updateEffect = () => { const _updateEffect = () => {
const [item] = _block.s const [item] = _block.s
_setText(n2, item) _setText(n2, item)

View File

@ -1,12 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`v-on > complex member expression w/ prefixIdentifiers: true 1`] = ` exports[`v-on > complex member expression w/ prefixIdentifiers: true 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _ctx.a['b' + _ctx.c]) _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.a['b' + _ctx.c]))
return n0 return n0
}" }"
`; `;
@ -45,11 +46,12 @@ export function render(_ctx) {
`; `;
exports[`v-on > event modifier 1`] = ` exports[`v-on > event modifier 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, on as _on, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<a></a>") const t0 = _template("<a></a>")
const t1 = _template("<form></form>") const t1 = _template("<form></form>")
const t2 = _template("<div></div>") const t2 = _template("<div></div>")
const t3 = _template("<input>") const t3 = _template("<input>")
_delegateEvents("click", "contextmenu", "mouseup", "keyup")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
@ -74,224 +76,249 @@ export function render(_ctx) {
const n19 = t3() const n19 = t3()
const n20 = t3() const n20 = t3()
const n21 = t3() const n21 = t3()
_on(n0, "click", () => _ctx.handleEvent, undefined, { _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["stop"] modifiers: ["stop"]
}) }))
_on(n1, "submit", () => _ctx.handleEvent, undefined, { _on(n1, "submit", () => _ctx.handleEvent, undefined, {
modifiers: ["prevent"] modifiers: ["prevent"]
}) })
_on(n2, "click", () => _ctx.handleEvent, undefined, { _recordMetadata(n2, "events", "click", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["stop", "prevent"] modifiers: ["stop", "prevent"]
}) }))
_on(n3, "click", () => _ctx.handleEvent, undefined, { _recordMetadata(n3, "events", "click", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["self"] modifiers: ["self"]
}) }))
_on(n4, "click", () => _ctx.handleEvent, { capture: true }) _on(n4, "click", () => _ctx.handleEvent, { capture: true })
_on(n5, "click", () => _ctx.handleEvent, { once: true }) _on(n5, "click", () => _ctx.handleEvent, { once: true })
_on(n6, "scroll", () => _ctx.handleEvent, { passive: true }) _on(n6, "scroll", () => _ctx.handleEvent, { passive: true })
_on(n7, "contextmenu", () => _ctx.handleEvent, undefined, { _recordMetadata(n7, "events", "contextmenu", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["right"] modifiers: ["right"]
}) }))
_on(n8, "click", () => _ctx.handleEvent, undefined, { _recordMetadata(n8, "events", "click", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["left"] modifiers: ["left"]
}) }))
_on(n9, "mouseup", () => _ctx.handleEvent, undefined, { _recordMetadata(n9, "events", "mouseup", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["middle"] modifiers: ["middle"]
}) }))
_on(n10, "contextmenu", () => _ctx.handleEvent, undefined, { _recordMetadata(n10, "events", "contextmenu", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["right"] modifiers: ["right"]
}) }))
_on(n11, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n11, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["enter"] keys: ["enter"]
}) }))
_on(n12, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n12, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["tab"] keys: ["tab"]
}) }))
_on(n13, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n13, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["delete"] keys: ["delete"]
}) }))
_on(n14, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n14, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["esc"] keys: ["esc"]
}) }))
_on(n15, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n15, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["space"] keys: ["space"]
}) }))
_on(n16, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n16, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["up"] keys: ["up"]
}) }))
_on(n17, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n17, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["down"] keys: ["down"]
}) }))
_on(n18, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n18, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
keys: ["left"] keys: ["left"]
}) }))
_on(n19, "keyup", () => _ctx.submit, undefined, { _recordMetadata(n19, "events", "keyup", _eventHandler(() => _ctx.submit, {
modifiers: ["middle"] modifiers: ["middle"]
}) }))
_on(n20, "keyup", () => _ctx.submit, undefined, { _recordMetadata(n20, "events", "keyup", _eventHandler(() => _ctx.submit, {
modifiers: ["middle", "self"] modifiers: ["middle", "self"]
}) }))
_on(n21, "keyup", () => _ctx.handleEvent, undefined, { _recordMetadata(n21, "events", "keyup", _eventHandler(() => _ctx.handleEvent, {
modifiers: ["self"], modifiers: ["self"],
keys: ["enter"] keys: ["enter"]
}) }))
return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20, n21] return [n0, n1, n2, n3, n4, n5, n6, n7, n8, n9, n10, n11, n12, n13, n14, n15, n16, n17, n18, n19, n20, n21]
}" }"
`; `;
exports[`v-on > function expression w/ prefixIdentifiers: true 1`] = ` exports[`v-on > function expression w/ prefixIdentifiers: true 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => e => _ctx.foo(e)) _recordMetadata(n0, "events", "click", _eventHandler(() => e => _ctx.foo(e)))
return n0 return n0
}" }"
`; `;
exports[`v-on > inline statement w/ prefixIdentifiers: true 1`] = ` exports[`v-on > inline statement w/ prefixIdentifiers: true 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => (_ctx.foo($event))) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => (_ctx.foo($event))))
return n0 return n0
}" }"
`; `;
exports[`v-on > multiple inline statements w/ prefixIdentifiers: true 1`] = ` exports[`v-on > multiple inline statements w/ prefixIdentifiers: true 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => {_ctx.foo($event);_ctx.bar()}) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => {_ctx.foo($event);_ctx.bar()}))
return n0 return n0
}" }"
`; `;
exports[`v-on > should NOT add a prefix to $event if the expression is a function expression 1`] = ` exports[`v-on > should NOT add a prefix to $event if the expression is a function expression 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => {_ctx.i++;_ctx.foo($event)}) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => {_ctx.i++;_ctx.foo($event)}))
return n0 return n0
}" }"
`; `;
exports[`v-on > should NOT wrap as function if expression is already function expression (with Typescript) 1`] = ` exports[`v-on > should NOT wrap as function if expression is already function expression (with Typescript) 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => (e: any): any => _ctx.foo(e)) _recordMetadata(n0, "events", "click", _eventHandler(() => (e: any): any => _ctx.foo(e)))
return n0 return n0
}" }"
`; `;
exports[`v-on > should NOT wrap as function if expression is already function expression (with newlines) 1`] = ` exports[`v-on > should NOT wrap as function if expression is already function expression (with newlines) 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _recordMetadata(n0, "events", "click", _eventHandler(() =>
$event => { $event => {
_ctx.foo($event) _ctx.foo($event)
} }
) ))
return n0 return n0
}" }"
`; `;
exports[`v-on > should NOT wrap as function if expression is already function expression 1`] = ` exports[`v-on > should NOT wrap as function if expression is already function expression 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => _ctx.foo($event)) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => _ctx.foo($event)))
return n0 return n0
}" }"
`; `;
exports[`v-on > should NOT wrap as function if expression is complex member expression 1`] = ` exports[`v-on > should NOT wrap as function if expression is complex member expression 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _ctx.a['b' + _ctx.c]) _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.a['b' + _ctx.c]))
return n0
}"
`;
exports[`v-on > should delegate event 1`] = `
"import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) {
const n0 = t0()
_recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.test))
return n0 return n0
}" }"
`; `;
exports[`v-on > should handle multi-line statement 1`] = ` exports[`v-on > should handle multi-line statement 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => { _recordMetadata(n0, "events", "click", _eventHandler(() => $event => {
_ctx.foo(); _ctx.foo();
_ctx.bar() _ctx.bar()
}) }))
return n0 return n0
}" }"
`; `;
exports[`v-on > should handle multiple inline statement 1`] = ` exports[`v-on > should handle multiple inline statement 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => {_ctx.foo();_ctx.bar()}) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => {_ctx.foo();_ctx.bar()}))
return n0 return n0
}" }"
`; `;
exports[`v-on > should not prefix member expression 1`] = ` exports[`v-on > should not prefix member expression 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _ctx.foo.bar) _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.foo.bar))
return n0 return n0
}" }"
`; `;
exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = ` exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("keyup")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "keyup", () => _ctx.test, undefined, { _recordMetadata(n0, "events", "keyup", _eventHandler(() => _ctx.test, {
modifiers: ["exact"] modifiers: ["exact"]
}) }))
return n0 return n0
}" }"
`; `;
exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = ` exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click", "keyup")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _ctx.test, undefined, { _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.test, {
modifiers: ["stop"] modifiers: ["stop"]
}) }))
_on(n0, "keyup", () => _ctx.test, undefined, { _recordMetadata(n0, "events", "keyup", _eventHandler(() => _ctx.test, {
keys: ["enter"] keys: ["enter"]
}) }))
return n0 return n0
}" }"
`; `;
@ -310,14 +337,15 @@ export function render(_ctx) {
`; `;
exports[`v-on > should transform click.middle 1`] = ` exports[`v-on > should transform click.middle 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("mouseup")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "mouseup", () => _ctx.test, undefined, { _recordMetadata(n0, "events", "mouseup", _eventHandler(() => _ctx.test, {
modifiers: ["middle"] modifiers: ["middle"]
}) }))
return n0 return n0
}" }"
`; `;
@ -338,14 +366,15 @@ export function render(_ctx) {
`; `;
exports[`v-on > should transform click.right 1`] = ` exports[`v-on > should transform click.right 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("contextmenu")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "contextmenu", () => _ctx.test, undefined, { _recordMetadata(n0, "events", "contextmenu", _eventHandler(() => _ctx.test, {
modifiers: ["right"] modifiers: ["right"]
}) }))
return n0 return n0
}" }"
`; `;
@ -367,12 +396,13 @@ export function render(_ctx) {
`; `;
exports[`v-on > should wrap as function if expression is inline statement 1`] = ` exports[`v-on > should wrap as function if expression is inline statement 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => $event => (_ctx.i++)) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => (_ctx.i++)))
return n0 return n0
}" }"
`; `;
@ -398,9 +428,9 @@ exports[`v-on > should wrap in unref if identifier is setup-maybe-ref w/ inline:
const n0 = t0() const n0 = t0()
const n1 = t0() const n1 = t0()
const n2 = t0() const n2 = t0()
_on(n0, "click", () => $event => (x.value=_unref(y))) _recordMetadata(n0, "events", "click", _eventHandler(() => $event => (x.value=_unref(y))))
_on(n1, "click", () => $event => (x.value++)) _recordMetadata(n1, "events", "click", _eventHandler(() => $event => (x.value++)))
_on(n2, "click", () => $event => ({ x: x.value } = _unref(y))) _recordMetadata(n2, "events", "click", _eventHandler(() => $event => ({ x: x.value } = _unref(y))))
return [n0, n1, n2] return [n0, n1, n2]
})()" })()"
`; `;
@ -420,25 +450,27 @@ export function render(_ctx) {
`; `;
exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = ` exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("keyup")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "keyup", () => _ctx.test, undefined, { _recordMetadata(n0, "events", "keyup", _eventHandler(() => _ctx.test, {
keys: ["left"] keys: ["left"]
}) }))
return n0 return n0
}" }"
`; `;
exports[`v-on > simple expression 1`] = ` exports[`v-on > simple expression 1`] = `
"import { on as _on, template as _template } from 'vue/vapor'; "import { recordMetadata as _recordMetadata, eventHandler as _eventHandler, delegateEvents as _delegateEvents, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
_delegateEvents("click")
export function render(_ctx) { export function render(_ctx) {
const n0 = t0() const n0 = t0()
_on(n0, "click", () => _ctx.handleClick) _recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.handleClick))
return n0 return n0
}" }"
`; `;

View File

@ -25,10 +25,10 @@ describe('v-on', () => {
}, },
) )
expect(vaporHelpers).contains('on') expect(code).matchSnapshot()
expect(vaporHelpers).not.contains('on')
expect(helpers.size).toBe(0) expect(helpers.size).toBe(0)
expect(ir.block.effect).toEqual([]) expect(ir.block.effect).toEqual([])
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
@ -45,10 +45,9 @@ describe('v-on', () => {
}, },
modifiers: { keys: [], nonKeys: [], options: [] }, modifiers: { keys: [], nonKeys: [], options: [] },
keyOverride: undefined, keyOverride: undefined,
delegate: true,
}, },
]) ])
expect(code).matchSnapshot()
}) })
test('event modifier', () => { test('event modifier', () => {
@ -155,10 +154,10 @@ describe('v-on', () => {
const { code, ir, helpers, vaporHelpers } = const { code, ir, helpers, vaporHelpers } =
compileWithVOn(`<div @click="i++"/>`) compileWithVOn(`<div @click="i++"/>`)
expect(vaporHelpers).contains('on') expect(code).matchSnapshot()
expect(vaporHelpers).not.contains('on')
expect(helpers.size).toBe(0) expect(helpers.size).toBe(0)
expect(ir.block.effect).toEqual([]) expect(ir.block.effect).toEqual([])
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
@ -168,11 +167,12 @@ describe('v-on', () => {
content: 'i++', content: 'i++',
isStatic: false, isStatic: false,
}, },
delegate: true,
}, },
]) ])
expect(code).contains(
expect(code).matchSnapshot() '_recordMetadata(n0, "events", "click", _eventHandler(() => $event => (_ctx.i++)))',
expect(code).contains('_on(n0, "click", () => $event => (_ctx.i++))') )
}) })
test('should wrap in unref if identifier is setup-maybe-ref w/ inline: true', () => { test('should wrap in unref if identifier is setup-maybe-ref w/ inline: true', () => {
@ -192,49 +192,49 @@ describe('v-on', () => {
expect(vaporHelpers).contains('unref') expect(vaporHelpers).contains('unref')
expect(helpers.size).toBe(0) expect(helpers.size).toBe(0)
expect(code).contains( expect(code).contains(
'_on(n0, "click", () => $event => (x.value=_unref(y)))', '_recordMetadata(n0, "events", "click", _eventHandler(() => $event => (x.value=_unref(y))))',
) )
expect(code).contains('_on(n1, "click", () => $event => (x.value++))')
expect(code).contains( expect(code).contains(
'_on(n2, "click", () => $event => ({ x: x.value } = _unref(y)))', '_recordMetadata(n1, "events", "click", _eventHandler(() => $event => (x.value++)))',
)
expect(code).contains(
'_recordMetadata(n2, "events", "click", _eventHandler(() => $event => ({ x: x.value } = _unref(y))))',
) )
}) })
test('should handle multiple inline statement', () => { test('should handle multiple inline statement', () => {
const { ir, code } = compileWithVOn(`<div @click="foo();bar()"/>`) const { ir, code } = compileWithVOn(`<div @click="foo();bar()"/>`)
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: 'foo();bar()' }, value: { content: 'foo();bar()' },
}, },
]) ])
expect(code).matchSnapshot()
// should wrap with `{` for multiple statements // should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is // in this case the return value is discarded and the behavior is
// consistent with 2.x // consistent with 2.x
expect(code).contains( expect(code).contains(
'_on(n0, "click", () => $event => {_ctx.foo();_ctx.bar()})', `_recordMetadata(n0, "events", "click", _eventHandler(() => $event => {_ctx.foo();_ctx.bar()}))`,
) )
}) })
test('should handle multi-line statement', () => { test('should handle multi-line statement', () => {
const { code, ir } = compileWithVOn(`<div @click="\nfoo();\nbar()\n"/>`) const { code, ir } = compileWithVOn(`<div @click="\nfoo();\nbar()\n"/>`)
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: '\nfoo();\nbar()\n' }, value: { content: '\nfoo();\nbar()\n' },
}, },
]) ])
expect(code).matchSnapshot()
// should wrap with `{` for multiple statements // should wrap with `{` for multiple statements
// in this case the return value is discarded and the behavior is // in this case the return value is discarded and the behavior is
// consistent with 2.x // consistent with 2.x
expect(code).contains( expect(code).contains(
'_on(n0, "click", () => $event => {\n_ctx.foo();\n_ctx.bar()\n})', '_recordMetadata(n0, "events", "click", _eventHandler(() => $event => {\n_ctx.foo();\n_ctx.bar()\n})',
) )
}) })
@ -243,17 +243,16 @@ describe('v-on', () => {
prefixIdentifiers: true, prefixIdentifiers: true,
}) })
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: 'foo($event)' }, value: { content: 'foo($event)' },
}, },
]) ])
expect(code).matchSnapshot()
// should NOT prefix $event // should NOT prefix $event
expect(code).contains( expect(code).contains(
'_on(n0, "click", () => $event => (_ctx.foo($event)))', '_recordMetadata(n0, "events", "click", _eventHandler(() => $event => (_ctx.foo($event))))',
) )
}) })
@ -262,32 +261,32 @@ describe('v-on', () => {
prefixIdentifiers: true, prefixIdentifiers: true,
}) })
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: 'foo($event);bar()' }, value: { content: 'foo($event);bar()' },
}, },
]) ])
expect(code).matchSnapshot()
// should NOT prefix $event // should NOT prefix $event
expect(code).contains( expect(code).contains(
'_on(n0, "click", () => $event => {_ctx.foo($event);_ctx.bar()})', '_recordMetadata(n0, "events", "click", _eventHandler(() => $event => {_ctx.foo($event);_ctx.bar()}))',
) )
}) })
test('should NOT wrap as function if expression is already function expression', () => { test('should NOT wrap as function if expression is already function expression', () => {
const { code, ir } = compileWithVOn(`<div @click="$event => foo($event)"/>`) const { code, ir } = compileWithVOn(`<div @click="$event => foo($event)"/>`)
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: '$event => foo($event)' }, value: { content: '$event => foo($event)' },
}, },
]) ])
expect(code).contains(
expect(code).matchSnapshot() '_recordMetadata(n0, "events", "click", _eventHandler(() => $event => _ctx.foo($event)))',
expect(code).contains('_on(n0, "click", () => $event => _ctx.foo($event))') )
}) })
test('should NOT wrap as function if expression is already function expression (with Typescript)', () => { test('should NOT wrap as function if expression is already function expression (with Typescript)', () => {
@ -296,16 +295,15 @@ describe('v-on', () => {
{ expressionPlugins: ['typescript'] }, { expressionPlugins: ['typescript'] },
) )
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: '(e: any): any => foo(e)' }, value: { content: '(e: any): any => foo(e)' },
}, },
]) ])
expect(code).matchSnapshot()
expect(code).contains( expect(code).contains(
'_on(n0, "click", () => (e: any): any => _ctx.foo(e))', '_recordMetadata(n0, "events", "click", _eventHandler(() => (e: any): any => _ctx.foo(e)))',
) )
}) })
@ -318,6 +316,7 @@ describe('v-on', () => {
"/>`, "/>`,
) )
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
@ -330,8 +329,6 @@ describe('v-on', () => {
}, },
}, },
]) ])
expect(code).matchSnapshot()
}) })
test('should NOT add a prefix to $event if the expression is a function expression', () => { test('should NOT add a prefix to $event if the expression is a function expression', () => {
@ -372,7 +369,9 @@ describe('v-on', () => {
]) ])
expect(code).matchSnapshot() expect(code).matchSnapshot()
expect(code).contains(`_on(n0, "click", () => _ctx.a['b' + _ctx.c])`) expect(code).contains(
`_recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.a['b' + _ctx.c]))`,
)
}) })
test('function expression w/ prefixIdentifiers: true', () => { test('function expression w/ prefixIdentifiers: true', () => {
@ -380,15 +379,16 @@ describe('v-on', () => {
prefixIdentifiers: true, prefixIdentifiers: true,
}) })
expect(code).matchSnapshot()
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
type: IRNodeTypes.SET_EVENT, type: IRNodeTypes.SET_EVENT,
value: { content: `e => foo(e)` }, value: { content: `e => foo(e)` },
}, },
]) ])
expect(code).contains(
expect(code).matchSnapshot() '_recordMetadata(n0, "events", "click", _eventHandler(() => e => _ctx.foo(e)))',
expect(code).contains('_on(n0, "click", () => e => _ctx.foo(e))') )
}) })
test('should error if no expression AND no modifier', () => { test('should error if no expression AND no modifier', () => {
@ -423,6 +423,7 @@ describe('v-on', () => {
}, },
) )
expect(code).matchSnapshot()
expect(vaporHelpers).contains('on') expect(vaporHelpers).contains('on')
expect(ir.block.operation).toMatchObject([ expect(ir.block.operation).toMatchObject([
{ {
@ -438,12 +439,13 @@ describe('v-on', () => {
options: ['capture', 'once'], options: ['capture', 'once'],
}, },
keyOverride: undefined, keyOverride: undefined,
delegate: false,
}, },
]) ])
expect(code).contains(
expect(code).matchSnapshot() '_on(n0, "click", () => _ctx.test, { capture: true, once: true }, {',
)
expect(code).contains('modifiers: ["stop", "prevent"]') expect(code).contains('modifiers: ["stop", "prevent"]')
expect(code).contains('{ capture: true, once: true }')
}) })
test('should support multiple events and modifiers options w/ prefixIdentifiers: true', () => { test('should support multiple events and modifiers options w/ prefixIdentifiers: true', () => {
@ -494,10 +496,14 @@ describe('v-on', () => {
]) ])
expect(code).matchSnapshot() expect(code).matchSnapshot()
expect(code).contains(`_on(n0, "click", () => _ctx.test, undefined`) expect(code).contains(
`_recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.test, {`,
)
expect(code).contains(`modifiers: ["stop"]`) expect(code).contains(`modifiers: ["stop"]`)
expect(code).contains(`_on(n0, "keyup", () => _ctx.test, undefined`) expect(code).contains(
`_recordMetadata(n0, "events", "keyup", _eventHandler(() => _ctx.test, {`,
)
expect(code).contains(`keys: ["enter"]`) expect(code).contains(`keys: ["enter"]`)
}) })
@ -680,6 +686,22 @@ describe('v-on', () => {
}) })
expect(code).matchSnapshot() expect(code).matchSnapshot()
expect(code).contains(`_on(n0, "click", () => _ctx.foo.bar)`) expect(code).contains(
`_recordMetadata(n0, "events", "click", _eventHandler(() => _ctx.foo.bar))`,
)
})
test('should delegate event', () => {
const { code, ir, vaporHelpers } = compileWithVOn(`<div @click="test"/>`)
expect(code).matchSnapshot()
expect(code).contains('_delegateEvents("click")')
expect(vaporHelpers).contains('delegateEvents')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.SET_EVENT,
delegate: true,
},
])
}) })
}) })

View File

@ -14,6 +14,7 @@ import {
LF, LF,
NEWLINE, NEWLINE,
buildCodeFragment, buildCodeFragment,
genCall,
genCodeFragment, genCodeFragment,
} from './generators/utils' } from './generators/utils'
@ -39,6 +40,8 @@ export class CodegenContext {
return `_${name}` return `_${name}`
} }
delegates = new Set<string>()
identifiers: Record<string, string[]> = Object.create(null) identifiers: Record<string, string[]> = Object.create(null)
withId = <T>(fn: () => T, map: Record<string, string | null>): T => { withId = <T>(fn: () => T, map: Record<string, string | null>): T => {
const { identifiers } = this const { identifiers } = this
@ -127,10 +130,11 @@ export function generate(
push('}') push('}')
} }
const deligates = genDeligates(context)
// TODO source map? // TODO source map?
const templates = genTemplates(ir.template, context) const templates = genTemplates(ir.template, context)
const imports = genHelperImports(context) const imports = genHelperImports(context)
const preamble = imports + templates const preamble = imports + templates + deligates
const newlineCount = [...preamble].filter(c => c === '\n').length const newlineCount = [...preamble].filter(c => c === '\n').length
if (newlineCount && !isSetupInlined) { if (newlineCount && !isSetupInlined) {
@ -152,6 +156,15 @@ export function generate(
} }
} }
function genDeligates({ delegates, vaporHelper }: CodegenContext) {
return delegates.size
? genCall(
vaporHelper('delegateEvents'),
...Array.from(delegates).map(v => `"${v}"`),
).join('') + '\n'
: ''
}
function genHelperImports({ helpers, vaporHelpers, options }: CodegenContext) { function genHelperImports({ helpers, vaporHelpers, options }: CodegenContext) {
let imports = '' let imports = ''
if (helpers.size) { if (helpers.size) {

View File

@ -1,4 +1,4 @@
import { isMemberExpression } from '@vue/compiler-dom' import { fnExpRE, isMemberExpression } from '@vue/compiler-dom'
import type { CodegenContext } from '../generate' import type { CodegenContext } from '../generate'
import type { SetEventIRNode } from '../ir' import type { SetEventIRNode } from '../ir'
import { genExpression } from './expression' import { genExpression } from './expression'
@ -11,10 +11,6 @@ import {
genCall, genCall,
} from './utils' } from './utils'
// TODO: share this with compiler-core
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
export function genSetEvent( export function genSetEvent(
oper: SetEventIRNode, oper: SetEventIRNode,
context: CodegenContext, context: CodegenContext,
@ -25,6 +21,22 @@ export function genSetEvent(
const name = genName() const name = genName()
const handler = genEventHandler() const handler = genEventHandler()
const modifierOptions = genModifierOptions() const modifierOptions = genModifierOptions()
if (oper.delegate) {
// oper.key is static
context.delegates.add(oper.key.content)
return [
NEWLINE,
...genCall(
vaporHelper('recordMetadata'),
`n${oper.element}`,
'"events"',
name,
genCall(vaporHelper('eventHandler'), handler, modifierOptions),
),
]
}
const handlerOptions = options.length const handlerOptions = options.length
? `{ ${options.map(v => `${v}: true`).join(', ')} }` ? `{ ${options.map(v => `${v}: true`).join(', ')} }`
: modifierOptions : modifierOptions
@ -45,8 +57,8 @@ export function genSetEvent(
function genName(): CodeFragment[] { function genName(): CodeFragment[] {
const expr = genExpression(oper.key, context) const expr = genExpression(oper.key, context)
// TODO unit test
if (oper.keyOverride) { if (oper.keyOverride) {
// TODO unit test
const find = JSON.stringify(oper.keyOverride[0]) const find = JSON.stringify(oper.keyOverride[0])
const replacement = JSON.stringify(oper.keyOverride[1]) const replacement = JSON.stringify(oper.keyOverride[1])
const wrapped: CodeFragment[] = ['(', ...expr, ')'] const wrapped: CodeFragment[] = ['(', ...expr, ')']

View File

@ -116,6 +116,7 @@ export interface SetEventIRNode extends BaseIRNode {
nonKeys: string[] nonKeys: string[]
} }
keyOverride?: KeyOverride keyOverride?: KeyOverride
delegate: boolean
} }
export interface SetHtmlIRNode extends BaseIRNode { export interface SetHtmlIRNode extends BaseIRNode {

View File

@ -2,9 +2,16 @@ import { ErrorCodes, createCompilerError } from '@vue/compiler-dom'
import type { DirectiveTransform } from '../transform' import type { DirectiveTransform } from '../transform'
import { IRNodeTypes, type KeyOverride, type SetEventIRNode } from '../ir' import { IRNodeTypes, type KeyOverride, type SetEventIRNode } from '../ir'
import { resolveModifiers } from '@vue/compiler-dom' import { resolveModifiers } from '@vue/compiler-dom'
import { extend } from '@vue/shared' import { extend, makeMap } from '@vue/shared'
import { resolveExpression } from '../utils' import { resolveExpression } from '../utils'
const delegatedEvents = /*#__PURE__*/ makeMap(
'beforeinput,click,dblclick,contextmenu,focusin,focusout,input,keydown,' +
'keyup,mousedown,mousemove,mouseout,mouseover,mouseup,pointerdown,' +
'pointermove,pointerout,pointerover,pointerup,touchend,touchmove,' +
'touchstart',
)
export const transformVOn: DirectiveTransform = (dir, node, context) => { export const transformVOn: DirectiveTransform = (dir, node, context) => {
let { arg, exp, loc, modifiers } = dir let { arg, exp, loc, modifiers } = dir
if (!exp && !modifiers.length) { if (!exp && !modifiers.length) {
@ -29,6 +36,8 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
let keyOverride: KeyOverride | undefined let keyOverride: KeyOverride | undefined
const isStaticClick = arg.isStatic && arg.content.toLowerCase() === 'click' const isStaticClick = arg.isStatic && arg.content.toLowerCase() === 'click'
const delegate =
arg.isStatic && !eventOptionModifiers.length && delegatedEvents(arg.content)
// normalize click.right and click.middle since they don't actually fire // normalize click.right and click.middle since they don't actually fire
if (nonKeyModifiers.includes('middle')) { if (nonKeyModifiers.includes('middle')) {
@ -60,6 +69,7 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
options: eventOptionModifiers, options: eventOptionModifiers,
}, },
keyOverride, keyOverride,
delegate,
} }
context.registerEffect([arg], [operation]) context.registerEffect([arg], [operation])

View File

@ -4,7 +4,7 @@ import {
onEffectCleanup, onEffectCleanup,
onScopeDispose, onScopeDispose,
} from '@vue/reactivity' } from '@vue/reactivity'
import { recordMetadata } from '../metadata' import { getMetadata, recordMetadata } from '../metadata'
import { withKeys, withModifiers } from '@vue/runtime-dom' import { withKeys, withModifiers } from '@vue/runtime-dom'
export function addEventListener( export function addEventListener(
@ -17,25 +17,19 @@ export function addEventListener(
return () => el.removeEventListener(event, handler, options) return () => el.removeEventListener(event, handler, options)
} }
interface ModifierOptions {
modifiers?: string[]
keys?: string[]
}
export function on( export function on(
el: HTMLElement, el: HTMLElement,
event: string, event: string,
handlerGetter: () => undefined | ((...args: any[]) => any), handlerGetter: () => undefined | ((...args: any[]) => any),
options?: AddEventListenerOptions, options?: AddEventListenerOptions,
{ modifiers, keys }: { modifiers?: string[]; keys?: string[] } = {}, modifierOptions?: ModifierOptions,
) { ) {
const handler = (...args: any[]) => { const handler = eventHandler(handlerGetter, modifierOptions)
let handler = handlerGetter()
if (!handler) return
if (modifiers) {
handler = withModifiers(handler, modifiers)
}
if (keys) {
handler = withKeys(handler, keys)
}
handler && handler(...args)
}
recordMetadata(el, 'events', event, handler) recordMetadata(el, 'events', event, handler)
const cleanup = addEventListener(el, event, handler, options) const cleanup = addEventListener(el, event, handler, options)
@ -48,3 +42,64 @@ export function on(
onScopeDispose(cleanup) onScopeDispose(cleanup)
} }
} }
export function eventHandler(
getter: () => undefined | ((...args: any[]) => any),
{ modifiers, keys }: ModifierOptions = {},
) {
return (...args: any[]) => {
let handler = getter()
if (!handler) return
if (modifiers) {
handler = withModifiers(handler, modifiers)
}
if (keys) {
handler = withKeys(handler, keys)
}
handler && handler(...args)
}
}
/**
* Event delegation borrowed from solid
*/
const delegatedEvents = Object.create(null)
export const delegateEvents = (...names: string[]) => {
for (const name of names) {
if (!delegatedEvents[name]) {
delegatedEvents[name] = true
// eslint-disable-next-line no-restricted-globals
document.addEventListener(name, delegatedEventHandler)
}
}
}
const delegatedEventHandler = (e: Event) => {
let node = ((e.composedPath && e.composedPath()[0]) || e.target) as any
if (e.target !== node) {
Object.defineProperty(e, 'target', {
configurable: true,
value: node,
})
}
Object.defineProperty(e, 'currentTarget', {
configurable: true,
get() {
// eslint-disable-next-line no-restricted-globals
return node || document
},
})
while (node !== null) {
const handler = getMetadata(node).events[e.type] as (...args: any[]) => any
if (handler && !node.disabled) {
handler(e)
if (e.cancelBubble) return
}
node =
node.host && node.host !== node && node.host instanceof Node
? node.host
: node.parentNode
}
}

View File

@ -57,6 +57,7 @@ export * from './apiLifecycle'
export * from './if' export * from './if'
export * from './for' export * from './for'
export { defineComponent } from './apiDefineComponent' export { defineComponent } from './apiDefineComponent'
export { recordMetadata } from './metadata'
export * from './directives/vShow' export * from './directives/vShow'
export * from './directives/vModel' export * from './directives/vModel'