import { on } from './utils' ; import { ExecuteCommandFunction , SelectorPrivate , } from '../editor/commands-definitions' ; export type ButtonHandlers = SelectorPrivate | [ SelectorPrivate , ... any [ ] ] | { default : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; pressed ? : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; alt ? : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; altshift ? : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; shift ? : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; pressAndHoldStart ? : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; pressAndHoldEnd ? : SelectorPrivate | [ SelectorPrivate , ... any [ ] ] ; } ; /**
 * Attach event handlers to an element so that it will react by executing
 * a command when pressed.
 * `"command"` can be:
 * - a string, a single selector
 * - an array, whose first element is a selector followed by one or more arguments.
 * - an object, with the following keys:
 *    * 'default': command performed on up, with a down + up sequence with no
 *      delay between down and up
 *    * 'alt', 'shift', 'altshift' keys: command performed on up with
 *      one of these modifiers pressed
 *    * 'pressed': command performed on 'down'
 *    * 'pressAndHoldStart': command performed after a tap/down followed by a
 * delay (optional)
 *    * 'pressAndHoldEnd': command performed on up, if there was a delay
 *     between down and up, if absent, 'default' is performed
 * The value of the keys specify which selector (string
 * or array) to perform depending on the keyboard state when the button is
 * pressed.
 *
 * The 'pressed' and 'active' classes will get added to
 * the element, as the :hover and :active pseudo-classes are not reliable
 * (at least on Chrome Android).
 *
 */ export function attachButtonHandlers ( executeCommand , element , command ) { if ( typeof command === 'object' && ( 'default' in command || 'pressed' in command ) ) { if ( command . default ) { element . dataset . command = JSON . stringify ( command . default ) ; } if ( command . alt ) { element . dataset . commandAlt = JSON . stringify ( command . alt ) ; } if ( command . altshift ) { element . dataset . commandAltshift = JSON . stringify ( command . altshift ) ; } if ( command . shift ) { element . dataset . commandShift = JSON . stringify ( command . shift ) ; } if ( command . pressed ) { element . dataset . commandPressed = JSON . stringify ( command . pressed ) ; } if ( command . pressAndHoldStart ) { element . dataset . commandPressAndHoldStart = JSON . stringify ( command . pressAndHoldStart ) ; } if ( command . pressAndHoldEnd ) { element . dataset . commandPressAndHoldEnd = JSON . stringify ( command . pressAndHoldEnd ) ; } } else { element . dataset . command = JSON . stringify ( command ) ; } let pressHoldStart ; let pressHoldElement ; let touchID ; let syntheticTarget ; let pressAndHoldTimer ; on ( element , 'mousedown touchstart:passive' , ( ev ) => { if ( ev . type !== 'mousedown' || ev . buttons === 1 ) { ev . stopPropagation ( ) ; if ( ev . type !== 'touchstart' ) { ev . preventDefault ( ) ; } document . body . style [ '-webkit-user-select' ] = 'none' ; element . classList . add ( 'is-pressed' ) ; pressHoldStart = Date . now ( ) ; if ( ev . type === 'touchstart' ) { touchID = ev . changedTouches [ 0 ] . identifier ; } const command = element . getAttribute ( 'data-command-pressed' ) ; if ( command ) { executeCommand ( JSON . parse ( command ) ) ; } const pressAndHoldStartCommand = element . getAttribute ( 'data-command-press-and-hold-start' ) ; if ( pressAndHoldStartCommand ) { pressHoldElement = element ; if ( pressAndHoldTimer ) { clearTimeout ( pressAndHoldTimer ) ; } pressAndHoldTimer = setTimeout ( ( ) => { if ( element . classList . contains ( 'is-pressed' ) ) { executeCommand ( JSON . parse ( pressAndHoldStartCommand ) ) ; } } , 300 ) ; } } } ) ; on ( element , 'mouseleave touchcancel' , ( ) => { element . classList . remove ( 'is-pressed' ) ; } ) ; on ( element , 'touchmove:passive' , ( ev ) => { for ( let i = 0 ; i < ev . changedTouches . length ; i ++ ) { if ( ev . changedTouches [ i ] . identifier === touchID ) { const target = document . elementFromPoint ( ev . changedTouches [ i ] . clientX , ev . changedTouches [ i ] . clientY ) ; if ( target !== syntheticTarget && syntheticTarget ) { syntheticTarget . dispatchEvent ( new MouseEvent ( 'mouseleave' ) , { bubbles : true , } ) ; syntheticTarget = null ; } if ( target ) { syntheticTarget = target ; target . dispatchEvent ( new MouseEvent ( 'mouseenter' , { bubbles : true , buttons : 1 , } ) ) ; } } } } ) ; on ( element , 'mouseenter' , ( ev ) => { if ( ev . buttons === 1 ) { element . classList . add ( 'is-pressed' ) ; } } ) ; on ( element , 'mouseup touchend click' , ( ev ) => { document . body . style [ '-webkit-user-select' ] = '' ; if ( syntheticTarget ) { ev . stopPropagation ( ) ; ev . preventDefault ( ) ; const target = syntheticTarget ; syntheticTarget = null ; target . dispatchEvent ( new MouseEvent ( 'mouseup' , { bubbles : true } ) ) ; return ; } element . classList . remove ( 'is-pressed' ) ; element . classList . add ( 'is-active' ) ; if ( ev . type === 'click' && ev . detail !== 0 ) { ev . stopPropagation ( ) ; ev . preventDefault ( ) ; return ; } window . setTimeout ( ( ) => { element . classList . remove ( 'is-active' ) ; } , 150 ) ; let command = element . getAttribute ( 'data-command-press-and-hold-end' ) ; const now = Date . now ( ) ; if ( element !== pressHoldElement || now < pressHoldStart + 300 ) { command = null ; } if ( ! command && ev . altKey && ev . shiftKey ) { command = element . getAttribute ( 'data-command-altshift' ) ; } if ( ! command && ev . altKey ) { command = element . getAttribute ( 'data-command-alt' ) ; } if ( ! command && ev . shiftKey ) { command = element . getAttribute ( 'data-command-shift' ) ; } if ( ! command ) { command = element . getAttribute ( 'data-command' ) ; } if ( command ) { executeCommand ( JSON . parse ( command ) ) ; } ev . stopPropagation ( ) ; ev . preventDefault ( ) ; } ) ; }