@uniweb/kit 0.4.2 → 0.4.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniweb/kit",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "Standard component library for Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -39,7 +39,7 @@
39
39
  "fuse.js": "^7.0.0",
40
40
  "shiki": "^3.0.0",
41
41
  "tailwind-merge": "^2.6.0",
42
- "@uniweb/core": "0.3.2"
42
+ "@uniweb/core": "0.3.4"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": "^18.0.0 || ^19.0.0",
@@ -38,32 +38,42 @@ const BUILT_IN_ICONS = {
38
38
  * @param {string} svgContent - Raw SVG string
39
39
  * @returns {Object} { viewBox, content, width, height }
40
40
  */
41
+ /**
42
+ * Extract an attribute value from an SVG opening tag string
43
+ */
44
+ function getAttr(svgTag, name) {
45
+ const match = svgTag.match(new RegExp(`${name}="([^"]*)"`, 'i'))
46
+ return match ? match[1] : null
47
+ }
48
+
41
49
  function parseSvg(svgContent) {
42
50
  if (!svgContent) return null
43
51
 
44
52
  try {
45
- const parser = new DOMParser()
46
- const doc = parser.parseFromString(svgContent, 'image/svg+xml')
47
- const svg = doc.querySelector('svg')
53
+ // Extract the <svg ...> opening tag and inner content
54
+ // Works in both browser and Node.js (no DOMParser needed)
55
+ const svgTagMatch = svgContent.match(/<svg\s[^>]*>/i)
56
+ if (!svgTagMatch) return null
48
57
 
49
- if (!svg) return null
58
+ const svgTag = svgTagMatch[0]
50
59
 
51
- const viewBox = svg.getAttribute('viewBox') || '0 0 24 24'
52
- const width = svg.getAttribute('width')
53
- const height = svg.getAttribute('height')
60
+ const viewBox = getAttr(svgTag, 'viewBox') || '0 0 24 24'
61
+ const width = getAttr(svgTag, 'width')
62
+ const height = getAttr(svgTag, 'height')
54
63
 
55
64
  // Preserve SVG presentation attributes from the source
56
65
  // Different icon families use different rendering styles:
57
66
  // - Lucide, Feather, Heroicons: stroke-based (fill="none", stroke="currentColor")
58
67
  // - Font Awesome, Bootstrap: fill-based (fill="currentColor")
59
- const fill = svg.getAttribute('fill')
60
- const stroke = svg.getAttribute('stroke')
61
- const strokeWidth = svg.getAttribute('stroke-width')
62
- const strokeLinecap = svg.getAttribute('stroke-linecap')
63
- const strokeLinejoin = svg.getAttribute('stroke-linejoin')
68
+ const fill = getAttr(svgTag, 'fill')
69
+ const stroke = getAttr(svgTag, 'stroke')
70
+ const strokeWidth = getAttr(svgTag, 'stroke-width')
71
+ const strokeLinecap = getAttr(svgTag, 'stroke-linecap')
72
+ const strokeLinejoin = getAttr(svgTag, 'stroke-linejoin')
64
73
 
65
- // Get inner content
66
- const content = svg.innerHTML
74
+ // Get inner content (everything between <svg> and </svg>)
75
+ const innerMatch = svgContent.match(/<svg\s[^>]*>([\s\S]*)<\/svg>/i)
76
+ const content = innerMatch ? innerMatch[1] : ''
67
77
 
68
78
  return {
69
79
  viewBox, content, width, height,
@@ -128,25 +138,37 @@ export function Icon({
128
138
  errorComponent,
129
139
  ...props
130
140
  }) {
131
- const [fetchedSvg, setFetchedSvg] = useState(null)
132
- const [loading, setLoading] = useState(false)
133
- const [error, setError] = useState(false)
134
-
135
141
  // Normalize props (handle legacy icon object)
136
142
  const iconLibrary = library || (typeof icon === 'object' ? icon.library : null)
137
143
  const iconUrl = url || (typeof icon === 'string' ? icon : icon?.url)
138
144
  const iconSvg = svg || (typeof icon === 'object' ? icon.svg : null)
139
145
  const iconName = name || (typeof icon === 'object' ? icon.name : null)
140
146
 
141
- // Fetch SVG from URL or resolve from library
147
+ // Check sync cache for SSR (pre-populated by prerender)
148
+ const cachedSvg = useMemo(() => {
149
+ if (iconLibrary && iconName) {
150
+ const uniweb = getUniweb()
151
+ return uniweb?.getIconSync?.(iconLibrary, iconName) || null
152
+ }
153
+ return null
154
+ }, [iconLibrary, iconName])
155
+
156
+ const [fetchedSvg, setFetchedSvg] = useState(cachedSvg)
157
+ const [loading, setLoading] = useState(false)
158
+ const [error, setError] = useState(false)
159
+
160
+ // Fetch SVG from URL or resolve from library (client-side only)
142
161
  useEffect(() => {
162
+ // Already have SVG from cache or direct prop
163
+ if (cachedSvg || iconSvg) {
164
+ setFetchedSvg(cachedSvg)
165
+ return
166
+ }
167
+
143
168
  // Reset state when source changes
144
169
  setFetchedSvg(null)
145
170
  setError(false)
146
171
 
147
- // Direct SVG - no fetch needed
148
- if (iconSvg) return
149
-
150
172
  // URL-based fetch
151
173
  if (iconUrl) {
152
174
  setLoading(true)
@@ -191,7 +213,7 @@ export function Icon({
191
213
  })
192
214
  }
193
215
  }
194
- }, [iconUrl, iconSvg, iconLibrary, iconName])
216
+ }, [iconUrl, iconSvg, iconLibrary, iconName, cachedSvg])
195
217
 
196
218
  // Determine the SVG content to render
197
219
  const svgData = useMemo(() => {