@riovir/wc-fontawesome
Version:
Web components for Font Awesome
162 lines (134 loc) • 5.83 kB
JavaScript
import { applyStyleWith } from './apply-style-with.js';
import { definePropsOn, BooleanProp, StringProp, ObjectOrStringProp } from './core.js';
import { resolve, iconToHtml, parseTransform } from './svg-core.js';
import { setHostAttribute, updateElementClass } from './utils.js';
const BLANK_ICON = { icon: [] };
const applyStyle = applyStyleWith({ css: /* css */`
:host {
box-sizing: border-box;
display: inline-block;
line-height: 1; }
:host([pull="left"]) {
float: left;
margin-right: .3em; }
:host([pull="right"]) {
float: right;
margin-left: .3em; }
` });
export const template = document.createElement('template');
template.innerHTML = toHtmlFallback(BLANK_ICON);
function toHtmlFallback({ icon = [] } = {}) {
const [width = 1, height = 1, /**/, /**/, path = ''] = icon;
const content = Array.isArray(path) ?
`<g class="fa-group">
<path class="fa-secondary" fill="currentColor" d="${path[1]}" />
<path class="fa-primary" fill="currentColor" d="${path[0]}" />
</g>` :
`<path fill="currentColor" d="${path}" />`;
return [/* html */`<svg
class="svg-inline--fa"
aria-hidden="true"
role="img"
focusable="false"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${width} ${height}"
>
${content}
</svg>`,
];
}
const Internal = {
SVG: Symbol('svg'),
PATH_PRIMARY: Symbol('path-primary'),
PATH_SECONDARY: Symbol('path-secondary'),
NEEDS_REDRAW: Symbol('needs-complete-redraw-of-structure'),
};
const updateSvgClass = updateElementClass(Internal.SVG);
const prefixSvgClass = prefix => updateSvgClass(value => prefix.concat(value));
export const Props = {
_init: { connect: initAriaAttributes },
icon: ObjectOrStringProp({ attribute: 'icon', observe: updateIcon }),
resolvedIcon: { get: ({ icon, _resolve }) => _resolve(icon) },
isDuotone: { get: isDuotone },
transform: ObjectOrStringProp({ attribute: 'transform', observe: updateIcon }),
parsedTransform: { get: resolveTransform },
size: StringProp({ attribute: 'size', observe: prefixSvgClass('fa-') }),
rotation: StringProp({ attribute: 'rotation', observe: prefixSvgClass('fa-rotate-') }),
flip: StringProp({ attribute: 'flip', observe: prefixSvgClass('fa-flip-') }),
pull: StringProp({ attribute: 'pull', observe: setHostAttribute('pull') }),
mask: ObjectOrStringProp({ attribute: 'mask', observe: updateIcon }),
resolvedMask: { get: ({ mask, _resolve }) => _resolve(mask) },
fixedWidth: BooleanProp({ attribute: 'fixed-width', observe: updateSvgClass('fa-fw') }),
spin: BooleanProp({ attribute: 'spin', observe: updateSvgClass('fa-spin') }),
pulse: BooleanProp({ attribute: 'pulse', observe: updateSvgClass('fa-pulse') }),
inverse: BooleanProp({ attribute: 'inverse', observe: updateSvgClass('fa-inverse') }),
swapOpacity: BooleanProp({ attribute: 'swap-opacity', observe: updateSvgClass('fa-swap-opacity') }),
};
export class FontAwesomeIcon extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({ mode: 'open' });
shadowRoot.appendChild(template.content.cloneNode(true));
applyStyle(this);
this[Internal.SVG] = shadowRoot.querySelector('svg');
this[Internal.PATH_PRIMARY] = shadowRoot.querySelector('path');
this[Internal.NEEDS_REDRAW] = host => host.isDuotone || host.transform || host.mask;
this._toHtml = iconToHtml;
this._resolve = resolve;
this._parseTransform = parseTransform;
this.spin = false;
}
}
definePropsOn(FontAwesomeIcon, Props);
function initAriaAttributes(host) {
const hasAccessibleName = host.getAttribute('aria-label') || host.getAttribute('title') || host.getAttribute('aria-labelledby');
if (host.hasAttribute('role') || hasAccessibleName) { return; }
host.setAttribute('role', 'presentation');
}
function isDuotone({ resolvedIcon }) {
return resolvedIcon && resolvedIcon.icon && Array.isArray(resolvedIcon.icon[4]);
}
function updateIcon(host) {
if (host[Internal.NEEDS_REDRAW](host)) {
redrawElements(host);
}
else {
updateShapes(host);
}
}
function redrawElements(host) {
const classes = host[Internal.SVG].classList;
host[Internal.SVG].remove();
const options = {
transform: host.parsedTransform,
mask: host.resolvedMask,
};
const [svg] = host._toHtml(host.resolvedIcon, options) || toHtmlFallback(host.resolvedIcon, options);
host.shadowRoot.innerHTML = host.shadowRoot.innerHTML.concat(svg);
host[Internal.SVG] = host.shadowRoot.querySelector('svg');
host[Internal.SVG].classList.add(...classes);
const paths = host.shadowRoot.querySelectorAll('path');
host[Internal.PATH_PRIMARY] = paths[1] ? paths[1] : paths[0];
host[Internal.PATH_SECONDARY] = paths[1] ? paths[0] : undefined;
const { isDuotone } = host;
host[Internal.NEEDS_REDRAW] = host => isDuotone !== host.isDuotone || host.transform || host.mask;
}
function updateShapes(host) {
const { resolvedIcon } = host;
if (!resolvedIcon) { return; }
host[Internal.SVG].setAttribute('viewBox', `0 0 ${resolvedIcon.icon[0]} ${resolvedIcon.icon[1]}`);
const updatePaths = isDuotone(host) ? updateDuotonePath : updateMonotonePath;
updatePaths(host, { vectorDefinition: resolvedIcon.icon[4] });
}
function updateMonotonePath(host, { vectorDefinition }) {
host[Internal.PATH_PRIMARY].setAttribute('d', vectorDefinition);
}
function updateDuotonePath(host, { vectorDefinition }) {
host[Internal.PATH_PRIMARY].setAttribute('d', vectorDefinition[0]);
host[Internal.PATH_SECONDARY].setAttribute('d', vectorDefinition[1]);
}
function resolveTransform({ _parseTransform, transform }) {
const needsParsing = transform && typeof transform === 'string';
const value = needsParsing ? _parseTransform(transform) : transform;
return value ? value : undefined;
}