@symbo.ls/scratch 3.8.9 → 3.14.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.
Files changed (119) hide show
  1. package/README.md +2 -2
  2. package/dist/cjs/defaultConfig/animation.js +4 -2
  3. package/dist/cjs/defaultConfig/cases.js +4 -2
  4. package/dist/cjs/defaultConfig/class.js +4 -2
  5. package/dist/cjs/defaultConfig/color.js +6 -9
  6. package/dist/cjs/defaultConfig/document.js +4 -2
  7. package/dist/cjs/defaultConfig/font-family.js +8 -12
  8. package/dist/cjs/defaultConfig/font.js +4 -11
  9. package/dist/cjs/defaultConfig/grid.js +5 -3
  10. package/dist/cjs/defaultConfig/icons.js +6 -4
  11. package/dist/cjs/defaultConfig/index.js +200 -24
  12. package/dist/cjs/defaultConfig/media.js +4 -2
  13. package/dist/cjs/defaultConfig/responsive.js +6 -4
  14. package/dist/cjs/defaultConfig/sequence.js +4 -2
  15. package/dist/cjs/defaultConfig/shadow.js +4 -2
  16. package/dist/cjs/defaultConfig/spacing.js +56 -7
  17. package/dist/cjs/defaultConfig/svg.js +6 -4
  18. package/dist/cjs/defaultConfig/templates.js +4 -2
  19. package/dist/cjs/defaultConfig/theme.js +4 -14
  20. package/dist/cjs/defaultConfig/timing.js +36 -5
  21. package/dist/cjs/defaultConfig/typography.js +36 -5
  22. package/dist/cjs/defaultConfig/unit.js +4 -2
  23. package/dist/cjs/factory.js +341 -40
  24. package/dist/cjs/index.js +6333 -11
  25. package/dist/cjs/package.json +4 -0
  26. package/dist/cjs/set.js +5614 -158
  27. package/dist/cjs/system/color.js +4481 -104
  28. package/dist/cjs/system/document.js +4371 -11
  29. package/dist/cjs/system/font.js +4401 -28
  30. package/dist/cjs/system/index.js +5748 -11
  31. package/dist/cjs/system/reset.js +4445 -21
  32. package/dist/cjs/system/shadow.js +4832 -41
  33. package/dist/cjs/system/spacing.js +4752 -39
  34. package/dist/cjs/system/svg.js +4437 -47
  35. package/dist/cjs/system/theme.js +4526 -335
  36. package/dist/cjs/system/timing.js +4695 -19
  37. package/dist/cjs/system/typography.js +4755 -33
  38. package/dist/cjs/tests/index.js +4 -2
  39. package/dist/cjs/transforms/index.js +5019 -134
  40. package/dist/cjs/utils/color.js +47 -66
  41. package/dist/cjs/utils/font.js +25 -46
  42. package/dist/cjs/utils/index.js +5068 -8
  43. package/dist/cjs/utils/sequence.js +4423 -35
  44. package/dist/cjs/utils/sprite.js +353 -12
  45. package/dist/cjs/utils/theme.js +3 -1
  46. package/dist/cjs/utils/unit.js +4 -2
  47. package/dist/cjs/utils/var.js +4390 -42
  48. package/index.js +1 -0
  49. package/package.json +11 -14
  50. package/src/defaultConfig/class.js +2 -1
  51. package/src/defaultConfig/font-family.js +3 -3
  52. package/src/defaultConfig/icons.js +1 -1
  53. package/src/defaultConfig/svg.js +1 -1
  54. package/src/defaultConfig/timing.js +1 -1
  55. package/src/factory.js +85 -13
  56. package/src/index.js +16 -5
  57. package/src/set.js +156 -63
  58. package/src/system/color.js +113 -12
  59. package/src/system/document.js +3 -3
  60. package/src/system/font.js +5 -5
  61. package/src/system/reset.js +41 -8
  62. package/src/system/shadow.js +4 -3
  63. package/src/system/spacing.js +3 -3
  64. package/src/system/svg.js +42 -5
  65. package/src/system/theme.js +87 -64
  66. package/src/system/timing.js +2 -2
  67. package/src/system/typography.js +12 -3
  68. package/src/transforms/index.js +4 -4
  69. package/src/utils/color.js +2 -1
  70. package/src/utils/font.js +7 -1
  71. package/src/utils/sequence.js +46 -29
  72. package/src/utils/sprite.js +44 -16
  73. package/src/utils/var.js +27 -9
  74. package/dist/esm/defaultConfig/animation.js +0 -4
  75. package/dist/esm/defaultConfig/cases.js +0 -4
  76. package/dist/esm/defaultConfig/class.js +0 -5
  77. package/dist/esm/defaultConfig/color.js +0 -11
  78. package/dist/esm/defaultConfig/document.js +0 -4
  79. package/dist/esm/defaultConfig/font-family.js +0 -18
  80. package/dist/esm/defaultConfig/font.js +0 -13
  81. package/dist/esm/defaultConfig/grid.js +0 -5
  82. package/dist/esm/defaultConfig/icons.js +0 -6
  83. package/dist/esm/defaultConfig/index.js +0 -25
  84. package/dist/esm/defaultConfig/media.js +0 -9
  85. package/dist/esm/defaultConfig/responsive.js +0 -30
  86. package/dist/esm/defaultConfig/sequence.js +0 -29
  87. package/dist/esm/defaultConfig/shadow.js +0 -4
  88. package/dist/esm/defaultConfig/spacing.js +0 -18
  89. package/dist/esm/defaultConfig/svg.js +0 -6
  90. package/dist/esm/defaultConfig/templates.js +0 -4
  91. package/dist/esm/defaultConfig/theme.js +0 -16
  92. package/dist/esm/defaultConfig/timing.js +0 -17
  93. package/dist/esm/defaultConfig/typography.js +0 -21
  94. package/dist/esm/defaultConfig/unit.js +0 -6
  95. package/dist/esm/factory.js +0 -60
  96. package/dist/esm/index.js +0 -12
  97. package/dist/esm/set.js +0 -219
  98. package/dist/esm/system/color.js +0 -193
  99. package/dist/esm/system/document.js +0 -16
  100. package/dist/esm/system/font.js +0 -58
  101. package/dist/esm/system/index.js +0 -10
  102. package/dist/esm/system/reset.js +0 -67
  103. package/dist/esm/system/shadow.js +0 -91
  104. package/dist/esm/system/spacing.js +0 -121
  105. package/dist/esm/system/svg.js +0 -86
  106. package/dist/esm/system/theme.js +0 -480
  107. package/dist/esm/system/timing.js +0 -32
  108. package/dist/esm/system/typography.js +0 -85
  109. package/dist/esm/tests/index.js +0 -8
  110. package/dist/esm/transforms/index.js +0 -216
  111. package/dist/esm/utils/color.js +0 -192
  112. package/dist/esm/utils/font.js +0 -92
  113. package/dist/esm/utils/index.js +0 -7
  114. package/dist/esm/utils/sequence.js +0 -303
  115. package/dist/esm/utils/sprite.js +0 -65
  116. package/dist/esm/utils/theme.js +0 -9
  117. package/dist/esm/utils/unit.js +0 -59
  118. package/dist/esm/utils/var.js +0 -82
  119. package/dist/iife/index.js +0 -3204
