@wix/origami-transforms 0.1.28 → 0.1.30
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/dist/cssPlugins/componentPosition.d.ts +6 -2
- package/dist/cssPlugins/customEnums.d.ts +5 -2
- package/dist/cssPlugins/customMedia.d.ts +7 -2
- package/dist/cssPlugins/fonts.d.ts +6 -2
- package/dist/cssPlugins/index.d.ts +2 -2
- package/dist/cssPlugins/layout/index.d.ts +7 -2
- package/dist/cssPlugins/mixins.d.ts +7 -2
- package/dist/cssPlugins/presets.d.ts +6 -2
- package/dist/cssPlugins/shapeDividers.d.ts +6 -2
- package/dist/cssPlugins/spx/calls.d.ts +3 -0
- package/dist/cssPlugins/spx/index.d.ts +7 -2
- package/dist/cssPlugins/spxThemeVariables/index.d.ts +6 -0
- package/dist/cssPlugins/spxThemeVariables/utils/breakpointUtils.d.ts +23 -0
- package/dist/cssPlugins/spxThemeVariables/utils/copyPlanUtils.d.ts +14 -0
- package/dist/cssPlugins/spxThemeVariables/utils/cssTreeUtils.d.ts +10 -0
- package/dist/cssPlugins/spxThemeVariables/utils/restateUtils.d.ts +9 -0
- package/dist/cssPlugins/spxThemeVariables/utils/sectionUtils.d.ts +6 -0
- package/dist/cssPlugins/spxThemeVariables/utils/stylesheetUtils.d.ts +10 -0
- package/dist/cssPlugins/spxThemeVariables/utils/themeUtils.d.ts +6 -0
- package/dist/cssPlugins/spxThemeVariables/utils/wrappingAtRuleUtils.d.ts +10 -0
- package/dist/cssPlugins/spxThemeVariables/utils/writeUtils.d.ts +17 -0
- package/dist/cssPlugins/stackChildren.d.ts +5 -2
- package/dist/cssPlugins/wixVectorUrls.d.ts +6 -2
- package/dist/jsxPlugins/index.d.ts +2 -2
- package/dist/jsxPlugins/mediaValidation/index.d.ts +5 -2
- package/dist/jsxPlugins/origamiCompatibility/index.d.ts +6 -2
- package/dist/jsxPlugins/pageSlots.d.ts +7 -6
- package/dist/jsxPlugins/validation/checks.d.ts +7 -7
- package/dist/jsxPlugins/validation/index.d.ts +5 -2
- package/dist/jsxPlugins/validation/rules.d.ts +25 -0
- package/dist/transform/bi.d.ts +9 -0
- package/dist/transform/layoutError.d.ts +7 -4
- package/dist/transform/pipeline.d.ts +2 -2
- package/dist/transform/types.d.ts +11 -11
- package/package.json +3 -3
- package/src/cssPlugins/componentPosition.js +5 -1
- package/src/cssPlugins/customEnums.js +17 -11
- package/src/cssPlugins/customMedia.js +10 -3
- package/src/cssPlugins/fonts.js +5 -1
- package/src/cssPlugins/index.js +3 -1
- package/src/cssPlugins/layout/index.js +8 -1
- package/src/cssPlugins/layout/parser.js +15 -14
- package/src/cssPlugins/mixins.js +13 -6
- package/src/cssPlugins/presets.js +5 -1
- package/src/cssPlugins/shapeDividers.js +5 -1
- package/src/cssPlugins/spx/README.md +12 -3
- package/src/cssPlugins/spx/calls.js +28 -1
- package/src/cssPlugins/spx/cascade.js +26 -6
- package/src/cssPlugins/spx/index.js +8 -1
- package/src/cssPlugins/spxThemeVariables/README.md +14 -0
- package/src/cssPlugins/spxThemeVariables/index.js +36 -0
- package/src/cssPlugins/spxThemeVariables/utils/breakpointUtils.js +126 -0
- package/src/cssPlugins/spxThemeVariables/utils/copyPlanUtils.js +162 -0
- package/src/cssPlugins/spxThemeVariables/utils/cssTreeUtils.js +58 -0
- package/src/cssPlugins/spxThemeVariables/utils/restateUtils.js +134 -0
- package/src/cssPlugins/spxThemeVariables/utils/sectionUtils.js +32 -0
- package/src/cssPlugins/spxThemeVariables/utils/stylesheetUtils.js +98 -0
- package/src/cssPlugins/spxThemeVariables/utils/themeUtils.js +90 -0
- package/src/cssPlugins/spxThemeVariables/utils/wrappingAtRuleUtils.js +68 -0
- package/src/cssPlugins/spxThemeVariables/utils/writeUtils.js +98 -0
- package/src/cssPlugins/stackChildren.js +6 -2
- package/src/cssPlugins/wixVectorUrls.js +5 -1
- package/src/jsxPlugins/index.js +1 -1
- package/src/jsxPlugins/mediaValidation/index.js +10 -5
- package/src/jsxPlugins/origamiCompatibility/index.js +5 -1
- package/src/jsxPlugins/pageSlots.js +6 -1
- package/src/jsxPlugins/validation/checks.js +15 -8
- package/src/jsxPlugins/validation/index.js +9 -3
- package/src/jsxPlugins/validation/rules.js +14 -0
- package/src/transform/bi.js +30 -0
- package/src/transform/css/runPlugins.js +5 -7
- package/src/transform/jsx/runPlugins.js +3 -6
- package/src/transform/layoutError.js +7 -3
- package/src/transform/pipeline.js +2 -2
- package/src/transform/types.js +28 -6
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { THEME_SPX } from '../../spx/calls.js'
|
|
2
|
+
import { cascadeAt, inheritedBreakpointsOf, themeBreakpointsOf, widestFirst } from './breakpointUtils.js'
|
|
3
|
+
import { DESKTOP } from './cssTreeUtils.js'
|
|
4
|
+
import { CONTENT_MAX_WIDTH } from './sectionUtils.js'
|
|
5
|
+
|
|
6
|
+
/** @typedef {import('./breakpointUtils.js').CascadedVariable} CascadedVariable */
|
|
7
|
+
/** @typedef {import('./breakpointUtils.js').ThemeVariables} ThemeVariables */
|
|
8
|
+
/** @typedef {import('./breakpointUtils.js').VariableMap} VariableMap */
|
|
9
|
+
/** @typedef {import('postcss').Declaration} Declaration */
|
|
10
|
+
/** @typedef {Map<string, VariableMap>} ThemeCopies */
|
|
11
|
+
/** @typedef {Map<string, string[]>} VariableReferences */
|
|
12
|
+
/** @typedef {Map<string, string[]>} BreakpointPlan */
|
|
13
|
+
|
|
14
|
+
const VAR_REFERENCE = /var\(\s*(--[\w-]+)/g
|
|
15
|
+
|
|
16
|
+
/** @param {ThemeVariables} theme */
|
|
17
|
+
const getPlanOrder = theme => {
|
|
18
|
+
const breakpoints = themeBreakpointsOf(theme)
|
|
19
|
+
const widest = widestFirst(breakpoints)
|
|
20
|
+
return [DESKTOP, ...widest]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {ThemeVariables} theme
|
|
25
|
+
* @returns {BreakpointPlan}
|
|
26
|
+
*/
|
|
27
|
+
export const planBreakpoints = theme => {
|
|
28
|
+
const breakpoints = getPlanOrder(theme)
|
|
29
|
+
const entries = breakpoints.map(breakpoint => {
|
|
30
|
+
const inheritedBreakpoints = inheritedBreakpointsOf(theme, breakpoint)
|
|
31
|
+
return /** @type {const} */ ([breakpoint, inheritedBreakpoints])
|
|
32
|
+
})
|
|
33
|
+
return new Map(entries)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** @param {Declaration} declaration */
|
|
37
|
+
const getReferencedNames = declaration => {
|
|
38
|
+
const matches = [...declaration.value.matchAll(VAR_REFERENCE)]
|
|
39
|
+
return matches.map(([, name]) => name)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @param {VariableMap} variables
|
|
44
|
+
* @returns {VariableReferences}
|
|
45
|
+
*/
|
|
46
|
+
const buildReferenceMap = variables => {
|
|
47
|
+
/** @type {VariableReferences} */
|
|
48
|
+
const references = new Map()
|
|
49
|
+
for (const [prop, declaration] of variables) {
|
|
50
|
+
const names = getReferencedNames(declaration)
|
|
51
|
+
references.set(prop, names)
|
|
52
|
+
}
|
|
53
|
+
return references
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {Set<string>} targets
|
|
58
|
+
* @param {readonly string[]} names
|
|
59
|
+
*/
|
|
60
|
+
const readsAny = (targets, names) => names.some(name => targets.has(name))
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {VariableReferences} references
|
|
64
|
+
* @param {Set<string>} seeds
|
|
65
|
+
*/
|
|
66
|
+
const collectReaders = (references, seeds) => {
|
|
67
|
+
const reached = new Set(seeds)
|
|
68
|
+
let previousSize = -1
|
|
69
|
+
while (reached.size !== previousSize) {
|
|
70
|
+
previousSize = reached.size
|
|
71
|
+
for (const [prop, names] of references) {
|
|
72
|
+
if (readsAny(reached, names)) reached.add(prop)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return reached
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @param {VariableMap} variables */
|
|
79
|
+
const findScaleDependent = variables => {
|
|
80
|
+
const scaledVariables = [...variables].filter(([, declaration]) => declaration.value.includes(THEME_SPX))
|
|
81
|
+
const scaledProps = scaledVariables.map(([prop]) => prop)
|
|
82
|
+
const scaled = new Set(scaledProps)
|
|
83
|
+
const references = buildReferenceMap(variables)
|
|
84
|
+
return collectReaders(references, scaled)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {Map<string, CascadedVariable>} cascaded
|
|
89
|
+
* @returns {VariableMap}
|
|
90
|
+
*/
|
|
91
|
+
const mergeCascade = cascaded => {
|
|
92
|
+
const variables = [...cascaded].map(([prop, { declaration }]) => /** @type {const} */ ([prop, declaration]))
|
|
93
|
+
return new Map(variables)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* @param {Map<string, CascadedVariable>} cascaded
|
|
98
|
+
* @param {string} breakpoint
|
|
99
|
+
* @returns {VariableMap}
|
|
100
|
+
*/
|
|
101
|
+
const ownVariablesOf = (cascaded, breakpoint) => {
|
|
102
|
+
const own = [...cascaded].filter(([, cascadedVariable]) => cascadedVariable.breakpoint === breakpoint)
|
|
103
|
+
const variables = own.map(([prop, { declaration }]) => /** @type {const} */ ([prop, declaration]))
|
|
104
|
+
return new Map(variables)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* @param {ThemeCopies} themeCopies
|
|
109
|
+
* @param {readonly string[]} inheritedBreakpoints
|
|
110
|
+
*/
|
|
111
|
+
const getWiderCopiedNames = (themeCopies, inheritedBreakpoints) => {
|
|
112
|
+
const props = inheritedBreakpoints.flatMap(breakpoint => {
|
|
113
|
+
const copies = themeCopies.get(breakpoint) ?? new Map()
|
|
114
|
+
return [...copies.keys()]
|
|
115
|
+
})
|
|
116
|
+
return new Set(props)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @typedef {object} CopyCriteria
|
|
121
|
+
* @property {VariableMap} ownVariables
|
|
122
|
+
* @property {Set<string>} scaleDependent
|
|
123
|
+
* @property {Set<string>} widerCopied
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} prop
|
|
128
|
+
* @param {CopyCriteria} criteria
|
|
129
|
+
*/
|
|
130
|
+
const shouldCopy = (prop, { ownVariables, scaleDependent, widerCopied }) =>
|
|
131
|
+
ownVariables.has(prop)
|
|
132
|
+
? scaleDependent.has(prop) || widerCopied.has(prop)
|
|
133
|
+
: scaleDependent.has(prop) && !widerCopied.has(prop)
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @param {string} prop
|
|
137
|
+
* @param {CopyCriteria} criteria
|
|
138
|
+
*/
|
|
139
|
+
const shouldCopyVariable = (prop, criteria) => prop !== CONTENT_MAX_WIDTH && shouldCopy(prop, criteria)
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* @param {ThemeVariables} theme
|
|
143
|
+
* @param {BreakpointPlan} breakpointPlan
|
|
144
|
+
* @returns {ThemeCopies}
|
|
145
|
+
*/
|
|
146
|
+
export const planThemeCopies = (theme, breakpointPlan) => {
|
|
147
|
+
/** @type {ThemeCopies} */
|
|
148
|
+
const themeCopies = new Map()
|
|
149
|
+
for (const [breakpoint, inheritedBreakpoints] of breakpointPlan) {
|
|
150
|
+
const cascadeBreakpoints = [...inheritedBreakpoints, breakpoint]
|
|
151
|
+
const cascaded = cascadeAt(theme, cascadeBreakpoints)
|
|
152
|
+
const ownVariables = ownVariablesOf(cascaded, breakpoint)
|
|
153
|
+
const effectiveVariables = mergeCascade(cascaded)
|
|
154
|
+
const scaleDependent = findScaleDependent(effectiveVariables)
|
|
155
|
+
const widerCopied = getWiderCopiedNames(themeCopies, inheritedBreakpoints)
|
|
156
|
+
const criteria = { ownVariables, scaleDependent, widerCopied }
|
|
157
|
+
const copiedEntries = [...effectiveVariables].filter(([prop]) => shouldCopyVariable(prop, criteria))
|
|
158
|
+
const copies = new Map(copiedEntries)
|
|
159
|
+
themeCopies.set(breakpoint, copies)
|
|
160
|
+
}
|
|
161
|
+
return themeCopies
|
|
162
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { DEFAULTS_LAYER } from '../../layout/contract.js'
|
|
2
|
+
|
|
3
|
+
export const DESKTOP = ''
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {import('postcss').Node} node
|
|
7
|
+
* @returns {import('postcss').Node[]}
|
|
8
|
+
*/
|
|
9
|
+
export const ancestorsOf = node => {
|
|
10
|
+
/** @type {import('postcss').Node[]} */
|
|
11
|
+
const ancestors = []
|
|
12
|
+
let current = node.parent
|
|
13
|
+
while (current !== undefined && current.type !== 'root' && current.type !== 'document') {
|
|
14
|
+
ancestors.push(current)
|
|
15
|
+
current = current.parent
|
|
16
|
+
}
|
|
17
|
+
return ancestors
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {import('postcss').Node} node
|
|
22
|
+
* @returns {node is import('postcss').AtRule}
|
|
23
|
+
*/
|
|
24
|
+
export const isAtRule = node => node.type === 'atrule'
|
|
25
|
+
|
|
26
|
+
/** @param {string} text */
|
|
27
|
+
export const collapseWhitespace = text => text.replace(/\s+/g, ' ').trim()
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {import('postcss').Node} node
|
|
31
|
+
* @param {string} name
|
|
32
|
+
*/
|
|
33
|
+
const isLayerNamed = (node, name) => isAtRule(node) && node.name === 'layer' && node.params.trim() === name
|
|
34
|
+
|
|
35
|
+
/** @param {readonly import('postcss').Node[]} ancestors */
|
|
36
|
+
export const isInContainerDefaults = ancestors => ancestors.some(node => isLayerNamed(node, DEFAULTS_LAYER))
|
|
37
|
+
|
|
38
|
+
/** @param {readonly import('postcss').Node[]} ancestors */
|
|
39
|
+
export const isTopLevel = ancestors => ancestors.every(isAtRule)
|
|
40
|
+
|
|
41
|
+
/** @param {import('postcss').Rule} rule */
|
|
42
|
+
export const hasRootSelector = rule => rule.selector.trim() === ':root'
|
|
43
|
+
|
|
44
|
+
/** @param {string} prop */
|
|
45
|
+
export const isCustomProperty = prop => prop.startsWith('--')
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {import('postcss').Node} node
|
|
49
|
+
* @returns {node is import('postcss').AtRule}
|
|
50
|
+
*/
|
|
51
|
+
export const isMedia = node => isAtRule(node) && node.name === 'media'
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {import('postcss').Rule} rule
|
|
55
|
+
* @returns {import('postcss').Declaration[]}
|
|
56
|
+
*/
|
|
57
|
+
export const declarationsOf = rule =>
|
|
58
|
+
rule.nodes.filter(/** @returns {node is import('postcss').Declaration} */ node => node.type === 'decl')
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { cascadeAt, maxWidthOf, refWidthAt } from './breakpointUtils.js'
|
|
2
|
+
import { isAtRule } from './cssTreeUtils.js'
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('./breakpointUtils.js').CascadedVariable} CascadedVariable */
|
|
5
|
+
/** @typedef {import('./breakpointUtils.js').ThemeVariables} ThemeVariables */
|
|
6
|
+
/** @typedef {import('./copyPlanUtils.js').BreakpointPlan} BreakpointPlan */
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} RestateTarget
|
|
10
|
+
* @property {string} breakpoint
|
|
11
|
+
* @property {number} index
|
|
12
|
+
* @property {number} refWidth
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const SPX_CALL = '--spx('
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {ThemeVariables} theme
|
|
19
|
+
* @param {string} breakpoint
|
|
20
|
+
*/
|
|
21
|
+
const lastIndexAt = ({ rules }, breakpoint) => {
|
|
22
|
+
const indexes = rules.flatMap(({ scanned }, index) => (scanned.breakpoint === breakpoint ? [index] : []))
|
|
23
|
+
return indexes.at(-1)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {ThemeVariables} theme
|
|
28
|
+
* @param {RestateTarget} target
|
|
29
|
+
* @param {[string, CascadedVariable]} entry
|
|
30
|
+
*/
|
|
31
|
+
const needsRestate = (theme, target, [, source]) => {
|
|
32
|
+
if (source.breakpoint === target.breakpoint) return false
|
|
33
|
+
if (source.index > target.index) return false
|
|
34
|
+
if (!source.declaration.value.includes(SPX_CALL)) return false
|
|
35
|
+
const sourceRefWidth = refWidthAt(theme, source.breakpoint)
|
|
36
|
+
return sourceRefWidth !== target.refWidth
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {ThemeVariables} theme
|
|
41
|
+
* @param {RestateTarget} target
|
|
42
|
+
* @param {[string, CascadedVariable]} entry
|
|
43
|
+
*/
|
|
44
|
+
const narrowerRuleIndexOf = ({ rules }, target, [prop, source]) => {
|
|
45
|
+
const targetWidth = maxWidthOf(target.breakpoint)
|
|
46
|
+
if (targetWidth === undefined) return -1
|
|
47
|
+
const sourceIndex = rules.findIndex(({ scanned }) => scanned.rule === source.declaration.parent)
|
|
48
|
+
const declaresNarrower = ({ scanned, variables }, index) => {
|
|
49
|
+
const width = maxWidthOf(scanned.breakpoint)
|
|
50
|
+
return index > sourceIndex && width !== undefined && width < targetWidth && variables.has(prop)
|
|
51
|
+
}
|
|
52
|
+
return rules.findIndex(declaresNarrower)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {import('./breakpointUtils.js').ThemeRule} target
|
|
57
|
+
* @param {[string, CascadedVariable]} entry
|
|
58
|
+
*/
|
|
59
|
+
const restateInto = ({ scanned, variables }, [prop, { declaration }]) => {
|
|
60
|
+
const clone = declaration.clone({ raws: {} })
|
|
61
|
+
scanned.rule.append(clone)
|
|
62
|
+
scanned.declarations.push(clone)
|
|
63
|
+
variables.set(prop, clone)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {import('postcss').Declaration} clone
|
|
68
|
+
* @param {import('postcss').Rule} targetRule
|
|
69
|
+
* @param {import('postcss').AtRule} targetMedia
|
|
70
|
+
*/
|
|
71
|
+
const wrapperOf = (clone, targetRule, targetMedia) => {
|
|
72
|
+
const rule = targetRule.clone({ raws: {} }).removeAll()
|
|
73
|
+
rule.append(clone)
|
|
74
|
+
const wrapper = targetMedia.clone({ raws: {} }).removeAll()
|
|
75
|
+
wrapper.append(rule)
|
|
76
|
+
return { rule, wrapper }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @param {ThemeVariables} theme
|
|
81
|
+
* @param {import('./breakpointUtils.js').ThemeRule} targetRule
|
|
82
|
+
* @param {number} index
|
|
83
|
+
* @param {[string, CascadedVariable]} entry
|
|
84
|
+
*/
|
|
85
|
+
const restateBefore = (theme, targetRule, index, entry) => {
|
|
86
|
+
const [prop, { declaration }] = entry
|
|
87
|
+
const targetMedia = targetRule.scanned.rule.parent
|
|
88
|
+
const narrowerMedia = theme.rules[index].scanned.rule.parent
|
|
89
|
+
if (targetMedia === undefined || narrowerMedia === undefined) return restateInto(targetRule, entry)
|
|
90
|
+
if (!isAtRule(targetMedia) || !isAtRule(narrowerMedia)) return restateInto(targetRule, entry)
|
|
91
|
+
const clone = declaration.clone({ raws: {} })
|
|
92
|
+
const { rule, wrapper } = wrapperOf(clone, targetRule.scanned.rule, targetMedia)
|
|
93
|
+
narrowerMedia.before(wrapper)
|
|
94
|
+
const { breakpoint } = targetRule.scanned
|
|
95
|
+
const scanned = { rule, breakpoint, declarations: [clone] }
|
|
96
|
+
theme.rules.splice(index, 0, { scanned, variables: new Map([[prop, clone]]) })
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {ThemeVariables} theme
|
|
101
|
+
* @param {import('./breakpointUtils.js').ThemeRule} targetRule
|
|
102
|
+
* @param {RestateTarget} target
|
|
103
|
+
* @param {[string, CascadedVariable]} entry
|
|
104
|
+
*/
|
|
105
|
+
const restate = (theme, targetRule, target, entry) => {
|
|
106
|
+
const index = narrowerRuleIndexOf(theme, target, entry)
|
|
107
|
+
const targetIndex = theme.rules.indexOf(targetRule)
|
|
108
|
+
if (index === -1 || index >= targetIndex) return restateInto(targetRule, entry)
|
|
109
|
+
restateBefore(theme, targetRule, index, entry)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {ThemeVariables} theme
|
|
114
|
+
* @param {string} breakpoint
|
|
115
|
+
* @param {readonly string[]} inheritedBreakpoints
|
|
116
|
+
*/
|
|
117
|
+
const restateAt = (theme, breakpoint, inheritedBreakpoints) => {
|
|
118
|
+
const index = lastIndexAt(theme, breakpoint)
|
|
119
|
+
if (index === undefined) return
|
|
120
|
+
const refWidth = refWidthAt(theme, breakpoint)
|
|
121
|
+
const target = { breakpoint, index, refWidth }
|
|
122
|
+
const cascaded = cascadeAt(theme, [...inheritedBreakpoints, breakpoint])
|
|
123
|
+
const stale = [...cascaded].filter(entry => needsRestate(theme, target, entry))
|
|
124
|
+
const targetRule = theme.rules[index]
|
|
125
|
+
for (const entry of stale) restate(theme, targetRule, target, entry)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @param {ThemeVariables} theme
|
|
130
|
+
* @param {BreakpointPlan} breakpointPlan
|
|
131
|
+
*/
|
|
132
|
+
export const restateThemeVariables = (theme, breakpointPlan) => {
|
|
133
|
+
for (const [breakpoint, inheritedBreakpoints] of breakpointPlan) restateAt(theme, breakpoint, inheritedBreakpoints)
|
|
134
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { DESKTOP } from './cssTreeUtils.js'
|
|
2
|
+
import { wrappingAtRulesOf } from './wrappingAtRuleUtils.js'
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('./stylesheetUtils.js').ScannedRule} ScannedRule */
|
|
5
|
+
/** @typedef {import('./wrappingAtRuleUtils.js').Section} Section */
|
|
6
|
+
|
|
7
|
+
export const CONTENT_MAX_WIDTH = '--content-max-width'
|
|
8
|
+
|
|
9
|
+
/** @param {ScannedRule} layoutRule */
|
|
10
|
+
const capsAtDesktop = ({ breakpoint, declarations }) => {
|
|
11
|
+
if (breakpoint !== DESKTOP) return false
|
|
12
|
+
return declarations.some(({ prop }) => prop === CONTENT_MAX_WIDTH)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {ScannedRule} layoutRule
|
|
17
|
+
* @returns {Section}
|
|
18
|
+
*/
|
|
19
|
+
const sectionOf = ({ rule }) => {
|
|
20
|
+
const selector = rule.selector.trim()
|
|
21
|
+
const wrappingAtRules = wrappingAtRulesOf(rule)
|
|
22
|
+
return { selector, wrappingAtRules }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {readonly ScannedRule[]} layoutRules
|
|
27
|
+
* @returns {Section[]}
|
|
28
|
+
*/
|
|
29
|
+
export const readSectionRules = layoutRules => {
|
|
30
|
+
const sectionsWithMaxWidthRules = layoutRules.filter(capsAtDesktop)
|
|
31
|
+
return sectionsWithMaxWidthRules.map(sectionOf)
|
|
32
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { breakpointOf, maxWidthOf } from './breakpointUtils.js'
|
|
2
|
+
import {
|
|
3
|
+
ancestorsOf,
|
|
4
|
+
declarationsOf,
|
|
5
|
+
hasRootSelector,
|
|
6
|
+
isInContainerDefaults,
|
|
7
|
+
isMedia,
|
|
8
|
+
isTopLevel,
|
|
9
|
+
} from './cssTreeUtils.js'
|
|
10
|
+
import { relativeToSite } from '../../../transform/relativeToSite.js'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} ScannedRule
|
|
14
|
+
* @property {import('postcss').Rule} rule
|
|
15
|
+
* @property {string} breakpoint
|
|
16
|
+
* @property {import('postcss').Declaration[]} declarations
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @typedef {object} StylesheetScan
|
|
21
|
+
* @property {ScannedRule[]} themeRules
|
|
22
|
+
* @property {ScannedRule[]} layoutRules
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const THEME_FILE = 'src/global/themes/theme.css'
|
|
26
|
+
|
|
27
|
+
/** @param {readonly import('postcss').Node[]} ancestors */
|
|
28
|
+
const isThemeContext = ancestors => {
|
|
29
|
+
const [parent] = ancestors
|
|
30
|
+
if (parent === undefined) return true
|
|
31
|
+
return ancestors.length === 1 && isMedia(parent) && maxWidthOf(parent.params) !== undefined
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {import('postcss').Rule} rule
|
|
36
|
+
* @param {string} siteFolder
|
|
37
|
+
*/
|
|
38
|
+
const isFromThemeFile = (rule, siteFolder) => {
|
|
39
|
+
const file = rule.source?.input.file
|
|
40
|
+
if (file === undefined) return false
|
|
41
|
+
const relative = relativeToSite(siteFolder, file)
|
|
42
|
+
return relative === THEME_FILE
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** @param {readonly import('postcss').Node[]} ancestors */
|
|
46
|
+
const isLayoutRule = ancestors => isTopLevel(ancestors) && !isInContainerDefaults(ancestors)
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {import('postcss').Rule} rule
|
|
50
|
+
* @param {import('postcss').Node[]} ancestors
|
|
51
|
+
* @returns {ScannedRule}
|
|
52
|
+
*/
|
|
53
|
+
const scannedRuleOf = (rule, ancestors) => {
|
|
54
|
+
const breakpoint = breakpointOf(ancestors)
|
|
55
|
+
const declarations = declarationsOf(rule)
|
|
56
|
+
return { rule, breakpoint, declarations }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {import('postcss').Rule} rule
|
|
61
|
+
* @param {string} siteFolder
|
|
62
|
+
* @returns {ScannedRule[]}
|
|
63
|
+
*/
|
|
64
|
+
const themeRuleOf = (rule, siteFolder) => {
|
|
65
|
+
if (!isFromThemeFile(rule, siteFolder)) return []
|
|
66
|
+
const ancestors = ancestorsOf(rule)
|
|
67
|
+
if (!isThemeContext(ancestors)) return []
|
|
68
|
+
return [scannedRuleOf(rule, ancestors)]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {import('postcss').Rule} rule
|
|
73
|
+
* @returns {ScannedRule[]}
|
|
74
|
+
*/
|
|
75
|
+
const layoutRuleOf = rule => {
|
|
76
|
+
const ancestors = ancestorsOf(rule)
|
|
77
|
+
return isLayoutRule(ancestors) ? [scannedRuleOf(rule, ancestors)] : []
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {import('postcss').Root} root
|
|
82
|
+
* @param {string} siteFolder
|
|
83
|
+
* @returns {StylesheetScan}
|
|
84
|
+
*/
|
|
85
|
+
export const scanStylesheet = (root, siteFolder) => {
|
|
86
|
+
/** @type {StylesheetScan} */
|
|
87
|
+
const scan = { themeRules: [], layoutRules: [] }
|
|
88
|
+
root.walkRules(rule => {
|
|
89
|
+
if (hasRootSelector(rule)) {
|
|
90
|
+
const themeRules = themeRuleOf(rule, siteFolder)
|
|
91
|
+
scan.themeRules.push(...themeRules)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
const layoutRules = layoutRuleOf(rule)
|
|
95
|
+
scan.layoutRules.push(...layoutRules)
|
|
96
|
+
})
|
|
97
|
+
return scan
|
|
98
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { toThemeCalls } from '../../spx/calls.js'
|
|
2
|
+
import { refWidthAt, resolveRefWidths } from './breakpointUtils.js'
|
|
3
|
+
import { DESKTOP, isCustomProperty } from './cssTreeUtils.js'
|
|
4
|
+
|
|
5
|
+
/** @typedef {import('./breakpointUtils.js').ThemeRule} ThemeRule */
|
|
6
|
+
/** @typedef {import('./breakpointUtils.js').ThemeVariables} ThemeVariables */
|
|
7
|
+
/** @typedef {import('./breakpointUtils.js').VariableMap} VariableMap */
|
|
8
|
+
/** @typedef {import('./stylesheetUtils.js').ScannedRule} ScannedRule */
|
|
9
|
+
|
|
10
|
+
const REF_WIDTH = '--spx-ref-width'
|
|
11
|
+
|
|
12
|
+
const SPX_SETTINGS = new Set([
|
|
13
|
+
REF_WIDTH,
|
|
14
|
+
'--spx-resolver-type',
|
|
15
|
+
'--spx-stopper-min',
|
|
16
|
+
'--spx-stopper-max',
|
|
17
|
+
'--scaling-factor',
|
|
18
|
+
'--scrollbar-width',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
/** @param {string} prop */
|
|
22
|
+
const isThemeVariable = prop => isCustomProperty(prop) && !SPX_SETTINGS.has(prop)
|
|
23
|
+
|
|
24
|
+
/** @param {number} refWidth */
|
|
25
|
+
const isUsableRefWidth = refWidth => Number.isFinite(refWidth) && refWidth > 0
|
|
26
|
+
|
|
27
|
+
/** @param {readonly ScannedRule[]} themeRules */
|
|
28
|
+
const rootDeclarationsByBreakpointOf = themeRules => {
|
|
29
|
+
/** @type {Map<string, VariableMap>} */
|
|
30
|
+
const declarationsByBreakpoint = new Map()
|
|
31
|
+
for (const { breakpoint, declarations } of themeRules) {
|
|
32
|
+
const declarationMap = declarationsByBreakpoint.get(breakpoint) ?? new Map()
|
|
33
|
+
declarationsByBreakpoint.set(breakpoint, declarationMap)
|
|
34
|
+
for (const declaration of declarations) {
|
|
35
|
+
declarationMap.set(declaration.prop, declaration)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return declarationsByBreakpoint
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {ScannedRule} scanned
|
|
43
|
+
* @returns {ThemeRule}
|
|
44
|
+
*/
|
|
45
|
+
const themeRuleOf = scanned => {
|
|
46
|
+
const themeVariables = scanned.declarations.filter(({ prop }) => isThemeVariable(prop))
|
|
47
|
+
const entries = themeVariables.map(declaration => /** @type {const} */ ([declaration.prop, declaration]))
|
|
48
|
+
const variables = new Map(entries)
|
|
49
|
+
return { scanned, variables }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {Map<string, VariableMap>} declarationsByBreakpoint */
|
|
53
|
+
const breakpointsOf = declarationsByBreakpoint => {
|
|
54
|
+
const breakpoints = [...declarationsByBreakpoint.keys()]
|
|
55
|
+
return breakpoints.filter(breakpoint => breakpoint !== DESKTOP)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @param {Map<string, VariableMap>} declarationsByBreakpoint */
|
|
59
|
+
const refWidthByBreakpointOf = declarationsByBreakpoint => {
|
|
60
|
+
const refWidths = [...declarationsByBreakpoint].flatMap(([breakpoint, declarationMap]) => {
|
|
61
|
+
const refWidth = Number(declarationMap.get(REF_WIDTH)?.value)
|
|
62
|
+
return isUsableRefWidth(refWidth) ? [/** @type {const} */ ([breakpoint, refWidth])] : []
|
|
63
|
+
})
|
|
64
|
+
return new Map(refWidths)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {readonly ScannedRule[]} themeRules
|
|
69
|
+
* @returns {ThemeVariables}
|
|
70
|
+
*/
|
|
71
|
+
export const readThemeVariables = themeRules => {
|
|
72
|
+
const declarationsByBreakpoint = rootDeclarationsByBreakpointOf(themeRules)
|
|
73
|
+
const rules = themeRules.map(themeRuleOf)
|
|
74
|
+
const breakpoints = breakpointsOf(declarationsByBreakpoint)
|
|
75
|
+
const ownRefWidths = refWidthByBreakpointOf(declarationsByBreakpoint)
|
|
76
|
+
const refWidthByBreakpoint = resolveRefWidths(breakpoints, ownRefWidths)
|
|
77
|
+
return { rules, breakpoints, refWidthByBreakpoint }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {readonly ScannedRule[]} themeRules
|
|
82
|
+
* @param {ThemeVariables} theme
|
|
83
|
+
*/
|
|
84
|
+
export const tagThemeVariables = (themeRules, theme) => {
|
|
85
|
+
for (const { breakpoint, declarations } of themeRules) {
|
|
86
|
+
const refWidth = refWidthAt(theme, breakpoint)
|
|
87
|
+
const variables = declarations.filter(({ prop }) => isThemeVariable(prop))
|
|
88
|
+
for (const declaration of variables) declaration.value = toThemeCalls(declaration.value, refWidth)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ancestorsOf, collapseWhitespace, isAtRule } from './cssTreeUtils.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {object} Section
|
|
5
|
+
* @property {string} selector
|
|
6
|
+
* @property {import('postcss').AtRule[]} wrappingAtRules
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} SectionGroup
|
|
11
|
+
* @property {import('postcss').AtRule[]} wrappingAtRules
|
|
12
|
+
* @property {string[]} selectors
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const UNWRAPPED_KEY = ''
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('postcss').Node} node
|
|
19
|
+
* @returns {node is import('postcss').AtRule}
|
|
20
|
+
*/
|
|
21
|
+
const isWrappingAtRule = node => isAtRule(node) && node.name !== 'layer'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {import('postcss').Rule} rule
|
|
25
|
+
* @returns {import('postcss').AtRule[]}
|
|
26
|
+
*/
|
|
27
|
+
export const wrappingAtRulesOf = rule => {
|
|
28
|
+
const ancestors = ancestorsOf(rule)
|
|
29
|
+
const wrappingAtRules = ancestors.filter(isWrappingAtRule)
|
|
30
|
+
return wrappingAtRules.reverse()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @param {import('postcss').AtRule} atRule */
|
|
34
|
+
const atRuleLabelOf = ({ name, params }) => {
|
|
35
|
+
const collapsed = collapseWhitespace(params)
|
|
36
|
+
return `@${name} ${collapsed}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** @param {readonly import('postcss').AtRule[]} wrappingAtRules */
|
|
40
|
+
const wrappingKeyOf = wrappingAtRules => {
|
|
41
|
+
const labels = wrappingAtRules.map(atRuleLabelOf)
|
|
42
|
+
return labels.join(' ')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {Map<string, SectionGroup>} groups
|
|
47
|
+
* @param {Section} section
|
|
48
|
+
*/
|
|
49
|
+
const addSectionToGroup = (groups, { selector, wrappingAtRules }) => {
|
|
50
|
+
const key = wrappingKeyOf(wrappingAtRules)
|
|
51
|
+
const group = groups.get(key) ?? { wrappingAtRules, selectors: [] }
|
|
52
|
+
if (!group.selectors.includes(selector)) group.selectors.push(selector)
|
|
53
|
+
groups.set(key, group)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {readonly Section[]} sections
|
|
58
|
+
* @returns {SectionGroup[]}
|
|
59
|
+
*/
|
|
60
|
+
export const groupSectionsByWrappingAtRules = sections => {
|
|
61
|
+
/** @type {Map<string, SectionGroup>} */
|
|
62
|
+
const groups = new Map()
|
|
63
|
+
for (const section of sections) addSectionToGroup(groups, section)
|
|
64
|
+
const unwrapped = groups.get(UNWRAPPED_KEY)
|
|
65
|
+
groups.delete(UNWRAPPED_KEY)
|
|
66
|
+
const wrapped = [...groups.values()]
|
|
67
|
+
return unwrapped === undefined ? wrapped : [unwrapped, ...wrapped]
|
|
68
|
+
}
|