Skip to content
This repository was archived by the owner on Jul 19, 2025. It is now read-only.

Commit e177e9b

Browse files
committed
wip
1 parent 7603455 commit e177e9b

File tree

4 files changed

+90
-106
lines changed

4 files changed

+90
-106
lines changed

packages/runtime-vapor/src/apiCreateFor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ function getItem(
397397
} else if (typeof source === 'number') {
398398
return [idx + 1, idx, undefined]
399399
} else if (isObject(source)) {
400-
if (source && source[Symbol.iterator as any]) {
400+
if (source[Symbol.iterator as any]) {
401401
source = Array.from(source as Iterable<any>)
402402
return [source[idx], idx, undefined]
403403
} else {

packages/runtime-vapor/src/directives.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,25 @@ import {
66
} from './component'
77
import { warn } from './warning'
88
import { normalizeBlock } from './dom/element'
9-
import { getCurrentScope } from '@vue/reactivity'
9+
import { type ShallowRef, getCurrentScope, shallowRef } from '@vue/reactivity'
1010
import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
1111

1212
export type DirectiveModifiers<M extends string = string> = Record<M, boolean>
1313

1414
export interface DirectiveBinding<T = any, V = any, M extends string = string> {
1515
instance: ComponentInternalInstance
16-
source?: () => V
16+
source: () => V
1717
arg?: string
1818
modifiers?: DirectiveModifiers<M>
1919
dir: Directive<T, V, M>
2020
}
2121

2222
export type DirectiveBindingsMap = Map<Node, DirectiveBinding[]>
2323

24-
export type Directive<
25-
T = any,
26-
V = any,
27-
M extends string = string,
28-
> = DirectiveHook<T, V, M>
29-
30-
export type DirectiveHook<
31-
T = any | null,
32-
V = any,
33-
M extends string = string,
34-
> = (node: T, binding: DirectiveBinding<T, V, M>) => void
24+
export type Directive<T = any, V = any, M extends string = string> = (
25+
node: ShallowRef<T>,
26+
binding: DirectiveBinding<T, V, M>,
27+
) => void
3528

3629
export function validateDirectiveName(name: string): void {
3730
if (isBuiltInDirective(name)) {
@@ -77,7 +70,7 @@ export function withDirectives<T extends ComponentInternalInstance | Node>(
7770
}
7871

7972
for (const directive of directives) {
80-
let [dir, source, arg, modifiers] = directive
73+
let [dir, source = () => undefined, arg, modifiers] = directive
8174
if (!dir) continue
8275

8376
const binding: DirectiveBinding = {
@@ -89,7 +82,7 @@ export function withDirectives<T extends ComponentInternalInstance | Node>(
8982
}
9083

9184
callWithAsyncErrorHandling(dir, instance, VaporErrorCodes.DIRECTIVE_HOOK, [
92-
node,
85+
shallowRef(node),
9386
binding,
9487
])
9588
}

packages/runtime-vapor/src/directives/vModel.ts

Lines changed: 77 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ import {
66
looseIndexOf,
77
looseToNumber,
88
} from '@vue/shared'
9-
import type {
10-
DirectiveBinding,
11-
DirectiveHook,
12-
DirectiveHookName,
13-
ObjectDirective,
14-
} from '../directives'
9+
import type { Directive } from '../directives'
1510
import { addEventListener } from '../dom/event'
1611
import { nextTick } from '../scheduler'
1712
import { warn } from '../warning'
1813
import { MetadataKind, getMetadata } from '../componentMetadata'
14+
import {
15+
onBeforeMount,
16+
onBeforeUnmount,
17+
onBeforeUpdate,
18+
onMounted,
19+
} from '../apiLifecycle'
20+
import { renderEffect } from '../renderEffect'
1921

2022
type AssignerFn = (value: any) => void
2123
function getModelAssigner(el: Element): AssignerFn {
@@ -41,12 +43,12 @@ const assigningMap = new WeakMap<HTMLElement, boolean>()
4143

4244
// We are exporting the v-model runtime directly as vnode hooks so that it can
4345
// be tree-shaken in case v-model is never used.
44-
export const vModelText: ObjectDirective<
46+
export const vModelText: Directive<
4547
HTMLInputElement | HTMLTextAreaElement,
4648
any,
4749
'lazy' | 'trim' | 'number'
48-
> = {
49-
beforeMount(el, { modifiers: { lazy, trim, number } = {} }) {
50+
> = ({ value: el }, { source, modifiers: { lazy, trim, number } = {} }) => {
51+
onBeforeMount(() => {
5052
const assigner = getModelAssigner(el)
5153
assignFnMap.set(el, assigner)
5254

@@ -78,12 +80,15 @@ export const vModelText: ObjectDirective<
7880
// fires "change" instead of "input" on autocomplete.
7981
addEventListener(el, 'change', onCompositionEnd)
8082
}
81-
},
82-
// set value on mounted so it's after min/max for type="range"
83-
mounted(el, { value }) {
83+
})
84+
85+
onMounted(() => {
86+
const value = source()
8487
el.value = value == null ? '' : value
85-
},
86-
beforeUpdate(el, { value, modifiers: { lazy, trim, number } = {} }) {
88+
})
89+
90+
renderEffect(() => {
91+
const value = source()
8792
assignFnMap.set(el, getModelAssigner(el))
8893

8994
// avoid clearing unresolved text. #2302
@@ -108,29 +113,34 @@ export const vModelText: ObjectDirective<
108113
}
109114

110115
el.value = newValue
111-
},
116+
})
112117
}
113118

114-
export const vModelRadio: ObjectDirective<HTMLInputElement> = {
115-
beforeMount(el, { value }) {
116-
el.checked = looseEqual(value, getValue(el))
119+
export const vModelRadio: Directive<HTMLInputElement> = (
120+
{ value: el },
121+
{ source },
122+
) => {
123+
onBeforeMount(() => {
124+
el.checked = looseEqual(source(), getValue(el))
117125
assignFnMap.set(el, getModelAssigner(el))
118126
addEventListener(el, 'change', () => {
119127
assignFnMap.get(el)!(getValue(el))
120128
})
121-
},
122-
beforeUpdate(el, { value, oldValue }) {
129+
})
130+
131+
renderEffect(() => {
132+
const value = source()
123133
assignFnMap.set(el, getModelAssigner(el))
124-
if (value !== oldValue) {
125-
el.checked = looseEqual(value, getValue(el))
126-
}
127-
},
134+
el.checked = looseEqual(value, getValue(el))
135+
})
128136
}
129137

130-
export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
131-
// <select multiple> value need to be deep traversed
132-
deep: true,
133-
beforeMount(el, { value, modifiers: { number = false } = {} }) {
138+
export const vModelSelect: Directive<HTMLSelectElement, any, 'number'> = (
139+
{ value: el },
140+
{ source, modifiers: { number = false } = {} },
141+
) => {
142+
onBeforeMount(() => {
143+
const value = source()
134144
const isSetModel = isSet(value)
135145
addEventListener(el, 'change', () => {
136146
const selectedVal = Array.prototype.filter
@@ -153,15 +163,17 @@ export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
153163
})
154164
assignFnMap.set(el, getModelAssigner(el))
155165
setSelected(el, value, number)
156-
},
157-
beforeUpdate(el) {
166+
})
167+
168+
onBeforeUnmount(() => {
158169
assignFnMap.set(el, getModelAssigner(el))
159-
},
160-
updated(el, { value, modifiers: { number = false } = {} }) {
170+
})
171+
172+
renderEffect(() => {
161173
if (!assigningMap.get(el)) {
162-
setSelected(el, value, number)
174+
setSelected(el, source(), number)
163175
}
164-
},
176+
})
165177
}
166178

167179
function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
@@ -223,27 +235,15 @@ function getCheckboxValue(el: HTMLInputElement, checked: boolean) {
223235
return checked
224236
}
225237

226-
const setChecked: DirectiveHook<HTMLInputElement> = (
227-
el,
228-
{ value, oldValue },
238+
export const vModelCheckbox: Directive<HTMLInputElement> = (
239+
{ value: el },
240+
{ source },
229241
) => {
230-
if (isArray(value)) {
231-
el.checked = looseIndexOf(value, getValue(el)) > -1
232-
} else if (isSet(value)) {
233-
el.checked = value.has(getValue(el))
234-
} else if (value !== oldValue) {
235-
el.checked = looseEqual(value, getCheckboxValue(el, true))
236-
}
237-
}
238-
239-
export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
240-
// #4096 array checkboxes need to be deep traversed
241-
deep: true,
242-
beforeMount(el, binding) {
242+
onBeforeMount(() => {
243243
assignFnMap.set(el, getModelAssigner(el))
244244

245245
addEventListener(el, 'change', () => {
246-
const modelValue = binding.value
246+
const modelValue = source()
247247
const elementValue = getValue(el)
248248
const checked = el.checked
249249
const assigner = assignFnMap.get(el)!
@@ -269,36 +269,38 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
269269
assigner(getCheckboxValue(el, checked))
270270
}
271271
})
272-
},
273-
// set initial checked on mount to wait for true-value/false-value
274-
mounted: setChecked,
275-
beforeUpdate(el, binding) {
272+
})
273+
274+
onMounted(() => {
275+
setChecked()
276+
})
277+
278+
onBeforeUpdate(() => {
276279
assignFnMap.set(el, getModelAssigner(el))
277-
setChecked(el, binding)
278-
},
280+
setChecked()
281+
})
282+
283+
function setChecked() {
284+
const value = source()
285+
if (isArray(value)) {
286+
el.checked = looseIndexOf(value, getValue(el)) > -1
287+
} else if (isSet(value)) {
288+
el.checked = value.has(getValue(el))
289+
} else {
290+
el.checked = looseEqual(value, getCheckboxValue(el, true))
291+
}
292+
}
279293
}
280294

281-
export const vModelDynamic: ObjectDirective<
295+
export const vModelDynamic: Directive<
282296
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
283-
> = {
284-
beforeMount(el, binding) {
285-
callModelHook(el, binding, 'beforeMount')
286-
},
287-
mounted(el, binding) {
288-
callModelHook(el, binding, 'mounted')
289-
},
290-
beforeUpdate(el, binding) {
291-
callModelHook(el, binding, 'beforeUpdate')
292-
},
293-
updated(el, binding) {
294-
callModelHook(el, binding, 'updated')
295-
},
297+
> = (elRef, binding) => {
298+
const type = elRef.value.getAttribute('type')
299+
const modelToUse = resolveDynamicModel(elRef.value.tagName, type)
300+
modelToUse(elRef, binding)
296301
}
297302

298-
function resolveDynamicModel(
299-
tagName: string,
300-
type: string | null,
301-
): ObjectDirective {
303+
function resolveDynamicModel(tagName: string, type: string | null): Directive {
302304
switch (tagName) {
303305
case 'SELECT':
304306
return vModelSelect
@@ -315,14 +317,3 @@ function resolveDynamicModel(
315317
}
316318
}
317319
}
318-
319-
function callModelHook(
320-
el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
321-
binding: DirectiveBinding,
322-
hook: DirectiveHookName,
323-
) {
324-
const type = el.getAttribute('type')
325-
const modelToUse = resolveDynamicModel(el.tagName, type)
326-
const fn = modelToUse[hook]
327-
fn && fn(el, binding)
328-
}

packages/runtime-vapor/src/directives/vShow.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ export interface VShowElement extends HTMLElement {
1111
[vShowHidden]: boolean
1212
}
1313

14-
export const vShow: Directive<VShowElement> = (node, { source }) => {
14+
export const vShow: Directive<VShowElement> = ({ value: el }, { source }) => {
1515
function getValue(): boolean {
1616
return source ? source() : false
1717
}
1818

1919
onBeforeMount(() => {
20-
node[vShowOriginalDisplay] =
21-
node.style.display === 'none' ? '' : node.style.display
20+
el[vShowOriginalDisplay] =
21+
el.style.display === 'none' ? '' : el.style.display
2222
})
2323

2424
renderEffect(() => {
25-
setDisplay(node, getValue())
25+
setDisplay(el, getValue())
2626
})
2727
}
2828

0 commit comments

Comments
 (0)