@zeix/ui-element
Version:
UIElement - a HTML-first library for reactive Web Components
122 lines (112 loc) • 2.73 kB
text/typescript
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>
}
}