apiCustomElement.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. import {
  2. type App,
  3. type Component,
  4. type ComponentCustomElementInterface,
  5. type ComponentInjectOptions,
  6. type ComponentObjectPropsOptions,
  7. type ComponentOptions,
  8. type ComponentOptionsBase,
  9. type ComponentOptionsMixin,
  10. type ComponentProvideOptions,
  11. type ComponentPublicInstance,
  12. type ComputedOptions,
  13. type ConcreteComponent,
  14. type CreateAppFunction,
  15. type CreateComponentPublicInstanceWithMixins,
  16. type DefineComponent,
  17. type Directive,
  18. type EmitsOptions,
  19. type EmitsToProps,
  20. type ExtractPropTypes,
  21. type GenericComponentInstance,
  22. type MethodOptions,
  23. type RenderFunction,
  24. type SetupContext,
  25. type SlotsType,
  26. type VNode,
  27. type VNodeProps,
  28. createVNode,
  29. defineComponent,
  30. nextTick,
  31. unref,
  32. useInstanceOption,
  33. warn,
  34. } from '@vue/runtime-core'
  35. import {
  36. camelize,
  37. extend,
  38. hasOwn,
  39. hyphenate,
  40. isArray,
  41. isPlainObject,
  42. toNumber,
  43. } from '@vue/shared'
  44. import { createApp, createSSRApp, render } from '.'
  45. // marker for attr removal
  46. const REMOVAL = {}
  47. export type VueElementConstructor<P = {}> = {
  48. new (initialProps?: Record<string, any>): VueElement & P
  49. }
  50. export interface CustomElementOptions {
  51. styles?: string[]
  52. shadowRoot?: boolean
  53. shadowRootOptions?: Omit<ShadowRootInit, 'mode'>
  54. nonce?: string
  55. configureApp?: (app: App) => void
  56. }
  57. // defineCustomElement provides the same type inference as defineComponent
  58. // so most of the following overloads should be kept in sync w/ defineComponent.
  59. // overload 1: direct setup function
  60. export function defineCustomElement<Props, RawBindings = object>(
  61. setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction,
  62. options?: Pick<ComponentOptions, 'name' | 'inheritAttrs' | 'emits'> &
  63. CustomElementOptions & {
  64. props?: (keyof Props)[]
  65. },
  66. ): VueElementConstructor<Props>
  67. export function defineCustomElement<Props, RawBindings = object>(
  68. setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction,
  69. options?: Pick<ComponentOptions, 'name' | 'inheritAttrs' | 'emits'> &
  70. CustomElementOptions & {
  71. props?: ComponentObjectPropsOptions<Props>
  72. },
  73. ): VueElementConstructor<Props>
  74. // overload 2: defineCustomElement with options object, infer props from options
  75. export function defineCustomElement<
  76. // props
  77. RuntimePropsOptions extends ComponentObjectPropsOptions =
  78. ComponentObjectPropsOptions,
  79. PropsKeys extends string = string,
  80. // emits
  81. RuntimeEmitsOptions extends EmitsOptions = {},
  82. EmitsKeys extends string = string,
  83. // other options
  84. Data = {},
  85. SetupBindings = {},
  86. Computed extends ComputedOptions = {},
  87. Methods extends MethodOptions = {},
  88. Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
  89. Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
  90. InjectOptions extends ComponentInjectOptions = {},
  91. InjectKeys extends string = string,
  92. Slots extends SlotsType = {},
  93. LocalComponents extends Record<string, Component> = {},
  94. Directives extends Record<string, Directive> = {},
  95. Exposed extends string = string,
  96. Provide extends ComponentProvideOptions = ComponentProvideOptions,
  97. // resolved types
  98. InferredProps = string extends PropsKeys
  99. ? ComponentObjectPropsOptions extends RuntimePropsOptions
  100. ? {}
  101. : ExtractPropTypes<RuntimePropsOptions>
  102. : { [key in PropsKeys]?: any },
  103. ResolvedProps = InferredProps & EmitsToProps<RuntimeEmitsOptions>,
  104. >(
  105. options: CustomElementOptions & {
  106. props?: (RuntimePropsOptions & ThisType<void>) | PropsKeys[]
  107. } & ComponentOptionsBase<
  108. ResolvedProps,
  109. SetupBindings,
  110. Data,
  111. Computed,
  112. Methods,
  113. Mixin,
  114. Extends,
  115. RuntimeEmitsOptions,
  116. EmitsKeys,
  117. {}, // Defaults
  118. InjectOptions,
  119. InjectKeys,
  120. Slots,
  121. LocalComponents,
  122. Directives,
  123. Exposed,
  124. Provide
  125. > &
  126. ThisType<
  127. CreateComponentPublicInstanceWithMixins<
  128. Readonly<ResolvedProps>,
  129. SetupBindings,
  130. Data,
  131. Computed,
  132. Methods,
  133. Mixin,
  134. Extends,
  135. RuntimeEmitsOptions,
  136. EmitsKeys,
  137. {},
  138. false,
  139. InjectOptions,
  140. Slots,
  141. LocalComponents,
  142. Directives,
  143. Exposed
  144. >
  145. >,
  146. extraOptions?: CustomElementOptions,
  147. ): VueElementConstructor<ResolvedProps>
  148. // overload 3: defining a custom element from the returned value of
  149. // `defineComponent`
  150. export function defineCustomElement<
  151. // this should be `ComponentPublicInstanceConstructor` but that type is not exported
  152. T extends { new (...args: any[]): ComponentPublicInstance<any> },
  153. >(
  154. options: T,
  155. extraOptions?: CustomElementOptions,
  156. ): VueElementConstructor<
  157. T extends DefineComponent<infer P, any, any, any> ? P : unknown
  158. >
  159. /*@__NO_SIDE_EFFECTS__*/
  160. export function defineCustomElement(
  161. options: any,
  162. extraOptions?: ComponentOptions,
  163. /**
  164. * @internal
  165. */
  166. _createApp?: CreateAppFunction<Element>,
  167. ): VueElementConstructor {
  168. let Comp = defineComponent(options, extraOptions) as any
  169. if (isPlainObject(Comp)) Comp = extend({}, Comp, extraOptions)
  170. class VueCustomElement extends VueElement {
  171. static def = Comp
  172. constructor(initialProps?: Record<string, any>) {
  173. super(Comp, initialProps, _createApp)
  174. }
  175. }
  176. return VueCustomElement
  177. }
  178. /*@__NO_SIDE_EFFECTS__*/
  179. export const defineSSRCustomElement = ((
  180. options: any,
  181. extraOptions?: ComponentOptions,
  182. ) => {
  183. // @ts-expect-error
  184. return defineCustomElement(options, extraOptions, createSSRApp)
  185. }) as typeof defineCustomElement
  186. const BaseClass = (
  187. typeof HTMLElement !== 'undefined' ? HTMLElement : class {}
  188. ) as typeof HTMLElement
  189. type InnerComponentDef = ConcreteComponent & CustomElementOptions
  190. export abstract class VueElementBase<
  191. E = Element,
  192. C = Component,
  193. Def extends CustomElementOptions & { props?: any } = InnerComponentDef,
  194. >
  195. extends BaseClass
  196. implements ComponentCustomElementInterface
  197. {
  198. _isVueCE = true
  199. /**
  200. * @internal
  201. */
  202. _instance: GenericComponentInstance | null = null
  203. /**
  204. * @internal
  205. */
  206. _app: App | null = null
  207. /**
  208. * @internal
  209. */
  210. _root: Element | ShadowRoot
  211. /**
  212. * @internal
  213. */
  214. _nonce: string | undefined
  215. /**
  216. * @internal
  217. */
  218. _teleportTargets?: Set<Element>
  219. protected _connected = false
  220. protected _resolved = false
  221. protected _numberProps: Record<string, true> | null = null
  222. protected _styleChildren: WeakSet<object> = new WeakSet()
  223. protected _styleAnchors: WeakMap<ConcreteComponent, HTMLStyleElement> =
  224. new WeakMap()
  225. protected _pendingResolve: Promise<void> | undefined
  226. protected _parent: VueElementBase | undefined
  227. protected _patching = false
  228. protected _dirty = false
  229. protected _def: Def
  230. protected _props: Record<string, any>
  231. protected _createApp: CreateAppFunction<E, C>
  232. /**
  233. * dev only
  234. */
  235. protected _styles?: HTMLStyleElement[]
  236. /**
  237. * dev only
  238. */
  239. protected _childStyles?: Map<string, HTMLStyleElement[]>
  240. protected _ob?: MutationObserver | null = null
  241. protected _slots?: Record<string, Node[]>
  242. /**
  243. * Check if this custom element needs hydration.
  244. * Returns true if it has a pre-rendered declarative shadow root that
  245. * needs to be hydrated.
  246. */
  247. protected abstract _needsHydration(): boolean
  248. protected abstract _mount(def: Def): void
  249. protected abstract _update(): void
  250. protected abstract _unmount(): void
  251. protected abstract _updateSlotNodes(slot: Map<Node, Node[]>): void
  252. constructor(
  253. /**
  254. * Component def - note this may be an AsyncWrapper, and this._def will
  255. * be overwritten by the inner component when resolved.
  256. */
  257. def: Def,
  258. props: Record<string, any> | undefined = {},
  259. createAppFn: CreateAppFunction<E, C>,
  260. ) {
  261. super()
  262. this._def = def
  263. this._props = props
  264. this._createApp = createAppFn
  265. this._nonce = def.nonce
  266. if (this._needsHydration()) {
  267. this._root = this.shadowRoot!
  268. } else {
  269. if (def.shadowRoot !== false) {
  270. this.attachShadow(
  271. extend({}, def.shadowRootOptions, {
  272. mode: 'open',
  273. }) as ShadowRootInit,
  274. )
  275. this._root = this.shadowRoot!
  276. } else {
  277. this._root = this
  278. }
  279. }
  280. }
  281. connectedCallback(): void {
  282. // avoid resolving component if it's not connected
  283. if (!this.isConnected) return
  284. // avoid re-parsing slots if already resolved
  285. if (!this.shadowRoot && !this._resolved) {
  286. this._parseSlots()
  287. }
  288. this._connected = true
  289. // locate nearest Vue custom element parent for provide/inject
  290. let parent: Node | null = this
  291. while (
  292. (parent =
  293. parent &&
  294. // #12479 should check assignedSlot first to get correct parent
  295. ((parent as Element).assignedSlot ||
  296. parent.parentNode ||
  297. (parent as ShadowRoot).host))
  298. ) {
  299. if (parent instanceof VueElementBase) {
  300. this._parent = parent
  301. break
  302. }
  303. }
  304. if (!this._instance) {
  305. if (this._resolved) {
  306. this._mountComponent(this._def)
  307. } else {
  308. if (parent && parent._pendingResolve) {
  309. this._pendingResolve = parent._pendingResolve.then(() => {
  310. this._pendingResolve = undefined
  311. this._resolveDef()
  312. })
  313. } else {
  314. this._resolveDef()
  315. }
  316. }
  317. }
  318. }
  319. disconnectedCallback(): void {
  320. this._connected = false
  321. nextTick(() => {
  322. if (!this._connected) {
  323. if (this._ob) {
  324. this._ob.disconnect()
  325. this._ob = null
  326. }
  327. this._unmount()
  328. if (this._teleportTargets) {
  329. this._teleportTargets.clear()
  330. this._teleportTargets = undefined
  331. }
  332. }
  333. })
  334. }
  335. protected _setParent(
  336. parent: VueElementBase | undefined = this._parent,
  337. ): void {
  338. if (parent && this._instance) {
  339. this._instance.parent = parent._instance
  340. this._inheritParentContext(parent)
  341. }
  342. }
  343. protected _inheritParentContext(
  344. parent: VueElementBase | undefined = this._parent,
  345. ): void {
  346. // #13212, the provides object of the app context must inherit the provides
  347. // object from the parent element so we can inject values from both places
  348. if (parent && this._app) {
  349. Object.setPrototypeOf(
  350. this._app._context.provides,
  351. parent._instance!.provides,
  352. )
  353. }
  354. }
  355. private _processMutations(mutations: MutationRecord[]) {
  356. for (const m of mutations) {
  357. this._setAttr(m.attributeName!)
  358. }
  359. }
  360. /**
  361. * resolve inner component definition (handle possible async component)
  362. */
  363. private _resolveDef() {
  364. if (this._pendingResolve) {
  365. return
  366. }
  367. // set initial attrs
  368. for (let i = 0; i < this.attributes.length; i++) {
  369. this._setAttr(this.attributes[i].name)
  370. }
  371. // watch future attr changes
  372. this._ob = new MutationObserver(this._processMutations.bind(this))
  373. this._ob.observe(this, { attributes: true })
  374. const resolve = (def: Def) => {
  375. this._resolved = true
  376. this._pendingResolve = undefined
  377. const { props, styles } = def
  378. // cast Number-type props set before resolve
  379. let numberProps
  380. if (props && !isArray(props)) {
  381. for (const key in props) {
  382. const opt = props[key]
  383. if (opt === Number || (opt && opt.type === Number)) {
  384. if (key in this._props) {
  385. this._props[key] = toNumber(this._props[key])
  386. }
  387. ;(numberProps || (numberProps = Object.create(null)))[
  388. camelize(key)
  389. ] = true
  390. }
  391. }
  392. }
  393. this._numberProps = numberProps
  394. this._resolveProps(def)
  395. // apply CSS
  396. if (this.shadowRoot) {
  397. this._applyStyles(styles)
  398. } else if (__DEV__ && styles) {
  399. warn(
  400. 'Custom element style injection is not supported when using ' +
  401. 'shadowRoot: false',
  402. )
  403. }
  404. // initial mount
  405. this._mountComponent(def)
  406. }
  407. const asyncDef = (this._def as ComponentOptions).__asyncLoader
  408. if (asyncDef) {
  409. const { configureApp } = this._def
  410. this._pendingResolve = asyncDef().then((def: any) => {
  411. def.configureApp = configureApp
  412. this._def = def
  413. resolve(def)
  414. })
  415. } else {
  416. resolve(this._def)
  417. }
  418. }
  419. private _mountComponent(def: Def): void {
  420. this._mount(def)
  421. // apply expose after mount
  422. this._processExposed()
  423. }
  424. protected _processExposed(): void {
  425. const exposed = this._instance && this._instance.exposed
  426. if (!exposed) return
  427. for (const key in exposed) {
  428. if (!hasOwn(this, key)) {
  429. // exposed properties are readonly
  430. Object.defineProperty(this, key, {
  431. // unwrap ref to be consistent with public instance behavior
  432. get: () => unref(exposed[key]),
  433. })
  434. } else if (__DEV__) {
  435. warn(`Exposed property "${key}" already exists on custom element.`)
  436. }
  437. }
  438. }
  439. protected _processInstance(): void {
  440. this._instance!.ce = this
  441. this._instance!.isCE = true
  442. if (__DEV__) {
  443. this._instance!.ceReload = newStyles => {
  444. if (this._styles) {
  445. this._styles.forEach(s => this._root.removeChild(s))
  446. this._styles.length = 0
  447. }
  448. this._styleAnchors.delete(this._def)
  449. this._applyStyles(newStyles)
  450. if (!this._instance!.vapor) {
  451. this._instance = null
  452. }
  453. this._update()
  454. }
  455. }
  456. const dispatch = (event: string, args: any[]) => {
  457. this.dispatchEvent(
  458. new CustomEvent(
  459. event,
  460. isPlainObject(args[0])
  461. ? extend({ detail: args }, args[0])
  462. : { detail: args },
  463. ),
  464. )
  465. }
  466. this._instance!.emit = (event: string, ...args: any[]) => {
  467. dispatch(event, args)
  468. if (hyphenate(event) !== event) {
  469. dispatch(hyphenate(event), args)
  470. }
  471. }
  472. this._setParent()
  473. }
  474. private _resolveProps(def: Def): void {
  475. const { props } = def
  476. const declaredPropKeys = isArray(props) ? props : Object.keys(props || {})
  477. // check if there are props set pre-upgrade or connect
  478. for (const key of Object.keys(this)) {
  479. if (key[0] !== '_' && declaredPropKeys.includes(key)) {
  480. this._setProp(key, this[key as keyof this])
  481. }
  482. }
  483. // defining getter/setters on prototype
  484. for (const key of declaredPropKeys.map(camelize)) {
  485. Object.defineProperty(this, key, {
  486. get(this: VueElement) {
  487. return this._getProp(key)
  488. },
  489. set(this: VueElement, val) {
  490. this._setProp(key, val, true, !this._patching)
  491. },
  492. })
  493. }
  494. }
  495. private _setAttr(key: string): void {
  496. if (key.startsWith('data-v-')) return
  497. const has = this.hasAttribute(key)
  498. let value = has ? this.getAttribute(key) : REMOVAL
  499. const camelKey = camelize(key)
  500. if (has && this._numberProps && this._numberProps[camelKey]) {
  501. value = toNumber(value)
  502. }
  503. this._setProp(camelKey, value, false, true)
  504. }
  505. /**
  506. * @internal
  507. */
  508. protected _getProp(key: string): any {
  509. return this._props[key]
  510. }
  511. /**
  512. * @internal
  513. */
  514. _setProp(
  515. key: string,
  516. val: any,
  517. shouldReflect = true,
  518. shouldUpdate = false,
  519. ): void {
  520. if (val !== this._props[key]) {
  521. this._dirty = true
  522. if (val === REMOVAL) {
  523. delete this._props[key]
  524. } else {
  525. this._props[key] = val
  526. // support set key on ceVNode
  527. if (key === 'key' && this._app && this._app._ceVNode) {
  528. this._app._ceVNode!.key = val
  529. }
  530. }
  531. if (shouldUpdate && this._instance) {
  532. this._update()
  533. }
  534. // reflect
  535. if (shouldReflect) {
  536. const ob = this._ob
  537. if (ob) {
  538. this._processMutations(ob.takeRecords())
  539. ob.disconnect()
  540. }
  541. if (val === true) {
  542. this.setAttribute(hyphenate(key), '')
  543. } else if (typeof val === 'string' || typeof val === 'number') {
  544. this.setAttribute(hyphenate(key), val + '')
  545. } else if (!val) {
  546. this.removeAttribute(hyphenate(key))
  547. }
  548. ob && ob.observe(this, { attributes: true })
  549. }
  550. }
  551. }
  552. protected _applyStyles(
  553. styles: string[] | undefined,
  554. owner?: ConcreteComponent,
  555. parentComp?: ConcreteComponent,
  556. ): void {
  557. if (!styles) return
  558. if (owner) {
  559. if (owner === this._def || this._styleChildren.has(owner)) {
  560. return
  561. }
  562. this._styleChildren.add(owner)
  563. }
  564. const nonce = this._nonce
  565. const root = this.shadowRoot!
  566. const insertionAnchor = parentComp
  567. ? this._getStyleAnchor(parentComp) || this._getStyleAnchor(this._def)
  568. : this._getRootStyleInsertionAnchor(root)
  569. let last: HTMLStyleElement | null = null
  570. for (let i = styles.length - 1; i >= 0; i--) {
  571. const s = document.createElement('style')
  572. if (nonce) s.setAttribute('nonce', nonce)
  573. s.textContent = styles[i]
  574. root.insertBefore(s, last || insertionAnchor)
  575. last = s
  576. if (i === 0) {
  577. if (!parentComp) this._styleAnchors.set(this._def, s)
  578. if (owner) this._styleAnchors.set(owner, s)
  579. }
  580. // record for HMR
  581. if (__DEV__) {
  582. if (owner) {
  583. if (owner.__hmrId) {
  584. if (!this._childStyles) this._childStyles = new Map()
  585. let entry = this._childStyles.get(owner.__hmrId)
  586. if (!entry) {
  587. this._childStyles.set(owner.__hmrId, (entry = []))
  588. }
  589. entry.push(s)
  590. }
  591. } else {
  592. ;(this._styles || (this._styles = [])).push(s)
  593. }
  594. }
  595. }
  596. }
  597. private _getStyleAnchor(comp?: ConcreteComponent): HTMLStyleElement | null {
  598. if (!comp) {
  599. return null
  600. }
  601. const anchor = this._styleAnchors.get(comp)
  602. if (anchor && anchor.parentNode === this.shadowRoot) {
  603. return anchor
  604. }
  605. if (anchor) {
  606. this._styleAnchors.delete(comp)
  607. }
  608. return null
  609. }
  610. private _getRootStyleInsertionAnchor(root: ShadowRoot): ChildNode | null {
  611. for (let i = 0; i < root.childNodes.length; i++) {
  612. const node = root.childNodes[i]
  613. if (!(node instanceof HTMLStyleElement)) {
  614. return node
  615. }
  616. }
  617. return null
  618. }
  619. /**
  620. * Only called when shadowRoot is false
  621. */
  622. private _parseSlots() {
  623. const slots: VueElementBase['_slots'] = (this._slots = {})
  624. let n
  625. while ((n = this.firstChild)) {
  626. const slotName =
  627. (n.nodeType === 1 && (n as Element).getAttribute('slot')) || 'default'
  628. ;(slots[slotName] || (slots[slotName] = [])).push(n)
  629. this.removeChild(n)
  630. }
  631. }
  632. /**
  633. * Only called when shadowRoot is false
  634. */
  635. protected _renderSlots(): void {
  636. const outlets = this._getSlots()
  637. const scopeId = this._instance!.type.__scopeId
  638. const slotReplacements: Map<Node, Node[]> = new Map()
  639. for (let i = 0; i < outlets.length; i++) {
  640. const o = outlets[i] as HTMLSlotElement
  641. const slotName = o.getAttribute('name') || 'default'
  642. const content = this._slots![slotName]
  643. const parent = o.parentNode!
  644. const replacementNodes: Node[] = []
  645. if (content) {
  646. for (const n of content) {
  647. // for :slotted css
  648. if (scopeId && n.nodeType === 1) {
  649. const id = scopeId + '-s'
  650. const walker = document.createTreeWalker(n, 1)
  651. ;(n as Element).setAttribute(id, '')
  652. let child
  653. while ((child = walker.nextNode())) {
  654. ;(child as Element).setAttribute(id, '')
  655. }
  656. }
  657. parent.insertBefore(n, o)
  658. replacementNodes.push(n)
  659. }
  660. } else {
  661. while (o.firstChild) {
  662. const child = o.firstChild
  663. parent.insertBefore(child, o)
  664. replacementNodes.push(child)
  665. }
  666. }
  667. parent.removeChild(o)
  668. slotReplacements.set(o, replacementNodes)
  669. }
  670. this._updateSlotNodes(slotReplacements)
  671. }
  672. /**
  673. * @internal
  674. */
  675. private _getSlots(): HTMLSlotElement[] {
  676. const roots: Element[] = [this]
  677. if (this._teleportTargets) {
  678. roots.push(...this._teleportTargets)
  679. }
  680. const slots = new Set<HTMLSlotElement>()
  681. for (const root of roots) {
  682. const found = root.querySelectorAll<HTMLSlotElement>('slot')
  683. for (let i = 0; i < found.length; i++) {
  684. slots.add(found[i])
  685. }
  686. }
  687. return Array.from(slots)
  688. }
  689. /**
  690. * @internal
  691. */
  692. _injectChildStyle(
  693. comp: ConcreteComponent & CustomElementOptions,
  694. parentComp?: ConcreteComponent,
  695. ): void {
  696. this._applyStyles(comp.styles, comp, parentComp)
  697. }
  698. /**
  699. * @internal
  700. */
  701. _beginPatch(): void {
  702. this._patching = true
  703. this._dirty = false
  704. }
  705. /**
  706. * @internal
  707. */
  708. _endPatch(): void {
  709. this._patching = false
  710. if (this._dirty && this._instance) {
  711. this._update()
  712. }
  713. }
  714. /**
  715. * @internal
  716. */
  717. _hasShadowRoot(): boolean {
  718. return this._def.shadowRoot !== false
  719. }
  720. /**
  721. * @internal
  722. */
  723. _removeChildStyle(comp: ConcreteComponent): void {
  724. if (__DEV__) {
  725. this._styleChildren.delete(comp)
  726. this._styleAnchors.delete(comp)
  727. if (this._childStyles && comp.__hmrId) {
  728. // clear old styles
  729. const oldStyles = this._childStyles.get(comp.__hmrId)
  730. if (oldStyles) {
  731. oldStyles.forEach(s => this._root.removeChild(s))
  732. oldStyles.length = 0
  733. }
  734. }
  735. }
  736. }
  737. }
  738. export class VueElement extends VueElementBase<
  739. Element,
  740. Component,
  741. InnerComponentDef
  742. > {
  743. constructor(
  744. def: InnerComponentDef,
  745. props: Record<string, any> | undefined = {},
  746. createAppFn: CreateAppFunction<Element, Component> = createApp,
  747. ) {
  748. super(def, props, createAppFn)
  749. }
  750. protected _needsHydration(): boolean {
  751. if (this.shadowRoot && this._createApp !== createApp) {
  752. return true
  753. } else {
  754. if (__DEV__ && this.shadowRoot) {
  755. warn(
  756. `Custom element has pre-rendered declarative shadow root but is not ` +
  757. `defined as hydratable. Use \`defineSSRCustomElement\`.`,
  758. )
  759. }
  760. }
  761. return false
  762. }
  763. protected _mount(def: InnerComponentDef): void {
  764. if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && !def.name) {
  765. // @ts-expect-error
  766. def.name = 'VueElement'
  767. }
  768. this._app = this._createApp(def)
  769. this._inheritParentContext()
  770. if (def.configureApp) {
  771. def.configureApp(this._app)
  772. }
  773. this._app._ceVNode = this._createVNode()
  774. this._app.mount(this._root)
  775. }
  776. protected _update(): void {
  777. if (!this._app) return
  778. const vnode = this._createVNode()
  779. vnode.appContext = this._app._context
  780. render(vnode, this._root)
  781. }
  782. protected _unmount(): void {
  783. if (this._app) {
  784. this._app.unmount()
  785. }
  786. if (this._instance && this._instance.ce) {
  787. this._instance.ce = undefined
  788. }
  789. this._app = this._instance = null
  790. }
  791. /**
  792. * Only called when shadowRoot is false
  793. */
  794. protected _updateSlotNodes(replacements: Map<Node, Node[]>): void {
  795. // do nothing
  796. }
  797. private _createVNode(): VNode<any, any> {
  798. const baseProps: VNodeProps = {}
  799. if (!this.shadowRoot) {
  800. baseProps.onVnodeMounted = baseProps.onVnodeUpdated =
  801. this._renderSlots.bind(this)
  802. }
  803. const vnode = createVNode(this._def, extend(baseProps, this._props))
  804. if (!this._instance) {
  805. vnode.ce = instance => {
  806. this._instance = instance
  807. this._processInstance()
  808. }
  809. }
  810. return vnode
  811. }
  812. }
  813. export function useHost(caller?: string): VueElementBase | null {
  814. const { hasInstance, value } = useInstanceOption('ce', true)
  815. const el = value as VueElementBase
  816. if (el) {
  817. return el
  818. } else if (__DEV__) {
  819. if (!hasInstance) {
  820. warn(
  821. `${caller || 'useHost'} called without an active component instance.`,
  822. )
  823. } else {
  824. warn(
  825. `${caller || 'useHost'} can only be used in components defined via ` +
  826. `defineCustomElement.`,
  827. )
  828. }
  829. }
  830. return null
  831. }
  832. /**
  833. * Retrieve the shadowRoot of the current custom element. Only usable in setup()
  834. * of a `defineCustomElement` component.
  835. */
  836. export function useShadowRoot(): ShadowRoot | null {
  837. const el = __DEV__ ? useHost('useShadowRoot') : useHost()
  838. return el && el.shadowRoot
  839. }