mirror of https://github.com/vuejs/core.git
feat(custom-element): support for expose on customElement (#6256)
close #5540
This commit is contained in:
parent
5a1a89bd61
commit
af838c1b5e
|
@ -1,3 +1,4 @@
|
|||
import type { MockedFunction } from 'vitest'
|
||||
import {
|
||||
type Ref,
|
||||
type VueElement,
|
||||
|
@ -881,4 +882,64 @@ describe('defineCustomElement', () => {
|
|||
expect(style.textContent).toBe(`div { color: red; }`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('expose', () => {
|
||||
test('expose attributes and callback', async () => {
|
||||
type SetValue = (value: string) => void
|
||||
let fn: MockedFunction<SetValue>
|
||||
|
||||
const E = defineCustomElement({
|
||||
setup(_, { expose }) {
|
||||
const value = ref('hello')
|
||||
|
||||
const setValue = (fn = vi.fn((_value: string) => {
|
||||
value.value = _value
|
||||
}))
|
||||
|
||||
expose({
|
||||
setValue,
|
||||
value,
|
||||
})
|
||||
|
||||
return () => h('div', null, [value.value])
|
||||
},
|
||||
})
|
||||
customElements.define('my-el-expose', E)
|
||||
|
||||
container.innerHTML = `<my-el-expose></my-el-expose>`
|
||||
const e = container.childNodes[0] as VueElement & {
|
||||
value: string
|
||||
setValue: MockedFunction<SetValue>
|
||||
}
|
||||
expect(e.shadowRoot!.innerHTML).toBe(`<div>hello</div>`)
|
||||
expect(e.value).toBe('hello')
|
||||
expect(e.setValue).toBe(fn!)
|
||||
e.setValue('world')
|
||||
expect(e.value).toBe('world')
|
||||
await nextTick()
|
||||
expect(e.shadowRoot!.innerHTML).toBe(`<div>world</div>`)
|
||||
})
|
||||
|
||||
test('warning when exposing an existing property', () => {
|
||||
const E = defineCustomElement({
|
||||
props: {
|
||||
value: String,
|
||||
},
|
||||
setup(props, { expose }) {
|
||||
expose({
|
||||
value: 'hello',
|
||||
})
|
||||
|
||||
return () => h('div', null, [props.value])
|
||||
},
|
||||
})
|
||||
customElements.define('my-el-expose-two', E)
|
||||
|
||||
container.innerHTML = `<my-el-expose-two value="world"></my-el-expose-two>`
|
||||
|
||||
expect(
|
||||
`[Vue warn]: Exposed property "value" already exists on custom element.`,
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -26,11 +26,13 @@ import {
|
|||
defineComponent,
|
||||
getCurrentInstance,
|
||||
nextTick,
|
||||
unref,
|
||||
warn,
|
||||
} from '@vue/runtime-core'
|
||||
import {
|
||||
camelize,
|
||||
extend,
|
||||
hasOwn,
|
||||
hyphenate,
|
||||
isArray,
|
||||
isPlainObject,
|
||||
|
@ -308,6 +310,9 @@ export class VueElement extends BaseClass {
|
|||
|
||||
// initial render
|
||||
this._update()
|
||||
|
||||
// apply expose
|
||||
this._applyExpose()
|
||||
}
|
||||
|
||||
const asyncDef = (this._def as ComponentOptions).__asyncLoader
|
||||
|
@ -342,6 +347,22 @@ export class VueElement extends BaseClass {
|
|||
}
|
||||
}
|
||||
|
||||
private _applyExpose() {
|
||||
const exposed = this._instance && this._instance.exposed
|
||||
if (!exposed) return
|
||||
for (const key in exposed) {
|
||||
if (!hasOwn(this, key)) {
|
||||
// exposed properties are readonly
|
||||
Object.defineProperty(this, key, {
|
||||
// unwrap ref to be consistent with public instance behavior
|
||||
get: () => unref(exposed[key]),
|
||||
})
|
||||
} else if (__DEV__) {
|
||||
warn(`Exposed property "${key}" already exists on custom element.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected _setAttr(key: string) {
|
||||
if (key.startsWith('data-v-')) return
|
||||
let value = this.hasAttribute(key) ? this.getAttribute(key) : undefined
|
||||
|
|
Loading…
Reference in New Issue