diff --git a/packages/calcite-components/src/components/link/link.e2e.ts b/packages/calcite-components/src/components/link/link.e2e.ts index 32b8252b38a..fd46149220a 100644 --- a/packages/calcite-components/src/components/link/link.e2e.ts +++ b/packages/calcite-components/src/components/link/link.e2e.ts @@ -65,19 +65,19 @@ describe("calcite-link", () => { expect(elementAsLink).not.toHaveAttribute("download"); }); - it("renders as a span with default props", async () => { + it("renders as a button with default props", async () => { const page = await newE2EPage(); await page.setContent(`Continue`); const element = await page.find("calcite-link"); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(element).not.toHaveAttribute("icon-flip-rtl"); expect(elementAsLink).toBeNull(); - expect(elementAsSpan).not.toBeNull(); + expect(elementAsButton).not.toBeNull(); expect(iconStart).toBeNull(); expect(iconEnd).toBeNull(); }); @@ -86,19 +86,19 @@ describe("calcite-link", () => { const page = await newE2EPage({ html: `Continue` }); const link = await page.find("calcite-link"); let elementAsLink: E2EElement; - let elementAsSpan: E2EElement; + let elementAsButton: E2EElement; - elementAsSpan = await page.find("calcite-link >>> span"); + elementAsButton = await page.find("calcite-link >>> button"); elementAsLink = await page.find("calcite-link >>> a"); - expect(elementAsSpan).not.toBeNull(); + expect(elementAsButton).not.toBeNull(); expect(elementAsLink).toBeNull(); link.setProperty("href", "/"); await page.waitForChanges(); - elementAsSpan = await page.find("calcite-link >>> span"); + elementAsButton = await page.find("calcite-link >>> button"); elementAsLink = await page.find("calcite-link >>> a"); - expect(elementAsSpan).toBeNull(); + expect(elementAsButton).toBeNull(); expect(elementAsLink).not.toBeNull(); }); @@ -106,28 +106,28 @@ describe("calcite-link", () => { const page = await newE2EPage(); await page.setContent(`Continue`); const element = await page.find("calcite-link"); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(element).not.toHaveAttribute("icon-flip-rtl"); expect(elementAsLink).not.toBeNull(); - expect(elementAsSpan).toBeNull(); + expect(elementAsButton).toBeNull(); expect(iconStart).toBeNull(); expect(iconEnd).toBeNull(); }); - it("renders as a span with requested props", async () => { + it("renders as a button with requested props", async () => { const page = await newE2EPage(); await page.setContent(`Continue`); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(elementAsLink).toBeNull(); - expect(elementAsSpan).not.toBeNull(); + expect(elementAsButton).not.toBeNull(); expect(iconStart).toBeNull(); expect(iconEnd).toBeNull(); }); @@ -135,13 +135,13 @@ describe("calcite-link", () => { it("renders as a link with requested props", async () => { const page = await newE2EPage(); await page.setContent(`Continue`); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(elementAsLink).not.toBeNull(); - expect(elementAsSpan).toBeNull(); + expect(elementAsButton).toBeNull(); expect(iconStart).toBeNull(); expect(iconEnd).toBeNull(); }); @@ -151,13 +151,13 @@ describe("calcite-link", () => { await page.setContent( `Continue`, ); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(elementAsLink).not.toBeNull(); - expect(elementAsSpan).toBeNull(); + expect(elementAsButton).toBeNull(); expect(elementAsLink).not.toHaveClass("my-custom-class"); expect(elementAsLink).toEqualAttribute("href", "google.com"); expect(elementAsLink).toEqualAttribute("rel", "noopener noreferrer"); @@ -169,12 +169,12 @@ describe("calcite-link", () => { it("renders with an icon-start", async () => { const page = await newE2EPage(); await page.setContent(`Continue`); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(elementAsLink).toBeNull(); - expect(elementAsSpan).not.toBeNull(); + expect(elementAsButton).not.toBeNull(); expect(iconStart).not.toBeNull(); expect(iconEnd).toBeNull(); }); @@ -182,12 +182,12 @@ describe("calcite-link", () => { it("renders with an icon-end", async () => { const page = await newE2EPage(); await page.setContent(`Continue`); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(elementAsLink).toBeNull(); - expect(elementAsSpan).not.toBeNull(); + expect(elementAsButton).not.toBeNull(); expect(iconStart).toBeNull(); expect(iconEnd).not.toBeNull(); }); @@ -195,12 +195,12 @@ describe("calcite-link", () => { it("renders with an icon-start and icon-end", async () => { const page = await newE2EPage(); await page.setContent(`Continue`); - const elementAsSpan = await page.find("calcite-link >>> span"); + const elementAsButton = await page.find("calcite-link >>> button"); const elementAsLink = await page.find("calcite-link >>> a"); const iconStart = await page.find("calcite-link >>> .calcite-link--icon.icon-start"); const iconEnd = await page.find("calcite-link >>> .calcite-link--icon.icon-end"); expect(elementAsLink).toBeNull(); - expect(elementAsSpan).not.toBeNull(); + expect(elementAsButton).not.toBeNull(); expect(iconStart).not.toBeNull(); expect(iconEnd).not.toBeNull(); }); @@ -231,6 +231,18 @@ describe("calcite-link", () => { expect(page.url()).toBe(targetUrl); }); + it("keyboard without href", async () => { + const element = await page.find("calcite-link"); + element.setProperty("href", undefined); + const clickEvent = await element.spyOnEvent("click"); + + await element.callMethod("setFocus"); + await page.waitForChanges(); + await page.keyboard.press("Enter"); + await page.waitForChanges(); + expect(clickEvent).toHaveReceivedEventTimes(1); + }); + it("mouse", async () => { // workaround for https://github.com/puppeteer/puppeteer/issues/2977 await page.$eval("calcite-link", (link: HTMLElement): void => { diff --git a/packages/calcite-components/src/components/link/link.scss b/packages/calcite-components/src/components/link/link.scss index 1506886179f..16093a0efdd 100644 --- a/packages/calcite-components/src/components/link/link.scss +++ b/packages/calcite-components/src/components/link/link.scss @@ -12,7 +12,7 @@ // link base :host a, -:host span { +:host button { @apply font-inherit relative flex @@ -34,7 +34,7 @@ // focus styles :host a, -:host span { +:host button { @apply focus-base; &:focus { @apply focus-outset; @@ -63,7 +63,7 @@ calcite-icon { } :host { - span, + button, a { @apply relative inline @@ -73,7 +73,8 @@ calcite-icon { color: var(--calcite-link-text-color, var(--calcite-color-text-link)); line-height: inherit; white-space: initial; - background-image: linear-gradient(currentColor, currentColor), + background-image: + linear-gradient(currentColor, currentColor), linear-gradient(var(--calcite-color-brand-underline), var(--calcite-color-brand-underline)); background-position-x: 0%, 100%; background-position-y: min(1.5em, 100%); diff --git a/packages/calcite-components/src/components/link/link.tsx b/packages/calcite-components/src/components/link/link.tsx index 5089b373e91..4e55e66d4d5 100644 --- a/packages/calcite-components/src/components/link/link.tsx +++ b/packages/calcite-components/src/components/link/link.tsx @@ -22,7 +22,7 @@ declare global { /** * Any attributes placed on component will propagate to the rendered child * - * Passing a 'href' will render an anchor link, instead of a span. Role will be set to link, or link, depending on this. + * Passing a 'href' will render an anchor link, instead of a button. * * It is the consumers responsibility to add aria information, rel, target, for links, and any link attributes for form submission * @@ -38,7 +38,7 @@ export class Link extends LitElement implements InteractiveComponent { // #region Private Properties /** the rendered child element */ - private childEl: HTMLAnchorElement | HTMLSpanElement; + private childEl: HTMLAnchorElement | HTMLButtonElement; // #endregion @@ -131,7 +131,7 @@ export class Link extends LitElement implements InteractiveComponent { override render(): JsxNode { const { download, el } = this; const dir = getElementDir(el); - const childElType = this.href ? "a" : "span"; + const childElType = this.href ? "a" : "button"; const iconStartEl = ( before. In Stencil, props overwrite user-provided props. If you don't wish to overwrite user-values, replace "=" here with "??=" */ this.el.role = "presentation"; @@ -178,7 +177,6 @@ export class Link extends LitElement implements InteractiveComponent { onClick={this.childElClickHandler} ref={this.storeTagRef} rel={childElType === "a" && this.rel} - role={role} tabIndex={tabIndex} target={childElType === "a" && this.target} >