license import '@material/mwc-ripple/mwc-ripple' ; import { ariaProperty } from '@material/mwc-base/aria-property' ; import { addHasRemoveClass , FormElement } from '@material/mwc-base/form-element' ; import { observer } from '@material/mwc-base/observer' ; import { SingleSelectionController } from '@material/mwc-radio/single-selection-controller' ; import { Ripple } from '@material/mwc-ripple/mwc-ripple' ; import { RippleHandlers } from '@material/mwc-ripple/ripple-handlers' ; import { MDCRadioAdapter } from '@material/radio/adapter' ; import MDCRadioFoundation from '@material/radio/foundation' ; import { html , TemplateResult } from 'lit' ; import { eventOptions , property , query , queryAsync , state } from 'lit/decorators.js' ; import { classMap } from 'lit/directives/class-map.js' ; import { ifDefined } from 'lit/directives/if-defined.js' ; fires soyCompatible export class RadioBase extends FormElement { @ query ( '.mdc-radio' ) protected mdcRoot ! : HTMLElement ; @ query ( 'input' ) protected formElement ! : HTMLInputElement ; protected _checked = false ; @ state ( ) protected useStateLayerCustomProperties = false ; @ property ( { type : Boolean } ) global = false ; @ property ( { type : Boolean , reflect : true } ) get checked ( ) : boolean { return this . _checked ; } /**
     * We define our own getter/setter for `checked` because we need to track
     * changes to it synchronously.
     *
     * The order in which the `checked` property is set across radio buttons
     * within the same group is very important. However, we can't rely on
     * UpdatingElement's `updated` callback to observe these changes (which is
     * also what the `@observer` decorator uses), because it batches changes to
     * all properties.
     *
     * Consider:
     *
     *   radio1.disabled = true;
     *   radio2.checked = true;
     *   radio1.checked = true;
     *
     * In this case we'd first see all changes for radio1, and then for radio2,
     * and we couldn't tell that radio1 was the most recently checked.
     */ set checked ( isChecked ) { const oldValue = this . _checked ; if ( isChecked === oldValue ) { return ; } this . _checked = isChecked ; if ( this . formElement ) { this . formElement . checked = isChecked ; } this . _selectionController ?. update ( this ) ; if ( isChecked === false ) { this . formElement ?. blur ( ) ; } this . requestUpdate ( 'checked' , oldValue ) ; this . dispatchEvent ( new Event ( 'checked' , { bubbles : true , composed : true } ) ) ; } @ property ( { type : Boolean } ) @ observer ( function ( this , disabled ) { this . mdcFoundation . setDisabled ( disabled ) ; } ) override disabled = false ; @ property ( { type : String } ) @ observer ( function ( this , value ) { this . _handleUpdatedValue ( value ) ; } ) value = 'on' ; _handleUpdatedValue ( newValue ) { this . formElement . value = newValue ; } @ property ( { type : String } ) name = '' ; see https @ property ( { type : Boolean } ) reducedTouchTarget = false ; protected mdcFoundationClass = MDCRadioFoundation ; protected mdcFoundation ! : MDCRadioFoundation ; protected _selectionController ? : SingleSelectionController ; /**
     * input's tabindex is updated based on checked status.
     * Tab navigation will be removed from unchecked radios.
     */ @ property ( { type : Number } ) formElementTabIndex = 0 ; @ state ( ) protected focused = false ; @ state ( ) protected shouldRenderRipple = false ; @ queryAsync ( 'mwc-ripple' ) override ripple ! : Promise < Ripple | null > ; soyPrefixAttribute @ ariaProperty @ property ( { attribute : 'aria-label' } ) override ariaLabel ! : string ; soyPrefixAttribute @ ariaProperty @ property ( { attribute : 'aria-labelledby' } ) ariaLabelledBy ! : string ; protected rippleElement : Ripple | null = null ; protected rippleHandlers : RippleHandlers = new RippleHandlers ( ( ) => { this . shouldRenderRipple = true ; this . ripple . then ( ( v ) => { this . rippleElement = v ; } ) ; return this . ripple ; } ) ; soyTemplate protected renderRipple ( ) { return this . shouldRenderRipple ? html `<mwc-ripple unbounded accent
        .internalUseStateLayerCustomProperties="${ this . useStateLayerCustomProperties }"
        .disabled="${ this . disabled }"></mwc-ripple>` : '' ; } get isRippleActive ( ) { return this . rippleElement ?. isActive || false ; } override connectedCallback ( ) { super . connectedCallback ( ) ; this . _selectionController = SingleSelectionController . getController ( this ) ; this . _selectionController . register ( this ) ; this . _selectionController . update ( this ) ; } override disconnectedCallback ( ) { this . _selectionController ! . unregister ( this ) ; this . _selectionController = undefined ; } override focus ( ) { this . formElement . focus ( ) ; } protected createAdapter ( ) { return { ... addHasRemoveClass ( this . mdcRoot ) , setNativeControlDisabled : ( disabled ) => { this . formElement . disabled = disabled ; } , } ; } protected handleFocus ( ) { this . focused = true ; this . handleRippleFocus ( ) ; } protected handleClick ( ) { this . formElement . focus ( ) ; } protected handleBlur ( ) { this . focused = false ; this . formElement . blur ( ) ; this . rippleHandlers . endFocus ( ) ; } protected setFormData ( formData ) { if ( this . name && this . checked ) { formData . append ( this . name , this . value ) ; } } soyTemplate soyAttributes soyClasses protected override render ( ) { classMap const classes = { 'mdc-radio--touch' : ! this . reducedTouchTarget , 'mdc-ripple-upgraded--background-focused' : this . focused , 'mdc-radio--disabled' : this . disabled , } ; return html `
      <div class="mdc-radio ${ classMap ( classes ) }">
        <input
          tabindex="${ this . formElementTabIndex }"
          class="mdc-radio__native-control"
          type="radio"
          name="${ this . name }"
          aria-label="${ ifDefined ( this . ariaLabel ) }"
          aria-labelledby="${ ifDefined ( this . ariaLabelledBy ) }"
          .checked="${ this . checked }"
          .value="${ this . value }"
          ?disabled="${ this . disabled }"
          @change="${ this . changeHandler }"
          @focus="${ this . handleFocus }"
          @click="${ this . handleClick }"
          @blur="${ this . handleBlur }"
          @mousedown="${ this . handleRippleMouseDown }"
          @mouseenter="${ this . handleRippleMouseEnter }"
          @mouseleave="${ this . handleRippleMouseLeave }"
          @touchstart="${ this . handleRippleTouchStart }"
          @touchend="${ this . handleRippleDeactivate }"
          @touchcancel="${ this . handleRippleDeactivate }">
        <div class="mdc-radio__background">
          <div class="mdc-radio__outer-circle"></div>
          <div class="mdc-radio__inner-circle"></div>
        </div>
        ${ this . renderRipple ( ) }
      </div>` ; } protected handleRippleMouseDown ( event ) { const onUp = ( ) => { window . removeEventListener ( 'mouseup' , onUp ) ; this . handleRippleDeactivate ( ) ; } ; window . addEventListener ( 'mouseup' , onUp ) ; this . rippleHandlers . startPress ( event ) ; } @ eventOptions ( { passive : true } ) protected handleRippleTouchStart ( event ) { this . rippleHandlers . startPress ( event ) ; } protected handleRippleDeactivate ( ) { this . rippleHandlers . endPress ( ) ; } protected handleRippleMouseEnter ( ) { this . rippleHandlers . startHover ( ) ; } protected handleRippleMouseLeave ( ) { this . rippleHandlers . endHover ( ) ; } protected handleRippleFocus ( ) { this . rippleHandlers . startFocus ( ) ; } protected changeHandler ( ) { this . checked = this . formElement . checked ; } }