137 lines
4.3 KiB
Text
137 lines
4.3 KiB
Text
---
|
|
// @ts-ignore virtual module
|
|
import icons, { config } from "virtual:astro-icon";
|
|
// @ts-ignore generated by typegen
|
|
import type { Icon } from "virtual:astro-icon";
|
|
import { getIconData, iconToSVG } from "@iconify/utils";
|
|
import type { HTMLAttributes } from "astro/types";
|
|
import { cache } from "./cache.js";
|
|
import type { IconifyIconBuildResult } from "@iconify/utils/lib/svg/build.js";
|
|
|
|
interface Props extends HTMLAttributes<"svg"> {
|
|
name: Icon;
|
|
"is:inline"?: boolean;
|
|
title?: string;
|
|
desc?: string;
|
|
size?: number | string;
|
|
width?: number | string;
|
|
height?: number | string;
|
|
}
|
|
|
|
class AstroIconError extends Error {
|
|
public hint: string = '';
|
|
|
|
constructor(message: string) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
const req = Astro.request;
|
|
const { name = "", title, desc, "is:inline": inline = false, ...props } = Astro.props;
|
|
const map = cache.get(req) ?? new Map();
|
|
const i = map.get(name) ?? 0;
|
|
map.set(name, i + 1);
|
|
cache.set(req, map);
|
|
|
|
const { include = {} } = config;
|
|
const sets = Object.keys(include);
|
|
|
|
const includeSymbol = !inline && i === 0;
|
|
|
|
let [setName, iconName] = (name as string).split(":");
|
|
|
|
if (!setName && iconName) {
|
|
const err = new AstroIconError(`Invalid "name" provided!`);
|
|
if (import.meta.env.DEV) {
|
|
err.hint = `The provided value of "${name}" is invalid.\n\nDid you forget the icon set name? If you were attemping to reference a local icon, use the icon's name directly. (ie. "${iconName}")`;
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
// No iconName, assume local icon reference
|
|
if (!iconName) {
|
|
// Assign the provided setName to the iconName
|
|
iconName = setName;
|
|
setName = "local";
|
|
|
|
// Check if the local icon set exists
|
|
if (!icons[setName]) {
|
|
const err = new AstroIconError('Unable to load the "local" icon set!');
|
|
if (import.meta.env.DEV) {
|
|
err.hint =
|
|
'It looks like the "local" set was not loaded.\n\nDid you forget to create the icon directory or to update your config?';
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
// Check if the icon is missing from the local collection
|
|
if (!(iconName in icons[setName].icons)) {
|
|
const err = new AstroIconError(`Unable to locate "${name}" icon!`);
|
|
if (import.meta.env.DEV) {
|
|
err.hint = `The icon named "${iconName}" was not found in your local icon directory.\n\nDid you forget to configure your icon directory or make a typo?`;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
const collection = icons[setName];
|
|
|
|
// Iconify collection not configured correctly
|
|
if (!collection) {
|
|
const err = new AstroIconError(`Unable to locate the "${setName}" icon set!`);
|
|
if (import.meta.env.DEV) {
|
|
if (sets.includes(setName)) {
|
|
err.hint = `It looks like the "${setName}" set was not loaded.\n\nDid you install the "@iconify-json/${setName}" dependency?`;
|
|
} else {
|
|
err.hint = `It looks like the "${setName}" set is not included in your configuration.\n\nDo you need to add the "${setName}" set?`;
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
const iconData = getIconData(collection, iconName ?? setName);
|
|
// Missing icon from the icon collection
|
|
if (!iconData) {
|
|
const err = new AstroIconError(`Unable to locate "${name}" icon!`);
|
|
if (import.meta.env.DEV) {
|
|
const [maybeStar] = include[setName];
|
|
if (maybeStar === "*" || include[setName].includes(iconName)) {
|
|
err.hint = `The "${setName}" set does not include an icon named "${iconName}".\n\nIs this a typo?`;
|
|
} else {
|
|
err.hint = `The "${setName}" set is not configured to include an icon named "${iconName}".\n\nDo you need to add it to your configuration?`;
|
|
}
|
|
}
|
|
throw err;
|
|
}
|
|
|
|
const id = `ai:${collection.prefix}:${iconName ?? setName}`;
|
|
|
|
if (props.size) {
|
|
props.width = props.size;
|
|
props.height = props.size;
|
|
delete props.size;
|
|
}
|
|
const renderData = iconToSVG(iconData);
|
|
const normalizedProps = { ...renderData.attributes as Partial<IconifyIconBuildResult['attributes']>, ...props };
|
|
const normalizedBody = renderData.body;
|
|
|
|
const { viewBox } = normalizedProps;
|
|
if (includeSymbol) {
|
|
delete normalizedProps.viewBox;
|
|
}
|
|
---
|
|
|
|
<svg {...normalizedProps} data-icon={name}>
|
|
{title && <title>{title}</title>}
|
|
{desc && <desc>{desc}</desc>}
|
|
{
|
|
inline ? (
|
|
<Fragment id={id} set:html={normalizedBody} />
|
|
) : (
|
|
<Fragment>
|
|
{includeSymbol && <symbol id={id} viewBox={viewBox} set:html={normalizedBody} />}
|
|
<use href={`#${id}`} />
|
|
</Fragment>
|
|
)
|
|
}
|
|
</svg>
|