From f94de5d30275273fab3667862caf35c47fa21c4e Mon Sep 17 00:00:00 2001 From: Elizabeth Mitchell Date: Thu, 26 Oct 2023 13:36:41 -0700 Subject: [PATCH] chore(aria): add separate setup function for polyfill and focusability PiperOrigin-RevId: 576973090 --- internal/aria/aria.ts | 39 ++++++++++++++-------- internal/aria/aria_test.ts | 67 ++++++++------------------------------ 2 files changed, 40 insertions(+), 66 deletions(-) diff --git a/internal/aria/aria.ts b/internal/aria/aria.ts index 64828cb69..8563aeb23 100644 --- a/internal/aria/aria.ts +++ b/internal/aria/aria.ts @@ -294,6 +294,27 @@ export type ARIARole = | 'doc-tip' | 'doc-toc'; +/** + * This function will polyfill `ARIAMixin` properties for Firefox. + * + * @param ctor The `ReactiveElement` constructor to set up. + */ +export function polyfillARIAMixin(ctor: typeof ReactiveElement) { + if (isServer || 'role' in Element.prototype) { + return; + } + + // Polyfill reflective aria properties for Firefox + for (const ariaProperty of ARIA_PROPERTIES) { + ctor.createProperty(ariaProperty, { + attribute: ariaPropertyToAttribute(ariaProperty), + reflect: true, + }); + } + + ctor.createProperty('role', {reflect: true}); +} + /** * Enables a host custom element to be the target for aria roles and attributes. * Components should set the `elementInternals.role` property. @@ -307,6 +328,8 @@ export type ARIARole = * * @param ctor The `ReactiveElement` constructor to set up. * @param options Options to configure the element's host aria. + * @deprecated use `mixinFocusable()` and `polyfillARIAMixin()` + * TODO(b/307785469): remove after updating components to use mixinFocusable */ export function setupHostAria( ctor: typeof ReactiveElement, @@ -326,23 +349,13 @@ export function setupHostAria( }); } - if (isServer || 'role' in Element.prototype) { - return; - } - - // Polyfill reflective aria properties for Firefox - for (const ariaProperty of ARIA_PROPERTIES) { - ctor.createProperty(ariaProperty, { - attribute: ariaPropertyToAttribute(ariaProperty), - reflect: true, - }); - } - - ctor.createProperty('role', {reflect: true}); + polyfillARIAMixin(ctor); } /** * Options for setting up a host element as an aria target. + * @deprecated use `mixinFocusable()` and `polyfillARIAMixin()` + * TODO(b/307785469): remove after updating components to use mixinFocusable */ export interface SetupHostAriaOptions { /** diff --git a/internal/aria/aria_test.ts b/internal/aria/aria_test.ts index 6a40249c4..efa2dafde 100644 --- a/internal/aria/aria_test.ts +++ b/internal/aria/aria_test.ts @@ -13,8 +13,8 @@ import { ARIAProperty, ariaPropertyToAttribute, isAriaAttribute, + polyfillARIAMixin, polyfillElementInternalsAria, - setupHostAria, } from './aria.js'; describe('aria', () => { @@ -52,11 +52,11 @@ describe('aria', () => { }); }); - describe('setupHostAria()', () => { + describe('polyfillARIAMixin()', () => { @customElement('test-setup-aria-host') class TestElement extends LitElement { static { - setupHostAria(TestElement); + polyfillARIAMixin(TestElement); } override render() { @@ -64,67 +64,28 @@ describe('aria', () => { } } - it('should not hydrate tabindex attribute on creation', () => { - const element = new TestElement(); - expect(element.hasAttribute('tabindex')) - .withContext('has tabindex attribute') - .toBeFalse(); - }); - - it('should set tabindex="0" on element once connected', () => { + it('should reflect ARIAMixin properties to attributes', async () => { const element = new TestElement(); document.body.appendChild(element); - expect(element.getAttribute('tabindex')) - .withContext('tabindex attribute value') - .toEqual('0'); + element.role = 'button'; + element.ariaLabel = 'Foo'; + await element.updateComplete; + expect(element.getAttribute('role')) + .withContext('role attribute value') + .toEqual('button'); + expect(element.getAttribute('aria-label')) + .withContext('aria-label attribute value') + .toEqual('Foo'); element.remove(); }); - - it('should not set tabindex on connected if one already exists', () => { - const element = new TestElement(); - element.tabIndex = -1; - document.body.appendChild(element); - expect(element.getAttribute('tabindex')) - .withContext('tabindex attribute value') - .toEqual('-1'); - - element.remove(); - }); - - it('should not change tabindex if disconnected and reconnected', () => { - const element = new TestElement(); - document.body.appendChild(element); - element.tabIndex = -1; - element.remove(); - document.body.appendChild(element); - expect(element.getAttribute('tabindex')) - .withContext('tabindex attribute value') - .toEqual('-1'); - }); - - if (!('role' in Element.prototype)) { - describe('polyfill', () => { - it('should hydrate aria attributes when ARIAMixin is not supported', async () => { - const element = new TestElement(); - document.body.appendChild(element); - element.role = 'button'; - await element.updateComplete; - expect(element.getAttribute('role')) - .withContext('role attribute value') - .toEqual('button'); - - element.remove(); - }); - }); - } }); describe('polyfillElementInternalsAria()', () => { @customElement('test-polyfill-element-internals-aria') class TestElement extends LitElement { static { - setupHostAria(TestElement); + polyfillARIAMixin(TestElement); } internals = polyfillElementInternalsAria(this, this.attachInternals());