@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/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './src/index.js'
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@symbo.ls/scratch",
|
|
3
3
|
"description": "Φ / CSS framework and methodology.",
|
|
4
4
|
"author": "symbo.ls",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.14.0",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"*.js",
|
|
@@ -10,15 +10,13 @@
|
|
|
10
10
|
],
|
|
11
11
|
"repository": "https://github.com/symbo-ls/smbls",
|
|
12
12
|
"type": "module",
|
|
13
|
-
"module": "./
|
|
13
|
+
"module": "./index.js",
|
|
14
14
|
"unpkg": "./dist/iife/index.js",
|
|
15
15
|
"jsdelivr": "./dist/iife/index.js",
|
|
16
|
-
"main": "./
|
|
16
|
+
"main": "./index.js",
|
|
17
17
|
"exports": {
|
|
18
|
-
".":
|
|
19
|
-
|
|
20
|
-
"require": "./dist/cjs/index.js"
|
|
21
|
-
}
|
|
18
|
+
".": "./index.js",
|
|
19
|
+
"./package.json": "./package.json"
|
|
22
20
|
},
|
|
23
21
|
"source": "src/index.js",
|
|
24
22
|
"publishConfig": {
|
|
@@ -26,18 +24,17 @@
|
|
|
26
24
|
},
|
|
27
25
|
"scripts": {
|
|
28
26
|
"copy:package:cjs": "cp ../../build/package-cjs.json dist/cjs/package.json",
|
|
29
|
-
"build:esm": "
|
|
30
|
-
"build:cjs": "
|
|
31
|
-
"build:iife": "
|
|
27
|
+
"build:esm": "NODE_ENV=$NODE_ENV esbuild $(find src -name '*.js') --target=es2020 --format=esm --outdir=dist/esm --define:process.env.NODE_ENV=process.env.NODE_ENV",
|
|
28
|
+
"build:cjs": "NODE_ENV=$NODE_ENV esbuild $(find src -name '*.js') --target=node18 --format=cjs --outdir=dist/cjs --define:process.env.NODE_ENV=process.env.NODE_ENV",
|
|
29
|
+
"build:iife": "NODE_ENV=$NODE_ENV esbuild src/index.js --bundle --target=es2020 --format=iife --global-name=SmblsScratch --outfile=dist/iife/index.js --define:process.env.NODE_ENV=process.env.NODE_ENV",
|
|
32
30
|
"build": "node ../../build/build.js",
|
|
33
31
|
"prepublish": "npm run build && npm run copy:package:cjs"
|
|
34
32
|
},
|
|
35
33
|
"dependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@symbo.ls/smbls-utils": "^3.8.8",
|
|
34
|
+
"@symbo.ls/utils": "^3.14.0",
|
|
38
35
|
"color-contrast-checker": "^1.5.0"
|
|
39
36
|
},
|
|
40
37
|
"gitHead": "9fc1b79b41cdc725ca6b24aec64920a599634681",
|
|
41
|
-
"browser": "./
|
|
42
|
-
"sideEffects":
|
|
38
|
+
"browser": "./index.js",
|
|
39
|
+
"sideEffects": true
|
|
43
40
|
}
|
|
@@ -6,10 +6,10 @@ var defaultFont = { // eslint-disable-line
|
|
|
6
6
|
type: ''
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export const
|
|
10
|
-
export const
|
|
9
|
+
export const fontFamily = {}
|
|
10
|
+
export const fontFamilyTypes = {
|
|
11
11
|
'sans-serif': 'Helvetica, Arial, sans-serif, --system-default',
|
|
12
12
|
serif: 'Times New Roman, Georgia, serif, --system-default',
|
|
13
13
|
monospace: 'Courier New, monospace, --system-default'
|
|
14
14
|
}
|
|
15
|
-
export const
|
|
15
|
+
export const fontFace = {}
|
package/src/defaultConfig/svg.js
CHANGED
package/src/factory.js
CHANGED
|
@@ -5,28 +5,24 @@ import {
|
|
|
5
5
|
deepMerge,
|
|
6
6
|
isDefined,
|
|
7
7
|
isObject
|
|
8
|
-
} from '@
|
|
9
|
-
import * as CONF from './defaultConfig'
|
|
8
|
+
} from '@symbo.ls/utils'
|
|
9
|
+
import * as CONF from './defaultConfig/index.js'
|
|
10
10
|
|
|
11
|
-
export const
|
|
12
|
-
export const
|
|
11
|
+
export const cssVars = {}
|
|
12
|
+
export const cssMediaVars = {}
|
|
13
13
|
|
|
14
|
-
// Build CONFIG with lowercase/camelCase keys as canonical
|
|
14
|
+
// Build CONFIG with lowercase/camelCase keys as canonical
|
|
15
15
|
const _CONF = CONF
|
|
16
16
|
const _confLower = {}
|
|
17
|
-
const toCamel = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase())
|
|
18
|
-
const toUpper = (s) => s.replace(/([A-Z])/g, '_$1').toUpperCase()
|
|
17
|
+
export const toCamel = (s) => s.replace(/_([a-z])/g, (_, c) => c.toUpperCase())
|
|
19
18
|
for (const key in _CONF) {
|
|
20
19
|
const lower = key.toLowerCase()
|
|
21
20
|
_confLower[lower] = _CONF[key]
|
|
22
|
-
// camelCase alias (e.g. font_family -> fontFamily,
|
|
21
|
+
// camelCase alias (e.g. font_family -> fontFamily, semanticIcons -> semanticIcons)
|
|
23
22
|
const camel = toCamel(lower)
|
|
24
23
|
if (camel !== lower) _confLower[camel] = _CONF[key]
|
|
25
24
|
// keep original key if different from lower
|
|
26
25
|
if (lower !== key) _confLower[key] = _CONF[key]
|
|
27
|
-
// backward compat: add UPPERCASE alias (e.g. typography -> TYPOGRAPHY, fontFamily -> FONT_FAMILY)
|
|
28
|
-
const upper = toUpper(key)
|
|
29
|
-
if (upper !== key) _confLower[upper] = _CONF[key]
|
|
30
26
|
}
|
|
31
27
|
|
|
32
28
|
export const CONFIG = {
|
|
@@ -34,8 +30,11 @@ export const CONFIG = {
|
|
|
34
30
|
useVariable: true,
|
|
35
31
|
useReset: true,
|
|
36
32
|
globalTheme: 'auto',
|
|
37
|
-
|
|
38
|
-
|
|
33
|
+
cssVars,
|
|
34
|
+
cssMediaVars,
|
|
35
|
+
CSS_VARS: cssVars,
|
|
36
|
+
CSS_MEDIA_VARS: cssMediaVars,
|
|
37
|
+
_scratchConfig: true,
|
|
39
38
|
..._confLower
|
|
40
39
|
}
|
|
41
40
|
|
|
@@ -51,7 +50,21 @@ export const activateConfig = (def) => {
|
|
|
51
50
|
return FACTORY[def || FACTORY.active]
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
// Config stack for context-local resolution
|
|
54
|
+
// When processing an element, its context.designSystem is pushed
|
|
55
|
+
// so all getActiveConfig() calls resolve to the right config
|
|
56
|
+
const configStack = []
|
|
57
|
+
|
|
58
|
+
export const pushConfig = (config) => {
|
|
59
|
+
if (config && config._scratchConfig) configStack.push(config)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const popConfig = () => {
|
|
63
|
+
configStack.pop()
|
|
64
|
+
}
|
|
65
|
+
|
|
54
66
|
export const getActiveConfig = (def) => {
|
|
67
|
+
if (configStack.length) return configStack[configStack.length - 1]
|
|
55
68
|
return FACTORY[def || FACTORY.active] || CONFIG
|
|
56
69
|
}
|
|
57
70
|
|
|
@@ -61,3 +74,62 @@ export const setActiveConfig = (newConfig) => {
|
|
|
61
74
|
FACTORY['1'] = deepMerge(newConfig, deepClone(cachedConfig))
|
|
62
75
|
return newConfig
|
|
63
76
|
}
|
|
77
|
+
|
|
78
|
+
// App-level flags that should NOT be inherited by isolated configs
|
|
79
|
+
const APP_FLAGS = [
|
|
80
|
+
'useReset', 'useVariable', 'useFontImport', 'useIconSprite',
|
|
81
|
+
'useSvgSprite', 'useDocumentTheme', 'useDefaultIcons', 'useDefaultConfig',
|
|
82
|
+
'verbose', 'globalTheme'
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
// Keep case-variant aliases pointing to the same object. `factory.js`
|
|
86
|
+
// bootstrap wires these as shared refs, but `deepClone` in `createConfig`
|
|
87
|
+
// breaks the sharing and a later `deepMerge` only writes into one spelling
|
|
88
|
+
// — downstream code that destructures a different spelling then reads
|
|
89
|
+
// stale data (e.g. `applyDocument` reads `fontFamily` while `setValue`
|
|
90
|
+
// writes to `fontfamily`, leaving `DOCUMENT.fontFamily` unset).
|
|
91
|
+
const ALIAS_GROUPS = [
|
|
92
|
+
['fontfamily', 'fontFamily', 'font_family'],
|
|
93
|
+
['fontfamilytypes', 'fontFamilyTypes'],
|
|
94
|
+
['semanticicons', 'semanticIcons'],
|
|
95
|
+
['svgdata', 'svgData']
|
|
96
|
+
]
|
|
97
|
+
const aliasCaseVariants = (cfg) => {
|
|
98
|
+
for (const vs of ALIAS_GROUPS) {
|
|
99
|
+
let merged
|
|
100
|
+
for (const v of vs) {
|
|
101
|
+
const val = cfg[v]
|
|
102
|
+
if (!isObject(val)) continue
|
|
103
|
+
merged = merged ? deepMerge(val, merged) : val
|
|
104
|
+
}
|
|
105
|
+
if (!merged) continue
|
|
106
|
+
for (const v of vs) if (isObject(cfg[v])) cfg[v] = merged
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export const createConfig = (name, overrides, { cleanBase = false } = {}) => {
|
|
111
|
+
// Secondary apps inheriting the primary app's processed config used to
|
|
112
|
+
// drag primary-specific tokens (e.g. an editor's brand themes like
|
|
113
|
+
// `sepia`, `canvas-card`) into the secondary's design system — breaking
|
|
114
|
+
// iframe isolation. When `cleanBase: true` is passed, clone the initial
|
|
115
|
+
// framework cache instead so the secondary starts from the same blank
|
|
116
|
+
// slate as the primary did. Legacy callers (no option) keep the old
|
|
117
|
+
// active-inherit behavior for backward compatibility.
|
|
118
|
+
const activeBase = cleanBase ? null : getActiveConfig()
|
|
119
|
+
const base = deepClone(
|
|
120
|
+
!cleanBase && activeBase && activeBase._scratchConfig ? activeBase : cachedConfig
|
|
121
|
+
)
|
|
122
|
+
// Remove app-level flags — they must come from overrides explicitly
|
|
123
|
+
for (const flag of APP_FLAGS) {
|
|
124
|
+
delete base[flag]
|
|
125
|
+
}
|
|
126
|
+
const cfg = deepMerge(overrides || {}, base)
|
|
127
|
+
cfg.cssVars = {}
|
|
128
|
+
cfg.cssMediaVars = {}
|
|
129
|
+
cfg.CSS_VARS = cfg.cssVars
|
|
130
|
+
cfg.CSS_MEDIA_VARS = cfg.cssMediaVars
|
|
131
|
+
cfg._scratchConfig = true
|
|
132
|
+
aliasCaseVariants(cfg)
|
|
133
|
+
if (name) FACTORY[name] = cfg
|
|
134
|
+
return cfg
|
|
135
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
// Note on the dual-form imports below: each `./X/index.js` is referenced
|
|
4
|
+
// twice in this file — once as a namespace import, once as a barrel
|
|
5
|
+
// re-export. That dual reference is load-bearing for parcel's optimizer:
|
|
6
|
+
// if a directory is only seen via barrel `export *` here AND via
|
|
7
|
+
// `import * as` in a different file (as factory.js does for
|
|
8
|
+
// ./defaultConfig), parcel collapses the dep-map entry to the bare
|
|
9
|
+
// directory form and marks it `false`, breaking runtime `require` for
|
|
10
|
+
// the explicit `/index.js` path. Keeping the namespace import in this
|
|
11
|
+
// file alongside the barrel forces parcel to track both forms.
|
|
12
|
+
|
|
3
13
|
import * as scratchUtils from './utils/index.js'
|
|
4
14
|
import * as scratchSystem from './system/index.js'
|
|
15
|
+
import * as scratchDefaultConfig from './defaultConfig/index.js'
|
|
5
16
|
|
|
6
17
|
export * from './factory.js'
|
|
7
|
-
export * from './defaultConfig'
|
|
8
|
-
export * from './system'
|
|
9
|
-
export * from './utils'
|
|
10
|
-
export * from './transforms'
|
|
18
|
+
export * from './defaultConfig/index.js'
|
|
19
|
+
export * from './system/index.js'
|
|
20
|
+
export * from './utils/index.js'
|
|
21
|
+
export * from './transforms/index.js'
|
|
11
22
|
export * from './set.js'
|
|
12
23
|
|
|
13
|
-
export { scratchUtils, scratchSystem }
|
|
24
|
+
export { scratchUtils, scratchSystem, scratchDefaultConfig }
|
package/src/set.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
import { FACTORY, getActiveConfig, setActiveConfig } from './factory.js' // eslint-disable-line no-unused-vars
|
|
3
|
+
import { FACTORY, getActiveConfig, setActiveConfig, toCamel } from './factory.js' // eslint-disable-line no-unused-vars
|
|
4
4
|
import {
|
|
5
5
|
setColor,
|
|
6
6
|
setGradient,
|
|
@@ -15,32 +15,36 @@ import {
|
|
|
15
15
|
applyTimingSequence,
|
|
16
16
|
applyDocument,
|
|
17
17
|
setShadow
|
|
18
|
-
} from './system'
|
|
18
|
+
} from './system/index.js'
|
|
19
19
|
|
|
20
|
-
import { deepMerge } from '@
|
|
20
|
+
import { deepMerge, isObject, isArray } from '@symbo.ls/utils'
|
|
21
21
|
|
|
22
22
|
const setVars = (val, key) => {
|
|
23
23
|
const CONFIG = getActiveConfig()
|
|
24
|
-
const {
|
|
24
|
+
const { cssVars } = CONFIG
|
|
25
25
|
const varName = key.startsWith('--') ? key : `--${key}`
|
|
26
|
-
|
|
26
|
+
cssVars[varName] = val
|
|
27
27
|
return val
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const asIs = (val) => val
|
|
31
31
|
|
|
32
|
+
// `toCamel` (imported above) normalises snake_case designSystem keys
|
|
33
|
+
// (e.g. `font_family` → `fontFamily`) before transformer dispatch — callers
|
|
34
|
+
// can use either casing without needing to register both variants here.
|
|
35
|
+
|
|
32
36
|
export const VALUE_TRANSFORMERS = {
|
|
33
37
|
color: setColor,
|
|
34
38
|
gradient: setGradient,
|
|
35
39
|
font: setFont,
|
|
36
|
-
|
|
40
|
+
fontFamily: setFontFamily,
|
|
37
41
|
fontfamily: setFontFamily,
|
|
38
42
|
theme: setTheme,
|
|
39
43
|
icons: setSvgIcon,
|
|
40
|
-
|
|
44
|
+
semanticIcons: asIs,
|
|
41
45
|
semanticicons: asIs,
|
|
42
46
|
svg: setSVG,
|
|
43
|
-
|
|
47
|
+
svgData: asIs,
|
|
44
48
|
typography: asIs,
|
|
45
49
|
shadow: setShadow,
|
|
46
50
|
spacing: asIs,
|
|
@@ -63,13 +67,15 @@ export const VALUE_TRANSFORMERS = {
|
|
|
63
67
|
*/
|
|
64
68
|
export const setValue = (factoryName, value, key) => {
|
|
65
69
|
const CONFIG = getActiveConfig()
|
|
66
|
-
const
|
|
67
|
-
const
|
|
70
|
+
const camelName = toCamel(factoryName)
|
|
71
|
+
const lowerName = camelName.toLowerCase()
|
|
72
|
+
const FACTORY = CONFIG[camelName] || CONFIG[lowerName] || CONFIG[factoryName]
|
|
68
73
|
|
|
69
|
-
|
|
74
|
+
const transformer = VALUE_TRANSFORMERS[camelName] || VALUE_TRANSFORMERS[lowerName]
|
|
75
|
+
if (transformer) {
|
|
70
76
|
try {
|
|
71
|
-
const result =
|
|
72
|
-
FACTORY[key] = result
|
|
77
|
+
const result = transformer(value, key)
|
|
78
|
+
if (FACTORY) FACTORY[key] = result
|
|
73
79
|
return FACTORY
|
|
74
80
|
} catch (error) {
|
|
75
81
|
if (CONFIG.verbose)
|
|
@@ -84,7 +90,16 @@ export const setValue = (factoryName, value, key) => {
|
|
|
84
90
|
export const setEach = (factoryName, props) => {
|
|
85
91
|
const CONFIG = getActiveConfig()
|
|
86
92
|
const lowerName = factoryName.toLowerCase()
|
|
87
|
-
|
|
93
|
+
// Process primitive (string/number) values before composite (array/object)
|
|
94
|
+
// ones so composite values can dereference primitives by name. Without this
|
|
95
|
+
// ordering, a color like `selectedDay: ['--blue .12', '--blue .18']` could
|
|
96
|
+
// be processed before `blue` itself was registered in CONFIG.color, leaving
|
|
97
|
+
// refColor undefined and falling back to CSS keyword resolution.
|
|
98
|
+
const keys = Object.keys(props).sort((a, b) => {
|
|
99
|
+
const aComposite = isObject(props[a]) || isArray(props[a]) ? 1 : 0
|
|
100
|
+
const bComposite = isObject(props[b]) || isArray(props[b]) ? 1 : 0
|
|
101
|
+
return aComposite - bComposite
|
|
102
|
+
})
|
|
88
103
|
|
|
89
104
|
keys.forEach((key) => {
|
|
90
105
|
try {
|
|
@@ -105,49 +120,90 @@ export const setEach = (factoryName, props) => {
|
|
|
105
120
|
return CONFIG[lowerName] || CONFIG[factoryName]
|
|
106
121
|
}
|
|
107
122
|
|
|
108
|
-
export const changeGlobalTheme = (newTheme) => {
|
|
109
|
-
|
|
123
|
+
export const changeGlobalTheme = (newTheme, targetConfig) => {
|
|
124
|
+
// Accept an explicit config so scoped code (inside pushConfig) can
|
|
125
|
+
// target the right app's theme without relying on the stack timing.
|
|
126
|
+
const CONFIG = targetConfig || getActiveConfig()
|
|
110
127
|
CONFIG.globalTheme = newTheme
|
|
111
128
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
129
|
+
// Apply the theme on the scope root. For a single-design-system page we
|
|
130
|
+
// write to `<html>`; multiple design systems on the same page each carry
|
|
131
|
+
// their own root element (set by the caller on CONFIG.themeRoot), so the
|
|
132
|
+
// attribute is applied there and leaves the rest of the document alone.
|
|
133
|
+
//
|
|
134
|
+
// `'auto'` switches to OS-driven mode: we resolve the current
|
|
135
|
+
// `prefers-color-scheme` and register (once) a listener so later OS
|
|
136
|
+
// changes flip the attribute live.
|
|
137
|
+
// Resolve the target document from the config, not the global `document`.
|
|
138
|
+
// In a multi-app setup the CONFIG belongs to an iframe — its themeRoot is
|
|
139
|
+
// inside iframe doc, and its stylesheets live in iframe doc. Reading
|
|
140
|
+
// global `document` here flips the parent's data-theme attribute instead.
|
|
141
|
+
// `CONFIG.document` is also a factory bucket (font/theme defaults consumed
|
|
142
|
+
// by `applyDocument`); only treat it as a DOM document override if it
|
|
143
|
+
// actually looks like one.
|
|
144
|
+
const configDoc = CONFIG.document && CONFIG.document.documentElement ? CONFIG.document : null
|
|
145
|
+
const targetDoc = configDoc || (typeof document !== 'undefined' ? document : null)
|
|
146
|
+
const targetWin = (targetDoc && targetDoc.defaultView) || (typeof window !== 'undefined' ? window : null)
|
|
147
|
+
if (targetDoc) {
|
|
148
|
+
const root = CONFIG.themeRoot || targetDoc.documentElement
|
|
149
|
+
const forced = newTheme && newTheme !== 'auto'
|
|
150
|
+
if (forced) {
|
|
151
|
+
root.setAttribute('data-theme', newTheme)
|
|
152
|
+
// Mirror `data-theme` into CSS `color-scheme` so Chrome stops
|
|
153
|
+
// forcibly re-rendering form controls (buttons, inputs, selects) in
|
|
154
|
+
// the opposite scheme of the design system. Only the standard
|
|
155
|
+
// `dark`/`light` schemes map to a UA color-scheme; custom schemes
|
|
156
|
+
// (sepia etc.) fall back to `light dark` so the browser picks by OS.
|
|
157
|
+
if (newTheme === 'dark' || newTheme === 'light') {
|
|
158
|
+
root.style.colorScheme = newTheme
|
|
159
|
+
} else {
|
|
160
|
+
root.style.colorScheme = 'light dark'
|
|
161
|
+
}
|
|
162
|
+
} else if (targetWin && targetWin.matchMedia) {
|
|
163
|
+
const apply = (mq) => { root.setAttribute('data-theme', mq.matches ? 'dark' : 'light') }
|
|
164
|
+
const mq = targetWin.matchMedia('(prefers-color-scheme: dark)')
|
|
165
|
+
apply(mq)
|
|
166
|
+
root.style.colorScheme = 'light dark'
|
|
167
|
+
if (!CONFIG.__prefersListener) {
|
|
168
|
+
CONFIG.__prefersListener = apply
|
|
169
|
+
try { mq.addEventListener('change', apply) } catch (e) { mq.addListener(apply) }
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
root.setAttribute('data-theme', 'light')
|
|
173
|
+
root.style.colorScheme = 'light'
|
|
174
|
+
}
|
|
120
175
|
}
|
|
121
176
|
|
|
122
|
-
// Clear
|
|
123
|
-
|
|
124
|
-
|
|
177
|
+
// Clear theme-related CSS vars (with or without varPrefix)
|
|
178
|
+
const themeVarPrefix = CONFIG.varPrefix ? `--${CONFIG.varPrefix}-theme-` : '--theme-'
|
|
179
|
+
for (const key in CONFIG.cssVars) {
|
|
180
|
+
if (key.startsWith(themeVarPrefix)) delete CONFIG.cssVars[key]
|
|
125
181
|
}
|
|
126
182
|
|
|
127
|
-
//
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
// Deep clone to avoid re-mutating the originals
|
|
131
|
-
const fresh = JSON.parse(JSON.stringify(source))
|
|
132
|
-
CONFIG.theme = fresh
|
|
133
|
-
setEach('theme', fresh)
|
|
183
|
+
// Clear all cssMediaVars (media queries + data-theme selectors)
|
|
184
|
+
for (const key in CONFIG.cssMediaVars) {
|
|
185
|
+
delete CONFIG.cssMediaVars[key]
|
|
134
186
|
}
|
|
135
187
|
|
|
136
|
-
// Apply updated CSS vars to the
|
|
137
|
-
|
|
138
|
-
|
|
188
|
+
// Apply updated CSS vars to the config's scoped stylesheet rule
|
|
189
|
+
// (`:root` for the primary app, `[data-smbls-app="<key>"]` for secondary apps).
|
|
190
|
+
if (targetDoc && CONFIG.cssVars) {
|
|
191
|
+
const targetSelector = CONFIG.scopeSelector || ':root'
|
|
192
|
+
const sheets = targetDoc.styleSheets
|
|
139
193
|
for (let i = 0; i < sheets.length; i++) {
|
|
140
194
|
try {
|
|
141
195
|
const rules = sheets[i].cssRules
|
|
142
196
|
for (let j = 0; j < rules.length; j++) {
|
|
143
|
-
if (rules[j].selectorText ===
|
|
144
|
-
for (const key in CONFIG.
|
|
145
|
-
rules[j].style.setProperty(key, CONFIG.
|
|
197
|
+
if (rules[j].selectorText === targetSelector) {
|
|
198
|
+
for (const key in CONFIG.cssVars) {
|
|
199
|
+
rules[j].style.setProperty(key, CONFIG.cssVars[key])
|
|
146
200
|
}
|
|
147
201
|
return CONFIG
|
|
148
202
|
}
|
|
149
203
|
}
|
|
150
|
-
} catch (e) {
|
|
204
|
+
} catch (e) {
|
|
205
|
+
/* cross-origin stylesheet */
|
|
206
|
+
}
|
|
151
207
|
}
|
|
152
208
|
}
|
|
153
209
|
|
|
@@ -157,7 +213,10 @@ export const changeGlobalTheme = (newTheme) => {
|
|
|
157
213
|
const SET_OPTIONS = {}
|
|
158
214
|
|
|
159
215
|
export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
160
|
-
|
|
216
|
+
// Accept an explicit config instance via options.config so callers
|
|
217
|
+
// (e.g. renderProject for an embedded app) can operate on an isolated
|
|
218
|
+
// config without touching the global singleton.
|
|
219
|
+
let CONFIG = options.config || getActiveConfig()
|
|
161
220
|
|
|
162
221
|
const {
|
|
163
222
|
version,
|
|
@@ -168,12 +227,12 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
|
168
227
|
useFontImport,
|
|
169
228
|
useIconSprite,
|
|
170
229
|
globalTheme,
|
|
230
|
+
themeRoot,
|
|
171
231
|
useDocumentTheme,
|
|
172
232
|
useDefaultConfig,
|
|
173
233
|
semanticIcons,
|
|
174
|
-
SEMANTIC_ICONS, // deprecated
|
|
175
|
-
semantic_icons,
|
|
176
234
|
files,
|
|
235
|
+
assets,
|
|
177
236
|
...config
|
|
178
237
|
} = recivedConfig
|
|
179
238
|
|
|
@@ -182,6 +241,7 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
|
182
241
|
}
|
|
183
242
|
|
|
184
243
|
if (files !== undefined) CONFIG.files = files
|
|
244
|
+
if (assets !== undefined) CONFIG.assets = assets
|
|
185
245
|
if (verbose !== undefined) CONFIG.verbose = verbose
|
|
186
246
|
if (useVariable !== undefined) CONFIG.useVariable = useVariable
|
|
187
247
|
if (useReset !== undefined) CONFIG.useReset = useReset
|
|
@@ -190,23 +250,51 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
|
190
250
|
if (useIconSprite !== undefined) CONFIG.useIconSprite = useIconSprite
|
|
191
251
|
if (useDocumentTheme !== undefined) CONFIG.useDocumentTheme = useDocumentTheme
|
|
192
252
|
if (globalTheme !== undefined) CONFIG.globalTheme = globalTheme
|
|
253
|
+
if (themeRoot !== undefined) CONFIG.themeRoot = themeRoot
|
|
193
254
|
if (recivedConfig.useThemeSuffixedVars !== undefined)
|
|
194
255
|
CONFIG.useThemeSuffixedVars = recivedConfig.useThemeSuffixedVars
|
|
195
256
|
if (useDefaultConfig !== undefined) CONFIG.useDefaultConfig = useDefaultConfig
|
|
196
|
-
|
|
197
|
-
if (_semanticIcons !== undefined) {
|
|
198
|
-
CONFIG.semantic_icons = _semanticIcons
|
|
199
|
-
CONFIG.semanticIcons = CONFIG.semantic_icons
|
|
200
|
-
CONFIG.SEMANTIC_ICONS = CONFIG.semantic_icons // backward compat alias
|
|
201
|
-
}
|
|
257
|
+
if (semanticIcons !== undefined) CONFIG.semanticIcons = semanticIcons
|
|
202
258
|
if (CONFIG.verbose) console.log(CONFIG)
|
|
203
259
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
260
|
+
// Apply `data-theme` to the scope root so CSS selectors resolve correctly.
|
|
261
|
+
// `auto` means "follow the OS": we read `prefers-color-scheme` and install
|
|
262
|
+
// a one-time listener so a later OS toggle flips `data-theme` live without
|
|
263
|
+
// reloading the page. Forced themes (`'dark'`, `'light'`, or any custom
|
|
264
|
+
// scheme name like `'ocean'`) stick until the user calls
|
|
265
|
+
// `changeGlobalTheme(...)` again.
|
|
266
|
+
// `CONFIG.document` is also a factory bucket (font/theme defaults consumed
|
|
267
|
+
// by `applyDocument`); only treat it as a DOM document override if it
|
|
268
|
+
// actually looks like one.
|
|
269
|
+
const setConfigDoc = CONFIG.document && CONFIG.document.documentElement ? CONFIG.document : null
|
|
270
|
+
const setTargetDoc = setConfigDoc || (typeof document !== 'undefined' ? document : null)
|
|
271
|
+
const setTargetWin = (setTargetDoc && setTargetDoc.defaultView) || (typeof window !== 'undefined' ? window : null)
|
|
272
|
+
const setRoot = setTargetDoc && (CONFIG.themeRoot || setTargetDoc.documentElement)
|
|
273
|
+
if (setRoot && typeof setRoot.setAttribute === 'function') {
|
|
274
|
+
const forced = CONFIG.globalTheme && CONFIG.globalTheme !== 'auto'
|
|
275
|
+
if (forced) {
|
|
276
|
+
setRoot.setAttribute('data-theme', CONFIG.globalTheme)
|
|
277
|
+
if (CONFIG.globalTheme === 'dark' || CONFIG.globalTheme === 'light') {
|
|
278
|
+
setRoot.style.colorScheme = CONFIG.globalTheme
|
|
279
|
+
} else {
|
|
280
|
+
setRoot.style.colorScheme = 'light dark'
|
|
281
|
+
}
|
|
282
|
+
} else if (setTargetWin && setTargetWin.matchMedia) {
|
|
283
|
+
const apply = (mq) => { setRoot.setAttribute('data-theme', mq.matches ? 'dark' : 'light') }
|
|
284
|
+
const mq = setTargetWin.matchMedia('(prefers-color-scheme: dark)')
|
|
285
|
+
apply(mq)
|
|
286
|
+
setRoot.style.colorScheme = 'light dark'
|
|
287
|
+
if (!CONFIG.__prefersListener) {
|
|
288
|
+
CONFIG.__prefersListener = apply
|
|
289
|
+
try { mq.addEventListener('change', apply) } catch (e) { mq.addListener(apply) }
|
|
290
|
+
}
|
|
291
|
+
} else {
|
|
292
|
+
setRoot.setAttribute('data-theme', 'light')
|
|
293
|
+
setRoot.style.colorScheme = 'light'
|
|
294
|
+
}
|
|
207
295
|
}
|
|
208
296
|
|
|
209
|
-
if (!CONFIG.
|
|
297
|
+
if (!CONFIG.__svgCache) CONFIG.__svgCache = {}
|
|
210
298
|
|
|
211
299
|
const keys = Object.keys(config)
|
|
212
300
|
const keySet = new Set(keys)
|
|
@@ -219,11 +307,6 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
|
219
307
|
}
|
|
220
308
|
})
|
|
221
309
|
|
|
222
|
-
// Store original theme definitions before processing mutates them
|
|
223
|
-
if (config.theme && !CONFIG._originalTheme) {
|
|
224
|
-
CONFIG._originalTheme = JSON.parse(JSON.stringify(config.theme))
|
|
225
|
-
}
|
|
226
|
-
|
|
227
310
|
// Process only lowercase keys (skip UPPERCASE when lowercase equivalent exists)
|
|
228
311
|
keys.map((key) => {
|
|
229
312
|
const lower = key.toLowerCase()
|
|
@@ -231,22 +314,29 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
|
231
314
|
return setEach(key, config[key])
|
|
232
315
|
})
|
|
233
316
|
|
|
317
|
+
// Propagate varPrefix to sequence sub-configs for CSS variable name isolation
|
|
318
|
+
if (CONFIG.varPrefix) {
|
|
319
|
+
if (CONFIG.typography) CONFIG.typography.varPrefix = CONFIG.varPrefix
|
|
320
|
+
if (CONFIG.spacing) CONFIG.spacing.varPrefix = CONFIG.varPrefix
|
|
321
|
+
if (CONFIG.timing) CONFIG.timing.varPrefix = CONFIG.varPrefix
|
|
322
|
+
}
|
|
323
|
+
|
|
234
324
|
// apply generic configs
|
|
235
|
-
if (config.typography
|
|
325
|
+
if (config.typography) {
|
|
236
326
|
try {
|
|
237
327
|
applyTypographySequence()
|
|
238
328
|
} catch (e) {
|
|
239
329
|
if (CONFIG.verbose) console.warn('Error applying typography sequence', e)
|
|
240
330
|
}
|
|
241
331
|
}
|
|
242
|
-
if (config.spacing
|
|
332
|
+
if (config.spacing) {
|
|
243
333
|
try {
|
|
244
334
|
applySpacingSequence()
|
|
245
335
|
} catch (e) {
|
|
246
336
|
if (CONFIG.verbose) console.warn('Error applying spacing sequence', e)
|
|
247
337
|
}
|
|
248
338
|
}
|
|
249
|
-
if (config.timing
|
|
339
|
+
if (config.timing) {
|
|
250
340
|
try {
|
|
251
341
|
applyTimingSequence()
|
|
252
342
|
} catch (e) {
|
|
@@ -254,7 +344,10 @@ export const set = (recivedConfig, options = SET_OPTIONS) => {
|
|
|
254
344
|
}
|
|
255
345
|
}
|
|
256
346
|
applyDocument()
|
|
257
|
-
|
|
347
|
+
// Capture so init.js injects the full html/body reset (with default
|
|
348
|
+
// font-family); dropping the return leaves CONFIG.reset as the bare seed.
|
|
349
|
+
const computedReset = applyReset()
|
|
350
|
+
if (computedReset) CONFIG.reset = computedReset
|
|
258
351
|
|
|
259
352
|
return CONFIG
|
|
260
353
|
}
|