import type { TextToSpeechOptions } from '../public/options' ; import { Atom } from '../core/atom' ; import { speakableText } from './speech' ; import type { ModelPrivate } from '../editor-model/model-private' ; import type { MathfieldPrivate } from '../editor-mathfield/mathfield-private' ; import { AnnounceVerb } from '../editor-model/utils' ; /**
 * Given an atom, describe the relationship between the atom
 * and its siblings and their parent.
 */ function relationName ( atom ) { let result = undefined ; if ( atom . treeBranch === 'body' ) { result = { enclose : 'cross out' , leftright : 'fence' , surd : 'square root' , root : 'math field' , mop : 'operator' , } [ atom . parent ! . type ] ; } else if ( atom . parent ! . type === 'genfrac' ) { if ( atom . treeBranch === 'above' ) { return 'numerator' ; } if ( atom . treeBranch === 'below' ) { return 'denominator' ; } } else if ( atom . parent ! . type === 'surd' ) { if ( atom . treeBranch === 'above' ) { result = 'index' ; } } else if ( atom . treeBranch === 'superscript' ) { result = 'superscript' ; } else if ( atom . treeBranch === 'subscript' ) { result = 'subscript' ; } if ( ! result ! ) { console . log ( 'unknown relationship' ) ; } return result ?? 'parent' ; } param action param previousPosition export function defaultAnnounceHook ( mathfield , action , previousPosition ? , atoms ? ) { let liveText = '' ; if ( action === 'plonk' ) { mathfield . plonkSound ?. play ( ) . catch ( ( error ) => console . warn ( error ) ) ; mathfield . resetKeystrokeBuffer ( ) ; } else if ( action === 'delete' ) { liveText = speakableText ( mathfield . options , 'deleted: ' , atoms ! ) ; } else if ( action === 'focus' || action . includes ( 'move' ) ) { liveText = getRelationshipAsSpokenText ( mathfield . model , previousPosition ) + ( mathfield . model . selectionIsCollapsed ? '' : 'selected: ' ) + getNextAtomAsSpokenText ( mathfield . model , mathfield . options ) ; } else if ( action === 'replacement' ) { liveText = speakableText ( mathfield . options , '' , mathfield . model . at ( mathfield . model . position ) ) ; } else if ( action === 'line' ) { liveText = speakableText ( mathfield . options , '' , mathfield . model . root ) ; mathfield . keyboardDelegate ! . setAriaLabel ( 'after: ' + liveText ) ; } else { liveText = atoms ? speakableText ( mathfield . options , action + ' ' , atoms ) : action ; } const ariaLiveChangeHack = mathfield . ariaLiveText ! . textContent ! . includes ( '\u00a0' ) ? ' \u202F ' : ' \u00A0 ' ; mathfield . ariaLiveText ! . textContent = liveText + ariaLiveChangeHack ; } function getRelationshipAsSpokenText ( model , previousOffset ? ) { if ( Number . isNaN ( previousOffset ) ) return '' ; const previous = model . at ( previousOffset ! ) ; if ( ! previous ) return '' ; if ( previous . treeDepth <= model . at ( model . position ) . treeDepth ) { return '' ; } let result = '' ; let ancestor = previous . parent ; const newParent = model . at ( model . position ) . parent ; while ( ancestor !== model . root && ancestor !== newParent ) { result += `out of ${ relationName ( ancestor ! ) };` ; ancestor = ancestor ! . parent ; } return result ; } /**
 *
 * Return the spoken text for the atom to the right of the current selection.
 * Take into consideration the position amongst siblings to include 'start of'
 * and 'end of' if applicable.
 */ function getNextAtomAsSpokenText ( model , options ) { if ( ! model . selectionIsCollapsed ) { return speakableText ( options , '' , model . getAtoms ( model . selection ) ) ; } let result = '' ; const cursor = model . at ( model . position ) ; const relation = relationName ( cursor ) ; if ( cursor . isFirstSibling ) { result = ( relation ? 'start of ' + relation : 'unknown' ) + ': ' ; } if ( cursor . isLastSibling ) { if ( ! cursor . isFirstSibling ) { result += relation ? 'end of ' + relation : 'unknown' ; } } else { result += speakableText ( options , '' , cursor ) ; } return result ; }