import nj, { registerExtension, ExtensionDelegateOption, Component, ComponentOption, ExtensionOption } from 'nornj'; import React, { useRef } from 'react'; import { toJS } from 'mobx'; import extensionConfigs from '../../../mobx/extensionConfig'; import { debounce } from '../../utils'; import { MobxFormDataInstance } from '../../interface'; const FORMDATA_NOT_TRIGGER = ['valueChange', 'none']; interface IProps extends React.InputHTMLAttributes { mobxBindDirectiveOptions: ExtensionDelegateOption; MobxBindTag: Component; _mobxBindValue: any; innerRef: React.Ref; } const MobxBindWrap = React.forwardRef( ( { MobxBindTag, mobxBindDirectiveOptions: { tagName, context: { $this }, props: directiveProps }, _mobxBindValue: value, ...props }, ref ) => { let valuePropName = 'value'; let changeEventName = 'onChange'; let componentConfig = nj.getComponentConfig(tagName) || {}; if (typeof componentConfig === 'function') { componentConfig = componentConfig(props); } const args = directiveProps && directiveProps.arguments; const debounceArg = _hasArg(args, 'debounce'); if (componentConfig.valuePropName != null) { valuePropName = componentConfig.valuePropName; } if (componentConfig.changeEventName != null) { changeEventName = componentConfig.changeEventName; } let _value = value.value; const isMultipleSelect = tagName === 'select' && props.multiple; if (componentConfig.needToJS || isMultipleSelect) { _value = toJS(_value); } const changeEvent = props[changeEventName]; let emitChangeDebounced; if (debounceArg) { const { modifiers } = debounceArg; emitChangeDebounced = useRef( debounce(args => { changeEvent && changeEvent.apply($this, args); }, (modifiers && +modifiers[0]) || 100) ); } const compProps: React.InputHTMLAttributes = {}; if (componentConfig.hasEventObject) { const targetPropName = componentConfig.targetPropName || 'value'; const isRadio = tagName === 'input' && props.type === 'radio'; const isCheckbox = tagName === 'input' && props.type === 'checkbox'; if (isRadio) { compProps.checked = props.value === _value; } else if (isCheckbox) { compProps.checked = _value != null && (nj.isArrayLike(_value) ? _value.indexOf(props.value) >= 0 : _value); } else { compProps[valuePropName] = _value; } compProps[changeEventName] = function(e: React.BaseSyntheticEvent) { e && e.persist && e.persist(); _setValue( e.target[targetPropName], { target: e.target, value, args: arguments, changeEventName, changeEvent, valuePropName, emitChangeDebounced, isMultipleSelect, isCheckbox }, $this ); }; } else { const getValueFromEvent = componentConfig.getValueFromEvent; compProps[valuePropName] = _value; compProps[changeEventName] = function(...args: any[]) { _setValue( getValueFromEvent ? getValueFromEvent(...args) : args[0], { value, args, changeEventName, changeEvent, valuePropName, emitChangeDebounced }, $this ); }; } _formDataTrigger(value, changeEventName, componentConfig, true, props, compProps, $this); return ; } ); function _formDataTrigger( value, changeEventName, componentConfig?: ComponentOption, notDirect?, props?, compProps?, $this? ) { const formData: MobxFormDataInstance = value?.source; if (formData?._njMobxFormData) { const fieldName: string = value.prop; const fieldData = formData.fieldDatas.get(fieldName); const fieldDefaultRule = componentConfig?.fieldDefaultRule; if (fieldDefaultRule) { fieldData.setDefaultRule(fieldDefaultRule); } const trigger = fieldData.trigger; if (FORMDATA_NOT_TRIGGER.indexOf(trigger) > -1) { return; } if (!notDirect) { if (trigger === changeEventName) { formData.validate(fieldName).catch(nj.noop); } } else if (trigger !== changeEventName) { const triggerEvent = props[trigger]; compProps[trigger] = function(e: React.BaseSyntheticEvent) { e && e.persist && e.persist(); formData.validate(fieldName).catch(nj.noop); triggerEvent && triggerEvent.apply($this, arguments); }; } } } function _setValue(value, params, $this) { let _value = value; if (params.isMultipleSelect) { _value = nj .arraySlice(params.target.options) .filter(option => option.selected) .map(option => option.value); } else if (params.isCheckbox) { const checkboxValue = params.value.value; if (nj.isArrayLike(checkboxValue)) { if (params.target.checked) { checkboxValue.push(value); } else { checkboxValue.splice(checkboxValue.indexOf(value), 1); } _value = checkboxValue; } else { _value = params.target.checked; } } const setter = params.value.source[`set${nj.upperFirst(params.value.prop)}`]; if (setter) { setter(_value, params.args); } else { params.value.source[params.value.prop] = _value; } _formDataTrigger(params.value, params.changeEventName); if (params.emitChangeDebounced) { params.emitChangeDebounced.current(params.args); } else if (params.changeEvent) { params.changeEvent.apply($this, params.args); } } function _hasArg(args, name) { let ret; args && args.every(arg => { if (arg.name == name) { ret = arg; return false; } return true; }); return ret; } registerExtension( 'mobxBind', options => { const ret = options.value(); if (ret == null) { return ret; } const { tagName, setTagName, tagProps, props } = options; setTagName(MobxBindWrap); tagProps.MobxBindTag = tagName; tagProps.mobxBindDirectiveOptions = options; tagProps._mobxBindValue = ret; }, extensionConfigs.mobxBind as ExtensionOption ); nj.extensions.mstBind = nj.extensions.mobxBind;