import { includes , object , take } from '@remirror/core-helpers' ; import type { KeyboardConstructorProps , KeyboardEventName , ModifierInformation , OptionsProps , OptionsWithTypingProps , TextInputProps , TypingInputProps , } from './test-keyboard-types' ; import { cleanKey , createKeyboardEvent , getModifierInformation } from './test-keyboard-utils' ; import { isUSKeyboardCharacter , noKeyPress , noKeyUp , SupportedCharacters , usKeyboardLayout , } from './us-keyboard-layout' ; export interface BatchedKeyboardAction { /**
     * When called will dispatched the stored event.
     */ dispatch : ( ) => void ; /**
     * The event that will be dispatched.
     */ event : KeyboardEvent ; /**
     * The type of the event.
     */ type : KeyboardEventName ; } /**
 * The callback function signature for the `eachEvent` which is available when
 * `batch` is true.
 */ export type BatchedCallback = ( action : BatchedKeyboardAction , index : number , actions : BatchedKeyboardAction [ ] ) => void ; export class Keyboard { static create ( props ) { return new Keyboard ( props ) ; } static get defaultOptions ( ) : KeyboardEventInit { return { bubbles : true , cancelable : true , composed : true , } ; } status : 'started' | 'ended' | 'idle' = 'idle' ; private readonly target : HTMLElement ; private readonly defaultOptions : KeyboardEventInit ; private readonly isMac : boolean ; private readonly batch : boolean ; private readonly onEventDispatch ? : ( event : KeyboardEvent ) => void ; private actions : BatchedKeyboardAction [ ] = [ ] ; private get started ( ) { return this . status === 'started' ; } constructor ( { target , defaultOptions = Keyboard . defaultOptions , isMac = false , batch = false , onEventDispatch , } ) { this . target = target as HTMLElement ; this . defaultOptions = defaultOptions ; this . isMac = isMac ; this . batch = batch ; this . onEventDispatch = onEventDispatch ; } /**
     * Starts the fake timers and sets the keyboard status to 'started'
     */ start ( ) { if ( this . started ) { return this ; } this . status = 'started' ; return this ; } /**
     * Ends the fake timers and sets the keyboard status to 'ended'
     */ end ( ) { if ( ! this . started ) { return this ; } if ( this . batch ) { this . runBatchedEvents ( ) ; this . actions = [ ] ; } this . status = 'ended' ; return this ; } /**
     * When batched is true the user can run through each event and fire as they
     * please.
     */ forEach ( fn ) { if ( ! this . started ) { return this ; } if ( ! this . batch ) { throw new Error ( `'forEach' is only available when 'batched' is set to 'true'.` ) ; } this . actions . forEach ( fn ) ; this . actions = [ ] ; this . status = 'ended' ; return this ; } /**
     * Runs all the batched events.
     */ private runBatchedEvents ( ) { this . actions . forEach ( ( action ) => { action . dispatch ( ) ; } ) ; } param props - see  TextInputProps usChar ( { text , options = object ( ) , typing = false } ) { if ( ! isUSKeyboardCharacter ( text ) ) { throw new Error ( 'This is not a supported character. For generic characters use the `keyboard.char` method instead' ) ; } return this . char ( { text , options , typing } ) ; } param props - see  TextInputProps char ( { text , options , typing } ) { options = { ... options , ... ( isUSKeyboardCharacter ( text ) ? cleanKey ( text ) : { key : text } ) , } ; this . fireAllEvents ( { options , typing } ) ; return this ; } param props - see  OptionsProps keyDown = ( { options } ) : this => { return this . dispatchEvent ( 'keydown' , options ) ; } ; param props - see  OptionsProps keyPress = ( { options } ) : this => { return this . dispatchEvent ( 'keypress' , options ) ; } ; param props - see  OptionsProps keyUp = ( { options } ) : this => { return this . dispatchEvent ( 'keyup' , options ) ; } ; param props - see  TypingInputProps type ( { text , options = object ( ) } ) { for ( const char of text ) { this . char ( { text : char , options , typing : true } ) ; } return this ; } param props - see  TextInputProps mod ( { text , options = object ( ) } ) { let modifiers = text . split ( /-(?!$)/ ) ; let result = modifiers [ modifiers . length - 1 ] ?? '' ; modifiers = take ( modifiers , modifiers . length - 1 ) ; if ( result === 'Space' ) { result = ' ' ; } const info = getModifierInformation ( { modifiers , isMac : this . isMac } ) ; this . fireModifierEvents ( info , 'keydown' ) ; this . type ( { text : result , options : { ... options , ... info } } ) ; this . fireModifierEvents ( info , 'keyup' ) ; return this ; } param options - see  OptionsWithTypingProps private fireAllEvents ( { options , typing = false } ) { this . keyDown ( { options } ) ; if ( ! includes ( noKeyPress , options . key ) || ( typing && isUSKeyboardCharacter ( options . key ) && usKeyboardLayout [ options . key ] . text ) ) { this . keyPress ( { options } ) ; } if ( ! includes ( noKeyUp , options . key ) ) { this . keyUp ( { options } ) ; } return this ; } param info - the modifier information for the keys see
     *  ModifierInformation param type private fireModifierEvents ( { altKey , ctrlKey , metaKey , shiftKey } , type ) { const event = type === 'keydown' ? this . keyDown : this . keyUp ; if ( shiftKey ) { event ( { options : { ... this . defaultOptions , ... cleanKey ( 'Shift' ) } } ) ; } if ( metaKey ) { if ( this . isMac ) { event ( { options : { ... this . defaultOptions , ... cleanKey ( 'Meta' ) } } ) ; } else { event ( { options : { ... this . defaultOptions , ... cleanKey ( 'Control' ) } } ) ; } } if ( ctrlKey ) { event ( { options : { ... this . defaultOptions , ... cleanKey ( 'Control' ) } } ) ; } if ( altKey ) { event ( { options : { ... this . defaultOptions , ... cleanKey ( 'Alt' ) } } ) ; } } param type param options - options passed to the keyboard event. See
     *  KeyboardEventInit private dispatchEvent ( type , options ) { if ( ! this . started ) { this . start ( ) ; } const event = createKeyboardEvent ( type , { ... this . defaultOptions , ... options } ) ; const dispatch = ( ) => { this . target . dispatchEvent ( event ) ; if ( this . onEventDispatch ) { this . onEventDispatch ( event ) ; } } ; if ( this . batch ) { this . actions . push ( { dispatch , event , type } ) ; } else { dispatch ( ) ; } return this ; } }