Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/free-candles-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@spectrum-web-components/link': patch
---

** Fixed ** : Safari keyboard navigation compatibility for sp-link component by implementing an approach with shadow DOM delegatesFocus and explicit tabindex.
20 changes: 18 additions & 2 deletions packages/link/src/Link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
* governing permissions and limitations under the License.
*/

import { CSSResultArray, TemplateResult } from '@spectrum-web-components/base';
import {
CSSResultArray,
LitElement,
TemplateResult,
} from '@spectrum-web-components/base';
import {
property,
query,
Expand All @@ -22,6 +26,7 @@ import linkStyles from './link.css.js';

/**
* @element sp-link
*
*/
export class Link extends LikeAnchor(Focusable) {
public static override get styles(): CSSResultArray {
Expand All @@ -48,6 +53,17 @@ export class Link extends LikeAnchor(Focusable) {
}

protected override render(): TemplateResult {
return this.renderAnchor({ id: 'anchor' });
return this.renderAnchor({
id: 'anchor',
tabindex: 0,
});
}
/**
* Static property to configure shadow root options
* This enables delegatesFocus for Safari compatibility
*/
static override shadowRootOptions = {
...LitElement.shadowRootOptions,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We usually use spread ...SpectrumElement.shadowRootOptions, should we also do it here?

delegatesFocus: true,
};
}
31 changes: 31 additions & 0 deletions packages/link/test/link.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Link } from '@spectrum-web-components/link';
import { elementUpdated, expect, fixture, html } from '@open-wc/testing';
import { testForLitDevWarnings } from '../../../test/testing-helpers.js';
import { spy } from 'sinon';
import { isWebKit } from '@spectrum-web-components/shared';

describe('Link', () => {
testForLitDevWarnings(
Expand Down Expand Up @@ -76,4 +77,34 @@ describe('Link', () => {
el.click();
expect(clickSpy.callCount).to.equal(0);
});

it('has proper Safari keyboard navigation support when running in WebKit', async () => {
const el = await fixture<Link>(html`
<sp-link href="test_url">WebKit Test Link</sp-link>
`);

await elementUpdated(el);

// Always verify basic configuration
expect(el.shadowRoot).to.not.be.null;
expect(el.shadowRoot?.delegatesFocus).to.be.true;

const anchor = el.shadowRoot?.querySelector(
'#anchor'
) as HTMLAnchorElement;
expect(anchor.getAttribute('tabindex')).to.eq('0');

// WebKit-specific enhanced tests
if (isWebKit()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can remove this check and run the assertions for all browsers... any reason we need to scope it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes there is an upstream issue around this. It is a known limitation of iOS/Safari with custom elements and shadow DOM. See WebKit bug reports and related issues.

// Verify that the anchor element is properly focusable in Safari
expect(anchor.tabIndex).to.be.greaterThan(-1);

// Verify that the link maintains proper ARIA attributes in Safari
expect(anchor.hasAttribute('href')).to.be.true;
expect(anchor.getAttribute('role')).to.not.equal('button');
}

// Common verification for all browsers
await expect(el).to.be.accessible();
});
});
Loading