From 43cf404e0e89481c9a93ab85b043a58e289819a7 Mon Sep 17 00:00:00 2001
From: Chris Holt <13071055+chrisdholt@users.noreply.github.com>
Date: Fri, 14 Jun 2024 18:04:53 -0700
Subject: [PATCH] fix anchor meta and ctrl key navigation
---
.../src/anchor-button/anchor-button.spec.ts | 65 +++++++++++++++++++
.../anchor-button/anchor-button.template.ts | 4 +-
.../src/anchor-button/anchor-button.ts | 35 ++++++++--
.../web-components/src/link/link.template.ts | 4 +-
4 files changed, 97 insertions(+), 11 deletions(-)
diff --git a/packages/web-components/src/anchor-button/anchor-button.spec.ts b/packages/web-components/src/anchor-button/anchor-button.spec.ts
index 4d2b2441c70bd6..50356228a7aa16 100644
--- a/packages/web-components/src/anchor-button/anchor-button.spec.ts
+++ b/packages/web-components/src/anchor-button/anchor-button.spec.ts
@@ -77,4 +77,69 @@ test.describe('Anchor Button', () => {
await expect(proxy).toHaveAttribute(`${attribute}`, `${value}`);
});
}
+
+ test('should navigate to the provided url when clicked', async ({ page }) => {
+ const element = page.locator('fluent-anchor-button');
+ const expectedUrl = '#foo';
+
+ await page.setContent(/* html */ `
+
+ `);
+
+ await element.click();
+
+ expect(page.url()).toContain(expectedUrl);
+ });
+
+ test('should navigate to the provided url when clicked while pressing the `Control` key on Windows or Meta on Mac', async ({
+ page,
+ context,
+ }) => {
+ const element = page.locator('fluent-anchor-button');
+ const expectedUrl = 'https://www.microsoft.com';
+
+ await page.setContent(/* html */ `
+
+ `);
+
+ const [newPage] = await Promise.all([
+ context.waitForEvent('page'),
+ element.click({ modifiers: ['ControlOrMeta'] }),
+ ]);
+
+ expect(newPage.url()).toContain(expectedUrl);
+ });
+
+ test('should navigate to the provided url when `Enter` is pressed via keyboard', async ({ page }) => {
+ const element = page.locator('fluent-anchor-button');
+ const expectedUrl = '#foo';
+
+ await page.setContent(/* html */ `
+
+ `);
+
+ await element.focus();
+
+ await element.press('Enter');
+
+ expect(page.url()).toContain(expectedUrl);
+ });
+
+ test('should navigate to the provided url when `ctrl` and `Enter` are pressed via keyboard', async ({
+ page,
+ context,
+ }) => {
+ const element = page.locator('fluent-anchor-button');
+ const expectedUrl = 'https://www.microsoft.com';
+
+ await page.setContent(/* html */ `
+
+ `);
+
+ await element.focus();
+
+ const [newPage] = await Promise.all([context.waitForEvent('page'), element.press('ControlOrMeta+Enter')]);
+
+ expect(newPage.url()).toContain(expectedUrl);
+ });
});
diff --git a/packages/web-components/src/anchor-button/anchor-button.template.ts b/packages/web-components/src/anchor-button/anchor-button.template.ts
index 7445870c4c94b7..46770804823883 100644
--- a/packages/web-components/src/anchor-button/anchor-button.template.ts
+++ b/packages/web-components/src/anchor-button/anchor-button.template.ts
@@ -10,8 +10,8 @@ export function anchorTemplate(options: AnchorOptions =
return html`
x.clickHandler()}"
- @keypress="${(x, c) => x.keypressHandler(c.event as KeyboardEvent)}"
+ @click="${(x, c) => x.clickHandler(c.event as PointerEvent)}"
+ @keydown="${(x, c) => x.keydownHandler(c.event as KeyboardEvent)}"
>
${startSlotTemplate(options)}
diff --git a/packages/web-components/src/anchor-button/anchor-button.ts b/packages/web-components/src/anchor-button/anchor-button.ts
index 88b7634248a79d..11be9aa058a363 100644
--- a/packages/web-components/src/anchor-button/anchor-button.ts
+++ b/packages/web-components/src/anchor-button/anchor-button.ts
@@ -31,6 +31,12 @@ export type AnchorOptions = StartEndOptions;
* @public
*/
export class BaseAnchor extends FASTElement {
+ /**
+ * Holds a reference to the platform to manage ctrl+click on Windows and cmd+click on Mac
+ * @internal
+ */
+ private readonly isMac = navigator.userAgent.includes('Mac');
+
/**
* The internal {@link https://developer.mozilla.org/docs/Web/API/ElementInternals | `ElementInternals`} instance for the component.
*
@@ -177,28 +183,43 @@ export class BaseAnchor extends FASTElement {
* @param e - The event object
* @internal
*/
- public clickHandler(): boolean {
- this.internalProxyAnchor.click();
+ public clickHandler(e: PointerEvent): boolean {
+ if (this.href) {
+ const newTab = !this.isMac ? e.ctrlKey : e.metaKey;
+ this.handleNavigation(newTab);
+ }
return true;
}
/**
- * Handles keypress events for the anchor.
+ * Handles keydown events for the anchor.
*
* @param e - the keyboard event
* @returns - the return value of the click handler
* @public
*/
- public keypressHandler(e: KeyboardEvent): boolean | void {
- if (e.key === keyEnter) {
- this.internalProxyAnchor.click();
- return;
+ public keydownHandler(e: KeyboardEvent): boolean | void {
+ if (this.href) {
+ if (e.key === keyEnter) {
+ const newTab = !this.isMac ? e.ctrlKey : e.metaKey || e.ctrlKey;
+ this.handleNavigation(newTab);
+ return;
+ }
}
return true;
}
+ /**
+ * Handles navigation based on input
+ * If the metaKey is pressed, opens the href in a new window, if false, uses the click on the proxy
+ * @internal
+ */
+ private handleNavigation(newTab: boolean): void {
+ newTab ? window.open(this.href, '_blank') : this.internalProxyAnchor.click();
+ }
+
/**
* A method for updating proxy attributes when attributes have changed
* @internal
diff --git a/packages/web-components/src/link/link.template.ts b/packages/web-components/src/link/link.template.ts
index abef8ca653811d..b657f0b6575206 100644
--- a/packages/web-components/src/link/link.template.ts
+++ b/packages/web-components/src/link/link.template.ts
@@ -9,8 +9,8 @@ export function anchorTemplate(): ViewTemplate {
return html`
x.clickHandler()}"
- @keypress="${(x, c) => x.keypressHandler(c.event as KeyboardEvent)}"
+ @click="${(x, c) => x.clickHandler(c.event as PointerEvent)}"
+ @keydown="${(x, c) => x.keydownHandler(c.event as KeyboardEvent)}"
>