import { CoreDescriptionModel } from './widget_core' ; import { DescriptionView , DescriptionStyleModel } from './widget_description' ; import { DOMWidgetView , WidgetView } from '@jupyter-widgets/base' ; import { uuid } from './utils' ; import { format } from 'd3-format' ; import noUiSlider from 'nouislider' ; export class IntModel extends CoreDescriptionModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'IntModel' , value : 0 , } ; } } export class BoundedIntModel extends IntModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'BoundedIntModel' , max : 100 , min : 0 , } ; } } export class SliderStyleModel extends DescriptionStyleModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'SliderStyleModel' } ; } public static styleProperties = { ... DescriptionStyleModel . styleProperties , handle_color : { selector : '.noUi-handle' , attribute : 'background-color' , default : null as any , } , } ; } export class IntSliderModel extends BoundedIntModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'IntSliderModel' , _view_name : 'IntSliderView' , step : 1 , orientation : 'horizontal' , readout : true , readout_format : 'd' , continuous_update : true , style : null , disabled : false , } ; } initialize ( attributes , options ) { super . initialize ( attributes , options ) ; this . on ( 'change:readout_format' , this . update_readout_format , this ) ; this . update_readout_format ( ) ; } update_readout_format ( ) { this . readout_formatter = format ( this . get ( 'readout_format' ) ) ; } readout_formatter : any ; } export class IntRangeSliderModel extends IntSliderModel { } export abstract class BaseIntSliderView extends DescriptionView { render ( ) { super . render ( ) ; this . el . classList . add ( 'jupyter-widgets' ) ; this . el . classList . add ( 'widget-inline-hbox' ) ; this . el . classList . add ( 'widget-slider' ) ; this . el . classList . add ( 'widget-hslider' ) ; this . $slider = document . createElement ( 'div' ) ; this . $slider . classList . add ( 'slider' ) ; this . slider_container = document . createElement ( 'div' ) ; this . slider_container . classList . add ( 'slider-container' ) ; this . slider_container . appendChild ( this . $slider ) ; this . el . appendChild ( this . slider_container ) ; this . readout = document . createElement ( 'div' ) ; this . el . appendChild ( this . readout ) ; this . readout . classList . add ( 'widget-readout' ) ; this . readout . contentEditable = 'true' ; this . readout . style . display = 'none' ; this . createSlider ( ) ; this . model . on ( 'change:orientation' , this . regenSlider , this ) ; this . model . on ( 'change:max' , this . updateSliderOptions , this ) ; this . model . on ( 'change:min' , this . updateSliderOptions , this ) ; this . model . on ( 'change:step' , this . updateSliderOptions , this ) ; this . model . on ( 'change:value' , this . updateSliderValue , this ) ; this . update ( ) ; } /**
     * Update the contents of this view
     *
     * Called when the model is changed.  The model may have been
     * changed by another view or by a state update from the back-end.
     */ update ( options ? ) { if ( options === undefined || options . updated_view !== this ) { if ( this . model . get ( 'disabled' ) ) { this . readout . contentEditable = 'false' ; this . $slider . setAttribute ( 'disabled' , true ) ; } else { this . readout . contentEditable = 'true' ; this . $slider . removeAttribute ( 'disabled' ) ; } const orientation = this . model . get ( 'orientation' ) ; if ( orientation === 'vertical' ) { this . el . classList . remove ( 'widget-hslider' ) ; this . el . classList . add ( 'widget-vslider' ) ; this . el . classList . remove ( 'widget-inline-hbox' ) ; this . el . classList . add ( 'widget-inline-vbox' ) ; } else { this . el . classList . remove ( 'widget-vslider' ) ; this . el . classList . add ( 'widget-hslider' ) ; this . el . classList . remove ( 'widget-inline-vbox' ) ; this . el . classList . add ( 'widget-inline-hbox' ) ; } const readout = this . model . get ( 'readout' ) ; if ( readout ) { this . readout . style . display = '' ; this . displayed . then ( ( ) => { if ( this . readout_overflow ( ) ) { this . readout . classList . add ( 'overflow' ) ; } else { this . readout . classList . remove ( 'overflow' ) ; } } ) ; } else { this . readout . style . display = 'none' ; } } return super . update ( ) ; } /**
     * Returns true if the readout box content overflows.
     */ readout_overflow ( ) { return this . readout . scrollWidth > this . readout . clientWidth ; } /**
     * Write value to a string
     */ abstract valueToString ( value ) ; /**
     * Parse value from a string
     */ abstract stringToValue ( text ) ; events ( ) { return { 'blur [contentEditable=true]' : 'handleTextChange' , 'keydown [contentEditable=true]' : 'handleKeyDown' , } ; } handleKeyDown ( e ) { if ( e . keyCode === 13 ) { e . preventDefault ( ) ; this . handleTextChange ( ) ; } } /**
     * Create a new noUiSlider object
     */ createSlider ( ) { const orientation = this . model . get ( 'orientation' ) ; const behavior = this . model . get ( 'behavior' ) ; noUiSlider . create ( this . $slider , { start : this . model . get ( 'value' ) , connect : true , behaviour : behavior , range : { min : this . model . get ( 'min' ) , max : this . model . get ( 'max' ) , } , step : this . model . get ( 'step' ) , animate : false , orientation : orientation , direction : orientation === 'horizontal' ? 'ltr' : 'rtl' , format : { from : ( value ) : number => Number ( value ) , to : ( value ) : number => value , } , } ) ; this . $slider . noUiSlider . on ( 'update' , ( values , handle ) => { this . handleSliderChange ( values , handle ) ; } ) ; this . $slider . noUiSlider . on ( 'end' , ( values , handle ) => { this . handleSliderChanged ( values , handle ) ; } ) ; } /**
     * Recreate/Regenerate a slider object
     * noUiSlider does not support in-place mutation of the orientation
     * state. We therefore need to destroy the current instance
     * and create a new one with the new properties. This is
     * handled in a separate function and has a dedicated event
     * handler.
     */ regenSlider ( e ) { this . $slider . noUiSlider . destroy ( ) ; this . createSlider ( ) ; } /**
     * Update noUiSlider object in-place with new options
     */ abstract updateSliderOptions ( e ) ; /**
     * Update noUiSlider's state so that it is
     * synced with the Backbone.jas model
     */ abstract updateSliderValue ( model , value , options ) ; /**
     * this handles the entry of text into the contentEditable label first, the
     * value is checked if it contains a parseable value then it is clamped
     * within the min-max range of the slider finally, the model is updated if
     * the value is to be changed
     *
     * if any of these conditions are not met, the text is reset
     */ abstract handleTextChange ( ) ; /**
     * Called when the slider value is changing.
     */ abstract handleSliderChange ( value , handle ) ; /**
     * Called when the slider value has changed.
     *
     * Calling model.set will trigger all of the other views of the
     * model to update.
     */ abstract handleSliderChanged ( values , handle ) ; /**
     * Validate the value of the slider before sending it to the back-end
     * and applying it to the other views on the page.
     */ _validate_slide_value ( x ) { return Math . floor ( x ) ; } $slider : any ; slider_container : HTMLElement ; readout : HTMLDivElement ; model : IntSliderModel ; _parse_value = parseInt ; } export class IntRangeSliderView extends BaseIntSliderView { update ( options ? ) { super . update ( options ) ; const value = this . model . get ( 'value' ) ; this . readout . textContent = this . valueToString ( value ) ; if ( this . model . get ( 'value' ) !== value ) { this . model . set ( 'value' , value , { updated_view : this } ) ; this . touch ( ) ; } } /**
     * Write value to a string
     */ valueToString ( value ) { const format = this . model . readout_formatter ; return value . map ( function ( v ) { return format ( v ) ; } ) . join ( ' – ' ) ; } /**
     * Parse value from a string
     */ stringToValue ( text ) { if ( text === null ) { return null ; } const match = this . _range_regex . exec ( text ) ; if ( match ) { return [ this . _parse_value ( match [ 1 ] ) , this . _parse_value ( match [ 2 ] ) ] ; } else { return null ; } } handleTextChange ( ) { let value = this . stringToValue ( this . readout . textContent ) ; const vmin = this . model . get ( 'min' ) ; const vmax = this . model . get ( 'max' ) ; if ( value === null || isNaN ( value [ 0 ] ) || isNaN ( value [ 1 ] ) || value [ 0 ] > value [ 1 ] ) { this . readout . textContent = this . valueToString ( this . model . get ( 'value' ) ) ; } else { value = [ Math . max ( Math . min ( value [ 0 ] , vmax ) , vmin ) , Math . max ( Math . min ( value [ 1 ] , vmax ) , vmin ) , ] ; if ( value [ 0 ] !== this . model . get ( 'value' ) [ 0 ] || value [ 1 ] !== this . model . get ( 'value' ) [ 1 ] ) { this . readout . textContent = this . valueToString ( value ) ; this . model . set ( 'value' , value , { updated_view : this } ) ; this . touch ( ) ; } else { this . readout . textContent = this . valueToString ( this . model . get ( 'value' ) ) ; } } } handleSliderChange ( values , handle ) { const actual_value = values . map ( this . _validate_slide_value ) ; this . readout . textContent = this . valueToString ( actual_value ) ; if ( this . model . get ( 'continuous_update' ) ) { this . handleSliderChanged ( values , handle ) ; } } handleSliderChanged ( values , handle ) { const actual_value = values . map ( this . _validate_slide_value ) ; this . model . set ( 'value' , actual_value , { updated_view : this } ) ; this . touch ( ) ; } updateSliderOptions ( e ) { this . $slider . noUiSlider . updateOptions ( { start : this . model . get ( 'value' ) , range : { min : this . model . get ( 'min' ) , max : this . model . get ( 'max' ) , } , step : this . model . get ( 'step' ) , } ) ; } updateSliderValue ( model , _ , options ) { if ( options . updated_view === this ) { return ; } const prev_value = this . $slider . noUiSlider . get ( ) ; const value = this . model . get ( 'value' ) ; if ( prev_value [ 0 ] !== value [ 0 ] || prev_value [ 1 ] !== value [ 1 ] ) { this . $slider . noUiSlider . set ( value ) ; } } _range_regex = /^\s*([+-]?\d+)\s*[-:–]\s*([+-]?\d+)/ ; } export class IntSliderView extends BaseIntSliderView { update ( options ? ) { super . update ( options ) ; const min = this . model . get ( 'min' ) ; const max = this . model . get ( 'max' ) ; let value = this . model . get ( 'value' ) ; if ( value > max ) { value = max ; } else if ( value < min ) { value = min ; } this . readout . textContent = this . valueToString ( value ) ; if ( this . model . get ( 'value' ) !== value ) { this . model . set ( 'value' , value , { updated_view : this } ) ; this . touch ( ) ; } } valueToString ( value ) { const format = this . model . readout_formatter ; return format ( value ) ; } stringToValue ( text ) { return this . _parse_value ( text ) ; } handleTextChange ( ) { let value = this . stringToValue ( this . readout . textContent ?? '' ) ; const vmin = this . model . get ( 'min' ) ; const vmax = this . model . get ( 'max' ) ; if ( isNaN ( value as number ) ) { this . readout . textContent = this . valueToString ( this . model . get ( 'value' ) ) ; } else { value = Math . max ( Math . min ( value as number , vmax ) , vmin ) ; if ( value !== this . model . get ( 'value' ) ) { this . readout . textContent = this . valueToString ( value ) ; this . model . set ( 'value' , value , { updated_view : this } ) ; this . touch ( ) ; } else { this . readout . textContent = this . valueToString ( this . model . get ( 'value' ) ) ; } } } handleSliderChange ( values , handle ) { const actual_value = this . _validate_slide_value ( values [ handle ] ) ; this . readout . textContent = this . valueToString ( actual_value ) ; if ( this . model . get ( 'continuous_update' ) ) { this . handleSliderChanged ( values , handle ) ; } } handleSliderChanged ( values , handle ) { const actual_value = this . _validate_slide_value ( values [ handle ] ) ; const model_value = this . model . get ( 'value' ) ; if ( parseFloat ( model_value ) !== actual_value ) { this . model . set ( 'value' , actual_value , { updated_view : this } ) ; this . touch ( ) ; } } updateSliderOptions ( e ) { this . $slider . noUiSlider . updateOptions ( { start : this . model . get ( 'value' ) , range : { min : this . model . get ( 'min' ) , max : this . model . get ( 'max' ) , } , step : this . model . get ( 'step' ) , } ) ; } updateSliderValue ( model , _ , options ) { if ( options . updated_view === this ) { return ; } const prev_value = this . $slider . noUiSlider . get ( ) ; const value = this . model . get ( 'value' ) ; if ( prev_value !== value ) { this . $slider . noUiSlider . set ( value ) ; } } } export class IntTextModel extends IntModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'IntTextModel' , _view_name : 'IntTextView' , disabled : false , continuous_update : false , } ; } } export class BoundedIntTextModel extends BoundedIntModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'BoundedIntTextModel' , _view_name : 'IntTextView' , disabled : false , continuous_update : false , step : 1 , } ; } } export class IntTextView extends DescriptionView { render ( ) { super . render ( ) ; this . el . classList . add ( 'jupyter-widgets' ) ; this . el . classList . add ( 'widget-inline-hbox' ) ; this . el . classList . add ( 'widget-text' ) ; this . textbox = document . createElement ( 'input' ) ; this . textbox . type = 'number' ; this . textbox . required = true ; this . textbox . id = this . label . htmlFor = uuid ( ) ; this . el . appendChild ( this . textbox ) ; this . update ( ) ; } /**
     * Update the contents of this view
     *
     * Called when the model is changed.  The model may have been
     * changed by another view or by a state update from the back-end.
     */ update ( options ? ) { if ( options === undefined || options . updated_view !== this ) { const value = this . model . get ( 'value' ) ; if ( this . _parse_value ( this . textbox . value ) !== value ) { this . textbox . value = value . toString ( ) ; } if ( this . model . get ( 'min' ) !== undefined ) { this . textbox . min = this . model . get ( 'min' ) ; } if ( this . model . get ( 'max' ) !== undefined ) { this . textbox . max = this . model . get ( 'max' ) ; } if ( this . model . get ( 'step' ) !== undefined && this . model . get ( 'step' ) !== null ) { this . textbox . step = this . model . get ( 'step' ) ; } else { this . textbox . step = this . _default_step ; } this . textbox . disabled = this . model . get ( 'disabled' ) ; } return super . update ( ) ; } events ( ) { return { 'keydown input' : 'handleKeyDown' , 'keypress input' : 'handleKeypress' , 'keyup input' : 'handleKeyUp' , 'input input' : 'handleChanging' , 'change input' : 'handleChanged' , } ; } /**
     * Handle key down
     *
     * Stop propagation so the event isn't sent to the application.
     */ handleKeyDown ( e ) { e . stopPropagation ( ) ; } /**
     * Handles key press
     */ handleKeypress ( e ) { if ( /[e,.\s]/ . test ( String . fromCharCode ( e . keyCode ) ) ) { e . preventDefault ( ) ; } } /**
     * Handle key up
     */ handleKeyUp ( e ) { if ( e . altKey || e . ctrlKey ) { return ; } const target = e . target as HTMLInputElement ; let value = target . value ; value = value . replace ( /[e,.\s]/g , '' ) ; if ( value . length >= 1 ) { const subvalue = value . substr ( 1 ) ; value = value [ 0 ] + subvalue . replace ( /[+-]/g , '' ) ; } if ( target . value !== value ) { e . preventDefault ( ) ; target . value = value ; } } /**
     * Call the submit handler if continuous update is true and we are not
     * obviously incomplete.
     */ handleChanging ( e ) { const target = e . target as HTMLInputElement ; const trimmed = target . value . trim ( ) ; if ( trimmed === '' || [ '-' , '-.' , '.' , '+.' , '+' ] . indexOf ( trimmed ) >= 0 ) { return ; } if ( this . model . get ( 'continuous_update' ) ) { this . handleChanged ( e ) ; } } /**
     * Applies validated input.
     */ handleChanged ( e ) { const target = e . target as HTMLInputElement ; let numericalValue = this . _parse_value ( target . value ) ; if ( isNaN ( numericalValue ) ) { target . value = this . model . get ( 'value' ) ; } else { let boundedValue = numericalValue ; if ( this . model . get ( 'max' ) !== undefined ) { boundedValue = Math . min ( this . model . get ( 'max' ) , boundedValue ) ; } if ( this . model . get ( 'min' ) !== undefined ) { boundedValue = Math . max ( this . model . get ( 'min' ) , boundedValue ) ; } if ( boundedValue !== numericalValue ) { target . value = boundedValue as any ; numericalValue = boundedValue ; } if ( numericalValue !== this . model . get ( 'value' ) ) { this . model . set ( 'value' , numericalValue , { updated_view : this } ) ; this . touch ( ) ; } } } _parse_value = parseInt ; _default_step = '1' ; textbox : HTMLInputElement ; } export class ProgressStyleModel extends DescriptionStyleModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'ProgressStyleModel' } ; } public static styleProperties = { ... DescriptionStyleModel . styleProperties , bar_color : { selector : '.progress-bar' , attribute : 'background-color' , default : null as any , } , } ; } export class IntProgressModel extends BoundedIntModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'IntProgressModel' , _view_name : 'ProgressView' , orientation : 'horizontal' , bar_style : '' , style : null , } ; } } export class ProgressView extends DescriptionView { initialize ( parameters ) { super . initialize ( parameters ) ; this . listenTo ( this . model , 'change:bar_style' , this . update_bar_style ) ; this . luminoWidget . addClass ( 'jupyter-widgets' ) ; } render ( ) { super . render ( ) ; const orientation = this . model . get ( 'orientation' ) ; const className = orientation === 'horizontal' ? 'widget-hprogress' : 'widget-vprogress' ; this . el . classList . add ( className ) ; this . progress = document . createElement ( 'div' ) ; this . progress . classList . add ( 'progress' ) ; this . progress . style . position = 'relative' ; this . el . appendChild ( this . progress ) ; this . bar = document . createElement ( 'div' ) ; this . bar . classList . add ( 'progress-bar' ) ; this . bar . style . position = 'absolute' ; this . bar . style . bottom = '0px' ; this . bar . style . left = '0px' ; this . progress . appendChild ( this . bar ) ; this . update ( ) ; this . set_bar_style ( ) ; } /**
     * Update the contents of this view
     *
     * Called when the model is changed.  The model may have been
     * changed by another view or by a state update from the back-end.
     */ update ( ) { const value = this . model . get ( 'value' ) ; const max = this . model . get ( 'max' ) ; const min = this . model . get ( 'min' ) ; const orientation = this . model . get ( 'orientation' ) ; const percent = ( 100.0 * ( value - min ) ) / ( max - min ) ; if ( orientation === 'horizontal' ) { this . el . classList . remove ( 'widget-inline-vbox' ) ; this . el . classList . remove ( 'widget-vprogress' ) ; this . el . classList . add ( 'widget-inline-hbox' ) ; this . el . classList . add ( 'widget-hprogress' ) ; this . bar . style . width = percent + '%' ; this . bar . style . height = '100%' ; } else { this . el . classList . remove ( 'widget-inline-hbox' ) ; this . el . classList . remove ( 'widget-hprogress' ) ; this . el . classList . add ( 'widget-inline-vbox' ) ; this . el . classList . add ( 'widget-vprogress' ) ; this . bar . style . width = '100%' ; this . bar . style . height = percent + '%' ; } return super . update ( ) ; } update_bar_style ( ) { this . update_mapped_classes ( ProgressView . class_map , 'bar_style' , this . bar ) ; } set_bar_style ( ) { this . set_mapped_classes ( ProgressView . class_map , 'bar_style' , this . bar ) ; } progress : HTMLDivElement ; bar : HTMLDivElement ; static class_map = { success : [ 'progress-bar-success' ] , info : [ 'progress-bar-info' ] , warning : [ 'progress-bar-warning' ] , danger : [ 'progress-bar-danger' ] , } ; } export class PlayModel extends BoundedIntModel { defaults ( ) { return { ... super . defaults ( ) , _model_name : 'PlayModel' , _view_name : 'PlayView' , repeat : false , playing : false , show_repeat : true , interval : 100 , step : 1 , disabled : false , } ; } initialize ( attributes , options ) { super . initialize ( attributes , options ) ; } loop ( ) { if ( ! this . get ( 'playing' ) ) { return ; } const next_value = this . get ( 'value' ) + this . get ( 'step' ) ; if ( next_value <= this . get ( 'max' ) ) { this . set ( 'value' , next_value ) ; this . schedule_next ( ) ; } else { if ( this . get ( 'repeat' ) ) { this . set ( 'value' , this . get ( 'min' ) ) ; this . schedule_next ( ) ; } else { this . pause ( ) ; } } this . save_changes ( ) ; } schedule_next ( ) { this . _timerId = window . setTimeout ( this . loop . bind ( this ) , this . get ( 'interval' ) ) ; } stop ( ) { this . pause ( ) ; this . set ( 'value' , this . get ( 'min' ) ) ; this . save_changes ( ) ; } pause ( ) { window . clearTimeout ( this . _timerId ) ; this . _timerId = undefined ; this . set ( 'playing' , false ) ; this . save_changes ( ) ; } animate ( ) { if ( this . _timerId !== undefined ) { return ; } if ( this . get ( 'value' ) === this . get ( 'max' ) ) { this . set ( 'value' , this . get ( 'min' ) ) ; this . schedule_next ( ) ; this . save_changes ( ) ; } else { this . loop ( ) ; } this . save_changes ( ) ; } play ( ) { this . set ( 'playing' , ! this . get ( 'playing' ) ) ; this . save_changes ( ) ; } repeat ( ) { this . set ( 'repeat' , ! this . get ( 'repeat' ) ) ; this . save_changes ( ) ; } private _timerId : number | undefined ; } export class PlayView extends DOMWidgetView { render ( ) { super . render ( ) ; this . el . classList . add ( 'jupyter-widgets' ) ; this . el . classList . add ( 'widget-inline-hbox' ) ; this . el . classList . add ( 'widget-play' ) ; this . playPauseButton = document . createElement ( 'button' ) ; this . stopButton = document . createElement ( 'button' ) ; this . repeatButton = document . createElement ( 'button' ) ; this . playPauseButton . className = 'jupyter-button' ; this . stopButton . className = 'jupyter-button' ; this . repeatButton . className = 'jupyter-button' ; this . el . appendChild ( this . playPauseButton ) ; this . el . appendChild ( this . stopButton ) ; this . el . appendChild ( this . repeatButton ) ; const playIcon = document . createElement ( 'i' ) ; playIcon . className = 'fa fa-play' ; this . playPauseButton . appendChild ( playIcon ) ; const stopIcon = document . createElement ( 'i' ) ; stopIcon . className = 'fa fa-stop' ; this . stopButton . appendChild ( stopIcon ) ; const repeatIcon = document . createElement ( 'i' ) ; repeatIcon . className = 'fa fa-retweet' ; this . repeatButton . appendChild ( repeatIcon ) ; this . playPauseButton . onclick = this . model . play . bind ( this . model ) ; this . stopButton . onclick = this . model . stop . bind ( this . model ) ; this . repeatButton . onclick = this . model . repeat . bind ( this . model ) ; this . listenTo ( this . model , 'change:playing' , this . onPlayingChanged ) ; this . listenTo ( this . model , 'change:repeat' , this . updateRepeat ) ; this . listenTo ( this . model , 'change:show_repeat' , this . updateRepeat ) ; this . updatePlaying ( ) ; this . updateRepeat ( ) ; this . update ( ) ; } update ( ) { const disabled = this . model . get ( 'disabled' ) ; this . playPauseButton . disabled = disabled ; this . stopButton . disabled = disabled ; this . repeatButton . disabled = disabled ; this . updatePlaying ( ) ; } onPlayingChanged ( ) { this . updatePlaying ( ) ; const previous = this . model . previous ( 'playing' ) ; const current = this . model . get ( 'playing' ) ; if ( ! previous && current ) { this . model . animate ( ) ; } else { this . model . pause ( ) ; } } updatePlaying ( ) { const playing = this . model . get ( 'playing' ) ; const icon = this . playPauseButton . getElementsByTagName ( 'i' ) [ 0 ] ; if ( playing ) { icon . className = 'fa fa-pause' ; } else { icon . className = 'fa fa-play' ; } } updateRepeat ( ) { const repeat = this . model . get ( 'repeat' ) ; this . repeatButton . style . display = this . model . get ( 'show_repeat' ) ? this . playPauseButton . style . display : 'none' ; if ( repeat ) { this . repeatButton . classList . add ( 'mod-active' ) ; } else { this . repeatButton . classList . remove ( 'mod-active' ) ; } } playPauseButton : HTMLButtonElement ; stopButton : HTMLButtonElement ; repeatButton : HTMLButtonElement ; model : PlayModel ; }