@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 { 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
@@ -1,12 +1,12 @@
1
1
  'use strict'
2
2
 
3
- import { toCamelCase } from '@symbo.ls/smbls-utils'
3
+ import { toCamelCase } from '@symbo.ls/utils'
4
4
  import { getActiveConfig } from '../factory.js'
5
5
  import {
6
6
  applySequenceVars,
7
7
  generateSequence,
8
8
  getSequenceValuePropertyPair
9
- } from '../utils'
9
+ } from '../utils/index.js'
10
10
 
11
11
  export const applyTimingSequence = () => {
12
12
  const CONFIG = getActiveConfig()
@@ -1,6 +1,6 @@
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
5
 
6
6
  import {
@@ -9,7 +9,7 @@ import {
9
9
  findHeadings,
10
10
  generateSequence,
11
11
  getSequenceValuePropertyPair
12
- } from '../utils'
12
+ } from '../utils/index.js'
13
13
 
14
14
  export const runThroughMedia = FACTORY => {
15
15
  const CONFIG = getActiveConfig()
@@ -35,14 +35,23 @@ export const runThroughMedia = FACTORY => {
35
35
  unit
36
36
  })
37
37
 
38
+ const VIEWPORT_UNITS = new Set(['vw', 'vh', 'vmin', 'vmax', 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh'])
39
+ const inheritedUnit = mediaValue.unit || unit
40
+ const mediaUnit = (!mediaValue.unit && VIEWPORT_UNITS.has(unit)) ? 'rem' : inheritedUnit
38
41
  const query = MEDIA[mediaName]
39
42
  const media =
40
43
  '@media ' + (query === 'print' ? `${query}` : `screen and ${query}`)
41
44
  TYPOGRAPHY.templates[media] = {
42
- fontSize: mediaValue.base / TYPOGRAPHY.browserDefault + unit
45
+ fontSize: mediaValue.base / TYPOGRAPHY.browserDefault + mediaUnit
43
46
  }
44
47
 
45
48
  if (!mediaRegenerate) {
49
+ merge(mediaValue, {
50
+ sequence: {},
51
+ scales: {},
52
+ vars: {}
53
+ })
54
+ generateSequence(mediaValue)
46
55
  applyMediaSequenceVars(FACTORY, prop)
47
56
  continue
48
57
  }
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- import { isString, isObject, exec } from '@domql/utils'
4
- import { getActiveConfig } from '../factory'
3
+ import { isString, isObject, exec } from '@symbo.ls/utils'
4
+ import { getActiveConfig } from '../factory.js'
5
5
  import {
6
6
  getSpacingByKey,
7
7
  getColor,
@@ -12,7 +12,7 @@ import {
12
12
  getSpacingBasedOnRatio,
13
13
  checkIfBoxSize,
14
14
  splitSpacedValue
15
- } from '../system'
15
+ } from '../system/index.js'
16
16
  import {
17
17
  getFnPrefixAndValue,
18
18
  isResolvedColor,
@@ -20,7 +20,7 @@ import {
20
20
  CSS_NATIVE_COLOR_REGEX,
21
21
  splitTopLevelCommas,
22
22
  parseColorToken
23
- } from '../utils'
23
+ } from '../utils/index.js'
24
24
 
25
25
  const BORDER_STYLES = new Set([
26
26
  'none', 'hidden', 'dotted', 'dashed', 'solid', 'double',
@@ -6,7 +6,7 @@ import {
6
6
  isString,
7
7
  isNumber,
8
8
  isNotProduction
9
- } from '@domql/utils'
9
+ } from '@symbo.ls/utils'
10
10
 
11
11
  export const colorStringToRgbaArray = color => {
12
12
  if (color === '') return [0, 0, 0, 0]
@@ -175,6 +175,7 @@ export const isCSSVar = (v) => v.charCodeAt(0) === 45 && v.charCodeAt(1) === 45
175
175
  export const CSS_NATIVE_COLOR_REGEX = /(?:rgba?\(|hsla?\(|#[0-9a-fA-F]{3,8}\b)/
176
176
 
177
177
  // Regex for Symbols color token: colorName[.opacity][+/-/=tone]
178
+ // Supports hyphenated names like 'highlight-reversed', 'canvas-card', 'line-highlight'
178
179
  // +N or -N = relative shade, =N = absolute lightness percentage
179
180
  const COLOR_TOKEN_REGEX = /^([a-zA-Z]\w*)(?:\.(\d+))?(?:([+-]\d+|=\d+))?$/
180
181
 
package/src/utils/font.js CHANGED
@@ -16,6 +16,9 @@ export const getDefaultOrFirstKey = (LIBRARY, key) => {
16
16
  }
17
17
 
18
18
  export const getFontFormat = (url) => {
19
+ // Tolerate missing urls — a font weight entry without a `url` should produce
20
+ // no `format(...)` hint rather than crashing the entire CSS pipeline.
21
+ if (typeof url !== 'string' || !url) return null
19
22
  const ext = url.split(/[#?]/)[0].split('.').pop().trim()
20
23
  if (['woff2', 'woff', 'ttf', 'otf', 'eot'].includes(ext)) return ext
21
24
  return null
@@ -30,7 +33,10 @@ export const setFontImport = (url) => `@import url('${url}');`
30
33
  export const setInCustomFontMedia = (str) => `@font-face { ${str} }`
31
34
 
32
35
  export const setCustomFont = (name, url, weight, options = {}) => {
33
- const urls = Array.isArray(url) ? url : [url]
36
+ // Filter out undefined / empty entries so a missing weight URL doesn't
37
+ // emit a broken `url('undefined')` rule or crash on .split downstream.
38
+ const rawUrls = Array.isArray(url) ? url : [url]
39
+ const urls = rawUrls.filter((u) => typeof u === 'string' && u)
34
40
  const srcList = urls.map((u) => {
35
41
  const format = getFontFormat(u)
36
42
  const formatStr = format ? ` format('${format}')` : ''
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- import { isString } from '@domql/utils'
4
- import { toDashCase } from '@symbo.ls/smbls-utils'
3
+ import { isString } from '@symbo.ls/utils'
4
+ import { toDashCase } from '@symbo.ls/utils'
5
5
  import { getActiveConfig } from '../factory.js'
6
6
  import { CSS_UNITS, isScalingUnit } from './unit.js'
7
7
  import { isCSSVar } from './color.js'
@@ -47,7 +47,8 @@ const setSequenceValue = (props, sequenceProps) => {
47
47
  variable
48
48
  }
49
49
  sequenceProps.scales[key] = scaling
50
- sequenceProps.vars[variable] = scaling + sequenceProps.unit
50
+ const varUnit = VIEWPORT_UNITS.has(sequenceProps.unit) ? 'rem' : sequenceProps.unit
51
+ sequenceProps.vars[variable] = scaling + varUnit
51
52
  }
52
53
 
53
54
  export const getFnPrefixAndValue = (val) => {
@@ -57,14 +58,18 @@ export const getFnPrefixAndValue = (val) => {
57
58
  return [prefix, value]
58
59
  }
59
60
 
61
+ const VIEWPORT_UNITS = new Set(['vw', 'vh', 'vmin', 'vmax', 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh'])
62
+
60
63
  export const setScalingVar = (key, sequenceProps) => {
61
- const { base, type, unit } = sequenceProps
64
+ const { base, type, unit: rawUnit } = sequenceProps
65
+ const unit = VIEWPORT_UNITS.has(rawUnit) ? 'rem' : rawUnit
62
66
 
63
67
  const defaultVal = (isScalingUnit(unit) ? 1 : base) + unit
64
68
 
65
69
  if (key === 0) return defaultVal
66
70
 
67
- const prefix = '--' + (type && type.replace('.', '-'))
71
+ const vp = sequenceProps.varPrefix ? sequenceProps.varPrefix + '-' : ''
72
+ const prefix = '--' + vp + (type && type.replace('.', '-'))
68
73
  const ratioVar = `${prefix}-ratio`
69
74
 
70
75
  if (key > 0) {
@@ -73,6 +78,15 @@ export const setScalingVar = (key, sequenceProps) => {
73
78
  }
74
79
  if (key < 0) {
75
80
  const nextLetterKey = numToLetterMap[key + 1]
81
+ const absKey = Math.abs(key)
82
+ const phiPow = Math.pow(PHI, 2 * absKey)
83
+ const prevAscKey = numToLetterMap[absKey]
84
+ // Clamp: max(current / ratio, normalized * ratio^n / phi^2n)
85
+ // current chain: A / ratio^n
86
+ // normalized: A * ratio^n / phi^(2n) = ASC mirror / phi^(2n)
87
+ if (prevAscKey) {
88
+ return `max(calc(var(${prefix}-${nextLetterKey}) / var(${ratioVar})), calc(var(${prefix}-${prevAscKey}) / ${phiPow.toFixed(4)}))`
89
+ }
76
90
  return `calc(var(${prefix}-${nextLetterKey}) / var(${ratioVar}))`
77
91
  }
78
92
  }
@@ -82,7 +96,8 @@ export const setSubScalingVar = (index, arr, variable, sequenceProps) => {
82
96
  const skipMiddle = index === 2 && arr.length === 2
83
97
  const indexMapWithLength = skipMiddle ? index + 1 : index
84
98
 
85
- const prefix = '--' + (type && type.replace('.', '-'))
99
+ const vp = sequenceProps.varPrefix ? sequenceProps.varPrefix + '-' : ''
100
+ const prefix = '--' + vp + (type && type.replace('.', '-'))
86
101
  const subRatioVarPrefix = `${prefix}-sub-ratio-`
87
102
 
88
103
  return `calc(var(${variable}) * var(${subRatioVarPrefix + indexMapWithLength}))`
@@ -136,24 +151,25 @@ export const generateSubSequence = (props, sequenceProps) => {
136
151
  })
137
152
  }
138
153
 
154
+ const PHI = 1.618
155
+
139
156
  const switchSequenceOnNegative = (key, base, ratio) => {
140
- // const values = Object.values(SEQUENCE)
141
- // const index = values.indexOf(ratio)
142
- // const diffRatio = ratio / SPACING.ratio
143
- // const total = values[values.length - 1] - values[0]
144
- // const avg = total / 2
145
- // const diff = avg - ratio
146
- // const scale = total / ratio
147
- // const finalDiff = avg + avg / diffRatio
148
-
149
- // if (key < 0) return base * Math.pow(avg, key)
150
- return base * Math.pow(ratio, key)
157
+ if (key >= 0) return base * Math.pow(ratio, key)
158
+
159
+ // DESC: clamp between current and normalized to preserve proportions
160
+ // Below golden ratio: use current (1/ratio^n) nothing changes
161
+ // Above golden ratio: use normalized (ratio^n / phi^2n) — proportion stays constant
162
+ const absKey = Math.abs(key)
163
+ const current = base * Math.pow(ratio, key) // base / ratio^n
164
+ const normalized = base * Math.pow(ratio, absKey) / Math.pow(PHI, 2 * absKey) // base * ratio^n / phi^(2n)
165
+ return Math.max(current, normalized)
151
166
  }
152
167
 
153
168
  export const generateSequence = (sequenceProps) => {
154
169
  const { type, base, ratio, range, subSequence } = sequenceProps
155
170
  const n = Math.abs(range[0]) + Math.abs(range[1])
156
- const prefix = '--' + (type && type.replace('.', '-')) + '-'
171
+ const vp = sequenceProps.varPrefix ? sequenceProps.varPrefix + '-' : ''
172
+ const prefix = '--' + vp + (type && type.replace('.', '-')) + '-'
157
173
 
158
174
  for (let i = 0; i <= n; i++) {
159
175
  const key = range[1] - i
@@ -204,7 +220,8 @@ export const generateSequencePosition = (sequenceProps, position = 0) => {
204
220
 
205
221
  const value = base * Math.pow(ratio, index)
206
222
  const scaling = ~~((value / base) * 100) / 100
207
- const prefix = '--' + (type && type.replace('.', '-')) + '-'
223
+ const vp = sequenceProps.varPrefix ? sequenceProps.varPrefix + '-' : ''
224
+ const prefix = '--' + vp + (type && type.replace('.', '-')) + '-'
208
225
  const variable = prefix + letterKey
209
226
  const scalingVariable = setScalingVar(index, sequenceProps)
210
227
 
@@ -256,7 +273,8 @@ export const getSequenceValue = (value = 'A', sequenceProps) => {
256
273
  ]
257
274
  if (skipArr.includes(value)) return value
258
275
 
259
- const prefix = `--${toDashCase(sequenceProps.type.replace('.', '-'))}-`
276
+ const vp = sequenceProps.varPrefix ? sequenceProps.varPrefix + '-' : ''
277
+ const prefix = `--${vp}${toDashCase(sequenceProps.type.replace('.', '-'))}-`
260
278
  const letterVal = value.toUpperCase()
261
279
  const isNegative = letterVal.slice(0, 1) === '-' ? '-' : ''
262
280
  let absValue = isNegative ? letterVal.slice(1) : letterVal
@@ -316,15 +334,14 @@ export const getSequenceValueBySymbols = (value, sequenceProps) => {
316
334
  )
317
335
  if (!mathArr.length) return value
318
336
 
319
- return mathArr
320
- .map((symbol) => {
321
- const valuesArr = value.split(symbol + ' ').map((v) => v.trim())
322
- const transformedValues = valuesArr.map((v) => {
323
- return getSequenceValue(v, sequenceProps)
324
- })
325
- return transformedValues.join(' ' + symbol + ' ')
326
- })
327
- .join('')
337
+ // Tokenize the expression preserving all operators, then resolve each token
338
+ const symbolRegex = /(\s*[+\-*/,]\s*)/
339
+ const tokens = value.split(symbolRegex)
340
+ return tokens.map((token) => {
341
+ const trimmed = token.trim()
342
+ if (!trimmed || ['+', '-', '*', '/', ','].includes(trimmed)) return token
343
+ return getSequenceValue(trimmed, sequenceProps)
344
+ }).join('')
328
345
  }
329
346
 
330
347
  export const getSequenceValuePropertyPair = (
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
- import { isArray, isNotProduction, isString } from '@domql/utils'
4
- import { getActiveConfig } from '../factory'
3
+ import { isArray, isNotProduction, isString } from '@symbo.ls/utils'
4
+ import { getActiveConfig } from '../factory.js'
5
5
 
6
6
  const isDev = isNotProduction()
7
7
 
@@ -11,8 +11,8 @@ export const generateSprite = (icons) => {
11
11
  let sprite = ''
12
12
 
13
13
  for (const key in icons) {
14
- if (CONFIG.__svg_cache[key]) continue
15
- else CONFIG.__svg_cache[key] = true
14
+ if (CONFIG.__svgCache[key]) continue
15
+ else CONFIG.__svgCache[key] = true
16
16
  sprite += icons[key]
17
17
  }
18
18
 
@@ -75,5 +75,16 @@ export const convertSvgToSymbol = (key, code) => {
75
75
  symbol = symbol.replace(/width="[^"]*"/, '')
76
76
  symbol = symbol.replace(/height="[^"]*"/, '')
77
77
  symbol = symbol.replace('</svg', '</symbol')
78
+ symbol = expandSvgSelfClosing(symbol)
78
79
  return symbol
79
80
  }
81
+
82
+ // SVG elements that are void in SVG but not in HTML.
83
+ // HTML parser treats <line .../> as <line ...> (ignores /), nesting siblings.
84
+ // Fix: expand self-closing tags to explicit open+close for HTML parser compat.
85
+ const SVG_VOID_TAGS = 'line|circle|ellipse|rect|polyline|polygon|path|stop|use|image'
86
+ const SVG_SELF_CLOSING_RE = new RegExp(
87
+ `<(${SVG_VOID_TAGS})\\b([^>]*?)\\s*/>`,
88
+ 'g'
89
+ )
90
+ const expandSvgSelfClosing = (str) => str.replace(SVG_SELF_CLOSING_RE, '<$1$2></$1>')
package/src/utils/var.js CHANGED
@@ -1,13 +1,13 @@
1
1
  'use strict'
2
2
 
3
- import { isObjectLike } from '@domql/utils'
3
+ import { isObjectLike } from '@symbo.ls/utils'
4
4
  import { getActiveConfig } from '../factory.js'
5
5
  import { getSubratio } from './sequence.js'
6
6
  import { isScalingUnit } from './unit.js'
7
7
 
8
8
  export const setVariables = (result, key) => {
9
9
  const CONFIG = getActiveConfig()
10
- const { CSS_VARS } = CONFIG
10
+ const { cssVars: CSS_VARS } = CONFIG
11
11
  if (isObjectLike(result.value)) {
12
12
  // TODO: handle nested object variables
13
13
  } else {
@@ -20,7 +20,8 @@ export const applySequenceGlobalVars = (vars, obj, options) => {
20
20
  const { unit: UNIT } = CONFIG
21
21
  const unit = obj.unit || UNIT.default
22
22
  const { base, ratio, type } = obj
23
- const prefix = '--' + (type && type.replace('.', '-'))
23
+ const vp = obj.varPrefix ? obj.varPrefix + '-' : ''
24
+ const prefix = '--' + vp + (type && type.replace('.', '-'))
24
25
  vars[`${prefix}-base`] = base
25
26
  vars[`${prefix}-unit`] = unit
26
27
  const ratioVar = `${prefix}-ratio`
@@ -36,11 +37,15 @@ export const applySequenceGlobalVars = (vars, obj, options) => {
36
37
  // vars[`${prefix}-sub-ratio-3`] = second
37
38
  }
38
39
 
40
+ // Viewport units are applied to root fontSize only; tokens use 'rem' instead
41
+ const VIEWPORT_UNITS = new Set(['vw', 'vh', 'vmin', 'vmax', 'svw', 'svh', 'lvw', 'lvh', 'dvw', 'dvh'])
42
+
39
43
  export const applySequenceVars = (FACTORY, options = {}) => {
40
44
  const CONFIG = getActiveConfig()
41
- const { unit: UNIT, timing: TIMING, CSS_VARS } = CONFIG
45
+ const { unit: UNIT, timing: TIMING, cssVars: CSS_VARS } = CONFIG
42
46
 
43
- const unit = FACTORY.unit || UNIT.default
47
+ const rawUnit = FACTORY.unit || UNIT.default
48
+ const unit = VIEWPORT_UNITS.has(rawUnit) ? 'rem' : rawUnit
44
49
  const { mediaRegenerate, sequence, scales } = FACTORY
45
50
 
46
51
  if (!mediaRegenerate) {
@@ -72,13 +77,15 @@ export const applySequenceVars = (FACTORY, options = {}) => {
72
77
 
73
78
  export const applyMediaSequenceVars = (FACTORY, media, options = {}) => {
74
79
  const CONFIG = getActiveConfig()
75
- const { unit: UNIT, media: MEDIA, CSS_VARS } = CONFIG
80
+ const { unit: UNIT, media: MEDIA, cssVars: CSS_VARS } = CONFIG
76
81
 
77
82
  const mediaName = media.slice(1)
78
83
 
79
- const unit = FACTORY.unit || UNIT.default
84
+ const mediaConfig = FACTORY[media]
85
+ const rawMediaUnit = mediaConfig.unit || FACTORY.unit || UNIT.default
86
+ const unit = VIEWPORT_UNITS.has(rawMediaUnit) ? 'rem' : rawMediaUnit
80
87
  const { mediaRegenerate } = FACTORY
81
- const { sequence, scales } = FACTORY[media]
88
+ const { sequence, scales } = mediaConfig
82
89
 
83
90
  const query = MEDIA[mediaName]
84
91
  if (!query && CONFIG.verbose) console.warn('Can\'t find media query ', query)
@@ -88,7 +95,18 @@ export const applyMediaSequenceVars = (FACTORY, media, options = {}) => {
88
95
  if (!mediaRegenerate) {
89
96
  let underMediaQuery = CSS_VARS[`@media ${query}`]
90
97
  if (!underMediaQuery) underMediaQuery = CSS_VARS[`@media ${query}`] = {}
91
- applySequenceGlobalVars(underMediaQuery, FACTORY[media], options)
98
+ applySequenceGlobalVars(underMediaQuery, mediaConfig, options)
99
+
100
+ // If unit changed, override token vars with new unit values
101
+ const parentUnit = FACTORY.unit || UNIT.default
102
+ if (unit !== parentUnit && sequence) {
103
+ for (const key in sequence) {
104
+ const item = sequence[key]
105
+ const value = scales[key] + unit
106
+ underMediaQuery[item.variable + '_default'] = value
107
+ underMediaQuery[item.variable] = value
108
+ }
109
+ }
92
110
  return
93
111
  }
94
112