import { transformVBind } from './transforms/vBind'
import { transformVOn } from './transforms/vOn'
import { transformVShow } from './transforms/vShow'
+import { transformRef } from './transforms/transformRef'
import { transformInterpolation } from './transforms/transformInterpolation'
import type { HackOptions } from './ir'
prefixIdentifiers?: boolean,
): TransformPreset {
return [
- [transformOnce, transformInterpolation, transformElement],
+ [transformOnce, transformRef, transformInterpolation, transformElement],
{
bind: transformVBind,
on: transformVOn,
type SetEventIRNode,
type SetHtmlIRNode,
type SetPropIRNode,
+ type SetRefIRNode,
type SetTextIRNode,
type VaporHelper,
type WithDirectiveIRNode,
return genSetEvent(oper, context)
case IRNodeTypes.SET_HTML:
return genSetHtml(oper, context)
+ case IRNodeTypes.SET_REF:
+ return genSetRef(oper, context)
case IRNodeTypes.CREATE_TEXT_NODE:
return genCreateTextNode(oper, context)
case IRNodeTypes.INSERT_NODE:
)
}
+function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
+ const { newline, pushFnCall, vaporHelper } = context
+ newline()
+ pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
+ genExpression(oper.value, context),
+ )
+}
+
function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
SET_TEXT,
SET_EVENT,
SET_HTML,
+ SET_REF,
INSERT_NODE,
PREPEND_NODE,
value: IRExpression
}
+export interface SetRefIRNode extends BaseIRNode {
+ type: IRNodeTypes.SET_REF
+ element: number
+ value: IRExpression
+}
+
export interface CreateTextNodeIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_TEXT_NODE
id: number
| SetTextIRNode
| SetEventIRNode
| SetHtmlIRNode
+ | SetRefIRNode
| CreateTextNodeIRNode
| InsertNodeIRNode
| PrependNodeIRNode
ElementTypes,
NodeTypes,
} from '@vue/compiler-dom'
-import { isBuiltInDirective, isVoidTag } from '@vue/shared'
+import { isBuiltInDirective, isReservedProp, isVoidTag } from '@vue/shared'
import type { NodeTransform, TransformContext } from '../transform'
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'
context: TransformContext<ElementNode>,
): void {
const { name, loc } = prop
+ if (isReservedProp(name)) return
+
if (prop.type === NodeTypes.ATTRIBUTE) {
context.template += ` ${name}`
if (prop.value) context.template += `="${prop.value.content}"`
--- /dev/null
+import {
+ NodeTypes,
+ type SimpleExpressionNode,
+ findProp,
+} from '@vue/compiler-dom'
+import type { NodeTransform } from '../transform'
+import { type IRExpression, IRNodeTypes } from '../ir'
+import { normalizeBindShorthand } from './vBind'
+
+export const transformRef: NodeTransform = (node, context) => {
+ if (node.type !== NodeTypes.ELEMENT) return
+ const dir = findProp(node, 'ref', false, true)
+
+ if (!dir) return
+
+ let value: IRExpression
+ if (dir.type === NodeTypes.DIRECTIVE) {
+ value =
+ (dir.exp as SimpleExpressionNode | undefined) ||
+ normalizeBindShorthand(dir.arg as SimpleExpressionNode)
+ } else {
+ value = dir.value ? JSON.stringify(dir.value.content) : '""'
+ }
+
+ context.registerOperation({
+ type: IRNodeTypes.SET_REF,
+ element: context.reference(),
+ value,
+ loc: dir.loc,
+ })
+}
createCompilerError,
createSimpleExpression,
} from '@vue/compiler-core'
-import { camelize } from '@vue/shared'
+import { camelize, isReservedProp } from '@vue/shared'
import { IRNodeTypes } from '../ir'
import type { DirectiveTransform } from '../transform'
+export function normalizeBindShorthand(
+ arg: SimpleExpressionNode,
+): SimpleExpressionNode {
+ // shorthand syntax https://github.com/vuejs/core/pull/9451
+ const propName = camelize(arg.content)
+ const exp = createSimpleExpression(propName, false, arg.loc)
+ exp.ast = null
+ return exp
+}
+
export const transformVBind: DirectiveTransform = (dir, node, context) => {
let { arg, exp, loc, modifiers } = dir
// TODO support v-bind="{}"
return
}
- if (!exp) {
- // shorthand syntax https://github.com/vuejs/core/pull/9451
- const propName = camelize(arg.content)
- exp = createSimpleExpression(propName, false, arg.loc)
- exp.ast = null
- }
+ if (arg.isStatic && isReservedProp(arg.content)) return
+
+ if (!exp) exp = normalizeBindShorthand(arg)
let camel = false
if (modifiers.includes('camel')) {
import type { Block, ParentBlock } from './render'
export * from './dom/patchProp'
+export * from './dom/templateRef'
export function insert(block: Block, parent: Node, anchor: Node | null = null) {
// if (!isHydrating) {
--- /dev/null
+import { type Ref, type SchedulerJob, isRef } from '@vue/reactivity'
+import { currentInstance } from '../component'
+import { VaporErrorCodes, callWithErrorHandling } from '../errorHandling'
+import { hasOwn, isFunction, isString } from '@vue/shared'
+import { warn } from '../warning'
+import { queuePostRenderEffect } from '../scheduler'
+
+export type NodeRef = string | Ref | ((ref: Element) => void)
+
+/**
+ * Function for handling a template ref
+ */
+export function setRef(el: Element, ref: NodeRef) {
+ if (!currentInstance) return
+ const { setupState, isUnmounted } = currentInstance
+
+ if (isFunction(ref)) {
+ callWithErrorHandling(ref, currentInstance, VaporErrorCodes.FUNCTION_REF, [
+ el,
+ // refs,
+ ])
+ } else {
+ const _isString = isString(ref)
+ const _isRef = isRef(ref)
+
+ if (_isString || _isRef) {
+ const doSet = () => {
+ if (_isString) {
+ if (hasOwn(setupState, ref)) {
+ setupState[ref] = el
+ }
+ } else if (_isRef) {
+ ref.value = el
+ } else if (__DEV__) {
+ warn('Invalid template ref type:', ref, `(${typeof ref})`)
+ }
+ }
+ // #9908 ref on v-for mutates the same array for both mount and unmount
+ // and should be done together
+ if (isUnmounted /* || isVFor */) {
+ doSet()
+ } else {
+ // #1789: set new refs in a post job so that they don't get overwritten
+ // by unmounting ones.
+ ;(doSet as SchedulerJob).id = -1
+ queuePostRenderEffect(doSet)
+ }
+ } else if (__DEV__) {
+ warn('Invalid template ref type:', ref, `(${typeof ref})`)
+ }
+ }
+}
import { initProps } from './componentProps'
import { invokeDirectiveHook } from './directive'
import { insert, remove } from './dom'
+import { queuePostRenderEffect } from './scheduler'
export type Block = Node | Fragment | Block[]
export type ParentBlock = ParentNode | Node[]
instance.isMounted = true
// hook: mounted
- invokeDirectiveHook(instance, 'mounted')
- m && invokeArrayFns(m)
+ queuePostRenderEffect(() => {
+ invokeDirectiveHook(instance, 'mounted')
+ m && invokeArrayFns(m)
+ })
reset()
return instance
<script setup lang="ts">
-import { ref } from 'vue/vapor'
+import { onMounted, ref } from 'vue/vapor'
interface Task {
title: string
}
const tasks = ref<Task[]>([])
const value = ref('hello')
+const inputRef = ref<HTMLInputElement>()
function handleAdd() {
tasks.value.push({
// TODO: clear input
value.value = ''
}
+
+onMounted(() => {
+ console.log('onMounted')
+ console.log(inputRef.value)
+})
</script>
<template>
<li>
<input
type="text"
+ :ref="el => (inputRef = el)"
:value="value"
@input="evt => (value = evt.target.value)"
/>