-
-
Notifications
You must be signed in to change notification settings - Fork 60
/
Icon.astro
137 lines (119 loc) · 4.25 KB
/
Icon.astro
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
---
// @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>