UNPKG

@fortawesome/react-fontawesome

Version:
360 lines (351 loc) 9.89 kB
import React, { useId } from 'react'; import { parse, icon } from '@fortawesome/fontawesome-svg-core'; import SVGCorePackageJson from '@fortawesome/fontawesome-svg-core/package.json'; import semver from 'semver'; // src/components/FontAwesomeIcon.tsx // src/utils/camelize.ts function _isNumerical(object) { object = object - 0; return object === object; } function camelize(string) { if (_isNumerical(string)) { return string; } string = string.replaceAll(/[_-]+(.)?/g, (_, chr) => { return chr ? chr.toUpperCase() : ""; }); return string.charAt(0).toLowerCase() + string.slice(1); } // src/converter.ts function capitalize(val) { return val.charAt(0).toUpperCase() + val.slice(1); } var styleCache = /* @__PURE__ */ new Map(); var STYLE_CACHE_LIMIT = 1e3; function styleToObject(style) { if (styleCache.has(style)) { return styleCache.get(style); } const result = {}; let start = 0; const len = style.length; while (start < len) { const semicolonIndex = style.indexOf(";", start); const end = semicolonIndex === -1 ? len : semicolonIndex; const pair = style.slice(start, end).trim(); if (pair) { const colonIndex = pair.indexOf(":"); if (colonIndex > 0) { const rawProp = pair.slice(0, colonIndex).trim(); const value = pair.slice(colonIndex + 1).trim(); if (rawProp && value) { const prop = camelize(rawProp); result[prop.startsWith("webkit") ? capitalize(prop) : prop] = value; } } } start = end + 1; } if (styleCache.size === STYLE_CACHE_LIMIT) { const oldestKey = styleCache.keys().next().value; if (oldestKey) { styleCache.delete(oldestKey); } } styleCache.set(style, result); return result; } function convert(createElement, element, extraProps = {}) { if (typeof element === "string") { return element; } const children = (element.children || []).map((child) => { return convert(createElement, child); }); const elementAttributes = element.attributes || {}; const attrs = {}; for (const [key, val] of Object.entries(elementAttributes)) { switch (true) { case key === "class": { attrs.className = val; delete elementAttributes.class; break; } case key === "style": { attrs.style = styleToObject(String(val)); break; } case key === "aria-label": { attrs["aria-label"] = val; attrs["aria-hidden"] = "false"; break; } case key === "aria-hidden": { attrs["aria-hidden"] = attrs["aria-label"] ? "false" : val; break; } case key.startsWith("aria-"): case key.startsWith("data-"): { attrs[key.toLowerCase()] = val; break; } default: { attrs[camelize(key)] = val; } } } const { style: existingStyle, ...remaining } = extraProps; if (existingStyle) { attrs.style = attrs.style ? { ...attrs.style, ...existingStyle } : existingStyle; } return createElement(element.tag, { ...remaining, ...attrs }, ...children); } var useAccessibilityId = (id, hasAccessibleProps) => { const generatedId = useId(); return id || (hasAccessibleProps ? generatedId : void 0); }; // src/logger.ts var Logger = class { constructor(scope = "react-fontawesome") { this.enabled = false; let IS_DEV = false; try { IS_DEV = typeof process !== "undefined" && process.env.NODE_ENV === "development"; } catch { } this.scope = scope; this.enabled = IS_DEV; } /** * Logs messages to the console if not in production. * @param args - The message and/or data to log. */ log(...args) { if (!this.enabled) return; console.log(`[${this.scope}]`, ...args); } /** * Logs warnings to the console if not in production. * @param args - The warning message and/or data to log. */ warn(...args) { if (!this.enabled) return; console.warn(`[${this.scope}]`, ...args); } /** * Logs errors to the console if not in production. * @param args - The error message and/or data to log. */ error(...args) { if (!this.enabled) return; console.error(`[${this.scope}]`, ...args); } }; var ICON_PACKS_STARTING_VERSION = "7.0.0"; var FA_VERSION = typeof process !== "undefined" && process.env.FA_VERSION || "7.0.0"; var SVG_CORE_VERSION = SVGCorePackageJson.version || FA_VERSION; var IS_VERSION_7_OR_LATER = semver.gte( SVG_CORE_VERSION, ICON_PACKS_STARTING_VERSION ); var ANIMATION_CLASSES = { beat: "fa-beat", fade: "fa-fade", beatFade: "fa-beat-fade", bounce: "fa-bounce", shake: "fa-shake", spin: "fa-spin", spinPulse: "fa-spin-pulse", spinReverse: "fa-spin-reverse", pulse: "fa-pulse" }; var PULL_CLASSES = { left: "fa-pull-left", right: "fa-pull-right" }; var ROTATE_CLASSES = { "90": "fa-rotate-90", "180": "fa-rotate-180", "270": "fa-rotate-270" }; var SIZE_CLASSES = { "2xs": "fa-2xs", xs: "fa-xs", sm: "fa-sm", lg: "fa-lg", xl: "fa-xl", "2xl": "fa-2xl", "1x": "fa-1x", "2x": "fa-2x", "3x": "fa-3x", "4x": "fa-4x", "5x": "fa-5x", "6x": "fa-6x", "7x": "fa-7x", "8x": "fa-8x", "9x": "fa-9x", "10x": "fa-10x" }; var STYLE_CLASSES = { border: "fa-border", /** @deprecated */ fixedWidth: "fa-fw", flip: "fa-flip", flipHorizontal: "fa-flip-horizontal", flipVertical: "fa-flip-vertical", inverse: "fa-inverse", rotateBy: "fa-rotate-by", swapOpacity: "fa-swap-opacity", widthAuto: "fa-width-auto" }; // src/utils/get-class-list-from-props.ts function getClassListFromProps(props) { const { beat, fade, beatFade, bounce, shake, spin, spinPulse, spinReverse, pulse, fixedWidth, inverse, border, flip, size, rotation, pull, swapOpacity, rotateBy, widthAuto, className } = props; const result = []; if (className) result.push(...className.split(" ")); if (beat) result.push(ANIMATION_CLASSES.beat); if (fade) result.push(ANIMATION_CLASSES.fade); if (beatFade) result.push(ANIMATION_CLASSES.beatFade); if (bounce) result.push(ANIMATION_CLASSES.bounce); if (shake) result.push(ANIMATION_CLASSES.shake); if (spin) result.push(ANIMATION_CLASSES.spin); if (spinReverse) result.push(ANIMATION_CLASSES.spinReverse); if (spinPulse) result.push(ANIMATION_CLASSES.spinPulse); if (pulse) result.push(ANIMATION_CLASSES.pulse); if (fixedWidth) result.push(STYLE_CLASSES.fixedWidth); if (inverse) result.push(STYLE_CLASSES.inverse); if (border) result.push(STYLE_CLASSES.border); if (flip === true) result.push(STYLE_CLASSES.flip); if (flip === "horizontal" || flip === "both") { result.push(STYLE_CLASSES.flipHorizontal); } if (flip === "vertical" || flip === "both") { result.push(STYLE_CLASSES.flipVertical); } if (size !== void 0 && size !== null) result.push(SIZE_CLASSES[size]); if (rotation !== void 0 && rotation !== null && rotation !== 0) { result.push(ROTATE_CLASSES[rotation]); } if (pull !== void 0 && pull !== null) result.push(PULL_CLASSES[pull]); if (swapOpacity) result.push(STYLE_CLASSES.swapOpacity); if (!IS_VERSION_7_OR_LATER) return result; if (rotateBy) result.push(STYLE_CLASSES.rotateBy); if (widthAuto) result.push(STYLE_CLASSES.widthAuto); return result; } var isIconDefinition = (icon) => typeof icon === "object" && "icon" in icon && !!icon.icon; function normalizeIconArgs(icon) { if (!icon) { return void 0; } if (isIconDefinition(icon)) { return icon; } return parse.icon(icon); } // src/utils/typed-object-keys.ts function typedObjectKeys(obj) { return Object.keys(obj); } // src/components/FontAwesomeIcon.tsx var logger = new Logger("FontAwesomeIcon"); var DEFAULT_PROPS = { border: false, className: "", mask: void 0, maskId: void 0, fixedWidth: false, inverse: false, flip: false, icon: void 0, listItem: false, pull: void 0, pulse: false, rotation: void 0, rotateBy: false, size: void 0, spin: false, spinPulse: false, spinReverse: false, beat: false, fade: false, beatFade: false, bounce: false, shake: false, symbol: false, title: "", titleId: void 0, transform: void 0, swapOpacity: false, widthAuto: false }; var DEFAULT_PROP_KEYS = new Set(Object.keys(DEFAULT_PROPS)); var FontAwesomeIcon = React.forwardRef((props, ref) => { const allProps = { ...DEFAULT_PROPS, ...props }; const { icon: iconArgs, mask: maskArgs, symbol, title, titleId: titleIdFromProps, maskId: maskIdFromProps, transform } = allProps; const maskId = useAccessibilityId(maskIdFromProps, Boolean(maskArgs)); const titleId = useAccessibilityId(titleIdFromProps, Boolean(title)); const iconLookup = normalizeIconArgs(iconArgs); if (!iconLookup) { logger.error("Icon lookup is undefined", iconArgs); return null; } const classList = getClassListFromProps(allProps); const transformProps = typeof transform === "string" ? parse.transform(transform) : transform; const normalizedMaskArgs = normalizeIconArgs(maskArgs); const renderedIcon = icon(iconLookup, { ...classList.length > 0 && { classes: classList }, ...transformProps && { transform: transformProps }, ...normalizedMaskArgs && { mask: normalizedMaskArgs }, symbol, title, titleId, maskId }); if (!renderedIcon) { logger.error("Could not find icon", iconLookup); return null; } const { abstract } = renderedIcon; const extraProps = { ref }; for (const key of typedObjectKeys(allProps)) { if (DEFAULT_PROP_KEYS.has(key)) { continue; } extraProps[key] = allProps[key]; } return convertCurry(abstract[0], extraProps); }); FontAwesomeIcon.displayName = "FontAwesomeIcon"; var convertCurry = convert.bind(null, React.createElement); export { FontAwesomeIcon };