import { throwIfNotInBrowser } from '../common/capabilities' ; import { Atom } from '../core/atom-class' ; import type { Range } from '../public/mathfield' ; import { OriginValidator } from '../public/options' ; import { MathfieldPrivate } from './mathfield-private' ; export type Rect = { top : number ; bottom : number ; left : number ; right : number ; } ; export function on ( element , inSelectors , listener , options ? ) { const selectors = inSelectors . split ( ' ' ) ; for ( const sel of selectors ) { const m = sel . match ( /(.*):(.*)/ ) ; if ( m ) { const options2 = options ?? { } ; if ( m [ 2 ] === 'active' ) { options2 . passive = false ; } else { options2 [ m [ 2 ] ] = true ; } element . addEventListener ( m [ 1 ] , listener , options2 ) ; } else { element . addEventListener ( sel , listener , options ) ; } } } export function off ( element , inSelectors , listener , options ? ) { const selectors = inSelectors . split ( ' ' ) ; for ( const sel of selectors ) { const m = sel . match ( /(.*):(.*)/ ) ; if ( m ) { const options2 = options ?? { } ; if ( m [ 2 ] === 'active' ) { options2 . passive = false ; } else { options2 [ m [ 2 ] ] = true ; } element . removeEventListener ( m [ 1 ] , listener , options2 ) ; } else { element . removeEventListener ( sel , listener , options ) ; } } } export function getSharedElement ( id ) { throwIfNotInBrowser ( ) ; let result = document . getElementById ( id ) ; if ( result ) { result . dataset . refcount = Number ( Number . parseInt ( result . getAttribute ( 'data-refcount' ) ?? '0' ) + 1 ) . toString ( ) ; } else { result = document . createElement ( 'div' ) ; result . setAttribute ( 'aria-hidden' , 'true' ) ; result . dataset . refcount = '1' ; result . id = id ; document . body . append ( result ) ; } return result ; } export function releaseSharedElement ( element ? ) { if ( ! element ) return ; const refcount = Number . parseInt ( element . getAttribute ( 'data-refcount' ) ?? '0' ) ; if ( refcount <= 1 ) { element . remove ( ) ; } else { element . dataset . refcount = Number ( refcount - 1 ) . toString ( ) ; } } /**
 * Checks if the argument is a valid Mathfield.
 * After a Mathfield has been destroyed (for example by calling `dispose()`
 * the Mathfield is no longer valid. However, there may be some pending
 * operations invoked via requestAnimationFrame() for example, that would
 * need to ensure the mathfield is still valid by the time they're executed.
 */ export function isValidMathfield ( mf ) { return mf . element !== undefined && mf . element . mathfield === mf ; } /**
 * Return the element which has the caret
 */ function findElementWithCaret ( element ) { return ( element . querySelector ( '.ML__caret' ) ?? element . querySelector ( '.ML__text-caret' ) ?? element . querySelector ( '.ML__latex-caret' ) ) ; } /**
 * Return the (x,y) client coordinates of the caret
 */ export function getCaretPoint ( element ) { const caret = findElementWithCaret ( element ) ; if ( ! caret ) return null ; const bounds = caret . getBoundingClientRect ( ) ; return { x : bounds . right , y : bounds . bottom , height : bounds . height , } ; } function branchId ( atom ) { if ( ! atom . parent ) return 'root' ; let result = atom . parent . id ?? '' ; result += typeof atom . treeBranch === 'string' ? '-' + atom . treeBranch : `-${ atom . treeBranch ! [ 0 ] }/${ atom . treeBranch ! [ 0 ] }` ; return result ; } export function adjustForScrolling ( mathfield , rect ) { if ( ! rect ) return null ; const fieldRect = mathfield . field ! . getBoundingClientRect ( ) ; const w = rect . right - rect . left ; const h = rect . bottom - rect . top ; const left = Math . ceil ( rect . left - fieldRect . left + mathfield . field ! . scrollLeft ) ; const top = Math . ceil ( rect . top - fieldRect . top ) ; return { left , right : left + w , top , bottom : top + h , } ; } function getNodeBounds ( node ) { const bounds = node . getBoundingClientRect ( ) ; const marginRight = parseInt ( getComputedStyle ( node ) . marginRight ) ; const result = { top : bounds . top - 1 , bottom : bounds . bottom , left : bounds . left , right : bounds . right - 1 + marginRight , } ; if ( node . children . length > 0 && node . tagName . toUpperCase ( ) !== 'SVG' ) { for ( const child of node . children ) { if ( child . nodeType === 1 ) { if ( 'atom-id' in ( child as HTMLElement ) . dataset && ! child . classList . contains ( 'pstrut' ) ) { const r = getNodeBounds ( child ) ; result . left = Math . min ( result . left , r . left ) ; result . right = Math . max ( result . right , r . right ) ; result . top = Math . min ( result . top , r . top ) ; result . bottom = Math . max ( result . bottom , r . bottom ) ; } } } } return result ; } export function getAtomBounds ( mathfield , atom ) { if ( ! atom . id ) return null ; let result = mathfield . _atomBoundsCache ?. get ( atom . id ) ?? null ; if ( result !== null ) return result ; const node = mathfield . field ! . querySelector ( `[data-atom-id="${ atom . id }"]` ) ; if ( ! node ) { console . log ( atom . id ) ; } result = node ? getNodeBounds ( node ) : null ; if ( mathfield . _atomBoundsCache ) { if ( result ) { mathfield . _atomBoundsCache . set ( atom . id , result ) ; } else { mathfield . _atomBoundsCache . delete ( atom . id ) ; } } return result ?? null ; } function getRangeBounds ( mathfield , range , options ? ) { const rects = new Map < string , Rect > ( ) ; for ( const atom of mathfield . model . getAtoms ( range , { includeChildren : true , } ) ) { if ( options ?. excludeAtomsWithBackground && atom . style . backgroundColor ) { break ; } const bounds = adjustForScrolling ( mathfield , getAtomBounds ( mathfield , atom ) ) ; if ( bounds ) { const id = branchId ( atom ) ; if ( rects . has ( id ) ) { const r = rects . get ( id ) ! ; rects . set ( id , { left : Math . min ( r . left , bounds . left ) , right : Math . max ( r . right , bounds . right ) , top : Math . min ( r . top , bounds . top ) , bottom : Math . max ( r . bottom , bounds . bottom ) , } ) ; } else { rects . set ( id , bounds ) ; } } } return [ ... rects . values ( ) ] ; } export function getSelectionBounds ( mathfield , options ? ) { return mathfield . model . selection . ranges . reduce ( ( acc , x ) => acc . concat ( ... getRangeBounds ( mathfield , x , options ) ) , [ ] ) ; } export function validateOrigin ( origin , originValidator ) { if ( originValidator === 'none' ) { return true ; } if ( originValidator === 'same-origin' ) { return origin === window . origin ; } if ( typeof originValidator === 'function' ) { return originValidator ( origin ) ; } return false ; }