@unrulysystems/native-motion-core 0.1.0-alpha.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 +9 -0
- package/LICENSE +21 -0
- package/README.md +48 -0
- package/dist/clock.cjs +71 -0
- package/dist/clock.d.cts +23 -0
- package/dist/clock.d.ts +23 -0
- package/dist/clock.js +66 -0
- package/dist/collect-reads.cjs +68 -0
- package/dist/collect-reads.d.cts +23 -0
- package/dist/collect-reads.d.ts +23 -0
- package/dist/collect-reads.js +62 -0
- package/dist/component/boundedArray.cjs +74 -0
- package/dist/component/boundedArray.d.cts +25 -0
- package/dist/component/boundedArray.d.ts +25 -0
- package/dist/component/boundedArray.js +68 -0
- package/dist/component/index.cjs +57 -0
- package/dist/component/index.d.cts +12 -0
- package/dist/component/index.d.ts +12 -0
- package/dist/component/index.js +22 -0
- package/dist/component/orchestration.cjs +129 -0
- package/dist/component/orchestration.d.cts +66 -0
- package/dist/component/orchestration.d.ts +66 -0
- package/dist/component/orchestration.js +124 -0
- package/dist/component/resolve.cjs +104 -0
- package/dist/component/resolve.d.cts +11 -0
- package/dist/component/resolve.d.ts +11 -0
- package/dist/component/resolve.js +97 -0
- package/dist/component/transition.cjs +352 -0
- package/dist/component/transition.d.cts +33 -0
- package/dist/component/transition.d.ts +33 -0
- package/dist/component/transition.js +339 -0
- package/dist/component/types.cjs +114 -0
- package/dist/component/types.d.cts +60 -0
- package/dist/component/types.d.ts +60 -0
- package/dist/component/types.js +111 -0
- package/dist/component/validate.cjs +1015 -0
- package/dist/component/validate.d.cts +37 -0
- package/dist/component/validate.d.ts +37 -0
- package/dist/component/validate.js +1002 -0
- package/dist/component/variants.cjs +333 -0
- package/dist/component/variants.d.cts +106 -0
- package/dist/component/variants.d.ts +106 -0
- package/dist/component/variants.js +321 -0
- package/dist/config/constants.cjs +41 -0
- package/dist/config/constants.d.cts +29 -0
- package/dist/config/constants.d.ts +29 -0
- package/dist/config/constants.js +38 -0
- package/dist/delay.cjs +72 -0
- package/dist/delay.d.cts +26 -0
- package/dist/delay.d.ts +26 -0
- package/dist/delay.js +70 -0
- package/dist/derived.cjs +143 -0
- package/dist/derived.d.cts +41 -0
- package/dist/derived.d.ts +41 -0
- package/dist/derived.js +139 -0
- package/dist/driver/index.cjs +22 -0
- package/dist/driver/index.d.cts +7 -0
- package/dist/driver/index.d.ts +7 -0
- package/dist/driver/index.js +14 -0
- package/dist/driver/keyframeTiming.cjs +130 -0
- package/dist/driver/keyframeTiming.d.cts +18 -0
- package/dist/driver/keyframeTiming.d.ts +18 -0
- package/dist/driver/keyframeTiming.js +124 -0
- package/dist/driver/keyframeTimingConfig.cjs +24 -0
- package/dist/driver/keyframeTimingConfig.d.cts +5 -0
- package/dist/driver/keyframeTimingConfig.d.ts +5 -0
- package/dist/driver/keyframeTimingConfig.js +22 -0
- package/dist/driver/prepare.cjs +459 -0
- package/dist/driver/prepare.d.cts +4 -0
- package/dist/driver/prepare.d.ts +4 -0
- package/dist/driver/prepare.js +454 -0
- package/dist/driver/reference.cjs +762 -0
- package/dist/driver/reference.d.cts +53 -0
- package/dist/driver/reference.d.ts +53 -0
- package/dist/driver/reference.js +757 -0
- package/dist/driver/step.cjs +55 -0
- package/dist/driver/step.d.cts +9 -0
- package/dist/driver/step.d.ts +9 -0
- package/dist/driver/step.js +52 -0
- package/dist/driver/tiers.cjs +40 -0
- package/dist/driver/tiers.d.cts +2 -0
- package/dist/driver/tiers.d.ts +2 -0
- package/dist/driver/tiers.js +37 -0
- package/dist/driver/types.cjs +9 -0
- package/dist/driver/types.d.cts +68 -0
- package/dist/driver/types.d.ts +68 -0
- package/dist/driver/types.js +8 -0
- package/dist/external-animation-ledger.cjs +1250 -0
- package/dist/external-animation-ledger.d.cts +416 -0
- package/dist/external-animation-ledger.d.ts +416 -0
- package/dist/external-animation-ledger.js +1243 -0
- package/dist/gesture/directionLock.cjs +25 -0
- package/dist/gesture/directionLock.d.cts +8 -0
- package/dist/gesture/directionLock.d.ts +8 -0
- package/dist/gesture/directionLock.js +21 -0
- package/dist/gesture/dragConfig.cjs +634 -0
- package/dist/gesture/dragConfig.d.cts +298 -0
- package/dist/gesture/dragConfig.d.ts +298 -0
- package/dist/gesture/dragConfig.js +624 -0
- package/dist/gesture/elastic.cjs +44 -0
- package/dist/gesture/elastic.d.cts +4 -0
- package/dist/gesture/elastic.d.ts +4 -0
- package/dist/gesture/elastic.js +39 -0
- package/dist/gesture/handoffSession.cjs +161 -0
- package/dist/gesture/handoffSession.d.cts +35 -0
- package/dist/gesture/handoffSession.d.ts +35 -0
- package/dist/gesture/handoffSession.js +158 -0
- package/dist/gesture/index.cjs +37 -0
- package/dist/gesture/index.d.cts +12 -0
- package/dist/gesture/index.d.ts +12 -0
- package/dist/gesture/index.js +11 -0
- package/dist/gesture/projection.cjs +95 -0
- package/dist/gesture/projection.d.cts +7 -0
- package/dist/gesture/projection.d.ts +7 -0
- package/dist/gesture/projection.js +87 -0
- package/dist/gesture/session.cjs +162 -0
- package/dist/gesture/session.d.cts +29 -0
- package/dist/gesture/session.d.ts +29 -0
- package/dist/gesture/session.js +158 -0
- package/dist/gesture/types.cjs +5 -0
- package/dist/gesture/types.d.cts +13 -0
- package/dist/gesture/types.d.ts +13 -0
- package/dist/gesture/types.js +4 -0
- package/dist/gesture/viewportConstraints.cjs +38 -0
- package/dist/gesture/viewportConstraints.d.cts +6 -0
- package/dist/gesture/viewportConstraints.d.ts +6 -0
- package/dist/gesture/viewportConstraints.js +34 -0
- package/dist/graph.cjs +226 -0
- package/dist/graph.d.cts +2 -0
- package/dist/graph.d.ts +2 -0
- package/dist/graph.js +223 -0
- package/dist/index.cjs +140 -0
- package/dist/index.d.cts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +70 -0
- package/dist/inertia.cjs +214 -0
- package/dist/inertia.d.cts +53 -0
- package/dist/inertia.d.ts +53 -0
- package/dist/inertia.js +212 -0
- package/dist/instant.cjs +66 -0
- package/dist/instant.d.cts +16 -0
- package/dist/instant.d.ts +16 -0
- package/dist/instant.js +63 -0
- package/dist/internal-driver.cjs +81 -0
- package/dist/internal-driver.d.cts +19 -0
- package/dist/internal-driver.d.ts +19 -0
- package/dist/internal-driver.js +35 -0
- package/dist/keyframes.cjs +191 -0
- package/dist/keyframes.d.cts +13 -0
- package/dist/keyframes.d.ts +13 -0
- package/dist/keyframes.js +188 -0
- package/dist/layout/commitDetector.cjs +67 -0
- package/dist/layout/commitDetector.d.cts +21 -0
- package/dist/layout/commitDetector.d.ts +21 -0
- package/dist/layout/commitDetector.js +64 -0
- package/dist/layout/compose.cjs +67 -0
- package/dist/layout/compose.d.cts +30 -0
- package/dist/layout/compose.d.ts +30 -0
- package/dist/layout/compose.js +65 -0
- package/dist/layout/constants.cjs +13 -0
- package/dist/layout/constants.d.cts +6 -0
- package/dist/layout/constants.d.ts +6 -0
- package/dist/layout/constants.js +10 -0
- package/dist/layout/identity.cjs +700 -0
- package/dist/layout/identity.d.cts +67 -0
- package/dist/layout/identity.d.ts +67 -0
- package/dist/layout/identity.js +698 -0
- package/dist/layout/index.cjs +36 -0
- package/dist/layout/index.d.cts +18 -0
- package/dist/layout/index.d.ts +18 -0
- package/dist/layout/index.js +13 -0
- package/dist/layout/measure.cjs +81 -0
- package/dist/layout/measure.d.cts +43 -0
- package/dist/layout/measure.d.ts +43 -0
- package/dist/layout/measure.js +78 -0
- package/dist/layout/projection.cjs +99 -0
- package/dist/layout/projection.d.cts +23 -0
- package/dist/layout/projection.d.ts +23 -0
- package/dist/layout/projection.js +97 -0
- package/dist/layout/scroll.cjs +27 -0
- package/dist/layout/scroll.d.cts +13 -0
- package/dist/layout/scroll.d.ts +13 -0
- package/dist/layout/scroll.js +24 -0
- package/dist/layout/session.cjs +207 -0
- package/dist/layout/session.d.cts +73 -0
- package/dist/layout/session.d.ts +73 -0
- package/dist/layout/session.js +205 -0
- package/dist/layout/tree.cjs +826 -0
- package/dist/layout/tree.d.cts +70 -0
- package/dist/layout/tree.d.ts +70 -0
- package/dist/layout/tree.js +823 -0
- package/dist/layout/types.cjs +36 -0
- package/dist/layout/types.d.cts +31 -0
- package/dist/layout/types.d.ts +31 -0
- package/dist/layout/types.js +35 -0
- package/dist/motion-arc.cjs +184 -0
- package/dist/motion-arc.d.cts +78 -0
- package/dist/motion-arc.d.ts +78 -0
- package/dist/motion-arc.js +183 -0
- package/dist/motion-mix.cjs +205 -0
- package/dist/motion-mix.d.cts +3 -0
- package/dist/motion-mix.d.ts +3 -0
- package/dist/motion-mix.js +202 -0
- package/dist/motion-value-driver-port.cjs +636 -0
- package/dist/motion-value-driver-port.d.cts +406 -0
- package/dist/motion-value-driver-port.d.ts +406 -0
- package/dist/motion-value-driver-port.js +627 -0
- package/dist/motion-value.cjs +189 -0
- package/dist/motion-value.d.cts +51 -0
- package/dist/motion-value.d.ts +51 -0
- package/dist/motion-value.js +185 -0
- package/dist/presence/controller.cjs +657 -0
- package/dist/presence/controller.d.cts +6 -0
- package/dist/presence/controller.d.ts +6 -0
- package/dist/presence/controller.js +652 -0
- package/dist/presence/index.cjs +19 -0
- package/dist/presence/index.d.cts +4 -0
- package/dist/presence/index.d.ts +4 -0
- package/dist/presence/index.js +12 -0
- package/dist/presence/machine.cjs +50 -0
- package/dist/presence/machine.d.cts +10 -0
- package/dist/presence/machine.d.ts +10 -0
- package/dist/presence/machine.js +46 -0
- package/dist/presence/types.cjs +6 -0
- package/dist/presence/types.d.cts +32 -0
- package/dist/presence/types.d.ts +32 -0
- package/dist/presence/types.js +5 -0
- package/dist/repeat.cjs +311 -0
- package/dist/repeat.d.cts +174 -0
- package/dist/repeat.d.ts +174 -0
- package/dist/repeat.js +300 -0
- package/dist/spring.cjs +128 -0
- package/dist/spring.d.cts +18 -0
- package/dist/spring.d.ts +18 -0
- package/dist/spring.js +125 -0
- package/dist/subscriptions.cjs +74 -0
- package/dist/subscriptions.d.cts +19 -0
- package/dist/subscriptions.d.ts +19 -0
- package/dist/subscriptions.js +69 -0
- package/dist/subset/index.cjs +23 -0
- package/dist/subset/index.d.cts +6 -0
- package/dist/subset/index.d.ts +6 -0
- package/dist/subset/index.js +13 -0
- package/dist/subset/normalize.cjs +90 -0
- package/dist/subset/normalize.d.cts +10 -0
- package/dist/subset/normalize.d.ts +10 -0
- package/dist/subset/normalize.js +85 -0
- package/dist/subset/registry.cjs +259 -0
- package/dist/subset/registry.d.cts +25 -0
- package/dist/subset/registry.d.ts +25 -0
- package/dist/subset/registry.js +256 -0
- package/dist/subset/resolve.cjs +98 -0
- package/dist/subset/resolve.d.cts +24 -0
- package/dist/subset/resolve.d.ts +24 -0
- package/dist/subset/resolve.js +90 -0
- package/dist/timing.cjs +206 -0
- package/dist/timing.d.cts +17 -0
- package/dist/timing.d.ts +17 -0
- package/dist/timing.js +201 -0
- package/dist/transformTemplate.cjs +219 -0
- package/dist/transformTemplate.d.cts +29 -0
- package/dist/transformTemplate.d.ts +29 -0
- package/dist/transformTemplate.js +215 -0
- package/dist/transition.cjs +231 -0
- package/dist/transition.d.cts +89 -0
- package/dist/transition.d.ts +89 -0
- package/dist/transition.js +223 -0
- package/dist/types.cjs +4 -0
- package/dist/types.d.cts +175 -0
- package/dist/types.d.ts +175 -0
- package/dist/types.js +3 -0
- package/dist/value-types/color.cjs +220 -0
- package/dist/value-types/color.d.cts +19 -0
- package/dist/value-types/color.d.ts +19 -0
- package/dist/value-types/color.js +218 -0
- package/dist/value-types/complex.cjs +160 -0
- package/dist/value-types/complex.d.cts +19 -0
- package/dist/value-types/complex.d.ts +19 -0
- package/dist/value-types/complex.js +155 -0
- package/dist/value-types/constants.cjs +15 -0
- package/dist/value-types/constants.d.cts +6 -0
- package/dist/value-types/constants.d.ts +6 -0
- package/dist/value-types/constants.js +12 -0
- package/dist/value-types/discrete.cjs +62 -0
- package/dist/value-types/discrete.d.cts +4 -0
- package/dist/value-types/discrete.d.ts +4 -0
- package/dist/value-types/discrete.js +56 -0
- package/dist/value-types/index.cjs +59 -0
- package/dist/value-types/index.d.cts +14 -0
- package/dist/value-types/index.d.ts +14 -0
- package/dist/value-types/index.js +24 -0
- package/dist/value-types/measure-resolve.cjs +325 -0
- package/dist/value-types/measure-resolve.d.cts +91 -0
- package/dist/value-types/measure-resolve.d.ts +91 -0
- package/dist/value-types/measure-resolve.js +313 -0
- package/dist/value-types/mix.cjs +90 -0
- package/dist/value-types/mix.d.cts +27 -0
- package/dist/value-types/mix.d.ts +27 -0
- package/dist/value-types/mix.js +85 -0
- package/dist/value-types/named-colors.cjs +61 -0
- package/dist/value-types/named-colors.d.cts +2 -0
- package/dist/value-types/named-colors.d.ts +2 -0
- package/dist/value-types/named-colors.js +58 -0
- package/dist/value-types/numeric.cjs +86 -0
- package/dist/value-types/numeric.d.cts +16 -0
- package/dist/value-types/numeric.d.ts +16 -0
- package/dist/value-types/numeric.js +79 -0
- package/dist/worklet-layout/config/constants.js +39 -0
- package/dist/worklet-layout/layout/constants.js +11 -0
- package/dist/worklet-layout/layout/identity.js +699 -0
- package/dist/worklet-layout/layout/projection.js +98 -0
- package/dist/worklet-layout/layout/session.js +206 -0
- package/dist/worklet-layout/layout/tree.js +824 -0
- package/dist/worklet-layout/layout/types.js +36 -0
- package/dist/worklet-layout/spring.js +125 -0
- package/dist/worklet-layout/timing.js +201 -0
- package/dist/worklet-layout/transition.js +223 -0
- package/dist/worklet-layout.cjs +47 -0
- package/dist/worklet-layout.d.cts +15 -0
- package/dist/worklet-layout.d.ts +15 -0
- package/dist/worklet-layout.js +28 -0
- package/dist/wrap.cjs +7 -0
- package/dist/wrap.d.cts +1 -0
- package/dist/wrap.d.ts +1 -0
- package/dist/wrap.js +4 -0
- package/package.json +45 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Color value types (SPEC-VALUE-TYPES §3, REQ-VALUETYPE-005/006). Pure parse (hex/rgb/hsl/named → RGBA),
|
|
3
|
+
// gamma-aware linear-light channel mix, and recompose. Host-agnostic (REQ-VALUETYPE-011): no DOM CSSOM,
|
|
4
|
+
// no react-native processColor, no locale formatting — the named-color table (./named-colors) and the
|
|
5
|
+
// HSL→RGB transform are this subsystem's own, so the same raw string yields the same RGBA on both engines.
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.parseColorVerdict = parseColorVerdict;
|
|
8
|
+
exports.parseColor = parseColor;
|
|
9
|
+
exports.isColor = isColor;
|
|
10
|
+
exports.hslaToRgba = hslaToRgba;
|
|
11
|
+
exports.mixColor = mixColor;
|
|
12
|
+
exports.transformColor = transformColor;
|
|
13
|
+
const named_colors_1 = require("./named-colors.cjs");
|
|
14
|
+
const refuseColor = (message) => ({ colorRefusal: message });
|
|
15
|
+
function isColorRefusal(value) {
|
|
16
|
+
// OWN-property discriminant (M3 r8 major dbffcd947d13): the `in` operator walks the
|
|
17
|
+
// prototype chain, so a polluted `Object.prototype.colorRefusal` would turn every VALID
|
|
18
|
+
// RGBA result into a refusal. hasOwn is pollution-invisible.
|
|
19
|
+
return typeof value === 'object' && value !== null && Object.hasOwn(value, 'colorRefusal');
|
|
20
|
+
}
|
|
21
|
+
function parseColorVerdict(raw) {
|
|
22
|
+
const s = raw.trim();
|
|
23
|
+
const lower = s.toLowerCase();
|
|
24
|
+
const named = named_colors_1.NAMED_COLORS[lower];
|
|
25
|
+
if (named !== undefined)
|
|
26
|
+
return { parsed: named, refusal: null };
|
|
27
|
+
let outcome = null;
|
|
28
|
+
if (s.startsWith('#'))
|
|
29
|
+
outcome = parseHex(s);
|
|
30
|
+
else if (lower.startsWith('rgb'))
|
|
31
|
+
outcome = parseRgb(s);
|
|
32
|
+
else if (lower.startsWith('hsl'))
|
|
33
|
+
outcome = parseHsl(s);
|
|
34
|
+
if (outcome === null) {
|
|
35
|
+
return { parsed: null, refusal: `Cannot parse ${JSON.stringify(raw)} as a color` };
|
|
36
|
+
}
|
|
37
|
+
return isColorRefusal(outcome)
|
|
38
|
+
? { parsed: null, refusal: outcome.colorRefusal }
|
|
39
|
+
: { parsed: outcome, refusal: null };
|
|
40
|
+
}
|
|
41
|
+
function parseColor(raw) {
|
|
42
|
+
const verdict = parseColorVerdict(raw);
|
|
43
|
+
if (verdict.refusal !== null)
|
|
44
|
+
throw new Error(verdict.refusal);
|
|
45
|
+
return verdict.parsed;
|
|
46
|
+
}
|
|
47
|
+
// Family predicate for the dispatcher (mix.ts): is the WHOLE raw string a single color? A named-table
|
|
48
|
+
// hit, a hex ATTEMPT, or a single rgb/hsl function. This recognizes the SHAPE, not validity: a "#"
|
|
49
|
+
// single token (`# ` + alphanumerics, no whitespace) is always a hex attempt, and `parseColor` is the
|
|
50
|
+
// strict validator that throws on a malformed one (wrong length/charset). Recognizing the shape here is
|
|
51
|
+
// what keeps a malformed hex like "#12" a LOUD color failure (REQ-VALUETYPE-008) instead of silently
|
|
52
|
+
// falling through to the complex tokenizer, which would treat "#" as a literal template and mix a bogus
|
|
53
|
+
// number out of it. Mirrors the rgb/hsl branch, which likewise defers channel validity to parseColor.
|
|
54
|
+
function isColor(raw) {
|
|
55
|
+
const s = raw.trim();
|
|
56
|
+
if (named_colors_1.NAMED_COLORS[s.toLowerCase()] !== undefined)
|
|
57
|
+
return true;
|
|
58
|
+
if (s.startsWith('#'))
|
|
59
|
+
return /^#[0-9a-z]+$/i.test(s);
|
|
60
|
+
return /^(?:rgba?|hsla?)\([^)]*\)$/i.test(s);
|
|
61
|
+
}
|
|
62
|
+
// #rgb, #rgba, #rrggbb, #rrggbbaa. Short forms double each nibble (matching CSS + motion).
|
|
63
|
+
function parseHex(s) {
|
|
64
|
+
const h = s.slice(1);
|
|
65
|
+
const hex2 = (a, b) => Number.parseInt(a + b, 16);
|
|
66
|
+
let red;
|
|
67
|
+
let green;
|
|
68
|
+
let blue;
|
|
69
|
+
let alpha255 = 255;
|
|
70
|
+
if (h.length === 3 || h.length === 4) {
|
|
71
|
+
red = hex2(h.charAt(0), h.charAt(0));
|
|
72
|
+
green = hex2(h.charAt(1), h.charAt(1));
|
|
73
|
+
blue = hex2(h.charAt(2), h.charAt(2));
|
|
74
|
+
if (h.length === 4)
|
|
75
|
+
alpha255 = hex2(h.charAt(3), h.charAt(3));
|
|
76
|
+
}
|
|
77
|
+
else if (h.length === 6 || h.length === 8) {
|
|
78
|
+
red = hex2(h.charAt(0), h.charAt(1));
|
|
79
|
+
green = hex2(h.charAt(2), h.charAt(3));
|
|
80
|
+
blue = hex2(h.charAt(4), h.charAt(5));
|
|
81
|
+
if (h.length === 8)
|
|
82
|
+
alpha255 = hex2(h.charAt(6), h.charAt(7));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
return refuseColor(`Invalid hex color ${JSON.stringify(s)} (need 3/4/6/8 digits)`);
|
|
86
|
+
}
|
|
87
|
+
if (!/^[0-9a-f]+$/i.test(h) || Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue)) {
|
|
88
|
+
return refuseColor(`Invalid hex color ${JSON.stringify(s)}`);
|
|
89
|
+
}
|
|
90
|
+
return { red, green, blue, alpha: alpha255 / 255 };
|
|
91
|
+
}
|
|
92
|
+
// Split the "(...)" argument list of an rgb()/rgba()/hsl()/hsla() function into trimmed channel tokens.
|
|
93
|
+
function channels(prefix, s, label) {
|
|
94
|
+
const m = prefix.exec(s.trim());
|
|
95
|
+
if (m === null || m[1] === undefined) {
|
|
96
|
+
return refuseColor(`Invalid ${label} color ${JSON.stringify(s)}`);
|
|
97
|
+
}
|
|
98
|
+
const parts = m[1].split(',').map((p) => p.trim());
|
|
99
|
+
if (parts.length < 3 || parts.length > 4) {
|
|
100
|
+
return refuseColor(`${label} color needs 3 or 4 channels: ${JSON.stringify(s)}`);
|
|
101
|
+
}
|
|
102
|
+
return parts;
|
|
103
|
+
}
|
|
104
|
+
function num(token, s) {
|
|
105
|
+
const n = token === undefined ? Number.NaN : Number.parseFloat(token);
|
|
106
|
+
if (Number.isNaN(n)) {
|
|
107
|
+
return refuseColor(`Invalid color channel ${JSON.stringify(token)} in ${JSON.stringify(s)}`);
|
|
108
|
+
}
|
|
109
|
+
return n;
|
|
110
|
+
}
|
|
111
|
+
function parseRgb(s) {
|
|
112
|
+
const parts = channels(/^rgba?\(([^)]*)\)$/i, s, 'rgb');
|
|
113
|
+
if (isColorRefusal(parts))
|
|
114
|
+
return parts;
|
|
115
|
+
const red = num(parts[0], s);
|
|
116
|
+
if (isColorRefusal(red))
|
|
117
|
+
return red;
|
|
118
|
+
const green = num(parts[1], s);
|
|
119
|
+
if (isColorRefusal(green))
|
|
120
|
+
return green;
|
|
121
|
+
const blue = num(parts[2], s);
|
|
122
|
+
if (isColorRefusal(blue))
|
|
123
|
+
return blue;
|
|
124
|
+
const alpha = parts.length === 4 ? num(parts[3], s) : 1;
|
|
125
|
+
if (isColorRefusal(alpha))
|
|
126
|
+
return alpha;
|
|
127
|
+
return { red, green, blue, alpha };
|
|
128
|
+
}
|
|
129
|
+
function parseHsl(s) {
|
|
130
|
+
const parts = channels(/^hsla?\(([^)]*)\)$/i, s, 'hsl');
|
|
131
|
+
if (isColorRefusal(parts))
|
|
132
|
+
return parts;
|
|
133
|
+
const hue = num(parts[0], s);
|
|
134
|
+
if (isColorRefusal(hue))
|
|
135
|
+
return hue;
|
|
136
|
+
const saturation = num(parts[1], s);
|
|
137
|
+
if (isColorRefusal(saturation))
|
|
138
|
+
return saturation;
|
|
139
|
+
const lightness = num(parts[2], s);
|
|
140
|
+
if (isColorRefusal(lightness))
|
|
141
|
+
return lightness;
|
|
142
|
+
// parseFloat stops at the trailing "%" on saturation/lightness.
|
|
143
|
+
const alpha = parts.length === 4 ? num(parts[3], s) : 1;
|
|
144
|
+
if (isColorRefusal(alpha))
|
|
145
|
+
return alpha;
|
|
146
|
+
return hslaToRgba(hue, saturation, lightness, alpha);
|
|
147
|
+
}
|
|
148
|
+
// Standard HSL→RGB (matches motion-dom's hslaToRgba): rgb rounded to integers, alpha passed through.
|
|
149
|
+
// Module-internal by the value-types barrel's own law; `motion-mix.ts` imports it directly for the
|
|
150
|
+
// pin's HSLA-object color arm (REQ-VALUETYPE-016) rather than growing a second conversion.
|
|
151
|
+
function hslaToRgba(hue, saturation, lightness, alpha) {
|
|
152
|
+
const h = hue / 360;
|
|
153
|
+
const s = saturation / 100;
|
|
154
|
+
const l = lightness / 100;
|
|
155
|
+
let red;
|
|
156
|
+
let green;
|
|
157
|
+
let blue;
|
|
158
|
+
if (s === 0) {
|
|
159
|
+
red = green = blue = l;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
163
|
+
const p = 2 * l - q;
|
|
164
|
+
red = hueToRgb(p, q, h + 1 / 3);
|
|
165
|
+
green = hueToRgb(p, q, h);
|
|
166
|
+
blue = hueToRgb(p, q, h - 1 / 3);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
red: Math.round(red * 255),
|
|
170
|
+
green: Math.round(green * 255),
|
|
171
|
+
blue: Math.round(blue * 255),
|
|
172
|
+
alpha,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function hueToRgb(p, q, tRaw) {
|
|
176
|
+
let t = tRaw;
|
|
177
|
+
if (t < 0)
|
|
178
|
+
t += 1;
|
|
179
|
+
if (t > 1)
|
|
180
|
+
t -= 1;
|
|
181
|
+
if (t < 1 / 6)
|
|
182
|
+
return p + (q - p) * 6 * t;
|
|
183
|
+
if (t < 1 / 2)
|
|
184
|
+
return q;
|
|
185
|
+
if (t < 2 / 3)
|
|
186
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
187
|
+
return p;
|
|
188
|
+
}
|
|
189
|
+
// REQ-VALUETYPE-006: linear-light (gamma-aware) channel mix, NOT a naive average. `sqrt` of the
|
|
190
|
+
// squared-channel interpolation — motion's mixLinearColor. Alpha mixes linearly.
|
|
191
|
+
function mixLinearColor(from, to, v) {
|
|
192
|
+
return Math.sqrt(Math.max(0, v * (to * to - from * from) + from * from));
|
|
193
|
+
}
|
|
194
|
+
function mixColor(from, to, progress) {
|
|
195
|
+
return {
|
|
196
|
+
red: mixLinearColor(from.red, to.red, progress),
|
|
197
|
+
green: mixLinearColor(from.green, to.green, progress),
|
|
198
|
+
blue: mixLinearColor(from.blue, to.blue, progress),
|
|
199
|
+
alpha: from.alpha + (to.alpha - from.alpha) * progress,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// String projection (REQ-VALUETYPE-006): rgb round→clamp[0,255]; alpha sanitized to Motion's 5-dp rule,
|
|
203
|
+
// formatted byte-identically to motion-dom's `rgba(r, g, b, a)` recompose.
|
|
204
|
+
function clampChannel(v) {
|
|
205
|
+
return Math.min(255, Math.max(0, Math.round(v)));
|
|
206
|
+
}
|
|
207
|
+
function sanitize(v) {
|
|
208
|
+
return Math.round(v * 1e5) / 1e5;
|
|
209
|
+
}
|
|
210
|
+
// Alpha is CLAMPED to [0,1] before the 5-dp sanitize, byte-identical to motion-dom's rgba formatter
|
|
211
|
+
// (`sanitize(alpha.transform(a))` where `alpha.transform = clamp(0,1)`; review major 50). A spring
|
|
212
|
+
// color array OVERSHOOTS its progress (>100), so `mixColor` extrapolates alpha past 1 — without the
|
|
213
|
+
// clamp native handed React Native an invalid `rgba(…, 1.396)`, diverging from the pinned oracle. The
|
|
214
|
+
// rgb channels already clamp to [0,255] via `clampChannel`, matching Motion's `clampRgbUnit`.
|
|
215
|
+
function clampAlpha(v) {
|
|
216
|
+
return Math.min(1, Math.max(0, v));
|
|
217
|
+
}
|
|
218
|
+
function transformColor(c) {
|
|
219
|
+
return `rgba(${clampChannel(c.red)}, ${clampChannel(c.green)}, ${clampChannel(c.blue)}, ${sanitize(clampAlpha(c.alpha))})`;
|
|
220
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RGBA {
|
|
2
|
+
readonly red: number;
|
|
3
|
+
readonly green: number;
|
|
4
|
+
readonly blue: number;
|
|
5
|
+
readonly alpha: number;
|
|
6
|
+
}
|
|
7
|
+
export type ColorParseVerdict = {
|
|
8
|
+
readonly parsed: RGBA;
|
|
9
|
+
readonly refusal: null;
|
|
10
|
+
} | {
|
|
11
|
+
readonly parsed: null;
|
|
12
|
+
readonly refusal: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function parseColorVerdict(raw: string): ColorParseVerdict;
|
|
15
|
+
export declare function parseColor(raw: string): RGBA;
|
|
16
|
+
export declare function isColor(raw: string): boolean;
|
|
17
|
+
export declare function hslaToRgba(hue: number, saturation: number, lightness: number, alpha: number): RGBA;
|
|
18
|
+
export declare function mixColor(from: RGBA, to: RGBA, progress: number): RGBA;
|
|
19
|
+
export declare function transformColor(c: RGBA): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface RGBA {
|
|
2
|
+
readonly red: number;
|
|
3
|
+
readonly green: number;
|
|
4
|
+
readonly blue: number;
|
|
5
|
+
readonly alpha: number;
|
|
6
|
+
}
|
|
7
|
+
export type ColorParseVerdict = {
|
|
8
|
+
readonly parsed: RGBA;
|
|
9
|
+
readonly refusal: null;
|
|
10
|
+
} | {
|
|
11
|
+
readonly parsed: null;
|
|
12
|
+
readonly refusal: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function parseColorVerdict(raw: string): ColorParseVerdict;
|
|
15
|
+
export declare function parseColor(raw: string): RGBA;
|
|
16
|
+
export declare function isColor(raw: string): boolean;
|
|
17
|
+
export declare function hslaToRgba(hue: number, saturation: number, lightness: number, alpha: number): RGBA;
|
|
18
|
+
export declare function mixColor(from: RGBA, to: RGBA, progress: number): RGBA;
|
|
19
|
+
export declare function transformColor(c: RGBA): string;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// Color value types (SPEC-VALUE-TYPES §3, REQ-VALUETYPE-005/006). Pure parse (hex/rgb/hsl/named → RGBA),
|
|
2
|
+
// gamma-aware linear-light channel mix, and recompose. Host-agnostic (REQ-VALUETYPE-011): no DOM CSSOM,
|
|
3
|
+
// no react-native processColor, no locale formatting — the named-color table (./named-colors) and the
|
|
4
|
+
// HSL→RGB transform are this subsystem's own, so the same raw string yields the same RGBA on both engines.
|
|
5
|
+
import { NAMED_COLORS } from "./named-colors.js";
|
|
6
|
+
const refuseColor = (message) => ({ colorRefusal: message });
|
|
7
|
+
function isColorRefusal(value) {
|
|
8
|
+
// OWN-property discriminant (M3 r8 major dbffcd947d13): the `in` operator walks the
|
|
9
|
+
// prototype chain, so a polluted `Object.prototype.colorRefusal` would turn every VALID
|
|
10
|
+
// RGBA result into a refusal. hasOwn is pollution-invisible.
|
|
11
|
+
return typeof value === 'object' && value !== null && Object.hasOwn(value, 'colorRefusal');
|
|
12
|
+
}
|
|
13
|
+
export function parseColorVerdict(raw) {
|
|
14
|
+
const s = raw.trim();
|
|
15
|
+
const lower = s.toLowerCase();
|
|
16
|
+
const named = NAMED_COLORS[lower];
|
|
17
|
+
if (named !== undefined)
|
|
18
|
+
return { parsed: named, refusal: null };
|
|
19
|
+
let outcome = null;
|
|
20
|
+
if (s.startsWith('#'))
|
|
21
|
+
outcome = parseHex(s);
|
|
22
|
+
else if (lower.startsWith('rgb'))
|
|
23
|
+
outcome = parseRgb(s);
|
|
24
|
+
else if (lower.startsWith('hsl'))
|
|
25
|
+
outcome = parseHsl(s);
|
|
26
|
+
if (outcome === null) {
|
|
27
|
+
return { parsed: null, refusal: `Cannot parse ${JSON.stringify(raw)} as a color` };
|
|
28
|
+
}
|
|
29
|
+
return isColorRefusal(outcome)
|
|
30
|
+
? { parsed: null, refusal: outcome.colorRefusal }
|
|
31
|
+
: { parsed: outcome, refusal: null };
|
|
32
|
+
}
|
|
33
|
+
export function parseColor(raw) {
|
|
34
|
+
const verdict = parseColorVerdict(raw);
|
|
35
|
+
if (verdict.refusal !== null)
|
|
36
|
+
throw new Error(verdict.refusal);
|
|
37
|
+
return verdict.parsed;
|
|
38
|
+
}
|
|
39
|
+
// Family predicate for the dispatcher (mix.ts): is the WHOLE raw string a single color? A named-table
|
|
40
|
+
// hit, a hex ATTEMPT, or a single rgb/hsl function. This recognizes the SHAPE, not validity: a "#"
|
|
41
|
+
// single token (`# ` + alphanumerics, no whitespace) is always a hex attempt, and `parseColor` is the
|
|
42
|
+
// strict validator that throws on a malformed one (wrong length/charset). Recognizing the shape here is
|
|
43
|
+
// what keeps a malformed hex like "#12" a LOUD color failure (REQ-VALUETYPE-008) instead of silently
|
|
44
|
+
// falling through to the complex tokenizer, which would treat "#" as a literal template and mix a bogus
|
|
45
|
+
// number out of it. Mirrors the rgb/hsl branch, which likewise defers channel validity to parseColor.
|
|
46
|
+
export function isColor(raw) {
|
|
47
|
+
const s = raw.trim();
|
|
48
|
+
if (NAMED_COLORS[s.toLowerCase()] !== undefined)
|
|
49
|
+
return true;
|
|
50
|
+
if (s.startsWith('#'))
|
|
51
|
+
return /^#[0-9a-z]+$/i.test(s);
|
|
52
|
+
return /^(?:rgba?|hsla?)\([^)]*\)$/i.test(s);
|
|
53
|
+
}
|
|
54
|
+
// #rgb, #rgba, #rrggbb, #rrggbbaa. Short forms double each nibble (matching CSS + motion).
|
|
55
|
+
function parseHex(s) {
|
|
56
|
+
const h = s.slice(1);
|
|
57
|
+
const hex2 = (a, b) => Number.parseInt(a + b, 16);
|
|
58
|
+
let red;
|
|
59
|
+
let green;
|
|
60
|
+
let blue;
|
|
61
|
+
let alpha255 = 255;
|
|
62
|
+
if (h.length === 3 || h.length === 4) {
|
|
63
|
+
red = hex2(h.charAt(0), h.charAt(0));
|
|
64
|
+
green = hex2(h.charAt(1), h.charAt(1));
|
|
65
|
+
blue = hex2(h.charAt(2), h.charAt(2));
|
|
66
|
+
if (h.length === 4)
|
|
67
|
+
alpha255 = hex2(h.charAt(3), h.charAt(3));
|
|
68
|
+
}
|
|
69
|
+
else if (h.length === 6 || h.length === 8) {
|
|
70
|
+
red = hex2(h.charAt(0), h.charAt(1));
|
|
71
|
+
green = hex2(h.charAt(2), h.charAt(3));
|
|
72
|
+
blue = hex2(h.charAt(4), h.charAt(5));
|
|
73
|
+
if (h.length === 8)
|
|
74
|
+
alpha255 = hex2(h.charAt(6), h.charAt(7));
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
return refuseColor(`Invalid hex color ${JSON.stringify(s)} (need 3/4/6/8 digits)`);
|
|
78
|
+
}
|
|
79
|
+
if (!/^[0-9a-f]+$/i.test(h) || Number.isNaN(red) || Number.isNaN(green) || Number.isNaN(blue)) {
|
|
80
|
+
return refuseColor(`Invalid hex color ${JSON.stringify(s)}`);
|
|
81
|
+
}
|
|
82
|
+
return { red, green, blue, alpha: alpha255 / 255 };
|
|
83
|
+
}
|
|
84
|
+
// Split the "(...)" argument list of an rgb()/rgba()/hsl()/hsla() function into trimmed channel tokens.
|
|
85
|
+
function channels(prefix, s, label) {
|
|
86
|
+
const m = prefix.exec(s.trim());
|
|
87
|
+
if (m === null || m[1] === undefined) {
|
|
88
|
+
return refuseColor(`Invalid ${label} color ${JSON.stringify(s)}`);
|
|
89
|
+
}
|
|
90
|
+
const parts = m[1].split(',').map((p) => p.trim());
|
|
91
|
+
if (parts.length < 3 || parts.length > 4) {
|
|
92
|
+
return refuseColor(`${label} color needs 3 or 4 channels: ${JSON.stringify(s)}`);
|
|
93
|
+
}
|
|
94
|
+
return parts;
|
|
95
|
+
}
|
|
96
|
+
function num(token, s) {
|
|
97
|
+
const n = token === undefined ? Number.NaN : Number.parseFloat(token);
|
|
98
|
+
if (Number.isNaN(n)) {
|
|
99
|
+
return refuseColor(`Invalid color channel ${JSON.stringify(token)} in ${JSON.stringify(s)}`);
|
|
100
|
+
}
|
|
101
|
+
return n;
|
|
102
|
+
}
|
|
103
|
+
function parseRgb(s) {
|
|
104
|
+
const parts = channels(/^rgba?\(([^)]*)\)$/i, s, 'rgb');
|
|
105
|
+
if (isColorRefusal(parts))
|
|
106
|
+
return parts;
|
|
107
|
+
const red = num(parts[0], s);
|
|
108
|
+
if (isColorRefusal(red))
|
|
109
|
+
return red;
|
|
110
|
+
const green = num(parts[1], s);
|
|
111
|
+
if (isColorRefusal(green))
|
|
112
|
+
return green;
|
|
113
|
+
const blue = num(parts[2], s);
|
|
114
|
+
if (isColorRefusal(blue))
|
|
115
|
+
return blue;
|
|
116
|
+
const alpha = parts.length === 4 ? num(parts[3], s) : 1;
|
|
117
|
+
if (isColorRefusal(alpha))
|
|
118
|
+
return alpha;
|
|
119
|
+
return { red, green, blue, alpha };
|
|
120
|
+
}
|
|
121
|
+
function parseHsl(s) {
|
|
122
|
+
const parts = channels(/^hsla?\(([^)]*)\)$/i, s, 'hsl');
|
|
123
|
+
if (isColorRefusal(parts))
|
|
124
|
+
return parts;
|
|
125
|
+
const hue = num(parts[0], s);
|
|
126
|
+
if (isColorRefusal(hue))
|
|
127
|
+
return hue;
|
|
128
|
+
const saturation = num(parts[1], s);
|
|
129
|
+
if (isColorRefusal(saturation))
|
|
130
|
+
return saturation;
|
|
131
|
+
const lightness = num(parts[2], s);
|
|
132
|
+
if (isColorRefusal(lightness))
|
|
133
|
+
return lightness;
|
|
134
|
+
// parseFloat stops at the trailing "%" on saturation/lightness.
|
|
135
|
+
const alpha = parts.length === 4 ? num(parts[3], s) : 1;
|
|
136
|
+
if (isColorRefusal(alpha))
|
|
137
|
+
return alpha;
|
|
138
|
+
return hslaToRgba(hue, saturation, lightness, alpha);
|
|
139
|
+
}
|
|
140
|
+
// Standard HSL→RGB (matches motion-dom's hslaToRgba): rgb rounded to integers, alpha passed through.
|
|
141
|
+
// Module-internal by the value-types barrel's own law; `motion-mix.ts` imports it directly for the
|
|
142
|
+
// pin's HSLA-object color arm (REQ-VALUETYPE-016) rather than growing a second conversion.
|
|
143
|
+
export function hslaToRgba(hue, saturation, lightness, alpha) {
|
|
144
|
+
const h = hue / 360;
|
|
145
|
+
const s = saturation / 100;
|
|
146
|
+
const l = lightness / 100;
|
|
147
|
+
let red;
|
|
148
|
+
let green;
|
|
149
|
+
let blue;
|
|
150
|
+
if (s === 0) {
|
|
151
|
+
red = green = blue = l;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
155
|
+
const p = 2 * l - q;
|
|
156
|
+
red = hueToRgb(p, q, h + 1 / 3);
|
|
157
|
+
green = hueToRgb(p, q, h);
|
|
158
|
+
blue = hueToRgb(p, q, h - 1 / 3);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
red: Math.round(red * 255),
|
|
162
|
+
green: Math.round(green * 255),
|
|
163
|
+
blue: Math.round(blue * 255),
|
|
164
|
+
alpha,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function hueToRgb(p, q, tRaw) {
|
|
168
|
+
let t = tRaw;
|
|
169
|
+
if (t < 0)
|
|
170
|
+
t += 1;
|
|
171
|
+
if (t > 1)
|
|
172
|
+
t -= 1;
|
|
173
|
+
if (t < 1 / 6)
|
|
174
|
+
return p + (q - p) * 6 * t;
|
|
175
|
+
if (t < 1 / 2)
|
|
176
|
+
return q;
|
|
177
|
+
if (t < 2 / 3)
|
|
178
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
179
|
+
return p;
|
|
180
|
+
}
|
|
181
|
+
// REQ-VALUETYPE-006: linear-light (gamma-aware) channel mix, NOT a naive average. `sqrt` of the
|
|
182
|
+
// squared-channel interpolation — motion's mixLinearColor. Alpha mixes linearly.
|
|
183
|
+
function mixLinearColor(from, to, v) {
|
|
184
|
+
'worklet';
|
|
185
|
+
return Math.sqrt(Math.max(0, v * (to * to - from * from) + from * from));
|
|
186
|
+
}
|
|
187
|
+
export function mixColor(from, to, progress) {
|
|
188
|
+
'worklet';
|
|
189
|
+
return {
|
|
190
|
+
red: mixLinearColor(from.red, to.red, progress),
|
|
191
|
+
green: mixLinearColor(from.green, to.green, progress),
|
|
192
|
+
blue: mixLinearColor(from.blue, to.blue, progress),
|
|
193
|
+
alpha: from.alpha + (to.alpha - from.alpha) * progress,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
// String projection (REQ-VALUETYPE-006): rgb round→clamp[0,255]; alpha sanitized to Motion's 5-dp rule,
|
|
197
|
+
// formatted byte-identically to motion-dom's `rgba(r, g, b, a)` recompose.
|
|
198
|
+
function clampChannel(v) {
|
|
199
|
+
'worklet';
|
|
200
|
+
return Math.min(255, Math.max(0, Math.round(v)));
|
|
201
|
+
}
|
|
202
|
+
function sanitize(v) {
|
|
203
|
+
'worklet';
|
|
204
|
+
return Math.round(v * 1e5) / 1e5;
|
|
205
|
+
}
|
|
206
|
+
// Alpha is CLAMPED to [0,1] before the 5-dp sanitize, byte-identical to motion-dom's rgba formatter
|
|
207
|
+
// (`sanitize(alpha.transform(a))` where `alpha.transform = clamp(0,1)`; review major 50). A spring
|
|
208
|
+
// color array OVERSHOOTS its progress (>100), so `mixColor` extrapolates alpha past 1 — without the
|
|
209
|
+
// clamp native handed React Native an invalid `rgba(…, 1.396)`, diverging from the pinned oracle. The
|
|
210
|
+
// rgb channels already clamp to [0,255] via `clampChannel`, matching Motion's `clampRgbUnit`.
|
|
211
|
+
function clampAlpha(v) {
|
|
212
|
+
'worklet';
|
|
213
|
+
return Math.min(1, Math.max(0, v));
|
|
214
|
+
}
|
|
215
|
+
export function transformColor(c) {
|
|
216
|
+
'worklet';
|
|
217
|
+
return `rgba(${clampChannel(c.red)}, ${clampChannel(c.green)}, ${clampChannel(c.blue)}, ${sanitize(clampAlpha(c.alpha))})`;
|
|
218
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Complex/compound value types (SPEC-VALUE-TYPES §3, REQ-VALUETYPE-007/008). Tokenize a compound string
|
|
3
|
+
// (borderRadius, box-shadow, transform lists) into an ordered list of typed slots (number+unit or color)
|
|
4
|
+
// plus the literal separators between them, mix positionally, and recompose using the TARGET's separators.
|
|
5
|
+
// Host-agnostic (REQ-VALUETYPE-011). Native TIGHTENS motion-dom: exact-count shape compatibility (motion
|
|
6
|
+
// allows origin.count >= target.count), a per-slot unit-identity check (motion folds units into its split
|
|
7
|
+
// template and silently re-applies the target's), and a loud throw where motion warns + snaps
|
|
8
|
+
// (mixImmediate). motion carries bare numbers + a unit-bearing split; Native carries number+unit slots +
|
|
9
|
+
// unit-free separators — the recompose STRING is byte-identical either way.
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.parseComplex = parseComplex;
|
|
12
|
+
exports.transformComplex = transformComplex;
|
|
13
|
+
exports.mixComplex = mixComplex;
|
|
14
|
+
const constants_1 = require("./constants.cjs");
|
|
15
|
+
const color_1 = require("./color.cjs");
|
|
16
|
+
const numeric_1 = require("./numeric.cjs");
|
|
17
|
+
// Longest-match-first token recognizers (REQ-VALUETYPE-007). Sticky (`y`) so each is anchored at the
|
|
18
|
+
// cursor. The hex branch grabs the WHOLE `#…` alphanumeric run (not a fixed 3/4/6/8 length) so a
|
|
19
|
+
// malformed hex like `#12` or `#1234567` is captured in full and handed to parseColor, which throws
|
|
20
|
+
// (REQ-VALUETYPE-008) — rather than the old fixed-length regex silently matching the `#123456` prefix and
|
|
21
|
+
// leaving a stray `7` to tokenize as a bogus number. A bare `#` (no alnum after) is caught by an explicit
|
|
22
|
+
// guard in the scan loop.
|
|
23
|
+
const COLOR_TOKEN_RE = /#[0-9a-z]+|(?:rgba?|hsla?)\([^)]*\)/iy;
|
|
24
|
+
const NUMBER_TOKEN_RE = /[-+]?(?:\d+\.?\d*|\.\d+)(?:e[-+]?\d+)?/iy;
|
|
25
|
+
const VAR_TOKEN_RE = /var\(\s*--[^)]*\)/iy;
|
|
26
|
+
// A color-function SIGIL (`rgb(`/`rgba(`/`hsl(`/`hsla(`). Non-sticky, tested against the remaining slice:
|
|
27
|
+
// if a sigil is present but COLOR_TOKEN_RE could not consume a complete function, the function is
|
|
28
|
+
// unclosed/malformed and must fail loud rather than silently tokenizing its interior numbers.
|
|
29
|
+
const COLOR_SIGIL_RE = /^(?:rgba?|hsla?)\(/i;
|
|
30
|
+
function matchStickyAt(re, raw, at) {
|
|
31
|
+
re.lastIndex = at;
|
|
32
|
+
const m = re.exec(raw);
|
|
33
|
+
return m !== null && m.index === at ? m[0] : null;
|
|
34
|
+
}
|
|
35
|
+
// A number token plus an optional trailing KNOWN unit. A non-KNOWN suffix (e.g. `em`) is left in the
|
|
36
|
+
// template, matching motion — REQ-VALUETYPE-004's unit check only governs the units this subsystem owns.
|
|
37
|
+
function matchNumberAt(raw, at) {
|
|
38
|
+
const numStr = matchStickyAt(NUMBER_TOKEN_RE, raw, at);
|
|
39
|
+
if (numStr === null)
|
|
40
|
+
return null;
|
|
41
|
+
const rest = raw.slice(at + numStr.length);
|
|
42
|
+
let unit = '';
|
|
43
|
+
for (const u of constants_1.KNOWN_UNITS) {
|
|
44
|
+
if (rest.startsWith(u)) {
|
|
45
|
+
unit = u;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { value: Number.parseFloat(numStr), unit, length: numStr.length + unit.length };
|
|
50
|
+
}
|
|
51
|
+
function parseComplex(raw) {
|
|
52
|
+
const slots = [];
|
|
53
|
+
const template = [];
|
|
54
|
+
let cursor = 0;
|
|
55
|
+
let literalStart = 0;
|
|
56
|
+
while (cursor < raw.length) {
|
|
57
|
+
// var() is web-only (REQ-VALUETYPE-007): on the native runtime it fails loud at parse, never silently
|
|
58
|
+
// becomes a literal.
|
|
59
|
+
if (matchStickyAt(VAR_TOKEN_RE, raw, cursor) !== null) {
|
|
60
|
+
throw new Error(`CSS variable tokens are web-only and cannot be parsed on the native runtime: ${JSON.stringify(raw)}`);
|
|
61
|
+
}
|
|
62
|
+
// color BEFORE number (longest-match-first): rgba(…) wins over the bare `0` at its opening paren.
|
|
63
|
+
const color = matchStickyAt(COLOR_TOKEN_RE, raw, cursor);
|
|
64
|
+
if (color !== null) {
|
|
65
|
+
template.push(raw.slice(literalStart, cursor));
|
|
66
|
+
slots.push({ kind: 'color', value: (0, color_1.parseColor)(color) });
|
|
67
|
+
cursor += color.length;
|
|
68
|
+
literalStart = cursor;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const num = matchNumberAt(raw, cursor);
|
|
72
|
+
if (num !== null) {
|
|
73
|
+
template.push(raw.slice(literalStart, cursor));
|
|
74
|
+
slots.push({ kind: 'number', value: num.value, unit: num.unit });
|
|
75
|
+
cursor += num.length;
|
|
76
|
+
literalStart = cursor;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// A color SIGIL the color branch could not accept — a bare/malformed `#`, or an unclosed/malformed
|
|
80
|
+
// `rgb()`/`hsl()` — is a malformed color, never literal template filler: fail loud (REQ-VALUETYPE-008)
|
|
81
|
+
// so it can never silently split into a `#`/`rgb(` literal plus bogus downstream numbers. An unknown
|
|
82
|
+
// keyword WITHOUT a sigil (`solid`, `inset`, `rgbicon`) stays a legitimate literal.
|
|
83
|
+
if (raw.charAt(cursor) === '#' || COLOR_SIGIL_RE.test(raw.slice(cursor))) {
|
|
84
|
+
throw new Error(`Malformed color token at index ${cursor} in complex value ${JSON.stringify(raw)}`);
|
|
85
|
+
}
|
|
86
|
+
cursor++;
|
|
87
|
+
}
|
|
88
|
+
template.push(raw.slice(literalStart));
|
|
89
|
+
if (slots.length === 0) {
|
|
90
|
+
throw new Error(`No animatable tokens (number/color) in complex value ${JSON.stringify(raw)}`);
|
|
91
|
+
}
|
|
92
|
+
return { slots, template };
|
|
93
|
+
}
|
|
94
|
+
// motion's mixComplex recompose runs each interpolated number through `sanitize` (round to 5 decimals) —
|
|
95
|
+
// note this is a DIVERGENCE from motion's STANDALONE number/unit transform, which is raw (see numeric.ts).
|
|
96
|
+
// Native mirrors motion per-path for byte-parity: raw standalone (transformNumeric), sanitized in complex.
|
|
97
|
+
function sanitize(v) {
|
|
98
|
+
return Math.round(v * 1e5) / 1e5;
|
|
99
|
+
}
|
|
100
|
+
function transformSlot(slot) {
|
|
101
|
+
if (slot.kind === 'color')
|
|
102
|
+
return (0, color_1.transformColor)(slot.value);
|
|
103
|
+
const v = sanitize(slot.value);
|
|
104
|
+
return slot.unit === '' ? String(v) : `${v}${slot.unit}`;
|
|
105
|
+
}
|
|
106
|
+
function transformComplex(v) {
|
|
107
|
+
let out = v.template[0] ?? '';
|
|
108
|
+
for (let i = 0; i < v.slots.length; i++) {
|
|
109
|
+
const slot = v.slots[i];
|
|
110
|
+
if (slot === undefined)
|
|
111
|
+
continue; // unreachable: i < slots.length
|
|
112
|
+
out += transformSlot(slot) + (v.template[i + 1] ?? '');
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function countByKind(v) {
|
|
117
|
+
let number = 0;
|
|
118
|
+
let color = 0;
|
|
119
|
+
for (const s of v.slots) {
|
|
120
|
+
if (s.kind === 'color')
|
|
121
|
+
color++;
|
|
122
|
+
else
|
|
123
|
+
number++;
|
|
124
|
+
}
|
|
125
|
+
return { number, color };
|
|
126
|
+
}
|
|
127
|
+
// Exact-count shape compatibility (REQ-VALUETYPE-007): motion mixes when origin.count >= target.count and
|
|
128
|
+
// snaps the excess; Native requires the counts to match exactly and fails loud otherwise.
|
|
129
|
+
function assertShapeCompatible(from, to) {
|
|
130
|
+
const cf = countByKind(from);
|
|
131
|
+
const ct = countByKind(to);
|
|
132
|
+
if (cf.number !== ct.number || cf.color !== ct.color) {
|
|
133
|
+
throw new Error(`Complex values are shape-incompatible: origin ${JSON.stringify(transformComplex(from))} has ` +
|
|
134
|
+
`${cf.number} number(s)/${cf.color} color(s), target ${JSON.stringify(transformComplex(to))} has ` +
|
|
135
|
+
`${ct.number} number(s)/${ct.color} color(s) — counts must match exactly`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Positionally mix each slot: numbers via mixNumeric (which enforces the per-slot unit-identity check,
|
|
139
|
+
// REQ-VALUETYPE-004), colors via the gamma-aware mixColor. Recompose with the TARGET's template
|
|
140
|
+
// (REQ-VALUETYPE-007). A same-count but positional type mismatch (number vs color at a slot) is a loud
|
|
141
|
+
// failure (REQ-VALUETYPE-008).
|
|
142
|
+
function mixComplex(from, to, progress) {
|
|
143
|
+
assertShapeCompatible(from, to);
|
|
144
|
+
const slots = to.slots.map((toSlot, i) => {
|
|
145
|
+
const fromSlot = from.slots[i];
|
|
146
|
+
if (fromSlot === undefined) {
|
|
147
|
+
throw new Error(`Missing origin slot ${i} for complex mix`); // unreachable after shape check
|
|
148
|
+
}
|
|
149
|
+
if (fromSlot.kind === 'number' && toSlot.kind === 'number') {
|
|
150
|
+
const mixed = (0, numeric_1.mixNumeric)({ value: fromSlot.value, unit: fromSlot.unit }, { value: toSlot.value, unit: toSlot.unit }, progress);
|
|
151
|
+
return { kind: 'number', value: mixed.value, unit: mixed.unit };
|
|
152
|
+
}
|
|
153
|
+
if (fromSlot.kind === 'color' && toSlot.kind === 'color') {
|
|
154
|
+
return { kind: 'color', value: (0, color_1.mixColor)(fromSlot.value, toSlot.value, progress) };
|
|
155
|
+
}
|
|
156
|
+
throw new Error(`Type mismatch at slot ${i}: origin is ${fromSlot.kind}, target is ${toSlot.kind} — ` +
|
|
157
|
+
`${JSON.stringify(transformComplex(from))} vs ${JSON.stringify(transformComplex(to))}`);
|
|
158
|
+
});
|
|
159
|
+
return { slots, template: to.template };
|
|
160
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RGBA } from "./color.cjs";
|
|
2
|
+
interface NumberSlot {
|
|
3
|
+
readonly kind: 'number';
|
|
4
|
+
readonly value: number;
|
|
5
|
+
readonly unit: string;
|
|
6
|
+
}
|
|
7
|
+
interface ColorSlot {
|
|
8
|
+
readonly kind: 'color';
|
|
9
|
+
readonly value: RGBA;
|
|
10
|
+
}
|
|
11
|
+
export type Slot = NumberSlot | ColorSlot;
|
|
12
|
+
export interface ComplexValue {
|
|
13
|
+
readonly slots: readonly Slot[];
|
|
14
|
+
readonly template: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function parseComplex(raw: string): ComplexValue;
|
|
17
|
+
export declare function transformComplex(v: ComplexValue): string;
|
|
18
|
+
export declare function mixComplex(from: ComplexValue, to: ComplexValue, progress: number): ComplexValue;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RGBA } from "./color.js";
|
|
2
|
+
interface NumberSlot {
|
|
3
|
+
readonly kind: 'number';
|
|
4
|
+
readonly value: number;
|
|
5
|
+
readonly unit: string;
|
|
6
|
+
}
|
|
7
|
+
interface ColorSlot {
|
|
8
|
+
readonly kind: 'color';
|
|
9
|
+
readonly value: RGBA;
|
|
10
|
+
}
|
|
11
|
+
export type Slot = NumberSlot | ColorSlot;
|
|
12
|
+
export interface ComplexValue {
|
|
13
|
+
readonly slots: readonly Slot[];
|
|
14
|
+
readonly template: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export declare function parseComplex(raw: string): ComplexValue;
|
|
17
|
+
export declare function transformComplex(v: ComplexValue): string;
|
|
18
|
+
export declare function mixComplex(from: ComplexValue, to: ComplexValue, progress: number): ComplexValue;
|
|
19
|
+
export {};
|