vira
Version:
A simple and highly versatile design system using element-vir.
131 lines (127 loc) • 4.34 kB
JavaScript
import { assertWrap } from '@augment-vir/assert';
import { attributes, css, html, ifDefined, listen, } from 'element-vir';
import { listenTo } from 'typed-event-target';
import { viraTheme } from '../styles/vira-color-theme.js';
import { defineViraElement } from '../util/define-vira-element.js';
/**
* A hyperlink wrapper element that can be configured to emit route change events rather than just
* being a raw link.
*
* @category Link
* @category Elements
* @see https://electrovir.github.io/vira/book/elements/vira-link
*/
export const ViraLink = defineViraElement()({
tagName: 'vira-link',
state() {
return {
/** Removes event listeners registered during init. */
cleanupListeners: undefined,
};
},
hostClasses: {
'vira-link-link-styles': ({ inputs }) => !inputs.disableLinkStyles,
},
styles: ({ hostClasses }) => css `
:host {
display: inline;
text-decoration: underline;
}
a,
a:visited,
a:active,
a:link,
a:hover {
color: inherit;
text-decoration: inherit;
white-space: inherit;
}
${hostClasses['vira-link-link-styles'].selector} {
&:hover a,
& a:hover {
color: ${viraTheme.colors['vira-blue-foreground-non-body'].foreground.value};
}
&:active a,
& a:active {
color: ${viraTheme.colors['vira-blue-foreground-body'].foreground.value};
}
}
`,
init({ state, updateState, host }) {
state.cleanupListeners?.();
let propagating = false;
const listenerRemovers = [
listenTo(host, 'click', (event) => {
if (propagating) {
return;
}
const anchorElement = assertWrap.instanceOf(host.shadowRoot.querySelector('a'), HTMLAnchorElement);
if (event.composedPath().includes(anchorElement)) {
return;
}
event.preventDefault();
event.stopPropagation();
propagating = true;
anchorElement.dispatchEvent(new MouseEvent(event.type, event));
propagating = false;
}),
];
updateState({
cleanupListeners: () => {
listenerRemovers.forEach((remover) => remover());
},
});
},
cleanup({ state, updateState }) {
state.cleanupListeners?.();
updateState({
cleanupListeners: undefined,
});
},
render({ inputs }) {
function clickCallback(event) {
if (!inputs.route) {
return;
}
const routed = inputs.route.router.setRouteOnDirectNavigation(inputs.route.route, event);
if (routed && inputs.route.scrollToTop) {
window.scrollTo({
left: 0,
top: 0,
behavior: 'instant',
});
}
}
if (inputs.link?.newTab) {
/** Noopener and noreferrer are needed for security reasons, do not remove! */
return html `
<a
href=${inputs.link.url}
target="_blank"
rel="noopener noreferrer"
${attributes(inputs.attributePassthrough?.a)}
style=${ifDefined(inputs.stylePassthrough?.a)}
>
<slot></slot>
</a>
`;
}
else {
const linkUrl = inputs.link
? inputs.link.url
: inputs.route.router.createRouteUrl(inputs.route.route).url;
/** Noopener and noreferrer are needed for security reasons, do not remove! */
return html `
<a
href=${linkUrl}
rel="noopener noreferrer"
${attributes(inputs.attributePassthrough?.a)}
style=${ifDefined(inputs.stylePassthrough?.a)}
${listen('click', clickCallback)}
>
<slot></slot>
</a>
`;
}
},
});