UNPKG

@riovir/wc-fontawesome

Version:

Web components for Font Awesome

162 lines (134 loc) 5.83 kB
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; }