@stnd/icon 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @stnd/icon
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Auto-bumped @stnd/icon due to modified code.
8
+
3
9
  ## 0.3.0
4
10
 
5
11
  ### Minor Changes
package/embedded.js ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Embedded / Custom SVG Icons
3
+ *
4
+ * These icons are bundled directly into JS memory so they are guaranteed to
5
+ * resolve instantly with 0 disk I/O and 0 external network requests, even in
6
+ * serverless runtimes (like Cloudflare Workers SSR) where Node.js filesystem
7
+ * access (`fs.readFile`) is unavailable and the icon is not in upstream Iconify.
8
+ */
9
+ export const embeddedIcons = {
10
+ "ph:obsidian": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
11
+ <path d="M19.355 18.538a68.967 68.959 0 0 0 1.858-2.954.81.81 0 0 0-.062-.9c-.516-.685-1.504-2.075-2.042-3.362-.553-1.321-.636-3.375-.64-4.377a1.707 1.707 0 0 0-.358-1.05l-3.198-4.064a3.744 3.744 0 0 1-.076.543c-.106.503-.307 1.004-.536 1.5-.134.29-.29.6-.446.914l-.31.626c-.516 1.068-.997 2.227-1.132 3.59-.124 1.26.046 2.73.815 4.481.128.011.257.025.386.044a6.363 6.363 0 0 1 3.326 1.505c.916.79 1.744 1.922 2.415 3.5zM8.199 22.569c.073.012.146.02.22.02.78.024 2.095.092 3.16.29.87.16 2.593.64 4.01 1.055 1.083.316 2.198-.548 2.355-1.664.114-.814.33-1.735.725-2.58l-.01.005c-.67-1.87-1.522-3.078-2.416-3.849a5.295 5.295 0 0 0-2.778-1.257c-1.54-.216-2.952.19-3.84.45.532 2.218.368 4.829-1.425 7.531zM5.533 9.938c-.023.1-.056.197-.098.29L2.82 16.059a1.602 1.602 0 0 0 .313 1.772l4.116 4.24c2.103-3.101 1.796-6.02.836-8.3-.728-1.73-1.832-3.081-2.55-3.831zM9.32 14.01c.615-.183 1.606-.465 2.745-.534-.683-1.725-.848-3.233-.716-4.577.154-1.552.7-2.847 1.235-3.95.113-.235.223-.454.328-.664.149-.297.288-.577.419-.86.217-.47.379-.885.46-1.27.08-.38.08-.72-.014-1.043-.095-.325-.297-.675-.68-1.06a1.6 1.6 0 0 0-1.475.36l-4.95 4.452a1.602 1.602 0 0 0-.513.952l-.427 2.83c.672.59 2.328 2.316 3.335 4.711.09.21.175.43.253.653z"/>
12
+ </svg>`,
13
+ "simple-icons:obsidian": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
14
+ <path d="M19.355 18.538a68.967 68.959 0 0 0 1.858-2.954.81.81 0 0 0-.062-.9c-.516-.685-1.504-2.075-2.042-3.362-.553-1.321-.636-3.375-.64-4.377a1.707 1.707 0 0 0-.358-1.05l-3.198-4.064a3.744 3.744 0 0 1-.076.543c-.106.503-.307 1.004-.536 1.5-.134.29-.29.6-.446.914l-.31.626c-.516 1.068-.997 2.227-1.132 3.59-.124 1.26.046 2.73.815 4.481.128.011.257.025.386.044a6.363 6.363 0 0 1 3.326 1.505c.916.79 1.744 1.922 2.415 3.5zM8.199 22.569c.073.012.146.02.22.02.78.024 2.095.092 3.16.29.87.16 2.593.64 4.01 1.055 1.083.316 2.198-.548 2.355-1.664.114-.814.33-1.735.725-2.58l-.01.005c-.67-1.87-1.522-3.078-2.416-3.849a5.295 5.295 0 0 0-2.778-1.257c-1.54-.216-2.952.19-3.84.45.532 2.218.368 4.829-1.425 7.531zM5.533 9.938c-.023.1-.056.197-.098.29L2.82 16.059a1.602 1.602 0 0 0 .313 1.772l4.116 4.24c2.103-3.101 1.796-6.02.836-8.3-.728-1.73-1.832-3.081-2.55-3.831zM9.32 14.01c.615-.183 1.606-.465 2.745-.534-.683-1.725-.848-3.233-.716-4.577.154-1.552.7-2.847 1.235-3.95.113-.235.223-.454.328-.664.149-.297.288-.577.419-.86.217-.47.379-.885.46-1.27.08-.38.08-.72-.014-1.043-.095-.325-.297-.675-.68-1.06a1.6 1.6 0 0 0-1.475.36l-4.95 4.452a1.602 1.602 0 0 0-.513.952l-.427 2.83c.672.59 2.328 2.316 3.335 4.711.09.21.175.43.253.653z"/>
15
+ </svg>`,
16
+ "ph:obsidian-color": `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">
17
+ <path fill="#7C3AED" d="m6.919 14.595c.64-.19 1.673-.484 2.86-.556-.712-1.797-.884-3.37-.746-4.77.16-1.616.73-2.966 1.286-4.114.118-.245.232-.473.342-.692.155-.31.3-.6.436-.895.227-.49.395-.922.48-1.324.084-.395.084-.748-.015-1.087-.099-.339-.31-.704-.71-1.104-.522-.135-1.101-.016-1.535.374L4.18 5.066c-.287.259-.476.61-.534.992l-.444 2.947c.699.618 2.424 2.414 3.473 4.906.094.223.182.45.264.684z"/>
18
+ <path fill="#6D28D9" d="m2.973 10.351c-.024.104-.058.205-.102.303l-2.725 6.073c-.28.624-.15 1.355.325 1.846l4.289 4.416c2.19-3.23 1.87-6.27.87-8.646-.758-1.8-1.908-3.21-2.657-3.992z"/>
19
+ <path fill="#8B5CF6" d="m5.75 23.51c.076.011.152.019.229.021.813.024 2.182.095 3.292.3.905.166 2.7.668 4.178 1.1 1.127.33 2.288-.57 2.452-1.733.119-.848.343-1.808.755-2.687l-.01.003c-.698-1.947-1.586-3.204-2.517-4.007-.929-.8-1.928-1.174-2.894-1.31-1.604-.225-3.075.197-4.001.469.555 2.31.384 5.03-1.484 7.844z"/>
20
+ <path fill="#A78BFA" d="m17.37 19.31c.927-1.398 1.587-2.486 1.936-3.076.174-.295.142-.664-.064-.938-.538-.713-1.567-2.16-2.127-3.502-.577-1.378-.663-3.518-.668-4.561-.002-.396-.127-.783-.372-1.095l-3.331-4.232c-.012.19-.04.38-.08.568-.11.524-.32 1.045-.558 1.56-.14.303-.301.626-.465.953-.108.218-.218.437-.322.652-.538 1.113-1.04 2.32-1.18 3.74-.13 1.314.048 2.844.849 4.67.133.012.267.027.402.046 1.149.161 2.355.611 3.465 1.568.954.823 1.816 2.001 2.515 3.646z"/>
21
+ </svg>`,
22
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stnd/icon",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./normalize.js",
package/resolve.server.js CHANGED
@@ -3,6 +3,8 @@ import * as path from "path";
3
3
  import * as fs from "fs/promises";
4
4
  import { getIconData, iconToSVG, iconToHTML, replaceIDs } from "@iconify/utils";
5
5
 
6
+ import { embeddedIcons } from "./embedded.js";
7
+
6
8
  let __filename = "";
7
9
  let __dirname = "";
8
10
  try {
@@ -28,8 +30,9 @@ export const usedIcons = new Set();
28
30
  *
29
31
  * Resolution order:
30
32
  * 1. In-memory cache (instant)
31
- * 2. Local SVG file in packages/icon/icons/{prefix}/{name}.svg (offline, curated library)
32
- * 3. Iconify API fetch (any icon set, last resort)
33
+ * 2. Embedded custom SVGs (offline in-memory, Cloudflare Workers SSR safe)
34
+ * 3. Local SVG file in packages/icon/icons/{prefix}/{name}.svg (offline, curated library)
35
+ * 4. Iconify API fetch (any icon set, last resort)
33
36
  *
34
37
  * @param {string} iconName Fully qualified icon name, e.g. "ph:house"
35
38
  * @param {{ width?: string, height?: string }} [attrs]
@@ -53,6 +56,14 @@ export async function resolveIcon(iconName, attrs = {}) {
53
56
  // Track every icon resolved — used by integration.js to copy only needed SVGs
54
57
  usedIcons.add(iconName);
55
58
 
59
+ // 1. In-memory embedded icons (works in Cloudflare Workers SSR where fs.readFile throws)
60
+ if (embeddedIcons[iconName]) {
61
+ const svg = applyDimensions(embeddedIcons[iconName], width, height);
62
+ cache.set(cacheKey, svg);
63
+ return svg;
64
+ }
65
+
66
+ // 2. Local SVG file on disk (Node.js dev & SSG build)
56
67
  let svg = await fromLocalFile(prefix, name, width, height);
57
68
  if (!svg) svg = await fromApi(prefix, name, width, height);
58
69
 
@@ -60,46 +71,47 @@ export async function resolveIcon(iconName, attrs = {}) {
60
71
  return svg;
61
72
  }
62
73
 
74
+ // ── SVG Dimension & ViewBox Normalizer ───────────────────────────────────────
75
+
76
+ function applyDimensions(rawSvg, width, height) {
77
+ let svg = rawSvg;
78
+
79
+ // Ensure SVG scales properly by injecting a viewBox if missing (case-insensitive)
80
+ if (!/viewBox=/i.test(svg)) {
81
+ const widthMatch = svg.match(
82
+ /<svg[^>]*\swidth\s*=\s*["']?([^"'\s>]+)["']?/i,
83
+ );
84
+ const heightMatch = svg.match(
85
+ /<svg[^>]*\sheight\s*=\s*["']?([^"'\s>]+)["']?/i,
86
+ );
87
+ if (widthMatch && heightMatch) {
88
+ const w = parseFloat(widthMatch[1]);
89
+ const h = parseFloat(heightMatch[1]);
90
+ if (!isNaN(w) && !isNaN(h)) {
91
+ svg = svg.replace(/<svg\b/i, (m) => `${m} viewBox="0 0 ${w} ${h}"`);
92
+ }
93
+ }
94
+ }
95
+
96
+ // Apply requested dimensions to the <svg> element
97
+ svg = svg.replace(/<svg([^>]*)>/i, (_, attrs) => {
98
+ let updated = attrs
99
+ .replace(/\s*width\s*=\s*["']?[^"'\s>]+["']?/i, "")
100
+ .replace(/\s*height\s*=\s*["']?[^"'\s>]+["']?/i, "");
101
+
102
+ updated = updated.replace(/\s+/g, " ").trim();
103
+ return `<svg${updated ? " " + updated : ""} width="${width}" height="${height}">`;
104
+ });
105
+ return svg;
106
+ }
107
+
63
108
  // ── Local SVG file resolver ──────────────────────────────────────────────────
64
109
 
65
110
  async function fromLocalFile(prefix, name, width, height) {
66
111
  const filePath = path.join(LOCAL_ICONS_DIR, prefix, `${name}.svg`);
67
112
  try {
68
- let svg = await fs.readFile(filePath, "utf8");
69
-
70
- // Ensure SVG scales properly by injecting a viewBox if missing (case-insensitive)
71
- // This handles files that may include XML prologs, comments, or different quote styles.
72
- if (!/viewBox=/i.test(svg)) {
73
- const widthMatch = svg.match(
74
- /<svg[^>]*\swidth\s*=\s*["']?([^"'\s>]+)["']?/i,
75
- );
76
- const heightMatch = svg.match(
77
- /<svg[^>]*\sheight\s*=\s*["']?([^"'\s>]+)["']?/i,
78
- );
79
- if (widthMatch && heightMatch) {
80
- const w = parseFloat(widthMatch[1]);
81
- const h = parseFloat(heightMatch[1]);
82
- if (!isNaN(w) && !isNaN(h)) {
83
- // Insert viewBox after the <svg tag name. Use a case-insensitive match so it works for varied files.
84
- svg = svg.replace(/<svg\b/i, (m) => `${m} viewBox="0 0 ${w} ${h}"`);
85
- }
86
- }
87
- }
88
-
89
- // Apply requested dimensions to the <svg> element
90
- // Use a non-anchored, case-insensitive match so we find the opening <svg> even if there
91
- // is an XML prolog or whitespace before it.
92
- svg = svg.replace(/<svg([^>]*)>/i, (_, attrs) => {
93
- // Replace or add width/height attributes (handle single or double quotes and optional spaces)
94
- let updated = attrs
95
- .replace(/\s*width\s*=\s*["']?[^"'\s>]+["']?/i, "")
96
- .replace(/\s*height\s*=\s*["']?[^"'\s>]+["']?/i, "");
97
-
98
- // Normalize spacing inside the tag to avoid accidental double spaces
99
- updated = updated.replace(/\s+/g, " ").trim();
100
- return `<svg${updated ? " " + updated : ""} width="${width}" height="${height}">`;
101
- });
102
- return svg;
113
+ const raw = await fs.readFile(filePath, "utf8");
114
+ return applyDimensions(raw, width, height);
103
115
  } catch {
104
116
  return null;
105
117
  }