packageDocumentation module import "./WeightPickerButton.scss" ; import classnames from "classnames" ; import * as React from "react" ; import { ColorDef } from "@itwin/core-common" ; import { RelativePosition , SpecialKey } from "@itwin/appui-abstract" ; import { CommonProps , ElementResizeObserver , Popup } from "@itwin/core-react" ; import { LineWeightSwatch } from "./Swatch" ; public export interface WeightPickerProps extends React . ButtonHTMLAttributes < HTMLButtonElement > , CommonProps { /** active weight */ activeWeight : number ; /** available weights */ weights : number [ ] ; /** color specification */ colorDef ? : ColorDef ; /** function to run when user selects weight swatch */ onLineWeightPick ? : ( ( weight : number ) => void ) | undefined ; /** Disabled or not */ disabled ? : boolean ; /** Readonly or not */ readonly ? : boolean ; /** hide the weight label */ hideLabel ? : boolean ; /** Title to show at top of DropDown */ dropDownTitle ? : string ; } internal interface WeightPickerState { showPopup : boolean ; targetElement : HTMLDivElement | null ; } public export class WeightPickerButton extends React . PureComponent < WeightPickerProps , WeightPickerState > { private _target = React . createRef < HTMLDivElement > ( ) ; private _weightsContainer : HTMLDivElement | null = null ; private _focusTarget = React . createRef < HTMLButtonElement > ( ) ; internal constructor ( props ) { super ( props ) ; this . state = { showPopup : false , targetElement : null } ; } public setFocus ( ) { this . _focusTarget . current && this . _focusTarget . current . focus ( ) ; } public static defaultProps = { weights : [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] } ; private _togglePopup = ( ) => { if ( this . props . readonly ) return ; this . setState ( ( prevState ) => ( { showPopup : ! prevState . showPopup } ) ) ; } ; private _onPopupOpened = ( ) => { } ; private _closePopup = ( ) => { this . setState ( ( _prevState ) => ( { showPopup : false } ) ) ; } ; private _handleWeightPicked = ( weight ) => { this . _closePopup ( ) ; this . props . onLineWeightPick && this . props . onLineWeightPick ( weight ) ; } ; public override componentDidMount ( ) { this . setState ( { targetElement : this . _target . current } ) ; } private buildIdForWeight ( weight ) { return `ui-core-lineweight-${ weight }` ; } private moveFocusInPopup ( moveUp , event ) { const activeElement = document . activeElement ; if ( this . _weightsContainer && activeElement && activeElement . tagName === "BUTTON" ) { const values = activeElement . id . split ( "-" ) ; if ( values . length === 4 && values [ 2 ] === "lineweight" ) { const weight = parseInt ( values [ 3 ] , 10 ) ; const foundIndex = this . props . weights . findIndex ( ( val ) => val === weight ) ; if ( foundIndex < 0 ) return ; const nextWeight = moveUp ? ( foundIndex <= 0 ? this . props . weights [ this . props . weights . length - 1 ] : this . props . weights [ foundIndex - 1 ] ) : ( foundIndex < this . props . weights . length - 1 ? this . props . weights [ foundIndex + 1 ] : this . props . weights [ 0 ] ) ; const focusLocation = this . _weightsContainer . querySelector ( `#${ this . buildIdForWeight ( nextWeight ) }` ) as HTMLButtonElement ; if ( focusLocation ) { focusLocation . focus ( ) ; event . preventDefault ( ) ; event . stopPropagation ( ) ; } } } } private _handleKeyDown = ( event ) => { if ( event . key === SpecialKey . Enter ) { event . preventDefault ( ) ; event . stopPropagation ( ) ; const weightButton = document . activeElement as HTMLElement ; if ( weightButton . tagName === "BUTTON" ) { try { const values = weightButton . id . split ( "-" ) ; if ( values . length ) { const weight = parseInt ( values [ values . length - 1 ] , 10 ) ; if ( ! isNaN ( weight ) ) { if ( this . props . onLineWeightPick ) this . props . onLineWeightPick ( weight ) ; } } } catch { } } this . _closePopup ( ) ; } else { switch ( event . key ) { case SpecialKey . ArrowDown : this . moveFocusInPopup ( false , event ) ; break ; case SpecialKey . ArrowUp : this . moveFocusInPopup ( true , event ) ; break ; } } } ; private _setWeightContainer = ( el ) => { this . _weightsContainer = el ; } ; private renderPopup ( title ) { return ( < div className = "components-weightpicker-popup-container" > { title && < h4 > { title } < /h4>} { } < ul data - testid = "components-weightpicker-popup-lines" className = "components-weightpicker-popup-lines" onKeyDown = { this . _handleKeyDown } ref = { this . _setWeightContainer } > { this . props . weights . map ( ( weight , index ) => { const classNames = classnames ( "components-weightpicker-swatch" , weight === this . props . activeWeight && "active" ) ; return ( < LineWeightSwatch className = { classNames } key = { index } colorDef = { this . props . colorDef } id = { this . buildIdForWeight ( weight ) } hideLabel = { this . props . hideLabel } onClick = { this . _handleWeightPicked . bind ( this , weight ) } weight = { weight } / > ) ; } ) } < /ul> < /div>); } public override render ( ) { const buttonClassNames = classnames ( "components-weightpicker-button" , this . props . className ) ; return ( < > < div ref = { this . _target } style = { this . props . style } > < LineWeightSwatch data - testid = "components-weightpicker-button" className = { buttonClassNames } weight = { this . props . activeWeight } colorDef = { this . props . colorDef } readonly = { this . props . readonly } disabled = { this . props . disabled } hideLabel = { this . props . hideLabel } aria - haspopup = "listbox" aria - expanded = { this . state . showPopup } onClick = { this . _togglePopup } / > < /div> < ElementResizeObserver watchedElement = { this . state . targetElement } render = { ( { width } ) => ( < Popup className = "components-weightpicker-popup" style = { { width : `${ width }px` } } isOpen = { this . state . showPopup } position = { RelativePosition . Bottom } offset = { 0 } showShadow = { false } onClose = { this . _closePopup } onOpen = { this . _onPopupOpened } focusTarget = { `#${ this . buildIdForWeight ( this . props . activeWeight ) }` } moveFocus = { true } target = { this . state . targetElement } closeOnNestedPopupOutsideClick > { this . renderPopup ( this . props . dropDownTitle ) } < /Popup>)}/ > < />); } }