Skip to content

Instantly share code, notes, and snippets.

@fabiospampinato
Created March 16, 2024 19:14
Show Gist options
  • Save fabiospampinato/f50550fbd7a6b03edf2028362d8a707a to your computer and use it in GitHub Desktop.
Save fabiospampinato/f50550fbd7a6b03edf2028362d8a707a to your computer and use it in GitHub Desktop.
use:controlled directive for voby
/* IMPORT */
import {$$, createDirective} from 'voby';
import {useUpdateEffect} from '~/hooks';
/* HELPERS */
const onCheckboxChange = ( target: HTMLInputElement, value: $<undefined | boolean | number | string>, onChange?: ( value: boolean | string ) => void ): void => {
const valuePrev = !!$$(value);
const valueNext = target.checked;
if ( valuePrev === valueNext ) return;
onChange?.( valueNext );
const valueCurrent = !!$$(value);
target.checked = valueCurrent;
};
const onRadioChange = ( target: HTMLInputElement, value: $<undefined | boolean | number | string>, onChange?: ( value: boolean | string ) => void ): void => {
const valuePrev = String ( $$(value) ?? '' );
const valueNext = target.value;
const checkedPrev = ( valuePrev === target.value );
const checkedNext = target.checked;
if ( checkedPrev === checkedNext ) return;
onChange?.( valueNext );
const valueCurrent = String ( $$(value) ?? '' );
const checkedCurrent = ( valueCurrent === target.value );
target.checked = checkedCurrent;
};
const onInputAreaChange = ( target: HTMLInputElement | HTMLTextAreaElement, value: $<undefined | boolean | number | string>, onChange?: ( value: boolean | string ) => void ): void => {
const valuePrev = String ( $$(value) ?? '' );
const valueNext = target.value;
if ( valuePrev === valueNext ) return;
onChange?.( valueNext );
const valueCurrent = String ( $$(value) ?? '' );
target.value = valueCurrent;
};
const onDetailsChange = ( target: HTMLDetailsElement, value: $<undefined | boolean | number | string>, onChange?: ( value: boolean | string ) => void ): void => {
const valuePrev = !!$$(value);
const valueNext = target.open;
if ( valuePrev === valueNext ) return;
onChange?.( valueNext );
const valueCurrent = !!$$(value);
target.open = valueCurrent;
};
/* MAIN */
const Controlled = createDirective ( 'controlled', ( target: Element, value: $<undefined | boolean | number | string>, onChange?: ( value: boolean | string ) => void ): void => {
const isInput = ( target instanceof HTMLInputElement );
const isTextarea = ( target instanceof HTMLTextAreaElement );
const isDetails = ( target instanceof HTMLDetailsElement );
if ( isInput || isTextarea ) {
useUpdateEffect ( value, () => { // It's important to trigger a change event so that other eventual directives can use it
target.dispatchEvent ( new Event ( 'change' ) );
});
if ( isInput && target.type === 'checkbox' ) {
const update = () => onCheckboxChange ( target, value, onChange );
target.addEventListener ( 'change', update );
} else if ( isInput && target.type === 'radio' ) {
const update = () => onRadioChange ( target, value, onChange );
target.addEventListener ( 'change', update );
} else {
const update = () => onInputAreaChange ( target, value, onChange );
target.addEventListener ( 'change', update );
target.addEventListener ( 'input', update );
}
} else if ( isDetails ) {
useUpdateEffect ( value, () => { // It's important to trigger a toggle event so that other eventual directives can use it
target.dispatchEvent ( new Event ( 'toggle' ) );
});
const update = () => onDetailsChange ( target, value, onChange );
target.addEventListener ( 'toggle', update );
} else {
throw new Error ( 'Unsupported element' );
}
});
/* EXPORT */
export default Controlled;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment