UNPKG

vira

Version:

A simple and highly versatile design system using element-vir.

131 lines (127 loc) 4.34 kB
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> `; } }, });