@wardic/dom 0.0.0-stage → 1.0.0-rc.2
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/LICENSE +21 -0
- package/README.md +8 -2
- package/THIRD_PARTY_NOTICES.md +145 -0
- package/dist/Appear.d.ts +24 -0
- package/dist/Appear.d.ts.map +1 -0
- package/dist/Appear.js +25 -0
- package/dist/Appear.js.map +1 -0
- package/dist/Color.d.ts +14 -0
- package/dist/Color.d.ts.map +1 -0
- package/dist/Color.js +16 -0
- package/dist/Color.js.map +1 -0
- package/dist/DomAnnouncer.d.ts +13 -0
- package/dist/DomAnnouncer.d.ts.map +1 -0
- package/dist/DomAnnouncer.js +14 -0
- package/dist/DomAnnouncer.js.map +1 -0
- package/dist/DomClock.d.ts +11 -0
- package/dist/DomClock.d.ts.map +1 -0
- package/dist/DomClock.js +12 -0
- package/dist/DomClock.js.map +1 -0
- package/dist/DomDefaultTransition.d.ts +12 -0
- package/dist/DomDefaultTransition.d.ts.map +1 -0
- package/dist/DomDefaultTransition.js +13 -0
- package/dist/DomDefaultTransition.js.map +1 -0
- package/dist/DomInput.d.ts +13 -0
- package/dist/DomInput.d.ts.map +1 -0
- package/dist/DomInput.js +15 -0
- package/dist/DomInput.js.map +1 -0
- package/dist/DomInterpolators.d.ts +13 -0
- package/dist/DomInterpolators.d.ts.map +1 -0
- package/dist/DomInterpolators.js +14 -0
- package/dist/DomInterpolators.js.map +1 -0
- package/dist/DomKeyRules.d.ts +13 -0
- package/dist/DomKeyRules.d.ts.map +1 -0
- package/dist/DomKeyRules.js +14 -0
- package/dist/DomKeyRules.js.map +1 -0
- package/dist/DomPositionalKeys.d.ts +13 -0
- package/dist/DomPositionalKeys.d.ts.map +1 -0
- package/dist/DomPositionalKeys.js +14 -0
- package/dist/DomPositionalKeys.js.map +1 -0
- package/dist/DomProbe.d.ts +13 -0
- package/dist/DomProbe.d.ts.map +1 -0
- package/dist/DomProbe.js +14 -0
- package/dist/DomProbe.js.map +1 -0
- package/dist/DomProjectionSystem.d.ts +28 -0
- package/dist/DomProjectionSystem.d.ts.map +1 -0
- package/dist/DomProjectionSystem.js +41 -0
- package/dist/DomProjectionSystem.js.map +1 -0
- package/dist/DomRenderer.d.ts +13 -0
- package/dist/DomRenderer.d.ts.map +1 -0
- package/dist/DomRenderer.js +15 -0
- package/dist/DomRenderer.js.map +1 -0
- package/dist/FlushGate.d.ts +48 -0
- package/dist/FlushGate.d.ts.map +1 -0
- package/dist/FlushGate.js +62 -0
- package/dist/FlushGate.js.map +1 -0
- package/dist/MeasuredPair.d.ts +14 -0
- package/dist/MeasuredPair.d.ts.map +1 -0
- package/dist/MeasuredPair.js +16 -0
- package/dist/MeasuredPair.js.map +1 -0
- package/dist/OwnValues.d.ts +14 -0
- package/dist/OwnValues.d.ts.map +1 -0
- package/dist/OwnValues.js +15 -0
- package/dist/OwnValues.js.map +1 -0
- package/dist/ProjectionSystem.d.ts +34 -0
- package/dist/ProjectionSystem.d.ts.map +1 -0
- package/dist/ProjectionSystem.js +29 -0
- package/dist/ProjectionSystem.js.map +1 -0
- package/dist/ReorderController.d.ts +41 -0
- package/dist/ReorderController.d.ts.map +1 -0
- package/dist/ReorderController.js +44 -0
- package/dist/ReorderController.js.map +1 -0
- package/dist/Report.d.ts +12 -0
- package/dist/Report.d.ts.map +1 -0
- package/dist/Report.js +13 -0
- package/dist/Report.js.map +1 -0
- package/dist/ScrollOffset.d.ts +34 -0
- package/dist/ScrollOffset.d.ts.map +1 -0
- package/dist/ScrollOffset.js +42 -0
- package/dist/ScrollOffset.js.map +1 -0
- package/dist/Style.d.ts +15 -0
- package/dist/Style.d.ts.map +1 -0
- package/dist/Style.js +17 -0
- package/dist/Style.js.map +1 -0
- package/dist/Svg.d.ts +13 -0
- package/dist/Svg.d.ts.map +1 -0
- package/dist/Svg.js +14 -0
- package/dist/Svg.js.map +1 -0
- package/dist/Variables.d.ts +13 -0
- package/dist/Variables.d.ts.map +1 -0
- package/dist/Variables.js +14 -0
- package/dist/Variables.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/announcer.d.ts +50 -0
- package/dist/internal/announcer.d.ts.map +1 -0
- package/dist/internal/announcer.js +115 -0
- package/dist/internal/announcer.js.map +1 -0
- package/dist/internal/appear.d.ts +169 -0
- package/dist/internal/appear.d.ts.map +1 -0
- package/dist/internal/appear.js +499 -0
- package/dist/internal/appear.js.map +1 -0
- package/dist/internal/auto-scroll.d.ts +24 -0
- package/dist/internal/auto-scroll.d.ts.map +1 -0
- package/dist/internal/auto-scroll.js +124 -0
- package/dist/internal/auto-scroll.js.map +1 -0
- package/dist/internal/clock.d.ts +19 -0
- package/dist/internal/clock.d.ts.map +1 -0
- package/dist/internal/clock.js +39 -0
- package/dist/internal/clock.js.map +1 -0
- package/dist/internal/colors.d.ts +45 -0
- package/dist/internal/colors.d.ts.map +1 -0
- package/dist/internal/colors.js +193 -0
- package/dist/internal/colors.js.map +1 -0
- package/dist/internal/complex.d.ts +24 -0
- package/dist/internal/complex.d.ts.map +1 -0
- package/dist/internal/complex.js +28 -0
- package/dist/internal/complex.js.map +1 -0
- package/dist/internal/content-watch.d.ts +20 -0
- package/dist/internal/content-watch.d.ts.map +1 -0
- package/dist/internal/content-watch.js +76 -0
- package/dist/internal/content-watch.js.map +1 -0
- package/dist/internal/default-transition.d.ts +25 -0
- package/dist/internal/default-transition.d.ts.map +1 -0
- package/dist/internal/default-transition.js +41 -0
- package/dist/internal/default-transition.js.map +1 -0
- package/dist/internal/drag-defaults.d.ts +18 -0
- package/dist/internal/drag-defaults.d.ts.map +1 -0
- package/dist/internal/drag-defaults.js +38 -0
- package/dist/internal/drag-defaults.js.map +1 -0
- package/dist/internal/gate.d.ts +186 -0
- package/dist/internal/gate.d.ts.map +1 -0
- package/dist/internal/gate.js +453 -0
- package/dist/internal/gate.js.map +1 -0
- package/dist/internal/input.d.ts +41 -0
- package/dist/internal/input.d.ts.map +1 -0
- package/dist/internal/input.js +476 -0
- package/dist/internal/input.js.map +1 -0
- package/dist/internal/interpolators.d.ts +19 -0
- package/dist/internal/interpolators.d.ts.map +1 -0
- package/dist/internal/interpolators.js +30 -0
- package/dist/internal/interpolators.js.map +1 -0
- package/dist/internal/key-rules.d.ts +11 -0
- package/dist/internal/key-rules.d.ts.map +1 -0
- package/dist/internal/key-rules.js +244 -0
- package/dist/internal/key-rules.js.map +1 -0
- package/dist/internal/measure.d.ts +150 -0
- package/dist/internal/measure.d.ts.map +1 -0
- package/dist/internal/measure.js +278 -0
- package/dist/internal/measure.js.map +1 -0
- package/dist/internal/measured-pairs.d.ts +38 -0
- package/dist/internal/measured-pairs.d.ts.map +1 -0
- package/dist/internal/measured-pairs.js +251 -0
- package/dist/internal/measured-pairs.js.map +1 -0
- package/dist/internal/own-values.d.ts +35 -0
- package/dist/internal/own-values.d.ts.map +1 -0
- package/dist/internal/own-values.js +117 -0
- package/dist/internal/own-values.js.map +1 -0
- package/dist/internal/pass-ends.d.ts +21 -0
- package/dist/internal/pass-ends.d.ts.map +1 -0
- package/dist/internal/pass-ends.js +14 -0
- package/dist/internal/pass-ends.js.map +1 -0
- package/dist/internal/positional-keys.d.ts +16 -0
- package/dist/internal/positional-keys.d.ts.map +1 -0
- package/dist/internal/positional-keys.js +21 -0
- package/dist/internal/positional-keys.js.map +1 -0
- package/dist/internal/probe.d.ts +10 -0
- package/dist/internal/probe.d.ts.map +1 -0
- package/dist/internal/probe.js +117 -0
- package/dist/internal/probe.js.map +1 -0
- package/dist/internal/projection-defaults.d.ts +34 -0
- package/dist/internal/projection-defaults.d.ts.map +1 -0
- package/dist/internal/projection-defaults.js +21 -0
- package/dist/internal/projection-defaults.js.map +1 -0
- package/dist/internal/projection.d.ts +529 -0
- package/dist/internal/projection.d.ts.map +1 -0
- package/dist/internal/projection.js +4990 -0
- package/dist/internal/projection.js.map +1 -0
- package/dist/internal/renderer.d.ts +86 -0
- package/dist/internal/renderer.d.ts.map +1 -0
- package/dist/internal/renderer.js +616 -0
- package/dist/internal/renderer.js.map +1 -0
- package/dist/internal/reorder.d.ts +159 -0
- package/dist/internal/reorder.d.ts.map +1 -0
- package/dist/internal/reorder.js +437 -0
- package/dist/internal/reorder.js.map +1 -0
- package/dist/internal/report.d.ts +30 -0
- package/dist/internal/report.d.ts.map +1 -0
- package/dist/internal/report.js +60 -0
- package/dist/internal/report.js.map +1 -0
- package/dist/internal/require-global.d.ts +11 -0
- package/dist/internal/require-global.d.ts.map +1 -0
- package/dist/internal/require-global.js +31 -0
- package/dist/internal/require-global.js.map +1 -0
- package/dist/internal/reveal.d.ts +16 -0
- package/dist/internal/reveal.d.ts.map +1 -0
- package/dist/internal/reveal.js +66 -0
- package/dist/internal/reveal.js.map +1 -0
- package/dist/internal/runner.d.ts +21 -0
- package/dist/internal/runner.d.ts.map +1 -0
- package/dist/internal/runner.js +48 -0
- package/dist/internal/runner.js.map +1 -0
- package/dist/internal/scale-correction.d.ts +80 -0
- package/dist/internal/scale-correction.d.ts.map +1 -0
- package/dist/internal/scale-correction.js +227 -0
- package/dist/internal/scale-correction.js.map +1 -0
- package/dist/internal/scroll.d.ts +35 -0
- package/dist/internal/scroll.d.ts.map +1 -0
- package/dist/internal/scroll.js +244 -0
- package/dist/internal/scroll.js.map +1 -0
- package/dist/internal/scrollers.d.ts +35 -0
- package/dist/internal/scrollers.d.ts.map +1 -0
- package/dist/internal/scrollers.js +38 -0
- package/dist/internal/scrollers.js.map +1 -0
- package/dist/internal/starting-style.d.ts +141 -0
- package/dist/internal/starting-style.d.ts.map +1 -0
- package/dist/internal/starting-style.js +300 -0
- package/dist/internal/starting-style.js.map +1 -0
- package/dist/internal/svg.d.ts +60 -0
- package/dist/internal/svg.d.ts.map +1 -0
- package/dist/internal/svg.js +257 -0
- package/dist/internal/svg.js.map +1 -0
- package/dist/internal/variables.d.ts +45 -0
- package/dist/internal/variables.d.ts.map +1 -0
- package/dist/internal/variables.js +92 -0
- package/dist/internal/variables.js.map +1 -0
- package/package.json +65 -4
- package/src/Appear.ts +45 -0
- package/src/Color.ts +24 -0
- package/src/DomAnnouncer.ts +22 -0
- package/src/DomClock.ts +13 -0
- package/src/DomDefaultTransition.ts +16 -0
- package/src/DomInput.ts +22 -0
- package/src/DomInterpolators.ts +16 -0
- package/src/DomKeyRules.ts +17 -0
- package/src/DomPositionalKeys.ts +16 -0
- package/src/DomProbe.ts +15 -0
- package/src/DomProjectionSystem.ts +44 -0
- package/src/DomRenderer.ts +26 -0
- package/src/FlushGate.ts +81 -0
- package/src/MeasuredPair.ts +15 -0
- package/src/OwnValues.ts +19 -0
- package/src/ProjectionSystem.ts +51 -0
- package/src/ReorderController.ts +72 -0
- package/src/Report.ts +17 -0
- package/src/ScrollOffset.ts +54 -0
- package/src/Style.ts +37 -0
- package/src/Svg.ts +21 -0
- package/src/Variables.ts +17 -0
- package/src/index.ts +106 -0
- package/src/internal/announcer.ts +132 -0
- package/src/internal/appear.ts +739 -0
- package/src/internal/auto-scroll.ts +176 -0
- package/src/internal/clock.ts +45 -0
- package/src/internal/colors.ts +258 -0
- package/src/internal/complex.ts +29 -0
- package/src/internal/content-watch.ts +84 -0
- package/src/internal/culori.d.ts +58 -0
- package/src/internal/default-transition.ts +50 -0
- package/src/internal/drag-defaults.ts +54 -0
- package/src/internal/gate.ts +593 -0
- package/src/internal/input.ts +579 -0
- package/src/internal/interpolators.ts +33 -0
- package/src/internal/key-rules.ts +357 -0
- package/src/internal/measure.ts +357 -0
- package/src/internal/measured-pairs.ts +297 -0
- package/src/internal/own-values.ts +166 -0
- package/src/internal/pass-ends.ts +23 -0
- package/src/internal/positional-keys.ts +24 -0
- package/src/internal/probe.ts +176 -0
- package/src/internal/projection-defaults.ts +45 -0
- package/src/internal/projection.ts +5999 -0
- package/src/internal/renderer.ts +646 -0
- package/src/internal/reorder.ts +604 -0
- package/src/internal/report.ts +69 -0
- package/src/internal/require-global.ts +46 -0
- package/src/internal/reveal.ts +69 -0
- package/src/internal/runner.ts +64 -0
- package/src/internal/scale-correction.ts +310 -0
- package/src/internal/scroll.ts +356 -0
- package/src/internal/scrollers.ts +51 -0
- package/src/internal/starting-style.ts +396 -0
- package/src/internal/svg.ts +300 -0
- package/src/internal/variables.ts +145 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `RendererDomLive` — the browser realization of `Renderer` (ADR-0002).
|
|
3
|
+
*
|
|
4
|
+
* Split into the two halves the contract mandates:
|
|
5
|
+
*
|
|
6
|
+
* - {@link project} — PURE, synchronous: translates engine values into a DOM
|
|
7
|
+
* `Patch`. Its one piece of real work is grouping the individual transform
|
|
8
|
+
* keys (`x`/`y`/`scale`/`rotate`/…) into a single CSS `transform` string in
|
|
9
|
+
* a canonical order, and applying default units (px/deg) to bare numbers.
|
|
10
|
+
* Reused by the Starting style so this grouping/unit logic isn't duplicated in
|
|
11
|
+
* the binding (ADR-0002). No side effects, no Effect.
|
|
12
|
+
* - {@link commit} — imperative: writes each `Patch` entry onto `target.style`, now.
|
|
13
|
+
* The flush gate calls it once per element per Refresh frame (ADR-0034), and
|
|
14
|
+
* `applyTo` once per Frame.
|
|
15
|
+
*
|
|
16
|
+
* Observability (CONTEXT.md convention): `project` and `commit` are the
|
|
17
|
+
* per-frame hot path, so — exactly like core's `applyTo` per-frame writes and
|
|
18
|
+
* the Clock's `tick` — they stay quiet (no span, no per-call log).
|
|
19
|
+
*/
|
|
20
|
+
import * as BaseValue from "@wardic/core/BaseValue"
|
|
21
|
+
import type * as Point from "@wardic/core/Point"
|
|
22
|
+
import * as Projection from "@wardic/core/Projection"
|
|
23
|
+
import * as Renderer from "@wardic/core/Renderer"
|
|
24
|
+
import { dashCase, isSvgInner, setSvgAttrs, splitPatch } from "./svg.ts"
|
|
25
|
+
import * as Bool from "effect/Boolean"
|
|
26
|
+
import * as Layer from "effect/Layer"
|
|
27
|
+
import * as Predicate from "effect/Predicate"
|
|
28
|
+
import * as UndefinedOr from "effect/UndefinedOr"
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* CSS transform functions in their canonical emission order: translate group,
|
|
32
|
+
* then scale, then rotate, then skew. `project` walks this list and emits a
|
|
33
|
+
* func for each key that is present in `values` and not at its identity, so the
|
|
34
|
+
* resulting `transform` string is always grouped in this order regardless of
|
|
35
|
+
* input key order.
|
|
36
|
+
*
|
|
37
|
+
* `key` is the property name accepted on engine values (shorthands `x`/`y`/`z`
|
|
38
|
+
* included); `fn` is the CSS function name it renders as; `identity` is core's
|
|
39
|
+
* (ADR-0022), the value at which `project` leaves the key out (ADR-0023).
|
|
40
|
+
*/
|
|
41
|
+
const TRANSFORM_FUNCS: ReadonlyArray<{ readonly key: string; readonly fn: string; readonly identity: number }> = [
|
|
42
|
+
// translate group — shorthands x/y/z map onto translateX/Y/Z
|
|
43
|
+
{ key: "x", fn: "translateX" },
|
|
44
|
+
{ key: "y", fn: "translateY" },
|
|
45
|
+
{ key: "z", fn: "translateZ" },
|
|
46
|
+
{ key: "translateX", fn: "translateX" },
|
|
47
|
+
{ key: "translateY", fn: "translateY" },
|
|
48
|
+
{ key: "translateZ", fn: "translateZ" },
|
|
49
|
+
// scale group — `scale` is uniform scale(v); scaleX/scaleY are per-axis (flip)
|
|
50
|
+
{ key: "scale", fn: "scale" },
|
|
51
|
+
{ key: "scaleX", fn: "scaleX" },
|
|
52
|
+
{ key: "scaleY", fn: "scaleY" },
|
|
53
|
+
// rotate group: a path's turn after the element's own (#438), which `rotate(a) rotate(b)` adds up
|
|
54
|
+
{ key: "rotate", fn: "rotate" },
|
|
55
|
+
{ key: "pathRotation", fn: "rotate" },
|
|
56
|
+
{ key: "rotateX", fn: "rotateX" },
|
|
57
|
+
{ key: "rotateY", fn: "rotateY" },
|
|
58
|
+
{ key: "rotateZ", fn: "rotateZ" },
|
|
59
|
+
// skew group
|
|
60
|
+
{ key: "skewX", fn: "skewX" },
|
|
61
|
+
{ key: "skewY", fn: "skewY" },
|
|
62
|
+
].map((f) => ({ ...f, identity: UndefinedOr.getOrThrow(BaseValue.TRANSFORM_IDENTITY[f.key]) }))
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Whether a transform value is at its identity (ADR-0023), Motion's test
|
|
66
|
+
* (motion-dom build-transform.ts:42-48 at 16c61222c4166bbef5ff6ab0051192faa79fa8c8):
|
|
67
|
+
* a number equal to it, or a string `parseFloat` takes to it (`"0px"`, `"0%"`,
|
|
68
|
+
* `"0deg"`; never `"calc(0px)"` or `"var(--x)"`). Exact: a settled spring lands on
|
|
69
|
+
* its target, and a value next to the identity mid-flight is still written.
|
|
70
|
+
*/
|
|
71
|
+
const isIdentity = (value: unknown, identity: number): boolean => {
|
|
72
|
+
if (Predicate.isNumber(value)) return value === identity
|
|
73
|
+
return Predicate.isString(value) && Number.parseFloat(value) === identity
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** `value` when it's a number, else `fallback`. Per frame: an early return, no Option. */
|
|
77
|
+
const numberOr = (value: unknown, fallback: number): number => {
|
|
78
|
+
if (Predicate.isNumber(value)) return value
|
|
79
|
+
return fallback
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Default unit appended to a bare numeric transform value, by transform key. */
|
|
83
|
+
type TransformUnit = "px" | "deg" | ""
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Default-unit map for transform values: translate (and the x/y/z shorthands)
|
|
87
|
+
* are lengths → `px`; rotate/skew are angles → `deg`; scale* are unitless.
|
|
88
|
+
* A value that is already a string is used verbatim (explicit units honoured),
|
|
89
|
+
* so this map only drives the formatting of bare numbers.
|
|
90
|
+
*/
|
|
91
|
+
const TRANSFORM_UNITS: Record<string, TransformUnit> = {
|
|
92
|
+
x: "px",
|
|
93
|
+
y: "px",
|
|
94
|
+
z: "px",
|
|
95
|
+
translateX: "px",
|
|
96
|
+
translateY: "px",
|
|
97
|
+
translateZ: "px",
|
|
98
|
+
scale: "",
|
|
99
|
+
scaleX: "",
|
|
100
|
+
scaleY: "",
|
|
101
|
+
rotate: "deg",
|
|
102
|
+
pathRotation: "deg",
|
|
103
|
+
rotateX: "deg",
|
|
104
|
+
rotateY: "deg",
|
|
105
|
+
rotateZ: "deg",
|
|
106
|
+
skewX: "deg",
|
|
107
|
+
skewY: "deg",
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Fast membership test for "is this key a transform key?": core's transform keys, the ones
|
|
112
|
+
* it gives an identity (ADR-0022), so the two lists can't drift. Every one has a unit above.
|
|
113
|
+
*/
|
|
114
|
+
const TRANSFORM_KEYS: ReadonlySet<string> = new Set(Object.keys(BaseValue.TRANSFORM_IDENTITY))
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The CSS properties a bare number on is a length, drawn in `px`, spelled as Wardic's keys are
|
|
118
|
+
* (`margin-top`): Motion's, and `min-width`, `min-height`, the gaps and `letter-spacing`. The Solid
|
|
119
|
+
* bindings' value types take a number on these and on no other length, so a number never draws CSS a
|
|
120
|
+
* browser drops. `line-height` isn't one: CSS reads its bare number as a multiple. A number on any other
|
|
121
|
+
* key, `opacity`, `z-index`, a custom property, is drawn as it is; a string always is.
|
|
122
|
+
*/
|
|
123
|
+
// #522: Motion's `numberValueTypes`' `px` keys; the bindings' `WardicValues`.
|
|
124
|
+
export const LENGTH_KEYS = [
|
|
125
|
+
"border-width",
|
|
126
|
+
"border-top-width",
|
|
127
|
+
"border-right-width",
|
|
128
|
+
"border-bottom-width",
|
|
129
|
+
"border-left-width",
|
|
130
|
+
"border-radius",
|
|
131
|
+
"border-top-left-radius",
|
|
132
|
+
"border-top-right-radius",
|
|
133
|
+
"border-bottom-right-radius",
|
|
134
|
+
"border-bottom-left-radius",
|
|
135
|
+
"width",
|
|
136
|
+
"min-width",
|
|
137
|
+
"max-width",
|
|
138
|
+
"height",
|
|
139
|
+
"min-height",
|
|
140
|
+
"max-height",
|
|
141
|
+
"top",
|
|
142
|
+
"right",
|
|
143
|
+
"bottom",
|
|
144
|
+
"left",
|
|
145
|
+
"inset",
|
|
146
|
+
"inset-block",
|
|
147
|
+
"inset-block-start",
|
|
148
|
+
"inset-block-end",
|
|
149
|
+
"inset-inline",
|
|
150
|
+
"inset-inline-start",
|
|
151
|
+
"inset-inline-end",
|
|
152
|
+
"padding",
|
|
153
|
+
"padding-top",
|
|
154
|
+
"padding-right",
|
|
155
|
+
"padding-bottom",
|
|
156
|
+
"padding-left",
|
|
157
|
+
"padding-block",
|
|
158
|
+
"padding-block-start",
|
|
159
|
+
"padding-block-end",
|
|
160
|
+
"padding-inline",
|
|
161
|
+
"padding-inline-start",
|
|
162
|
+
"padding-inline-end",
|
|
163
|
+
"margin",
|
|
164
|
+
"margin-top",
|
|
165
|
+
"margin-right",
|
|
166
|
+
"margin-bottom",
|
|
167
|
+
"margin-left",
|
|
168
|
+
"margin-block",
|
|
169
|
+
"margin-block-start",
|
|
170
|
+
"margin-block-end",
|
|
171
|
+
"margin-inline",
|
|
172
|
+
"margin-inline-start",
|
|
173
|
+
"margin-inline-end",
|
|
174
|
+
"gap",
|
|
175
|
+
"row-gap",
|
|
176
|
+
"column-gap",
|
|
177
|
+
"font-size",
|
|
178
|
+
"letter-spacing",
|
|
179
|
+
"background-position-x",
|
|
180
|
+
"background-position-y",
|
|
181
|
+
] as const
|
|
182
|
+
|
|
183
|
+
/** One of `LENGTH_KEYS`: a CSS property a bare number on is drawn in `px`. */
|
|
184
|
+
export type LengthKey = (typeof LENGTH_KEYS)[number]
|
|
185
|
+
|
|
186
|
+
/** {@link LENGTH_KEYS}, for a membership test a frame can make. */
|
|
187
|
+
const LENGTH_KEY_SET: ReadonlySet<string> = new Set(LENGTH_KEYS)
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Format one transform value into its CSS func argument. Strings pass through
|
|
191
|
+
* verbatim (explicit units respected); numbers get the key's default unit.
|
|
192
|
+
*/
|
|
193
|
+
const formatTransformValue = (key: string, value: unknown): string => {
|
|
194
|
+
if (Predicate.isString(value)) return value
|
|
195
|
+
if (Predicate.isNumber(value)) return `${value}${unitOf(key)}`
|
|
196
|
+
return String(value)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** A transform key's default unit; none for a key the table doesn't name. */
|
|
200
|
+
const unitOf = (key: string): TransformUnit => {
|
|
201
|
+
const unit: TransformUnit | undefined = TRANSFORM_UNITS[key]
|
|
202
|
+
if (Predicate.isUndefined(unit)) return ""
|
|
203
|
+
return unit
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* The unit {@link project} draws a bare number of `key` in: a transform key's (`px`, `deg`, or none
|
|
208
|
+
* for a scale), `px` for a length key, none for the rest. The Appear sampler judges its stops in it
|
|
209
|
+
* (#48, ADR-0044).
|
|
210
|
+
*/
|
|
211
|
+
export const drawnUnitOf = (key: string): TransformUnit =>
|
|
212
|
+
Bool.match(LENGTH_KEY_SET.has(key), { onTrue: (): TransformUnit => "px", onFalse: () => unitOf(key) })
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Format one non-transform value. Strings pass through verbatim; numbers on a
|
|
216
|
+
* known length key get `px`; every other number stays raw (unitless).
|
|
217
|
+
*/
|
|
218
|
+
const formatStyleValue = (key: string, value: unknown): unknown => {
|
|
219
|
+
if (Predicate.isNumber(value) && LENGTH_KEY_SET.has(key)) return `${value}px`
|
|
220
|
+
return value
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* User-animated transform funcs woven INTO a projection transform (ADR-0009,
|
|
225
|
+
* Motion's `buildProjectionTransform`): `rotate`/`skew` compose with a layout
|
|
226
|
+
* projection and are emitted between the inverse-tree-scale and the element
|
|
227
|
+
* scale, each left out at its identity, 0 (ADR-0009 *Amendment (#57)*). The element's own
|
|
228
|
+
* translate is added to the projection's ({@link ownTranslate}, #363), its own
|
|
229
|
+
* `scale`/`scaleX`/`scaleY` multiply the element scale ({@link ownScale}), and `z` is folded
|
|
230
|
+
* into the projection's `translate3d` (handled inline).
|
|
231
|
+
*/
|
|
232
|
+
const PROJECTION_WOVEN: ReadonlyArray<{ readonly key: string; readonly fn: string }> = [
|
|
233
|
+
{ key: "rotate", fn: "rotate" },
|
|
234
|
+
{ key: "pathRotation", fn: "rotate" },
|
|
235
|
+
{ key: "rotateX", fn: "rotateX" },
|
|
236
|
+
{ key: "rotateY", fn: "rotateY" },
|
|
237
|
+
{ key: "rotateZ", fn: "rotateZ" },
|
|
238
|
+
{ key: "skewX", fn: "skewX" },
|
|
239
|
+
{ key: "skewY", fn: "skewY" },
|
|
240
|
+
]
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* The element's own scale on one axis (ADR-0009 *Amendment (Reorder)*): `scale` times
|
|
244
|
+
* `scaleX` (or `scaleY`), 1 for an absent or non-numeric value. Motion renders a
|
|
245
|
+
* projected node with the delta to its target with the element's own transforms
|
|
246
|
+
* applied (`transformBox(targetWithTransforms, latestValues)`,
|
|
247
|
+
* create-projection-node.ts:1820-1845 at 16c61222c4166bbef5ff6ab0051192faa79fa8c8);
|
|
248
|
+
* scaled about the box centre, that multiplies the element scale by this factor and
|
|
249
|
+
* leaves the translate alone (delta-apply.ts:18-30). So a `whileDrag: { scale }` on a
|
|
250
|
+
* `layout` element shows, and its children scale with it through CSS, as in Motion.
|
|
251
|
+
*/
|
|
252
|
+
const ownScale = (latest: Record<string, unknown>, axisKey: "scaleX" | "scaleY"): number =>
|
|
253
|
+
numberOr(latest.scale, 1) * numberOr(latest[axisKey], 1)
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* A translate key's pixels on a projected element (#363): a number, or a px string. Another unit
|
|
257
|
+
* (`%`, `rem`) can't be added to the projection's pixel translate without measuring, so it's 0 here,
|
|
258
|
+
* and {@link otherUnitTranslate} draws it as CSS (#594). Per frame.
|
|
259
|
+
*/
|
|
260
|
+
const pxOf = (value: unknown): number => {
|
|
261
|
+
if (Predicate.isNumber(value)) return value
|
|
262
|
+
if (!Predicate.isString(value)) return 0
|
|
263
|
+
if (!value.endsWith("px")) return 0
|
|
264
|
+
const px = Number.parseFloat(value)
|
|
265
|
+
if (Number.isFinite(px)) return px
|
|
266
|
+
return 0
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* The element's own translate on one axis (#363, ADR-0009 *Amendment (#363)*): `x` plus
|
|
271
|
+
* `translateX` (or `y` plus `translateY`), in page pixels. Motion lays a layout element's own `x`
|
|
272
|
+
* and `y` on its target box (`transformBox(targetWithTransforms, latestValues)`,
|
|
273
|
+
* `applyTransformsToTarget` at 16c6122), so its delta carries them, divided by the tree scale where
|
|
274
|
+
* it's drawn: this is added to the projection's translate the same way. Motion leaves
|
|
275
|
+
* `translateX`/`translateY` out; nothing set on a layout element is dropped here.
|
|
276
|
+
*/
|
|
277
|
+
const ownTranslate = (latest: Record<string, unknown>, key: "x" | "y", translateKey: "translateX" | "translateY"): number => pxOf(latest[key]) + pxOf(latest[translateKey])
|
|
278
|
+
|
|
279
|
+
/** A length in another unit than px: `-100%`, `1.5rem`, `50vw`. */
|
|
280
|
+
const OTHER_UNIT = /^-?[\d.]+(?!px$)[a-z%]+$/i
|
|
281
|
+
|
|
282
|
+
/** A translate key's CSS when it's a length in another unit than px (#594), else `0px`: {@link pxOf} reads the rest. */
|
|
283
|
+
const otherUnitOf = (value: unknown): string => {
|
|
284
|
+
if (Predicate.isString(value) && OTHER_UNIT.test(value)) return value
|
|
285
|
+
return "0px"
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* One axis pair's CSS translate when either is in another unit than px (#594), else "". The browser
|
|
290
|
+
* resolves it against the element's own box, as it would on a plain element.
|
|
291
|
+
*/
|
|
292
|
+
const otherUnitTranslate = (x: unknown, y: unknown): string => {
|
|
293
|
+
const cssX = otherUnitOf(x)
|
|
294
|
+
const cssY = otherUnitOf(y)
|
|
295
|
+
if (cssX === "0px" && cssY === "0px") return ""
|
|
296
|
+
return `translate(${cssX}, ${cssY})`
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* An element's own move, from its values: what the renderer draws on top of its projection. A binding
|
|
301
|
+
* hands it to the element's layout node, so a twin, a handover and a follower start from, and are drawn,
|
|
302
|
+
* where it's drawn.
|
|
303
|
+
*/
|
|
304
|
+
// `ownTranslate`; #363. Tier 2, for binding authors.
|
|
305
|
+
export const ownTranslateOf = (values: Readonly<Record<string, unknown>>): Point.Point => ({
|
|
306
|
+
x: ownTranslate(values, "x", "translateX"),
|
|
307
|
+
y: ownTranslate(values, "y", "translateY"),
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* An element's own scale on each axis, from its values: what the renderer draws on top of its projection.
|
|
312
|
+
* A binding hands it to the element's layout node, so a handover starts from where a twin is drawn, and a
|
|
313
|
+
* follower is drawn where its lead is.
|
|
314
|
+
*/
|
|
315
|
+
// `ownScale`; ADR-0010 *Amendment (#8)*. Tier 2, for binding authors.
|
|
316
|
+
export const ownScaleOf = (values: Readonly<Record<string, unknown>>): Point.Point => ({
|
|
317
|
+
x: ownScale(values, "scaleX"),
|
|
318
|
+
y: ownScale(values, "scaleY"),
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* An element's own `rotate`, in degrees, from its values: what the renderer weaves into its projection,
|
|
323
|
+
* 0 when it's absent or not a number. A binding hands it to the element's layout node, so a handover
|
|
324
|
+
* blends from a twin's rotation into its own.
|
|
325
|
+
*/
|
|
326
|
+
// `ownRotate`; ADR-0010 *Amendment (#166)*. Tier 2, for binding authors.
|
|
327
|
+
export const ownRotateOf = (values: Readonly<Record<string, unknown>>): number => numberOr(values.rotate, 0)
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* An element's own opacity, from its values: what the renderer draws, 1 when it's absent or not a
|
|
331
|
+
* number. A binding hands it to the element's layout node, so a handover blends from the leaving
|
|
332
|
+
* element's opacity into it.
|
|
333
|
+
*/
|
|
334
|
+
// `ownOpacity`; a Switch (ADR-0010 *Amendment (#166)*). Tier 2, for binding authors.
|
|
335
|
+
export const ownOpacityOf = (values: Readonly<Record<string, unknown>>): number => numberOr(values.opacity, 1)
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Assemble a projection transform string (ADR-0009), ported from Motion's
|
|
339
|
+
* `buildProjectionTransform`: `translate3d` (translation divided by `treeScale`)
|
|
340
|
+
* → inverse tree-scale `scale(1/treeScale)` → woven user `rotate`/`skew` (from
|
|
341
|
+
* `latest`, the element's value-map) → element `scale(delta.scale × treeScale)`.
|
|
342
|
+
* Each term is emitted only when it is non-identity; with nothing to emit the
|
|
343
|
+
* result is `"none"` (an identity projection carrying no woven transform).
|
|
344
|
+
*/
|
|
345
|
+
const buildProjectionTransform = (
|
|
346
|
+
delta: Projection.Delta,
|
|
347
|
+
treeScale: Point.Point,
|
|
348
|
+
latest: Readonly<Record<string, unknown>>,
|
|
349
|
+
rotate: number | undefined,
|
|
350
|
+
): string => {
|
|
351
|
+
const funcs: Array<string> = []
|
|
352
|
+
|
|
353
|
+
// The element's own translate, in page pixels, on the projection's: divided by the tree scale
|
|
354
|
+
// together, as Motion's target box carries it (#363).
|
|
355
|
+
const tx = (delta.x.translate + ownTranslate(latest, "x", "translateX")) / treeScale.x
|
|
356
|
+
const ty = (delta.y.translate + ownTranslate(latest, "y", "translateY")) / treeScale.y
|
|
357
|
+
const z = latest["z"]
|
|
358
|
+
const translateZ = latest["translateZ"]
|
|
359
|
+
if (tx !== 0 || ty !== 0 || (Predicate.isNotUndefined(z) && !isIdentity(z, 0)) || (Predicate.isNotUndefined(translateZ) && !isIdentity(translateZ, 0))) {
|
|
360
|
+
funcs.push(`translate3d(${tx}px, ${ty}px, ${zArgOf(z, translateZ)})`)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (treeScale.x !== 1 || treeScale.y !== 1) {
|
|
364
|
+
funcs.push(`scale(${1 / treeScale.x}, ${1 / treeScale.y})`)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
for (const { key, fn } of PROJECTION_WOVEN) {
|
|
368
|
+
// A handover's blended rotation stands in for the element's own (ADR-0010, *Amendment (#166)*).
|
|
369
|
+
if (key === "rotate" && Predicate.isNotUndefined(rotate)) {
|
|
370
|
+
if (rotate !== 0) funcs.push(`rotate(${formatTransformValue(key, rotate)})`)
|
|
371
|
+
continue
|
|
372
|
+
}
|
|
373
|
+
if (Object.hasOwn(latest, key) && !isIdentity(latest[key], 0)) funcs.push(`${fn}(${formatTransformValue(key, latest[key])})`)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const sx = delta.x.scale * treeScale.x * ownScale(latest, "scaleX")
|
|
377
|
+
const sy = delta.y.scale * treeScale.y * ownScale(latest, "scaleY")
|
|
378
|
+
if (sx !== 1 || sy !== 1) funcs.push(`scale(${sx}, ${sy})`)
|
|
379
|
+
|
|
380
|
+
// An own translate in another unit than px (`x: "-100%"`) can't be added to the projection's pixels,
|
|
381
|
+
// so it's drawn before them, and the browser resolves it against the element's own box (#594). The
|
|
382
|
+
// projection's own math (a handover's start) counts it as 0.
|
|
383
|
+
const ownByUnit = otherUnitTranslate(latest["x"], latest["y"])
|
|
384
|
+
const translateByUnit = otherUnitTranslate(latest["translateX"], latest["translateY"])
|
|
385
|
+
if (translateByUnit !== "") funcs.unshift(translateByUnit)
|
|
386
|
+
if (ownByUnit !== "") funcs.unshift(ownByUnit)
|
|
387
|
+
if (funcs.length === 0) return "none"
|
|
388
|
+
return funcs.join(" ")
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The key an element's transform template rides under in its values, as its projection rides under
|
|
393
|
+
* `Projection.KEY`: `project` calls it for the element's transform. Namespaced, `@transformTemplate`, so
|
|
394
|
+
* it never collides with a CSS property or a value's key.
|
|
395
|
+
*/
|
|
396
|
+
// A **Transform template** (#443, Motion's `transformTemplate`). Tier 2, for binding authors.
|
|
397
|
+
export const TEMPLATE_KEY = "@transformTemplate"
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* A transform template, as Motion's `transformTemplate`: the element's whole transform, from its values
|
|
401
|
+
* and the transform Wardic would draw (`generated`). `project` calls it:
|
|
402
|
+
* - **at a plain render**, with its transform keys in their units (`x: "10px"`, `rotate: "45deg"`,
|
|
403
|
+
* `scale: 1.5`), and `generated` `""` when every one is at its identity, or it has none;
|
|
404
|
+
* - **while a projection moves it**, with its values as they are (`x: 10`) and the projection's
|
|
405
|
+
* transform; once nothing moves it, as at a plain render;
|
|
406
|
+
* - **held for a measure**, with its values and `""` (`heldTransformOf`).
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* const centred: DomRenderer.TransformTemplate = (_values, generated) => `translateX(-50%) ${generated}`
|
|
410
|
+
*/
|
|
411
|
+
// A **Transform template** (#443).
|
|
412
|
+
export type TransformTemplate = (values: Readonly<Record<string, string | number>>, generated: string) => string
|
|
413
|
+
|
|
414
|
+
/** `values`' template, or `undefined`: a function under {@link TEMPLATE_KEY}. Per frame: an early return. */
|
|
415
|
+
const templateOf = (values: Record<string, unknown>): TransformTemplate | undefined => {
|
|
416
|
+
const template = values[TEMPLATE_KEY]
|
|
417
|
+
if (Predicate.isFunction(template)) return template as TransformTemplate
|
|
418
|
+
return undefined
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* An element's transform keys as a template takes them at a plain render (#443, Motion's
|
|
423
|
+
* `buildTransform`): each one it has, at its identity too, a number in its unit (`x: "10px"`,
|
|
424
|
+
* `rotate: "45deg"`) and a scale's as it is (`1.5`). Not `pathRotation`, which Motion adds to the
|
|
425
|
+
* transform alone (#438). A templated element's, per frame.
|
|
426
|
+
*/
|
|
427
|
+
const templateValuesOf = (values: Record<string, unknown>): Record<string, string | number> => {
|
|
428
|
+
const out: Record<string, string | number> = {}
|
|
429
|
+
for (const { key } of TRANSFORM_FUNCS) {
|
|
430
|
+
if (key === "pathRotation" || !Object.hasOwn(values, key)) continue
|
|
431
|
+
const value = values[key]
|
|
432
|
+
out[key] = templateValueOf(key, value)
|
|
433
|
+
}
|
|
434
|
+
return out
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** One transform value as a template takes it: a number in its key's unit, a unitless one as it is, a string as it is. */
|
|
438
|
+
const templateValueOf = (key: string, value: unknown): string | number => {
|
|
439
|
+
if (Predicate.isString(value)) return value
|
|
440
|
+
if (!Predicate.isNumber(value)) return String(value)
|
|
441
|
+
const unit = unitOf(key)
|
|
442
|
+
if (unit === "") return value
|
|
443
|
+
return `${value}${unit}`
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* An element's values as a template takes them while projected or held (#443, Motion's
|
|
448
|
+
* `latestValues`): every one as it is, but the reserved keys. A templated element's, per frame.
|
|
449
|
+
*/
|
|
450
|
+
const rawValuesOf = (values: Record<string, unknown>): Record<string, string | number> => {
|
|
451
|
+
const out: Record<string, string | number> = {}
|
|
452
|
+
for (const key in values) {
|
|
453
|
+
if (key === Projection.KEY || key === TEMPLATE_KEY) continue
|
|
454
|
+
out[key] = values[key] as string | number
|
|
455
|
+
}
|
|
456
|
+
return out
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Whether a projection moves nothing (#443): no delta, no tree scale, no handover's rotation. A
|
|
461
|
+
* templated element it draws is drawn as at a plain render then, as Motion's is once its projection
|
|
462
|
+
* ends (`transformTemplate({}, "")` with no transform key), so a template is never handed `none`.
|
|
463
|
+
*/
|
|
464
|
+
const projectsNothing = (projection: Projection.Value): boolean => {
|
|
465
|
+
const { delta, treeScale } = projection
|
|
466
|
+
if (delta.x.translate !== 0 || delta.y.translate !== 0) return false
|
|
467
|
+
if (delta.x.scale !== 1 || delta.y.scale !== 1) return false
|
|
468
|
+
if (treeScale.x !== 1 || treeScale.y !== 1) return false
|
|
469
|
+
return Predicate.isUndefined(projection.rotate)
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* The transform keys a plain render draws (ADR-0023): each one present and not at its identity, in
|
|
474
|
+
* the canonical order; `""` when every one present is at its identity; `undefined` with none. Built
|
|
475
|
+
* by concatenation, not an array joined (ADR-0034): this runs per element per frame.
|
|
476
|
+
*/
|
|
477
|
+
const plainTransformOf = (values: Record<string, unknown>): string | undefined => {
|
|
478
|
+
let transform = ""
|
|
479
|
+
let separator = ""
|
|
480
|
+
let present = false
|
|
481
|
+
for (const { key, fn, identity } of TRANSFORM_FUNCS) {
|
|
482
|
+
if (!Object.hasOwn(values, key)) continue
|
|
483
|
+
present = true
|
|
484
|
+
const value = values[key]
|
|
485
|
+
if (isIdentity(value, identity)) continue
|
|
486
|
+
transform = `${transform}${separator}${fn}(${formatTransformValue(key, value)})`
|
|
487
|
+
separator = " "
|
|
488
|
+
}
|
|
489
|
+
if (!present) return undefined
|
|
490
|
+
return transform
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* The transform an element draws at a plain render, or `undefined` to leave its own: its template's,
|
|
495
|
+
* called with no transform key too, as Motion's is (#443); else its keys', `none` when every one is
|
|
496
|
+
* at its identity (ADR-0023), so a class's transform stays overridden and its last one doesn't stay
|
|
497
|
+
* inline. Per frame: early returns.
|
|
498
|
+
*/
|
|
499
|
+
const drawnTransformOf = (values: Record<string, unknown>): string | undefined => {
|
|
500
|
+
const template = templateOf(values)
|
|
501
|
+
if (Predicate.isNotUndefined(template)) return templatedPlain(template, values)
|
|
502
|
+
const transform = plainTransformOf(values)
|
|
503
|
+
if (transform === "") return "none"
|
|
504
|
+
return transform
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* A templated element's transform at a plain render (#443): its template's, from its keys and the
|
|
509
|
+
* transform Wardic would draw, `""` when that's nothing.
|
|
510
|
+
*/
|
|
511
|
+
const templatedPlain = (template: TransformTemplate, values: Record<string, unknown>): string => {
|
|
512
|
+
const generated = plainTransformOf(values)
|
|
513
|
+
if (Predicate.isUndefined(generated)) return template(templateValuesOf(values), "")
|
|
514
|
+
return template(templateValuesOf(values), generated)
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* The transform a projected element draws (ADR-0009): its projection's, through its template while
|
|
519
|
+
* the projection moves it, and as at a plain render once nothing does (#443). Per frame: early returns.
|
|
520
|
+
*/
|
|
521
|
+
const projectedTransformOf = (projection: Projection.Value, values: Record<string, unknown>): string => {
|
|
522
|
+
const template = templateOf(values)
|
|
523
|
+
if (Predicate.isUndefined(template)) return buildProjectionTransform(projection.delta, projection.treeScale, values, projection.rotate)
|
|
524
|
+
if (projectsNothing(projection)) return templatedPlain(template, values)
|
|
525
|
+
return template(rawValuesOf(values), buildProjectionTransform(projection.delta, projection.treeScale, values, projection.rotate))
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* The transform a measure holds an element at, as Motion's `transformTemplate(values, "")`: its
|
|
530
|
+
* template's, with no transform of Wardic's, so a static part of it, centring say, is in the box
|
|
531
|
+
* measured. `""` for an element with none, which the measure clears it to.
|
|
532
|
+
*/
|
|
533
|
+
// A Measure pass (#443). Tier 2, for binding authors.
|
|
534
|
+
export const heldTransformOf = (values: Readonly<Record<string, unknown>>): string => {
|
|
535
|
+
const template = templateOf(values as Record<string, unknown>)
|
|
536
|
+
if (Predicate.isUndefined(template)) return ""
|
|
537
|
+
return template(rawValuesOf(values as Record<string, unknown>), "")
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** `translate3d`'s z argument: the element's own `z`, plus its `translateZ` in pixels when it has one (#363), else none. */
|
|
541
|
+
const zArgOf = (z: unknown, translateZ: unknown): string => {
|
|
542
|
+
if (Predicate.isNotUndefined(translateZ)) return `${pxOf(z) + pxOf(translateZ)}px`
|
|
543
|
+
if (Predicate.isUndefined(z)) return "0px"
|
|
544
|
+
return formatTransformValue("z", z)
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* The opacity a projection draws, or `undefined` to leave the user's own (ADR-0010): a Switch's
|
|
549
|
+
* blended opacity as it is (*Amendment (#166)*), else the crossfade factor times the user's
|
|
550
|
+
* opacity. Per frame: early returns.
|
|
551
|
+
*/
|
|
552
|
+
const projectedOpacity = (projection: Projection.Value, values: Record<string, unknown>): number | undefined => {
|
|
553
|
+
if (Predicate.isNotUndefined(projection.blendOpacity)) return projection.blendOpacity
|
|
554
|
+
if (Predicate.isUndefined(projection.opacity)) return undefined
|
|
555
|
+
return numberOr(values.opacity, 1) * projection.opacity
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* A Frame's values as a DOM style patch, pure. The transform keys become one `transform`, each left out
|
|
560
|
+
* at its identity, and `none` when every one is; with no transform key there's no `transform`, so a
|
|
561
|
+
* class's shows. A node's projection, when its values carry one under `Projection.KEY`, becomes its
|
|
562
|
+
* transform instead, with the element's own move, rotation, skew and scale drawn on it. Every other key is
|
|
563
|
+
* copied across, a bare number on a length in `px`. A binding's server render, which builds no runtime,
|
|
564
|
+
* projects with it directly.
|
|
565
|
+
*
|
|
566
|
+
* @example
|
|
567
|
+
* DomRenderer.project({ x: 0, scale: 1, width: 200 }) // { transform: "none", width: "200px" }
|
|
568
|
+
*/
|
|
569
|
+
// Two modes. PROJECTION (ADR-0009): the transform is assembled by `buildProjectionTransform` from the
|
|
570
|
+
// node's `delta`/`treeScale`, adding the element's own translate (`x`, `y`, `translate*`, #363), weaving
|
|
571
|
+
// its own animated `rotate`/`skew` and multiplying in its own `scale`/`scaleX`/`scaleY`. NORMAL: the
|
|
572
|
+
// value-spine path, `none` at every identity (ADR-0023). The same function `RendererDomLive` provides
|
|
573
|
+
// (ADR-0021, decision 2). Tier 2, for binding authors.
|
|
574
|
+
export const project = (values: Readonly<Record<string, unknown>>): Renderer.Patch => {
|
|
575
|
+
const patch: Renderer.Patch = {}
|
|
576
|
+
|
|
577
|
+
const projection = values[Projection.KEY] as Projection.Value | undefined
|
|
578
|
+
if (Predicate.isNotUndefined(projection)) {
|
|
579
|
+
// Projection owns the transform; weave this element's rotate/skew from `values`, through its
|
|
580
|
+
// Transform template when it has one (#443).
|
|
581
|
+
patch.transform = projectedTransformOf(projection, values)
|
|
582
|
+
// Copy non-transform style keys (transform keys are consumed by the weave /
|
|
583
|
+
// owned by the projection; the reserved keys never render).
|
|
584
|
+
for (const key in values) {
|
|
585
|
+
if (key === Projection.KEY || key === TEMPLATE_KEY || TRANSFORM_KEYS.has(key)) continue
|
|
586
|
+
patch[key] = formatStyleValue(key, values[key])
|
|
587
|
+
}
|
|
588
|
+
// Shared-element crossfade (ADR-0010): compose the crossfade factor with the
|
|
589
|
+
// user's opacity — `finalOpacity = (userOpacity ?? 1) × factor`. Pure: both
|
|
590
|
+
// values are already in the value-map (like buildProjectionTransform above).
|
|
591
|
+
// A Switch's blended opacity is drawn as it is (ADR-0010, *Amendment (#166)*).
|
|
592
|
+
const opacity = projectedOpacity(projection, values)
|
|
593
|
+
if (Predicate.isNotUndefined(opacity)) patch.opacity = opacity
|
|
594
|
+
// A dragged node's raise (ADR-0016 *Amendment (Reorder)*): kebab-case, as the
|
|
595
|
+
// scale correctors' keys are, so the commit's `setProperty` takes it.
|
|
596
|
+
if (Predicate.isNotUndefined(projection.raise)) patch["z-index"] = projection.raise
|
|
597
|
+
// A Follower takes no pointer input (`Projection`'s `passesPointer`, ADR-0010, *Amendment
|
|
598
|
+
// (#165)*; Motion's): `pointer-events: none`. A Lead's write leaves the key out, and the gate
|
|
599
|
+
// gives the element its own value back.
|
|
600
|
+
if (projection.passesPointer === true) patch["pointer-events"] = "none"
|
|
601
|
+
return patch
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Grouped transform: the present keys in the canonical order, through the Transform template
|
|
605
|
+
// when the element has one (#443).
|
|
606
|
+
const transform = drawnTransformOf(values)
|
|
607
|
+
if (Predicate.isNotUndefined(transform)) patch.transform = transform
|
|
608
|
+
|
|
609
|
+
// Non-transform keys: copy across with default-unit handling.
|
|
610
|
+
for (const key in values) {
|
|
611
|
+
if (key === TEMPLATE_KEY || TRANSFORM_KEYS.has(key)) continue
|
|
612
|
+
patch[key] = formatStyleValue(key, values[key])
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return patch
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Imperative: write a `Patch` onto `target`, now. The engine treats `target` as
|
|
620
|
+
* opaque; the DOM renderer narrows it to an `HTMLElement`/`SVGElement`. CSS keys go
|
|
621
|
+
* through `setProperty` (dash-cased so `transform-box` etc. apply — `el.style[kebab]=`
|
|
622
|
+
* silently fails). An svg-inner element's patch is routed first (ADR-0014,
|
|
623
|
+
* {@link splitPatch}): its attribute keys via `setAttribute`.
|
|
624
|
+
*
|
|
625
|
+
* On the per-frame path (ADR-0034): an HTML element's patch is written as it is, with no
|
|
626
|
+
* routing objects, and each key's dash-cased name is cached. Quiet — no span/log.
|
|
627
|
+
*/
|
|
628
|
+
const commit = (target: unknown, patch: Renderer.Patch): void => {
|
|
629
|
+
const el = target as Element & { style: CSSStyleDeclaration }
|
|
630
|
+
if (isSvgInner(el)) {
|
|
631
|
+
const { style, attrs } = splitPatch(el, patch)
|
|
632
|
+
writeStyle(el.style, style)
|
|
633
|
+
setSvgAttrs(el, attrs)
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
writeStyle(el.style, patch)
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
/** Each key of `patch` onto `style`, through `setProperty`. */
|
|
640
|
+
const writeStyle = (style: CSSStyleDeclaration, patch: Record<string, unknown>): void => {
|
|
641
|
+
for (const key in patch) style.setProperty(dashCase(key), String(patch[key]))
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Core's `Renderer` for the DOM: `project` and a `commit` that writes a patch onto an element's style. It needs nothing. */
|
|
645
|
+
// `project` and `commit` have no service dependencies, so this is a plain `Layer.succeed`.
|
|
646
|
+
export const RendererDomLive: Layer.Layer<Renderer.Renderer> = Layer.succeed(Renderer.Renderer, { project, commit })
|