@@ -49,17 +49,6 @@ function isContent(node: Node) {
4949 }
5050}
5151
52- /**
53- * If it's a named slot, return its children,
54- * for the default slot, look for direct children not assigned to a slot
55- * @param n slot name
56- */
57- const isSlot =
58- < T extends Element = Element > ( n : string | typeof SlotController . default ) =>
59- ( child : Element ) : child is T =>
60- n === SlotController . default ? ! child . hasAttribute ( 'slot' )
61- : child . getAttribute ( 'slot' ) === n ;
62-
6352export declare class SlotControllerPublicAPI implements ReactiveController {
6453 static default : symbol ;
6554
@@ -109,45 +98,67 @@ export declare class SlotControllerPublicAPI implements ReactiveController {
10998 isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean ;
11099}
111100
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+
112130export class SlotController implements SlotControllerPublicAPI {
113131 public static default = Symbol ( 'default slot' ) satisfies symbol as symbol ;
114132
115133 /** @deprecated use `default` */
116134 public static anonymous : symbol = this . default ;
117135
118- #nodes = new Map < string | typeof SlotController . default , Slot > ( ) ;
136+ #slotRecords = new Map < string | typeof SlotController . default , SlotRecord > ( ) ;
119137
120- #slotMapInitialized = false ;
121-
122- #slotNames: ( string | null ) [ ] = [ ] ;
138+ #slotNames: ( string | symbol | null ) [ ] = [ ] ;
123139
124140 #deprecations: Record < string , string > = { } ;
125141
126142 #initSlotMap = async ( ) => {
127143 const { host } = this ;
128144 await host . updateComplete ;
129- const nodes = this . #nodes ;
145+ const slotRecords = this . #slotRecords ;
130146 // Loop over the properties provided by the schema
131- for ( const slotName of this . #slotNames
132- . concat ( Object . values ( this . #deprecations) ) ) {
133- const slotId = slotName || SlotController . default ;
134- const name = slotName ?? '' ;
135- const elements = this . #getChildrenForSlot( slotId ) ;
136- const slot = this . #getSlotElement( slotId ) ;
137- const hasContent =
138- slotId === SlotController . default ? ! ! [ ...host . childNodes ] . some ( isContent )
139- : ! ! slot ?. assignedNodes ?.( ) . some ( isContent ) ;
140- nodes . set ( slotId , { elements, name, hasContent, slot } ) ;
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+ }
141153 }
142154 host . requestUpdate ( ) ;
143- this . #slotMapInitialized = true ;
144155 } ;
145156
146157 #mo = new MutationObserver ( this . #initSlotMap) ;
147158
148159 constructor ( public host : ReactiveElement , ...args : SlotControllerArgs ) {
149- this . #initialize( ...args ) ;
150160 host . addController ( this ) ;
161+ this . #initialize( ...args ) ;
151162 if ( ! this . #slotNames. length ) {
152163 this . #slotNames = [ null ] ;
153164 }
@@ -164,43 +175,27 @@ export class SlotController implements SlotControllerPublicAPI {
164175 }
165176 }
166177
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+
167184 async hostConnected ( ) : Promise < void > {
168185 this . #mo. observe ( this . host , { childList : true } ) ;
169186 // Map the defined slots into an object that is easier to query
170- this . #nodes. clear ( ) ;
187+ this . #slotRecords. clear ( ) ;
188+ await this . host . updateComplete ;
171189 this . #initSlotMap( ) ;
172190 // insurance for framework integrations
173191 await this . host . updateComplete ;
174192 this . host . requestUpdate ( ) ;
175193 }
176194
177- hostUpdated ( ) : void {
178- if ( ! this . #slotMapInitialized) {
179- this . #initSlotMap( ) ;
180- }
181- }
182-
183195 hostDisconnected ( ) : void {
184196 this . #mo. disconnect ( ) ;
185197 }
186198
187- #getSlotElement( slotId : string | symbol ) {
188- const selector =
189- slotId === SlotController . default ? 'slot:not([name])' : `slot[name="${ slotId as string } "]` ;
190- return this . host . shadowRoot ?. querySelector ?.< HTMLSlotElement > ( selector ) ?? null ;
191- }
192-
193- #getChildrenForSlot< T extends Element = Element > (
194- name : string | typeof SlotController . default ,
195- ) : T [ ] {
196- if ( this . #nodes. has ( name ) ) {
197- return ( this . #nodes. get ( name ) ! . slot ?. assignedElements ?.( ) ?? [ ] ) as T [ ] ;
198- } else {
199- const children = Array . from ( this . host . children ) as T [ ] ;
200- return children . filter ( isSlot ( name ) ) ;
201- }
202- }
203-
204199 /**
205200 * Given a slot name or slot names, returns elements assigned to the requested slots as an array.
206201 * If no value is provided, it returns all children not assigned to a slot (without a slot attribute).
@@ -218,12 +213,12 @@ export class SlotController implements SlotControllerPublicAPI {
218213 * this.getSlotted();
219214 * ```
220215 */
221- getSlotted < T extends Element = Element > ( ...slotNames : string [ ] ) : T [ ] {
222- if ( ! slotNames . length ) {
223- 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 [ ] ;
224219 } else {
225220 return slotNames . flatMap ( slotName =>
226- this . #nodes . get ( slotName ) ?. elements ?? [ ] ) as T [ ] ;
221+ this . #slotRecords . get ( slotName ?? SlotController . default ) ?. elements ?? [ ] ) as T [ ] ;
227222 }
228223 }
229224
@@ -232,12 +227,20 @@ export class SlotController implements SlotControllerPublicAPI {
232227 * @param names The slot names to check.
233228 * @example this.hasSlotted('header');
234229 */
235- hasSlotted ( ...names : ( string | null | undefined ) [ ] ) : boolean {
236- 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 ) ;
237233 if ( ! slotNames . length ) {
238234 slotNames . push ( SlotController . default ) ;
239235 }
240- 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+ } ) ;
241244 }
242245
243246 /**
@@ -247,7 +250,7 @@ export class SlotController implements SlotControllerPublicAPI {
247250 * @example this.isEmpty();
248251 * @returns
249252 */
250- isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean {
253+ public isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean {
251254 return ! this . hasSlotted ( ...names ) ;
252255 }
253256}
0 commit comments