license import { getCorrectEventName } from '@material/animation/util' ; import { MDCComponent } from '@material/base/component' ; import { applyPassive } from '@material/dom/events' ; import { matches } from '@material/dom/ponyfill' ; import { MDCRippleAdapter } from '@material/ripple/adapter' ; import { MDCRipple } from '@material/ripple/component' ; import { MDCRippleFoundation } from '@material/ripple/foundation' ; import { MDCRippleCapableSurface } from '@material/ripple/types' ; import { MDCCheckboxAdapter } from './adapter' ; import { strings } from './constants' ; import { MDCCheckboxFoundation } from './foundation' ; /**
 * This type is needed for compatibility with Closure Compiler.
 */ type PropertyDescriptorGetter = ( ( ) => unknown ) | undefined ; const CB_PROTO_PROPS = [ 'checked' , 'indeterminate' ] ; export type MDCCheckboxFactory = ( el : Element , foundation ? : MDCCheckboxFoundation ) => MDCCheckbox ; export class MDCCheckbox extends MDCComponent < MDCCheckboxFoundation > implements MDCRippleCapableSurface { static override attachTo ( root ) { return new MDCCheckbox ( root ) ; } get ripple ( ) : MDCRipple { return this . rippleSurface ; } get checked ( ) : boolean { return this . getNativeControl ( ) . checked ; } set checked ( checked ) { this . getNativeControl ( ) . checked = checked ; } get indeterminate ( ) : boolean { return this . getNativeControl ( ) . indeterminate ; } set indeterminate ( indeterminate ) { this . getNativeControl ( ) . indeterminate = indeterminate ; } get disabled ( ) : boolean { return this . getNativeControl ( ) . disabled ; } set disabled ( disabled ) { this . foundation . setDisabled ( disabled ) ; } get value ( ) : string { return this . getNativeControl ( ) . value ; } set value ( value ) { this . getNativeControl ( ) . value = value ; } private readonly rippleSurface : MDCRipple = this . createRipple ( ) ; private handleChange ! : EventListener ; private handleAnimationEnd ! : EventListener ; override initialize ( ) { const { DATA_INDETERMINATE_ATTR } = strings ; this . getNativeControl ( ) . indeterminate = this . getNativeControl ( ) . getAttribute ( DATA_INDETERMINATE_ATTR ) === 'true' ; this . getNativeControl ( ) . removeAttribute ( DATA_INDETERMINATE_ATTR ) ; } override initialSyncWithDOM ( ) { this . handleChange = ( ) => { this . foundation . handleChange ( ) ; } ; this . handleAnimationEnd = ( ) => { this . foundation . handleAnimationEnd ( ) ; } ; this . getNativeControl ( ) . addEventListener ( 'change' , this . handleChange ) ; this . listen ( getCorrectEventName ( window , 'animationend' ) , this . handleAnimationEnd ) ; this . installPropertyChangeHooks ( ) ; } override destroy ( ) { this . rippleSurface . destroy ( ) ; this . getNativeControl ( ) . removeEventListener ( 'change' , this . handleChange ) ; this . unlisten ( getCorrectEventName ( window , 'animationend' ) , this . handleAnimationEnd ) ; this . uninstallPropertyChangeHooks ( ) ; super . destroy ( ) ; } override getDefaultFoundation ( ) { const adapter = { addClass : ( className ) => this . root . classList . add ( className ) , forceLayout : ( ) => ( this . root as HTMLElement ) . offsetWidth , hasNativeControl : ( ) => ! ! this . getNativeControl ( ) , isAttachedToDOM : ( ) => Boolean ( this . root . parentNode ) , isChecked : ( ) => this . checked , isIndeterminate : ( ) => this . indeterminate , removeClass : ( className ) => { this . root . classList . remove ( className ) ; } , removeNativeControlAttr : ( attr ) => { this . getNativeControl ( ) . removeAttribute ( attr ) ; } , setNativeControlAttr : ( attr , value ) => { this . getNativeControl ( ) . setAttribute ( attr , value ) ; } , setNativeControlDisabled : ( disabled ) => { this . getNativeControl ( ) . disabled = disabled ; } , } ; return new MDCCheckboxFoundation ( adapter ) ; } private createRipple ( ) { const adapter = { ... MDCRipple . createAdapter ( this ) , deregisterInteractionHandler : ( evtType , handler ) => { this . getNativeControl ( ) . removeEventListener ( evtType , handler , applyPassive ( ) ) ; } , isSurfaceActive : ( ) => matches ( this . getNativeControl ( ) , ':active' ) , isUnbounded : ( ) => true , registerInteractionHandler : ( evtType , handler ) => { this . getNativeControl ( ) . addEventListener ( evtType , handler , applyPassive ( ) ) ; } , } ; return new MDCRipple ( this . root , new MDCRippleFoundation ( adapter ) ) ; } private installPropertyChangeHooks ( ) { const nativeCb = this . getNativeControl ( ) ; const cbProto = Object . getPrototypeOf ( nativeCb ) ; for ( const controlState of CB_PROTO_PROPS ) { const desc = Object . getOwnPropertyDescriptor ( cbProto , controlState ) ; if ( ! validDescriptor ( desc ) ) { return ; } const nativeGetter = ( desc as { get : PropertyDescriptorGetter ; } ) . get ; const nativeCbDesc = { configurable : desc . configurable , enumerable : desc . enumerable , get : nativeGetter , set : ( state ) => { desc . set ! . call ( nativeCb , state ) ; this . foundation . handleChange ( ) ; } , } ; Object . defineProperty ( nativeCb , controlState , nativeCbDesc ) ; } } private uninstallPropertyChangeHooks ( ) { const nativeCb = this . getNativeControl ( ) ; const cbProto = Object . getPrototypeOf ( nativeCb ) ; for ( const controlState of CB_PROTO_PROPS ) { const desc = Object . getOwnPropertyDescriptor ( cbProto , controlState ) ; if ( ! validDescriptor ( desc ) ) { return ; } Object . defineProperty ( nativeCb , controlState , desc ) ; } } private getNativeControl ( ) { const { NATIVE_CONTROL_SELECTOR } = strings ; const el = this . root . querySelector < HTMLInputElement > ( NATIVE_CONTROL_SELECTOR ) ; if ( ! el ) { throw new Error ( `Checkbox component requires a ${ NATIVE_CONTROL_SELECTOR } element` ) ; } return el ; } } function validDescriptor ( inputPropDesc ) { return ! ! inputPropDesc && typeof inputPropDesc . set === 'function' ; }