diff --git a/apiExamples/dynamichostname.html b/apiExamples/dynamichostname.html
new file mode 100644
index 0000000..bd10566
--- /dev/null
+++ b/apiExamples/dynamichostname.html
@@ -0,0 +1,53 @@
+
+ Expect this
+
+ Alaska Airlines
+
+ link to be redefined based on the hostname of the current page.
+
+
+ Expect this
+
+ Alaska Airlines
+
+ link to be maintain the hostname of the original link.
+
+
+ Expect this
+
+ Alaska Airlines
+
+ link to be maintain the hostname of the original link.
+
+
+ Expect this
+
+ Alaska Airlines
+
+ link to be redefined based on the hostname of the current page.
+
+
+ Expect this
+
+ Alaska Airlines
+
+ link to be redefined based on the hostname of the current page.
+
+
+ Expect this
+
+ Alaska Airlines
+
+ link to be remain relative.
+
diff --git a/docs/api.md b/docs/api.md
index dfc262c..2acf9bd 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -10,6 +10,7 @@
| `fluid` | `fluid` | | `Boolean` | | If true and `type="cta"`, the hyperlink will have a fluid-width UI. |
| `href` | `href` | | `String` | | Defines the URL of the linked page. |
| `ondark` | `ondark` | | `Boolean` | false | If true, the hyperlink will be styled for use on a dark background. |
+| `origin` | `origin` | | `String` | | Defines hostname for the origin of the URL. Options are `hostname` or `dynamic`. Default is `dynamic`. Using `hostname` will force the URL to be rewritten to use the hostname of the `href` attribute. Using `dynamic` will rewrite the URL to use the hostname of the current page. |
| `referrerpolicy` | `referrerpolicy` | | `Boolean` | | If true, sets `strict-origin-when-cross-origin` to control the referrer information sent with requests. |
| `rel` | `rel` | | `String` | | Defines the relationship between the current document and the linked document. |
| `relative` | `relative` | | `Boolean` | false | If true, the auto URL re-write feature will be disabled. |
diff --git a/docs/partials/api.md b/docs/partials/api.md
index c37ae1c..cf6c2a7 100644
--- a/docs/partials/api.md
+++ b/docs/partials/api.md
@@ -25,6 +25,23 @@
+## Dynamic hostname support
+
+The `auro-hyperlink` component defaults to modifying the origin of any provided URL to match the hostname of the current page unless explicitly configured. Using `origin="hostname"` will retain the hostname of the URL entered into the `href` attribute. Using `target="_blank"`, the URL on the anchor tag will maintain the hostname of the URL in the `href` attribute. Using `origin="dynamic"` in combination with `target="_blank"` will allow the targeted link to match the hostname of the current page.
+
+
+
+
+
+
+
+ See code
+
+
+
+
+
+
## External Links
Hyperlinks when used with the `target="_blank"` attribute are domain aware and return either an internal domain new-window icon versus an icon that communicates users will be taken to a new domain.
diff --git a/docs/partials/index.md b/docs/partials/index.md
index ced20c5..c99bd27 100644
--- a/docs/partials/index.md
+++ b/docs/partials/index.md
@@ -31,6 +31,23 @@ If the `href` attribute is not added, the hyperlink element will render back sim
+## Dynamic hostname support
+
+The `auro-hyperlink` component defaults to modifying the origin of any provided URL to match the hostname of the current page unless explicitly configured. Using `origin="hostname"` will retain the hostname of the URL entered into the `href` attribute. Using `target="_blank"`, the URL on the anchor tag will maintain the hostname of the URL in the `href` attribute. Using `origin="dynamic"` in combination with `target="_blank"` will allow the targeted link to match the hostname of the current page.
+
+
+
+
+
+
+
+ See code
+
+
+
+
+
+
## Navigation pattern example
Auro's hyperlink element can be used in many creative ways in combination with other elements for easy-to-manage UI solutions. This example uses the `auro-hyperlink` with the `auro-icon` elements with a little CSS to create a pretty popular nav style UI.
diff --git a/src/auro-hyperlink.js b/src/auro-hyperlink.js
index 95a670e..a12e5ba 100644
--- a/src/auro-hyperlink.js
+++ b/src/auro-hyperlink.js
@@ -36,6 +36,7 @@ import tokensCss from "./tokens-css.js";
* @attr {String} href - Defines the URL of the linked page.
* @attr {String} target - Defines where to open the linked document.
* @attr {String} type - Defines the type of hyperlink; accepts `nav` or `cta`.
+ * @attr {String} origin - Defines hostname for the origin of the URL. Options are `hostname` or `dynamic`. Default is `dynamic`. Using `hostname` will force the URL to be rewritten to use the hostname of the `href` attribute. Using `dynamic` will rewrite the URL to use the hostname of the current page.
* @csspart link - Allows styling to be applied to the `a` element.
* @csspart targetIcon - Allows styling to be applied to the icon that appears next to the hyperlink.
*/
diff --git a/src/component-base.mjs b/src/component-base.mjs
index 1a1c8af..4e31413 100644
--- a/src/component-base.mjs
+++ b/src/component-base.mjs
@@ -62,6 +62,7 @@ export default class ComponentBase extends LitElement {
static get properties() {
return {
href: { type: String },
+ origin: { type: String },
rel: { type: String },
role: { type: String },
target: { type: String },
@@ -147,19 +148,67 @@ export default class ComponentBase extends LitElement {
* @returns {string|undefined} The safe URL or `undefined`.
*/
safeUrl(href, relative) {
+
if (!href) {
return undefined;
}
- const url = new URL(href, 'https://www.alaskaair.com');
+ /**
+ * Determines the current hostname based on the environment (browser or server-side).
+ *
+ * @param {string} [href] - The URL to extract the hostname from when running in a server-side environment.
+ * @returns {string} The current hostname.
+ */
+ let currentHostname;
+ if (typeof window !== 'undefined') {
+ currentHostname = window.location.origin;
+ } else {
+ // Extract the hostname from the provided href for SSR
+ try {
+ currentHostname = new URL(href).origin;
+ } catch (e) {
+ // If href is not a valid URL, provide a default fallback
+ currentHostname = 'http://localhost:8000';
+ }
+ }
+
+ /**
+ * Processes the given href and currentHostname to generate a URL object.
+ *
+ * @param {string} href - The href attribute of the anchor element.
+ * @param {string} currentHostname - The current hostname to be used as a base.
+ * @returns {URL|undefined} The generated URL object, or undefined if an error occurs.
+ * @private
+ */
+ let url;
+
+ // let's refactor this so that it's using a enum versus a boolean value
+ try {
+ url = new URL(href, currentHostname);
+ if (this.origin !== `hostname` || this.origin === `dynamic`) {
+ const currentUrl = new URL(currentHostname);
+ url.hostname = currentUrl.hostname;
+ url.port = currentUrl.port; // Preserve the port number
+ }
+ if (this.target === `_blank` && this.origin === undefined) {
+ const hrefURL = new URL(href);
+ url.hostname = hrefURL.hostname;
+ url.port = ''; // Remove the port number
+ }
+ } catch (e) {
+ return undefined;
+ }
+
+ // return href if protocol is supported
switch (url.protocol) {
case 'tel:':
case 'sms:':
case 'mailto:':
return href;
- // Specifically want to render NO shadowDOM for the following refs
+ // Return undefined for unsupported protocols
+ // renders NO shadow DOM in the UI
case 'javascript:':
case 'data:':
case 'vbscript:':
@@ -215,22 +264,26 @@ export default class ComponentBase extends LitElement {
* @returns {HTMLElement|undefined} The HTML element containing the icon, or undefined if no icon is generated.
*/
targetIcon(target) {
-
/**
- * Checks if a URL's domain is from the 'alaskaair.com' domain or its subdomains.
+ * Checks if a given URL belongs to an internal domain.
+ *
* @param {string} url - The URL to check.
- * @returns {boolean} Returns true if the URL's domain is 'alaskaair.com' or one of its subdomains, otherwise false.
+ * @returns {boolean} - Returns true if the URL is an internal domain, otherwise false.
*/
- const isAlaskaAirDomain = (url) => {
+ const isInternalDomain = (url) => {
const urlObject = new URL(url);
- return urlObject.hostname.endsWith('.alaskaair.com');
+ const hostname = urlObject.hostname;
+ const isInternal = hostname.includes('.alaskaair.com') ||
+ hostname.includes('.hawaiianairlines.com') ||
+ hostname.includes('localhost');
+ return isInternal;
};
-
- // If target is '_blank' and the URL's domain is 'alaskaair.com' or one of its subdomains, return icon for new window
- if (target === '_blank' && isAlaskaAirDomain(this.safeUri)) {
+ // If target is '_blank' and the URL's domain is internal or one of its subdomains,
+ // return icon for new window
+ if (target === '_blank' && isInternalDomain(this.safeUri)) {
return this.generateIconHtml(newWindow.svg);
- } else if (target === '_blank' && !isAlaskaAirDomain(this.safeUri) && this.includesDomain) {
- // If target is '_blank' and the URL does not belong to 'alaskaair.com' or its subdomains but contains a domain, return icon for external link
+ } else if (target === '_blank' && !isInternalDomain(this.safeUri) && this.includesDomain) {
+ // If target is '_blank' and the URL is not internal, return icon for external link
return this.generateIconHtml(externalLink.svg);
}
diff --git a/test/auro-hyperlink.test.js b/test/auro-hyperlink.test.js
index 99fedbf..aae77e2 100644
--- a/test/auro-hyperlink.test.js
+++ b/test/auro-hyperlink.test.js
@@ -5,9 +5,10 @@ describe('auro-hyperlink', () => {
it('auro-hyperlink is accessible', async () => {
const el = await fixture(html`
- Alaska air
+ Alaska air
`);
+ // console.log(el.shadowRoot);
await expect(el).to.be.accessible();
});
@@ -37,6 +38,42 @@ describe('auro-hyperlink', () => {
expect(anchor).not.to.have.attribute('href', 'https://www.alaskaair.com/auro');
});
+ it('inserted URL is updated to be dynamic based on environment', async () => {
+ const el = await fixture(html`
+ It's Auro!
+ `);
+
+ const anchor = el.shadowRoot.querySelector('a');
+ expect(anchor).to.have.attribute('href').that.includes('localhost');
+ });
+
+ it('inserted URL is expecte to maintain initial URL hostname', async () => {
+ const el = await fixture(html`
+ It's Auro!
+ `);
+
+ const anchor = el.shadowRoot.querySelector('a');
+ expect(anchor).to.have.attribute('href').that.includes('alaskaair');
+ });
+
+ it('shadow DOM href is persisted from original href with target=_blank', async () => {
+ const el = await fixture(html`
+ It's Auro!
+ `);
+
+ const anchor = el.shadowRoot.querySelector('a');
+ expect(anchor).to.have.attribute('href').that.includes('alaskaair');
+ });
+
+ it('shadow DOM anchor is dynamic even with target=_blank applied', async () => {
+ const el = await fixture(html`
+ It's Auro!
+ `);
+
+ const anchor = el.shadowRoot.querySelector('a');
+ expect(anchor).to.have.attribute('href').that.includes('localhost');
+ });
+
// eval that JS in the href attr is ignored
it('auro-hyperlink is javascript', async () => {
const el = await fixture(html`
@@ -148,11 +185,15 @@ describe('safeUrl function', () => {
});
it('returns href when protocol is https:', async () => {
- const result = component.safeUrl('https://www.example.com', false);
+ component.origin = 'hostname'; // Set the origin value
+
+ const result = component.safeUrl('http://www.example.com', false);
expect(result).to.equal('https://www.example.com/');
});
it('returns href with https protocol when relative is false', async () => {
+ component.origin = 'hostname'; // Set the origin value
+
const result = component.safeUrl('http://www.example.com', false);
expect(result).to.equal('https://www.example.com/');
});