@@ -38,16 +38,16 @@ export function isObjectSpread(config: SlotControllerArgs): config is [SlotsConf
3838 return config . length === 1 && typeof config [ 0 ] === 'object' && config [ 0 ] !== null ;
3939}
4040
41- /**
42- * If it's a named slot, return its children,
43- * for the default slot, look for direct children not assigned to a slot
44- * @param n slot name
45- */
46- const isSlot =
47- < T extends Element = Element > ( n : string | typeof SlotController . default ) =>
48- ( child : Element ) : child is T =>
49- n === SlotController . default ? ! child . hasAttribute ( 'slot' )
50- : child . getAttribute ( 'slot' ) === n ;
41+ function isContent ( node : Node ) {
42+ switch ( node . nodeType ) {
43+ case Node . TEXT_NODE :
44+ return ! ! node . textContent ?. trim ( ) ;
45+ case Node . COMMENT_NODE :
46+ return false ;
47+ default :
48+ return true ;
49+ }
50+ }
5151
5252export declare class SlotControllerPublicAPI implements ReactiveController {
5353 static default : symbol ;
@@ -98,25 +98,67 @@ export declare class SlotControllerPublicAPI implements ReactiveController {
9898 isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean ;
9999}
100100
101+ class SlotRecord {
102+ constructor (
103+ public slot : HTMLSlotElement ,
104+ public name : string | symbol ,
105+ private host : ReactiveElement ,
106+ ) { }
107+
108+ get elements ( ) {
109+ return this . slot ?. assignedElements ?.( ) ;
110+ }
111+
112+ get hasContent ( ) {
113+ if ( this . name === SlotController . default ) {
114+ return ! ! this . elements . length
115+ || ! ! [ ...this . host . childNodes ]
116+ . some ( node => {
117+ if ( node instanceof Element ) {
118+ return ! node . hasAttribute ( 'slot' ) ;
119+ } else {
120+ return isContent ( node ) ;
121+ }
122+ } ) ;
123+ } else {
124+ return ! ! this . slot . assignedNodes ( )
125+ . some ( isContent ) ;
126+ }
127+ }
128+ }
129+
101130export class SlotController implements SlotControllerPublicAPI {
102131 public static default = Symbol ( 'default slot' ) satisfies symbol as symbol ;
103132
104133 /** @deprecated use `default` */
105134 public static anonymous : symbol = this . default ;
106135
107- #nodes = new Map < string | typeof SlotController . default , Slot > ( ) ;
108-
109- #slotMapInitialized = false ;
136+ #slotRecords = new Map < string | typeof SlotController . default , SlotRecord > ( ) ;
110137
111- #slotNames: ( string | null ) [ ] = [ ] ;
138+ #slotNames: ( string | symbol | null ) [ ] = [ ] ;
112139
113140 #deprecations: Record < string , string > = { } ;
114141
115- #mo = new MutationObserver ( this . #initSlotMap. bind ( this ) ) ;
142+ #initSlotMap = async ( ) => {
143+ const { host } = this ;
144+ await host . updateComplete ;
145+ const slotRecords = this . #slotRecords;
146+ // Loop over the properties provided by the schema
147+ for ( let slotName of this . #slotNames. concat ( Object . values ( this . #deprecations) ) ) {
148+ slotName ||= SlotController . default ;
149+ const slot = this . #getSlotElement( slotName ) ;
150+ if ( slot ) {
151+ slotRecords . set ( slotName , new SlotRecord ( slot , slotName , host ) ) ;
152+ }
153+ }
154+ host . requestUpdate ( ) ;
155+ } ;
156+
157+ #mo = new MutationObserver ( this . #initSlotMap) ;
116158
117159 constructor ( public host : ReactiveElement , ...args : SlotControllerArgs ) {
118- this . #initialize( ...args ) ;
119160 host . addController ( this ) ;
161+ this . #initialize( ...args ) ;
120162 if ( ! this . #slotNames. length ) {
121163 this . #slotNames = [ null ] ;
122164 }
@@ -133,59 +175,27 @@ export class SlotController implements SlotControllerPublicAPI {
133175 }
134176 }
135177
178+ #getSlotElement( slotId : string | symbol ) {
179+ const selector =
180+ slotId === SlotController . default ? 'slot:not([name])' : `slot[name="${ slotId as string } "]` ;
181+ return this . host . shadowRoot ?. querySelector ?.< HTMLSlotElement > ( selector ) ?? null ;
182+ }
183+
136184 async hostConnected ( ) : Promise < void > {
137185 this . #mo. observe ( this . host , { childList : true } ) ;
138186 // Map the defined slots into an object that is easier to query
139- this . #nodes. clear ( ) ;
187+ this . #slotRecords. clear ( ) ;
188+ await this . host . updateComplete ;
140189 this . #initSlotMap( ) ;
141190 // insurance for framework integrations
142191 await this . host . updateComplete ;
143192 this . host . requestUpdate ( ) ;
144193 }
145194
146- hostUpdated ( ) : void {
147- if ( ! this . #slotMapInitialized) {
148- this . #initSlotMap( ) ;
149- }
150- }
151-
152195 hostDisconnected ( ) : void {
153196 this . #mo. disconnect ( ) ;
154197 }
155198
156- #initSlotMap( ) {
157- // Loop over the properties provided by the schema
158- for ( const slotName of this . #slotNames
159- . concat ( Object . values ( this . #deprecations) ) ) {
160- const slotId = slotName || SlotController . default ;
161- const name = slotName ?? '' ;
162- const elements = this . #getChildrenForSlot( slotId ) ;
163- const slot = this . #getSlotElement( slotId ) ;
164- const hasContent =
165- ! ! elements . length || ! ! slot ?. assignedNodes ?.( ) ?. filter ( x => x . textContent ?. trim ( ) ) . length ;
166- this . #nodes. set ( slotId , { elements, name, hasContent, slot } ) ;
167- }
168- this . host . requestUpdate ( ) ;
169- this . #slotMapInitialized = true ;
170- }
171-
172- #getSlotElement( slotId : string | symbol ) {
173- const selector =
174- slotId === SlotController . default ? 'slot:not([name])' : `slot[name="${ slotId as string } "]` ;
175- return this . host . shadowRoot ?. querySelector ?.< HTMLSlotElement > ( selector ) ?? null ;
176- }
177-
178- #getChildrenForSlot< T extends Element = Element > (
179- name : string | typeof SlotController . default ,
180- ) : T [ ] {
181- if ( this . #nodes. has ( name ) ) {
182- return ( this . #nodes. get ( name ) ! . slot ?. assignedElements ?.( ) ?? [ ] ) as T [ ] ;
183- } else {
184- const children = Array . from ( this . host . children ) as T [ ] ;
185- return children . filter ( isSlot ( name ) ) ;
186- }
187- }
188-
189199 /**
190200 * Given a slot name or slot names, returns elements assigned to the requested slots as an array.
191201 * If no value is provided, it returns all children not assigned to a slot (without a slot attribute).
@@ -203,12 +213,12 @@ export class SlotController implements SlotControllerPublicAPI {
203213 * this.getSlotted();
204214 * ```
205215 */
206- getSlotted < T extends Element = Element > ( ...slotNames : string [ ] ) : T [ ] {
207- if ( ! slotNames . length ) {
208- return ( this . #nodes . get ( SlotController . default ) ?. elements ?? [ ] ) as T [ ] ;
216+ public getSlotted < T extends Element = Element > ( ...slotNames : string [ ] | [ null ] ) : T [ ] {
217+ if ( ! slotNames . length || slotNames . length === 1 && slotNames . at ( 0 ) === null ) {
218+ return ( this . #slotRecords . get ( SlotController . default ) ?. elements ?? [ ] ) as T [ ] ;
209219 } else {
210220 return slotNames . flatMap ( slotName =>
211- this . #nodes . get ( slotName ) ?. elements ?? [ ] ) as T [ ] ;
221+ this . #slotRecords . get ( slotName ?? SlotController . default ) ?. elements ?? [ ] ) as T [ ] ;
212222 }
213223 }
214224
@@ -217,12 +227,20 @@ export class SlotController implements SlotControllerPublicAPI {
217227 * @param names The slot names to check.
218228 * @example this.hasSlotted('header');
219229 */
220- hasSlotted ( ...names : ( string | null | undefined ) [ ] ) : boolean {
221- const slotNames = Array . from ( names , x => x == null ? SlotController . default : x ) ;
230+ public hasSlotted ( ...names : ( string | null | undefined ) [ ] ) : boolean {
231+ const slotNames = Array . from ( names , x =>
232+ x == null ? SlotController . default : x ) ;
222233 if ( ! slotNames . length ) {
223234 slotNames . push ( SlotController . default ) ;
224235 }
225- return slotNames . some ( x => this . #nodes. get ( x ) ?. hasContent ?? false ) ;
236+ return slotNames . some ( slotName => {
237+ const slot = this . #slotRecords. get ( slotName ) ;
238+ if ( ! slot ) {
239+ return false ;
240+ } else {
241+ return slot . hasContent ;
242+ }
243+ } ) ;
226244 }
227245
228246 /**
@@ -232,7 +250,7 @@ export class SlotController implements SlotControllerPublicAPI {
232250 * @example this.isEmpty();
233251 * @returns
234252 */
235- isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean {
253+ public isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean {
236254 return ! this . hasSlotted ( ...names ) ;
237255 }
238256}
0 commit comments