@symbo.ls/scratch 3.8.8 → 3.14.0

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.
Files changed (76) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/defaultConfig/class.js +1 -2
  3. package/dist/cjs/defaultConfig/font-family.js +6 -6
  4. package/dist/cjs/defaultConfig/icons.js +2 -2
  5. package/dist/cjs/defaultConfig/svg.js +2 -2
  6. package/dist/cjs/defaultConfig/timing.js +1 -1
  7. package/dist/cjs/factory.js +72 -12
  8. package/dist/cjs/index.js +6 -4
  9. package/dist/cjs/set.js +113 -52
  10. package/dist/cjs/system/color.js +72 -12
  11. package/dist/cjs/system/document.js +3 -3
  12. package/dist/cjs/system/font.js +14 -14
  13. package/dist/cjs/system/reset.js +34 -7
  14. package/dist/cjs/system/shadow.js +4 -3
  15. package/dist/cjs/system/spacing.js +18 -18
  16. package/dist/cjs/system/svg.js +34 -7
  17. package/dist/cjs/system/theme.js +51 -50
  18. package/dist/cjs/system/timing.js +6 -6
  19. package/dist/cjs/system/typography.js +12 -3
  20. package/dist/cjs/transforms/index.js +4 -4
  21. package/dist/cjs/utils/color.js +1 -1
  22. package/dist/cjs/utils/font.js +3 -1
  23. package/dist/cjs/utils/sequence.js +35 -16
  24. package/dist/cjs/utils/sprite.js +11 -4
  25. package/dist/cjs/utils/var.js +23 -9
  26. package/dist/esm/defaultConfig/class.js +1 -2
  27. package/dist/esm/defaultConfig/font-family.js +6 -6
  28. package/dist/esm/defaultConfig/icons.js +2 -2
  29. package/dist/esm/defaultConfig/svg.js +2 -2
  30. package/dist/esm/defaultConfig/timing.js +1 -1
  31. package/dist/esm/factory.js +72 -12
  32. package/dist/esm/index.js +6 -4
  33. package/dist/esm/set.js +114 -53
  34. package/dist/esm/system/color.js +72 -12
  35. package/dist/esm/system/document.js +3 -3
  36. package/dist/esm/system/font.js +5 -5
  37. package/dist/esm/system/reset.js +34 -7
  38. package/dist/esm/system/shadow.js +4 -3
  39. package/dist/esm/system/spacing.js +3 -3
  40. package/dist/esm/system/svg.js +34 -7
  41. package/dist/esm/system/theme.js +51 -50
  42. package/dist/esm/system/timing.js +2 -2
  43. package/dist/esm/system/typography.js +12 -3
  44. package/dist/esm/transforms/index.js +4 -4
  45. package/dist/esm/utils/color.js +1 -1
  46. package/dist/esm/utils/font.js +3 -1
  47. package/dist/esm/utils/sequence.js +35 -16
  48. package/dist/esm/utils/sprite.js +11 -4
  49. package/dist/esm/utils/var.js +23 -9
  50. package/dist/iife/index.js +728 -302
  51. package/index.js +1 -0
  52. package/package.json +11 -14
  53. package/src/defaultConfig/class.js +2 -1
  54. package/src/defaultConfig/font-family.js +3 -3
  55. package/src/defaultConfig/icons.js +1 -1
  56. package/src/defaultConfig/svg.js +1 -1
  57. package/src/defaultConfig/timing.js +1 -1
  58. package/src/factory.js +85 -13
  59. package/src/index.js +16 -5
  60. package/src/set.js +156 -63
  61. package/src/system/color.js +113 -12
  62. package/src/system/document.js +3 -3
  63. package/src/system/font.js +5 -5
  64. package/src/system/reset.js +41 -8
  65. package/src/system/shadow.js +4 -3
  66. package/src/system/spacing.js +3 -3
  67. package/src/system/svg.js +44 -7
  68. package/src/system/theme.js +87 -64
  69. package/src/system/timing.js +2 -2
  70. package/src/system/typography.js +12 -3
  71. package/src/transforms/index.js +4 -4
  72. package/src/utils/color.js +2 -1
  73. package/src/utils/font.js +7 -1
  74. package/src/utils/sequence.js +46 -29
  75. package/src/utils/sprite.js +15 -4
  76. package/src/utils/var.js +27 -9
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- import { isObject, isArray, isString } from '@domql/utils'
3
+ import { isObject, isArray, isString } from '@symbo.ls/utils'
4
4
  import { getActiveConfig } from '../factory.js'
