@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.
- package/README.md +2 -2
- package/dist/cjs/defaultConfig/class.js +1 -2
- package/dist/cjs/defaultConfig/font-family.js +6 -6
- package/dist/cjs/defaultConfig/icons.js +2 -2
- package/dist/cjs/defaultConfig/svg.js +2 -2
- package/dist/cjs/defaultConfig/timing.js +1 -1
- package/dist/cjs/factory.js +72 -12
- package/dist/cjs/index.js +6 -4
- package/dist/cjs/set.js +113 -52
- package/dist/cjs/system/color.js +72 -12
- package/dist/cjs/system/document.js +3 -3
- package/dist/cjs/system/font.js +14 -14
- package/dist/cjs/system/reset.js +34 -7
- package/dist/cjs/system/shadow.js +4 -3
- package/dist/cjs/system/spacing.js +18 -18
- package/dist/cjs/system/svg.js +34 -7
- package/dist/cjs/system/theme.js +51 -50
- package/dist/cjs/system/timing.js +6 -6
- package/dist/cjs/system/typography.js +12 -3
- package/dist/cjs/transforms/index.js +4 -4
- package/dist/cjs/utils/color.js +1 -1
- package/dist/cjs/utils/font.js +3 -1
- package/dist/cjs/utils/sequence.js +35 -16
- package/dist/cjs/utils/sprite.js +11 -4
- package/dist/cjs/utils/var.js +23 -9
- package/dist/esm/defaultConfig/class.js +1 -2
- package/dist/esm/defaultConfig/font-family.js +6 -6
- package/dist/esm/defaultConfig/icons.js +2 -2
- package/dist/esm/defaultConfig/svg.js +2 -2
- package/dist/esm/defaultConfig/timing.js +1 -1
- package/dist/esm/factory.js +72 -12
- package/dist/esm/index.js +6 -4
- package/dist/esm/set.js +114 -53
- package/dist/esm/system/color.js +72 -12
- package/dist/esm/system/document.js +3 -3
- package/dist/esm/system/font.js +5 -5
- package/dist/esm/system/reset.js +34 -7
- package/dist/esm/system/shadow.js +4 -3
- package/dist/esm/system/spacing.js +3 -3
- package/dist/esm/system/svg.js +34 -7
- package/dist/esm/system/theme.js +51 -50
- package/dist/esm/system/timing.js +2 -2
- package/dist/esm/system/typography.js +12 -3
- package/dist/esm/transforms/index.js +4 -4
- package/dist/esm/utils/color.js +1 -1
- package/dist/esm/utils/font.js +3 -1
- package/dist/esm/utils/sequence.js +35 -16
- package/dist/esm/utils/sprite.js +11 -4
- package/dist/esm/utils/var.js +23 -9
- package/dist/iife/index.js +728 -302
- package/index.js +1 -0
- package/package.json +11 -14
- package/src/defaultConfig/class.js +2 -1
- package/src/defaultConfig/font-family.js +3 -3
- package/src/defaultConfig/icons.js +1 -1
- package/src/defaultConfig/svg.js +1 -1
- package/src/defaultConfig/timing.js +1 -1
- package/src/factory.js +85 -13
- package/src/index.js +16 -5
- package/src/set.js +156 -63
- package/src/system/color.js +113 -12
- package/src/system/document.js +3 -3
- package/src/system/font.js +5 -5
- package/src/system/reset.js +41 -8
- package/src/system/shadow.js +4 -3
- package/src/system/spacing.js +3 -3
- package/src/system/svg.js +44 -7
- package/src/system/theme.js +87 -64
- package/src/system/timing.js +2 -2
- package/src/system/typography.js +12 -3
- package/src/transforms/index.js +4 -4
- package/src/utils/color.js +2 -1
- package/src/utils/font.js +7 -1
- package/src/utils/sequence.js +46 -29
- package/src/utils/sprite.js +15 -4
- package/src/utils/var.js +27 -9
package/src/system/theme.js
CHANGED
|
@@ -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 '@
|
|
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
|
|
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.
|
|
190
|
-
const MEDIA_VARS = CONFIG.
|
|
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
|
-
//
|
|
203
|
-
//
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
//
|
|
276
|
-
//
|
|
277
|
-
if (
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
//
|
|
282
|
-
const
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
294
|
+
|
|
295
|
+
// `[data-theme="X"]` selector — works 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
|
|
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
|
package/src/system/timing.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { toCamelCase } from '@symbo.ls/
|
|
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()
|
package/src/system/typography.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { merge } from '@
|
|
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 +
|
|
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
|
}
|
package/src/transforms/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { isString, isObject, exec } from '@
|
|
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',
|
package/src/utils/color.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
isString,
|
|
7
7
|
isNumber,
|
|
8
8
|
isNotProduction
|
|
9
|
-
} from '@
|
|
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
|
-
|
|
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}')` : ''
|
package/src/utils/sequence.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { isString } from '@
|
|
4
|
-
import { toDashCase } from '@symbo.ls/
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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 = (
|
package/src/utils/sprite.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { isArray, isNotProduction, isString } from '@
|
|
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.
|
|
15
|
-
else CONFIG.
|
|
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 '@
|
|
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
|
|
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
|
|
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
|
|
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 } =
|
|
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,
|
|
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
|
|