Merge remote-tracking branch 'github/master' into changing_unwrap_ref
# Conflicts: # packages/reactivity/src/ref.ts # packages/runtime-core/__tests__/apiTemplateRef.spec.ts # packages/runtime-core/src/apiWatch.ts
This commit is contained in:
@@ -9,5 +9,5 @@ const RootComponent = {
|
||||
}
|
||||
}
|
||||
|
||||
createApp().mount(RootComponent, '#app')
|
||||
createApp(RootComponent).mount('#app')
|
||||
```
|
||||
|
||||
20
packages/runtime-dom/__tests__/customizedBuiltIn.spec.ts
Normal file
20
packages/runtime-dom/__tests__/customizedBuiltIn.spec.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { render, h } from '@vue/runtime-dom'
|
||||
|
||||
describe('customimized built-in elements support', () => {
|
||||
let createElement: jest.SpyInstance
|
||||
afterEach(() => {
|
||||
createElement.mockRestore()
|
||||
})
|
||||
|
||||
test('should created element with is option', () => {
|
||||
const root = document.createElement('div')
|
||||
createElement = jest.spyOn(document, 'createElement')
|
||||
render(h('button', { is: 'plastic-button' }), root)
|
||||
expect(createElement.mock.calls[0]).toMatchObject([
|
||||
'button',
|
||||
{ is: 'plastic-button' }
|
||||
])
|
||||
// should also render the attribute
|
||||
expect(root.innerHTML).toBe(`<button is="plastic-button"></button>`)
|
||||
})
|
||||
})
|
||||
12
packages/runtime-dom/__tests__/directives/vCloak.spec.ts
Normal file
12
packages/runtime-dom/__tests__/directives/vCloak.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { createApp } from '@vue/runtime-dom'
|
||||
|
||||
describe('vCloak', () => {
|
||||
test('should be removed after compile', () => {
|
||||
const root = document.createElement('div')
|
||||
root.setAttribute('v-cloak', '')
|
||||
createApp({
|
||||
render() {}
|
||||
}).mount(root)
|
||||
expect(root.hasAttribute('v-cloak')).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,12 @@
|
||||
import {
|
||||
createApp,
|
||||
h,
|
||||
render,
|
||||
nextTick,
|
||||
createComponent,
|
||||
defineComponent,
|
||||
vModelDynamic,
|
||||
withDirectives,
|
||||
VNode
|
||||
VNode,
|
||||
ref
|
||||
} from '@vue/runtime-dom'
|
||||
|
||||
const triggerEvent = (type: string, el: Element) => {
|
||||
@@ -20,16 +21,15 @@ const setValue = function(this: any, value: any) {
|
||||
this.value = value
|
||||
}
|
||||
|
||||
let app: any, root: any
|
||||
let root: any
|
||||
|
||||
beforeEach(() => {
|
||||
app = createApp()
|
||||
root = document.createElement('div') as any
|
||||
})
|
||||
|
||||
describe('vModel', () => {
|
||||
it('should work with text input', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -44,9 +44,9 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('input')
|
||||
const input = root.querySelector('input')!
|
||||
const data = root._vnode.component.data
|
||||
|
||||
input.value = 'foo'
|
||||
@@ -59,8 +59,74 @@ describe('vModel', () => {
|
||||
expect(input.value).toEqual('bar')
|
||||
})
|
||||
|
||||
it('should work with multiple listeners', async () => {
|
||||
const spy = jest.fn()
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
render() {
|
||||
return [
|
||||
withVModel(
|
||||
h('input', {
|
||||
'onUpdate:modelValue': [setValue.bind(this), spy]
|
||||
}),
|
||||
this.value
|
||||
)
|
||||
]
|
||||
}
|
||||
})
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('input')!
|
||||
const data = root._vnode.component.data
|
||||
|
||||
input.value = 'foo'
|
||||
triggerEvent('input', input)
|
||||
await nextTick()
|
||||
expect(data.value).toEqual('foo')
|
||||
expect(spy).toHaveBeenCalledWith('foo')
|
||||
})
|
||||
|
||||
it('should work with updated listeners', async () => {
|
||||
const spy1 = jest.fn()
|
||||
const spy2 = jest.fn()
|
||||
const toggle = ref(true)
|
||||
|
||||
const component = defineComponent({
|
||||
render() {
|
||||
return [
|
||||
withVModel(
|
||||
h('input', {
|
||||
'onUpdate:modelValue': toggle.value ? spy1 : spy2
|
||||
}),
|
||||
'foo'
|
||||
)
|
||||
]
|
||||
}
|
||||
})
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('input')!
|
||||
|
||||
input.value = 'foo'
|
||||
triggerEvent('input', input)
|
||||
await nextTick()
|
||||
expect(spy1).toHaveBeenCalledWith('foo')
|
||||
|
||||
// udpate listener
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
|
||||
input.value = 'bar'
|
||||
triggerEvent('input', input)
|
||||
await nextTick()
|
||||
expect(spy1).not.toHaveBeenCalledWith('bar')
|
||||
expect(spy2).toHaveBeenCalledWith('bar')
|
||||
})
|
||||
|
||||
it('should work with textarea', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -75,7 +141,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('textarea')
|
||||
const data = root._vnode.component.data
|
||||
@@ -91,7 +157,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should support modifiers', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { number: null, trim: null, lazy: null }
|
||||
},
|
||||
@@ -136,7 +202,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const number = root.querySelector('.number')
|
||||
const trim = root.querySelector('.trim')
|
||||
@@ -160,7 +226,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should work with checkbox', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -176,7 +242,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('input')
|
||||
const data = root._vnode.component.data
|
||||
@@ -201,7 +267,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should work with checkbox and true-value/false-value', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -219,7 +285,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('input')
|
||||
const data = root._vnode.component.data
|
||||
@@ -244,7 +310,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should work with checkbox and true-value/false-value with object values', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -262,7 +328,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('input')
|
||||
const data = root._vnode.component.data
|
||||
@@ -287,7 +353,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it(`should support array as a checkbox model`, async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: [] }
|
||||
},
|
||||
@@ -314,7 +380,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const foo = root.querySelector('.foo')
|
||||
const bar = root.querySelector('.bar')
|
||||
@@ -357,7 +423,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should work with radio', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -384,7 +450,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const foo = root.querySelector('.foo')
|
||||
const bar = root.querySelector('.bar')
|
||||
@@ -417,7 +483,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should work with single select', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: null }
|
||||
},
|
||||
@@ -437,7 +503,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('select')
|
||||
const foo = root.querySelector('option[value=foo]')
|
||||
@@ -473,7 +539,7 @@ describe('vModel', () => {
|
||||
})
|
||||
|
||||
it('should work with multiple select', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: [] }
|
||||
},
|
||||
@@ -494,7 +560,7 @@ describe('vModel', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const input = root.querySelector('select')
|
||||
const foo = root.querySelector('option[value=foo]')
|
||||
|
||||
@@ -22,9 +22,9 @@ describe('runtime-dom: v-on directive', () => {
|
||||
const child = document.createElement('input')
|
||||
parent.appendChild(child)
|
||||
const childNextValue = withModifiers(jest.fn(), ['prevent', 'stop'])
|
||||
patchEvent(child, 'click', null, childNextValue, null)
|
||||
patchEvent(child, 'onClick', null, childNextValue, null)
|
||||
const parentNextValue = jest.fn()
|
||||
patchEvent(parent, 'click', null, parentNextValue, null)
|
||||
patchEvent(parent, 'onClick', null, parentNextValue, null)
|
||||
expect(triggerEvent(child, 'click').defaultPrevented).toBe(true)
|
||||
expect(parentNextValue).not.toBeCalled()
|
||||
})
|
||||
@@ -35,7 +35,7 @@ describe('runtime-dom: v-on directive', () => {
|
||||
parent.appendChild(child)
|
||||
const fn = jest.fn()
|
||||
const handler = withModifiers(fn, ['self'])
|
||||
patchEvent(parent, 'click', null, handler, null)
|
||||
patchEvent(parent, 'onClick', null, handler, null)
|
||||
triggerEvent(child, 'click')
|
||||
expect(fn).not.toBeCalled()
|
||||
})
|
||||
@@ -48,7 +48,7 @@ describe('runtime-dom: v-on directive', () => {
|
||||
'esc',
|
||||
'arrow-left'
|
||||
])
|
||||
patchEvent(el, 'keyup', null, nextValue, null)
|
||||
patchEvent(el, 'onKeyup', null, nextValue, null)
|
||||
|
||||
triggerEvent(el, 'keyup', e => (e.key = 'a'))
|
||||
expect(fn).not.toBeCalled()
|
||||
@@ -77,7 +77,7 @@ describe('runtime-dom: v-on directive', () => {
|
||||
// Case 1: <div @keyup.exact="test"/>
|
||||
const fn1 = jest.fn()
|
||||
const next1 = withModifiers(fn1, ['exact'])
|
||||
patchEvent(el, 'keyup', null, next1, null)
|
||||
patchEvent(el, 'onKeyup', null, next1, null)
|
||||
triggerEvent(el, 'keyup')
|
||||
expect(fn1.mock.calls.length).toBe(1)
|
||||
triggerEvent(el, 'keyup', e => (e.ctrlKey = true))
|
||||
@@ -85,7 +85,7 @@ describe('runtime-dom: v-on directive', () => {
|
||||
// Case 2: <div @keyup.ctrl.a.exact="test"/>
|
||||
const fn2 = jest.fn()
|
||||
const next2 = withKeys(withModifiers(fn2, ['ctrl', 'exact']), ['a'])
|
||||
patchEvent(el, 'keyup', null, next2, null)
|
||||
patchEvent(el, 'onKeyup', null, next2, null)
|
||||
triggerEvent(el, 'keyup', e => (e.key = 'a'))
|
||||
expect(fn2).not.toBeCalled()
|
||||
triggerEvent(el, 'keyup', e => {
|
||||
@@ -109,7 +109,7 @@ describe('runtime-dom: v-on directive', () => {
|
||||
const el = document.createElement('div')
|
||||
const fn = jest.fn()
|
||||
const handler = withModifiers(fn, [button])
|
||||
patchEvent(el, 'mousedown', null, handler, null)
|
||||
patchEvent(el, 'onMousedown', null, handler, null)
|
||||
buttons.filter(b => b !== button).forEach(button => {
|
||||
triggerEvent(el, 'mousedown', e => (e.button = buttonCodes[button]))
|
||||
})
|
||||
|
||||
@@ -1,25 +1,24 @@
|
||||
import {
|
||||
withDirectives,
|
||||
createComponent,
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
VNode
|
||||
} from '@vue/runtime-core'
|
||||
import { createApp, vShow } from '@vue/runtime-dom'
|
||||
import { render, vShow } from '@vue/runtime-dom'
|
||||
|
||||
const withVShow = (node: VNode, exp: any) =>
|
||||
withDirectives(node, [[vShow, exp]])
|
||||
|
||||
let app: any, root: any
|
||||
let root: any
|
||||
|
||||
beforeEach(() => {
|
||||
app = createApp()
|
||||
root = document.createElement('div') as any
|
||||
root = document.createElement('div')
|
||||
})
|
||||
|
||||
describe('runtime-dom: v-show directive', () => {
|
||||
test('should check show value is truthy', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: true }
|
||||
},
|
||||
@@ -27,7 +26,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
return [withVShow(h('div'), this.value)]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const $div = root.querySelector('div')
|
||||
|
||||
@@ -35,7 +34,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
})
|
||||
|
||||
test('should check show value is falsy', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: false }
|
||||
},
|
||||
@@ -43,7 +42,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
return [withVShow(h('div'), this.value)]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const $div = root.querySelector('div')
|
||||
|
||||
@@ -51,7 +50,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
})
|
||||
|
||||
it('should update show value changed', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: true }
|
||||
},
|
||||
@@ -59,7 +58,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
return [withVShow(h('div'), this.value)]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const $div = root.querySelector('div')
|
||||
const data = root._vnode.component.data
|
||||
@@ -100,7 +99,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
})
|
||||
|
||||
test('should respect display value in style attribute', async () => {
|
||||
const component = createComponent({
|
||||
const component = defineComponent({
|
||||
data() {
|
||||
return { value: true }
|
||||
},
|
||||
@@ -110,7 +109,7 @@ describe('runtime-dom: v-show directive', () => {
|
||||
]
|
||||
}
|
||||
})
|
||||
app.mount(component, root)
|
||||
render(h(component), root)
|
||||
|
||||
const $div = root.querySelector('div')
|
||||
const data = root._vnode.component.data
|
||||
|
||||
27
packages/runtime-dom/__tests__/modules/attrs.spec.ts
Normal file
27
packages/runtime-dom/__tests__/modules/attrs.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { patchAttr, xlinkNS } from '../../src/modules/attrs'
|
||||
|
||||
describe('attrs', () => {
|
||||
test('xlink attributes', () => {
|
||||
const el = document.createElementNS('http://www.w3.org/2000/svg', 'use')
|
||||
patchAttr(el, 'xlink:href', 'a', true)
|
||||
expect(el.getAttributeNS(xlinkNS, 'href')).toBe('a')
|
||||
patchAttr(el, 'xlink:href', null, true)
|
||||
expect(el.getAttributeNS(xlinkNS, 'href')).toBe(null)
|
||||
})
|
||||
|
||||
test('boolean attributes', () => {
|
||||
const el = document.createElement('input')
|
||||
patchAttr(el, 'readonly', true, false)
|
||||
expect(el.getAttribute('readonly')).toBe('')
|
||||
patchAttr(el, 'readonly', false, false)
|
||||
expect(el.getAttribute('readonly')).toBe(null)
|
||||
})
|
||||
|
||||
test('attributes', () => {
|
||||
const el = document.createElement('div')
|
||||
patchAttr(el, 'id', 'a', false)
|
||||
expect(el.getAttribute('id')).toBe('a')
|
||||
patchAttr(el, 'id', null, false)
|
||||
expect(el.getAttribute('id')).toBe(null)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
// https://github.com/vuejs/vue/blob/dev/test/unit/features/directives/class.spec.js
|
||||
|
||||
import { h, render, createComponent } from '../../src'
|
||||
import { h, render, defineComponent } from '../../src'
|
||||
|
||||
type ClassItem = {
|
||||
value: string | object | string[]
|
||||
@@ -70,13 +70,11 @@ describe('class', () => {
|
||||
|
||||
const childClass: ClassItem = { value: 'd' }
|
||||
const child = {
|
||||
props: {},
|
||||
render: () => h('div', { class: ['c', childClass.value] })
|
||||
}
|
||||
|
||||
const parentClass: ClassItem = { value: 'b' }
|
||||
const parent = {
|
||||
props: {},
|
||||
render: () => h(child, { class: ['a', parentClass.value] })
|
||||
}
|
||||
|
||||
@@ -100,29 +98,26 @@ describe('class', () => {
|
||||
})
|
||||
|
||||
test('class merge between multiple nested components sharing same element', () => {
|
||||
const component1 = createComponent({
|
||||
props: {},
|
||||
const component1 = defineComponent({
|
||||
render() {
|
||||
return this.$slots.default()[0]
|
||||
return this.$slots.default!()[0]
|
||||
}
|
||||
})
|
||||
|
||||
const component2 = createComponent({
|
||||
props: {},
|
||||
const component2 = defineComponent({
|
||||
render() {
|
||||
return this.$slots.default()[0]
|
||||
return this.$slots.default!()[0]
|
||||
}
|
||||
})
|
||||
|
||||
const component3 = createComponent({
|
||||
props: {},
|
||||
const component3 = defineComponent({
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: 'staticClass'
|
||||
},
|
||||
[this.$slots.default()]
|
||||
[this.$slots.default!()]
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { patchEvent } from '../../src/modules/events'
|
||||
import { nextTick } from '@vue/runtime-dom'
|
||||
|
||||
const timeout = () => new Promise(r => setTimeout(r))
|
||||
|
||||
describe(`events`, () => {
|
||||
it('should assign event handler', async () => {
|
||||
const el = document.createElement('div')
|
||||
const event = new Event('click')
|
||||
const fn = jest.fn()
|
||||
patchEvent(el, 'click', null, fn, null)
|
||||
patchEvent(el, 'onClick', null, fn, null)
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(fn).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
@@ -21,14 +22,14 @@ describe(`events`, () => {
|
||||
const event = new Event('click')
|
||||
const prevFn = jest.fn()
|
||||
const nextFn = jest.fn()
|
||||
patchEvent(el, 'click', null, prevFn, null)
|
||||
patchEvent(el, 'onClick', null, prevFn, null)
|
||||
el.dispatchEvent(event)
|
||||
patchEvent(el, 'click', prevFn, nextFn, null)
|
||||
await nextTick()
|
||||
patchEvent(el, 'onClick', prevFn, nextFn, null)
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(prevFn).toHaveBeenCalledTimes(1)
|
||||
expect(nextFn).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
@@ -38,9 +39,9 @@ describe(`events`, () => {
|
||||
const event = new Event('click')
|
||||
const fn1 = jest.fn()
|
||||
const fn2 = jest.fn()
|
||||
patchEvent(el, 'click', null, [fn1, fn2], null)
|
||||
patchEvent(el, 'onClick', null, [fn1, fn2], null)
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(fn1).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -49,10 +50,10 @@ describe(`events`, () => {
|
||||
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)
|
||||
patchEvent(el, 'onClick', null, fn, null)
|
||||
patchEvent(el, 'onClick', fn, null, null)
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(fn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
@@ -66,11 +67,11 @@ describe(`events`, () => {
|
||||
once: true
|
||||
}
|
||||
}
|
||||
patchEvent(el, 'click', null, nextValue, null)
|
||||
patchEvent(el, 'onClick', null, nextValue, null)
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
@@ -85,12 +86,12 @@ describe(`events`, () => {
|
||||
once: true
|
||||
}
|
||||
}
|
||||
patchEvent(el, 'click', null, prevFn, null)
|
||||
patchEvent(el, 'click', prevFn, nextValue, null)
|
||||
patchEvent(el, 'onClick', null, prevFn, null)
|
||||
patchEvent(el, 'onClick', prevFn, nextValue, null)
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(prevFn).not.toHaveBeenCalled()
|
||||
expect(nextFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
@@ -105,12 +106,30 @@ describe(`events`, () => {
|
||||
once: true
|
||||
}
|
||||
}
|
||||
patchEvent(el, 'click', null, nextValue, null)
|
||||
patchEvent(el, 'click', nextValue, null, null)
|
||||
patchEvent(el, 'onClick', null, nextValue, null)
|
||||
patchEvent(el, 'onClick', nextValue, null, null)
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
el.dispatchEvent(event)
|
||||
await nextTick()
|
||||
await timeout()
|
||||
expect(fn).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should assign native onclick attribute', async () => {
|
||||
const el = document.createElement('div')
|
||||
const event = new Event('click')
|
||||
const fn = ((window as any)._nativeClickSpy = jest.fn())
|
||||
|
||||
patchEvent(el, 'onclick', null, '_nativeClickSpy()' as any)
|
||||
el.dispatchEvent(event)
|
||||
await timeout()
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
|
||||
const fn2 = jest.fn()
|
||||
patchEvent(el, 'onclick', null, fn2)
|
||||
el.dispatchEvent(event)
|
||||
await timeout()
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
expect(fn2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -21,7 +21,7 @@ describe(`module style`, () => {
|
||||
|
||||
it('remove if falsy value', () => {
|
||||
const el = document.createElement('div')
|
||||
patchStyle(el, { color: 'red' }, { color: null })
|
||||
patchStyle(el, { color: 'red' }, { color: undefined })
|
||||
expect(el.style.cssText.replace(/\s/g, '')).toBe('')
|
||||
})
|
||||
|
||||
|
||||
900
packages/runtime-dom/jsx.d.ts
vendored
900
packages/runtime-dom/jsx.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.0.0-alpha.0",
|
||||
"version": "3.0.0-alpha.11",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
"types": "dist/runtime-dom.d.ts",
|
||||
"unpkg": "dist/runtime-dom.global.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"dist"
|
||||
],
|
||||
"types": "dist/runtime-dom.d.ts",
|
||||
"unpkg": "dist/runtime-dom.global.js",
|
||||
"sideEffects": false,
|
||||
"buildOptions": {
|
||||
"name": "VueDOMRuntime",
|
||||
"name": "VueRuntimeDOM",
|
||||
"formats": [
|
||||
"esm-bundler",
|
||||
"cjs",
|
||||
@@ -25,7 +25,7 @@
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/vue.git"
|
||||
"url": "git+https://github.com/vuejs/vue-next.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue"
|
||||
@@ -33,10 +33,12 @@
|
||||
"author": "Evan You",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/vue/issues"
|
||||
"url": "https://github.com/vuejs/vue-next/issues"
|
||||
},
|
||||
"homepage": "https://github.com/vuejs/vue/tree/dev/packages/runtime-dom#readme",
|
||||
"homepage": "https://github.com/vuejs/vue-next/tree/master/packages/runtime-dom#readme",
|
||||
"dependencies": {
|
||||
"@vue/runtime-core": "3.0.0-alpha.0"
|
||||
"@vue/shared": "3.0.0-alpha.11",
|
||||
"@vue/runtime-core": "3.0.0-alpha.11",
|
||||
"csstype": "^2.6.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ErrorCodes } from 'packages/runtime-core/src/errorHandling'
|
||||
const TRANSITION = 'transition'
|
||||
const ANIMATION = 'animation'
|
||||
|
||||
export interface TransitionProps extends BaseTransitionProps {
|
||||
export interface TransitionProps extends BaseTransitionProps<Element> {
|
||||
name?: string
|
||||
type?: typeof TRANSITION | typeof ANIMATION
|
||||
css?: boolean
|
||||
@@ -37,7 +37,7 @@ export const Transition: FunctionalComponent<TransitionProps> = (
|
||||
{ slots }
|
||||
) => h(BaseTransition, resolveTransitionProps(props), slots)
|
||||
|
||||
export const TransitionPropsValidators = {
|
||||
export const TransitionPropsValidators = (Transition.props = {
|
||||
...(BaseTransition as any).props,
|
||||
name: String,
|
||||
type: String,
|
||||
@@ -45,7 +45,7 @@ export const TransitionPropsValidators = {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
duration: Object,
|
||||
duration: [String, Number, Object],
|
||||
enterFromClass: String,
|
||||
enterActiveClass: String,
|
||||
enterToClass: String,
|
||||
@@ -55,11 +55,7 @@ export const TransitionPropsValidators = {
|
||||
leaveFromClass: String,
|
||||
leaveActiveClass: String,
|
||||
leaveToClass: String
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
Transition.props = TransitionPropsValidators
|
||||
}
|
||||
})
|
||||
|
||||
export function resolveTransitionProps({
|
||||
name = 'v',
|
||||
@@ -76,7 +72,7 @@ export function resolveTransitionProps({
|
||||
leaveActiveClass = `${name}-leave-active`,
|
||||
leaveToClass = `${name}-leave-to`,
|
||||
...baseProps
|
||||
}: TransitionProps): BaseTransitionProps {
|
||||
}: TransitionProps): BaseTransitionProps<Element> {
|
||||
if (!css) {
|
||||
return baseProps
|
||||
}
|
||||
@@ -94,7 +90,7 @@ export function resolveTransitionProps({
|
||||
enterToClass = appearToClass
|
||||
}
|
||||
|
||||
type Hook = (el: HTMLElement, done?: () => void) => void
|
||||
type Hook = (el: Element, done?: () => void) => void
|
||||
|
||||
const finishEnter: Hook = (el, done) => {
|
||||
removeTransitionClass(el, enterToClass)
|
||||
@@ -124,7 +120,7 @@ export function resolveTransitionProps({
|
||||
onEnter(el, done) {
|
||||
nextFrame(() => {
|
||||
const resolve = () => finishEnter(el, done)
|
||||
onEnter && callHookWithErrorHandling(onEnter, [el, resolve])
|
||||
onEnter && callHookWithErrorHandling(onEnter as Hook, [el, resolve])
|
||||
removeTransitionClass(el, enterFromClass)
|
||||
addTransitionClass(el, enterToClass)
|
||||
if (!(onEnter && onEnter.length > 1)) {
|
||||
@@ -141,7 +137,7 @@ export function resolveTransitionProps({
|
||||
addTransitionClass(el, leaveFromClass)
|
||||
nextFrame(() => {
|
||||
const resolve = () => finishLeave(el, done)
|
||||
onLeave && callHookWithErrorHandling(onLeave, [el, resolve])
|
||||
onLeave && callHookWithErrorHandling(onLeave as Hook, [el, resolve])
|
||||
removeTransitionClass(el, leaveFromClass)
|
||||
addTransitionClass(el, leaveToClass)
|
||||
if (!(onLeave && onLeave.length > 1)) {
|
||||
@@ -199,17 +195,21 @@ export interface ElementWithTransition extends HTMLElement {
|
||||
_vtc?: Set<string>
|
||||
}
|
||||
|
||||
export function addTransitionClass(el: ElementWithTransition, cls: string) {
|
||||
el.classList.add(cls)
|
||||
;(el._vtc || (el._vtc = new Set())).add(cls)
|
||||
export function addTransitionClass(el: Element, cls: string) {
|
||||
cls.split(/\s+/).forEach(c => c && el.classList.add(c))
|
||||
;(
|
||||
(el as ElementWithTransition)._vtc ||
|
||||
((el as ElementWithTransition)._vtc = new Set())
|
||||
).add(cls)
|
||||
}
|
||||
|
||||
export function removeTransitionClass(el: ElementWithTransition, cls: string) {
|
||||
el.classList.remove(cls)
|
||||
if (el._vtc) {
|
||||
el._vtc.delete(cls)
|
||||
if (!el._vtc!.size) {
|
||||
el._vtc = undefined
|
||||
export function removeTransitionClass(el: Element, cls: string) {
|
||||
cls.split(/\s+/).forEach(c => c && el.classList.remove(c))
|
||||
const { _vtc } = el as ElementWithTransition
|
||||
if (_vtc) {
|
||||
_vtc.delete(cls)
|
||||
if (!_vtc!.size) {
|
||||
;(el as ElementWithTransition)._vtc = undefined
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from './Transition'
|
||||
import {
|
||||
Fragment,
|
||||
Comment,
|
||||
VNode,
|
||||
warn,
|
||||
resolveTransitionHooks,
|
||||
@@ -35,6 +36,12 @@ export type TransitionGroupProps = Omit<TransitionProps, 'mode'> & {
|
||||
}
|
||||
|
||||
const TransitionGroupImpl = {
|
||||
props: {
|
||||
...TransitionPropsValidators,
|
||||
tag: String,
|
||||
moveClass: String
|
||||
},
|
||||
|
||||
setup(props: TransitionGroupProps, { slots }: SetupContext) {
|
||||
const instance = getCurrentInstance()!
|
||||
const state = useTransitionState()
|
||||
@@ -52,8 +59,8 @@ const TransitionGroupImpl = {
|
||||
hasMove =
|
||||
hasMove === null
|
||||
? (hasMove = hasCSSTransform(
|
||||
prevChildren[0].el,
|
||||
instance.vnode.el,
|
||||
prevChildren[0].el as ElementWithTransition,
|
||||
instance.vnode.el as Node,
|
||||
moveClass
|
||||
))
|
||||
: hasMove
|
||||
@@ -71,17 +78,17 @@ const TransitionGroupImpl = {
|
||||
forceReflow()
|
||||
|
||||
movedChildren.forEach(c => {
|
||||
const el = c.el
|
||||
const el = c.el as ElementWithTransition
|
||||
const style = el.style
|
||||
addTransitionClass(el, moveClass)
|
||||
style.transform = style.WebkitTransform = style.transitionDuration = ''
|
||||
const cb = (el._moveCb = (e: TransitionEvent) => {
|
||||
style.transform = style.webkitTransform = style.transitionDuration = ''
|
||||
const cb = ((el as any)._moveCb = (e: TransitionEvent) => {
|
||||
if (e && e.target !== el) {
|
||||
return
|
||||
}
|
||||
if (!e || /transform$/.test(e.propertyName)) {
|
||||
el.removeEventListener('transitionend', cb)
|
||||
el._moveCb = null
|
||||
;(el as any)._moveCb = null
|
||||
removeTransitionClass(el, moveClass)
|
||||
}
|
||||
})
|
||||
@@ -108,7 +115,7 @@ const TransitionGroupImpl = {
|
||||
child,
|
||||
resolveTransitionHooks(child, cssTransitionProps, state, instance)
|
||||
)
|
||||
} else if (__DEV__) {
|
||||
} else if (__DEV__ && child.type !== Comment) {
|
||||
warn(`<TransitionGroup> children must be keyed.`)
|
||||
}
|
||||
}
|
||||
@@ -120,7 +127,7 @@ const TransitionGroupImpl = {
|
||||
child,
|
||||
resolveTransitionHooks(child, cssTransitionProps, state, instance)
|
||||
)
|
||||
positionMap.set(child, child.el.getBoundingClientRect())
|
||||
positionMap.set(child, (child.el as Element).getBoundingClientRect())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,32 +136,27 @@ const TransitionGroupImpl = {
|
||||
}
|
||||
}
|
||||
|
||||
// remove mode props as TransitionGroup doesn't support it
|
||||
delete TransitionGroupImpl.props.mode
|
||||
|
||||
export const TransitionGroup = (TransitionGroupImpl as unknown) as {
|
||||
new (): {
|
||||
$props: TransitionGroupProps
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
const props = ((TransitionGroup as any).props = {
|
||||
...TransitionPropsValidators,
|
||||
tag: String,
|
||||
moveClass: String
|
||||
})
|
||||
delete props.mode
|
||||
}
|
||||
|
||||
function callPendingCbs(c: VNode) {
|
||||
if (c.el._moveCb) {
|
||||
c.el._moveCb()
|
||||
const el = c.el as any
|
||||
if (el._moveCb) {
|
||||
el._moveCb()
|
||||
}
|
||||
if (c.el._enterCb) {
|
||||
c.el._enterCb()
|
||||
if (el._enterCb) {
|
||||
el._enterCb()
|
||||
}
|
||||
}
|
||||
|
||||
function recordPosition(c: VNode) {
|
||||
newPositionMap.set(c, c.el.getBoundingClientRect())
|
||||
newPositionMap.set(c, (c.el as Element).getBoundingClientRect())
|
||||
}
|
||||
|
||||
function applyTranslation(c: VNode): VNode | undefined {
|
||||
@@ -163,8 +165,8 @@ function applyTranslation(c: VNode): VNode | undefined {
|
||||
const dx = oldPos.left - newPos.left
|
||||
const dy = oldPos.top - newPos.top
|
||||
if (dx || dy) {
|
||||
const s = c.el.style
|
||||
s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`
|
||||
const s = (c.el as HTMLElement).style
|
||||
s.transform = s.webkitTransform = `translate(${dx}px,${dy}px)`
|
||||
s.transitionDuration = '0s'
|
||||
return c
|
||||
}
|
||||
@@ -187,9 +189,11 @@ function hasCSSTransform(
|
||||
// is applied.
|
||||
const clone = el.cloneNode() as HTMLElement
|
||||
if (el._vtc) {
|
||||
el._vtc.forEach(cls => clone.classList.remove(cls))
|
||||
el._vtc.forEach(cls => {
|
||||
cls.split(/\s+/).forEach(c => c && clone.classList.remove(c))
|
||||
})
|
||||
}
|
||||
clone.classList.add(moveClass)
|
||||
moveClass.split(/\s+/).forEach(c => c && clone.classList.add(c))
|
||||
clone.style.display = 'none'
|
||||
const container = (root.nodeType === 1
|
||||
? root
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
import {
|
||||
ObjectDirective,
|
||||
VNode,
|
||||
DirectiveHook,
|
||||
DirectiveBinding,
|
||||
warn
|
||||
} from '@vue/runtime-core'
|
||||
import { addEventListener } from '../modules/events'
|
||||
import { isArray, isObject } from '@vue/shared'
|
||||
import { isArray, looseEqual, looseIndexOf, invokeArrayFns } from '@vue/shared'
|
||||
|
||||
const getModelAssigner = (vnode: VNode): ((value: any) => void) =>
|
||||
vnode.props!['onUpdate:modelValue']
|
||||
type AssignerFn = (value: any) => void
|
||||
|
||||
function onCompositionStart(e: CompositionEvent) {
|
||||
const getModelAssigner = (vnode: VNode): AssignerFn => {
|
||||
const fn = vnode.props!['onUpdate:modelValue']
|
||||
return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
|
||||
}
|
||||
|
||||
function onCompositionStart(e: Event) {
|
||||
;(e.target as any).composing = true
|
||||
}
|
||||
|
||||
function onCompositionEnd(e: CompositionEvent) {
|
||||
function onCompositionEnd(e: Event) {
|
||||
const target = e.target as any
|
||||
if (target.composing) {
|
||||
target.composing = false
|
||||
@@ -33,14 +38,16 @@ function toNumber(val: string): number | string {
|
||||
return isNaN(n) ? val : n
|
||||
}
|
||||
|
||||
type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>
|
||||
|
||||
// We are exporting the v-model runtime directly as vnode hooks so that it can
|
||||
// be tree-shaken in case v-model is never used.
|
||||
export const vModelText: ObjectDirective<
|
||||
export const vModelText: ModelDirective<
|
||||
HTMLInputElement | HTMLTextAreaElement
|
||||
> = {
|
||||
beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
||||
el.value = value
|
||||
const assign = getModelAssigner(vnode)
|
||||
el._assign = getModelAssigner(vnode)
|
||||
const castToNumber = number || el.type === 'number'
|
||||
addEventListener(el, lazy ? 'change' : 'input', () => {
|
||||
let domValue: string | number = el.value
|
||||
@@ -49,7 +56,7 @@ export const vModelText: ObjectDirective<
|
||||
} else if (castToNumber) {
|
||||
domValue = toNumber(domValue)
|
||||
}
|
||||
assign(domValue)
|
||||
el._assign(domValue)
|
||||
})
|
||||
if (trim) {
|
||||
addEventListener(el, 'change', () => {
|
||||
@@ -66,7 +73,8 @@ export const vModelText: ObjectDirective<
|
||||
addEventListener(el, 'change', onCompositionEnd)
|
||||
}
|
||||
},
|
||||
beforeUpdate(el, { value, oldValue, modifiers: { trim, number } }) {
|
||||
beforeUpdate(el, { value, oldValue, modifiers: { trim, number } }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
if (value === oldValue) {
|
||||
return
|
||||
}
|
||||
@@ -82,14 +90,15 @@ export const vModelText: ObjectDirective<
|
||||
}
|
||||
}
|
||||
|
||||
export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
|
||||
export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
||||
beforeMount(el, binding, vnode) {
|
||||
setChecked(el, binding, vnode)
|
||||
const assign = getModelAssigner(vnode)
|
||||
el._assign = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
const modelValue = (el as any)._modelValue
|
||||
const elementValue = getValue(el)
|
||||
const checked = el.checked
|
||||
const assign = el._assign
|
||||
if (isArray(modelValue)) {
|
||||
const index = looseIndexOf(modelValue, elementValue)
|
||||
const found = index !== -1
|
||||
@@ -105,7 +114,10 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
|
||||
}
|
||||
})
|
||||
},
|
||||
beforeUpdate: setChecked
|
||||
beforeUpdate(el, binding, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
setChecked(el, binding, vnode)
|
||||
}
|
||||
}
|
||||
|
||||
function setChecked(
|
||||
@@ -123,33 +135,37 @@ function setChecked(
|
||||
}
|
||||
}
|
||||
|
||||
export const vModelRadio: ObjectDirective<HTMLInputElement> = {
|
||||
export const vModelRadio: ModelDirective<HTMLInputElement> = {
|
||||
beforeMount(el, { value }, vnode) {
|
||||
el.checked = looseEqual(value, vnode.props!.value)
|
||||
const assign = getModelAssigner(vnode)
|
||||
el._assign = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
assign(getValue(el))
|
||||
el._assign(getValue(el))
|
||||
})
|
||||
},
|
||||
beforeUpdate(el, { value, oldValue }, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
if (value !== oldValue) {
|
||||
el.checked = looseEqual(value, vnode.props!.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const vModelSelect: ObjectDirective<HTMLSelectElement> = {
|
||||
export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
||||
// use mounted & updated because <select> relies on its children <option>s.
|
||||
mounted(el, { value }, vnode) {
|
||||
setSelected(el, value)
|
||||
const assign = getModelAssigner(vnode)
|
||||
el._assign = getModelAssigner(vnode)
|
||||
addEventListener(el, 'change', () => {
|
||||
const selectedVal = Array.prototype.filter
|
||||
.call(el.options, (o: HTMLOptionElement) => o.selected)
|
||||
.map(getValue)
|
||||
assign(el.multiple ? selectedVal : selectedVal[0])
|
||||
el._assign(el.multiple ? selectedVal : selectedVal[0])
|
||||
})
|
||||
},
|
||||
beforeUpdate(el, _binding, vnode) {
|
||||
el._assign = getModelAssigner(vnode)
|
||||
},
|
||||
updated(el, { value }) {
|
||||
setSelected(el, value)
|
||||
}
|
||||
@@ -182,47 +198,6 @@ function setSelected(el: HTMLSelectElement, value: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function looseEqual(a: any, b: any): boolean {
|
||||
if (a === b) return true
|
||||
const isObjectA = isObject(a)
|
||||
const isObjectB = isObject(b)
|
||||
if (isObjectA && isObjectB) {
|
||||
try {
|
||||
const isArrayA = isArray(a)
|
||||
const isArrayB = isArray(b)
|
||||
if (isArrayA && isArrayB) {
|
||||
return (
|
||||
a.length === b.length &&
|
||||
a.every((e: any, i: any) => looseEqual(e, b[i]))
|
||||
)
|
||||
} else if (a instanceof Date && b instanceof Date) {
|
||||
return a.getTime() === b.getTime()
|
||||
} else if (!isArrayA && !isArrayB) {
|
||||
const keysA = Object.keys(a)
|
||||
const keysB = Object.keys(b)
|
||||
return (
|
||||
keysA.length === keysB.length &&
|
||||
keysA.every(key => looseEqual(a[key], b[key]))
|
||||
)
|
||||
} else {
|
||||
/* istanbul ignore next */
|
||||
return false
|
||||
}
|
||||
} catch (e) {
|
||||
/* istanbul ignore next */
|
||||
return false
|
||||
}
|
||||
} else if (!isObjectA && !isObjectB) {
|
||||
return String(a) === String(b)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function looseIndexOf(arr: any[], val: any): number {
|
||||
return arr.findIndex(item => looseEqual(item, val))
|
||||
}
|
||||
|
||||
// retrieve raw value set via :value bindings
|
||||
function getValue(el: HTMLOptionElement | HTMLInputElement) {
|
||||
return '_value' in el ? (el as any)._value : el.value
|
||||
@@ -259,7 +234,7 @@ function callModelHook(
|
||||
binding: DirectiveBinding,
|
||||
vnode: VNode,
|
||||
prevVNode: VNode | null,
|
||||
hook: keyof ObjectDirective
|
||||
hook: 'beforeMount' | 'mounted' | 'beforeUpdate' | 'updated'
|
||||
) {
|
||||
let modelToUse: ObjectDirective
|
||||
switch (el.tagName) {
|
||||
@@ -281,6 +256,27 @@ function callModelHook(
|
||||
modelToUse = vModelText
|
||||
}
|
||||
}
|
||||
const fn = modelToUse[hook]
|
||||
const fn = modelToUse[hook] as DirectiveHook
|
||||
fn && fn(el, binding, vnode, prevVNode)
|
||||
}
|
||||
|
||||
// SSR vnode transforms
|
||||
if (__NODE_JS__) {
|
||||
vModelText.getSSRProps = ({ value }) => ({ value })
|
||||
|
||||
vModelRadio.getSSRProps = ({ value }, vnode) => {
|
||||
if (vnode.props && looseEqual(vnode.props.value, value)) {
|
||||
return { checked: true }
|
||||
}
|
||||
}
|
||||
|
||||
vModelCheckbox.getSSRProps = ({ value }, vnode) => {
|
||||
if (isArray(value)) {
|
||||
if (vnode.props && looseIndexOf(value, vnode.props.value) > -1) {
|
||||
return { checked: true }
|
||||
}
|
||||
} else if (value) {
|
||||
return { checked: true }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent
|
||||
|
||||
const modifierGuards: Record<
|
||||
string,
|
||||
(e: Event, modifiers?: string[]) => void | boolean
|
||||
(e: Event, modifiers: string[]) => void | boolean
|
||||
> = {
|
||||
stop: e => e.stopPropagation(),
|
||||
prevent: e => e.preventDefault(),
|
||||
@@ -18,7 +18,7 @@ const modifierGuards: Record<
|
||||
left: e => 'button' in e && (e as MouseEvent).button !== 0,
|
||||
middle: e => 'button' in e && (e as MouseEvent).button !== 1,
|
||||
right: e => 'button' in e && (e as MouseEvent).button !== 2,
|
||||
exact: (e, modifiers: string[]) =>
|
||||
exact: (e, modifiers) =>
|
||||
systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m))
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,14 @@ export const vShow: ObjectDirective<VShowElement> = {
|
||||
}
|
||||
}
|
||||
|
||||
if (__NODE_JS__) {
|
||||
vShow.getSSRProps = ({ value }) => {
|
||||
if (!value) {
|
||||
return { style: { display: 'none' } }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setDisplay(el: VShowElement, value: unknown): void {
|
||||
el.style.display = value ? el._vod : 'none'
|
||||
}
|
||||
|
||||
@@ -1,58 +1,112 @@
|
||||
import {
|
||||
createRenderer,
|
||||
createHydrationRenderer,
|
||||
warn,
|
||||
RootRenderFunction,
|
||||
CreateAppFunction,
|
||||
Renderer,
|
||||
HydrationRenderer,
|
||||
App,
|
||||
RootRenderFunction
|
||||
RootHydrateFunction
|
||||
} from '@vue/runtime-core'
|
||||
import { nodeOps } from './nodeOps'
|
||||
import { patchProp } from './patchProp'
|
||||
// Importing from the compiler, will be tree-shaken in prod
|
||||
import { isFunction, isString, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||
|
||||
const { render: baseRender, createApp: baseCreateApp } = createRenderer({
|
||||
const rendererOptions = {
|
||||
patchProp,
|
||||
...nodeOps
|
||||
})
|
||||
}
|
||||
|
||||
// lazy create the renderer - this makes core renderer logic tree-shakable
|
||||
// in case the user only imports reactivity utilities from Vue.
|
||||
let renderer: Renderer | HydrationRenderer
|
||||
|
||||
let enabledHydration = false
|
||||
|
||||
function ensureRenderer() {
|
||||
return renderer || (renderer = createRenderer(rendererOptions))
|
||||
}
|
||||
|
||||
function ensureHydrationRenderer() {
|
||||
renderer = enabledHydration
|
||||
? renderer
|
||||
: createHydrationRenderer(rendererOptions)
|
||||
enabledHydration = true
|
||||
return renderer as HydrationRenderer
|
||||
}
|
||||
|
||||
// use explicit type casts here to avoid import() calls in rolled-up d.ts
|
||||
export const render = baseRender as RootRenderFunction<Node, Element>
|
||||
export const render = ((...args) => {
|
||||
ensureRenderer().render(...args)
|
||||
}) as RootRenderFunction<Element>
|
||||
|
||||
export const createApp = (): App<Element> => {
|
||||
const app = baseCreateApp()
|
||||
export const hydrate = ((...args) => {
|
||||
ensureHydrationRenderer().hydrate(...args)
|
||||
}) as RootHydrateFunction
|
||||
|
||||
export const createApp = ((...args) => {
|
||||
const app = ensureRenderer().createApp(...args)
|
||||
|
||||
if (__DEV__) {
|
||||
// Inject `isNativeTag`
|
||||
// this is used for component name validation (dev only)
|
||||
Object.defineProperty(app.config, 'isNativeTag', {
|
||||
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
|
||||
writable: false
|
||||
})
|
||||
injectNativeTagCheck(app)
|
||||
}
|
||||
|
||||
const mount = app.mount
|
||||
app.mount = (component, container, props): any => {
|
||||
if (isString(container)) {
|
||||
container = document.querySelector(container)!
|
||||
if (!container) {
|
||||
__DEV__ &&
|
||||
warn(`Failed to mount app: mount target selector returned null.`)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (
|
||||
__RUNTIME_COMPILE__ &&
|
||||
!isFunction(component) &&
|
||||
!component.render &&
|
||||
!component.template
|
||||
) {
|
||||
const { mount } = app
|
||||
app.mount = (containerOrSelector: Element | string): any => {
|
||||
const container = normalizeContainer(containerOrSelector)
|
||||
if (!container) return
|
||||
const component = app._component
|
||||
if (!isFunction(component) && !component.render && !component.template) {
|
||||
component.template = container.innerHTML
|
||||
}
|
||||
// clear content before mounting
|
||||
container.innerHTML = ''
|
||||
return mount(component, container, props)
|
||||
const proxy = mount(container)
|
||||
container.removeAttribute('v-cloak')
|
||||
return proxy
|
||||
}
|
||||
|
||||
return app
|
||||
}) as CreateAppFunction<Element>
|
||||
|
||||
export const createSSRApp = ((...args) => {
|
||||
const app = ensureHydrationRenderer().createApp(...args)
|
||||
|
||||
if (__DEV__) {
|
||||
injectNativeTagCheck(app)
|
||||
}
|
||||
|
||||
const { mount } = app
|
||||
app.mount = (containerOrSelector: Element | string): any => {
|
||||
const container = normalizeContainer(containerOrSelector)
|
||||
if (container) {
|
||||
return mount(container, true)
|
||||
}
|
||||
}
|
||||
|
||||
return app
|
||||
}) as CreateAppFunction<Element>
|
||||
|
||||
function injectNativeTagCheck(app: App) {
|
||||
// Inject `isNativeTag`
|
||||
// this is used for component name validation (dev only)
|
||||
Object.defineProperty(app.config, 'isNativeTag', {
|
||||
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
|
||||
writable: false
|
||||
})
|
||||
}
|
||||
|
||||
function normalizeContainer(container: Element | string): Element | null {
|
||||
if (isString(container)) {
|
||||
const res = document.querySelector(container)
|
||||
if (__DEV__ && !res) {
|
||||
warn(`Failed to mount app: mount target selector returned null.`)
|
||||
}
|
||||
return res
|
||||
}
|
||||
return container
|
||||
}
|
||||
|
||||
// DOM-only runtime directive helpers
|
||||
|
||||
@@ -1,7 +1,27 @@
|
||||
export function patchAttr(el: Element, key: string, value: any) {
|
||||
if (value == null) {
|
||||
el.removeAttribute(key)
|
||||
import { isSpecialBooleanAttr } from '@vue/shared'
|
||||
|
||||
export const xlinkNS = 'http://www.w3.org/1999/xlink'
|
||||
|
||||
export function patchAttr(
|
||||
el: Element,
|
||||
key: string,
|
||||
value: any,
|
||||
isSVG: boolean
|
||||
) {
|
||||
if (isSVG && key.indexOf('xlink:') === 0) {
|
||||
if (value == null) {
|
||||
el.removeAttributeNS(xlinkNS, key.slice(6, key.length))
|
||||
} else {
|
||||
el.setAttributeNS(xlinkNS, key, value)
|
||||
}
|
||||
} else {
|
||||
el.setAttribute(key, value)
|
||||
// note we are only checking boolean attributes that don't have a
|
||||
// correspoding dom prop of the same name here.
|
||||
const isBoolean = isSpecialBooleanAttr(key)
|
||||
if (value == null || (isBoolean && value === false)) {
|
||||
el.removeAttribute(key)
|
||||
} else {
|
||||
el.setAttribute(key, isBoolean ? '' : value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ export function patchClass(el: Element, value: string | null, isSVG: boolean) {
|
||||
} else {
|
||||
// if this is an element during a transition, take the temporary transition
|
||||
// classes into account.
|
||||
const transtionClasses = (el as ElementWithTransition)._vtc
|
||||
if (transtionClasses) {
|
||||
value = [value, ...transtionClasses].join(' ')
|
||||
const transitionClasses = (el as ElementWithTransition)._vtc
|
||||
if (transitionClasses) {
|
||||
value = [value, ...transitionClasses].join(' ')
|
||||
}
|
||||
el.className = value
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EMPTY_OBJ } from '@vue/shared'
|
||||
import { EMPTY_OBJ, isString } from '@vue/shared'
|
||||
import {
|
||||
ComponentInternalInstance,
|
||||
callWithAsyncErrorHandling
|
||||
@@ -66,11 +66,22 @@ export function removeEventListener(
|
||||
|
||||
export function patchEvent(
|
||||
el: Element,
|
||||
name: string,
|
||||
rawName: string,
|
||||
prevValue: EventValueWithOptions | EventValue | null,
|
||||
nextValue: EventValueWithOptions | EventValue | null,
|
||||
instance: ComponentInternalInstance | null = null
|
||||
) {
|
||||
// support native onxxx handlers
|
||||
if (rawName in el) {
|
||||
if (isString(nextValue)) {
|
||||
el.setAttribute(rawName, nextValue)
|
||||
} else {
|
||||
;(el as any)[rawName] = nextValue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const name = rawName.slice(2).toLowerCase()
|
||||
const prevOptions = prevValue && 'options' in prevValue && prevValue.options
|
||||
const nextOptions = nextValue && 'options' in nextValue && nextValue.options
|
||||
const invoker = prevValue && prevValue.invoker
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// __UNSAFE__
|
||||
// Reason: potentially setting innerHTML.
|
||||
// This can come from explicit usage of v-html or innerHTML as a prop in render
|
||||
// functions. The user is reponsible for using them with only trusted content.
|
||||
export function patchDOMProp(
|
||||
el: any,
|
||||
key: string,
|
||||
@@ -10,7 +14,7 @@ export function patchDOMProp(
|
||||
parentSuspense: any,
|
||||
unmountChildren: any
|
||||
) {
|
||||
if ((key === 'innerHTML' || key === 'textContent') && prevChildren != null) {
|
||||
if ((key === 'innerHTML' || key === 'textContent') && prevChildren) {
|
||||
unmountChildren(prevChildren, parentComponent, parentSuspense)
|
||||
el[key] = value == null ? '' : value
|
||||
return
|
||||
|
||||
@@ -1,42 +1,70 @@
|
||||
const doc = document
|
||||
import { RendererOptions } from '@vue/runtime-core'
|
||||
|
||||
const doc = (typeof document !== 'undefined' ? document : null) as Document
|
||||
const svgNS = 'http://www.w3.org/2000/svg'
|
||||
|
||||
export const nodeOps = {
|
||||
insert: (child: Node, parent: Node, anchor?: Node) => {
|
||||
if (anchor != null) {
|
||||
let tempContainer: HTMLElement
|
||||
let tempSVGContainer: SVGElement
|
||||
|
||||
export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
|
||||
insert: (child, parent, anchor) => {
|
||||
if (anchor) {
|
||||
parent.insertBefore(child, anchor)
|
||||
} else {
|
||||
parent.appendChild(child)
|
||||
}
|
||||
},
|
||||
|
||||
remove: (child: Node) => {
|
||||
remove: child => {
|
||||
const parent = child.parentNode
|
||||
if (parent != null) {
|
||||
if (parent) {
|
||||
parent.removeChild(child)
|
||||
}
|
||||
},
|
||||
|
||||
createElement: (tag: string, isSVG?: boolean): Element =>
|
||||
isSVG ? doc.createElementNS(svgNS, tag) : doc.createElement(tag),
|
||||
createElement: (tag, isSVG, is): Element =>
|
||||
isSVG
|
||||
? doc.createElementNS(svgNS, tag)
|
||||
: doc.createElement(tag, is ? { is } : undefined),
|
||||
|
||||
createText: (text: string): Text => doc.createTextNode(text),
|
||||
createText: text => doc.createTextNode(text),
|
||||
|
||||
createComment: (text: string): Comment => doc.createComment(text),
|
||||
createComment: text => doc.createComment(text),
|
||||
|
||||
setText: (node: Text, text: string) => {
|
||||
setText: (node, text) => {
|
||||
node.nodeValue = text
|
||||
},
|
||||
|
||||
setElementText: (el: HTMLElement, text: string) => {
|
||||
setElementText: (el, text) => {
|
||||
el.textContent = text
|
||||
},
|
||||
|
||||
parentNode: (node: Node): HTMLElement | null =>
|
||||
node.parentNode as HTMLElement,
|
||||
parentNode: node => node.parentNode as Element | null,
|
||||
|
||||
nextSibling: (node: Node): Node | null => node.nextSibling,
|
||||
nextSibling: node => node.nextSibling,
|
||||
|
||||
querySelector: (selector: string): Element | null =>
|
||||
doc.querySelector(selector)
|
||||
querySelector: selector => doc.querySelector(selector),
|
||||
|
||||
setScopeId(el, id) {
|
||||
el.setAttribute(id, '')
|
||||
},
|
||||
|
||||
cloneNode(el) {
|
||||
return el.cloneNode(true)
|
||||
},
|
||||
|
||||
// __UNSAFE__
|
||||
// Reason: innerHTML.
|
||||
// Static content here can only come from compiled templates.
|
||||
// As long as the user only uses trusted templates, this is safe.
|
||||
insertStaticContent(content, parent, anchor, isSVG) {
|
||||
const temp = isSVG
|
||||
? tempSVGContainer ||
|
||||
(tempSVGContainer = doc.createElementNS(svgNS, 'svg'))
|
||||
: tempContainer || (tempContainer = doc.createElement('div'))
|
||||
temp.innerHTML = content
|
||||
const node = temp.children[0]
|
||||
nodeOps.insert(node, parent, anchor)
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,23 +4,19 @@ import { patchAttr } from './modules/attrs'
|
||||
import { patchDOMProp } from './modules/props'
|
||||
import { patchEvent } from './modules/events'
|
||||
import { isOn } from '@vue/shared'
|
||||
import {
|
||||
ComponentInternalInstance,
|
||||
SuspenseBoundary,
|
||||
VNode
|
||||
} from '@vue/runtime-core'
|
||||
import { RendererOptions } from '@vue/runtime-core'
|
||||
|
||||
export function patchProp(
|
||||
el: Element,
|
||||
key: string,
|
||||
nextValue: any,
|
||||
prevValue: any,
|
||||
isSVG: boolean,
|
||||
prevChildren?: VNode[],
|
||||
parentComponent?: ComponentInternalInstance,
|
||||
parentSuspense?: SuspenseBoundary<Node, Element>,
|
||||
unmountChildren?: any
|
||||
) {
|
||||
export const patchProp: RendererOptions<Node, Element>['patchProp'] = (
|
||||
el,
|
||||
key,
|
||||
prevValue,
|
||||
nextValue,
|
||||
isSVG = false,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren
|
||||
) => {
|
||||
switch (key) {
|
||||
// special
|
||||
case 'class':
|
||||
@@ -29,19 +25,12 @@ export function patchProp(
|
||||
case 'style':
|
||||
patchStyle(el, prevValue, nextValue)
|
||||
break
|
||||
case 'modelValue':
|
||||
case 'onUpdate:modelValue':
|
||||
// Do nothing. This is handled by v-model directives.
|
||||
break
|
||||
default:
|
||||
if (isOn(key)) {
|
||||
patchEvent(
|
||||
el,
|
||||
key.slice(2).toLowerCase(),
|
||||
prevValue,
|
||||
nextValue,
|
||||
parentComponent
|
||||
)
|
||||
// ignore v-model listeners
|
||||
if (key.indexOf('onUpdate:') < 0) {
|
||||
patchEvent(el, key, prevValue, nextValue, parentComponent)
|
||||
}
|
||||
} else if (!isSVG && key in el) {
|
||||
patchDOMProp(
|
||||
el,
|
||||
@@ -62,7 +51,7 @@ export function patchProp(
|
||||
} else if (key === 'false-value') {
|
||||
;(el as any)._falseValue = nextValue
|
||||
}
|
||||
patchAttr(el, key, nextValue)
|
||||
patchAttr(el, key, nextValue, isSVG)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user