import type { TemplatePatcher } from '@kirei/html' ; import { hyphenate , isFunction , isString } from '@vue/shared' ; import { getComponentInstance } from '../runtime/instance' ; import { warn } from '../warn' ; type EventListener = ( e : Event ) => any ; const NOOP = ( ) => { } ; const KEYBOARD_EVENTS = [ 'keydown' , 'keyup' , 'keypress' ] ; const KEYBOARD_MODS = [ 'ctrl' , 'alt' , 'shift' , 'meta' ] ; const KEYBOARD_ALIASES = { 'Enter' : 'enter' , 'Tab' : 'tab' , 'Delete' : 'delete' , 'Backspace' : 'delete' , ' ' : 'space' , 'Spacebar' : 'space' , 'ArrowUp' : 'up' , 'ArrowDown' : 'down' , 'ArrowLeft' : 'left' , 'ArrowRight' : 'right' , } ; const MOUSE_EVENTS = [ 'click' , 'dblclick' , 'mouseup' , 'mousedown' ] ; const MOUSE_KEYS = [ 'left' , 'middle' , 'right' ] ; function hasMeta ( e , meta ) { return ! meta . length || meta . every ( name => e [ `${ name }Key` ] ) ; } function hasMod ( modifiers , mod ) { const idx = modifiers . indexOf ( mod ) ; return idx != - 1 ? ( modifiers . splice ( idx , 1 ) , true ) : false ; } function keyboardListener ( listener , modifiers ) { const meta = KEYBOARD_MODS . filter ( mod => hasMod ( modifiers , mod ) ) ; return ( e ) => { if ( meta . length && ! hasMeta ( e , meta ) ) return ; const key = KEYBOARD_ALIASES [ e . key ] ?? hyphenate ( e . key ) ; if ( modifiers . length && ! modifiers . includes ( key ) ) return ; return listener ( e ) ; } ; } function mouseListener ( listener , modifiers ) { const meta = KEYBOARD_MODS . filter ( mod => hasMod ( modifiers , mod ) ) ; const btn = MOUSE_KEYS . findIndex ( mod => hasMod ( modifiers , mod ) ) ; return ( e ) => { if ( meta . length && ! hasMeta ( e , meta ) ) return ; if ( btn != - 1 && e . button != btn ) return ; return listener ( e ) ; } ; } function nativePatcher ( el , eventName , modifiers ) { const prevent = hasMod ( modifiers , 'prevent' ) ; const stop = hasMod ( modifiers , 'stop' ) ; const self = hasMod ( modifiers , 'self' ) ; const forceBind = prevent || stop ; const options = { capture : hasMod ( modifiers , 'capture' ) , once : hasMod ( modifiers , 'once' ) , passive : hasMod ( modifiers , 'passive' ) , } ; let value = NOOP ; let listener = ( e ) => { if ( self && e . target !== el ) return ; prevent && e . preventDefault ( ) ; stop && e . stopPropagation ( ) ; return value . call ( null , e ) ; } ; if ( modifiers . length ) { if ( KEYBOARD_EVENTS . includes ( eventName ) ) { listener = keyboardListener ( listener , modifiers ) ; } else if ( MOUSE_EVENTS . includes ( eventName ) ) { listener = mouseListener ( listener , modifiers ) ; } } return ( pending = NOOP ) => { if ( ! isFunction ( pending ) ) { throw new TypeError ( 'Kirei: Events can only be bound to functions' ) ; } if ( value !== pending ) { if ( value != NOOP && pending == NOOP && ! forceBind ) { el . removeEventListener ( eventName , listener , options ) ; } else if ( value == NOOP && pending != NOOP || forceBind ) { el . addEventListener ( eventName , listener , options ) ; } } value = pending ; } ; } export function on ( el , arg , modifiers ) { const instance = getComponentInstance ( el ) ; if ( instance ) { if ( ! isString ( arg ) ) { throw new TypeError ( '' ) ; } else if ( instance . options . emits [ arg ] ) { const once = modifiers . includes ( 'once' ) ; let value ; return ( pending ) => { if ( value === pending ) return ; if ( ! isFunction ( pending ) || pending != null ) { warn ( `v-on directive expected a function or a nullable value, got ${ typeof pending }` ) ; } if ( ! pending ) { instance . off ( arg , value ) ; } else if ( once ) { instance . once ( arg , pending ) ; } else { instance . on ( arg , pending ) ; } value = pending ; } ; } } return nativePatcher ( el , arg , modifiers ) ; }