@pikacss/plugin-icons 0.0.45 → 0.0.47

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/dist/index.d.mts CHANGED
@@ -1,14 +1,67 @@
1
- import { EnginePlugin, Simplify, StyleItem } from "@pikacss/core";
2
- import { IconsOptions } from "@unocss/preset-icons";
1
+ import { CustomCollections, IconCustomizations, IconifyLoaderOptions } from "@iconify/utils";
2
+ import { EnginePlugin, StyleItem } from "@pikacss/core";
3
3
 
4
4
  //#region src/index.d.ts
5
5
  interface IconMeta {
6
6
  collection: string;
7
7
  name: string;
8
8
  svg: string;
9
+ source: IconSource;
9
10
  mode?: IconsConfig['mode'];
10
11
  }
11
- type IconsConfig = Simplify<Omit<IconsOptions, 'warn' | 'layer' | 'processor' | 'customFetcher'> & {
12
+ type IconSource = 'custom' | 'local' | 'cdn';
13
+ interface IconsConfig {
14
+ /**
15
+ * Class name prefix for icon shortcuts.
16
+ *
17
+ * @default 'i-'
18
+ */
19
+ prefix?: string | string[];
20
+ /**
21
+ * Default rendering mode.
22
+ *
23
+ * @default 'auto'
24
+ */
25
+ mode?: 'auto' | 'mask' | 'bg';
26
+ /**
27
+ * Scale icons against 1em.
28
+ *
29
+ * @default 1
30
+ */
31
+ scale?: number;
32
+ /**
33
+ * Native Iconify custom collections.
34
+ */
35
+ collections?: CustomCollections;
36
+ /**
37
+ * Native Iconify SVG customizations.
38
+ */
39
+ customizations?: IconCustomizations;
40
+ /**
41
+ * Auto install missing Iconify JSON packages when supported by the runtime.
42
+ *
43
+ * @default false
44
+ */
45
+ autoInstall?: IconifyLoaderOptions['autoInstall'];
46
+ /**
47
+ * Current working directory used to resolve local Iconify JSON packages.
48
+ *
49
+ * @default process.cwd()
50
+ */
51
+ cwd?: IconifyLoaderOptions['cwd'];
52
+ /**
53
+ * Optional CDN base URL or URL template for collection JSON.
54
+ * Use `{collection}` as a placeholder to fully control the final URL.
55
+ */
56
+ cdn?: string;
57
+ /**
58
+ * CSS unit used when width or height need to be synthesized.
59
+ */
60
+ unit?: string;
61
+ /**
62
+ * Additional CSS properties applied to every resolved icon.
63
+ */
64
+ extraProperties?: Record<string, string>;
12
65
  /**
13
66
  * Processor for the CSS object before stringify
14
67
  */
@@ -17,7 +70,7 @@ type IconsConfig = Simplify<Omit<IconsOptions, 'warn' | 'layer' | 'processor' |
17
70
  * Specify the icons for auto-completion.
18
71
  */
19
72
  autocomplete?: string[];
20
- }>;
73
+ }
21
74
  declare module '@pikacss/core' {
22
75
  interface EngineConfig {
23
76
  icons?: IconsConfig;
package/dist/index.mjs CHANGED
@@ -1,18 +1,12 @@
1
1
  import process from "node:process";
2
- import { encodeSvgForCss, loadIcon } from "@iconify/utils";
2
+ import { encodeSvgForCss, loadIcon, quicklyValidateIconSet, searchForIcon, stringToIcon } from "@iconify/utils";
3
+ import { loadNodeIcon } from "@iconify/utils/lib/loader/node-loader";
3
4
  import { defineEnginePlugin, log } from "@pikacss/core";
4
- import { combineLoaders, createCDNFetchLoader, createNodeLoader, parseIconWithLoader } from "@unocss/preset-icons";
5
5
  import { $fetch } from "ofetch";
6
6
 
7
7
  //#region src/index.ts
8
8
  /**
9
9
  * Environment flags helper function to detect the current runtime environment.
10
- * This replaces the removed `getEnvFlags` export from `@unocss/preset-icons` v66+.
11
- *
12
- * @returns An object containing:
13
- * - `isNode`: Whether the code is running in a Node.js environment
14
- * - `isVSCode`: Whether the code is running within VS Code (extension host)
15
- * - `isESLint`: Whether the code is running within ESLint
16
10
  */
17
11
  function getEnvFlags() {
18
12
  const isNode = typeof process !== "undefined" && typeof process.versions?.node !== "undefined";
@@ -23,27 +17,133 @@ function getEnvFlags() {
23
17
  };
24
18
  }
25
19
  function icons() {
26
- return createIconsPlugin(createIconsLoader);
20
+ return createIconsPlugin();
27
21
  }
28
- function createCDNLoader(cdnBase) {
29
- return createCDNFetchLoader($fetch, cdnBase);
22
+ const globalColonRE = /:/g;
23
+ const currentColorRE = /currentColor/;
24
+ function normalizePrefixes(prefix) {
25
+ const prefixes = [prefix ?? "i-"].flat().filter(Boolean);
26
+ return [...new Set(prefixes)];
27
+ }
28
+ function escapeRegExp(value) {
29
+ return value.replace(/[|\\{}()[\]^$+*?.-]/g, "\\$&");
30
+ }
31
+ function createShortcutRegExp(prefixes) {
32
+ return new RegExp(`^(?:${prefixes.map(escapeRegExp).join("|")})([\\w:-]+)(?:\\?(mask|bg|auto))?$`);
33
+ }
34
+ function getPossibleIconNames(iconName) {
35
+ return [
36
+ iconName,
37
+ iconName.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase(),
38
+ iconName.replace(/([a-z])(\d+)/g, "$1-$2")
39
+ ];
40
+ }
41
+ function createAutocomplete(prefixes, autocomplete = []) {
42
+ const prefixRE = new RegExp(`^(?:${prefixes.map(escapeRegExp).join("|")})`);
43
+ return [...prefixes, ...prefixes.flatMap((prefix) => autocomplete.map((icon) => `${prefix}${icon.replace(prefixRE, "")}`))];
44
+ }
45
+ function createAutocompletePatterns(prefixes) {
46
+ return prefixes.flatMap((prefix) => [
47
+ `\`${prefix}\${string}:\${string}\``,
48
+ `\`${prefix}\${string}:\${string}?mask\``,
49
+ `\`${prefix}\${string}:\${string}?bg\``,
50
+ `\`${prefix}\${string}:\${string}?auto\``
51
+ ]);
52
+ }
53
+ function resolveCdnCollectionUrl(cdn, collection) {
54
+ if (cdn.includes("{collection}")) return cdn.replaceAll("{collection}", collection);
55
+ return `${cdn.replace(/\/$/, "")}/${collection}.json`;
56
+ }
57
+ function createLoaderOptions(config, usedProps) {
58
+ const { scale = 1, collections, autoInstall = false, cwd, unit, extraProperties = {}, customizations = {} } = config;
59
+ const iconCustomizer = customizations.iconCustomizer;
60
+ return {
61
+ addXmlNs: true,
62
+ scale,
63
+ customCollections: collections,
64
+ autoInstall,
65
+ cwd,
66
+ usedProps,
67
+ customizations: {
68
+ ...customizations,
69
+ additionalProps: {
70
+ ...customizations.additionalProps,
71
+ ...extraProperties
72
+ },
73
+ trimCustomSvg: customizations.trimCustomSvg ?? true,
74
+ async iconCustomizer(collection, icon, props) {
75
+ await iconCustomizer?.(collection, icon, props);
76
+ if (unit) {
77
+ if (!props.width) props.width = `${scale}${unit}`;
78
+ if (!props.height) props.height = `${scale}${unit}`;
79
+ }
80
+ }
81
+ }
82
+ };
83
+ }
84
+ async function loadCollectionFromCdn(cdn, collection, cache) {
85
+ if (!cache.has(collection)) cache.set(collection, (async () => {
86
+ try {
87
+ return quicklyValidateIconSet(await $fetch(resolveCdnCollectionUrl(cdn, collection))) ?? void 0;
88
+ } catch {
89
+ return;
90
+ }
91
+ })());
92
+ return cache.get(collection);
30
93
  }
31
- async function createIconsLoader(config) {
32
- const { cdn } = config;
33
- const loaders = [];
34
- const { isNode, isVSCode, isESLint } = getEnvFlags();
35
- if (isNode && !isVSCode && !isESLint) {
36
- const nodeLoader = await createNodeLoader();
37
- if (nodeLoader != null) loaders.push(nodeLoader);
94
+ async function resolveIcon(body, config, flags, cdnCollectionCache) {
95
+ const parsed = stringToIcon(body, true);
96
+ if (parsed == null || !parsed.prefix) return null;
97
+ const customProps = {};
98
+ const customSvg = await loadIcon(parsed.prefix, parsed.name, createLoaderOptions(config, customProps));
99
+ if (customSvg != null) return {
100
+ collection: parsed.prefix,
101
+ name: parsed.name,
102
+ svg: customSvg,
103
+ usedProps: customProps,
104
+ source: "custom"
105
+ };
106
+ if (flags.isNode && !flags.isVSCode && !flags.isESLint) {
107
+ const localProps = {};
108
+ const localSvg = await loadNodeIcon(parsed.prefix, parsed.name, {
109
+ ...createLoaderOptions(config, localProps),
110
+ customCollections: void 0
111
+ });
112
+ if (localSvg != null) return {
113
+ collection: parsed.prefix,
114
+ name: parsed.name,
115
+ svg: localSvg,
116
+ usedProps: localProps,
117
+ source: "local"
118
+ };
38
119
  }
39
- if (cdn) loaders.push(createCDNLoader(cdn));
40
- loaders.push(loadIcon);
41
- return combineLoaders(loaders);
120
+ if (config.cdn) {
121
+ const iconSet = await loadCollectionFromCdn(config.cdn, parsed.prefix, cdnCollectionCache);
122
+ if (iconSet != null) {
123
+ const remoteProps = {};
124
+ const remoteSvg = await searchForIcon(iconSet, parsed.prefix, getPossibleIconNames(parsed.name), createLoaderOptions(config, remoteProps));
125
+ if (remoteSvg != null) return {
126
+ collection: parsed.prefix,
127
+ name: parsed.name,
128
+ svg: remoteSvg,
129
+ usedProps: remoteProps,
130
+ source: "cdn"
131
+ };
132
+ }
133
+ }
134
+ return {
135
+ collection: parsed.prefix,
136
+ name: parsed.name,
137
+ svg: null,
138
+ usedProps: {},
139
+ source: null
140
+ };
42
141
  }
43
- const globalColonRE = /:/g;
44
- function createIconsPlugin(lookupIconLoader) {
142
+ function createIconsPlugin() {
45
143
  let engine;
46
- let iconsConfig;
144
+ let iconsConfig = {};
145
+ const flags = getEnvFlags();
146
+ const cdnCollectionCache = /* @__PURE__ */ new Map();
47
147
  return defineEnginePlugin({
48
148
  name: "icons",
49
149
  configureRawConfig: async (config) => {
@@ -51,46 +151,25 @@ function createIconsPlugin(lookupIconLoader) {
51
151
  },
52
152
  configureEngine: async (_engine) => {
53
153
  engine = _engine;
54
- const { scale = 1, mode = "auto", prefix = "i-", iconifyCollectionsNames, collections: customCollections, customizations = {}, autoInstall = false, collectionsNodeResolvePath, unit, extraProperties = {}, processor, autocomplete: _autocomplete } = iconsConfig;
55
- const loaderOptions = {
56
- addXmlNs: true,
57
- scale,
58
- customCollections,
59
- autoInstall,
60
- cwd: collectionsNodeResolvePath,
61
- warn: void 0,
62
- customizations: {
63
- ...customizations,
64
- additionalProps: { ...extraProperties },
65
- trimCustomSvg: true,
66
- async iconCustomizer(collection, icon, props) {
67
- await customizations.iconCustomizer?.(collection, icon, props);
68
- if (unit) {
69
- if (!props.width) props.width = `${scale}${unit}`;
70
- if (!props.height) props.height = `${scale}${unit}`;
71
- }
72
- }
73
- }
74
- };
75
- const prefixRE = new RegExp(`^(${[prefix].flat().join("|")})`);
76
- const autocompletePrefix = [prefix].flat();
77
- const autocomplete = [...autocompletePrefix, ...autocompletePrefix.flatMap((p) => _autocomplete?.map((a) => `${p}${a.replace(prefixRE, "")}`) || [])];
78
- let iconLoader;
154
+ const { mode = "auto", prefix = "i-", processor, autocomplete: _autocomplete } = iconsConfig;
155
+ const prefixes = normalizePrefixes(prefix);
156
+ const autocomplete = createAutocomplete(prefixes, _autocomplete);
157
+ const autocompletePatterns = createAutocompletePatterns(prefixes);
158
+ engine.appendAutocomplete({ patterns: { styleItemStrings: autocompletePatterns } });
79
159
  engine.shortcuts.add({
80
- shortcut: new RegExp(`^(?:${[prefix].flat().join("|")})([\\w:-]+)(?:\\?(mask|bg|auto))?$`),
160
+ shortcut: createShortcutRegExp(prefixes),
81
161
  value: async (match) => {
82
162
  let [full, body, _mode = mode] = match;
83
- iconLoader = iconLoader || await lookupIconLoader(iconsConfig);
84
- const usedProps = {};
85
- const parsed = await parseIconWithLoader(body, iconLoader, {
86
- ...loaderOptions,
87
- usedProps
88
- }, iconifyCollectionsNames);
89
- if (parsed == null) {
163
+ const resolved = await resolveIcon(body, iconsConfig, flags, cdnCollectionCache);
164
+ if (resolved == null) {
165
+ log.warn(`invalid icon name "${full}"`);
166
+ return {};
167
+ }
168
+ if (resolved.svg == null) {
90
169
  log.warn(`failed to load icon "${full}"`);
91
170
  return {};
92
171
  }
93
- const url = `url("data:image/svg+xml;utf8,${encodeSvgForCss(parsed.svg)}")`;
172
+ const url = `url("data:image/svg+xml;utf8,${encodeSvgForCss(resolved.svg)}")`;
94
173
  const varName = `--${engine.config.prefix}svg-icon-${body.replace(globalColonRE, "-")}`;
95
174
  if (engine.variables.store.has(varName) === false) engine.variables.add({ [varName]: {
96
175
  value: url,
@@ -100,7 +179,7 @@ function createIconsPlugin(lookupIconLoader) {
100
179
  },
101
180
  pruneUnused: true
102
181
  } });
103
- if (_mode === "auto") _mode = parsed.svg.includes("currentColor") ? "mask" : "bg";
182
+ if (_mode === "auto") _mode = currentColorRE.test(resolved.svg) ? "mask" : "bg";
104
183
  let styleItem;
105
184
  if (_mode === "mask") styleItem = {
106
185
  "--svg-icon": `var(${varName})`,
@@ -110,17 +189,20 @@ function createIconsPlugin(lookupIconLoader) {
110
189
  "mask-size": "100% 100%",
111
190
  "background-color": "currentColor",
112
191
  "color": "inherit",
113
- ...usedProps
192
+ ...resolved.usedProps
114
193
  };
115
194
  else styleItem = {
116
195
  "--svg-icon": `var(${varName})`,
117
196
  "background": "var(--svg-icon) no-repeat",
118
197
  "background-size": "100% 100%",
119
198
  "background-color": "transparent",
120
- ...usedProps
199
+ ...resolved.usedProps
121
200
  };
122
201
  processor?.(styleItem, {
123
- ...parsed,
202
+ collection: resolved.collection,
203
+ name: resolved.name,
204
+ svg: resolved.svg,
205
+ source: resolved.source,
124
206
  mode: _mode
125
207
  });
126
208
  return styleItem;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pikacss/plugin-icons",
3
3
  "type": "module",
4
- "version": "0.0.45",
4
+ "version": "0.0.47",
5
5
  "author": "DevilTea <ch19980814@gmail.com>",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -37,15 +37,14 @@
37
37
  "dist"
38
38
  ],
39
39
  "peerDependencies": {
40
- "@pikacss/core": "0.0.45"
40
+ "@pikacss/core": "0.0.47"
41
41
  },
42
42
  "dependencies": {
43
43
  "@iconify/utils": "^3.1.0",
44
- "@unocss/preset-icons": "^66.6.1",
45
44
  "ofetch": "^1.5.1"
46
45
  },
47
46
  "devDependencies": {
48
- "@pikacss/core": "0.0.45"
47
+ "@pikacss/core": "0.0.47"
49
48
  },
50
49
  "scripts": {
51
50
  "build": "tsdown && pnpm exec publint",