@@ -21,6 +21,7 @@ import {
21
21
type SetupContext ,
22
22
type SlotsType ,
23
23
type VNode ,
24
+ type VNodeProps ,
24
25
createVNode ,
25
26
defineComponent ,
26
27
nextTick ,
@@ -33,21 +34,28 @@ export type VueElementConstructor<P = {}> = {
33
34
new ( initialProps ?: Record < string , any > ) : VueElement & P
34
35
}
35
36
37
+ export interface CustomElementOptions {
38
+ styles ?: string [ ]
39
+ shadowRoot ?: boolean
40
+ }
41
+
36
42
// defineCustomElement provides the same type inference as defineComponent
37
43
// so most of the following overloads should be kept in sync w/ defineComponent.
38
44
39
45
// overload 1: direct setup function
40
46
export function defineCustomElement < Props , RawBindings = object > (
41
47
setup : ( props : Props , ctx : SetupContext ) => RawBindings | RenderFunction ,
42
- options ?: Pick < ComponentOptions , 'name' | 'inheritAttrs' | 'emits' > & {
43
- props ?: ( keyof Props ) [ ]
44
- } ,
48
+ options ?: Pick < ComponentOptions , 'name' | 'inheritAttrs' | 'emits' > &
49
+ CustomElementOptions & {
50
+ props ?: ( keyof Props ) [ ]
51
+ } ,
45
52
) : VueElementConstructor < Props >
46
53
export function defineCustomElement < Props , RawBindings = object > (
47
54
setup : ( props : Props , ctx : SetupContext ) => RawBindings | RenderFunction ,
48
- options ?: Pick < ComponentOptions , 'name' | 'inheritAttrs' | 'emits' > & {
49
- props ?: ComponentObjectPropsOptions < Props >
50
- } ,
55
+ options ?: Pick < ComponentOptions , 'name' | 'inheritAttrs' | 'emits' > &
56
+ CustomElementOptions & {
57
+ props ?: ComponentObjectPropsOptions < Props >
58
+ } ,
51
59
) : VueElementConstructor < Props >
52
60
53
61
// overload 2: defineCustomElement with options object, infer props from options
@@ -81,27 +89,27 @@ export function defineCustomElement<
81
89
: { [ key in PropsKeys ] ?: any } ,
82
90
ResolvedProps = InferredProps & EmitsToProps < RuntimeEmitsOptions > ,
83
91
> (
84
- options : {
92
+ options : CustomElementOptions & {
85
93
props ?: ( RuntimePropsOptions & ThisType < void > ) | PropsKeys [ ]
86
94
} & ComponentOptionsBase <
87
- ResolvedProps ,
88
- SetupBindings ,
89
- Data ,
90
- Computed ,
91
- Methods ,
92
- Mixin ,
93
- Extends ,
94
- RuntimeEmitsOptions ,
95
- EmitsKeys ,
96
- { } , // Defaults
97
- InjectOptions ,
98
- InjectKeys ,
99
- Slots ,
100
- LocalComponents ,
101
- Directives ,
102
- Exposed ,
103
- Provide
104
- > &
95
+ ResolvedProps ,
96
+ SetupBindings ,
97
+ Data ,
98
+ Computed ,
99
+ Methods ,
100
+ Mixin ,
101
+ Extends ,
102
+ RuntimeEmitsOptions ,
103
+ EmitsKeys ,
104
+ { } , // Defaults
105
+ InjectOptions ,
106
+ InjectKeys ,
107
+ Slots ,
108
+ LocalComponents ,
109
+ Directives ,
110
+ Exposed ,
111
+ Provide
112
+ > &
105
113
ThisType <
106
114
CreateComponentPublicInstanceWithMixins <
107
115
Readonly < ResolvedProps > ,
@@ -163,7 +171,7 @@ const BaseClass = (
163
171
typeof HTMLElement !== 'undefined' ? HTMLElement : class { }
164
172
) as typeof HTMLElement
165
173
166
- type InnerComponentDef = ConcreteComponent & { styles ?: string [ ] }
174
+ type InnerComponentDef = ConcreteComponent & CustomElementOptions
167
175
168
176
export class VueElement extends BaseClass {
169
177
/**
@@ -176,22 +184,32 @@ export class VueElement extends BaseClass {
176
184
private _numberProps : Record < string , true > | null = null
177
185
private _styles ?: HTMLStyleElement [ ]
178
186
private _ob ?: MutationObserver | null = null
187
+ private _root : Element | ShadowRoot
188
+ private _slots ?: Record < string , Node [ ] >
189
+
179
190
constructor (
180
191
private _def : InnerComponentDef ,
181
192
private _props : Record < string , any > = { } ,
182
193
hydrate ?: RootHydrateFunction ,
183
194
) {
184
195
super ( )
196
+ // TODO handle non-shadowRoot hydration
185
197
if ( this . shadowRoot && hydrate ) {
186
198
hydrate ( this . _createVNode ( ) , this . shadowRoot )
199
+ this . _root = this . shadowRoot
187
200
} else {
188
201
if ( __DEV__ && this . shadowRoot ) {
189
202
warn (
190
203
`Custom element has pre-rendered declarative shadow root but is not ` +
191
204
`defined as hydratable. Use \`defineSSRCustomElement\`.` ,
192
205
)
193
206
}
194
- this . attachShadow ( { mode : 'open' } )
207
+ if ( _def . shadowRoot !== false ) {
208
+ this . attachShadow ( { mode : 'open' } )
209
+ this . _root = this . shadowRoot !
210
+ } else {
211
+ this . _root = this
212
+ }
195
213
if ( ! ( this . _def as ComponentOptions ) . __asyncLoader ) {
196
214
// for sync component defs we can immediately resolve props
197
215
this . _resolveProps ( this . _def )
@@ -200,6 +218,9 @@ export class VueElement extends BaseClass {
200
218
}
201
219
202
220
connectedCallback ( ) {
221
+ if ( ! this . shadowRoot ) {
222
+ this . _parseSlots ( )
223
+ }
203
224
this . _connected = true
204
225
if ( ! this . _instance ) {
205
226
if ( this . _resolved ) {
@@ -218,7 +239,7 @@ export class VueElement extends BaseClass {
218
239
this . _ob . disconnect ( )
219
240
this . _ob = null
220
241
}
221
- render ( null , this . shadowRoot ! )
242
+ render ( null , this . _root )
222
243
this . _instance = null
223
244
}
224
245
} )
@@ -353,11 +374,16 @@ export class VueElement extends BaseClass {
353
374
}
354
375
355
376
private _update ( ) {
356
- render ( this . _createVNode ( ) , this . shadowRoot ! )
377
+ render ( this . _createVNode ( ) , this . _root )
357
378
}
358
379
359
380
private _createVNode ( ) : VNode < any , any > {
360
- const vnode = createVNode ( this . _def , extend ( { } , this . _props ) )
381
+ const baseProps : VNodeProps = { }
382
+ if ( ! this . shadowRoot ) {
383
+ baseProps . onVnodeMounted = baseProps . onVnodeUpdated =
384
+ this . _renderSlots . bind ( this )
385
+ }
386
+ const vnode = createVNode ( this . _def , extend ( baseProps , this . _props ) )
361
387
if ( ! this . _instance ) {
362
388
vnode . ce = instance => {
363
389
this . _instance = instance
@@ -367,7 +393,7 @@ export class VueElement extends BaseClass {
367
393
instance . ceReload = newStyles => {
368
394
// always reset styles
369
395
if ( this . _styles ) {
370
- this . _styles . forEach ( s => this . shadowRoot ! . removeChild ( s ) )
396
+ this . _styles . forEach ( s => this . _root . removeChild ( s ) )
371
397
this . _styles . length = 0
372
398
}
373
399
this . _applyStyles ( newStyles )
@@ -416,12 +442,58 @@ export class VueElement extends BaseClass {
416
442
styles . forEach ( css => {
417
443
const s = document . createElement ( 'style' )
418
444
s . textContent = css
419
- this . shadowRoot ! . appendChild ( s )
445
+ this . _root . appendChild ( s )
420
446
// record for HMR
421
447
if ( __DEV__ ) {
422
448
; ( this . _styles || ( this . _styles = [ ] ) ) . push ( s )
423
449
}
424
450
} )
425
451
}
426
452
}
453
+
454
+ /**
455
+ * Only called when shaddowRoot is false
456
+ */
457
+ private _parseSlots ( ) {
458
+ const slots : VueElement [ '_slots' ] = ( this . _slots = { } )
459
+ let n
460
+ while ( ( n = this . firstChild ) ) {
461
+ const slotName =
462
+ ( n . nodeType === 1 && ( n as Element ) . getAttribute ( 'slot' ) ) || 'default'
463
+ ; ( slots [ slotName ] || ( slots [ slotName ] = [ ] ) ) . push ( n )
464
+ this . removeChild ( n )
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Only called when shaddowRoot is false
470
+ */
471
+ private _renderSlots ( ) {
472
+ const outlets = this . querySelectorAll ( 'slot' )
473
+ const scopeId = this . _instance ! . type . __scopeId
474
+ for ( let i = 0 ; i < outlets . length ; i ++ ) {
475
+ const o = outlets [ i ] as HTMLSlotElement
476
+ const slotName = o . getAttribute ( 'name' ) || 'default'
477
+ const content = this . _slots ! [ slotName ]
478
+ const parent = o . parentNode !
479
+ if ( content ) {
480
+ for ( const n of content ) {
481
+ // for :slotted css
482
+ if ( scopeId && n . nodeType === 1 ) {
483
+ const id = scopeId + '-s'
484
+ const walker = document . createTreeWalker ( n , 1 )
485
+ ; ( n as Element ) . setAttribute ( id , '' )
486
+ let child
487
+ while ( ( child = walker . nextNode ( ) ) ) {
488
+ ; ( child as Element ) . setAttribute ( id , '' )
489
+ }
490
+ }
491
+ parent . insertBefore ( n , o )
492
+ }
493
+ } else {
494
+ while ( o . firstChild ) parent . insertBefore ( o . firstChild , o )
495
+ }
496
+ parent . removeChild ( o )
497
+ }
498
+ }
427
499
}
0 commit comments