parent
5bd0ac67f3
commit
0cfa2112ce
@ -136,26 +136,40 @@ describe('defineCustomElement', () => {
|
|||||||
const E = defineCustomElement({
|
const E = defineCustomElement({
|
||||||
props: {
|
props: {
|
||||||
foo: Number,
|
foo: Number,
|
||||||
bar: Boolean
|
bar: Boolean,
|
||||||
|
baz: String
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return [this.foo, typeof this.foo, this.bar, typeof this.bar].join(
|
return [
|
||||||
' '
|
this.foo,
|
||||||
)
|
typeof this.foo,
|
||||||
|
this.bar,
|
||||||
|
typeof this.bar,
|
||||||
|
this.baz,
|
||||||
|
typeof this.baz
|
||||||
|
].join(' ')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
customElements.define('my-el-props-cast', E)
|
customElements.define('my-el-props-cast', E)
|
||||||
container.innerHTML = `<my-el-props-cast foo="1"></my-el-props-cast>`
|
container.innerHTML = `<my-el-props-cast foo="1" baz="12345"></my-el-props-cast>`
|
||||||
const e = container.childNodes[0] as VueElement
|
const e = container.childNodes[0] as VueElement
|
||||||
expect(e.shadowRoot!.innerHTML).toBe(`1 number false boolean`)
|
expect(e.shadowRoot!.innerHTML).toBe(
|
||||||
|
`1 number false boolean 12345 string`
|
||||||
|
)
|
||||||
|
|
||||||
e.setAttribute('bar', '')
|
e.setAttribute('bar', '')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(e.shadowRoot!.innerHTML).toBe(`1 number true boolean`)
|
expect(e.shadowRoot!.innerHTML).toBe(`1 number true boolean 12345 string`)
|
||||||
|
|
||||||
e.setAttribute('foo', '2e1')
|
e.setAttribute('foo', '2e1')
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(e.shadowRoot!.innerHTML).toBe(`20 number true boolean`)
|
expect(e.shadowRoot!.innerHTML).toBe(
|
||||||
|
`20 number true boolean 12345 string`
|
||||||
|
)
|
||||||
|
|
||||||
|
e.setAttribute('baz', '2e1')
|
||||||
|
await nextTick()
|
||||||
|
expect(e.shadowRoot!.innerHTML).toBe(`20 number true boolean 2e1 string`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('handling properties set before upgrading', () => {
|
test('handling properties set before upgrading', () => {
|
||||||
@ -392,5 +406,25 @@ describe('defineCustomElement', () => {
|
|||||||
e2.msg = 'hello'
|
e2.msg = 'hello'
|
||||||
expect(e2.shadowRoot!.innerHTML).toBe(`<div>hello</div>`)
|
expect(e2.shadowRoot!.innerHTML).toBe(`<div>hello</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Number prop casting before resolve', async () => {
|
||||||
|
const E = defineCustomElement(
|
||||||
|
defineAsyncComponent(() => {
|
||||||
|
return Promise.resolve({
|
||||||
|
props: { n: Number },
|
||||||
|
render(this: any) {
|
||||||
|
return h('div', this.n + ',' + typeof this.n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
customElements.define('my-el-async-3', E)
|
||||||
|
container.innerHTML = `<my-el-async-3 n="2e1"></my-el-async-3>`
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
|
||||||
|
const e = container.childNodes[0] as VueElement
|
||||||
|
expect(e.shadowRoot!.innerHTML).toBe(`<div>20,number</div>`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -154,6 +154,7 @@ export class VueElement extends BaseClass {
|
|||||||
|
|
||||||
private _connected = false
|
private _connected = false
|
||||||
private _resolved = false
|
private _resolved = false
|
||||||
|
private _numberProps: Record<string, true> | null = null
|
||||||
private _styles?: HTMLStyleElement[]
|
private _styles?: HTMLStyleElement[]
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -179,19 +180,18 @@ export class VueElement extends BaseClass {
|
|||||||
this._setAttr(this.attributes[i].name)
|
this._setAttr(this.attributes[i].name)
|
||||||
}
|
}
|
||||||
// watch future attr changes
|
// watch future attr changes
|
||||||
const observer = new MutationObserver(mutations => {
|
new MutationObserver(mutations => {
|
||||||
for (const m of mutations) {
|
for (const m of mutations) {
|
||||||
this._setAttr(m.attributeName!)
|
this._setAttr(m.attributeName!)
|
||||||
}
|
}
|
||||||
})
|
}).observe(this, { attributes: true })
|
||||||
observer.observe(this, { attributes: true })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
this._connected = true
|
this._connected = true
|
||||||
if (!this._instance) {
|
if (!this._instance) {
|
||||||
this._resolveDef()
|
this._resolveDef()
|
||||||
render(this._createVNode(), this.shadowRoot!)
|
this._update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,15 +215,33 @@ export class VueElement extends BaseClass {
|
|||||||
|
|
||||||
const resolve = (def: InnerComponentDef) => {
|
const resolve = (def: InnerComponentDef) => {
|
||||||
this._resolved = true
|
this._resolved = true
|
||||||
|
const { props, styles } = def
|
||||||
|
const hasOptions = !isArray(props)
|
||||||
|
const rawKeys = props ? (hasOptions ? Object.keys(props) : props) : []
|
||||||
|
|
||||||
|
// cast Number-type props set before resolve
|
||||||
|
let numberProps
|
||||||
|
if (hasOptions) {
|
||||||
|
for (const key in this._props) {
|
||||||
|
const opt = props[key]
|
||||||
|
if (opt === Number || (opt && opt.type === Number)) {
|
||||||
|
this._props[key] = toNumber(this._props[key])
|
||||||
|
;(numberProps || (numberProps = Object.create(null)))[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (numberProps) {
|
||||||
|
this._numberProps = numberProps
|
||||||
|
this._update()
|
||||||
|
}
|
||||||
|
|
||||||
// check if there are props set pre-upgrade or connect
|
// check if there are props set pre-upgrade or connect
|
||||||
for (const key of Object.keys(this)) {
|
for (const key of Object.keys(this)) {
|
||||||
if (key[0] !== '_') {
|
if (key[0] !== '_') {
|
||||||
this._setProp(key, this[key as keyof this])
|
this._setProp(key, this[key as keyof this])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const { props, styles } = def
|
|
||||||
// defining getter/setters on prototype
|
// defining getter/setters on prototype
|
||||||
const rawKeys = props ? (isArray(props) ? props : Object.keys(props)) : []
|
|
||||||
for (const key of rawKeys.map(camelize)) {
|
for (const key of rawKeys.map(camelize)) {
|
||||||
Object.defineProperty(this, key, {
|
Object.defineProperty(this, key, {
|
||||||
get() {
|
get() {
|
||||||
@ -246,7 +264,11 @@ export class VueElement extends BaseClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected _setAttr(key: string) {
|
protected _setAttr(key: string) {
|
||||||
this._setProp(camelize(key), toNumber(this.getAttribute(key)), false)
|
let value = this.getAttribute(key)
|
||||||
|
if (this._numberProps && this._numberProps[key]) {
|
||||||
|
value = toNumber(value)
|
||||||
|
}
|
||||||
|
this._setProp(camelize(key), value, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,7 +285,7 @@ export class VueElement extends BaseClass {
|
|||||||
if (val !== this._props[key]) {
|
if (val !== this._props[key]) {
|
||||||
this._props[key] = val
|
this._props[key] = val
|
||||||
if (this._instance) {
|
if (this._instance) {
|
||||||
render(this._createVNode(), this.shadowRoot!)
|
this._update()
|
||||||
}
|
}
|
||||||
// reflect
|
// reflect
|
||||||
if (shouldReflect) {
|
if (shouldReflect) {
|
||||||
@ -278,6 +300,10 @@ export class VueElement extends BaseClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _update() {
|
||||||
|
render(this._createVNode(), this.shadowRoot!)
|
||||||
|
}
|
||||||
|
|
||||||
private _createVNode(): VNode<any, any> {
|
private _createVNode(): VNode<any, any> {
|
||||||
const vnode = createVNode(this._def, extend({}, this._props))
|
const vnode = createVNode(this._def, extend({}, this._props))
|
||||||
if (!this._instance) {
|
if (!this._instance) {
|
||||||
@ -298,7 +324,7 @@ export class VueElement extends BaseClass {
|
|||||||
if (!(this._def as ComponentOptions).__asyncLoader) {
|
if (!(this._def as ComponentOptions).__asyncLoader) {
|
||||||
// reload
|
// reload
|
||||||
this._instance = null
|
this._instance = null
|
||||||
render(this._createVNode(), this.shadowRoot!)
|
this._update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user