@@ -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 = {
@@ -30,7 +30,7 @@ export const appendSVGSprite = (LIBRARY, options = DEF_OPTIONS) => {
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
  }
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- import { getColor } from './color'
3
+ import { getColor } from './color.js'
4
4
  import { getActiveConfig } from '../factory.js'
5
5
  import { isCSSVar } from '../utils/color.js'
6
6
 
@@ -9,7 +9,7 @@ import {
9
9
  isString,
10
10
  isObjectLike,
11
11
  isArray
12
- } from '@domql/utils'
12
+ } from '@symbo.ls/utils'
13
13
 
14
14
  // Common CSS named colors — used to distinguish valid CSS color names
15
15
  // from unresolved design-system tokens (e.g. 'blackRussian', 'grayMid')
@@ -43,6 +43,20 @@ const CSS_NAMED_COLORS = new Set([
43
43
  'whitesmoke', 'yellowgreen', 'rebeccapurple'
44
44
  ])
45
45
 
46
+ // A theme param is treated as a color-producing property when its name
47
+ // contains one of these substrings. Mirrors the check in `setThemeValue`
48
+ // so the broken-scheme pre-scan skips non-color props like `borderStyle`,
49
+ // `boxShadow`, `backdropFilter`, `cursor`, etc. that would otherwise trip
50
+ // the `/^[a-z][a-zA-Z]+$/` heuristic with values like 'solid' or 'pointer'.
51
+ const COLOR_PARAM_TOKENS = ['color', 'Color', 'background', 'Background', 'fill', 'Fill', 'stroke', 'Stroke']
52
+
53
+ const isColorParam = (param) => {
54
+ for (let i = 0; i < COLOR_PARAM_TOKENS.length; i++) {
55
+ if (param.includes(COLOR_PARAM_TOKENS[i])) return true
56
+ }
57
+ return false
58
+ }
59
+
46
60
  const setThemeValue = theme => {
47
61
  const value = {}
48
62
  const { state, media, helpers, ...rest } = theme
@@ -159,7 +173,8 @@ export const setTheme = (val, key) => {
159
173
 
160
174
  const { state, media, helpers } = val
161
175
  const value = setThemeValue(val, key)
162
- const CSSvar = `--theme-${key}`
176
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
177
+ const CSSvar = `--${vp}theme-${key}`
163
178
 
164
179
  setSelectors(val, value)
165
180
  setMedia(val, value)
@@ -185,9 +200,9 @@ const keySetters = { // eslint-disable-line
185
200
  * @param {Object} CONFIG - active config
186
201
  */
187
202
  const generateAutoVars = (schemes, varPrefix, CONFIG) => {
188
- const { CSS_VARS } = CONFIG
189
- if (!CONFIG.CSS_MEDIA_VARS) CONFIG.CSS_MEDIA_VARS = {}
190
- const MEDIA_VARS = CONFIG.CSS_MEDIA_VARS
203
+ const { cssVars: CSS_VARS } = CONFIG
204
+ if (!CONFIG.cssMediaVars) CONFIG.cssMediaVars = {}
205
+ const MEDIA_VARS = CONFIG.cssMediaVars
191
206
  const globalTheme = CONFIG.globalTheme !== undefined ? CONFIG.globalTheme : 'auto'
192
207
 
193
208
  const result = {}
@@ -198,23 +213,28 @@ const generateAutoVars = (schemes, varPrefix, CONFIG) => {
198
213
  if (schemes[scheme]) for (const k of Object.keys(schemes[scheme])) allKeys.add(k)
199
214
  }
200
215
 
201
- // Pre-scan: detect schemes with unresolvable color values
202
- // If any color in a scheme fails to resolve, skip the ENTIRE scheme
203
- // to avoid partial theme application (e.g. white text without dark background)
216
+ // Pre-scan: detect schemes with unresolvable color values. If any color in
217
+ // a scheme fails to resolve, skip the ENTIRE scheme to avoid partial theme
218
+ // application (e.g. white text without a dark background). Runs regardless
219
+ // of `globalTheme` because every scheme is always emitted now.
220
+ //
221
+ // Only check params whose names indicate colors — otherwise legitimate
222
+ // non-color values like `borderStyle: 'solid'` or `cursor: 'pointer'`
223
+ // match the `/^[a-z][a-zA-Z]+$/` heuristic and falsely break the scheme,
224
+ // which would silently drop the entire theme's CSS vars.
204
225
  const brokenSchemes = new Set()
205
- if (globalTheme === 'auto') {
206
- for (const param of allKeys) {
207
- const symb = param.slice(0, 1)
208
- if (symb === '@' || symb === '.' || symb === ':') continue
209
- for (const scheme in schemes) {
210
- if (brokenSchemes.has(scheme)) continue
211
- const val = schemes[scheme]?.[param]
212
- if (val === undefined) continue
213
- const color = getColor(val, `@${scheme}`)
214
- if (color === undefined) continue
215
- if (isString(color) && /^[a-z][a-zA-Z]+$/.test(color) && !CSS_NAMED_COLORS.has(color)) {
216
- brokenSchemes.add(scheme)
217
- }
226
+ for (const param of allKeys) {
227
+ const symb = param.slice(0, 1)
228
+ if (symb === '@' || symb === '.' || symb === ':') continue
229
+ if (!isColorParam(param)) continue
230
+ for (const scheme in schemes) {
231
+ if (brokenSchemes.has(scheme)) continue
232
+ const val = schemes[scheme]?.[param]
233
+ if (val === undefined) continue
234
+ const color = getColor(val, `@${scheme}`)
235
+ if (color === undefined) continue
236
+ if (isString(color) && /^[a-z][a-zA-Z]+$/.test(color) && !CSS_NAMED_COLORS.has(color)) {
237
+ brokenSchemes.add(scheme)
218
238
  }
219
239
  }
220
240
  }
@@ -242,51 +262,53 @@ const generateAutoVars = (schemes, varPrefix, CONFIG) => {
242
262
  }
243
263
  result[param] = generateAutoVars(subSchemes, `${varPrefix}-${pseudoName}`, CONFIG)
244
264
  } else if (symb !== '@' && symb !== '.' && symb !== ':') {
245
- // Regular CSS param — generate auto-switching var
246
- const autoVar = `--theme-${varPrefix}-${param}`
247
-
248
- if (globalTheme === 'auto') {
249
- let fallbackColor
250
- for (const scheme in schemes) {
251
- if (brokenSchemes.has(scheme)) continue
252
- const val = schemes[scheme]?.[param]
253
- if (val === undefined) continue
254
- const color = getColor(val, `@${scheme}`)
255
- if (color === undefined) continue
256
-
257
- // Use 'light' scheme (or first resolved) as base fallback
258
- if (scheme === 'light' || fallbackColor === undefined) {
259
- fallbackColor = color
260
- }
261
-
262
- // [data-theme] selector for ALL schemes (custom + standard)
263
- const selector = `[data-theme="${scheme}"]`
264
- if (!MEDIA_VARS[selector]) MEDIA_VARS[selector] = {}
265
- MEDIA_VARS[selector][autoVar] = color
266
-
267
- // prefers-color-scheme media query only for dark/light
268
- if (scheme === 'dark' || scheme === 'light') {
269
- const mq = `@media (prefers-color-scheme: ${scheme})`
270
- if (!MEDIA_VARS[mq]) MEDIA_VARS[mq] = {}
271
- MEDIA_VARS[mq][autoVar] = color
272
- }
273
- }
265
+ // Regular CSS param — generate auto-switching var.
266
+ //
267
+ // We always emit the FULL switch table — `:root` fallback + one
268
+ // rule per scheme on `[data-theme="X"]` + `@media
269
+ // (prefers-color-scheme: dark|light)` — regardless of whether the
270
+ // user provided an initial `globalTheme`. A forced initial theme
271
+ // only decides which value lands in the `:root` fallback; runtime
272
+ // switching (via `changeGlobalTheme` → `data-theme` attribute)
273
+ // must always work, and OS preference must still drive auto mode.
274
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
275
+ const autoVar = `--${vp}theme-${varPrefix}-${param}`
276
+ const forced = globalTheme && globalTheme !== 'auto'
277
+ ? String(globalTheme).replace(/^'|'$/g, '')
278
+ : null
279
+
280
+ let fallbackColor
281
+ for (const scheme in schemes) {
282
+ if (brokenSchemes.has(scheme)) continue
283
+ const val = schemes[scheme]?.[param]
284
+ if (val === undefined) continue
285
+ const color = getColor(val, `@${scheme}`)
286
+ if (color === undefined) continue
274
287
 
275
- // Set fallback default (light or first scheme) so the variable
276
- // always resolves even without a matching media query
277
- if (fallbackColor !== undefined) {
278
- CSS_VARS[autoVar] = fallbackColor
288
+ // Pick the `:root` fallback: prefer the user's forced scheme if
289
+ // any, else `light`, else the first resolved scheme.
290
+ if ((forced && scheme === forced) ||
291
+ (!forced && (scheme === 'light' || fallbackColor === undefined))) {
292
+ fallbackColor = color
279
293
  }
280
- } else {
281
- // Force specific theme set non-suffixed var directly
282
- const forced = String(globalTheme).replace(/^'|'$/g, '')
283
- const source = schemes[forced]?.[param]
284
- if (source !== undefined) {
285
- const color = getColor(source, `@${forced}`)
286
- if (color !== undefined) CSS_VARS[autoVar] = color
294
+
295
+ // `[data-theme="X"]` selectorworks for any scheme incl. custom.
296
+ const selector = `[data-theme="${scheme}"]`
297
+ if (!MEDIA_VARS[selector]) MEDIA_VARS[selector] = {}
298
+ MEDIA_VARS[selector][autoVar] = color
299
+
300
+ // `prefers-color-scheme` media query only for standard light/dark.
301
+ if (scheme === 'dark' || scheme === 'light') {
302
+ const mq = `@media (prefers-color-scheme: ${scheme})`
303
+ if (!MEDIA_VARS[mq]) MEDIA_VARS[mq] = {}
304
+ MEDIA_VARS[mq][autoVar] = color
287
305
  }
288
306
  }
289
307
 
308
+ if (fallbackColor !== undefined) {
309
+ CSS_VARS[autoVar] = fallbackColor
310
+ }
311
+
290
312
  result[param] = `var(${autoVar})`
291
313
  result[`.${param}`] = { [param]: result[param] }
292
314
  }
@@ -304,7 +326,7 @@ const generateAutoVars = (schemes, varPrefix, CONFIG) => {
304
326
 
305
327
  export const setMediaTheme = (val, key, suffix, prefers) => {
306
328
  const CONFIG = getActiveConfig()
307
- const { CSS_VARS } = CONFIG
329
+ const { cssVars: CSS_VARS } = CONFIG
308
330
  const theme = { value: val }
309
331
  const isTopLevel = !suffix && !prefers
310
332
 
@@ -334,7 +356,8 @@ export const setMediaTheme = (val, key, suffix, prefers) => {
334
356
  const color = getColor(value, prefers)
335
357
  const metaSuffixes = [...new Set([prefers, suffix].filter(v => v).map(v => v.slice(1)))]
336
358
  const varmetaSuffixName = metaSuffixes.length ? '-' + metaSuffixes.join('-') : ''
337
- const CSSVar = `--theme-${key}${varmetaSuffixName}-${param}`
359
+ const vp = CONFIG.varPrefix ? CONFIG.varPrefix + '-' : ''
360
+ const CSSVar = `--${vp}theme-${key}${varmetaSuffixName}-${param}`
338
361
  if (CONFIG.useVariable) {
339
362
  // Suffixed vars (--theme-key-dark-param) only when opted in
340
363
  if (CONFIG.useThemeSuffixedVars) CSS_VARS[CSSVar] = color