import { isArray } from '../common/types' ; import { SelectorPrivate , CommandRegistry } from './commands-definitions' ; import type { MathfieldPrivate } from '../editor-mathfield/mathfield-private' ; import { requestUpdate } from '../editor-mathfield/render' ; import { updateAutocomplete , complete } from '../editor-mathfield/autocomplete' ; import { canVibrate } from '../common/capabilities' ; export { SelectorPrivate } ; export const HAPTIC_FEEDBACK_DURATION = 3 ; type CommandTarget = 'model' | 'mathfield' | 'virtual-keyboard' ; interface RegisterCommandOptions { target : CommandTarget ; category ? : 'delete' | 'edit' | 'array-edit' | 'autocomplete' | 'clipboard' | 'scroll' | 'selection-anchor' | 'selection-extend' | 'speech' | 'virtual-keyboard' | '' ; audioFeedback ? : string ; canUndo ? : boolean ; changeContent ? : boolean ; changeSelection ? : boolean ; } export const COMMANDS = { } ; /**
 * Register one or more selectors.
 * The selector function return true to request a render update of the expression.
 */ export function register ( commands , options ? ) { options = options ?? { target : 'mathfield' , canUndo : false } ; for ( const selector of Object . keys ( commands ) ) { console . assert ( ! COMMANDS [ selector ] , 'Selector already defined: ' , selector ) ; COMMANDS [ selector ] = { ... options , fn : commands [ selector ] } ; } } export function getCommandTarget ( command ) { let selector ; selector = isArray ( command ) ? command [ 0 ] : command ; selector = selector . replace ( /-\w/g , ( m ) => m [ 1 ] . toUpperCase ( ) ) as SelectorPrivate ; return COMMANDS [ selector ] ?. target ; } export function perform ( mathfield , command ) { if ( ! command ) { return false ; } let selector ; let args = [ ] ; let handled = false ; let dirty = false ; if ( isArray ( command ) ) { selector = command [ 0 ] ; args = command . slice ( 1 ) ; } else { selector = command ; } selector = selector . replace ( /-\w/g , ( m ) => m [ 1 ] . toUpperCase ( ) ) as SelectorPrivate ; const commandTarget = COMMANDS [ selector ] ?. target ; if ( commandTarget === 'model' ) { if ( mathfield . options . readOnly && /^(paste|cut|insert|delete|transpose|add)/ . test ( selector ) ) { mathfield . model . announce ( 'plonk' ) ; return false ; } if ( /^(delete|transpose|add)/ . test ( selector ) ) { if ( selector !== 'deleteBackward' ) { mathfield . resetKeystrokeBuffer ( ) ; } } if ( /^(delete|transpose|add)/ . test ( selector ) && mathfield . mode !== 'latex' ) { mathfield . popUndoStack ( ) ; mathfield . snapshot ( ) ; } COMMANDS [ selector ] ! . fn ( mathfield . model , ... args ) ; if ( /^(delete|transpose|add)/ . test ( selector ) && mathfield . mode !== 'latex' ) { mathfield . snapshot ( ) ; } if ( mathfield . mode === 'latex' ) { updateAutocomplete ( mathfield ) ; } dirty = true ; handled = true ; } else if ( commandTarget === 'virtual-keyboard' ) { dirty = mathfield . virtualKeyboard ?. executeCommand ( command ) ?? false ; handled = true ; } else if ( COMMANDS [ selector ] ) { dirty = COMMANDS [ selector ] ! . fn ( mathfield , ... args ) ; handled = true ; } else { throw new Error ( 'Unknown command "' + selector + '"' ) ; } if ( commandTarget !== 'virtual-keyboard' ) { if ( ! mathfield . model . selectionIsCollapsed || /^(transpose|paste|complete|((moveToNextChar|moveToPreviousChar|extend).*))_$/ . test ( selector ) ) { mathfield . resetKeystrokeBuffer ( ) ; mathfield . style = { } ; } } if ( dirty ) { requestUpdate ( mathfield ) ; } return handled ; } /**
 * Perform a command, but:
 * * focus the mathfield
 * * provide haptic and audio feedback
 * This is used by the virtual keyboard when command keys (delete, arrows, etc..)
 * are pressed.
 */ export function performWithFeedback ( mathfield , selector ) { mathfield . focus ( ) ; if ( mathfield . options . keypressVibration && canVibrate ( ) ) { navigator . vibrate ( HAPTIC_FEEDBACK_DURATION ) ; } selector = selector . replace ( /-\w/g , ( m ) => m [ 1 ] . toUpperCase ( ) ) as SelectorPrivate ; if ( selector === 'moveToNextPlaceholder' || selector === 'moveToPreviousPlaceholder' || selector === 'complete' ) { mathfield . returnKeypressSound ?. play ( ) . catch ( console . warn ) ; } else if ( selector === 'deleteBackward' || selector === 'deleteForward' || selector === 'deletePreviousWord' || selector === 'deleteNextWord' || selector === 'deleteToGroupStart' || selector === 'deleteToGroupEnd' || selector === 'deleteToMathFieldStart' || selector === 'deleteToMathFieldEnd' ) { mathfield . deleteKeypressSound ?. play ( ) . catch ( console . warn ) ; } else { mathfield . keypressSound ?. play ( ) . catch ( console . warn ) ; } const result = mathfield . executeCommand ( selector ) ; mathfield . scrollIntoView ( ) ; return result ; } register ( { performWithFeedback : ( mathfield , command ) : boolean => performWithFeedback ( mathfield , command ) , } ) ; function nextSuggestion ( mathfield ) { updateAutocomplete ( mathfield , { atIndex : mathfield . suggestionIndex + 1 } ) ; return false ; } function previousSuggestion ( mathfield ) { updateAutocomplete ( mathfield , { atIndex : mathfield . suggestionIndex - 1 } ) ; return false ; } register ( { complete , nextSuggestion , previousSuggestion , } , { target : 'mathfield' , category : 'autocomplete' } ) ;