import { ElementPart , Part } from 'lit' ; import { nothing } from 'lit/html.js' ; import { AsyncDirective , directive } from 'lit/async-directive.js' ; type EventListenerWithOptions = EventListenerOrEventListenerObject & Partial < AddEventListenerOptions > ; TODO class SpreadDirective extends AsyncDirective { host ! : EventTarget | object | Element ; element ! : Element ; prevData : { [ key ] : unknown ; } = { } ; render ( _spreadData ) { return nothing ; } update ( part , [ spreadData ] ) { if ( this . element !== ( part as ElementPart ) . element ) { this . element = ( part as ElementPart ) . element ; } this . host = part . options ?. host || this . element ; this . apply ( spreadData ) ; this . groom ( spreadData ) ; this . prevData = spreadData ; } apply ( data ) { if ( ! data ) return ; const { prevData , element } = this ; for ( const key in data ) { const value = data [ key ] ; if ( value === prevData [ key ] ) { continue ; } const name = key . slice ( 1 ) ; switch ( key [ 0 ] ) { case '@' : const prevHandler = prevData [ key ] ; if ( prevHandler ) { element . removeEventListener ( name , this , value as EventListenerWithOptions ) ; } element . addEventListener ( name , this , value as EventListenerWithOptions ) ; break ; case '.' : element [ name ] = value ; break ; case '?' : if ( value ) { element . setAttribute ( name , '' ) ; } else { element . removeAttribute ( name ) ; } break ; default : if ( value != null ) { element . setAttribute ( key , String ( value ) ) ; } else { element . removeAttribute ( key ) ; } break ; } } } groom ( data ) { const { prevData , element } = this ; if ( ! prevData ) return ; for ( const key in prevData ) { if ( ! data || ! ( key in data ) ) { switch ( key [ 0 ] ) { case '@' : const value = prevData [ key ] ; element . removeEventListener ( key . slice ( 1 ) , this , value as EventListenerWithOptions ) ; break ; case '.' : element [ key . slice ( 1 ) ] = undefined ; break ; case '?' : element . removeAttribute ( key . slice ( 1 ) ) ; break ; default : element . removeAttribute ( key ) ; break ; } } } } handleEvent ( event ) { const value = this . prevData [ `@${ event . type }` ] as Function | EventListenerObject ; if ( typeof value === 'function' ) { ( value as Function ) . call ( this . host , event ) ; } else { ( value as EventListenerObject ) . handleEvent ( event ) ; } } disconnected ( ) { const { prevData , element } = this ; for ( const key in prevData ) { if ( key [ 0 ] !== '@' ) continue ; const value = prevData [ key ] ; element . removeEventListener ( key . slice ( 1 ) , this , value as EventListenerWithOptions ) ; } } reconnected ( ) { const { prevData , element } = this ; for ( const key in prevData ) { if ( key [ 0 ] !== '@' ) continue ; const value = prevData [ key ] ; element . addEventListener ( key . slice ( 1 ) , this , value as EventListenerWithOptions ) ; } } } export const spread = directive ( SpreadDirective ) ; class SpreadPropsDirective extends AsyncDirective { host ! : EventTarget | object | Element ; element ! : Element ; prevData : { [ key ] : unknown ; } = { } ; render ( _spreadData ) { return nothing ; } update ( part , [ spreadData ] ) { if ( this . element !== ( part as ElementPart ) . element ) { this . element = ( part as ElementPart ) . element ; } this . host = part . options ?. host || this . element ; this . apply ( spreadData ) ; this . groom ( spreadData ) ; this . prevData = spreadData ; } apply ( data ) { if ( ! data ) return ; const { prevData , element } = this ; for ( const key in data ) { const value = data [ key ] ; if ( value === prevData [ key ] ) { continue ; } element [ key ] = value ; } } groom ( data ) { const { prevData , element } = this ; if ( ! prevData ) return ; for ( const key in prevData ) { if ( ! data || ! ( key in data ) ) { element [ key ] = undefined ; } } } } export const spreadProps = directive ( SpreadPropsDirective ) ;