import { isComment, isFunction, isNull, isNullish, isString } from '../core/is-type' import { parse } from '../core/parse' import { effect } from '../cause-effect' import type { Enqueue } from '../core/scheduler' import type { UI, StateLike } from '../ui-element' /* === Internal Functions === */ /** * Auto-effect for setting properties of a target element according to a given state * * @since 0.8.0 * @param {UI} ui - UI object of host UIElement and target element to update properties * @param {StateLike} state - state to be set to the host element * @param {string} prop - property name to be updated * @param {() => T} getter - getter function to retrieve current value in the DOM * @param {(value: T) => (element: E) => () => void} setter - callback to be executed when state is changed * @returns {UI} object with host and target */ const autoEffect = ( ui: UI, state: StateLike, prop: string, getter: () => T, setter: (value: T) => (element: E) => () => void, remover?: (element: E) => () => void ): UI => { const fallback = getter() if (!isFunction(state)) ui.host.set( state, isString(state) && isString(fallback) ? parse(ui.host, state, fallback) : fallback, false ) effect((enqueue: Enqueue) => { const current = getter() const value = isFunction(state) ? state(current) : ui.host.get(state) if (!Object.is(value, current)) enqueue( ui.target, prop, remover && isNull(value) ? remover : isNullish(value) ? setter(fallback) : setter(value) ) }) return ui } /* === Exported Functions === */ /** * Set text content of an element * * @since 0.8.0 * @param {StateLike} state - state bounded to the text content */ const setText = (state: StateLike) => (ui: UI): UI => autoEffect( ui, state, 't', () => ui.target.textContent || '', (value: string) => (element: E) => () => { Array.from(element.childNodes) .filter(isComment) .forEach(match => match.remove()) element.append(document.createTextNode(value)) } ) /** * Set property of an element * * @since 0.8.0 * @param {PropertyKey} key - name of property to be set * @param {StateLike} state - state bounded to the property value */ const setProperty = (key: PropertyKey, state: StateLike = key) => (ui: UI): UI => autoEffect( ui, state, `p-${String(key)}`, () => ui.target[key], (value: any) => (element: E) => () => element[key] = value ) /** * Set attribute of an element * * @since 0.8.0 * @param {string} name - name of attribute to be set * @param {StateLike} state - state bounded to the attribute value */ const setAttribute = (name: string, state: StateLike = name) => (ui: UI): UI => autoEffect( ui, state, `a-${name}`, () => ui.target.getAttribute(name), (value: string) => (element: E) => () => element.setAttribute(name, value), (element: E) => () => element.removeAttribute(name) ) /** * Toggle a boolan attribute of an element * * @since 0.8.0 * @param {string} name - name of attribute to be toggled * @param {StateLike} state - state bounded to the attribute existence */ const toggleAttribute = (name: string, state: StateLike = name) => (ui: UI): UI => autoEffect( ui, state, `a-${name}`, () => ui.target.hasAttribute(name), (value: boolean) => (element: E) => () => element.toggleAttribute(name, value) ) /** * Toggle a classList token of an element * * @since 0.8.0 * @param {string} token - class token to be toggled * @param {StateLike} state - state bounded to the class existence */ const toggleClass = (token: string, state: StateLike = token) => (ui: UI): UI => autoEffect( ui, state, `c-${token}`, () => ui.target.classList.contains(token), (value: boolean) => (element: E) => () => element.classList.toggle(token, value) ) /** * Set a style property of an element * * @since 0.8.0 * @param {string} prop - name of style property to be set * @param {StateLike} state - state bounded to the style property value */ const setStyle = (prop: string, state: StateLike = prop) => (ui: UI): UI => autoEffect( ui, state, `s-${prop}`, () => ui.target.style.getPropertyValue(prop), (value: string) => (element: E) => () => element.style.setProperty(prop, value), (element: E) => () => element.style.removeProperty(prop) ) /* === Exported Types === */ export { setText, setProperty, setAttribute, toggleAttribute, toggleClass, setStyle }