UNPKG

@muban/muban

Version:

Writing components for server-rendered HTML

132 lines (131 loc) 5.28 kB
import { computed, unref } from '@vue/reactivity'; import { watch, watchEffect } from '@vue/runtime-core'; import { checkInitialBindingState } from '../utils/checkInitialBindingState'; export function checkedBinding(target, model, bindingHelpers) { let useElementValue = false; let saveOldValue = null; let oldElementValue = null; const checkedValue = computed(() => { // Treat "value" like "checkedValue" when it is included with "checked" binding if (bindingHelpers.hasBinding('checkedValue')) { return unref(bindingHelpers.getBinding('checkedValue')); } if (useElementValue) { if (bindingHelpers.hasBinding('value')) { return unref(bindingHelpers.getBinding('value')); } return target.value; } }); const isCheckbox = target.type === 'checkbox'; const isRadio = target.type === 'radio'; // Only bind to check boxes and radio buttons if (!isCheckbox && !isRadio) { return () => undefined; } const rawValue = unref(model); const valueIsArray = isCheckbox && Array.isArray(rawValue); useElementValue = isRadio || valueIsArray; const getNewHtmlValueFromModel = () => { const modelValue = unref(model); const elementValue = checkedValue.value; let newValue; if (Array.isArray(modelValue)) { // When a checkbox is bound to an array, // being checked represents its value being present in that array newValue = modelValue.includes(elementValue); } else if (isCheckbox && elementValue === undefined) { // When a checkbox is bound to any other value (not an array) and "checkedValue" is not defined, // being checked represents the value being trueish newValue = Boolean(modelValue); } else { newValue = elementValue === modelValue; } return newValue; }; // update the checkbox based on model changes const updateHtml = () => { target.checked = getNewHtmlValueFromModel(); }; const getNewModelValueFromHtml = () => { const isChecked = target.checked; let elementValue = checkedValue.value; // We can ignore unchecked radio buttons, because some other radio // button will be checked, and that one can take care of updating state. // Also ignore value changes to an already unchecked checkbox. if (!isChecked && isRadio) { return; } const modelValue = unref(model); let newModelValue; if (Array.isArray(modelValue)) { saveOldValue = oldElementValue; oldElementValue = elementValue; if (saveOldValue !== elementValue) { // When we're responding to the checkedValue changing, and the element is // currently checked, replace the old elem value with the new elem value // in the model array. if (isChecked) { newModelValue = modelValue // add current .concat(elementValue) // remove old .filter((v) => v !== saveOldValue); } else { newModelValue = modelValue.filter((v) => v !== elementValue); } } else { // When we're responding to the user having checked/unchecked a checkbox, // add/remove the element value to the model array. // eslint-disable-next-line no-lonely-if if (isChecked) { newModelValue = modelValue.concat(elementValue); } else { newModelValue = modelValue.filter((v) => v !== elementValue); } } } else { if (isCheckbox) { if (elementValue === undefined) { elementValue = isChecked; } else if (!isChecked) { elementValue = undefined; } } newModelValue = elementValue; } return newModelValue; }; // update the modal based on user input const updateModel = () => { model.value = getNewModelValueFromHtml(); }; if (checkInitialBindingState('checked', target.checked, model.value, unref(bindingHelpers.getBinding('initialValueSource'))) === 'binding') { updateModel(); } // when checkedValue changes it should first update the modal, // so when it later triggers the updateHTML, the modal already correctly reflects the updated state const unwatchCheckedValue = watch(() => checkedValue.value, updateModel); const unwatch = watchEffect(updateHtml); target.addEventListener('change', updateModel); return () => { unwatch(); unwatchCheckedValue(); target.removeEventListener('change', updateModel); }; } export function checkedValueBinding(target, model) { const unwatch = watchEffect(() => { target.value = unref(model).value; }); return () => { unwatch(); }; }