@unocss/preset-icons 0.44.3 → 0.44.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -95,6 +95,8 @@ To load `iconify` collections you should use `@iconify-json/[the-collection-you-
95
95
  When using bundlers, you can provide the collections using `dynamic imports` so they will be bundler as async chunk and loaded on demand.
96
96
 
97
97
  ```ts
98
+ import presetIcons from '@unocss/preset-icons/browser'
99
+
98
100
  presetIcons({
99
101
  collections: {
100
102
  carbon: () => import('@iconify-json/carbon/icons.json').then(i => i.default),
@@ -109,6 +111,8 @@ presetIcons({
109
111
  Or if you prefer to fetch them from CDN, you can specify the `cdn` option since `v0.32.10`. We recommend [esm.sh](https://esm.sh/) as the CDN provider.
110
112
 
111
113
  ```ts
114
+ import presetIcons from '@unocss/preset-icons/browser'
115
+
112
116
  presetIcons({
113
117
  cdn: 'https://esm.sh/'
114
118
  })
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ const cdn = require('./chunks/cdn.cjs');
6
+ const core = require('./core.cjs');
7
+ require('ohmyfetch');
8
+ require('@unocss/core');
9
+
10
+ const presetIcons = core.createPresetIcons(async (options) => {
11
+ const { cdn: cdn$1 } = options;
12
+ if (cdn$1)
13
+ return cdn.createCDNLoader(cdn$1);
14
+ return cdn.loadIcon;
15
+ });
16
+
17
+ exports.createPresetIcons = core.createPresetIcons;
18
+ exports["default"] = presetIcons;
19
+ exports.presetIcons = presetIcons;
@@ -0,0 +1,9 @@
1
+ import * as _unocss_core from '@unocss/core';
2
+ import { IconsOptions } from './core.js';
3
+ export { IconsOptions, createPresetIcons } from './core.js';
4
+ import '@iconify/utils/lib/loader/types';
5
+ import '@iconify/types';
6
+
7
+ declare const presetIcons: (options?: IconsOptions) => _unocss_core.Preset<{}>;
8
+
9
+ export { presetIcons as default, presetIcons };
@@ -0,0 +1,14 @@
1
+ import { c as createCDNLoader, l as loadIcon } from './chunks/cdn.mjs';
2
+ import { createPresetIcons } from './core.mjs';
3
+ export { createPresetIcons } from './core.mjs';
4
+ import 'ohmyfetch';
5
+ import '@unocss/core';
6
+
7
+ const presetIcons = createPresetIcons(async (options) => {
8
+ const { cdn } = options;
9
+ if (cdn)
10
+ return createCDNLoader(cdn);
11
+ return loadIcon;
12
+ });
13
+
14
+ export { presetIcons as default, presetIcons };
@@ -0,0 +1,440 @@
1
+ 'use strict';
2
+
3
+ const ohmyfetch = require('ohmyfetch');
4
+
5
+ const defaults = Object.freeze({
6
+ inline: false,
7
+ width: null,
8
+ height: null,
9
+ hAlign: "center",
10
+ vAlign: "middle",
11
+ slice: false,
12
+ hFlip: false,
13
+ vFlip: false,
14
+ rotate: 0
15
+ });
16
+
17
+ const iconDefaults = Object.freeze({
18
+ left: 0,
19
+ top: 0,
20
+ width: 16,
21
+ height: 16,
22
+ rotate: 0,
23
+ vFlip: false,
24
+ hFlip: false
25
+ });
26
+ function fullIcon(data) {
27
+ return { ...iconDefaults, ...data };
28
+ }
29
+
30
+ function mergeIconData(icon, alias) {
31
+ const result = { ...icon };
32
+ for (const key in iconDefaults) {
33
+ const prop = key;
34
+ if (alias[prop] !== void 0) {
35
+ const value = alias[prop];
36
+ if (result[prop] === void 0) {
37
+ result[prop] = value;
38
+ continue;
39
+ }
40
+ switch (prop) {
41
+ case "rotate":
42
+ result[prop] = (result[prop] + value) % 4;
43
+ break;
44
+ case "hFlip":
45
+ case "vFlip":
46
+ result[prop] = value !== result[prop];
47
+ break;
48
+ default:
49
+ result[prop] = value;
50
+ }
51
+ }
52
+ }
53
+ return result;
54
+ }
55
+
56
+ function getIconData(data, name, full = false) {
57
+ function getIcon(name2, iteration) {
58
+ if (data.icons[name2] !== void 0) {
59
+ return Object.assign({}, data.icons[name2]);
60
+ }
61
+ if (iteration > 5) {
62
+ return null;
63
+ }
64
+ const aliases = data.aliases;
65
+ if (aliases && aliases[name2] !== void 0) {
66
+ const item = aliases[name2];
67
+ const result2 = getIcon(item.parent, iteration + 1);
68
+ if (result2) {
69
+ return mergeIconData(result2, item);
70
+ }
71
+ return result2;
72
+ }
73
+ const chars = data.chars;
74
+ if (!iteration && chars && chars[name2] !== void 0) {
75
+ return getIcon(chars[name2], iteration + 1);
76
+ }
77
+ return null;
78
+ }
79
+ const result = getIcon(name, 0);
80
+ if (result) {
81
+ for (const key in iconDefaults) {
82
+ if (result[key] === void 0 && data[key] !== void 0) {
83
+ result[key] = data[key];
84
+ }
85
+ }
86
+ }
87
+ return result && full ? fullIcon(result) : result;
88
+ }
89
+
90
+ const unitsSplit = /(-?[0-9.]*[0-9]+[0-9.]*)/g;
91
+ const unitsTest = /^-?[0-9.]*[0-9]+[0-9.]*$/g;
92
+ function calculateSize(size, ratio, precision) {
93
+ if (ratio === 1) {
94
+ return size;
95
+ }
96
+ precision = precision === void 0 ? 100 : precision;
97
+ if (typeof size === "number") {
98
+ return Math.ceil(size * ratio * precision) / precision;
99
+ }
100
+ if (typeof size !== "string") {
101
+ return size;
102
+ }
103
+ const oldParts = size.split(unitsSplit);
104
+ if (oldParts === null || !oldParts.length) {
105
+ return size;
106
+ }
107
+ const newParts = [];
108
+ let code = oldParts.shift();
109
+ let isNumber = unitsTest.test(code);
110
+ while (true) {
111
+ if (isNumber) {
112
+ const num = parseFloat(code);
113
+ if (isNaN(num)) {
114
+ newParts.push(code);
115
+ } else {
116
+ newParts.push(Math.ceil(num * ratio * precision) / precision);
117
+ }
118
+ } else {
119
+ newParts.push(code);
120
+ }
121
+ code = oldParts.shift();
122
+ if (code === void 0) {
123
+ return newParts.join("");
124
+ }
125
+ isNumber = !isNumber;
126
+ }
127
+ }
128
+
129
+ function preserveAspectRatio(props) {
130
+ let result = "";
131
+ switch (props.hAlign) {
132
+ case "left":
133
+ result += "xMin";
134
+ break;
135
+ case "right":
136
+ result += "xMax";
137
+ break;
138
+ default:
139
+ result += "xMid";
140
+ }
141
+ switch (props.vAlign) {
142
+ case "top":
143
+ result += "YMin";
144
+ break;
145
+ case "bottom":
146
+ result += "YMax";
147
+ break;
148
+ default:
149
+ result += "YMid";
150
+ }
151
+ result += props.slice ? " slice" : " meet";
152
+ return result;
153
+ }
154
+ function iconToSVG(icon, customisations) {
155
+ const box = {
156
+ left: icon.left,
157
+ top: icon.top,
158
+ width: icon.width,
159
+ height: icon.height
160
+ };
161
+ let body = icon.body;
162
+ [icon, customisations].forEach((props) => {
163
+ const transformations = [];
164
+ const hFlip = props.hFlip;
165
+ const vFlip = props.vFlip;
166
+ let rotation = props.rotate;
167
+ if (hFlip) {
168
+ if (vFlip) {
169
+ rotation += 2;
170
+ } else {
171
+ transformations.push("translate(" + (box.width + box.left).toString() + " " + (0 - box.top).toString() + ")");
172
+ transformations.push("scale(-1 1)");
173
+ box.top = box.left = 0;
174
+ }
175
+ } else if (vFlip) {
176
+ transformations.push("translate(" + (0 - box.left).toString() + " " + (box.height + box.top).toString() + ")");
177
+ transformations.push("scale(1 -1)");
178
+ box.top = box.left = 0;
179
+ }
180
+ let tempValue;
181
+ if (rotation < 0) {
182
+ rotation -= Math.floor(rotation / 4) * 4;
183
+ }
184
+ rotation = rotation % 4;
185
+ switch (rotation) {
186
+ case 1:
187
+ tempValue = box.height / 2 + box.top;
188
+ transformations.unshift("rotate(90 " + tempValue.toString() + " " + tempValue.toString() + ")");
189
+ break;
190
+ case 2:
191
+ transformations.unshift("rotate(180 " + (box.width / 2 + box.left).toString() + " " + (box.height / 2 + box.top).toString() + ")");
192
+ break;
193
+ case 3:
194
+ tempValue = box.width / 2 + box.left;
195
+ transformations.unshift("rotate(-90 " + tempValue.toString() + " " + tempValue.toString() + ")");
196
+ break;
197
+ }
198
+ if (rotation % 2 === 1) {
199
+ if (box.left !== 0 || box.top !== 0) {
200
+ tempValue = box.left;
201
+ box.left = box.top;
202
+ box.top = tempValue;
203
+ }
204
+ if (box.width !== box.height) {
205
+ tempValue = box.width;
206
+ box.width = box.height;
207
+ box.height = tempValue;
208
+ }
209
+ }
210
+ if (transformations.length) {
211
+ body = '<g transform="' + transformations.join(" ") + '">' + body + "</g>";
212
+ }
213
+ });
214
+ let width, height;
215
+ if (customisations.width === null && customisations.height === null) {
216
+ height = "1em";
217
+ width = calculateSize(height, box.width / box.height);
218
+ } else if (customisations.width !== null && customisations.height !== null) {
219
+ width = customisations.width;
220
+ height = customisations.height;
221
+ } else if (customisations.height !== null) {
222
+ height = customisations.height;
223
+ width = calculateSize(height, box.width / box.height);
224
+ } else {
225
+ width = customisations.width;
226
+ height = calculateSize(width, box.height / box.width);
227
+ }
228
+ if (width === "auto") {
229
+ width = box.width;
230
+ }
231
+ if (height === "auto") {
232
+ height = box.height;
233
+ }
234
+ width = typeof width === "string" ? width : width.toString() + "";
235
+ height = typeof height === "string" ? height : height.toString() + "";
236
+ const result = {
237
+ attributes: {
238
+ width,
239
+ height,
240
+ preserveAspectRatio: preserveAspectRatio(customisations),
241
+ viewBox: box.left.toString() + " " + box.top.toString() + " " + box.width.toString() + " " + box.height.toString()
242
+ },
243
+ body
244
+ };
245
+ if (customisations.inline) {
246
+ result.inline = true;
247
+ }
248
+ return result;
249
+ }
250
+
251
+ function trimSVG(str) {
252
+ return str.replace(/(["';{}><])\s*\n\s*/g, "$1").replace(/\s*\n\s*/g, " ").replace(/\s+"/g, '"').replace(/="\s+/g, '="').trim();
253
+ }
254
+
255
+ const svgWidthRegex = /width\s*=\s*["'](\w+)["']/;
256
+ const svgHeightRegex = /height\s*=\s*["'](\w+)["']/;
257
+ function configureSvgSize(svg, props, scale) {
258
+ const svgNode = svg.slice(0, svg.indexOf(">"));
259
+ let result = svgWidthRegex.exec(svgNode);
260
+ const w = result != null;
261
+ if (typeof props.width === "undefined" || props.width === null) {
262
+ if (typeof scale === "number") {
263
+ props.width = `${scale}em`;
264
+ } else if (result) {
265
+ props.width = result[1];
266
+ }
267
+ }
268
+ result = svgHeightRegex.exec(svgNode);
269
+ const h = result != null;
270
+ if (typeof props.height === "undefined" || props.height === null) {
271
+ if (typeof scale === "number") {
272
+ props.height = `${scale}em`;
273
+ } else if (result) {
274
+ props.height = result[1];
275
+ }
276
+ }
277
+ return [w, h];
278
+ }
279
+ async function mergeIconProps(svg, collection, icon, options, propsProvider, afterCustomizations) {
280
+ const { scale, addXmlNs = false } = options ?? {};
281
+ const { additionalProps = {}, iconCustomizer } = options?.customizations ?? {};
282
+ const props = await propsProvider?.() ?? {};
283
+ await iconCustomizer?.(collection, icon, props);
284
+ Object.keys(additionalProps).forEach((p) => {
285
+ const v = additionalProps[p];
286
+ if (v !== void 0 && v !== null)
287
+ props[p] = v;
288
+ });
289
+ afterCustomizations?.(props);
290
+ const [widthOnSvg, heightOnSvg] = configureSvgSize(svg, props, scale);
291
+ if (addXmlNs) {
292
+ if (!svg.includes(" xmlns=") && !props["xmlns"]) {
293
+ props["xmlns"] = "http://www.w3.org/2000/svg";
294
+ }
295
+ if (!svg.includes(" xmlns:xlink=") && svg.includes("xlink:") && !props["xmlns:xlink"]) {
296
+ props["xmlns:xlink"] = "http://www.w3.org/1999/xlink";
297
+ }
298
+ }
299
+ const propsToAdd = Object.keys(props).map((p) => p === "width" && widthOnSvg || p === "height" && heightOnSvg ? null : `${p}="${props[p]}"`).filter((p) => p != null);
300
+ if (propsToAdd.length) {
301
+ svg = svg.replace("<svg ", `<svg ${propsToAdd.join(" ")} `);
302
+ }
303
+ if (options) {
304
+ const { defaultStyle, defaultClass } = options;
305
+ if (defaultClass && !svg.includes(" class=")) {
306
+ svg = svg.replace("<svg ", `<svg class="${defaultClass}" `);
307
+ }
308
+ if (defaultStyle && !svg.includes(" style=")) {
309
+ svg = svg.replace("<svg ", `<svg style="${defaultStyle}" `);
310
+ }
311
+ }
312
+ const usedProps = options?.usedProps;
313
+ if (usedProps) {
314
+ Object.keys(additionalProps).forEach((p) => {
315
+ const v = props[p];
316
+ if (v !== void 0 && v !== null)
317
+ usedProps[p] = v;
318
+ });
319
+ if (typeof props.width !== "undefined" && props.width !== null) {
320
+ usedProps.width = props.width;
321
+ }
322
+ if (typeof props.height !== "undefined" && props.height !== null) {
323
+ usedProps.height = props.height;
324
+ }
325
+ }
326
+ return svg;
327
+ }
328
+
329
+ async function getCustomIcon(custom, collection, icon, options) {
330
+ let result;
331
+ if (typeof custom === "function") {
332
+ result = await custom(icon);
333
+ } else {
334
+ const inline = custom[icon];
335
+ result = typeof inline === "function" ? await inline() : inline;
336
+ }
337
+ if (result) {
338
+ const cleanupIdx = result.indexOf("<svg");
339
+ if (cleanupIdx > 0)
340
+ result = result.slice(cleanupIdx);
341
+ const { transform } = options?.customizations ?? {};
342
+ result = typeof transform === "function" ? await transform(result, collection, icon) : result;
343
+ if (!result.startsWith("<svg")) {
344
+ console.warn(`Custom icon "${icon}" in "${collection}" is not a valid SVG`);
345
+ return result;
346
+ }
347
+ return await mergeIconProps(options?.customizations?.trimCustomSvg === true ? trimSVG(result) : result, collection, icon, options, void 0);
348
+ }
349
+ }
350
+
351
+ async function searchForIcon(iconSet, collection, ids, options) {
352
+ let iconData;
353
+ const { customize } = options?.customizations ?? {};
354
+ for (const id of ids) {
355
+ iconData = getIconData(iconSet, id, true);
356
+ if (iconData) {
357
+ let defaultCustomizations = { ...defaults };
358
+ if (typeof customize === "function")
359
+ defaultCustomizations = customize(defaultCustomizations);
360
+ const {
361
+ attributes: { width, height, ...restAttributes },
362
+ body
363
+ } = iconToSVG(iconData, defaultCustomizations);
364
+ const scale = options?.scale;
365
+ return await mergeIconProps(`<svg >${body}</svg>`, collection, id, options, () => {
366
+ return { ...restAttributes };
367
+ }, (props) => {
368
+ if (typeof props.width === "undefined" || props.width === null) {
369
+ if (typeof scale === "number") {
370
+ props.width = `${scale}em`;
371
+ } else {
372
+ props.width = width;
373
+ }
374
+ }
375
+ if (typeof props.height === "undefined" || props.height === null) {
376
+ if (typeof scale === "number") {
377
+ props.height = `${scale}em`;
378
+ } else {
379
+ props.height = height;
380
+ }
381
+ }
382
+ });
383
+ }
384
+ }
385
+ }
386
+
387
+ const loadIcon = async (collection, icon, options) => {
388
+ const custom = options?.customCollections?.[collection];
389
+ if (custom) {
390
+ if (typeof custom === "function") {
391
+ const result = await custom(icon);
392
+ if (result) {
393
+ if (typeof result === "string") {
394
+ return await getCustomIcon(() => result, collection, icon, options);
395
+ }
396
+ if ("icons" in result) {
397
+ const ids = [
398
+ icon,
399
+ icon.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
400
+ icon.replace(/([a-z])(\d+)/g, "$1-$2")
401
+ ];
402
+ return await searchForIcon(result, collection, ids, options);
403
+ }
404
+ }
405
+ } else {
406
+ return await getCustomIcon(custom, collection, icon, options);
407
+ }
408
+ }
409
+ return void 0;
410
+ };
411
+
412
+ const supportedCollection = ["material-symbols", "ic", "mdi", "ph", "ri", "carbon", "bi", "tabler", "ion", "uil", "teenyicons", "clarity", "iconoir", "majesticons", "zondicons", "ant-design", "bx", "bxs", "gg", "cil", "lucide", "pixelarticons", "system-uicons", "ci", "akar-icons", "typcn", "radix-icons", "ep", "mdi-light", "fe", "eos-icons", "line-md", "charm", "prime", "heroicons-outline", "heroicons-solid", "uiw", "uim", "uit", "uis", "maki", "gridicons", "mi", "quill", "gala", "fluent", "icon-park-outline", "icon-park", "vscode-icons", "jam", "codicon", "pepicons", "bytesize", "ei", "fa6-solid", "fa6-regular", "octicon", "ooui", "nimbus", "openmoji", "twemoji", "noto", "noto-v1", "emojione", "emojione-monotone", "emojione-v1", "fxemoji", "bxl", "logos", "simple-icons", "cib", "fa6-brands", "arcticons", "file-icons", "brandico", "entypo-social", "cryptocurrency", "flag", "circle-flags", "flagpack", "cif", "gis", "map", "geo", "fad", "academicons", "wi", "healthicons", "medical-icon", "la", "eva", "dashicons", "flat-color-icons", "entypo", "foundation", "raphael", "icons8", "iwwa", "fa-solid", "fa-regular", "fa-brands", "fa", "fontisto", "icomoon-free", "ps", "subway", "oi", "wpf", "simple-line-icons", "et", "el", "vaadin", "grommet-icons", "whh", "si-glyph", "zmdi", "ls", "bpmn", "flat-ui", "vs", "topcoat", "il", "websymbol", "fontelico", "feather", "mono-icons"];
413
+ function createCDNLoader(cdnBase) {
414
+ const cache = /* @__PURE__ */ new Map();
415
+ function fetchCollection(name) {
416
+ if (!supportedCollection.includes(name))
417
+ return void 0;
418
+ if (!cache.has(name))
419
+ cache.set(name, ohmyfetch.$fetch(`${cdnBase}@iconify-json/${name}/icons.json`));
420
+ return cache.get(name);
421
+ }
422
+ return async (collection, icon, options) => {
423
+ let result = await loadIcon(collection, icon, options);
424
+ if (result)
425
+ return result;
426
+ const iconSet = await fetchCollection(collection);
427
+ if (iconSet) {
428
+ const ids = [
429
+ icon,
430
+ icon.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
431
+ icon.replace(/([a-z])(\d+)/g, "$1-$2")
432
+ ];
433
+ result = await searchForIcon(iconSet, collection, ids, options);
434
+ }
435
+ return result;
436
+ };
437
+ }
438
+
439
+ exports.createCDNLoader = createCDNLoader;
440
+ exports.loadIcon = loadIcon;