strokoff

ElementInternals и ассоциированные с формой пользовательские элементы

webkit

В 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.

Обратные вызовы пользовательских элементов, связанных с формой

Кроме того, настраиваемые элементы, связанные с формой, предоставляют следующий набор новых обратных вызовов реакции настраиваемого элемента:

Давайте посмотрим на 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