+import type { MockedFunction } from 'vitest'
import {
type Ref,
type VueElement,
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()
+ })
+ })
})
defineComponent,
getCurrentInstance,
nextTick,
+ unref,
warn,
} from '@vue/runtime-core'
import {
camelize,
extend,
+ hasOwn,
hyphenate,
isArray,
isPlainObject,
// initial render
this._update()
+
+ // apply expose
+ this._applyExpose()
}
const asyncDef = (this._def as ComponentOptions).__asyncLoader
}
}
+ 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