UNPKG

@zeix/ui-element

Version:

UIElement - a HTML-first library for reactive Web Components

122 lines (112 loc) 2.73 kB
import { type Component, UNSET, // batch, component, computed, fromEvents, on, setAttribute, setProperty, setText, show, } from '../../..' import { createClearFunction } from '../../functions/shared/clear-input' export type FormTextboxProps = { value: string length: number error: string description: string clear(): void } export default component<FormTextboxProps>( 'form-textbox', { value: fromEvents< string, HTMLInputElement | HTMLTextAreaElement, HTMLElement & { error: string } >('', 'input, textarea', { change: ({ host, target }) => { target.checkValidity() host.error = target.validationMessage return target.value }, }), length: fromEvents<number, HTMLInputElement | HTMLTextAreaElement>( 0, 'input, textarea', { input: ({ target }) => target.value.length, }, ), error: '', description: '', clear() {}, }, (el, { first }) => { const input = el.querySelector<HTMLInputElement | HTMLTextAreaElement>( 'input, textarea', ) if (!input) throw new Error('No Input or textarea element found') // Add clear method to component using shared functionality el.clear = createClearFunction(input) // If there's a description with data-remaining attribute we set a computed signal to update the description text const description = el.querySelector<HTMLElement>('.description') if (description?.dataset.remaining && input.maxLength) { el.setSignal( 'description', computed(() => description.dataset.remaining!.replace( '${n}', String(input.maxLength - el.length), ), ), ) } const errorId = el.querySelector('.error')?.id const descriptionId = description?.id return [ setAttribute('value'), // Effects on input / textarea first( 'input, textarea', setProperty('ariaInvalid', () => String(!!el.error)), setAttribute('aria-errormessage', () => el.error && errorId ? errorId : UNSET, ), setAttribute('aria-describedby', () => el.description && descriptionId ? descriptionId : UNSET, ), /* on({ input: () => { el.length = input.value.length }, change: () => { input.checkValidity() batch(() => { el.value = input.value el.error = input.validationMessage ?? '' }) }, }), */ ), // Effects and event listeners on clear button first( '.clear', show(() => !!el.length), on('click', () => { el.clear() }), ), // Effects on error and description first('.error', setText('error')), first('.description', setText('description')), ] }, ) declare global { interface HTMLElementTagNameMap { 'form-textbox': Component<FormTextboxProps> } }