import { isContentEditable , isElementType } from '..' ; declare global { interface Text { nodeValue : string ; } } export function getNextCursorPosition ( node , offset , direction , inputType ? ) { if ( isTextNode ( node ) && offset + direction >= 0 && offset + direction <= node . nodeValue . length ) { return { node , offset : offset + direction } ; } const nextNode = getNextCharacterContentNode ( node , offset , direction ) ; if ( nextNode ) { if ( isTextNode ( nextNode ) ) { return { node : nextNode , offset : direction > 0 ? Math . min ( 1 , nextNode . nodeValue . length ) : Math . max ( nextNode . nodeValue . length - 1 , 0 ) , } ; } else if ( isElementType ( nextNode , 'br' ) ) { const nextPlusOne = getNextCharacterContentNode ( nextNode , undefined , direction ) ; if ( ! nextPlusOne ) { if ( direction < 0 && inputType === 'deleteContentBackward' ) { return { node : nextNode . parentNode as Node , offset : getOffset ( nextNode ) , } ; } return undefined ; } else if ( isTextNode ( nextPlusOne ) ) { return { node : nextPlusOne , offset : direction > 0 ? 0 : nextPlusOne . nodeValue . length , } ; } else if ( direction < 0 && isElementType ( nextPlusOne , 'br' ) ) { return { node : nextNode . parentNode as Node , offset : getOffset ( nextNode ) , } ; } else { return { node : nextPlusOne . parentNode as Node , offset : getOffset ( nextPlusOne ) + ( direction > 0 ? 0 : 1 ) , } ; } } else { return { node : nextNode . parentNode as Node , offset : getOffset ( nextNode ) + ( direction > 0 ? 1 : 0 ) , } ; } } } function getNextCharacterContentNode ( node , offset , direction ) { const nextOffset = Number ( offset ) + ( direction < 0 ? - 1 : 0 ) ; if ( offset !== undefined && isElement ( node ) && nextOffset >= 0 && nextOffset < node . children . length ) { node = node . children [ nextOffset ] ; } return walkNodes ( node , direction === 1 ? 'next' : 'previous' , isTreatedAsCharacterContent ) ; } function isTreatedAsCharacterContent ( node ) { if ( isTextNode ( node ) ) { return true ; } if ( isElement ( node ) ) { if ( isElementType ( node , [ 'input' , 'textarea' ] ) ) { return ( node as HTMLInputElement ) . type !== 'hidden' ; } else if ( isElementType ( node , 'br' ) ) { return true ; } } return false ; } function getOffset ( node ) { let i = 0 ; while ( node . previousSibling ) { i ++ ; node = node . previousSibling ; } return i ; } function isElement ( node ) { return node . nodeType === 1 ; } function isTextNode ( node ) { return node . nodeType === 3 ; } function walkNodes < T extends Node > ( node , direction , callback ) { for ( ; ; ) { const sibling = node [ `${ direction }Sibling` ] ; if ( sibling ) { node = getDescendant ( sibling , direction === 'next' ? 'first' : 'last' ) ; if ( callback ( node ) ) { return node ; } } else if ( node . parentNode && ( ! isElement ( node . parentNode ) || ( ! isContentEditable ( node . parentNode ) && node . parentNode !== node . ownerDocument ?. body ) ) ) { node = node . parentNode ; } else { break ; } } } function getDescendant ( node , direction ) { while ( node . hasChildNodes ( ) ) { node = node [ `${ direction }Child` ] as ChildNode ; } return node ; }