ElementInternals и ассоциированные с формой пользовательские элементы
В safari technology preview включена поддержка ElementInternals и form-assoiciated пользовательских элементов по умолчанию Пользовательские элементы — это функция, которая позволяет веб-разработчикам создавать повторно используемые компоненты, определяя свои собственные элементы HTML, не полагаясь на структуру JavaScript. ElementInternals
— это новое дополнение к API пользовательских элементов, которое позволяет разработчикам управлять внутренними состояниями пользовательских элементов, такими как роль ARIA по умолчанию или метка ARIA, а также участие пользовательских элементов в отправке и проверке форм.
ARIA для пользовательских элементов
Чтобы использовать ElementInternals
с пользовательским элементом, вызовите this.attachInternals()
в конструкторе пользовательского элемента точно так же, как мы вызывали бы attachShadow()
следующим образом:
class SomeButtonElement extends HTMLElement { #internals; #shadowRoot; constructor() { super(); this.#internals = this.attachInternals(); this.#internals.ariaRole = 'button'; this.#shadowRoot = this.attachShadow({mode: 'closed'}); this.#shadowRoot.innerHTML = ''; } } customElements.define('some-button', SomeButtonElement);
Здесь #internals
и #shadowRoot
являются закрытыми приватными свойствами. Приведенный выше код определяет простой настраиваемый элемент, роль ARIA которого по умолчанию — кнопка. Достижение того же эффекта без использования ElementInternals
потребовало добавления атрибута содержимого ARIA к самому пользовательскому элементу следующим образом:
class SomeButtonElement extends HTMLElement { #shadowRoot; constructor() { super(); this.#shadowRoot = this.attachShadow({mode: 'closed'}); this.#shadowRoot.innerHTML = ''; this.setAttribute('role', 'button'); } } customElements.define('some-button', SomeButtonElement);
Этот код проблематичен по нескольким причинам. Во-первых, удивительно, что элемент автоматически добавляет к себе атрибуты контента, поскольку ни один встроенный элемент этого не делает. Но что более важно, приведенный выше код не позволяет пользователям этого пользовательского элемента переопределять роль ARIA, поскольку конструктор переопределяет атрибут содержимого роли при обновлении:
<some-button role="switch"></some-button>
Используя свойство ElementInternals
ARIA role, как это сделано выше, этот пример работает без проблем. Аналогичным образом ElementInternals
позволяет указать значения по умолчанию для других функций ARIA, таких как ARIA label.
Участие в отправке формы
ElementInternals
также добавляет возможность для пользовательских элементов участвовать в отправке формы. Чтобы использовать эту функцию пользовательских элементов, мы должны объявить, что пользовательский элемент связан с формами следующим образом:
class SomeButtonElement extends HTMLElement { static formAssociated = true; static observedAttributes = ['value']; #internals; constructor() { super(); this.#internals = this.attachInternals(); this.#internals.ariaRole = 'button'; } attributeChangedCallback(name, oldValue, newValue) { this.#internals.setFormValue(newValue); } } customElements.define('some-button', SomeButtonElement);
С приведенным выше определением элемента some-button
, some-button
будет передавать значение атрибута value
, указанного в элементе, для атрибута name
, указанного в том же элементе. Например, если бы у нас была разметка типа some-key=some-value
.
Участие в валидации формы
Точно так же ElementInternals
добавляет возможность для пользовательских элементов участвовать в проверке формы. В следующем примере для некоторого текстового поля требуется как минимум два символа в элементе ввода внутри его теневого дерева. Когда имеется менее двух символов, он сообщает пользователю об ошибке проверки, используя собственный пользовательский интерфейс браузера, используя setValidity()
и reportValidity()
:
class SomeTextFieldElement extends HTMLElement { static formAssociated = true; #internals; #shadowRoot; constructor() { super(); this.#internals = this.attachInternals(); this.#shadowRoot = this.attachShadow({mode: 'closed', delegatesFocus: true}); this.#shadowRoot.innerHTML = '<input autofocus="">'; const input = this.#shadowRoot.firstChild; input.addEventListener('change', () => { this.#internals.setFormValue(input.value); this.updateValidity(input.value); }); } updateValidity(newValue) { if (newValue.length >= 2) { this.#internals.setValidity({ }); return; } this.#internals.setValidity({tooShort: true}, 'value is too short', this.#shadowRoot.firstChild); this.#internals.reportValidity(); } } customElements.define('some-text-field', SomeTextFieldElement);
С этой настройкой псевдокласс :invalid
будет автоматически применяться к элементу, когда количество введенных пользователем символов меньше 2.
Обратные вызовы пользовательских элементов, связанных с формой
Кроме того, настраиваемые элементы, связанные с формой, предоставляют следующий набор новых обратных вызовов реакции настраиваемого элемента:
formAssociatedCallback(form)
— вызывается, когда связанный элемент формы изменяется на form.ElementInternals.form
возвращает связанный элемент from.formResetCallback()
— вызывается при сбросе формы. (например, пользователь нажал кнопкуinput[type=reset]
). Пользовательский элемент должен очистить любое значение, установленное пользователем.formDisabledCallback(isDisabled)
— вызывается при изменении отключенного состояния элемента.formStateRestoreCallback (state, reason)
— вызывается, когда браузер пытается восстановить состояние элемента до состояния, в этом случае причина — «восстановление», или когда браузер пытается выполнить автозаполнение от имени пользователя, и в этом случае причина — «автозаполнение». В случае «восстановления» состояние — это строка, файл или объектFormData
, ранее установленный в качестве второго аргумента дляsetFormValue
.
Давайте посмотрим на formStateRestoreCallback
в качестве примера. В следующем примере мы сохраняем input.value
как состояние всякий раз, когда значение элемента ввода внутри теневого дерева изменяется (второй аргумент для setFormValue
). Когда пользователь переходит на другую страницу и возвращается на эту страницу, браузер может восстановить это состояние с помощью formStateRestoreCallback
. Обратите внимание, что WebKit в настоящее время имеет ограничение, заключающееся в том, что для состояния можно использовать только строку, а «автозаполнение» пока не поддерживается.
class SomeTextFieldElement extends HTMLElement { static formAssociated = true; #internals; #shadowRoot; constructor() { super(); this.#internals = this.attachInternals(); this.#shadowRoot = this.attachShadow({mode: 'closed', delegatesFocus: true}); this.#shadowRoot.innerHTML = '<input autofocus="">'; const input = this.#shadowRoot.querySelector('input'); input.addEventListener('change', () => { this.#internals.setFormValue(input.value, input.value); }); } formStateRestoreCallback(state, reason) { this.#shadowRoot.querySelector('input').value = state; } } customElements.define('some-text-field', SomeTextFieldElement);
Таким образом, ElementInternals
и настраиваемые элементы, связанные с формами, предоставляют новый способ написания повторно используемого веб-компонента, который участвует в отправке и проверке формы. ElementInternals
также предоставляет возможность указать значение по умолчанию для роли ARIA и других свойств ARIA для пользовательского элемента.
Оригинал статьи на английском — ElementInternals and Form-Associated Custom Elements
Последняя редакция 7 февраля, 2023 в 01:02