@symbo.ls/scratch 3.1.2 → 3.2.7

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 (67) hide show
  1. package/dist/cjs/factory.js +23 -475
  2. package/dist/cjs/index.js +11 -2684
  3. package/dist/cjs/set.js +66 -1992
  4. package/dist/esm/factory.js +43 -0
  5. package/dist/esm/index.js +12 -0
  6. package/dist/esm/set.js +148 -0
  7. package/dist/iife/index.js +2580 -0
  8. package/package.json +26 -16
  9. package/src/set.js +22 -7
  10. package/src/system/color.js +48 -11
  11. package/src/system/font.js +19 -5
  12. package/src/system/reset.js +12 -3
  13. package/src/system/shadow.js +20 -13
  14. package/src/system/spacing.js +63 -56
  15. package/src/system/theme.js +4 -3
  16. package/src/system/timing.js +1 -1
  17. package/src/system/typography.js +12 -20
  18. package/src/transforms/index.js +212 -95
  19. package/src/utils/color.js +121 -31
  20. package/src/utils/font.js +54 -14
  21. package/src/utils/sequence.js +94 -39
  22. package/src/utils/sprite.js +10 -6
  23. package/src/utils/unit.js +69 -2
  24. package/src/utils/var.js +1 -2
  25. package/dist/cjs/defaultConfig/animation.js +0 -26
  26. package/dist/cjs/defaultConfig/cases.js +0 -26
  27. package/dist/cjs/defaultConfig/class.js +0 -27
  28. package/dist/cjs/defaultConfig/color.js +0 -28
  29. package/dist/cjs/defaultConfig/document.js +0 -26
  30. package/dist/cjs/defaultConfig/font-family.js +0 -34
  31. package/dist/cjs/defaultConfig/font.js +0 -26
  32. package/dist/cjs/defaultConfig/grid.js +0 -27
  33. package/dist/cjs/defaultConfig/icons.js +0 -28
  34. package/dist/cjs/defaultConfig/index.js +0 -222
  35. package/dist/cjs/defaultConfig/media.js +0 -31
  36. package/dist/cjs/defaultConfig/responsive.js +0 -52
  37. package/dist/cjs/defaultConfig/sequence.js +0 -51
  38. package/dist/cjs/defaultConfig/shadow.js +0 -26
  39. package/dist/cjs/defaultConfig/spacing.js +0 -87
  40. package/dist/cjs/defaultConfig/svg.js +0 -28
  41. package/dist/cjs/defaultConfig/templates.js +0 -26
  42. package/dist/cjs/defaultConfig/theme.js +0 -26
  43. package/dist/cjs/defaultConfig/timing.js +0 -68
  44. package/dist/cjs/defaultConfig/typography.js +0 -72
  45. package/dist/cjs/defaultConfig/unit.js +0 -28
  46. package/dist/cjs/package.json +0 -4
  47. package/dist/cjs/system/color.js +0 -1096
  48. package/dist/cjs/system/document.js +0 -906
  49. package/dist/cjs/system/font.js +0 -928
  50. package/dist/cjs/system/index.js +0 -2163
  51. package/dist/cjs/system/reset.js +0 -1018
  52. package/dist/cjs/system/shadow.js +0 -1305
  53. package/dist/cjs/system/spacing.js +0 -1257
  54. package/dist/cjs/system/svg.js +0 -1007
  55. package/dist/cjs/system/theme.js +0 -1197
  56. package/dist/cjs/system/timing.js +0 -1132
  57. package/dist/cjs/system/typography.js +0 -1243
  58. package/dist/cjs/tests/index.js +0 -30
  59. package/dist/cjs/transforms/index.js +0 -1534
  60. package/dist/cjs/utils/color.js +0 -336
  61. package/dist/cjs/utils/font.js +0 -69
  62. package/dist/cjs/utils/index.js +0 -1477
  63. package/dist/cjs/utils/sequence.js +0 -1125
  64. package/dist/cjs/utils/sprite.js +0 -554
  65. package/dist/cjs/utils/theme.js +0 -31
  66. package/dist/cjs/utils/unit.js +0 -28
  67. package/dist/cjs/utils/var.js +0 -967
@@ -1,6 +1,6 @@
1
1
  'use strict'
2
2
 
