import { isString } from '@vue/shared' ; import { isRef } from '@vue/reactivity' ; import type { Ref } from '@vue/reactivity' ; import type { TemplatePatcher } from '@kirei/html' ; import { push } from '../runtime/queue' ; import { getComponentInstance } from '../runtime/instance' ; const DEFAULT_PROP = 'modelValue' ; function selectHandler ( el ) { const options = el . selectedOptions ; if ( el . multiple ) { return ! options . length ? [ ] : Array . from ( options ) . map ( o => o . value ) ; } return options [ 0 ] ?. value ; } function selectCommit ( el , value ) { const { options } = el ; if ( el . multiple ) { const values = value as string [ ] ; for ( let i = 0 ; i < options . length ; i ++ ) { const o = options [ i ] ; o . selected = values . includes ( o . value ) ; } } else { for ( let i = 0 ; i < options . length ; i ++ ) { const o = options [ i ] ; o . selected = value === o . value ; } } } function radioCommit ( el , value ) { el . checked = value === el . value ; } function checkboxHandler ( el , ref ) { const list = ref . value ; if ( Array . isArray ( list ) ) { const idx = list . indexOf ( el . value ) ; if ( idx >= 0 ) { list . splice ( idx , 1 ) ; } else { list . push ( el . value ) ; } return list ; } return el . checked ; } function checkboxCommit ( el , value ) { el . checked = Array . isArray ( value ) ? value . includes ( el . value ) : ! ! value ; } function castValue ( value , trim , number ) { if ( isString ( value ) ) { if ( trim ) { value = value . trim ( ) ; } if ( number ) { const num = parseFloat ( value as string ) ; if ( ! isNaN ( num ) ) { value = num ; } } } return value ; } function nativeModel ( el , arg , modifiers ) { const castNumber = modifiers . includes ( 'number' ) ; const trimValue = modifiers . includes ( 'trim' ) ; let ref ; let event ; let prop = arg ; let handler ; let commit ; const tag = el . tagName . toLowerCase ( ) ; const type = ( el as HTMLInputElement ) . type ; const isInput = tag == 'input' ; if ( tag == 'select' ) { event = 'change' ; handler = selectHandler ; commit = selectCommit ; } else if ( isInput && type == 'checkbox' ) { event = 'change' ; handler = checkboxHandler ; commit = checkboxCommit ; } else if ( isInput && type == 'radio' ) { event = 'change' ; prop = 'value' ; commit = radioCommit ; } else if ( isInput || tag == 'textarea' ) { event = modifiers . includes ( 'lazy' ) ? 'change' : 'input' ; prop = 'value' ; commit = ( el , value ) => { el [ prop ] = value ; } ; } else { throw new TypeError ( `Model not supported for element '${ tag }'.` ) ; } function listener ( e ) { e . stopPropagation ( ) ; const value = handler ? handler ( el , ref ) : el [ prop ] ; ref . value = castValue ( value , trimValue , castNumber ) ; } el . addEventListener ( event , listener , false ) ; return ( pending ) => { if ( ! isRef ( pending ) ) { throw new TypeError ( `Model directive requires a ref as its value` ) ; } ref = pending ; const value = ref . value ; push ( ( ) => commit ( el , value ) ) ; } ; } export function model ( el , arg , modifiers ) { const instance = getComponentInstance ( el ) ; if ( instance ) { const castNumber = modifiers . includes ( 'number' ) ; const trimValue = modifiers . includes ( 'trim' ) ; const prop = arg || DEFAULT_PROP ; let ref ; instance . on ( `update:${ prop }` , ( value ) => { ref . value = value ; } ) ; return ( pending ) => { if ( ! isRef ( pending ) ) { throw new TypeError ( `Model directive requires a ref as its value` ) ; } ref = pending ; instance . props [ prop ] = castValue ( ref . value , trimValue , castNumber ) ; } ; } else if ( arg === '' ) { return nativeModel ( el , arg , modifiers ) ; } throw new TypeError ( `Custom Model only supported for Kirei elements, got '${ el . tagName . toLowerCase ( ) }'.` ) ; }