11import { BUILD } from '@app-data' ;
22
33import type * as d from '../declarations' ;
4+ import { internalCall } from './dom-extras' ;
45import { NODE_TYPE } from './runtime-constants' ;
56
67/**
78 * Adjust the `.hidden` property as-needed on any nodes in a DOM subtree which
8- * are slot fallbacks nodes - `<slot-fb>...</slot-fb>`
9+ * are slot fallback nodes - `<slot-fb>...</slot-fb>`
910 *
1011 * A slot fallback node should be visible by default. Then, it should be
1112 * conditionally hidden if:
@@ -17,15 +18,15 @@ import { NODE_TYPE } from './runtime-constants';
1718 * @param elm the element of interest
1819 */
1920export const updateFallbackSlotVisibility = ( elm : d . RenderNode ) => {
20- const childNodes : d . RenderNode [ ] = elm . __childNodes || ( elm . childNodes as any ) ;
21+ const childNodes = internalCall ( elm , 'childNodes' ) ;
2122
2223 // is this is a stencil component?
2324 if ( elm . tagName && elm . tagName . includes ( '-' ) && elm [ 's-cr' ] && elm . tagName !== 'SLOT-FB' ) {
2425 // stencil component - try to find any slot fallback nodes
2526 getHostSlotNodes ( childNodes as any , ( elm as HTMLElement ) . tagName ) . forEach ( ( slotNode ) => {
2627 if ( slotNode . nodeType === NODE_TYPE . ElementNode && slotNode . tagName === 'SLOT-FB' ) {
2728 // this is a slot fallback node
28- if ( getHostSlotChildNodes ( slotNode , slotNode [ 's-sn' ] , false ) ?. length ) {
29+ if ( getSlotChildSiblings ( slotNode , getSlotName ( slotNode ) , false ) ?. length ) {
2930 // has slotted nodes, hide fallback
3031 slotNode . hidden = true ;
3132 } else {
@@ -35,8 +36,11 @@ export const updateFallbackSlotVisibility = (elm: d.RenderNode) => {
3536 }
3637 } ) ;
3738 }
38- for ( const childNode of childNodes ) {
39- if ( childNode . nodeType === NODE_TYPE . ElementNode && ( childNode . __childNodes || childNode . childNodes ) . length ) {
39+
40+ let i = 0 ;
41+ for ( i = 0 ; i < childNodes . length ; i ++ ) {
42+ const childNode = childNodes [ i ] as d . RenderNode ;
43+ if ( childNode . nodeType === NODE_TYPE . ElementNode && internalCall ( childNode , 'childNodes' ) . length ) {
4044 // keep drilling down
4145 updateFallbackSlotVisibility ( childNode ) ;
4246 }
@@ -54,7 +58,7 @@ export const updateFallbackSlotVisibility = (elm: d.RenderNode) => {
5458 * @returns An array of slotted reference nodes.
5559 */
5660export const getSlottedChildNodes = ( childNodes : NodeListOf < ChildNode > ) : d . PatchedSlotNode [ ] => {
57- const result = [ ] ;
61+ const result : d . PatchedSlotNode [ ] = [ ] ;
5862 for ( let i = 0 ; i < childNodes . length ; i ++ ) {
5963 const slottedNode = ( ( childNodes [ i ] as d . RenderNode ) [ 's-nr' ] as d . PatchedSlotNode ) || undefined ;
6064 if ( slottedNode && slottedNode . isConnected ) {
@@ -71,7 +75,7 @@ export const getSlottedChildNodes = (childNodes: NodeListOf<ChildNode>): d.Patch
7175 * @param slotName the name of the slot to match on.
7276 * @returns a reference to the slot node that matches the provided name, `null` otherwise
7377 */
74- export function getHostSlotNodes ( childNodes : NodeListOf < ChildNode > , hostName : string , slotName ?: string ) {
78+ export function getHostSlotNodes ( childNodes : NodeListOf < ChildNode > , hostName ? : string , slotName ?: string ) {
7579 let i = 0 ;
7680 let slottedNodes : d . RenderNode [ ] = [ ] ;
7781 let childNode : d . RenderNode ;
@@ -80,8 +84,8 @@ export function getHostSlotNodes(childNodes: NodeListOf<ChildNode>, hostName: st
8084 childNode = childNodes [ i ] as any ;
8185 if (
8286 childNode [ 's-sr' ] &&
83- childNode [ 's-hn' ] === hostName &&
84- ( slotName === undefined || childNode [ 's-sn' ] === slotName )
87+ ( ! hostName || childNode [ 's-hn' ] === hostName ) &&
88+ ( slotName === undefined || getSlotName ( childNode ) === slotName )
8589 ) {
8690 slottedNodes . push ( childNode ) ;
8791 if ( typeof slotName !== 'undefined' ) return slottedNodes ;
@@ -92,18 +96,19 @@ export function getHostSlotNodes(childNodes: NodeListOf<ChildNode>, hostName: st
9296}
9397
9498/**
95- * Get slotted child nodes of a slot node
96- * @param node - the slot node to get the child nodes from
99+ * Get all ' child' sibling nodes of a slot node
100+ * @param slot - the slot node to get the child nodes from
97101 * @param slotName - the name of the slot to match on
98102 * @param includeSlot - whether to include the slot node in the result
99- * @returns slotted child nodes of the slot node
103+ * @returns child nodes of the slot node
100104 */
101- export const getHostSlotChildNodes = ( node : d . RenderNode , slotName : string , includeSlot = true ) => {
105+ export const getSlotChildSiblings = ( slot : d . RenderNode , slotName : string , includeSlot = true ) => {
102106 const childNodes : d . RenderNode [ ] = [ ] ;
103- if ( ( includeSlot && node [ 's-sr' ] ) || ! node [ 's-sr' ] ) childNodes . push ( node as any ) ;
107+ if ( ( includeSlot && slot [ 's-sr' ] ) || ! slot [ 's-sr' ] ) childNodes . push ( slot as any ) ;
108+ let node = slot ;
104109
105- while ( ( node = node . nextSibling as any ) && ( node as d . RenderNode ) [ 's-sn' ] === slotName ) {
106- childNodes . push ( node as any ) ;
110+ while ( ( node = node . nextSibling as any ) ) {
111+ if ( getSlotName ( node ) === slotName ) childNodes . push ( node as any ) ;
107112 }
108113 return childNodes ;
109114} ;
@@ -150,37 +155,34 @@ export const addSlotRelocateNode = (
150155 prepend ?: boolean ,
151156 position ?: number ,
152157) => {
153- let slottedNodeLocation : d . RenderNode ;
154-
155- // does newChild already have a slot location node?
156158 if ( newChild [ 's-ol' ] && newChild [ 's-ol' ] . isConnected ) {
157- slottedNodeLocation = newChild [ 's-ol' ] ;
158- } else {
159- slottedNodeLocation = document . createTextNode ( '' ) as any ;
160- slottedNodeLocation [ 's-nr' ] = newChild ;
159+ // newChild already has a slot location node
160+ return ;
161161 }
162162
163+ const slottedNodeLocation = document . createTextNode ( '' ) as any ;
164+ slottedNodeLocation [ 's-nr' ] = newChild ;
165+
166+ // if there's no content reference node, or parentNode we can't do anything
163167 if ( ! slotNode [ 's-cr' ] || ! slotNode [ 's-cr' ] . parentNode ) return ;
164168
165169 const parent = slotNode [ 's-cr' ] . parentNode as any ;
166- const appendMethod = prepend ? parent . __prepend || parent . prepend : parent . __appendChild || parent . appendChild ;
167-
168- if ( typeof position !== 'undefined' ) {
169- if ( BUILD . hydrateClientSide ) {
170- slottedNodeLocation [ 's-oo' ] = position ;
171- const childNodes = ( parent . __childNodes || parent . childNodes ) as NodeListOf < d . RenderNode > ;
172- const slotRelocateNodes : d . RenderNode [ ] = [ slottedNodeLocation ] ;
173- childNodes . forEach ( ( n ) => {
174- if ( n [ 's-nr' ] ) slotRelocateNodes . push ( n ) ;
175- } ) ;
170+ const appendMethod = prepend ? internalCall ( parent , 'prepend' ) : internalCall ( parent , 'appendChild' ) ;
176171
177- slotRelocateNodes . sort ( ( a , b ) => {
178- if ( ! a [ 's-oo' ] || a [ 's-oo' ] < b [ 's-oo' ] ) return - 1 ;
179- else if ( ! b [ 's-oo' ] || b [ 's-oo' ] < a [ 's-oo' ] ) return 1 ;
180- return 0 ;
181- } ) ;
182- slotRelocateNodes . forEach ( ( n ) => appendMethod . call ( parent , n ) ) ;
183- }
172+ if ( BUILD . hydrateClientSide && typeof position !== 'undefined' ) {
173+ slottedNodeLocation [ 's-oo' ] = position ;
174+ const childNodes = internalCall ( parent , 'childNodes' ) as NodeListOf < d . RenderNode > ;
175+ const slotRelocateNodes : d . RenderNode [ ] = [ slottedNodeLocation ] ;
176+ childNodes . forEach ( ( n ) => {
177+ if ( n [ 's-nr' ] ) slotRelocateNodes . push ( n ) ;
178+ } ) ;
179+
180+ slotRelocateNodes . sort ( ( a , b ) => {
181+ if ( ! a [ 's-oo' ] || a [ 's-oo' ] < ( b [ 's-oo' ] || 0 ) ) return - 1 ;
182+ else if ( ! b [ 's-oo' ] || b [ 's-oo' ] < a [ 's-oo' ] ) return 1 ;
183+ return 0 ;
184+ } ) ;
185+ slotRelocateNodes . forEach ( ( n ) => appendMethod . call ( parent , n ) ) ;
184186 } else {
185187 appendMethod . call ( parent , slottedNodeLocation ) ;
186188 }
@@ -190,4 +192,75 @@ export const addSlotRelocateNode = (
190192} ;
191193
192194export const getSlotName = ( node : d . PatchedSlotNode ) =>
193- node [ 's-sn' ] || ( node . nodeType === 1 && ( node as Element ) . getAttribute ( 'slot' ) ) || '' ;
195+ typeof node [ 's-sn' ] === 'string'
196+ ? node [ 's-sn' ]
197+ : ( node . nodeType === 1 && ( node as Element ) . getAttribute ( 'slot' ) ) || undefined ;
198+
199+ /**
200+ * Add `assignedElements` and `assignedNodes` methods on a fake slot node
201+ *
202+ * @param node - slot node to patch
203+ */
204+ export function patchSlotNode ( node : d . RenderNode ) {
205+ if ( ( node as any ) . assignedElements || ( node as any ) . assignedNodes || ! node [ 's-sr' ] ) return ;
206+
207+ const assignedFactory = ( elementsOnly : boolean ) =>
208+ function ( opts ?: { flatten : boolean } ) {
209+ const toReturn : d . RenderNode [ ] = [ ] ;
210+ const slotName = this [ 's-sn' ] ;
211+
212+ if ( opts ?. flatten ) {
213+ console . error ( `
214+ Flattening is not supported for Stencil non-shadow slots.
215+ You can use \`.childNodes\` to nested slot fallback content.
216+ If you have a particular use case, please open an issue on the Stencil repo.
217+ ` ) ;
218+ }
219+
220+ const parent = this [ 's-cr' ] . parentElement as d . RenderNode ;
221+ // get all light dom nodes
222+ const slottedNodes = parent . __childNodes ? parent . childNodes : getSlottedChildNodes ( parent . childNodes ) ;
223+
224+ ( slottedNodes as d . RenderNode [ ] ) . forEach ( ( n ) => {
225+ // find all the nodes assigned to slots we care about
226+ if ( slotName === getSlotName ( n ) ) {
227+ toReturn . push ( n ) ;
228+ }
229+ } ) ;
230+
231+ if ( elementsOnly ) {
232+ return toReturn . filter ( ( n ) => n . nodeType === NODE_TYPE . ElementNode ) ;
233+ }
234+ return toReturn ;
235+ } . bind ( node ) ;
236+
237+ ( node as any ) . assignedElements = assignedFactory ( true ) ;
238+ ( node as any ) . assignedNodes = assignedFactory ( false ) ;
239+ }
240+
241+ /**
242+ * Dispatches a `slotchange` event on a fake `<slot />` node.
243+ *
244+ * @param elm the slot node to dispatch the event from
245+ */
246+ export function dispatchSlotChangeEvent ( elm : d . RenderNode ) {
247+ elm . dispatchEvent ( new CustomEvent ( 'slotchange' , { bubbles : false , cancelable : false , composed : false } ) ) ;
248+ }
249+
250+ /**
251+ * Find the slot node that a slotted node belongs to
252+ *
253+ * @param slottedNode - the slotted node to find the slot for
254+ * @param parentHost - the parent host element of the slotted node
255+ * @returns the slot node and slot name
256+ */
257+ export function findSlotFromSlottedNode ( slottedNode : d . PatchedSlotNode , parentHost ?: HTMLElement ) {
258+ parentHost = parentHost || slottedNode [ 's-ol' ] ?. parentElement ;
259+
260+ if ( ! parentHost ) return { slotNode : null , slotName : '' } ;
261+
262+ const slotName = ( slottedNode [ 's-sn' ] = getSlotName ( slottedNode ) || '' ) ;
263+ const childNodes = internalCall ( parentHost , 'childNodes' ) ;
264+ const slotNode = getHostSlotNodes ( childNodes , parentHost . tagName , slotName ) [ 0 ] ;
265+ return { slotNode, slotName } ;
266+ }
0 commit comments