11import { isServer , type ReactiveController , type ReactiveElement } from 'lit' ;
22
3- import { Logger } from './logger.js' ;
4-
53interface AnonymousSlot {
64 hasContent : boolean ;
75 elements : Element [ ] ;
@@ -15,8 +13,10 @@ interface NamedSlot extends AnonymousSlot {
1513
1614export type Slot = NamedSlot | AnonymousSlot ;
1715
16+ export type SlotName = string | null ;
17+
1818export interface SlotsConfig {
19- slots : ( string | null ) [ ] ;
19+ slots : SlotName [ ] ;
2020 /**
2121 * Object mapping new slot name keys to deprecated slot name values
2222 * @example `pf-modal--header` is deprecated in favour of `header`
@@ -32,9 +32,9 @@ export interface SlotsConfig {
3232 deprecations ?: Record < string , string > ;
3333}
3434
35- function isObjectConfigSpread (
36- config : ( [ SlotsConfig ] | ( string | null ) [ ] ) ,
37- ) : config is [ SlotsConfig ] {
35+ export type SlotControllerArgs = [ SlotsConfig ] | SlotName [ ] ;
36+
37+ export function isObjectSpread ( config : SlotControllerArgs ) : config is [ SlotsConfig ] {
3838 return config . length === 1 && typeof config [ 0 ] === 'object' && config [ 0 ] !== null ;
3939}
4040
@@ -57,58 +57,92 @@ export class SlotController implements ReactiveController {
5757
5858 #nodes = new Map < string | typeof SlotController . default , Slot > ( ) ;
5959
60- #logger: Logger ;
61-
62- #firstUpdated = false ;
60+ #slotMapInitialized = false ;
6361
64- #mo = new MutationObserver ( records => this . #onMutation( records ) ) ;
65-
66- #slotNames: ( string | null ) [ ] ;
62+ #slotNames: ( string | null ) [ ] = [ ] ;
6763
6864 #deprecations: Record < string , string > = { } ;
6965
70- constructor ( public host : ReactiveElement , ...config : ( [ SlotsConfig ] | ( string | null ) [ ] ) ) {
71- this . #logger = new Logger ( this . host ) ;
66+ #mo = new MutationObserver ( this . #initSlotMap. bind ( this ) ) ;
7267
73- if ( isObjectConfigSpread ( config ) ) {
68+ constructor ( public host : ReactiveElement , ...args : SlotControllerArgs ) {
69+ this . #initialize( ...args ) ;
70+ host . addController ( this ) ;
71+ if ( ! this . #slotNames. length ) {
72+ this . #slotNames = [ null ] ;
73+ }
74+ }
75+
76+ #initialize( ...config : SlotControllerArgs ) {
77+ if ( isObjectSpread ( config ) ) {
7478 const [ { slots, deprecations } ] = config ;
7579 this . #slotNames = slots ;
7680 this . #deprecations = deprecations ?? { } ;
7781 } else if ( config . length >= 1 ) {
7882 this . #slotNames = config ;
7983 this . #deprecations = { } ;
80- } else {
81- this . #slotNames = [ null ] ;
8284 }
83-
84-
85- host . addController ( this ) ;
8685 }
8786
8887 async hostConnected ( ) : Promise < void > {
89- this . host . addEventListener ( 'slotchange' , this . #onSlotChange as EventListener ) ;
90- this . #firstUpdated = false ;
9188 this . #mo. observe ( this . host , { childList : true } ) ;
9289 // Map the defined slots into an object that is easier to query
9390 this . #nodes. clear ( ) ;
94- // Loop over the properties provided by the schema
95- this . #slotNames. forEach ( this . #initSlot) ;
96- Object . values ( this . #deprecations) . forEach ( this . #initSlot) ;
97- this . host . requestUpdate ( ) ;
91+ this . #initSlotMap( ) ;
9892 // insurance for framework integrations
9993 await this . host . updateComplete ;
10094 this . host . requestUpdate ( ) ;
10195 }
10296
97+ hostDisconnected ( ) : void {
98+ this . #mo. disconnect ( ) ;
99+ }
100+
103101 hostUpdated ( ) : void {
104- if ( ! this . #firstUpdated) {
105- this . #slotNames. forEach ( this . #initSlot) ;
106- this . #firstUpdated = true ;
102+ if ( ! this . #slotMapInitialized) {
103+ this . #initSlotMap( ) ;
107104 }
108105 }
109106
110- hostDisconnected ( ) : void {
111- this . #mo. disconnect ( ) ;
107+ #initSlotMap( ) {
108+ // Loop over the properties provided by the schema
109+ for ( const slotName of this . #slotNames
110+ . concat ( Object . values ( this . #deprecations) ) ) {
111+ const slotId = slotName || SlotController . default ;
112+ const name = slotName ?? '' ;
113+ const elements = this . #getChildrenForSlot( slotId ) ;
114+ const slot = this . #getSlotElement( slotId ) ;
115+ const hasContent =
116+ ! isServer
117+ && ! ! elements . length
118+ || ! ! slot ?. assignedNodes ?.( ) ?. filter ( x => x . textContent ?. trim ( ) ) . length ;
119+ this . #nodes. set ( slotId , { elements, name, hasContent, slot } ) ;
120+ }
121+ this . host . requestUpdate ( ) ;
122+ this . #slotMapInitialized = true ;
123+ }
124+
125+ #getSlotElement( slotId : string | symbol ) {
126+ if ( isServer ) {
127+ return null ;
128+ } else {
129+ const selector =
130+ slotId === SlotController . default ? 'slot:not([name])' : `slot[name="${ slotId as string } "]` ;
131+ return this . host . shadowRoot ?. querySelector ?.< HTMLSlotElement > ( selector ) ?? null ;
132+ }
133+ }
134+
135+ #getChildrenForSlot< T extends Element = Element > (
136+ name : string | typeof SlotController . default ,
137+ ) : T [ ] {
138+ if ( isServer ) {
139+ return [ ] ;
140+ } else if ( this . #nodes. has ( name ) ) {
141+ return ( this . #nodes. get ( name ) ! . slot ?. assignedElements ?.( ) ?? [ ] ) as T [ ] ;
142+ } else {
143+ const children = Array . from ( this . host . children ) as T [ ] ;
144+ return children . filter ( isSlot ( name ) ) ;
145+ }
112146 }
113147
114148 /**
@@ -143,19 +177,11 @@ export class SlotController implements ReactiveController {
143177 * @example this.hasSlotted('header');
144178 */
145179 hasSlotted ( ...names : ( string | null | undefined ) [ ] ) : boolean {
146- if ( isServer ) {
147- return this . host
148- . getAttribute ( 'ssr-hint-has-slotted' )
149- ?. split ( ',' )
150- . map ( name => name . trim ( ) )
151- . some ( name => names . includes ( name === 'default' ? null : name ) ) ?? false ;
152- } else {
153- const slotNames = Array . from ( names , x => x == null ? SlotController . default : x ) ;
154- if ( ! slotNames . length ) {
155- slotNames . push ( SlotController . default ) ;
156- }
157- return slotNames . some ( x => this . #nodes. get ( x ) ?. hasContent ?? false ) ;
180+ const slotNames = Array . from ( names , x => x == null ? SlotController . default : x ) ;
181+ if ( ! slotNames . length ) {
182+ slotNames . push ( SlotController . default ) ;
158183 }
184+ return slotNames . some ( x => this . #nodes. get ( x ) ?. hasContent ?? false ) ;
159185 }
160186
161187 /**
@@ -168,42 +194,4 @@ export class SlotController implements ReactiveController {
168194 isEmpty ( ...names : ( string | null | undefined ) [ ] ) : boolean {
169195 return ! this . hasSlotted ( ...names ) ;
170196 }
171-
172- #onSlotChange = ( event : Event & { target : HTMLSlotElement } ) => {
173- const slotName = event . target . name ;
174- this . #initSlot( slotName ) ;
175- this . host . requestUpdate ( ) ;
176- } ;
177-
178- #onMutation = async ( records : MutationRecord [ ] ) => {
179- const changed = [ ] ;
180- for ( const { addedNodes, removedNodes } of records ) {
181- for ( const node of [ ...addedNodes , ...removedNodes ] ) {
182- if ( node instanceof HTMLElement && node . slot ) {
183- this . #initSlot( node . slot ) ;
184- changed . push ( node . slot ) ;
185- }
186- }
187- }
188- this . host . requestUpdate ( ) ;
189- } ;
190-
191- #getChildrenForSlot< T extends Element = Element > (
192- name : string | typeof SlotController . default ,
193- ) : T [ ] {
194- const children = Array . from ( this . host . children ) as T [ ] ;
195- return children . filter ( isSlot ( name ) ) ;
196- }
197-
198- #initSlot = ( slotName : string | null ) => {
199- const name = slotName || SlotController . default ;
200- const elements = this . #nodes. get ( name ) ?. slot ?. assignedElements ?.( )
201- ?? this . #getChildrenForSlot( name ) ;
202- const selector = slotName ? `slot[name="${ slotName } "]` : 'slot:not([name])' ;
203- const slot = this . host . shadowRoot ?. querySelector ?.< HTMLSlotElement > ( selector ) ?? null ;
204- const nodes = slot ?. assignedNodes ?.( ) ;
205- const hasContent = ! ! elements . length || ! ! nodes ?. filter ( x => x . textContent ?. trim ( ) ) . length ;
206- this . #nodes. set ( name , { elements, name : slotName ?? '' , hasContent, slot } ) ;
207- this . #logger. debug ( slotName , hasContent ) ;
208- } ;
209197}
0 commit comments