Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,28 @@ export function render(_ctx) {
}"
`;

exports[`compiler: element transform > component > props merging: event handlers 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';

export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const n0 = _createComponentWithFallback(_component_Foo, { onClick: () => [_ctx.a, _ctx.b] }, null, true)
return n0
}"
`;

exports[`compiler: element transform > component > props merging: inline event handlers 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';

export function render(_ctx) {
const _component_Foo = _resolveComponent("Foo")
const _on_click = e => _ctx.a(e)
const _on_click1 = e => _ctx.b(e)
const n0 = _createComponentWithFallback(_component_Foo, { onClick: () => [_on_click, _on_click1] }, null, true)
return n0
}"
`;

exports[`compiler: element transform > component > resolve component from setup bindings (inline const) 1`] = `
"
const n0 = _createComponent(Example, null, null, true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,26 +328,22 @@ describe('compiler: element transform', () => {
})
})

test.todo('props merging: event handlers', () => {
const { code, ir } = compileWithElementTransform(
test('props merging: event handlers', () => {
const { code } = compileWithElementTransform(
`<Foo @click.foo="a" @click.bar="b" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('onClick: () => [_ctx.a, _ctx.b]')
expect(ir.block.operation).toMatchObject([
{
type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo',
props: [
[
{
key: { content: 'onClick', isStatic: true },
values: [{ content: 'a' }, { content: 'b' }],
},
],
],
},
])
})

test('props merging: inline event handlers', () => {
const { code } = compileWithElementTransform(
`<Foo @click.foo="e => a(e)" @click.bar="e => b(e)" />`,
)
expect(code).toMatchSnapshot()
expect(code).contains('const _on_click = e => _ctx.a(e)')
expect(code).contains('const _on_click1 = e => _ctx.b(e)')
expect(code).contains('onClick: () => [_on_click, _on_click1]')
})

test.todo('props merging: style', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler-vapor/src/generators/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function genCreateComponent(

const inlineHandlers: CodeFragment[] = handlers.reduce<CodeFragment[]>(
(acc, { name, value }: InlineHandler) => {
const handler = genEventHandler(context, value, undefined, false)
const handler = genEventHandler(context, [value], undefined, false)
return [...acc, `const ${name} = `, ...handler, NEWLINE]
},
[],
Expand Down Expand Up @@ -226,7 +226,7 @@ function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
...(prop.handler
? genEventHandler(
context,
prop.values[0],
prop.values,
prop.handlerModifiers,
true /* wrap handlers passed to components */,
)
Expand Down
93 changes: 52 additions & 41 deletions packages/compiler-vapor/src/generators/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function genSetEvent(
const name = genName()
const handler = [
`${context.helper('createInvoker')}(`,
...genEventHandler(context, value, modifiers),
...genEventHandler(context, [value], modifiers),
`)`,
]
const eventOptions = genEventOptions()
Expand Down Expand Up @@ -112,57 +112,68 @@ export function genSetDynamicEvents(

export function genEventHandler(
context: CodegenContext,
value: SimpleExpressionNode | undefined,
values: (SimpleExpressionNode | undefined)[] | undefined,
modifiers: {
nonKeys: string[]
keys: string[]
} = { nonKeys: [], keys: [] },
// passed as component prop - need additional wrap
extraWrap: boolean = false,
): CodeFragment[] {
let handlerExp: CodeFragment[] = [`() => {}`]
if (value && value.content.trim()) {
// Determine how the handler should be wrapped so it always reference the
// latest value when invoked.
if (isMemberExpression(value, context.options)) {
// e.g. @click="foo.bar"
handlerExp = genExpression(value, context)
if (!isConstantBinding(value, context) && !extraWrap) {
// non constant, wrap with invocation as `e => foo.bar(e)`
// when passing as component handler, access is always dynamic so we
// can skip this
const isTSNode = value.ast && TS_NODE_TYPES.includes(value.ast.type)
handlerExp = [
`e => `,
isTSNode ? '(' : '',
...handlerExp,
isTSNode ? ')' : '',
`(e)`,
]
let handlerExp: CodeFragment[] = []
if (values) {
values.forEach((value, index) => {
let exp: CodeFragment[] = []
if (value && value.content.trim()) {
// Determine how the handler should be wrapped so it always reference the
// latest value when invoked.
if (isMemberExpression(value, context.options)) {
// e.g. @click="foo.bar"
exp = genExpression(value, context)
if (!isConstantBinding(value, context) && !extraWrap) {
// non constant, wrap with invocation as `e => foo.bar(e)`
// when passing as component handler, access is always dynamic so we
// can skip this
const isTSNode = value.ast && TS_NODE_TYPES.includes(value.ast.type)
exp = [
`e => `,
isTSNode ? '(' : '',
...exp,
isTSNode ? ')' : '',
`(e)`,
]
}
} else if (isFnExpression(value, context.options)) {
// Fn expression: @click="e => foo(e)"
// no need to wrap in this case
exp = genExpression(value, context)
} else {
// inline statement
// @click="foo($event)" ---> $event => foo($event)
const referencesEvent = value.content.includes('$event')
const hasMultipleStatements = value.content.includes(`;`)
const expr = referencesEvent
? context.withId(() => genExpression(value, context), {
$event: null,
})
: genExpression(value, context)
exp = [
referencesEvent ? '$event => ' : '() => ',
hasMultipleStatements ? '{' : '(',
...expr,
hasMultipleStatements ? '}' : ')',
]
}
handlerExp = handlerExp.concat([index !== 0 ? ', ' : '', ...exp])
}
} else if (isFnExpression(value, context.options)) {
// Fn expression: @click="e => foo(e)"
// no need to wrap in this case
handlerExp = genExpression(value, context)
} else {
// inline statement
// @click="foo($event)" ---> $event => foo($event)
const referencesEvent = value.content.includes('$event')
const hasMultipleStatements = value.content.includes(`;`)
const expr = referencesEvent
? context.withId(() => genExpression(value, context), {
$event: null,
})
: genExpression(value, context)
handlerExp = [
referencesEvent ? '$event => ' : '() => ',
hasMultipleStatements ? '{' : '(',
...expr,
hasMultipleStatements ? '}' : ')',
]
})

if (values.length > 1) {
handlerExp = ['[', ...handlerExp, ']']
}
}

if (handlerExp.length === 0) handlerExp = ['() => {}']
const { keys, nonKeys } = modifiers
if (nonKeys.length)
handlerExp = genWithModifiers(context, handlerExp, nonKeys)
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ function dedupeProperties(results: DirectiveTransformResult[]): IRProp[] {
// prop names and event handler names can be the same but serve different purposes
// e.g. `:appear="true"` is a prop while `@appear="handler"` is an event handler
if (existing && existing.handler === prop.handler) {
if (name === 'style' || name === 'class') {
if (name === 'style' || name === 'class' || prop.handler) {
mergePropValues(existing, prop)
}
// unexpected duplicate, should have emitted error during parse
Expand Down
16 changes: 10 additions & 6 deletions packages/runtime-vapor/src/dom/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,18 @@ export function addEventListener(
export function on(
el: Element,
event: string,
handler: (e: Event) => any,
handler: (e: Event) => any | ((e: Event) => any)[],
options: AddEventListenerOptions & { effect?: boolean } = {},
): void {
addEventListener(el, event, handler, options)
if (options.effect) {
onEffectCleanup(() => {
el.removeEventListener(event, handler, options)
})
if (isArray(handler)) {
handler.forEach(fn => on(el, event, fn, options))
} else {
addEventListener(el, event, handler, options)
if (options.effect) {
onEffectCleanup(() => {
el.removeEventListener(event, handler, options)
})
}
}
}

Expand Down
Loading