import { customAttributes , CustomAttributeRegistry , CustomAttribute } from '@lume/custom-attributes/dist/index.js' ; import type { Constructor } from 'lowclass' ; type PossibleBehaviorInstance = { awaitElementDefined ? : boolean ; connectedCallback ? : ( ) => void ; disconnectedCallback ? : ( ) => void ; attributeChangedCallback ? : ( attr : string , oldValue : string | null , newValue : string | null ) => void ; [ k ] : any ; [ k ] : any ; } ; type PossibleBehaviorConstructor = Constructor < PossibleBehaviorInstance , [ Element ] , { observedAttributes ? : string [ ] ; } > ; class BehaviorRegistry { protected _definedBehaviors = new Map < string , PossibleBehaviorConstructor > ( ) ; define < T extends PossibleBehaviorConstructor > ( name , Behavior ) { if ( ! this . _definedBehaviors . has ( name ) ) { this . _definedBehaviors . set ( name , Behavior ) ; } else { throw new Error ( `Behavior ${ name } is already defined.` ) ; } } get ( name ) { return this . _definedBehaviors . get ( name ) ; } has ( name ) { return this . _definedBehaviors . has ( name ) ; } } declare global { const elementBehaviors ; interface Window { elementBehaviors : BehaviorRegistry ; } } export const elementBehaviors = ( window . elementBehaviors = new BehaviorRegistry ( ) ) ; class BehaviorMap < K , V > extends Map < K , V > { } const behaviorMaps = new WeakMap ( ) ; Object . defineProperty ( Element . prototype , 'behaviors' , { get ( ) { let thisBehaviors = null ; if ( ! behaviorMaps . has ( this ) ) { behaviorMaps . set ( this , ( thisBehaviors = new BehaviorMap ( ) ) ) ; } else thisBehaviors = behaviorMaps . get ( this ) ; return thisBehaviors ; } , } ) ; type ElementWithBehaviors = Element & { behaviors : BehaviorMap < string , PossibleBehaviorInstance > ; } ; class HasAttribute implements CustomAttribute { declare ownerElement : ElementWithBehaviors ; declare value : string ; declare name : string ; behaviors ? : BehaviorMap < string , PossibleBehaviorInstance > ; observers = new Map < PossibleBehaviorInstance , MutationObserver > ( ) ; connectedBehaviors = new Set < PossibleBehaviorInstance > ( ) ; definePromises = new Map < string , CancelablePromise < CustomElementConstructor > > ( ) ; connectedCallback ( ) { this . behaviors = this . ownerElement . behaviors ; this . changedCallback ( '' , this . value ) ; } disconnectedCallback ( ) { this . changedCallback ( this . value , '' ) ; this . observers . clear ( ) ; this . connectedBehaviors . clear ( ) ; for ( const [ , promise ] of this . definePromises ) promise . cancel ( ) ; this . definePromises . clear ( ) ; delete this . behaviors ; } changedCallback ( _oldVal , newVal ) { const newBehaviors = this . getBehaviorNames ( newVal ) ; const previousBehaviors = Array . from ( this . behaviors ! . keys ( ) ) ; if ( newBehaviors . length == 0 && previousBehaviors . length == 0 ) return ; const { removed , added } = this . getDiff ( previousBehaviors , newBehaviors ) ; this . handleDiff ( removed , added ) ; } private getBehaviorNames ( string ) { if ( string . trim ( ) == '' ) return [ ] ; else return string . split ( /\s+/ ) ; } private getDiff ( previousBehaviors , newBehaviors ) { const diff = { removed : [ ] as string [ ] , added : newBehaviors , } ; for ( let i = 0 , l = previousBehaviors . length ; i < l ; i += 1 ) { const oldBehavior = previousBehaviors [ i ] ; if ( ! diff . added . includes ( oldBehavior ) ) { diff . removed . push ( oldBehavior ) ; } else { diff . added . splice ( diff . added . indexOf ( oldBehavior ) , 1 ) ; } } return diff ; } private handleDiff ( removed , added ) { for ( const name of removed ) this . disconnectBehavior ( name ) ; for ( const name of added ) this . connectBehavior ( name ) ; } private connectBehavior ( name ) { const Behavior = elementBehaviors . get ( name ) ; if ( ! Behavior ) return ; const behavior = new Behavior ( this . ownerElement ) ; this . behaviors ! . set ( name , behavior ) ; const observedAttributes = ( behavior . constructor as PossibleBehaviorConstructor ) . observedAttributes ; if ( this . ownerElement . isConnected ) { const tagName = this . ownerElement . tagName ; if ( behavior . awaitElementDefined && tagName . includes ( '-' ) ) { const promiseId = name + '_' + tagName ; let promise = this . definePromises . get ( promiseId ) ; if ( ! promise ) { promise = new CancelablePromise ( customElements . whenDefined ( tagName . toLowerCase ( ) ) , { rejectOnCancel : false , } ) ; this . definePromises . set ( promiseId , promise ) ; } promise . then ( ( ) => { this . definePromises . delete ( promiseId ) ; this . connectedBehaviors . add ( behavior ) ; behavior . connectedCallback && behavior . connectedCallback ( ) ; } ) ; } else { this . connectedBehaviors . add ( behavior ) ; behavior . connectedCallback && behavior . connectedCallback ( ) ; } } if ( Array . isArray ( observedAttributes ) && observedAttributes . length ) { this . fireInitialAttributeChangedCallbacks ( behavior , observedAttributes ) ; this . createAttributeObserver ( behavior ) ; } } private disconnectBehavior ( name ) { const behavior = this . behaviors ! . get ( name ) ; if ( ! behavior ) return ; { if ( this . connectedBehaviors . has ( behavior ) ) { behavior . disconnectedCallback && behavior . disconnectedCallback ( ) ; this . connectedBehaviors . delete ( behavior ) ; } const promiseId = name + '_' + this . ownerElement . tagName ; const promise = this . definePromises . get ( promiseId ) ; if ( promise ) { promise . cancel ( ) ; this . definePromises . delete ( promiseId ) ; } } this . destroyAttributeObserver ( behavior ) ; this . behaviors ! . delete ( name ) ; } destroyAttributeObserver ( behavior ) { const observer = this . observers . get ( behavior ) ; if ( ! observer ) return ; observer . disconnect ( ) ; this . observers . delete ( behavior ) ; } createAttributeObserver ( behavior ) { const el = this . ownerElement ; const observer = new MutationObserver ( records => { if ( ! behavior . attributeChangedCallback ) return ; let lastAttributeValues = { } ; let name = '' ; for ( const record of records ) { if ( record . type !== 'attributes' ) continue ; name = record . attributeName ! ; if ( lastAttributeValues [ name ] === undefined ) { lastAttributeValues [ name ] = record . oldValue ; continue ; } behavior . attributeChangedCallback ( name , lastAttributeValues [ name ] , record . oldValue ) ; lastAttributeValues [ name ] = record . oldValue ; } let attr ; for ( const name in lastAttributeValues ) { attr = el . attributes . getNamedItem ( name ) ; behavior . attributeChangedCallback ( name , lastAttributeValues [ name ] , attr === null ? null : attr . value ) ; } } ) ; observer . observe ( el , { attributes : true , attributeOldValue : true , attributeFilter : ( behavior . constructor as PossibleBehaviorConstructor ) . observedAttributes , } ) ; this . observers . set ( behavior , observer ) ; } fireInitialAttributeChangedCallbacks ( behavior , attributes ) { if ( ! behavior . attributeChangedCallback ) return ; for ( const name of attributes ) { if ( this . ownerElement . hasAttribute ( name ) ) behavior . attributeChangedCallback ( name , null , this . ownerElement . attributes . getNamedItem ( name ) ! . value ) ; } } } function Privates ( ) { const storage = new WeakMap ( ) ; return ( obj ) => { let privates = storage . get ( obj ) ; if ( ! privates ) storage . set ( obj , ( privates = { } ) ) ; return privates ; } ; } const _ = Privates ( ) ; interface CancelablePromiseOptions { rejectOnCancel ? : boolean ; } class CancelablePromise < T > extends Promise < T > { canceled : boolean ; constructor ( executor , options ) { const rejectOnCancel = options ? options . rejectOnCancel : false ; let originalReject ; if ( executor instanceof Promise ) { const promise = executor ; super ( ( resolve , reject ) => { originalReject = reject ; promise . then ( value => { if ( this . canceled ) return ; resolve ( value ) ; } ) . catch ( error => { if ( this . canceled ) return ; reject ( error ) ; } ) ; } ) ; } else { super ( ( resolve , reject ) => { originalReject = reject ; executor ( value => { if ( this . canceled ) return ; resolve ( value ) ; } , error => { if ( this . canceled ) return ; reject ( error ) ; } ) ; } ) ; } this . canceled = false ; _ ( this ) . originalReject = originalReject ; _ ( this ) . rejectOnCancel = rejectOnCancel ; } cancel ( ) { this . canceled = true ; if ( _ ( this ) . rejectOnCancel ) { _ ( this ) . originalReject ( new PromiseCancellation ( 'canceled' ) ) ; } } } class PromiseCancellation extends Error { } customAttributes . define ( 'has' , HasAttribute ) ; if ( Element . prototype . attachShadow ) { const _attachShadow = Element . prototype . attachShadow ; Element . prototype . attachShadow = function ( ... args ) { const root = _attachShadow . call ( this , ... args ) ; const attributes = new CustomAttributeRegistry ( root ) ; attributes . define ( 'has' , HasAttribute ) ; return root ; } ; } export const version = '2.2.11' ;