5
5
 
6
6
  import {
@@ -8,7 +8,7 @@ import {
8
8
  getRgbTone,
9
9
  isCSSVar,
10
10
  parseColorToken
11
- } from '../utils'
11
+ } from '../utils/index.js'
12
12
 
13
13
  export const getColor = (value, key, config) => {
14
14
  const CONFIG = config || getActiveConfig()
@@ -99,11 +99,21 @@ export const getMediaColor = (value, globalTheme, config) => {
99
99
 
100
100
  if (isObj && val.value) return getColor(value, `@${globalTheme}`, config)
101
101
  else if (isObj) {
102
+ // Prefer the adaptive var (--color-NAME) when available so runtime theme
103
+ // switches via [data-theme] actually take effect. Without this, setting
104
+ // `globalTheme: 'dark'` in config would lock every color to the dark
105
+ // scheme-specific var (--color-NAME-dark), which never responds to
106
+ // runtime theme toggles.
107
+ if (CONFIG.useVariable && val.var) return `var(${val.var})`
102
108
  if (globalTheme) return getColor(value, `@${globalTheme}`, config)
103
109
  else {
110
+ // Legacy fallback when adaptive var is unavailable (useVariable: false).
111
+ // Only @-prefixed keys are scheme variants — skip metadata like `var`.
104
112
  const obj = {}
105
113
  for (const mediaName in val) {
114
+ if (mediaName.charCodeAt(0) !== 64 /* @ */) continue
106
115
  const query = CONFIG.media[mediaName.slice(1)]
116
+ if (!query) continue
107
117
  const media = '@media ' + (query === 'print' ? `${query}` : `screen and ${query}`)
108
118
  obj[media] = getColor(value, mediaName, config)
109
119
  }
@@ -128,7 +138,16 @@ export const setColor = (val, key, suffix) => {
128
138
  )) {
129
139
  // Handle space-separated format: '--colorName alpha' (e.g. '--gray1 1')
130
140
  const parts = rawRef.split(' ')
131
- const refColor = CONFIG.color[parts[0]]
141
+ let refColor = CONFIG.color[parts[0]]
142
+ // Lazy resolution: composite colors (arrays/objects) often reference
143
+ // primitives by name. If iteration order processed the composite first,
144
+ // the primitive is still its raw input — process it now so this
145
+ // dereference works regardless of key order in CONFIG.color. setEach()
146
+ // also pre-sorts so primitives go first, but this is a safety net.
147
+ if (refColor && !refColor.value && !refColor.rgb && (isString(refColor) || isArray(refColor) || isObject(refColor))) {
148
+ CONFIG.color[parts[0]] = setColor(refColor, parts[0])
149
+ refColor = CONFIG.color[parts[0]]
150
+ }
132
151
  if (refColor && refColor.value) {
133
152
  let rgb = refColor.rgb
134
153
  const alpha = parts[1] !== undefined ? parts[1] : '1'
@@ -160,11 +179,10 @@ export const setColor = (val, key, suffix) => {
160
179
  }
161
180
  }
162
181
 
182
+ // Normalize array shorthand `[lightVal, darkVal]` to scheme-keyed object
183
+ // so the multi-scheme branch below handles both cases uniformly.
163
184
  if (isArray(val)) {
164
- return {
165
- '@light': setColor(val[0], key, 'light'),
166
- '@dark': setColor(val[1], key, 'dark')
167
- }
185
+ val = { '@light': val[0], '@dark': val[1] }
168
186
  }
169
187
 
170
188
  if (isObject(val)) {
@@ -176,17 +194,99 @@ export const setColor = (val, key, suffix) => {
176
194
  variant.slice(0, 1) === '@' ? variant.slice(1) : variant
177
195
  )
178
196
  }
197
+
198
+ // Generate a single adaptive `--color-${key}` CSS var so consumers can
199
+ // write `border-color: var(--color-${key})` and have it switch via
200
+ // `@media (prefers-color-scheme)` and `[data-theme]` automatically.
201
+ // Scheme-agnostic: works for @light/@dark plus arbitrary custom schemes
202
+ // (@ocean, @sunset, etc). Mirrors generateAutoVars() in system/theme.js.
203
+ //
204
+ // Without this, getMediaColor() falls into the @media-keyed-object branch
205
+ // (when globalTheme === 'auto'), which the CSS engine cannot serialize as
206
+ // a single CSS property value — emitting broken rules like
207
+ // `.classname borderrightcolor { }`.
208
+ if (CONFIG.useVariable) {
209
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
210
+ const adaptiveVar = `--${vp}color-${key}`
211
+ let fallbackValue
212
+ const schemeValues = {}
213
+
214
+ for (const variant in obj) {
215
+ if (variant.charCodeAt(0) !== 64 /* @ */) continue
216
+ const scheme = variant.slice(1)
217
+ const entry = obj[variant]
218
+ const value = entry && (entry.value || entry)
219
+ if (!value || typeof value !== 'string') continue
220
+ schemeValues[scheme] = value
221
+ // Prefer 'light' as the :root fallback; otherwise first resolved scheme.
222
+ if (scheme === 'light' || fallbackValue === undefined) fallbackValue = value
223
+ }
224
+
225
+ if (fallbackValue !== undefined) {
226
+ CONFIG.cssVars[adaptiveVar] = fallbackValue
227
+
228
+ if (!CONFIG.cssMediaVars) CONFIG.cssMediaVars = {}
229
+ for (const scheme in schemeValues) {
230
+ // [data-theme] selector — works for ANY scheme (including custom ones)
231
+ const sel = `[data-theme="${scheme}"]`
232
+ if (!CONFIG.cssMediaVars[sel]) CONFIG.cssMediaVars[sel] = {}
233
+ CONFIG.cssMediaVars[sel][adaptiveVar] = schemeValues[scheme]
234
+
235
+ // prefers-color-scheme media query — only browsers know light/dark
236
+ if (scheme === 'light' || scheme === 'dark') {
237
+ const mq = `@media (prefers-color-scheme: ${scheme})`
238
+ if (!CONFIG.cssMediaVars[mq]) CONFIG.cssMediaVars[mq] = {}
239
+ CONFIG.cssMediaVars[mq][adaptiveVar] = schemeValues[scheme]
240
+ }
241
+ }
242
+
243
+ // Expose the adaptive var on the wrapper so getColor() returns it
244
+ // directly when called without an explicit @scheme key.
245
+ obj.var = adaptiveVar
246
+ }
247
+ }
248
+
179
249
  return obj
180
250
  }
181
251
 
182
- const CSSVar = `--color-${key}` + (suffix ? `-${suffix}` : '')
183
- const colorArr = colorStringToRgbaArray(val.value || val)
252
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
253
+ const CSSVar = `--${vp}color-${key}` + (suffix ? `-${suffix}` : '')
254
+
255
+ // Resolve dot notation: 'green.08' → take green color at 8% opacity
256
+ let resolvedVal = val.value || val
257
+ if (isString(resolvedVal) && resolvedVal.includes('.') && !resolvedVal.includes('(')) {
258
+ const [colorRef, alphaStr] = resolvedVal.split('.')
259
+ const refColor = CONFIG.color[colorRef]
260
+ if (refColor && refColor.rgb) {
261
+ resolvedVal = `rgba(${refColor.rgb}, ${parseFloat('0.' + alphaStr) || 1})`
262
+ }
263
+ }
264
+
265
+ // Resolve token notation: 'gray-168' / 'gray+50' / 'gray=90' → apply tone to base
266
+ if (isString(resolvedVal) && !resolvedVal.includes('(') && !resolvedVal.startsWith('#')) {
267
+ const parsed = parseColorToken(resolvedVal)
268
+ if (parsed && parsed.name && !parsed.passthrough && !parsed.cssVar) {
269
+ const refColor = CONFIG.color[parsed.name]
270
+ if (refColor && !refColor.value && !refColor.rgb && (isString(refColor) || isArray(refColor) || isObject(refColor))) {
271
+ CONFIG.color[parsed.name] = setColor(refColor, parsed.name)
272
+ }
273
+ const baseColor = CONFIG.color[parsed.name]
274
+ if (baseColor && baseColor.rgb) {
275
+ let rgb = baseColor.rgb
276
+ if (parsed.tone) rgb = getRgbTone(rgb, parsed.tone)
277
+ const alphaVal = parsed.alpha ? parseFloat(parsed.alpha) : 1
278
+ resolvedVal = `rgba(${rgb}, ${alphaVal})`
279
+ }
280
+ }
281
+ }
282
+
283
+ const colorArr = colorStringToRgbaArray(resolvedVal)
184
284
  const [r, g, b, a = 1] = colorArr
185
285
  const alpha = parseFloat(a.toFixed(2))
186
286
  const rgb = `${r}, ${g}, ${b}`
187
287
  const value = `rgba(${rgb}, ${alpha})`
188
288
 
189
- if (CONFIG.useVariable) { CONFIG.CSS_VARS[CSSVar] = value }
289
+ if (CONFIG.useVariable) { CONFIG.cssVars[CSSVar] = value }
190
290
 
191
291
  return {
192
292
  var: CSSVar,
@@ -213,10 +313,11 @@ export const setGradient = (val, key, suffix) => {
213
313
  return obj
214
314
  }
215
315
 
216
- const CSSVar = `--gradient-${key}` + (suffix ? `-${suffix}` : '')
316
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
317
+ const CSSVar = `--${vp}gradient-${key}` + (suffix ? `-${suffix}` : '')
217
318
 
218
319
  if (CONFIG.useVariable) {
219
- CONFIG.CSS_VARS[CSSVar] = val.value || val
320
+ CONFIG.cssVars[CSSVar] = val.value || val
220
321
  }
221
322
 
222
323
  return {
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
- import { merge } from '@domql/utils'
3
+ import { merge } from '@symbo.ls/utils'
4
4
  import { getActiveConfig } from '../factory.js'
5
- import { getDefaultOrFirstKey } from '../utils'
5
+ import { getDefaultOrFirstKey } from '../utils/index.js'
6
6
 
7
7
  export const applyDocument = () => {
8
8
  const CONFIG = getActiveConfig()
9
- const { document: DOCUMENT, font_family: FONT_FAMILY, theme: THEME, typography: TYPOGRAPHY } = CONFIG
9
+ const { document: DOCUMENT, fontFamily: FONT_FAMILY, theme: THEME, typography: TYPOGRAPHY } = CONFIG
10
10
  return merge(DOCUMENT, {
11
11
  theme: THEME.document,
12
12
  fontFamily: getDefaultOrFirstKey(FONT_FAMILY),
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- import { isObject, isArray } from '@domql/utils'
4
- import { arrayzeValue } from '@symbo.ls/smbls-utils'
3
+ import { isObject, isArray } from '@symbo.ls/utils'
4
+ import { arrayzeValue } from '@symbo.ls/utils'
5
5
  import { getActiveConfig } from '../factory.js'
6
6
 
7
7
  import {
@@ -11,7 +11,7 @@ import {
11
11
  setCustomFontMedia,
12
12
  setFontImport,
13
13
  resolveFileUrl
14
- } from '../utils'
14
+ } from '../utils/index.js'
15
15
 
16
16
  export const setFont = (val, key) => {
17
17
  const CONFIG = getActiveConfig()
@@ -48,13 +48,13 @@ export const setFont = (val, key) => {
48
48
 
49
49
  export const getFontFamily = (key, factory) => {
50
50
  const CONFIG = getActiveConfig()
51
- const { font_family: FONT_FAMILY } = CONFIG
51
+ const { fontFamily: FONT_FAMILY } = CONFIG
52
52
  return getDefaultOrFirstKey(factory || FONT_FAMILY, key)
53
53
  }
54
54
 
55
55
  export const setFontFamily = (val, key) => {
56
56
  const CONFIG = getActiveConfig()
57
- const { font_family: FONT_FAMILY, font_family_types: FONT_FAMILY_TYPES } = CONFIG
57
+ const { fontFamily: FONT_FAMILY, fontFamilyTypes: FONT_FAMILY_TYPES } = CONFIG
58
58
  let { value, type } = val
59
59
  if (val.isDefault) FONT_FAMILY.default = key
60
60
 
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- import { deepMerge, merge, overwriteDeep } from '@domql/utils'
3
+ import { deepMerge, merge, overwriteDeep } from '@symbo.ls/utils'
4
4
  import { getActiveConfig } from '../factory.js'
5
5
  import { getMediaTheme } from './theme.js'
6
6
 
@@ -34,10 +34,31 @@ export const applyReset = (reset = {}) => {
34
34
  : {}
35
35
  if (RESET.html) overwriteDeep(RESET.html, globalTheme)
36
36
 
37
+ // Generate html fontSize overrides for typography media breakpoints
38
+ if (TYPOGRAPHY.unit) {
39
+ const { media: MEDIA } = CONFIG
40
+ for (const key in TYPOGRAPHY) {
41
+ if (key.charAt(0) !== '@') continue
42
+ const mediaTypo = TYPOGRAPHY[key]
43
+ if (!mediaTypo) continue
44
+ // Only override html fontSize if this media explicitly changes the unit
45
+ if (mediaTypo.unit === TYPOGRAPHY.unit || !mediaTypo.unit) continue
46
+ const mediaUnit = mediaTypo.unit
47
+ const mediaBase = mediaTypo.base || TYPOGRAPHY.base
48
+ const mediaBrowserDefault = mediaTypo.browserDefault || TYPOGRAPHY.browserDefault
49
+ const mediaName = key.slice(1)
50
+ const query = MEDIA[mediaName]
51
+ if (!query) continue
52
+ const mediaKey = '@media ' + (query === 'print' ? query : 'screen and ' + query)
53
+ if (!RESET[mediaKey]) RESET[mediaKey] = {}
54
+ if (!RESET[mediaKey].html) RESET[mediaKey].html = {}
55
+ RESET[mediaKey].html.fontSize = (mediaBase / mediaBrowserDefault) + mediaUnit
56
+ }
57
+ }
58
+
37
59
  return deepMerge(merge(RESET, reset), {
38
60
  html: {
39
61
  position: 'absolute',
40
- // overflow: 'hidden',
41
62
  width: '100%',
42
63
  height: '100%',
43
64
  top: '0',
@@ -48,7 +69,9 @@ export const applyReset = (reset = {}) => {
48
69
 
49
70
  ...globalTheme,
50
71
 
51
- fontSize: TYPOGRAPHY.browserDefault + 'px',
72
+ fontSize: TYPOGRAPHY.unit
73
+ ? (TYPOGRAPHY.base / TYPOGRAPHY.browserDefault) + TYPOGRAPHY.unit
74
+ : TYPOGRAPHY.browserDefault + 'px',
52
75
 
53
76
  fontFamily: DOCUMENT.fontFamily,
54
77
  lineHeight: DOCUMENT.lineHeight
@@ -71,15 +94,25 @@ export const applyReset = (reset = {}) => {
71
94
  color: 'currentColor'
72
95
  },
73
96
 
74
- // form elements
97
+ button: {
98
+ color: 'inherit',
99
+ font: 'inherit',
100
+ background: 'transparent',
101
+ border: 'none',
102
+ cursor: 'pointer',
103
+ appearance: 'none',
104
+ WebkitAppearance: 'none'
105
+ },
106
+
107
+ 'input, select, textarea': {
108
+ color: 'inherit',
109
+ font: 'inherit'
110
+ },
111
+
75
112
  fieldset: {
76
113
  border: 0,
77
114
  padding: 0,
78
115
  margin: 0
79
- },
80
-
81
- 'select, input': {
82
- fontFamily: DOCUMENT.fontFamily
83
116
  }
84
117
  })
85
118
  }
@@ -9,7 +9,7 @@ import {
9
9
  isObject,
10
10
  isString,
11
11
  isArray
12
- } from '@domql/utils'
12
+ } from '@symbo.ls/utils'
13
13
 
14
14
  export const setShadow = (value, key, suffix, prefers) => {
15
15
  const CONFIG = getActiveConfig()
@@ -53,9 +53,10 @@ export const setShadow = (value, key, suffix, prefers) => {
53
53
  }).join(', ')
54
54
  }
55
55
 
56
- const CSSVar = `--shadow-${key}` + (suffix ? `-${suffix}` : '')
56
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
57
+ const CSSVar = `--${vp}shadow-${key}` + (suffix ? `-${suffix}` : '')
57
58
 
58
- if (CONFIG.useVariable) { CONFIG.CSS_VARS[CSSVar] = value }
59
+ if (CONFIG.useVariable) { CONFIG.cssVars[CSSVar] = value }
59
60
 
60
61
  return {
61
62
  var: CSSVar,
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- import { arrayzeValue } from '@symbo.ls/smbls-utils'
4
- import { isArray, isString, merge } from '@domql/utils'
3
+ import { arrayzeValue } from '@symbo.ls/utils'
4
+ import { isArray, isString, merge } from '@symbo.ls/utils'
5
5
 
6
6
  import { getActiveConfig } from '../factory.js'
7
7
  import {
@@ -11,7 +11,7 @@ import {
11
11
  generateSequence,
12
12
  getFnPrefixAndValue,
13
13
  getSequenceValuePropertyPair
14
- } from '../utils'
14
+ } from '../utils/index.js'
15
15
 
16
16
  const runThroughMedia = (FACTORY) => {
17
17
  for (const prop in FACTORY) {
package/src/system/svg.js CHANGED
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- import { document } from '@domql/utils'
4
- import { generateSprite, convertSvgToSymbol } from '../utils'
3
+ import { document } from '@symbo.ls/utils'
4
+ import { generateSprite, convertSvgToSymbol } from '../utils/index.js'
5
5
  import { getActiveConfig } from '../factory.js'
6
6
 
7
7
  const DEF_OPTIONS = {
@@ -23,14 +23,14 @@ export const appendSVGSprite = (LIBRARY, options = DEF_OPTIONS) => {
23
23
  const CONFIG = getActiveConfig()
24
24
 
25
25
  const lib = Object.keys(LIBRARY).length ? {} : CONFIG.svg
26
- for (const key in LIBRARY) lib[key] = CONFIG.svg[key]
26
+ for (const key in LIBRARY) lib[key] = LIBRARY[key]
27
27
 
28
28
  appendSVG(lib, options)
29
29
  }
30
30
 
31
31
  export const setSvgIcon = (val, key) => {
32
32
  const CONFIG = getActiveConfig()
33
- if (CONFIG.useIconSprite && !CONFIG.semantic_icons?.[key]) {
33
+ if (CONFIG.useIconSprite && !CONFIG.semanticIcons?.[key]) {
34
34
  return setSVG(val, key)
35
35
  } return val
36
36
  }
@@ -39,7 +39,7 @@ export const appendSvgIconsSprite = (LIBRARY, options = DEF_OPTIONS) => {
39
39
  const CONFIG = getActiveConfig()
40
40
 
41
41
  const lib = Object.keys(LIBRARY).length ? {} : CONFIG.icons
42
- for (const key in LIBRARY) lib[key] = CONFIG.icons[key]
42
+ for (const key in LIBRARY) lib[key] = LIBRARY[key]
43
43
 
44
44
  appendSVG(lib, options)
45
45
  }
@@ -57,6 +57,32 @@ const createSVGSpriteElement = (doc, options = { isRoot: true }) => {
57
57
  return svgElem
58
58
  }
59
59
 
60
+ // Parse SVG sprite string using XML parser to correctly handle self-closing tags.
61
+ // HTML parser treats <line .../> as <line ...> (ignores /), nesting siblings.
62
+ const parseSVGSprite = (doc, svgString) => {
63
+ const DOMParserCtor = (typeof DOMParser !== 'undefined') ? DOMParser : null
64
+ if (DOMParserCtor) {
65
+ const wrapped = `<svg xmlns="http://www.w3.org/2000/svg">${svgString}</svg>`
66
+ const parser = new DOMParserCtor()
67
+ const parsed = parser.parseFromString(wrapped, 'image/svg+xml')
68
+ // Check for parse errors — if the SVG is malformed, fall back to innerHTML
69
+ if (parsed.querySelector('parsererror')) return null
70
+ return parsed.documentElement
71
+ }
72
+ return null
73
+ }
74
+
75
+ // Move all children from a parsed SVG element into a target element.
76
+ // Uses importNode to cross document boundaries, then removes the source child
77
+ // so the loop terminates.
78
+ const moveChildren = (doc, from, to) => {
79
+ while (from.firstChild) {
80
+ const child = from.firstChild
81
+ to.appendChild(doc.importNode(child, true))
82
+ child.remove()
83
+ }
84
+ }
85
+
60
86
  const appendSVG = (lib, options = DEF_OPTIONS) => {
61
87
  const CONFIG = getActiveConfig()
62
88
  const doc = options.document || document
@@ -73,8 +99,12 @@ const appendSVG = (lib, options = DEF_OPTIONS) => {
73
99
 
74
100
  const spriteHtml = `<svg aria-hidden="true" width="0" height="0" style="position:absolute" id="svgSprite">${SVGsprite}</svg>`
75
101
 
102
+ const parsed = parseSVGSprite(doc, SVGsprite)
103
+
76
104
  if (exists) {
77
- if (doc.body.insertAdjacentHTML) {
105
+ if (parsed) {
106
+ moveChildren(doc, parsed, exists)
107
+ } else if (doc.body.insertAdjacentHTML) {
78
108
  exists.insertAdjacentHTML('beforeend', SVGsprite)
79
109
  } else {
80
110
  const tempSVG = createSVGSpriteElement(doc, { isRoot: false })
@@ -82,7 +112,13 @@ const appendSVG = (lib, options = DEF_OPTIONS) => {
82
112
  exists.append(...tempSVG.children)
83
113
  }
84
114
  } else {
85
- if (doc.body.insertAdjacentHTML) {
115
+ if (parsed) {
116
+ const svgSpriteDOM = createSVGSpriteElement(doc)
117
+ if (svgSpriteDOM && svgSpriteDOM.nodeType) {
118
+ moveChildren(doc, parsed, svgSpriteDOM)
119
+ doc.body.prepend(svgSpriteDOM)
120
+ }
121
+ } else if (doc.body.insertAdjacentHTML) {
86
122
  doc.body.insertAdjacentHTML('afterbegin', spriteHtml)
87
123
  } else {
88
124
  const svgSpriteDOM = createSVGSpriteElement(doc)
@@ -92,4 +128,5 @@ const appendSVG = (lib, options = DEF_OPTIONS) => {
92
128
  }
93
129
  }
94
130
  }
131
+
95
132
  }