Form-associated custom elements

Form-associated custom elements (FAE) — это расширение возможностей стандартных веб-компонентов, позволяющее интегрировать пользовательские элементы с HTML-формами. В контексте Stencil это особенно актуально, так как фреймворк предоставляет мощный механизм для создания изолированных, реактивных компонентов с декларативной разметкой.

Основные концепции

Form-associated custom elements позволяют компоненту вести себя как стандартный <input>, <select> или <textarea>. Это означает, что пользовательский элемент может:

  • участвовать в отправке формы, предоставляя значение через formData;
  • реагировать на атрибуты формы, такие как disabled, required, name;
  • автоматически валидироваться при использовании встроенных HTML-валидаторов;
  • синхронизироваться с методами формы (form.reset(), form.submit()).

Для активации поведения FAE элемент должен быть создан с использованием свойства formAssociated: true в конструкторе компонента.

export class MyCustomInput extends HTMLElement {
  static formAssociated = true;
}

Интеграция с Stencil

Stencil позволяет создавать FAE через использование декоратора @Component и стандартного API веб-компонентов. Основные шаги:

  1. Создание компонента с FAE:
import { Component, Prop, h, Element, State } from '@stencil/core';

@Component({
  tag: 'my-custom-input',
  styleUrl: 'my-custom-input.css',
  shadow: true
})
export class MyCustomInput {
  @Element() el!: HTMLElement;
  @Prop() name!: string;
  @Prop({ mutable: true }) value: string = '';
  @Prop() required: boolean = false;

  private internals!: ElementInternals;

  componentWillLoad() {
    this.internals = (this.el as any).attachInternals();
  }

  handleInput(event: Event) {
    const input = event.target as HTMLInputElement;
    this.value = input.value;
    this.internals.setFormValue(this.value);
  }

  render() {
    return <input type="text" value={this.value} onIn put={(e) => this.handleInput(e)} />;
  }
}
  1. Использование ElementInternals

ElementInternals — ключевой API для FAE, предоставляющий методы:

  • setFormValue(value: any, state?: File | string | FormData): связывает значение компонента с формой;
  • setValidity(validityState: ValidityState, message?: string, anchor?: HTMLElement): управляет валидацией;
  • form: ссылка на родительскую форму;
  • labels: возвращает связанные <label>.
  1. Валидация и атрибуты формы

Компонент автоматически отслеживает атрибуты required, disabled, и может использовать встроенные методы проверки:

validate() {
  if (this.required && !this.value) {
    this.internals.setValidity({ valueMissing: true }, 'Поле обязательно для заполнения');
  } else {
    this.internals.setValidity({});
  }
}

Обработка событий формы

FAE поддерживают стандартные события формы, такие как submit, reset. При сбросе формы (form.reset()) можно сбросить внутреннее состояние компонента через метод internals.setFormValue('') или вручную обновить @Prop() value.

connectedCallback() {
  const form = this.internals.form;
  form?.addEventListener('reset', () => {
    this.value = '';
    this.internals.setFormValue('');
  });
}

Преимущества использования FAE в Stencil

  • Полная интеграция с существующими HTML-формами без необходимости писать дополнительный код для сериализации данных.
  • Возможность использовать стандартные механизмы браузера для валидации.
  • Сохранение реактивности компонента при взаимодействии с формой.
  • Поддержка <label> и атрибутов, как у нативных элементов.

Ограничения и особенности

  • Поддержка FAE реализована только в современных браузерах (Chrome, Edge, Safari). Firefox пока не поддерживает ElementInternals для кастомных элементов.
  • Необходимо использовать Shadow DOM осторожно, так как связка с <label> и формой может требовать явного управления internals.labels.
  • Атрибут form позволяет подключать компонент к форме вне иерархии DOM, но это требует явного указания formAssociated = true.

Практические рекомендации

  • Всегда инициализировать internals в componentWillLoad или connectedCallback, чтобы гарантировать доступ к API формы.
  • Синхронизировать @Prop() value с internals.setFormValue при каждом изменении состояния.
  • Реализовать валидацию через setValidity и при необходимости отображать пользовательские сообщения ошибки.
  • Поддерживать reset через событие формы, чтобы компонент корректно возвращался к исходному состоянию.

Использование form-associated custom elements в Stencil позволяет создавать полностью совместимые с HTML-формами кастомные элементы, сохраняющие все преимущества веб-компонентов, включая инкапсуляцию, повторное использование и реактивность состояния.