@variance-authority/core 0.1.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/CHANGELOG.md +79 -0
- package/LICENSE +21 -0
- package/README.md +169 -0
- package/dist/artifact.d.ts +47 -0
- package/dist/artifact.js +2 -0
- package/dist/artifact.js.map +1 -0
- package/dist/attribute/because.d.ts +43 -0
- package/dist/attribute/because.js +64 -0
- package/dist/attribute/because.js.map +1 -0
- package/dist/attribute/boundary.d.ts +191 -0
- package/dist/attribute/boundary.js +328 -0
- package/dist/attribute/boundary.js.map +1 -0
- package/dist/attribute/call-site.d.ts +96 -0
- package/dist/attribute/call-site.js +183 -0
- package/dist/attribute/call-site.js.map +1 -0
- package/dist/attribute/component-hash.d.ts +188 -0
- package/dist/attribute/component-hash.js +312 -0
- package/dist/attribute/component-hash.js.map +1 -0
- package/dist/attribute/composition-fixture.d.ts +18 -0
- package/dist/attribute/composition-fixture.js +53 -0
- package/dist/attribute/composition-fixture.js.map +1 -0
- package/dist/attribute/composition.d.ts +182 -0
- package/dist/attribute/composition.js +169 -0
- package/dist/attribute/composition.js.map +1 -0
- package/dist/attribute/control.d.ts +43 -0
- package/dist/attribute/control.js +54 -0
- package/dist/attribute/control.js.map +1 -0
- package/dist/attribute/coverage.d.ts +99 -0
- package/dist/attribute/coverage.js +86 -0
- package/dist/attribute/coverage.js.map +1 -0
- package/dist/attribute/divergence.d.ts +102 -0
- package/dist/attribute/divergence.js +152 -0
- package/dist/attribute/divergence.js.map +1 -0
- package/dist/attribute/index.d.ts +43 -0
- package/dist/attribute/index.js +29 -0
- package/dist/attribute/index.js.map +1 -0
- package/dist/attribute/instability.d.ts +89 -0
- package/dist/attribute/instability.js +183 -0
- package/dist/attribute/instability.js.map +1 -0
- package/dist/attribute/instances.d.ts +145 -0
- package/dist/attribute/instances.js +140 -0
- package/dist/attribute/instances.js.map +1 -0
- package/dist/attribute/locate.d.ts +22 -0
- package/dist/attribute/locate.js +100 -0
- package/dist/attribute/locate.js.map +1 -0
- package/dist/attribute/mask.d.ts +156 -0
- package/dist/attribute/mask.js +201 -0
- package/dist/attribute/mask.js.map +1 -0
- package/dist/attribute/movement.d.ts +211 -0
- package/dist/attribute/movement.js +181 -0
- package/dist/attribute/movement.js.map +1 -0
- package/dist/attribute/region.d.ts +188 -0
- package/dist/attribute/region.js +144 -0
- package/dist/attribute/region.js.map +1 -0
- package/dist/attribute/source-map.d.ts +98 -0
- package/dist/attribute/source-map.js +264 -0
- package/dist/attribute/source-map.js.map +1 -0
- package/dist/attribute/source.d.ts +77 -0
- package/dist/attribute/source.js +95 -0
- package/dist/attribute/source.js.map +1 -0
- package/dist/attribute/stack.d.ts +80 -0
- package/dist/attribute/stack.js +108 -0
- package/dist/attribute/stack.js.map +1 -0
- package/dist/compare/band.d.ts +85 -0
- package/dist/compare/band.js +70 -0
- package/dist/compare/band.js.map +1 -0
- package/dist/compare/cascade.d.ts +45 -0
- package/dist/compare/cascade.js +71 -0
- package/dist/compare/cascade.js.map +1 -0
- package/dist/compare/derive.d.ts +78 -0
- package/dist/compare/derive.js +39 -0
- package/dist/compare/derive.js.map +1 -0
- package/dist/compare/diff/attribution.d.ts +23 -0
- package/dist/compare/diff/attribution.js +244 -0
- package/dist/compare/diff/attribution.js.map +1 -0
- package/dist/compare/diff/compare-nodes.d.ts +15 -0
- package/dist/compare/diff/compare-nodes.js +227 -0
- package/dist/compare/diff/compare-nodes.js.map +1 -0
- package/dist/compare/diff/components.d.ts +20 -0
- package/dist/compare/diff/components.js +76 -0
- package/dist/compare/diff/components.js.map +1 -0
- package/dist/compare/diff/delta.d.ts +165 -0
- package/dist/compare/diff/delta.js +12 -0
- package/dist/compare/diff/delta.js.map +1 -0
- package/dist/compare/diff/index.d.ts +69 -0
- package/dist/compare/diff/index.js +101 -0
- package/dist/compare/diff/index.js.map +1 -0
- package/dist/compare/diff/match.d.ts +37 -0
- package/dist/compare/diff/match.js +167 -0
- package/dist/compare/diff/match.js.map +1 -0
- package/dist/compare/explain.d.ts +23 -0
- package/dist/compare/explain.js +190 -0
- package/dist/compare/explain.js.map +1 -0
- package/dist/compare/holding-diff.d.ts +25 -0
- package/dist/compare/holding-diff.js +74 -0
- package/dist/compare/holding-diff.js.map +1 -0
- package/dist/compare/impact.d.ts +56 -0
- package/dist/compare/impact.js +124 -0
- package/dist/compare/impact.js.map +1 -0
- package/dist/compare/index.d.ts +30 -0
- package/dist/compare/index.js +22 -0
- package/dist/compare/index.js.map +1 -0
- package/dist/compare/instance.d.ts +30 -0
- package/dist/compare/instance.js +145 -0
- package/dist/compare/instance.js.map +1 -0
- package/dist/compare/observability.d.ts +49 -0
- package/dist/compare/observability.js +57 -0
- package/dist/compare/observability.js.map +1 -0
- package/dist/compare/parting.d.ts +179 -0
- package/dist/compare/parting.js +244 -0
- package/dist/compare/parting.js.map +1 -0
- package/dist/compare/slice.d.ts +74 -0
- package/dist/compare/slice.js +79 -0
- package/dist/compare/slice.js.map +1 -0
- package/dist/compare/value.d.ts +60 -0
- package/dist/compare/value.js +111 -0
- package/dist/compare/value.js.map +1 -0
- package/dist/format/accessibility.d.ts +27 -0
- package/dist/format/accessibility.js +17 -0
- package/dist/format/accessibility.js.map +1 -0
- package/dist/format/canonical.d.ts +38 -0
- package/dist/format/canonical.js +65 -0
- package/dist/format/canonical.js.map +1 -0
- package/dist/format/capture.d.ts +212 -0
- package/dist/format/capture.js +2 -0
- package/dist/format/capture.js.map +1 -0
- package/dist/format/document.d.ts +279 -0
- package/dist/format/document.js +65 -0
- package/dist/format/document.js.map +1 -0
- package/dist/format/environment.d.ts +120 -0
- package/dist/format/environment.js +53 -0
- package/dist/format/environment.js.map +1 -0
- package/dist/format/hash.d.ts +45 -0
- package/dist/format/hash.js +41 -0
- package/dist/format/hash.js.map +1 -0
- package/dist/format/holding.d.ts +134 -0
- package/dist/format/holding.js +2 -0
- package/dist/format/holding.js.map +1 -0
- package/dist/format/index.d.ts +39 -0
- package/dist/format/index.js +25 -0
- package/dist/format/index.js.map +1 -0
- package/dist/format/intervention.d.ts +87 -0
- package/dist/format/intervention.js +11 -0
- package/dist/format/intervention.js.map +1 -0
- package/dist/format/profile.d.ts +59 -0
- package/dist/format/profile.js +66 -0
- package/dist/format/profile.js.map +1 -0
- package/dist/format/provenance.d.ts +179 -0
- package/dist/format/provenance.js +222 -0
- package/dist/format/provenance.js.map +1 -0
- package/dist/format/sha256.d.ts +34 -0
- package/dist/format/sha256.js +152 -0
- package/dist/format/sha256.js.map +1 -0
- package/dist/format/snapshot.d.ts +263 -0
- package/dist/format/snapshot.js +2 -0
- package/dist/format/snapshot.js.map +1 -0
- package/dist/format/stabilize.d.ts +190 -0
- package/dist/format/stabilize.js +333 -0
- package/dist/format/stabilize.js.map +1 -0
- package/dist/format/tier.d.ts +19 -0
- package/dist/format/tier.js +11 -0
- package/dist/format/tier.js.map +1 -0
- package/dist/format/value.d.ts +115 -0
- package/dist/format/value.js +182 -0
- package/dist/format/value.js.map +1 -0
- package/dist/format/wiring.d.ts +115 -0
- package/dist/format/wiring.js +60 -0
- package/dist/format/wiring.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/judge/docket.d.ts +85 -0
- package/dist/judge/docket.js +162 -0
- package/dist/judge/docket.js.map +1 -0
- package/dist/judge/fingerprint.d.ts +53 -0
- package/dist/judge/fingerprint.js +144 -0
- package/dist/judge/fingerprint.js.map +1 -0
- package/dist/judge/ignore.d.ts +206 -0
- package/dist/judge/ignore.js +237 -0
- package/dist/judge/ignore.js.map +1 -0
- package/dist/judge/index.d.ts +44 -0
- package/dist/judge/index.js +34 -0
- package/dist/judge/index.js.map +1 -0
- package/dist/judge/inspect.d.ts +148 -0
- package/dist/judge/inspect.js +208 -0
- package/dist/judge/inspect.js.map +1 -0
- package/dist/judge/intent.d.ts +146 -0
- package/dist/judge/intent.js +150 -0
- package/dist/judge/intent.js.map +1 -0
- package/dist/judge/locale.d.ts +111 -0
- package/dist/judge/locale.js +247 -0
- package/dist/judge/locale.js.map +1 -0
- package/dist/judge/scope.d.ts +41 -0
- package/dist/judge/scope.js +47 -0
- package/dist/judge/scope.js.map +1 -0
- package/dist/judge/sensitivity.d.ts +202 -0
- package/dist/judge/sensitivity.js +152 -0
- package/dist/judge/sensitivity.js.map +1 -0
- package/dist/judge/trail.d.ts +122 -0
- package/dist/judge/trail.js +96 -0
- package/dist/judge/trail.js.map +1 -0
- package/dist/judge/verdict.d.ts +39 -0
- package/dist/judge/verdict.js +32 -0
- package/dist/judge/verdict.js.map +1 -0
- package/dist/plan/index.d.ts +12 -0
- package/dist/plan/index.js +11 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/tool.d.ts +85 -0
- package/dist/plan/tool.js +87 -0
- package/dist/plan/tool.js.map +1 -0
- package/dist/relate/graph.d.ts +190 -0
- package/dist/relate/graph.js +234 -0
- package/dist/relate/graph.js.map +1 -0
- package/dist/relate/index.d.ts +16 -0
- package/dist/relate/index.js +16 -0
- package/dist/relate/index.js.map +1 -0
- package/dist/relate/merkle.d.ts +110 -0
- package/dist/relate/merkle.js +246 -0
- package/dist/relate/merkle.js.map +1 -0
- package/dist/relate/reach.d.ts +65 -0
- package/dist/relate/reach.js +111 -0
- package/dist/relate/reach.js.map +1 -0
- package/dist/relate/records.d.ts +129 -0
- package/dist/relate/records.js +112 -0
- package/dist/relate/records.js.map +1 -0
- package/dist/rules/index.d.ts +19 -0
- package/dist/rules/index.js +17 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/normalize/alias.d.ts +50 -0
- package/dist/rules/normalize/alias.js +111 -0
- package/dist/rules/normalize/alias.js.map +1 -0
- package/dist/rules/normalize/cascade.d.ts +85 -0
- package/dist/rules/normalize/cascade.js +324 -0
- package/dist/rules/normalize/cascade.js.map +1 -0
- package/dist/rules/normalize/color.d.ts +30 -0
- package/dist/rules/normalize/color.js +185 -0
- package/dist/rules/normalize/color.js.map +1 -0
- package/dist/rules/normalize/fixture.d.ts +83 -0
- package/dist/rules/normalize/fixture.js +74 -0
- package/dist/rules/normalize/fixture.js.map +1 -0
- package/dist/rules/normalize/index.d.ts +39 -0
- package/dist/rules/normalize/index.js +268 -0
- package/dist/rules/normalize/index.js.map +1 -0
- package/dist/rules/normalize/project.d.ts +42 -0
- package/dist/rules/normalize/project.js +83 -0
- package/dist/rules/normalize/project.js.map +1 -0
- package/dist/rules/normalize/shorthand.d.ts +38 -0
- package/dist/rules/normalize/shorthand.js +388 -0
- package/dist/rules/normalize/shorthand.js.map +1 -0
- package/dist/rules/normalize/value.d.ts +23 -0
- package/dist/rules/normalize/value.js +162 -0
- package/dist/rules/normalize/value.js.map +1 -0
- package/dist/rules/normalize/wrapper.d.ts +16 -0
- package/dist/rules/normalize/wrapper.js +137 -0
- package/dist/rules/normalize/wrapper.js.map +1 -0
- package/dist/rules/ruleset.d.ts +93 -0
- package/dist/rules/ruleset.js +175 -0
- package/dist/rules/ruleset.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +69 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { admits, isCustomProperty } from '../ruleset.js';
|
|
2
|
+
import { canonicalizeValue } from './value.js';
|
|
3
|
+
import { expandDeclaration } from './shorthand.js';
|
|
4
|
+
/**
|
|
5
|
+
* Cascade resolution — ADR-0003 step 5.
|
|
6
|
+
*
|
|
7
|
+
* Losing declarations cannot reach a pixel, so they must not reach a hash. A
|
|
8
|
+
* specificity war that changes *which rule wins* without changing the *winning
|
|
9
|
+
* value* is a refactor, and refactors must not invalidate baselines.
|
|
10
|
+
*
|
|
11
|
+
* Under a profile with computed style the engine has already done this, and its
|
|
12
|
+
* answer is authoritative. This code runs anyway, for two reasons: it supplies
|
|
13
|
+
* the values under `declared-only` profiles, and under both profiles it supplies
|
|
14
|
+
* the attribution side-channel — *which rule set this?* — which computed style
|
|
15
|
+
* cannot answer at all.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Properties that inherit.
|
|
19
|
+
*
|
|
20
|
+
* Restricted to the allowlist: inheriting a property the snapshot does not
|
|
21
|
+
* record would cost work and change nothing. Without this, a `color` set once on
|
|
22
|
+
* a container would be invisible on every descendant that renders text under a
|
|
23
|
+
* declared-only profile — the most common styling pattern there is.
|
|
24
|
+
*/
|
|
25
|
+
export const INHERITED_PROPERTIES = [
|
|
26
|
+
'color', 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',
|
|
27
|
+
'font-stretch', 'line-height', 'letter-spacing', 'word-spacing',
|
|
28
|
+
'text-align', 'text-indent', 'text-transform', 'white-space', 'word-break',
|
|
29
|
+
'overflow-wrap', 'visibility', 'direction', 'writing-mode',
|
|
30
|
+
'caption-side', 'border-collapse', 'border-spacing',
|
|
31
|
+
];
|
|
32
|
+
const INHERITED = new Set(INHERITED_PROPERTIES);
|
|
33
|
+
export const EMPTY_CONTEXT = { inherited: {}, customProperties: {} };
|
|
34
|
+
export function resolveStyle(input) {
|
|
35
|
+
const candidates = collectCandidates(input);
|
|
36
|
+
const winners = new Map();
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
const incumbent = winners.get(candidate.property);
|
|
39
|
+
if (incumbent === undefined || wins(candidate, incumbent)) {
|
|
40
|
+
winners.set(candidate.property, candidate);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const customProperties = { ...input.context.customProperties };
|
|
44
|
+
for (const [property, candidate] of winners) {
|
|
45
|
+
if (isCustomProperty(property))
|
|
46
|
+
customProperties[property] = candidate.value.trim();
|
|
47
|
+
}
|
|
48
|
+
const style = {};
|
|
49
|
+
const tokens = {};
|
|
50
|
+
const origins = {};
|
|
51
|
+
// Inherited values are the floor: a node that declares nothing still renders
|
|
52
|
+
// with what flowed down to it, and a snapshot omitting that would report two
|
|
53
|
+
// visually different nodes as identical.
|
|
54
|
+
for (const [property, value] of Object.entries(input.context.inherited)) {
|
|
55
|
+
style[property] = value;
|
|
56
|
+
// The token's *own* value, not the property's. Storing the inherited
|
|
57
|
+
// property value here made `tokens` mean two different things depending on
|
|
58
|
+
// whether the node also declared the property: a `<button>` recorded
|
|
59
|
+
// `--va-line-height: 1.4` (correct, from the second loop) while the text
|
|
60
|
+
// node under it recorded `--va-line-height: 19.6px` — the used line-height.
|
|
61
|
+
// The differ reads this map to decide whether a token moved, so a font-size
|
|
62
|
+
// change made an untouched token look like it had moved, and the text node
|
|
63
|
+
// became a second root for a change with one cause. Journal 0006's defect 5
|
|
64
|
+
// in a place its corpus could not reach without a layout engine.
|
|
65
|
+
const token = input.context.inheritedTokens?.[property];
|
|
66
|
+
if (token !== undefined) {
|
|
67
|
+
const resolved = customProperties[token];
|
|
68
|
+
// Falls back to the property value when the token's own value is unknown,
|
|
69
|
+
// which is the whole of a declared-only profile: `:root` is outside the
|
|
70
|
+
// subject, so applicability pruning drops it and no custom property can be
|
|
71
|
+
// resolved. The fallback is a deliberate over-report — a stand-in witness
|
|
72
|
+
// that *something* under this token moved — because an absent entry is
|
|
73
|
+
// indistinguishable from "the token held", and that reads as `unchanged`.
|
|
74
|
+
tokens[token] = resolved === undefined ? value : tokenValue(property, resolved, customProperties);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (const [property, candidate] of winners) {
|
|
78
|
+
if (isCustomProperty(property))
|
|
79
|
+
continue;
|
|
80
|
+
const resolution = resolveVariables(candidate.value, customProperties);
|
|
81
|
+
style[property] = canonicalizeValue(property, resolution.value);
|
|
82
|
+
for (const name of resolution.used) {
|
|
83
|
+
const resolved = customProperties[name];
|
|
84
|
+
if (resolved !== undefined)
|
|
85
|
+
tokens[name] = tokenValue(property, resolved, customProperties);
|
|
86
|
+
}
|
|
87
|
+
origins[property] = {
|
|
88
|
+
sheet: candidate.sheet,
|
|
89
|
+
selector: candidate.selector,
|
|
90
|
+
...(candidate.source ? { source: candidate.source } : {}),
|
|
91
|
+
...(resolution.used[0] !== undefined ? { tokenName: resolution.used[0] } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
// The engine's own answer supersedes ours wherever it has one. Ours still
|
|
95
|
+
// produced the origins above, which is the half computed style cannot supply.
|
|
96
|
+
if (input.computedStyle) {
|
|
97
|
+
for (const [property, value] of Object.entries(input.computedStyle)) {
|
|
98
|
+
if (!admits(property) || isCustomProperty(property))
|
|
99
|
+
continue;
|
|
100
|
+
style[property] = canonicalizeValue(property, value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Which token drives each property, resolved once and reused for both this
|
|
104
|
+
// node's own record and what its children inherit.
|
|
105
|
+
//
|
|
106
|
+
// The precedence matters: a node with its own winning declaration is described
|
|
107
|
+
// by *that* declaration's token, or by no token at all — never by the
|
|
108
|
+
// ancestor's. Falling back to the inherited token when a node declared the
|
|
109
|
+
// property itself makes a rule that *overrode* a token look like collateral of
|
|
110
|
+
// it, which splits one docket root into two.
|
|
111
|
+
const propertyTokens = {};
|
|
112
|
+
for (const property of Object.keys(style)) {
|
|
113
|
+
const own = origins[property];
|
|
114
|
+
const token = own !== undefined ? own.tokenName : input.context.inheritedTokens?.[property];
|
|
115
|
+
if (token !== undefined)
|
|
116
|
+
propertyTokens[property] = token;
|
|
117
|
+
}
|
|
118
|
+
const childInherited = {};
|
|
119
|
+
const childInheritedTokens = {};
|
|
120
|
+
for (const [property, value] of Object.entries(style)) {
|
|
121
|
+
if (!INHERITED.has(property))
|
|
122
|
+
continue;
|
|
123
|
+
childInherited[property] = value;
|
|
124
|
+
// Attribution rides along with the value, so a descendant that merely
|
|
125
|
+
// inherits a token-driven colour is still collateral of that token.
|
|
126
|
+
const token = propertyTokens[property];
|
|
127
|
+
if (token !== undefined)
|
|
128
|
+
childInheritedTokens[property] = token;
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
style,
|
|
132
|
+
tokens,
|
|
133
|
+
origins,
|
|
134
|
+
propertyTokens,
|
|
135
|
+
childContext: {
|
|
136
|
+
inherited: childInherited,
|
|
137
|
+
customProperties,
|
|
138
|
+
inheritedTokens: childInheritedTokens,
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function collectCandidates(input) {
|
|
143
|
+
const candidates = [];
|
|
144
|
+
for (const rule of input.matchedRules) {
|
|
145
|
+
for (const declaration of rule.declarations) {
|
|
146
|
+
for (const admitted of admitDeclaration(declaration)) {
|
|
147
|
+
candidates.push({
|
|
148
|
+
...admitted,
|
|
149
|
+
inline: false,
|
|
150
|
+
specificity: specificityRank(rule.specificity),
|
|
151
|
+
order: rule.order,
|
|
152
|
+
sheet: rule.sheet,
|
|
153
|
+
selector: rule.selector,
|
|
154
|
+
...(rule.source ? { source: rule.source } : {}),
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const [property, value] of Object.entries(input.inlineStyle ?? {})) {
|
|
160
|
+
const important = value.trimEnd().toLowerCase().endsWith('!important');
|
|
161
|
+
const bare = important ? value.replace(/!\s*important\s*$/i, '').trim() : value;
|
|
162
|
+
for (const admitted of admitDeclaration({ property, value: bare, important })) {
|
|
163
|
+
candidates.push({
|
|
164
|
+
...admitted,
|
|
165
|
+
inline: true,
|
|
166
|
+
specificity: 0,
|
|
167
|
+
order: Number.MAX_SAFE_INTEGER,
|
|
168
|
+
sheet: 'inline',
|
|
169
|
+
selector: '[style]',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return candidates;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Expand and project one declaration (ADR-0003 step 4).
|
|
177
|
+
*
|
|
178
|
+
* A shorthand that could not be decomposed with confidence is kept under its own
|
|
179
|
+
* name rather than dropped. Dropping it would remove a real value from the hash.
|
|
180
|
+
*/
|
|
181
|
+
function admitDeclaration(declaration) {
|
|
182
|
+
const property = declaration.property.trim().toLowerCase();
|
|
183
|
+
if (isCustomProperty(declaration.property)) {
|
|
184
|
+
// Custom property names are case-sensitive, unlike every other property.
|
|
185
|
+
return [{
|
|
186
|
+
property: declaration.property.trim(),
|
|
187
|
+
value: declaration.value,
|
|
188
|
+
important: declaration.important,
|
|
189
|
+
}];
|
|
190
|
+
}
|
|
191
|
+
const expansion = expandDeclaration(property, declaration.value);
|
|
192
|
+
// An unexpandable shorthand is admitted under its own name. It is not on the
|
|
193
|
+
// allowlist — nothing that decomposes ever is — but dropping it would remove a
|
|
194
|
+
// real declared value from the hash, and a missing value is a false
|
|
195
|
+
// `unchanged`. Under a profile with computed style the engine's longhands
|
|
196
|
+
// arrive separately and supersede it, so this only widens the declared-only tier.
|
|
197
|
+
if (!expansion.confident) {
|
|
198
|
+
return [{ property, value: declaration.value, important: declaration.important }];
|
|
199
|
+
}
|
|
200
|
+
return expansion.declarations
|
|
201
|
+
.filter((entry) => admits(entry.property))
|
|
202
|
+
.map((entry) => ({
|
|
203
|
+
property: entry.property,
|
|
204
|
+
value: entry.value,
|
|
205
|
+
important: declaration.important,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The cascade order that matters here: importance, then origin, then
|
|
210
|
+
* specificity, then document order.
|
|
211
|
+
*
|
|
212
|
+
* Author-origin only — user and user-agent stylesheets are the engine's business,
|
|
213
|
+
* and under a profile with computed style the engine's answer overrides this
|
|
214
|
+
* anyway.
|
|
215
|
+
*/
|
|
216
|
+
function wins(candidate, incumbent) {
|
|
217
|
+
if (candidate.important !== incumbent.important)
|
|
218
|
+
return candidate.important;
|
|
219
|
+
if (candidate.inline !== incumbent.inline)
|
|
220
|
+
return candidate.inline;
|
|
221
|
+
if (candidate.specificity !== incumbent.specificity) {
|
|
222
|
+
return candidate.specificity > incumbent.specificity;
|
|
223
|
+
}
|
|
224
|
+
return candidate.order >= incumbent.order;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Collapse `[id, class, type]` into one comparable number.
|
|
228
|
+
*
|
|
229
|
+
* The 1 000-per-column base is far above any real selector's count in a column,
|
|
230
|
+
* so this cannot produce the classic "255 classes outrank an id" overflow bug.
|
|
231
|
+
*/
|
|
232
|
+
function specificityRank(specificity) {
|
|
233
|
+
return specificity[0] * 1_000_000 + specificity[1] * 1_000 + specificity[2];
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Substitute `var(--name, fallback)` against the in-scope custom properties.
|
|
237
|
+
*
|
|
238
|
+
* The *names* are recorded alongside the resolved value, and that is the point:
|
|
239
|
+
* a name is what lets one design-token edit collapse into a single docket root
|
|
240
|
+
* with counted collateral, instead of hundreds of unrelated colour diffs
|
|
241
|
+
* (spec §5, `token` band). A resolved value alone cannot be grouped.
|
|
242
|
+
*/
|
|
243
|
+
/**
|
|
244
|
+
* A custom property's value, resolved through any custom properties it uses.
|
|
245
|
+
*
|
|
246
|
+
* The distinction is not cosmetic and it broke the product's headline claim.
|
|
247
|
+
* `--ks-card-radius: var(--va-radius-md)` is how every real design system is
|
|
248
|
+
* built — semantic tokens aliasing primitive ones — and recording the *declared*
|
|
249
|
+
* value stores the literal string `var(--va-radius-md)` on both sides of a
|
|
250
|
+
* change to `--va-radius-md`. The differ reads this map to decide whether a
|
|
251
|
+
* token moved, sees two identical strings, and concludes it did not. So the root
|
|
252
|
+
* fell through to `component:Card`: **one edit to the radius scale was reported
|
|
253
|
+
* as "Card changed internally", once per consuming component**, instead of as
|
|
254
|
+
* one token root with counted collateral.
|
|
255
|
+
*
|
|
256
|
+
* The corpus declared the right answer for `token-radius/card` — its `spec` line
|
|
257
|
+
* reads "token delta ⇒ token is the root" — and asserted only the root *count*,
|
|
258
|
+
* which is one either way.
|
|
259
|
+
*/
|
|
260
|
+
function tokenValue(property, declared, customProperties) {
|
|
261
|
+
return canonicalizeValue(property, resolveVariables(declared, customProperties).value);
|
|
262
|
+
}
|
|
263
|
+
export function resolveVariables(value, customProperties, depth = 0) {
|
|
264
|
+
if (!value.includes('var(') || depth > 16)
|
|
265
|
+
return { value, used: [] };
|
|
266
|
+
const used = [];
|
|
267
|
+
let result = '';
|
|
268
|
+
let index = 0;
|
|
269
|
+
while (index < value.length) {
|
|
270
|
+
const start = value.indexOf('var(', index);
|
|
271
|
+
if (start < 0) {
|
|
272
|
+
result += value.slice(index);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
result += value.slice(index, start);
|
|
276
|
+
const end = matchingParen(value, start + 3);
|
|
277
|
+
const inner = value.slice(start + 4, end);
|
|
278
|
+
const comma = topLevelComma(inner);
|
|
279
|
+
const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();
|
|
280
|
+
const fallback = comma < 0 ? undefined : inner.slice(comma + 1).trim();
|
|
281
|
+
if (!used.includes(name))
|
|
282
|
+
used.push(name);
|
|
283
|
+
const declared = customProperties[name];
|
|
284
|
+
const substituted = declared ?? fallback ?? '';
|
|
285
|
+
// A custom property may itself reference another. The depth cap is a cycle
|
|
286
|
+
// guard: `--a: var(--b); --b: var(--a)` is invalid CSS the engine drops, and
|
|
287
|
+
// recursing on it would hang the collector rather than report anything.
|
|
288
|
+
const nested = resolveVariables(substituted, customProperties, depth + 1);
|
|
289
|
+
result += nested.value;
|
|
290
|
+
for (const nestedName of nested.used) {
|
|
291
|
+
if (!used.includes(nestedName))
|
|
292
|
+
used.push(nestedName);
|
|
293
|
+
}
|
|
294
|
+
index = end + 1;
|
|
295
|
+
}
|
|
296
|
+
return { value: result, used };
|
|
297
|
+
}
|
|
298
|
+
function matchingParen(input, openIndex) {
|
|
299
|
+
let depth = 0;
|
|
300
|
+
for (let i = openIndex; i < input.length; i += 1) {
|
|
301
|
+
if (input[i] === '(')
|
|
302
|
+
depth += 1;
|
|
303
|
+
else if (input[i] === ')') {
|
|
304
|
+
depth -= 1;
|
|
305
|
+
if (depth === 0)
|
|
306
|
+
return i;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return input.length;
|
|
310
|
+
}
|
|
311
|
+
function topLevelComma(input) {
|
|
312
|
+
let depth = 0;
|
|
313
|
+
for (let i = 0; i < input.length; i += 1) {
|
|
314
|
+
const char = input[i];
|
|
315
|
+
if (char === '(')
|
|
316
|
+
depth += 1;
|
|
317
|
+
else if (char === ')')
|
|
318
|
+
depth -= 1;
|
|
319
|
+
else if (char === ',' && depth === 0)
|
|
320
|
+
return i;
|
|
321
|
+
}
|
|
322
|
+
return -1;
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=cascade.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cascade.js","sourceRoot":"","sources":["../../../src/rules/normalize/cascade.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Declaration, MatchedRule } from '../../format/capture.js';\nimport { admits, isCustomProperty } from '../ruleset.js';\nimport { canonicalizeValue } from './value.js';\nimport { expandDeclaration } from './shorthand.js';\n\n/**\n * Cascade resolution — ADR-0003 step 5.\n *\n * Losing declarations cannot reach a pixel, so they must not reach a hash. A\n * specificity war that changes *which rule wins* without changing the *winning\n * value* is a refactor, and refactors must not invalidate baselines.\n *\n * Under a profile with computed style the engine has already done this, and its\n * answer is authoritative. This code runs anyway, for two reasons: it supplies\n * the values under `declared-only` profiles, and under both profiles it supplies\n * the attribution side-channel — *which rule set this?* — which computed style\n * cannot answer at all.\n */\n\n/**\n * Properties that inherit.\n *\n * Restricted to the allowlist: inheriting a property the snapshot does not\n * record would cost work and change nothing. Without this, a `color` set once on\n * a container would be invisible on every descendant that renders text under a\n * declared-only profile — the most common styling pattern there is.\n */\nexport const INHERITED_PROPERTIES: readonly string[] = [\n 'color', 'font-family', 'font-size', 'font-weight', 'font-style', 'font-variant',\n 'font-stretch', 'line-height', 'letter-spacing', 'word-spacing',\n 'text-align', 'text-indent', 'text-transform', 'white-space', 'word-break',\n 'overflow-wrap', 'visibility', 'direction', 'writing-mode',\n 'caption-side', 'border-collapse', 'border-spacing',\n];\n\nconst INHERITED = new Set(INHERITED_PROPERTIES);\n\n/** Values flowing into a node from its ancestors. */\nexport interface InheritContext {\n /** Resolved values of inheritable properties. */\n readonly inherited: Readonly<Record<string, string>>;\n /** Custom properties in scope. Every custom property inherits. */\n readonly customProperties: Readonly<Record<string, string>>;\n\n /**\n * For each inherited property, the token its value came from.\n *\n * Inheritance would otherwise launder away the attribution: a heading that\n * inherits `color` from a container styled with `var(--brand)` produces a\n * delta with no token, no owner chain of its own, and therefore its own\n * `unattributed` root. One token edit would fan out into a docket entry per\n * inheriting text node — destroying precisely the \"one root, N collateral\"\n * claim the token band exists to make.\n */\n readonly inheritedTokens?: Readonly<Record<string, string>>;\n}\n\nexport const EMPTY_CONTEXT: InheritContext = { inherited: {}, customProperties: {} };\n\nexport interface ResolvedStyle {\n /** Winning, canonicalized values for allowlisted properties. */\n readonly style: Readonly<Record<string, string>>;\n /** Custom properties a winning value resolved through: name → resolved value. */\n readonly tokens: Readonly<Record<string, string>>;\n /** Where each winning declaration came from. Outside the hash by design. */\n readonly origins: Readonly<Record<string, DeclarationOrigin>>;\n /**\n * Which token each property's value came from, including inherited ones.\n *\n * Needed because \"this node uses tokens\" is not specific enough to attribute a\n * delta: a node may resolve `color` through a token while its `padding` comes\n * from a literal, and only the first is collateral of a token edit.\n */\n readonly propertyTokens: Readonly<Record<string, string>>;\n /** Context to pass to this node's children. */\n readonly childContext: InheritContext;\n}\n\nexport interface DeclarationOrigin {\n readonly sheet: string;\n readonly selector: string;\n readonly source?: { readonly file: string; readonly line: number };\n readonly tokenName?: string;\n}\n\ninterface Candidate {\n readonly property: string;\n readonly value: string;\n readonly important: boolean;\n readonly inline: boolean;\n readonly specificity: number;\n readonly order: number;\n readonly sheet: string;\n readonly selector: string;\n readonly source?: { readonly file: string; readonly line: number };\n}\n\nexport interface ResolveInput {\n readonly matchedRules: readonly MatchedRule[];\n readonly inlineStyle?: Readonly<Record<string, string>>;\n /** Engine-resolved values. When present these win over anything computed here. */\n readonly computedStyle?: Readonly<Record<string, string>>;\n readonly context: InheritContext;\n}\n\nexport function resolveStyle(input: ResolveInput): ResolvedStyle {\n const candidates = collectCandidates(input);\n const winners = new Map<string, Candidate>();\n\n for (const candidate of candidates) {\n const incumbent = winners.get(candidate.property);\n if (incumbent === undefined || wins(candidate, incumbent)) {\n winners.set(candidate.property, candidate);\n }\n }\n\n const customProperties: Record<string, string> = { ...input.context.customProperties };\n for (const [property, candidate] of winners) {\n if (isCustomProperty(property)) customProperties[property] = candidate.value.trim();\n }\n\n const style: Record<string, string> = {};\n const tokens: Record<string, string> = {};\n const origins: Record<string, DeclarationOrigin> = {};\n\n // Inherited values are the floor: a node that declares nothing still renders\n // with what flowed down to it, and a snapshot omitting that would report two\n // visually different nodes as identical.\n for (const [property, value] of Object.entries(input.context.inherited)) {\n style[property] = value;\n\n // The token's *own* value, not the property's. Storing the inherited\n // property value here made `tokens` mean two different things depending on\n // whether the node also declared the property: a `<button>` recorded\n // `--va-line-height: 1.4` (correct, from the second loop) while the text\n // node under it recorded `--va-line-height: 19.6px` — the used line-height.\n // The differ reads this map to decide whether a token moved, so a font-size\n // change made an untouched token look like it had moved, and the text node\n // became a second root for a change with one cause. Journal 0006's defect 5\n // in a place its corpus could not reach without a layout engine.\n const token = input.context.inheritedTokens?.[property];\n if (token !== undefined) {\n const resolved = customProperties[token];\n // Falls back to the property value when the token's own value is unknown,\n // which is the whole of a declared-only profile: `:root` is outside the\n // subject, so applicability pruning drops it and no custom property can be\n // resolved. The fallback is a deliberate over-report — a stand-in witness\n // that *something* under this token moved — because an absent entry is\n // indistinguishable from \"the token held\", and that reads as `unchanged`.\n tokens[token] = resolved === undefined ? value : tokenValue(property, resolved, customProperties);\n }\n }\n\n for (const [property, candidate] of winners) {\n if (isCustomProperty(property)) continue;\n\n const resolution = resolveVariables(candidate.value, customProperties);\n style[property] = canonicalizeValue(property, resolution.value);\n\n for (const name of resolution.used) {\n const resolved = customProperties[name];\n if (resolved !== undefined) tokens[name] = tokenValue(property, resolved, customProperties);\n }\n\n origins[property] = {\n sheet: candidate.sheet,\n selector: candidate.selector,\n ...(candidate.source ? { source: candidate.source } : {}),\n ...(resolution.used[0] !== undefined ? { tokenName: resolution.used[0] } : {}),\n };\n }\n\n // The engine's own answer supersedes ours wherever it has one. Ours still\n // produced the origins above, which is the half computed style cannot supply.\n if (input.computedStyle) {\n for (const [property, value] of Object.entries(input.computedStyle)) {\n if (!admits(property) || isCustomProperty(property)) continue;\n style[property] = canonicalizeValue(property, value);\n }\n }\n\n // Which token drives each property, resolved once and reused for both this\n // node's own record and what its children inherit.\n //\n // The precedence matters: a node with its own winning declaration is described\n // by *that* declaration's token, or by no token at all — never by the\n // ancestor's. Falling back to the inherited token when a node declared the\n // property itself makes a rule that *overrode* a token look like collateral of\n // it, which splits one docket root into two.\n const propertyTokens: Record<string, string> = {};\n for (const property of Object.keys(style)) {\n const own = origins[property];\n const token = own !== undefined ? own.tokenName : input.context.inheritedTokens?.[property];\n if (token !== undefined) propertyTokens[property] = token;\n }\n\n const childInherited: Record<string, string> = {};\n const childInheritedTokens: Record<string, string> = {};\n\n for (const [property, value] of Object.entries(style)) {\n if (!INHERITED.has(property)) continue;\n childInherited[property] = value;\n\n // Attribution rides along with the value, so a descendant that merely\n // inherits a token-driven colour is still collateral of that token.\n const token = propertyTokens[property];\n if (token !== undefined) childInheritedTokens[property] = token;\n }\n\n return {\n style,\n tokens,\n origins,\n propertyTokens,\n childContext: {\n inherited: childInherited,\n customProperties,\n inheritedTokens: childInheritedTokens,\n },\n };\n}\n\nfunction collectCandidates(input: ResolveInput): Candidate[] {\n const candidates: Candidate[] = [];\n\n for (const rule of input.matchedRules) {\n for (const declaration of rule.declarations) {\n for (const admitted of admitDeclaration(declaration)) {\n candidates.push({\n ...admitted,\n inline: false,\n specificity: specificityRank(rule.specificity),\n order: rule.order,\n sheet: rule.sheet,\n selector: rule.selector,\n ...(rule.source ? { source: rule.source } : {}),\n });\n }\n }\n }\n\n for (const [property, value] of Object.entries(input.inlineStyle ?? {})) {\n const important = value.trimEnd().toLowerCase().endsWith('!important');\n const bare = important ? value.replace(/!\\s*important\\s*$/i, '').trim() : value;\n\n for (const admitted of admitDeclaration({ property, value: bare, important })) {\n candidates.push({\n ...admitted,\n inline: true,\n specificity: 0,\n order: Number.MAX_SAFE_INTEGER,\n sheet: 'inline',\n selector: '[style]',\n });\n }\n }\n\n return candidates;\n}\n\n/**\n * Expand and project one declaration (ADR-0003 step 4).\n *\n * A shorthand that could not be decomposed with confidence is kept under its own\n * name rather than dropped. Dropping it would remove a real value from the hash.\n */\nfunction admitDeclaration(\n declaration: Declaration,\n): readonly { property: string; value: string; important: boolean }[] {\n const property = declaration.property.trim().toLowerCase();\n\n if (isCustomProperty(declaration.property)) {\n // Custom property names are case-sensitive, unlike every other property.\n return [{\n property: declaration.property.trim(),\n value: declaration.value,\n important: declaration.important,\n }];\n }\n\n const expansion = expandDeclaration(property, declaration.value);\n\n // An unexpandable shorthand is admitted under its own name. It is not on the\n // allowlist — nothing that decomposes ever is — but dropping it would remove a\n // real declared value from the hash, and a missing value is a false\n // `unchanged`. Under a profile with computed style the engine's longhands\n // arrive separately and supersede it, so this only widens the declared-only tier.\n if (!expansion.confident) {\n return [{ property, value: declaration.value, important: declaration.important }];\n }\n\n return expansion.declarations\n .filter((entry) => admits(entry.property))\n .map((entry) => ({\n property: entry.property,\n value: entry.value,\n important: declaration.important,\n }));\n}\n\n/**\n * The cascade order that matters here: importance, then origin, then\n * specificity, then document order.\n *\n * Author-origin only — user and user-agent stylesheets are the engine's business,\n * and under a profile with computed style the engine's answer overrides this\n * anyway.\n */\nfunction wins(candidate: Candidate, incumbent: Candidate): boolean {\n if (candidate.important !== incumbent.important) return candidate.important;\n if (candidate.inline !== incumbent.inline) return candidate.inline;\n if (candidate.specificity !== incumbent.specificity) {\n return candidate.specificity > incumbent.specificity;\n }\n return candidate.order >= incumbent.order;\n}\n\n/**\n * Collapse `[id, class, type]` into one comparable number.\n *\n * The 1 000-per-column base is far above any real selector's count in a column,\n * so this cannot produce the classic \"255 classes outrank an id\" overflow bug.\n */\nfunction specificityRank(specificity: readonly [number, number, number]): number {\n return specificity[0] * 1_000_000 + specificity[1] * 1_000 + specificity[2];\n}\n\ninterface VariableResolution {\n readonly value: string;\n /** Custom property names consulted, in order of first appearance. */\n readonly used: readonly string[];\n}\n\n/**\n * Substitute `var(--name, fallback)` against the in-scope custom properties.\n *\n * The *names* are recorded alongside the resolved value, and that is the point:\n * a name is what lets one design-token edit collapse into a single docket root\n * with counted collateral, instead of hundreds of unrelated colour diffs\n * (spec §5, `token` band). A resolved value alone cannot be grouped.\n */\n/**\n * A custom property's value, resolved through any custom properties it uses.\n *\n * The distinction is not cosmetic and it broke the product's headline claim.\n * `--ks-card-radius: var(--va-radius-md)` is how every real design system is\n * built — semantic tokens aliasing primitive ones — and recording the *declared*\n * value stores the literal string `var(--va-radius-md)` on both sides of a\n * change to `--va-radius-md`. The differ reads this map to decide whether a\n * token moved, sees two identical strings, and concludes it did not. So the root\n * fell through to `component:Card`: **one edit to the radius scale was reported\n * as \"Card changed internally\", once per consuming component**, instead of as\n * one token root with counted collateral.\n *\n * The corpus declared the right answer for `token-radius/card` — its `spec` line\n * reads \"token delta ⇒ token is the root\" — and asserted only the root *count*,\n * which is one either way.\n */\nfunction tokenValue(\n property: string,\n declared: string,\n customProperties: Readonly<Record<string, string>>,\n): string {\n return canonicalizeValue(property, resolveVariables(declared, customProperties).value);\n}\n\nexport function resolveVariables(\n value: string,\n customProperties: Readonly<Record<string, string>>,\n depth = 0,\n): VariableResolution {\n if (!value.includes('var(') || depth > 16) return { value, used: [] };\n\n const used: string[] = [];\n let result = '';\n let index = 0;\n\n while (index < value.length) {\n const start = value.indexOf('var(', index);\n if (start < 0) {\n result += value.slice(index);\n break;\n }\n\n result += value.slice(index, start);\n\n const end = matchingParen(value, start + 3);\n const inner = value.slice(start + 4, end);\n const comma = topLevelComma(inner);\n const name = (comma < 0 ? inner : inner.slice(0, comma)).trim();\n const fallback = comma < 0 ? undefined : inner.slice(comma + 1).trim();\n\n if (!used.includes(name)) used.push(name);\n\n const declared = customProperties[name];\n const substituted = declared ?? fallback ?? '';\n\n // A custom property may itself reference another. The depth cap is a cycle\n // guard: `--a: var(--b); --b: var(--a)` is invalid CSS the engine drops, and\n // recursing on it would hang the collector rather than report anything.\n const nested = resolveVariables(substituted, customProperties, depth + 1);\n result += nested.value;\n for (const nestedName of nested.used) {\n if (!used.includes(nestedName)) used.push(nestedName);\n }\n\n index = end + 1;\n }\n\n return { value: result, used };\n}\n\nfunction matchingParen(input: string, openIndex: number): number {\n let depth = 0;\n for (let i = openIndex; i < input.length; i += 1) {\n if (input[i] === '(') depth += 1;\n else if (input[i] === ')') {\n depth -= 1;\n if (depth === 0) return i;\n }\n }\n return input.length;\n}\n\nfunction topLevelComma(input: string): number {\n let depth = 0;\n for (let i = 0; i < input.length; i += 1) {\n const char = input[i];\n if (char === '(') depth += 1;\n else if (char === ')') depth -= 1;\n else if (char === ',' && depth === 0) return i;\n }\n return -1;\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour canonicalization.
|
|
3
|
+
*
|
|
4
|
+
* `red`, `#f00`, `#ff0000`, `rgb(255,0,0)`, `rgb(255 0 0)`, and
|
|
5
|
+
* `hsl(0, 100%, 50%)` all paint identical pixels. Left alone they produce six
|
|
6
|
+
* different hashes, so a stylesheet refactor that swaps hex for a named colour
|
|
7
|
+
* would invalidate every baseline it touches while changing nothing a user sees.
|
|
8
|
+
*
|
|
9
|
+
* Everything sRGB-expressible collapses to `rgb(R G B / A)`. Wide-gamut and
|
|
10
|
+
* relative forms (`oklch`, `color-mix`, `light-dark`) are *not* converted:
|
|
11
|
+
* doing so requires colour-space maths whose rounding would itself become a
|
|
12
|
+
* source of hash instability, and — worse — would silently claim two colours are
|
|
13
|
+
* equal when the engine may render them differently. They are whitespace- and
|
|
14
|
+
* case-normalized only, and left symbolic.
|
|
15
|
+
*/
|
|
16
|
+
export type Rgba = readonly [number, number, number, number];
|
|
17
|
+
/** Parse any sRGB-expressible colour. Returns `null` for anything else. */
|
|
18
|
+
export declare function parseColor(input: string): Rgba | null;
|
|
19
|
+
/**
|
|
20
|
+
* Serialize to the single canonical form: `rgb(R G B / A)`.
|
|
21
|
+
*
|
|
22
|
+
* Channels are integers; alpha keeps three decimals, enough to distinguish every
|
|
23
|
+
* 8-bit alpha step while discarding float noise from `hsl` conversion.
|
|
24
|
+
*/
|
|
25
|
+
export declare function formatColor([r, g, b, a]: Rgba): string;
|
|
26
|
+
/** Canonicalize if recognizable; otherwise normalize whitespace and case only. */
|
|
27
|
+
export declare function canonicalizeColor(input: string): string;
|
|
28
|
+
/** Whether a property's value should be read as a colour. */
|
|
29
|
+
export declare function isColorProperty(property: string): boolean;
|
|
30
|
+
//# sourceMappingURL=color.d.ts.map
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour canonicalization.
|
|
3
|
+
*
|
|
4
|
+
* `red`, `#f00`, `#ff0000`, `rgb(255,0,0)`, `rgb(255 0 0)`, and
|
|
5
|
+
* `hsl(0, 100%, 50%)` all paint identical pixels. Left alone they produce six
|
|
6
|
+
* different hashes, so a stylesheet refactor that swaps hex for a named colour
|
|
7
|
+
* would invalidate every baseline it touches while changing nothing a user sees.
|
|
8
|
+
*
|
|
9
|
+
* Everything sRGB-expressible collapses to `rgb(R G B / A)`. Wide-gamut and
|
|
10
|
+
* relative forms (`oklch`, `color-mix`, `light-dark`) are *not* converted:
|
|
11
|
+
* doing so requires colour-space maths whose rounding would itself become a
|
|
12
|
+
* source of hash instability, and — worse — would silently claim two colours are
|
|
13
|
+
* equal when the engine may render them differently. They are whitespace- and
|
|
14
|
+
* case-normalized only, and left symbolic.
|
|
15
|
+
*/
|
|
16
|
+
/** Named CSS colours. `transparent` included; `currentcolor` deliberately not. */
|
|
17
|
+
const NAMED = {
|
|
18
|
+
transparent: [0, 0, 0, 0],
|
|
19
|
+
aliceblue: [240, 248, 255, 1], antiquewhite: [250, 235, 215, 1], aqua: [0, 255, 255, 1],
|
|
20
|
+
aquamarine: [127, 255, 212, 1], azure: [240, 255, 255, 1], beige: [245, 245, 220, 1],
|
|
21
|
+
bisque: [255, 228, 196, 1], black: [0, 0, 0, 1], blanchedalmond: [255, 235, 205, 1],
|
|
22
|
+
blue: [0, 0, 255, 1], blueviolet: [138, 43, 226, 1], brown: [165, 42, 42, 1],
|
|
23
|
+
burlywood: [222, 184, 135, 1], cadetblue: [95, 158, 160, 1], chartreuse: [127, 255, 0, 1],
|
|
24
|
+
chocolate: [210, 105, 30, 1], coral: [255, 127, 80, 1], cornflowerblue: [100, 149, 237, 1],
|
|
25
|
+
cornsilk: [255, 248, 220, 1], crimson: [220, 20, 60, 1], cyan: [0, 255, 255, 1],
|
|
26
|
+
darkblue: [0, 0, 139, 1], darkcyan: [0, 139, 139, 1], darkgoldenrod: [184, 134, 11, 1],
|
|
27
|
+
darkgray: [169, 169, 169, 1], darkgreen: [0, 100, 0, 1], darkgrey: [169, 169, 169, 1],
|
|
28
|
+
darkkhaki: [189, 183, 107, 1], darkmagenta: [139, 0, 139, 1], darkolivegreen: [85, 107, 47, 1],
|
|
29
|
+
darkorange: [255, 140, 0, 1], darkorchid: [153, 50, 204, 1], darkred: [139, 0, 0, 1],
|
|
30
|
+
darksalmon: [233, 150, 122, 1], darkseagreen: [143, 188, 143, 1], darkslateblue: [72, 61, 139, 1],
|
|
31
|
+
darkslategray: [47, 79, 79, 1], darkslategrey: [47, 79, 79, 1], darkturquoise: [0, 206, 209, 1],
|
|
32
|
+
darkviolet: [148, 0, 211, 1], deeppink: [255, 20, 147, 1], deepskyblue: [0, 191, 255, 1],
|
|
33
|
+
dimgray: [105, 105, 105, 1], dimgrey: [105, 105, 105, 1], dodgerblue: [30, 144, 255, 1],
|
|
34
|
+
firebrick: [178, 34, 34, 1], floralwhite: [255, 250, 240, 1], forestgreen: [34, 139, 34, 1],
|
|
35
|
+
fuchsia: [255, 0, 255, 1], gainsboro: [220, 220, 220, 1], ghostwhite: [248, 248, 255, 1],
|
|
36
|
+
gold: [255, 215, 0, 1], goldenrod: [218, 165, 32, 1], gray: [128, 128, 128, 1],
|
|
37
|
+
green: [0, 128, 0, 1], greenyellow: [173, 255, 47, 1], grey: [128, 128, 128, 1],
|
|
38
|
+
honeydew: [240, 255, 240, 1], hotpink: [255, 105, 180, 1], indianred: [205, 92, 92, 1],
|
|
39
|
+
indigo: [75, 0, 130, 1], ivory: [255, 255, 240, 1], khaki: [240, 230, 140, 1],
|
|
40
|
+
lavender: [230, 230, 250, 1], lavenderblush: [255, 240, 245, 1], lawngreen: [124, 252, 0, 1],
|
|
41
|
+
lemonchiffon: [255, 250, 205, 1], lightblue: [173, 216, 230, 1], lightcoral: [240, 128, 128, 1],
|
|
42
|
+
lightcyan: [224, 255, 255, 1], lightgoldenrodyellow: [250, 250, 210, 1],
|
|
43
|
+
lightgray: [211, 211, 211, 1], lightgreen: [144, 238, 144, 1], lightgrey: [211, 211, 211, 1],
|
|
44
|
+
lightpink: [255, 182, 193, 1], lightsalmon: [255, 160, 122, 1], lightseagreen: [32, 178, 170, 1],
|
|
45
|
+
lightskyblue: [135, 206, 250, 1], lightslategray: [119, 136, 153, 1],
|
|
46
|
+
lightslategrey: [119, 136, 153, 1], lightsteelblue: [176, 196, 222, 1],
|
|
47
|
+
lightyellow: [255, 255, 224, 1], lime: [0, 255, 0, 1], limegreen: [50, 205, 50, 1],
|
|
48
|
+
linen: [250, 240, 230, 1], magenta: [255, 0, 255, 1], maroon: [128, 0, 0, 1],
|
|
49
|
+
mediumaquamarine: [102, 205, 170, 1], mediumblue: [0, 0, 205, 1],
|
|
50
|
+
mediumorchid: [186, 85, 211, 1], mediumpurple: [147, 112, 219, 1],
|
|
51
|
+
mediumseagreen: [60, 179, 113, 1], mediumslateblue: [123, 104, 238, 1],
|
|
52
|
+
mediumspringgreen: [0, 250, 154, 1], mediumturquoise: [72, 209, 204, 1],
|
|
53
|
+
mediumvioletred: [199, 21, 133, 1], midnightblue: [25, 25, 112, 1], mintcream: [245, 255, 250, 1],
|
|
54
|
+
mistyrose: [255, 228, 225, 1], moccasin: [255, 228, 181, 1], navajowhite: [255, 222, 173, 1],
|
|
55
|
+
navy: [0, 0, 128, 1], oldlace: [253, 245, 230, 1], olive: [128, 128, 0, 1],
|
|
56
|
+
olivedrab: [107, 142, 35, 1], orange: [255, 165, 0, 1], orangered: [255, 69, 0, 1],
|
|
57
|
+
orchid: [218, 112, 214, 1], palegoldenrod: [238, 232, 170, 1], palegreen: [152, 251, 152, 1],
|
|
58
|
+
paleturquoise: [175, 238, 238, 1], palevioletred: [219, 112, 147, 1],
|
|
59
|
+
papayawhip: [255, 239, 213, 1], peachpuff: [255, 218, 185, 1], peru: [205, 133, 63, 1],
|
|
60
|
+
pink: [255, 192, 203, 1], plum: [221, 160, 221, 1], powderblue: [176, 224, 230, 1],
|
|
61
|
+
purple: [128, 0, 128, 1], rebeccapurple: [102, 51, 153, 1], red: [255, 0, 0, 1],
|
|
62
|
+
rosybrown: [188, 143, 143, 1], royalblue: [65, 105, 225, 1], saddlebrown: [139, 69, 19, 1],
|
|
63
|
+
salmon: [250, 128, 114, 1], sandybrown: [244, 164, 96, 1], seagreen: [46, 139, 87, 1],
|
|
64
|
+
seashell: [255, 245, 238, 1], sienna: [160, 82, 45, 1], silver: [192, 192, 192, 1],
|
|
65
|
+
skyblue: [135, 206, 235, 1], slateblue: [106, 90, 205, 1], slategray: [112, 128, 144, 1],
|
|
66
|
+
slategrey: [112, 128, 144, 1], snow: [255, 250, 250, 1], springgreen: [0, 255, 127, 1],
|
|
67
|
+
steelblue: [70, 130, 180, 1], tan: [210, 180, 140, 1], teal: [0, 128, 128, 1],
|
|
68
|
+
thistle: [216, 191, 216, 1], tomato: [255, 99, 71, 1], turquoise: [64, 224, 208, 1],
|
|
69
|
+
violet: [238, 130, 238, 1], wheat: [245, 222, 179, 1], white: [255, 255, 255, 1],
|
|
70
|
+
whitesmoke: [245, 245, 245, 1], yellow: [255, 255, 0, 1], yellowgreen: [154, 205, 50, 1],
|
|
71
|
+
};
|
|
72
|
+
/** Parse any sRGB-expressible colour. Returns `null` for anything else. */
|
|
73
|
+
export function parseColor(input) {
|
|
74
|
+
const value = input.trim().toLowerCase();
|
|
75
|
+
const named = NAMED[value];
|
|
76
|
+
if (named)
|
|
77
|
+
return named;
|
|
78
|
+
if (value.startsWith('#'))
|
|
79
|
+
return parseHex(value);
|
|
80
|
+
if (value.startsWith('rgb'))
|
|
81
|
+
return parseRgbFunction(value);
|
|
82
|
+
if (value.startsWith('hsl'))
|
|
83
|
+
return parseHslFunction(value);
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Serialize to the single canonical form: `rgb(R G B / A)`.
|
|
88
|
+
*
|
|
89
|
+
* Channels are integers; alpha keeps three decimals, enough to distinguish every
|
|
90
|
+
* 8-bit alpha step while discarding float noise from `hsl` conversion.
|
|
91
|
+
*/
|
|
92
|
+
export function formatColor([r, g, b, a]) {
|
|
93
|
+
const channels = `${clampChannel(r)} ${clampChannel(g)} ${clampChannel(b)}`;
|
|
94
|
+
const alpha = Math.round(clamp(a, 0, 1) * 1000) / 1000;
|
|
95
|
+
return `rgb(${channels} / ${alpha})`;
|
|
96
|
+
}
|
|
97
|
+
/** Canonicalize if recognizable; otherwise normalize whitespace and case only. */
|
|
98
|
+
export function canonicalizeColor(input) {
|
|
99
|
+
const parsed = parseColor(input);
|
|
100
|
+
return parsed ? formatColor(parsed) : collapseWhitespace(input);
|
|
101
|
+
}
|
|
102
|
+
/** Whether a property's value should be read as a colour. */
|
|
103
|
+
export function isColorProperty(property) {
|
|
104
|
+
return (property === 'color' ||
|
|
105
|
+
property.endsWith('-color') ||
|
|
106
|
+
property === 'background-color' ||
|
|
107
|
+
property === 'caret-color');
|
|
108
|
+
}
|
|
109
|
+
function parseHex(value) {
|
|
110
|
+
const hex = value.slice(1);
|
|
111
|
+
const expand = (c) => Number.parseInt(c + c, 16);
|
|
112
|
+
const pair = (i) => Number.parseInt(hex.slice(i, i + 2), 16);
|
|
113
|
+
switch (hex.length) {
|
|
114
|
+
case 3:
|
|
115
|
+
return [expand(hex[0]), expand(hex[1]), expand(hex[2]), 1];
|
|
116
|
+
case 4:
|
|
117
|
+
return [expand(hex[0]), expand(hex[1]), expand(hex[2]), expand(hex[3]) / 255];
|
|
118
|
+
case 6:
|
|
119
|
+
return [pair(0), pair(2), pair(4), 1];
|
|
120
|
+
case 8:
|
|
121
|
+
return [pair(0), pair(2), pair(4), pair(6) / 255];
|
|
122
|
+
default:
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Accepts both the legacy comma syntax and the modern space syntax, which are
|
|
128
|
+
* the same colour written two ways — a distinction no renderer preserves and no
|
|
129
|
+
* baseline should record.
|
|
130
|
+
*/
|
|
131
|
+
function parseRgbFunction(value) {
|
|
132
|
+
const args = functionArguments(value);
|
|
133
|
+
if (!args || args.length < 3)
|
|
134
|
+
return null;
|
|
135
|
+
const channel = (raw) => raw.endsWith('%') ? (Number.parseFloat(raw) / 100) * 255 : Number.parseFloat(raw);
|
|
136
|
+
const r = channel(args[0]);
|
|
137
|
+
const g = channel(args[1]);
|
|
138
|
+
const b = channel(args[2]);
|
|
139
|
+
const a = args.length > 3 ? parseAlpha(args[3]) : 1;
|
|
140
|
+
return [r, g, b, a].some(Number.isNaN) ? null : [Math.round(r), Math.round(g), Math.round(b), a];
|
|
141
|
+
}
|
|
142
|
+
function parseHslFunction(value) {
|
|
143
|
+
const args = functionArguments(value);
|
|
144
|
+
if (!args || args.length < 3)
|
|
145
|
+
return null;
|
|
146
|
+
const h = ((Number.parseFloat(args[0]) % 360) + 360) % 360;
|
|
147
|
+
const s = Number.parseFloat(args[1]) / 100;
|
|
148
|
+
const l = Number.parseFloat(args[2]) / 100;
|
|
149
|
+
const a = args.length > 3 ? parseAlpha(args[3]) : 1;
|
|
150
|
+
if ([h, s, l, a].some(Number.isNaN))
|
|
151
|
+
return null;
|
|
152
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
153
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
154
|
+
const m = l - c / 2;
|
|
155
|
+
const sextant = Math.floor(h / 60) % 6;
|
|
156
|
+
const rgb = [
|
|
157
|
+
[c, x, 0], [x, c, 0], [0, c, x], [0, x, c], [x, 0, c], [c, 0, x],
|
|
158
|
+
];
|
|
159
|
+
const [r, g, b] = rgb[sextant];
|
|
160
|
+
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255), a];
|
|
161
|
+
}
|
|
162
|
+
function parseAlpha(raw) {
|
|
163
|
+
return raw.endsWith('%') ? Number.parseFloat(raw) / 100 : Number.parseFloat(raw);
|
|
164
|
+
}
|
|
165
|
+
/** Split `fn(a, b c / d)` into `['a','b','c','d']`, tolerating both separators. */
|
|
166
|
+
function functionArguments(value) {
|
|
167
|
+
const open = value.indexOf('(');
|
|
168
|
+
const close = value.lastIndexOf(')');
|
|
169
|
+
if (open < 0 || close < open)
|
|
170
|
+
return null;
|
|
171
|
+
return value
|
|
172
|
+
.slice(open + 1, close)
|
|
173
|
+
.split(/[\s,/]+/)
|
|
174
|
+
.filter((part) => part.length > 0);
|
|
175
|
+
}
|
|
176
|
+
function clampChannel(value) {
|
|
177
|
+
return Math.round(clamp(value, 0, 255));
|
|
178
|
+
}
|
|
179
|
+
function clamp(value, min, max) {
|
|
180
|
+
return value < min ? min : value > max ? max : value;
|
|
181
|
+
}
|
|
182
|
+
function collapseWhitespace(value) {
|
|
183
|
+
return value.trim().replace(/\s+/g, ' ');
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=color.js.map
|