3
- import { isNull, isString, isObject, isUndefined } from '@domql/utils'
3
+ import { isString, isObject, exec } from '@domql/utils'
4
4
  import { getActiveConfig } from '../factory'
5
5
  import {
6
6
  getSpacingByKey,
@@ -9,134 +9,251 @@ import {
9
9
  getMediaColor,
10
10
  getTimingByKey,
11
11
  getTimingFunction,
12
- getSpacingBasedOnRatio
12
+ getSpacingBasedOnRatio,
13
+ checkIfBoxSize,
14
+ splitSpacedValue
13
15
  } from '../system'
16
+ import {
17
+ getFnPrefixAndValue,
18
+ isResolvedColor,
19
+ isCSSVar,
20
+ CSS_NATIVE_COLOR_REGEX,
21
+ splitTopLevelCommas,
22
+ parseColorToken
23
+ } from '../utils'
24
+
25
+ const BORDER_STYLES = new Set([
26
+ 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
27
+ 'groove', 'ridge', 'inset', 'outset', 'initial'
28
+ ])
29
+
30
+ const GRADIENT_KEYWORDS = new Set([
31
+ 'to', 'top', 'bottom', 'left', 'right', 'center', 'at',
32
+ 'circle', 'ellipse', 'closest-side', 'farthest-side',
33
+ 'closest-corner', 'farthest-corner'
34
+ ])
35
+ const isBorderStyle = (str) => BORDER_STYLES.has(str)
36
+
37
+ export const transformBorder = (border) => {
38
+ const str = border + ''
39
+
40
+ // CSS passthrough: native CSS color syntax
41
+ if (CSS_NATIVE_COLOR_REGEX.test(str)) return str
42
+
43
+ // Simple CSS keywords
44
+ const trimmed = str.trim()
45
+ if (trimmed === 'none' || trimmed === '0' || trimmed === 'initial' || trimmed === 'inherit' || trimmed === 'unset') return str
14
46
 
15
- const isBorderStyle = str => [
16
- 'none',
17
- 'hidden',
18
- 'dotted',
19
- 'dashed',
20
- 'solid',
21
- 'double',
22
- 'groove',
23
- 'ridge',
24
- 'inset',
25
- 'outset',
26
- 'initial'
27
- ].some(v => str.includes(v))
28
-
29
- export const transformBorder = border => {
30
- const arr = border.split(', ')
31
- return arr.map(v => {
32
- v = v.trim()
33
- if (v.slice(0, 2) === '--') return `var(${v})`
34
- else if (isBorderStyle(v)) return v || 'solid'
35
- else if (v.slice(-2) === 'px' || v.slice(-2) === 'em') return v // TODO: add map spacing
36
- else if (getColor(v).length > 2) return getColor(v)
37
- return getSpacingByKey(v, 'border').border
38
- }).join(' ')
47
+ // Space-separated tokens (CSS-like syntax)
48
+ const tokens = str.split(/\s+/)
49
+
50
+ return tokens
51
+ .map((v) => {
52
+ v = v.trim()
53
+ if (!v) return ''
54
+ if (isCSSVar(v)) return `var(${v})`
55
+ if (isBorderStyle(v)) return v
56
+ if (/^\d/.test(v) || v === '0') return v
57
+ // Try color resolution
58
+ const color = getColor(v)
59
+ if (isResolvedColor(color)) return color
60
+ // Try spacing key
61
+ const spacing = getSpacingByKey(v, 'border')
62
+ if (spacing && spacing.border) return spacing.border
63
+ return v
64
+ })
65
+ .join(' ')
39
66
  }
40
67
 
41
- export const transformTextStroke = stroke => {
42
- return stroke.split(', ').map(v => {
43
- if (v.slice(0, 2) === '--') return `var(${v})`
44
- if (v.includes('px')) return v
45
- else if (getColor(v)) return getColor(v)
46
- return v
47
- }).join(' ')
68
+ export const transformTextStroke = (stroke) => {
69
+ // CSS passthrough
70
+ if (CSS_NATIVE_COLOR_REGEX.test(stroke)) return stroke
71
+
72
+ return stroke
73
+ .split(/\s+/)
74
+ .map((v) => {
75
+ v = v.trim()
76
+ if (!v) return ''
77
+ if (isCSSVar(v)) return `var(${v})`
78
+ if (/^\d/.test(v) || v.includes('px') || v === '0') return v
79
+ const color = getColor(v)
80
+ if (isResolvedColor(color)) return color
81
+ return v
82
+ })
83
+ .join(' ')
48
84
  }
49
85
 
50
86
  export const transformShadow = (sh, globalTheme) => getShadow(sh, globalTheme)
51
87
 
52
- export const transformBoxShadow = (shadows, globalTheme) => shadows.split('|').map(shadow => {
53
- return shadow.split(',').map(v => {
54
- v = v.trim()
55
- if (v.slice(0, 2) === '--') return `var(${v})`
56
- if (getColor(v).length > 2) {
57
- const color = getMediaColor(v, globalTheme)
58
- if (isObject(color)) return Object.values(color).filter(v => v.includes(': ' + globalTheme))[0]
59
- return color
60
- }
61
- if (v.includes('px') || v.slice(-2) === 'em') return v
62
- const arr = v.split(' ')
63
- if (!arr.length) return v
64
- return arr.map(v => getSpacingByKey(v, 'shadow').shadow).join(' ')
65
- }).join(' ')
66
- }).join(',')
88
+ export const transformBoxShadow = (shadows, globalTheme) => {
89
+ // CSS passthrough: native CSS color syntax
90
+ if (CSS_NATIVE_COLOR_REGEX.test(shadows)) return shadows
91
+
92
+ // Split multiple shadows by commas (CSS standard), respecting parentheses
93
+ return splitTopLevelCommas(shadows)
94
+ .map((shadow) => {
95
+ shadow = shadow.trim()
96
+ if (!shadow) return ''
97
+
98
+ // Each shadow: space-separated tokens
99
+ return shadow.split(/\s+/)
100
+ .map((v) => {
101
+ v = v.trim()
102
+ if (!v) return ''
103
+ if (isCSSVar(v)) return `var(${v})`
104
+ if (v === 'inset' || v === 'none') return v
105
+
106
+ // Try color resolution
107
+ const color = getColor(v)
108
+ if (isResolvedColor(color)) {
109
+ const mediaColor = getMediaColor(v, globalTheme)
110
+ if (isObject(mediaColor))
111
+ return Object.values(mediaColor).filter((c) =>
112
+ c.includes(': ' + globalTheme)
113
+ )[0]
114
+ return mediaColor
115
+ }
116
+
117
+ // CSS unit values
118
+ if (/^\d/.test(v) || v === '0' || v.includes('px') || v.slice(-2) === 'em') return v
119
+
120
+ // Spacing key
121
+ const spacing = getSpacingByKey(v, 'shadow')
122
+ if (spacing && spacing.shadow) return spacing.shadow
123
+
124
+ return v
125
+ })
126
+ .join(' ')
127
+ })
128
+ .join(', ')
129
+ }
130
+
131
+ /**
132
+ * Resolve Symbols color tokens inside a CSS gradient string.
133
+ * e.g. 'linear-gradient(to bottom, white.97 65%, white.0 100%)'
134
+ * → 'linear-gradient(to bottom, rgba(255, 255, 255, 0.97) 65%, rgba(255, 255, 255, 0.0) 100%)'
135
+ */
136
+ export const resolveColorsInGradient = (gradient, globalTheme) => {
137
+ // Find the opening paren after the gradient type
138
+ const parenStart = gradient.indexOf('(')
139
+ if (parenStart === -1) return gradient
140
+
141
+ const prefix = gradient.slice(0, parenStart + 1)
142
+ const inner = gradient.slice(parenStart + 1, gradient.lastIndexOf(')'))
143
+ const suffix = ')'
144
+
145
+ // Split by top-level commas (respects nested rgba() etc.)
146
+ const segments = splitTopLevelCommas(inner)
147
+
148
+ const resolved = segments.map((segment) => {
149
+ segment = segment.trim()
150
+ // Split segment into space-separated tokens
151
+ const tokens = segment.split(/\s+/)
152
+
153
+ return tokens.map((token) => {
154
+ if (!token) return token
155
+ // Skip CSS values: percentages, degrees, direction keywords, native colors
156
+ if (/^\d/.test(token) || token === '0') return token
157
+ if (GRADIENT_KEYWORDS.has(token)) return token
158
+ if (token === 'transparent') return token
159
+ if (CSS_NATIVE_COLOR_REGEX.test(token)) return token
160
+
161
+ // Try to resolve as a Symbols color token
162
+ const color = getColor(token)
163
+ if (isResolvedColor(color)) return color
164
+
165
+ return token
166
+ }).join(' ')
167
+ })
168
+
169
+ return prefix + resolved.join(', ') + suffix
170
+ }
67
171
 
68
172
  export const transformBackgroundImage = (backgroundImage, globalTheme) => {
69
173
  const CONFIG = getActiveConfig()
70
- return backgroundImage.split(', ').map(v => {
71
- if (v.slice(0, 2) === '--') return `var(${v})`
72
- if (v.includes('url') || v.includes('gradient')) return v
73
- else if (CONFIG.GRADIENT[backgroundImage]) {
74
- return {
75
- backgroundImage: getMediaColor(backgroundImage, globalTheme || CONFIG.globalTheme)
76
- }
77
- } else if (v.includes('/') || v.startsWith('http') || v.includes('.')) return `url(${v})`
78
- return v
79
- }).join(' ')
174
+ return backgroundImage
175
+ .split(', ')
176
+ .map((v) => {
177
+ if (isCSSVar(v)) return `var(${v})`
178
+ if (v.includes('url')) return v
179
+ if (v.includes('gradient')) return resolveColorsInGradient(v, globalTheme)
180
+ else if (CONFIG.GRADIENT[backgroundImage]) {
181
+ return {
182
+ backgroundImage: getMediaColor(
183
+ backgroundImage,
184
+ globalTheme || CONFIG.globalTheme
185
+ )
186
+ }
187
+ } else if (v.includes('/') || v.startsWith('http') || (v.includes('.') && !parseColorToken(v)))
188
+ return `url(${v})`
189
+ return v
190
+ })
191
+ .join(' ')
80
192
  }
81
193
 
82
- export const transfromGap = gap => isString(gap) && (
83
- gap.split(' ').map(v => getSpacingByKey(v, 'gap').gap).join(' ')
84
- )
194
+ export const transfromGap = (gap) =>
195
+ isString(gap) &&
196
+ gap
197
+ .split(' ')
198
+ .map((v) => getSpacingByKey(v, 'gap').gap)
199
+ .join(' ')
85
200
 
86
- export const transformTransition = transition => {
201
+ export const transformTransition = (transition) => {
87
202
  const arr = transition.split(' ')
88
203
 
89
204
  if (!arr.length) return transition
90
205
 
91
- return arr.map(v => {
92
- if (v.slice(0, 2) === '--') return `var(${v})`
93
- if (v.length < 3 || v.includes('ms')) {
94
- const mapWithSequence = getTimingByKey(v)
95
- return mapWithSequence.timing || v
96
- }
97
- if (getTimingFunction(v)) return getTimingFunction(v)
98
- return v
99
- }).join(' ')
206
+ return arr
207
+ .map((v) => {
208
+ if (isCSSVar(v)) return `var(${v})`
209
+ if (v.length < 3 || v.includes('ms')) {
210
+ const mapWithSequence = getTimingByKey(v)
211
+ return mapWithSequence.timing || v
212
+ }
213
+ if (getTimingFunction(v)) return getTimingFunction(v)
214
+ return v
215
+ })
216
+ .join(' ')
100
217
  }
101
218
 
102
219
  export const transformDuration = (duration, props, propertyName) => {
103
220
  if (!isString(duration)) return
104
- return duration.split(',').map(v => getTimingByKey(v).timing || v).join(',')
221
+ return duration
222
+ .split(',')
223
+ .map((v) => getTimingByKey(v).timing || v)
224
+ .join(',')
105
225
  }
106
226
 
107
- export const splitTransition = transition => {
227
+ export const splitTransition = (transition) => {
108
228
  const arr = transition.split(',')
109
229
  if (!arr.length) return
110
230
  return arr.map(transformTransition).join(',')
111
231
  }
112
232
 
113
- export const checkIfBoxSize = propertyName => {
114
- const prop = propertyName.toLowerCase()
115
- return (prop.includes('width') || prop.includes('height')) && !prop.includes('border')
116
- }
117
-
118
- export const transformSize = (propertyName, val, props = {}, opts = {}) => {
119
- let value = val || props[propertyName]
233
+ export function transformSize(propertyName, val, props = {}, opts = {}) {
234
+ let value = exec.call(this, val || props[propertyName])
120
235
 
121
- if (isUndefined(value) && isNull(value)) return
236
+ if (value === undefined || value === null) return
122
237
 
123
- const shouldScaleBoxSize = props.scaleBoxSize
124
- const isBoxSize = checkIfBoxSize(propertyName)
238
+ let fnPrefix
239
+ if (isString(value)) {
240
+ // has function prefix
241
+ if (value.includes('(')) {
242
+ const fnArr = getFnPrefixAndValue(value)
243
+ fnPrefix = fnArr[0]
244
+ value = fnArr[1]
245
+ }
125
246
 
126
- if (!shouldScaleBoxSize && isBoxSize && isString(value)) {
127
- value = value.split(' ').map(v => {
128
- const isSingleLetter = v.length < 3 && /[A-Z]/.test(v)
129
- const hasUnits = ['%', 'vw', 'vh', 'ch'].some(unit => value.includes(unit))
130
- if (isSingleLetter && !hasUnits) return v + '_default'
131
- return v
132
- }).join(' ')
247
+ const shouldScaleBoxSize = props.scaleBoxSize
248
+ const isBoxSize = checkIfBoxSize(propertyName)
249
+ if (!shouldScaleBoxSize && isBoxSize) {
250
+ value = splitSpacedValue(value)
251
+ }
133
252
  }
134
253
 
135
- if (opts.ratio) {
136
- return getSpacingBasedOnRatio(props, propertyName, value)
137
- } else {
138
- return getSpacingByKey(value, propertyName)
139
- }
254
+ return opts.ratio
255
+ ? getSpacingBasedOnRatio(props, propertyName, value, fnPrefix)
256
+ : getSpacingByKey(value, propertyName, undefined, fnPrefix)
140
257
  }
141
258
 
142
259
  export const transformSizeRatio = (propertyName, val = null, props) => {
@@ -1,7 +1,12 @@
1
1
  'use strict'
2
2
 
3
- import { document, window, isString, isNumber } from '@domql/utils'
4
- const ENV = process.env.NODE_ENV
3
+ import {
4
+ document,
5
+ window,
6
+ isString,
7
+ isNumber,
8
+ isNotProduction
9
+ } from '@domql/utils'
5
10
 
6
11
  export const colorStringToRgbaArray = color => {
7
12
  if (color === '') return [0, 0, 0, 0]
@@ -10,11 +15,22 @@ export const colorStringToRgbaArray = color => {
10
15
  // convert #RGB and #RGBA to #RRGGBB and #RRGGBBAA
11
16
  if (color[0] === '#') {
12
17
  if (color.length < 7) {
13
- color = '#' + color[1] + color[1] + color[2] + color[2] + color[3] + color[3] + (color.length > 4 ? color[4] + color[4] : '')
14
- } return [parseInt(color.substr(1, 2), 16),
18
+ color =
19
+ '#' +
20
+ color[1] +
21
+ color[1] +
22
+ color[2] +
23
+ color[2] +
24
+ color[3] +
25
+ color[3] +
26
+ (color.length > 4 ? color[4] + color[4] : '')
27
+ }
28
+ return [
29
+ parseInt(color.substr(1, 2), 16),
15
30
  parseInt(color.substr(3, 2), 16),
16
31
  parseInt(color.substr(5, 2), 16),
17
- color.length > 7 ? parseInt(color.substr(7, 2), 16) / 255 : 1]
32
+ color.length > 7 ? parseInt(color.substr(7, 2), 16) / 255 : 1
33
+ ]
18
34
  }
19
35
 
20
36
  // convert named colors
@@ -81,11 +97,7 @@ export const hexToRgba = (hex, alpha = 1) => {
81
97
  export const mixTwoRgb = (colorA, colorB, range = 0.5) => {
82
98
  const arr = []
83
99
  for (let i = 0; i < 3; i++) {
84
- arr[i] = ~~(
85
- colorA[i] + (
86
- (colorB[i] - colorA[i]) * range
87
- )
88
- )
100
+ arr[i] = ~~(colorA[i] + (colorB[i] - colorA[i]) * range)
89
101
  }
90
102
  return `rgb(${arr})`
91
103
  }
@@ -93,25 +105,27 @@ export const mixTwoRgb = (colorA, colorB, range = 0.5) => {
93
105
  export const changeLightness = (delta, hsl) => {
94
106
  const [hue, saturation, lightness] = hsl
95
107
 
96
- const newLightness = Math.max(
97
- 0,
98
- Math.min(100, lightness + parseFloat(delta))
99
- )
108
+ const newLightness = Math.max(0, Math.min(100, lightness + parseFloat(delta)))
100
109
 
101
110
  return [hue, saturation, newLightness]
102
111
  }
103
112
 
104
113
  export const rgbToHSL = (r, g, b) => {
105
- const a = Math.max(r, g, b); const n = a - Math.min(r, g, b); const f = (1 - Math.abs(a + a - n - 1))
106
- const h = n && ((a == r) ? (g - b) / n : ((a == g) ? 2 + (b - r) / n : 4 + (r - g) / n)) //eslint-disable-line
114
+ const a = Math.max(r, g, b)
115
+ const n = a - Math.min(r, g, b)
116
+ const f = 1 - Math.abs(a + a - n - 1)
117
+ const h =
118
+ n && (a == r ? (g - b) / n : a == g ? 2 + (b - r) / n : 4 + (r - g) / n) //eslint-disable-line
107
119
  return [60 * (h < 0 ? h + 6 : h), f ? n / f : 0, (a + a - n) / 2]
108
120
  }
109
121
 
110
- export const hslToRgb = (h, s, l,
122
+ export const hslToRgb = (
123
+ h,
124
+ s,
125
+ l,
111
126
  a = s * Math.min(l, 1 - l),
112
- f = (n, k = (n + h / 30) % 12) => l - a * Math.max(
113
- Math.min(k - 3, 9 - k, 1), -1
114
- )
127
+ f = (n, k = (n + h / 30) % 12) =>
128
+ l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
115
129
  ) => [f(0), f(8), f(4)]
116
130
 
117
131
  export const getColorShade = (col, amt) => {
@@ -122,12 +136,12 @@ export const getColorShade = (col, amt) => {
122
136
  if (r > 255) r = 255
123
137
  else if (r < 0) r = 0
124
138
 
125
- let b = ((num >> 8) & 0x00FF) + amt
139
+ let b = ((num >> 8) & 0x00ff) + amt
126
140
 
127
141
  if (b > 255) b = 255
128
142
  else if (b < 0) b = 0
129
143
 
130
- let g = (num & 0x0000FF) + amt
144
+ let g = (num & 0x0000ff) + amt
131
145
 
132
146
  if (g > 255) g = 255
133
147
  else if (g < 0) g = 0
@@ -138,12 +152,8 @@ export const getColorShade = (col, amt) => {
138
152
  export const mixTwoRgba = (colorA, colorB, range = 0.5) => {
139
153
  const arr = []
140
154
  for (let i = 0; i < 4; i++) {
141
- const round = (i === 3) ? x => x : Math.round
142
- arr[i] = round(
143
- (colorA[i] + (
144
- (colorB[i] - colorA[i]) * range
145
- ))
146
- )
155
+ const round = i === 3 ? x => x : Math.round
156
+ arr[i] = round(colorA[i] + (colorB[i] - colorA[i]) * range)
147
157
  }
148
158
  return `rgba(${arr})`
149
159
  }
@@ -151,15 +161,95 @@ export const mixTwoRgba = (colorA, colorB, range = 0.5) => {
151
161
  export const opacify = (color, opacity) => {
152
162
  const arr = colorStringToRgbaArray(color)
153
163
  if (!arr) {
154
- if (ENV === 'testing' || ENV === 'development') console.warn(color + ' color is not rgba')
164
+ if (isNotProduction()) console.warn(color + ' color is not rgba')
155
165
  return
156
166
  }
157
167
  arr[3] = opacity
158
168
  return `rgba(${arr})`
159
169
  }
160
170
 
171
+ // Check if value is a CSS custom property reference (starts with --)
172
+ export const isCSSVar = (v) => v.charCodeAt(0) === 45 && v.charCodeAt(1) === 45
173
+
174
+ // Regex for CSS native color values - signals passthrough
175
+ export const CSS_NATIVE_COLOR_REGEX = /(?:rgba?\(|hsla?\(|#[0-9a-fA-F]{3,8}\b)/
176
+
177
+ // Regex for Symbols color token: colorName[.opacity][+/-/=tone]
178
+ // +N or -N = relative shade, =N = absolute lightness percentage
179
+ const COLOR_TOKEN_REGEX = /^([a-zA-Z]\w*)(?:\.(\d+))?(?:([+-]\d+|=\d+))?$/
180
+
181
+ /**
182
+ * Parse a color token string into its components.
183
+ * Returns { name, alpha, tone } for valid tokens,
184
+ * { passthrough: value } for CSS native values,
185
+ * or null for non-color tokens (e.g. '10px', '0')
186
+ *
187
+ * Tone prefixes:
188
+ * +N relative shade (add N to RGB)
189
+ * -N relative shade (subtract N from RGB)
190
+ * =N absolute lightness (set HSL lightness to N%)
191
+ */
192
+ export const parseColorToken = (value) => {
193
+ if (!isString(value)) return null
194
+
195
+ // CSS native color passthrough
196
+ if (CSS_NATIVE_COLOR_REGEX.test(value)) return { passthrough: value }
197
+
198
+ // CSS var passthrough
199
+ if (isCSSVar(value)) return { cssVar: value }
200
+
201
+ const match = value.match(COLOR_TOKEN_REGEX)
202
+ if (!match) return null
203
+
204
+ const [, name, alphaDigits, rawTone] = match
205
+ const alpha = alphaDigits !== undefined ? `0.${alphaDigits}` : undefined
206
+ // Strip '=' prefix for absolute tones — getRgbTone handles unsigned values as absolute
207
+ const tone = rawTone && rawTone[0] === '=' ? rawTone.slice(1) : rawTone
208
+
209
+ return { name, alpha, tone }
210
+ }
211
+
212
+ /**
213
+ * Check if a getColor() result is a resolved CSS color
214
+ * (as opposed to the original unresolved value being returned)
215
+ */
216
+ export const isResolvedColor = (result) => {
217
+ return isString(result) && (
218
+ result.includes('rgb') ||
219
+ result.includes('var(') ||
220
+ result.includes('#')
221
+ )
222
+ }
223
+
224
+ /**
225
+ * Split a string by commas, respecting parenthesized groups.
226
+ * e.g. 'rgba(0,0,0,0.1), white.5 0 A' → ['rgba(0,0,0,0.1)', ' white.5 0 A']
227
+ */
228
+ export const splitTopLevelCommas = (value) => {
229
+ const result = []
230
+ let current = ''
231
+ let depth = 0
232
+
233
+ for (const char of value) {
234
+ if (char === '(') depth += 1
235
+ else if (char === ')' && depth > 0) depth -= 1
236
+
237
+ if (char === ',' && depth === 0) {
238
+ result.push(current)
239
+ current = ''
240
+ continue
241
+ }
242
+
243
+ current += char
244
+ }
245
+
246
+ if (current.length || !result.length) result.push(current)
247
+ return result
248
+ }
249
+
161
250
  export const getRgbTone = (rgb, tone) => {
162
- if (isString(rgb) && rgb.includes('rgb')) rgb = colorStringToRgbaArray(rgb).join(', ')
251
+ if (isString(rgb) && rgb.includes('rgb'))
252
+ rgb = colorStringToRgbaArray(rgb).join(', ')
163
253
  if (isString(rgb)) rgb = rgb.split(',').map(v => parseFloat(v.trim()))
164
254
  if (isNumber(tone)) tone += ''
165
255
  const toHex = rgbArrayToHex(rgb)
@@ -172,7 +262,7 @@ export const getRgbTone = (rgb, tone) => {
172
262
  const [r, g, b] = rgb
173
263
  const hsl = rgbToHSL(r, g, b)
174
264
  const [h, s, l] = hsl // eslint-disable-line
175
- const newRgb = hslToRgb(h, s, parseFloat(tone) / 100 * 255)
265
+ const newRgb = hslToRgb(h, s, (parseFloat(tone) / 100) * 255)
176
266
  return newRgb
177
267
  }
178
268
  }
package/src/utils/font.js CHANGED
@@ -7,41 +7,81 @@ export const getDefaultOrFirstKey = (LIBRARY, key) => {
7
7
  return hasValue && LIBRARY[hasValue] && LIBRARY[hasValue].value
8
8
  }
9
9
 
10
- export const getFontFormat = url => url.split(/[#?]/)[0].split('.').pop().trim()
10
+ export const getFontFormat = (url) => {
11
+ const ext = url.split(/[#?]/)[0].split('.').pop().trim()
12
+ if (['woff2', 'woff', 'ttf', 'otf', 'eot'].includes(ext)) return ext
13
+ return null
14
+ }
15
+
16
+ export const isGoogleFontsUrl = (url) =>
17
+ url &&
18
+ (url.includes('fonts.googleapis.com') || url.includes('fonts.gstatic.com'))
11
19
 
12
- export const setInCustomFontMedia = str => `@font-face { ${str} }`
20
+ export const setFontImport = (url) => `@import url('${url}');`
13
21
 
14
- export const setCustomFont = (name, url, weight) => `
22
+ export const setInCustomFontMedia = (str) => `@font-face { ${str} }`
23
+
24
+ export const setCustomFont = (name, url, weight, options = {}) => {
25
+ const format = getFontFormat(url)
26
+ const formatStr = format ? ` format('${format}')` : ''
27
+ return `
15
28
  font-family: '${name}';
16
- font-style: normal;
17
- ${weight && `font-weight: ${weight};`}
18
- src: url('${url}') format('${getFontFormat(url)}');`
29
+ font-style: normal;${
30
+ weight
31
+ ? `
32
+ font-weight: ${weight};`
33
+ : ''
34
+ }${
35
+ options.fontStretch
36
+ ? `
37
+ font-stretch: ${options.fontStretch};`
38
+ : ''
39
+ }${
40
+ options.fontDisplay
41
+ ? `
42
+ font-display: ${options.fontDisplay};`
43
+ : ''
44
+ }
45
+ src: url('${url}')${formatStr};`
46
+ }
19
47
 
20
- export const setCustomFontMedia = (name, url, weight) => `@font-face {
21
- ${setCustomFont(name, url, weight)}
48
+ export const setCustomFontMedia = (
49
+ name,
50
+ url,
51
+ weight,
52
+ options
53
+ ) => `@font-face {${setCustomFont(name, url, weight, options)}
22
54
  }`
23
- // src: url('${url}') format('${getFontFormat(url)}');
24
55
 
25
56
  export const getFontFaceEach = (name, weights) => {
26
57
  const keys = Object.keys(weights)
27
- return keys.map(key => {
58
+ return keys.map((key) => {
28
59
  const { url, fontWeight } = weights[key]
29
60
  return setCustomFont(name, url, fontWeight)
30
61
  })
31
62
  }
32
63
 
33
- export const getFontFace = LIBRARY => {
64
+ export const getFontFace = (LIBRARY) => {
34
65
  const keys = Object.keys(LIBRARY)
35
- return keys.map(key => getFontFaceEach(key, LIBRARY[key].value))
66
+ return keys.map((key) => getFontFaceEach(key, LIBRARY[key].value))
36
67
  }
37
68
 
38
69
  export const getFontFaceEachString = (name, weights) => {
70
+ if (weights && weights.isVariable) {
71
+ if (isGoogleFontsUrl(weights.url)) {
72
+ return setFontImport(weights.url)
73
+ }
74
+ return setCustomFontMedia(name, weights.url, weights.fontWeight, {
75
+ fontStretch: weights.fontStretch,
76
+ fontDisplay: weights.fontDisplay || 'swap'
77
+ })
78
+ }
39
79
  const isArr = weights[0]
40
80
  if (isArr) return getFontFaceEach(name, weights).map(setInCustomFontMedia)
41
81
  return setCustomFontMedia(name, weights.url)
42
82
  }
43
83
 
44
- export const getFontFaceString = LIBRARY => {
84
+ export const getFontFaceString = (LIBRARY) => {
45
85
  const keys = Object.keys(LIBRARY)
46
- return keys.map(key => getFontFaceEachString(key, LIBRARY[key].value))
86
+ return keys.map((key) => getFontFaceEachString(key, LIBRARY[key].value))
47
87
  }