Кастомные элементы форм

Atomico — это современная библиотека для разработки веб-компонентов на JavaScript, которая использует реактивный подход, схожий с React, но ориентированная на нативные Custom Elements. Она идеально подходит для создания кастомных формовых элементов с полной поддержкой реактивности, состояния и событий.

Создание базового компонента формы

Каждый компонент в Atomico создается с помощью функции component. Она принимает функцию рендера и возвращает объект, который можно использовать как стандартный Custom Element. Для формы важно правильно определить свойства (props) и события (events), чтобы компонент корректно взаимодействовал с внешним кодом.

import { component, html, useState } from "atomico";

function CustomInput({ value, placeholder, onChange }) {
    const [inputValue, setInputValue] = useState(value || "");

    function handleInput(event) {
        setInputValue(event.target.value);
        onChange?.(event.target.value);
    }

    return html`
        <input
            type="text"
            .value=${inputValue}
            placeholder=${placeholder || ""}
            @input=${handleInput}
        />
    `;
}

CustomInput.props = {
    value: String,
    placeholder: String,
    onChange: Function
};

export const customInput = component(CustomInput);

Ключевые моменты:

  • Использование useState для управления локальным состоянием.
  • Передача функции onChange через props для внешней синхронизации.
  • Привязка событий через синтаксис @event.

Обработка сложных форм

Для сложных форм, включающих несколько полей и проверку данных, Atomico позволяет использовать реактивные объекты и хуки. Например, можно создать форму регистрации с валидаторами:

function RegistrationForm() {
    const [formData, setFormData] = useState({ username: "", email: "" });
    const [errors, setErrors] = useState({});

    function handleChange(field) {
        return (value) => {
            setFormData({ ...formData, [field]: value });
        };
    }

    function validate() {
        const newErrors = {};
        if (!formData.username) newErrors.username = "Username required";
        if (!formData.email.includes("@")) newErrors.email = "Invalid email";
        setErrors(newErrors);
        return Object.keys(newErrors).length === 0;
    }

    function handleSubmit(event) {
        event.preventDefault();
        if (validate()) {
            console.log("Submitting", formData);
        }
    }

    return html`
        <form @submit=${handleSubmit}>
            <label>
                Username:
                <custom-input
                    .value=${formData.username}
                    placeholder="Enter username"
                    .onCha nge=${handleChange("username")}
                ></custom-input>
                ${errors.username && html`<span class="error">${errors.username}</span>`}
            </label>
            <label>
                Email:
                <custom-input
                    .value=${formData.email}
                    placeholder="Enter email"
                    .onCha nge=${handleChange("email")}
                ></custom-input>
                ${errors.email && html`<span class="error">${errors.email}</span>`}
            </label>
            <button type="submit">Register</button>
        </form>
    `;
}

export const registrationForm = component(RegistrationForm);

Особенности:

  • Использование объекта состояния formData для хранения значений всех полей.
  • Валидаторы интегрированы непосредственно в компонент через функцию validate.
  • Ошибки отображаются динамически без перезагрузки страницы.

Поддержка событий и взаимодействие с родительскими компонентами

Atomico позволяет легко передавать события из кастомного элемента наружу. Для этого можно использовать dispatchEvent внутри компонента:

function CustomSelect({ options, value }) {
    function handleChange(event) {
        const selectedValue = event.target.value;
        dispatchEvent(new CustomEvent("change", { detail: selectedValue }));
    }

    return html`
        <select .value=${value} @change=${handleChange}>
            ${options.map(option => html`<option value=${option.value}>${option.label}</option>`)}
        </select>
    `;
}

CustomSelect.props = {
    options: Array,
    value: String
};

export const customSelect = component(CustomSelect);
  • dispatchEvent позволяет родительским компонентам подписываться на событие change.
  • Детали события передаются через CustomEvent.detail.

Композиция формовых элементов

Atomico поддерживает вложенные кастомные элементы и композицию без потери реактивности. Можно строить модульные формы, где каждый элемент управляет собственным состоянием и взаимодействует через props и события.

function UserForm() {
    const [user, setUser] = useState({ role: "", name: "" });

    return html`
        <form>
            <custom-input
                .value=${user.name}
                placeholder="Name"
                .onCha nge=${val => setUser({ ...user, name: val })}
            ></custom-input>

            <custom-select
                .options=${[
                    { value: "admin", label: "Admin" },
                    { value: "user", label: "User" }
                ]}
                .value=${user.role}
                @change=${e => setUser({ ...user, role: e.detail })}
            ></custom-select>
        </form>
    `;
}

export const userForm = component(UserForm);

Стиль и доступность

Atomico позволяет интегрировать CSS напрямую через шаблон html или использовать Shadow DOM для изоляции стилей. Для формовых элементов важно соблюдать доступность:

  • Использование тегов <label> для привязки к полям.
  • Поддержка aria-* атрибутов.
  • Видимые ошибки и подсказки.
return html`
    <label for="username">Username</label>
    <input id="username" type="text" aria-describedby="username-error"/>
    ${errors.username && html`<span id="username-error" role="alert">${errors.username}</span>`}
`;

Итоговая архитектура

Кастомные элементы форм в Atomico строятся на следующих принципах:

  • Реактивность через хуки (useState, useEffect).
  • Props и события для связи с родителями.
  • Композиция компонентов для модульной структуры.
  • Поддержка валидации и состояния ошибок.
  • Изоляция стилей через Shadow DOM и соблюдение доступности.

Это позволяет создавать полностью настраиваемые, расширяемые и поддерживаемые формы, которые интегрируются как в небольшие виджеты, так и в крупные веб-приложения.