@symbo.ls/scratch 3.8.0 → 3.8.6

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/src/set.js CHANGED
@@ -17,14 +17,17 @@ import {
17
17
  setShadow
18
18
  } from './system'
19
19
 
20
- import { isFunction, deepMerge } from '@domql/utils'
20
+ import { deepMerge } from '@domql/utils'
21
21
 
22
- const setCases = (val, key) => {
23
- if (isFunction(val)) return val()
22
+ const setVars = (val, key) => {
23
+ const CONFIG = getActiveConfig()
24
+ const { CSS_VARS } = CONFIG
25
+ const varName = key.startsWith('--') ? key : `--${key}`
26
+ CSS_VARS[varName] = val
24
27
  return val
25
28
  }
26
29
 
27
- const setSameValue = (val, key) => val
30
+ const asIs = (val) => val
28
31
 
29
32
  export const VALUE_TRANSFORMERS = {
30
33
  color: setColor,
@@ -34,21 +37,21 @@ export const VALUE_TRANSFORMERS = {
34
37
  fontfamily: setFontFamily,
35
38
  theme: setTheme,
36
39
  icons: setSvgIcon,
37
- semantic_icons: setSameValue,
38
- semanticicons: setSameValue,
40
+ semantic_icons: asIs,
41
+ semanticicons: asIs,
39
42
  svg: setSVG,
40
- svg_data: setSameValue,
41
- typography: setSameValue,
42
- cases: setCases,
43
+ svg_data: asIs,
44
+ typography: asIs,
43
45
  shadow: setShadow,
44
- spacing: setSameValue,
45
- media: setSameValue,
46
- grid: setSameValue,
47
- class: setSameValue,
48
- timing: setSameValue,
49
- reset: setSameValue,
50
- unit: setSameValue,
51
- animation: setSameValue
46
+ spacing: asIs,
47
+ media: asIs,
48
+ grid: asIs,
49
+ class: asIs,
50
+ timing: asIs,
51
+ reset: asIs,
52
+ unit: asIs,
53
+ animation: asIs,
54
+ vars: setVars
52
55
  }
53
56
 
54
57
  /**
@@ -69,11 +72,13 @@ export const setValue = (factoryName, value, key) => {
69
72
  FACTORY[key] = result
70
73
  return FACTORY
71
74
  } catch (error) {
72
- if (CONFIG.verbose) console.warn('Error setting', lowerName, 'value', value, key, error)
75
+ if (CONFIG.verbose)
76
+ console.warn('Error setting', lowerName, 'value', value, key, error)
73
77
  }
74
78
  }
75
79
 
76
- if (CONFIG.verbose) console.warn('Can not find', lowerName, 'method in scratch')
80
+ if (CONFIG.verbose)
81
+ console.warn('Can not find', lowerName, 'method in scratch')
77
82
  }
78
83
 
79
84
  export const setEach = (factoryName, props) => {
@@ -85,7 +90,15 @@ export const setEach = (factoryName, props) => {
85
90
  try {
86
91
  return setValue(lowerName, props[key], key)
87
92
  } catch (error) {
88
- if (CONFIG.verbose) console.warn('Error setting', lowerName, 'value', props[key], key, error)
93
+ if (CONFIG.verbose)
94
+ console.warn(
95
+ 'Error setting',
96
+ lowerName,
97
+ 'value',
98
+ props[key],
99
+ key,
100
+ error
101
+ )
89
102
  }
90
103
  })
91
104
 
@@ -131,7 +144,7 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
131
144
  useDocumentTheme,
132
145
  useDefaultConfig,
133
146
  semanticIcons,
134
- SEMANTIC_ICONS,
147
+ SEMANTIC_ICONS, // deprecated
135
148
  semantic_icons,
136
149
  files,
137
150
  ...config
@@ -150,7 +163,8 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
150
163
  if (useIconSprite !== undefined) CONFIG.useIconSprite = useIconSprite
151
164
  if (useDocumentTheme !== undefined) CONFIG.useDocumentTheme = useDocumentTheme
152
165
  if (globalTheme !== undefined) CONFIG.globalTheme = globalTheme
153
- if (recivedConfig.useThemeSuffixedVars !== undefined) CONFIG.useThemeSuffixedVars = recivedConfig.useThemeSuffixedVars
166
+ if (recivedConfig.useThemeSuffixedVars !== undefined)
167
+ CONFIG.useThemeSuffixedVars = recivedConfig.useThemeSuffixedVars
154
168
  if (useDefaultConfig !== undefined) CONFIG.useDefaultConfig = useDefaultConfig
155
169
  const _semanticIcons = semanticIcons || SEMANTIC_ICONS || semantic_icons
156
170
  if (_semanticIcons !== undefined) {
@@ -166,7 +180,7 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
166
180
  const keySet = new Set(keys)
167
181
 
168
182
  // Pre-merge: fold UPPERCASE default keys into lowercase project keys
169
- keys.forEach(key => {
183
+ keys.forEach((key) => {
170
184
  const lower = key.toLowerCase()
171
185
  if (lower !== key && keySet.has(lower)) {
172
186
  deepMerge(config[lower], config[key])
@@ -174,25 +188,31 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
174
188
  })
175
189
 
176
190
  // Process only lowercase keys (skip UPPERCASE when lowercase equivalent exists)
177
- keys.map(key => {
191
+ keys.map((key) => {
178
192
  const lower = key.toLowerCase()
179
193
  if (lower !== key && keySet.has(lower)) return
180
194
  return setEach(key, config[key])
181
195
  })
182
196
 
183
197
  // apply generic configs
184
- if (config.typography || config.TYPOGRAPHY) {
185
- try { applyTypographySequence() } catch (e) {
198
+ if (config.typography || config.TYPOGRAPHY) { // UPPERCASE deprecated
199
+ try {
200
+ applyTypographySequence()
201
+ } catch (e) {
186
202
  if (CONFIG.verbose) console.warn('Error applying typography sequence', e)
187
203
  }
188
204
  }
189
- if (config.spacing || config.SPACING) {
190
- try { applySpacingSequence() } catch (e) {
205
+ if (config.spacing || config.SPACING) { // UPPERCASE deprecated
206
+ try {
207
+ applySpacingSequence()
208
+ } catch (e) {
191
209
  if (CONFIG.verbose) console.warn('Error applying spacing sequence', e)
192
210
  }
193
211
  }
194
- if (config.timing || config.TIMING) {
195
- try { applyTimingSequence() } catch (e) {
212
+ if (config.timing || config.TIMING) { // UPPERCASE deprecated
213
+ try {
214
+ applyTimingSequence()
215
+ } catch (e) {
196
216
  if (CONFIG.verbose) console.warn('Error applying timing sequence', e)
197
217
  }
198
218
  }
@@ -26,7 +26,13 @@ export const getColor = (value, key, config) => {
26
26
  [name, alpha, tone] = value
27
27
  } else {
28
28
  const parsed = parseColorToken(value)
29
- if (!parsed) return value
29
+ if (!parsed) {
30
+ // Direct lookup for hyphenated names (e.g. 'gradient-light', 'gradient-dark-active')
31
+ const { color: COLOR, gradient: GRADIENT } = CONFIG
32
+ const directVal = GRADIENT[value] || COLOR[value]
33
+ if (directVal) return CONFIG.useVariable ? `var(${directVal.var})` : directVal.value
34
+ return value
35
+ }
30
36
  if (parsed.passthrough) return parsed.passthrough
31
37
  if (parsed.cssVar) return `var(${parsed.cssVar})`
32
38
  name = parsed.name
@@ -33,8 +33,14 @@ export const setFont = (val, key) => {
33
33
  } else if (val[0]) {
34
34
  fontFace = getFontFaceEach(key, val, CONFIG.files)
35
35
  } else {
36
- const url = resolveFileUrl(val.url, CONFIG.files) || val.url
37
- fontFace = setCustomFontMedia(key, url)
36
+ const url = Array.isArray(val.url)
37
+ ? val.url.map((u) => resolveFileUrl(u, CONFIG.files) || u)
38
+ : resolveFileUrl(val.url, CONFIG.files) || val.url
39
+ fontFace = setCustomFontMedia(key, url, val.fontWeight, {
40
+ fontStyle: val.fontStyle,
41
+ fontDisplay: val.fontDisplay,
42
+ fontStretch: val.fontStretch
43
+ })
38
44
  }
39
45
 
40
46
  return { var: CSSvar, value: val, fontFace }
@@ -11,6 +11,38 @@ import {
11
11
  isArray
12
12
  } from '@domql/utils'
13
13
 
14
+ // Common CSS named colors — used to distinguish valid CSS color names
15
+ // from unresolved design-system tokens (e.g. 'blackRussian', 'grayMid')
16
+ const CSS_NAMED_COLORS = new Set([
17
+ 'black', 'white', 'red', 'green', 'blue', 'yellow', 'orange', 'purple',
18
+ 'pink', 'brown', 'gray', 'grey', 'cyan', 'magenta', 'lime', 'olive',
19
+ 'navy', 'teal', 'aqua', 'maroon', 'silver', 'fuchsia', 'transparent',
20
+ 'currentColor', 'currentcolor', 'inherit', 'initial', 'unset', 'none',
21
+ 'aliceblue', 'antiquewhite', 'aquamarine', 'azure', 'beige', 'bisque',
22
+ 'blanchedalmond', 'blueviolet', 'burlywood', 'cadetblue', 'chartreuse',
23
+ 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'darkblue',
24
+ 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkgrey', 'darkkhaki',
25
+ 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred',
26
+ 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkslategrey',
27
+ 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dimgrey',
28
+ 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'gainsboro', 'ghostwhite',
29
+ 'gold', 'goldenrod', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo',
30
+ 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon',
31
+ 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray',
32
+ 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen',
33
+ 'lightskyblue', 'lightslategray', 'lightslategrey', 'lightsteelblue', 'lightyellow',
34
+ 'limegreen', 'linen', 'mediumaquamarine', 'mediumblue', 'mediumorchid',
35
+ 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen',
36
+ 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose',
37
+ 'moccasin', 'navajowhite', 'oldlace', 'olivedrab', 'orangered', 'orchid',
38
+ 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip',
39
+ 'peachpuff', 'peru', 'plum', 'powderblue', 'rosybrown', 'royalblue',
40
+ 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna',
41
+ 'skyblue', 'slateblue', 'slategray', 'slategrey', 'snow', 'springgreen',
42
+ 'steelblue', 'tan', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat',
43
+ 'whitesmoke', 'yellowgreen', 'rebeccapurple'
44
+ ])
45
+
14
46
  const setThemeValue = theme => {
15
47
  const value = {}
16
48
  const { state, media, helpers, ...rest } = theme
@@ -166,6 +198,27 @@ const generateAutoVars = (schemes, varPrefix, CONFIG) => {
166
198
  if (schemes[scheme]) for (const k of Object.keys(schemes[scheme])) allKeys.add(k)
167
199
  }
168
200
 
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)
204
+ 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
+ }
218
+ }
219
+ }
220
+ }
221
+
169
222
  for (const param of allKeys) {
170
223
  const symb = param.slice(0, 1)
171
224
 
@@ -193,12 +246,19 @@ const generateAutoVars = (schemes, varPrefix, CONFIG) => {
193
246
  const autoVar = `--theme-${varPrefix}-${param}`
194
247
 
195
248
  if (globalTheme === 'auto') {
249
+ let fallbackColor
196
250
  for (const scheme in schemes) {
251
+ if (brokenSchemes.has(scheme)) continue
197
252
  const val = schemes[scheme]?.[param]
198
253
  if (val === undefined) continue
199
254
  const color = getColor(val, `@${scheme}`)
200
255
  if (color === undefined) continue
201
256
 
257
+ // Use 'light' scheme (or first resolved) as base fallback
258
+ if (scheme === 'light' || fallbackColor === undefined) {
259
+ fallbackColor = color
260
+ }
261
+
202
262
  // [data-theme] selector for ALL schemes (custom + standard)
203
263
  const selector = `[data-theme="${scheme}"]`
204
264
  if (!MEDIA_VARS[selector]) MEDIA_VARS[selector] = {}
@@ -211,6 +271,12 @@ const generateAutoVars = (schemes, varPrefix, CONFIG) => {
211
271
  MEDIA_VARS[mq][autoVar] = color
212
272
  }
213
273
  }
274
+
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
279
+ }
214
280
  } else {
215
281
  // Force specific theme — set non-suffixed var directly
216
282
  const forced = String(globalTheme).replace(/^'|'$/g, '')
@@ -310,6 +376,8 @@ const recursiveTheme = val => {
310
376
  continue
311
377
  } else if (symb === ':') {
312
378
  obj[`&${param}`] = recursiveTheme(val[param])
379
+ } else if (symb === '.') {
380
+ obj[`&${param}`] = recursiveTheme(val[param])
313
381
  }
314
382
  } else obj[param] = val[param]
315
383
  }
package/src/utils/font.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  export const resolveFileUrl = (url, files) => {
4
4
  if (!url || !files) return null
5
- try { new URL(url); return null } catch (e) { }
5
+ try { new URL(url); return null } catch (e) { } // expected: src is not an absolute URL, treating as relative path
6
6
  const file = files[url]
7
7
  if (file) return file.content && file.content.src
8
8
  return null
@@ -30,11 +30,15 @@ export const setFontImport = (url) => `@import url('${url}');`
30
30
  export const setInCustomFontMedia = (str) => `@font-face { ${str} }`
31
31
 
32
32
  export const setCustomFont = (name, url, weight, options = {}) => {
33
- const format = getFontFormat(url)
34
- const formatStr = format ? ` format('${format}')` : ''
33
+ const urls = Array.isArray(url) ? url : [url]
34
+ const srcList = urls.map((u) => {
35
+ const format = getFontFormat(u)
36
+ const formatStr = format ? ` format('${format}')` : ''
37
+ return `url('${u}')${formatStr}`
38
+ }).join(',\n ')
35
39
  return `
36
40
  font-family: '${name}';
37
- font-style: normal;${
41
+ font-style: ${options.fontStyle || 'normal'};${
38
42
  weight
39
43
  ? `
40
44
  font-weight: ${weight};`
@@ -50,7 +54,7 @@ export const setCustomFont = (name, url, weight, options = {}) => {
50
54
  font-display: ${options.fontDisplay};`
51
55
  : ''
52
56
  }
53
- src: url('${url}')${formatStr};`
57
+ src: ${srcList};`
54
58
  }
55
59
 
56
60
  export const setCustomFontMedia = (
@@ -64,9 +68,11 @@ export const setCustomFontMedia = (
64
68
  export const getFontFaceEach = (name, weights, files) => {
65
69
  const keys = Object.keys(weights)
66
70
  return keys.map((key) => {
67
- const { url, fontWeight } = weights[key]
68
- const resolvedUrl = resolveFileUrl(url, files) || url
69
- return setCustomFont(name, resolvedUrl, fontWeight)
71
+ const { url, fontWeight, fontStyle, fontDisplay, fontStretch } = weights[key]
72
+ const resolvedUrl = Array.isArray(url)
73
+ ? url.map((u) => resolveFileUrl(u, files) || u)
74
+ : resolveFileUrl(url, files) || url
75
+ return setCustomFont(name, resolvedUrl, fontWeight, { fontStyle, fontDisplay, fontStretch })
70
76
  })
71
77
  }
72
78
 
@@ -88,8 +94,14 @@ export const getFontFaceEachString = (name, weights, files) => {
88
94
  }
89
95
  const isArr = weights[0]
90
96
  if (isArr) return getFontFaceEach(name, weights, files).map(setInCustomFontMedia)
91
- const url = resolveFileUrl(weights.url, files) || weights.url
92
- return setCustomFontMedia(name, url)
97
+ const url = Array.isArray(weights.url)
98
+ ? weights.url.map((u) => resolveFileUrl(u, files) || u)
99
+ : resolveFileUrl(weights.url, files) || weights.url
100
+ return setCustomFontMedia(name, url, weights.fontWeight, {
101
+ fontStyle: weights.fontStyle,
102
+ fontDisplay: weights.fontDisplay,
103
+ fontStretch: weights.fontStretch
104
+ })
93
105
  }
94
106
 
95
107
  export const getFontFaceString = (LIBRARY, files) => {