@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,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Starting style (ADR-0003, ADR-0021): an element's style before its first Frame,
|
|
3
|
+
* `project(resolveInitial(config))`, computed purely with no runtime. It is the server's
|
|
4
|
+
* markup, the style the hydration render keeps, and the animation's `from`. And the style
|
|
5
|
+
* after the first Frame, the fallback: the user's, without the keys Wardic holds (ADR-0034).
|
|
6
|
+
*
|
|
7
|
+
* Framework-free, so every binding renders it the same way on a server and on the client
|
|
8
|
+
* (the thin-bindings rule): a binding reads its config, calls these, and casts the style to
|
|
9
|
+
* its own JSX type. `toValues`, `resolveInitial` and the merge `over` does moved here from
|
|
10
|
+
* @wardic/solid with their behaviour unchanged; `over` and `without` are `dual` (#132's PR 3,
|
|
11
|
+
* ADR-0041 decision 5). Tier 2, for binding authors.
|
|
12
|
+
*
|
|
13
|
+
* Observability: pure and per element, so quiet (no span, no log).
|
|
14
|
+
*/
|
|
15
|
+
import type * as Values from "@wardic/core/Values"
|
|
16
|
+
import * as Arr from "effect/Array"
|
|
17
|
+
import * as Bool from "effect/Boolean"
|
|
18
|
+
import { dual } from "effect/Function"
|
|
19
|
+
import * as Match from "effect/Match"
|
|
20
|
+
import * as Option from "effect/Option"
|
|
21
|
+
import * as Predicate from "effect/Predicate"
|
|
22
|
+
import * as Rec from "effect/Record"
|
|
23
|
+
import * as UndefinedOr from "effect/UndefinedOr"
|
|
24
|
+
import { project } from "./renderer.ts"
|
|
25
|
+
import { dashCase, type SplitPatch, splitPatchForTag } from "./svg.ts"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A values map with only the keys an animation can take: numbers and strings (custom
|
|
29
|
+
* properties included). `undefined` and anything else drop out, so the engine never sees
|
|
30
|
+
* them.
|
|
31
|
+
*/
|
|
32
|
+
export const toValues = (v: object | undefined): Values.Values =>
|
|
33
|
+
UndefinedOr.match(v, {
|
|
34
|
+
onUndefined: (): Values.Values => ({}),
|
|
35
|
+
onDefined: (values) => Rec.filter(values as Readonly<Record<string, unknown>>, isAnimatable),
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
/** Whether a value is one an animation can take: a number or a string. */
|
|
39
|
+
const isAnimatable = Predicate.or(Predicate.isNumber, Predicate.isString)
|
|
40
|
+
|
|
41
|
+
/** A keyframes value's stops, as Motion's: each a value, or `null`, for where the key is (the first) or the stop before (a later one). */
|
|
42
|
+
// **Keyframes** (#429).
|
|
43
|
+
export type Stops = ReadonlyArray<number | string | null>
|
|
44
|
+
|
|
45
|
+
/** Where a keyframes value is read: `initial`'s at its first stop, a target's at its last. */
|
|
46
|
+
// **Keyframes** (#429).
|
|
47
|
+
export type StopAt = "first" | "last"
|
|
48
|
+
|
|
49
|
+
/** Whether a stop is one an animation can take, or `null`. */
|
|
50
|
+
const isStop = Predicate.or(isAnimatable, Predicate.isNull)
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A Keyframes value's stop (#429): its first, as Motion's `initial` reads it (`use-visual-state.ts`),
|
|
54
|
+
* or its last that isn't `null`, where it rests (Motion's final keyframe, `fill-wildcards.ts`'s).
|
|
55
|
+
* `null` or `undefined` when it has none.
|
|
56
|
+
*/
|
|
57
|
+
const stopOf = (stops: ReadonlyArray<unknown>, at: StopAt): unknown =>
|
|
58
|
+
Bool.match(at === "first", { onTrue: () => stops[0], onFalse: () => Option.getOrUndefined(Arr.findLast(stops, Predicate.isNotNull)) })
|
|
59
|
+
|
|
60
|
+
/** Whether a value is Keyframes an animation can take (#429): an array of stops, one of them not `null`. */
|
|
61
|
+
const isStops = (value: unknown): value is Stops => {
|
|
62
|
+
if (Bool.not(Arr.isArray(value))) return false
|
|
63
|
+
const stops = value as ReadonlyArray<unknown>
|
|
64
|
+
return Bool.and(Arr.every(stops, isStop), Arr.some(stops, Predicate.isNotNull))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The Keyframes {@link keepAnimatable} set aside while it filtered a values map (#452): written by it,
|
|
69
|
+
* read and cleared right after, never across a call. A target with none leaves it `undefined`.
|
|
70
|
+
*/
|
|
71
|
+
// Module state (#460, Q7): scratch within one synchronous call, never held across one.
|
|
72
|
+
let setAside: Record<string, Stops> | undefined
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A values map's filter (#452): a value an animation can take stays, and Keyframes go aside, into
|
|
76
|
+
* {@link setAside}, so one pass over the keys finds both. Built once, at module load.
|
|
77
|
+
*/
|
|
78
|
+
const keepAnimatable = (value: unknown, key: string): boolean => {
|
|
79
|
+
if (Bool.not(Arr.isArray(value))) return isAnimatable(value)
|
|
80
|
+
if (isStops(value)) {
|
|
81
|
+
if (Predicate.isUndefined(setAside)) setAside = {}
|
|
82
|
+
setAside[key] = value
|
|
83
|
+
}
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** A values map's numbers and strings, its Keyframes set aside (#452, {@link keepAnimatable}): {@link toValues}' one pass. */
|
|
88
|
+
const filtered = (v: object | undefined): Values.Values => {
|
|
89
|
+
// Per element at mount (#540): statements and a loop.
|
|
90
|
+
setAside = undefined
|
|
91
|
+
const out: Record<string, number | string> = {}
|
|
92
|
+
if (Predicate.isUndefined(v)) return out
|
|
93
|
+
const values = v as Readonly<Record<string, unknown>>
|
|
94
|
+
for (const key of Object.keys(values)) {
|
|
95
|
+
const value = values[key]
|
|
96
|
+
// `keepAnimatable` keeps numbers and strings alone, as `isAnimatable` does.
|
|
97
|
+
if (keepAnimatable(value, key)) out[key] = value as number | string
|
|
98
|
+
}
|
|
99
|
+
return out
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The Keyframes the last {@link filtered} set aside, taken: `undefined` with none. */
|
|
103
|
+
const takeSetAside = (): Readonly<Record<string, Stops>> | undefined => {
|
|
104
|
+
const taken = setAside
|
|
105
|
+
setAside = undefined
|
|
106
|
+
return taken
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** `values` with each of `keyframes` at one of its stops (#429), one with no such stop left out. */
|
|
110
|
+
const withStops = (values: Values.Values, keyframes: Readonly<Record<string, Stops>>, at: StopAt): Values.Values => ({
|
|
111
|
+
...values,
|
|
112
|
+
...toValues(Rec.map(keyframes, (stops) => stopOf(stops, at))),
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
/** A values map's own values and its keyframes: `splitValues`' result. */
|
|
116
|
+
// #452.
|
|
117
|
+
export interface SplitValues {
|
|
118
|
+
/** Its numbers and strings, each Keyframes value at one of its stops. */
|
|
119
|
+
readonly values: Values.Values
|
|
120
|
+
/** Its Keyframes, if it has any. */
|
|
121
|
+
// An `Option` (#618, A1): none is Effect's one `None`, so a target with no Keyframes allocates nothing.
|
|
122
|
+
readonly keyframes: Option.Option<Readonly<Record<string, Stops>>>
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A values map's values, each keyframes value read at one of its stops, and its keyframes, in one pass
|
|
127
|
+
* over its keys: a target's resolution reads both, and a target with no keyframes makes nothing
|
|
128
|
+
* `toValues` doesn't.
|
|
129
|
+
*/
|
|
130
|
+
// **Keyframes** (#429); one pass (#452).
|
|
131
|
+
export const splitValues = (v: object | undefined, at: StopAt): SplitValues => {
|
|
132
|
+
const values = filtered(v)
|
|
133
|
+
const keyframes = takeSetAside()
|
|
134
|
+
if (Predicate.isUndefined(keyframes)) return { values, keyframes: Option.none() }
|
|
135
|
+
return { values: withStops(values, keyframes, at), keyframes: Option.some(keyframes) }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* A values map with only the keys an animation can take, each keyframes value read at one of its stops:
|
|
140
|
+
* `initial`'s first, a target's last. A keyframes value with no such stop drops out, as does an array
|
|
141
|
+
* `keyframesOf` doesn't take, and anything that isn't a number or a string. One pass over its keys, as
|
|
142
|
+
* `toValues`'.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* Style.valuesAt({ opacity: [0, 0.5, 1], x: 10 }, "first") // { x: 10, opacity: 0 }
|
|
146
|
+
*/
|
|
147
|
+
// **Keyframes** (#429); one pass (#452).
|
|
148
|
+
export const valuesAt = (v: object | undefined, at: StopAt): Values.Values => {
|
|
149
|
+
const values = filtered(v)
|
|
150
|
+
const keyframes = takeSetAside()
|
|
151
|
+
if (Predicate.isUndefined(keyframes)) return values
|
|
152
|
+
return withStops(values, keyframes, at)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* A values map's keyframes values: each array of stops an animation can take, one with a stop that isn't
|
|
157
|
+
* `null`. None when it has none, so a target without any makes nothing.
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* Style.keyframesOf({ opacity: [0, 0.5, 1], x: 10 }) // { opacity: [0, 0.5, 1] }
|
|
161
|
+
*/
|
|
162
|
+
// **Keyframes** (#429).
|
|
163
|
+
export const keyframesOf = (v: object | undefined): Option.Option<Readonly<Record<string, Stops>>> => splitValues(v, "last").keyframes
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* What of an element's config its starting style is read from. Its values are typed `object`, so a
|
|
167
|
+
* binding's own value types fit as they are.
|
|
168
|
+
*/
|
|
169
|
+
export interface InitialConfig {
|
|
170
|
+
/**
|
|
171
|
+
* Its start: values used as they are, the name of a `variants` entry, a list of names merged in order,
|
|
172
|
+
* a later one's winning, or `false`, to start at `animate`, as Motion's `initial={false}`.
|
|
173
|
+
*/
|
|
174
|
+
// #297 (lists); #134 (`false`).
|
|
175
|
+
readonly initial?: object | string | ReadonlyArray<string> | false | undefined
|
|
176
|
+
/** What `initial: false` starts at: a values map, the name of a `variants` entry, or a list of names. */
|
|
177
|
+
readonly animate?: object | string | ReadonlyArray<string> | undefined
|
|
178
|
+
/** Its variants: the values each name stands for. */
|
|
179
|
+
readonly variants?: Readonly<Record<string, object | undefined>> | undefined
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** One values entry, the variants a name in it would name, and the stop its Keyframes values are read at (#429). */
|
|
183
|
+
interface Entry {
|
|
184
|
+
readonly entry: object | string | ReadonlyArray<string> | undefined
|
|
185
|
+
readonly variants: InitialConfig["variants"]
|
|
186
|
+
readonly at: StopAt
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Whether an entry is a list of names (#297): an array, which a values map never is. */
|
|
190
|
+
const isList = (entry: unknown): entry is ReadonlyArray<string> => Arr.isArray(entry)
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* A values map, or the `variants` entry a name names (an unknown name is empty), or nothing.
|
|
194
|
+
* Built once, at module load: every element reads its own at setup (#69).
|
|
195
|
+
*/
|
|
196
|
+
const entryOf: (entry: Entry) => Values.Values = Match.type<Entry>().pipe(
|
|
197
|
+
Match.when({ entry: Match.undefined }, (): Values.Values => ({})),
|
|
198
|
+
Match.when({ entry: Match.string }, ({ entry, variants, at }) => valuesAt(UndefinedOr.map(variants, (byName) => byName[entry]), at)),
|
|
199
|
+
// A list's names merged in order, a later one's winning a key both set (#297, Motion's).
|
|
200
|
+
Match.when({ entry: isList }, ({ entry, variants, at }) =>
|
|
201
|
+
Arr.reduce(entry, {} as Values.Values, (merged, name) => ({ ...merged, ...valuesAt(UndefinedOr.map(variants, (byName) => byName[name]), at) }))),
|
|
202
|
+
Match.orElse(({ entry, at }) => valuesAt(entry as object, at)),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* The values an element starts from: its `initial` values, or the `variants` entries its `initial` names,
|
|
207
|
+
* an unknown name empty, or nothing. With `initial: false`, its `animate`, resolved the same way, so it
|
|
208
|
+
* starts at its target and nothing animates at mount, as Motion's. A keyframes value starts at its first
|
|
209
|
+
* stop, or, with `initial: false`, at `animate`'s last, where the animation it skips would end. Read once,
|
|
210
|
+
* at setup: the starting style is where it starts, never a live target.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* Style.resolveInitial({ initial: false, animate: { opacity: 1 } }) // { opacity: 1 }
|
|
214
|
+
*/
|
|
215
|
+
// #134, Motion's `initial={false}`; **Keyframes** (#429, Motion's `use-visual-state.ts`).
|
|
216
|
+
export const resolveInitial = (config: InitialConfig): Values.Values => {
|
|
217
|
+
// Per element at mount (#540): statements.
|
|
218
|
+
if (config.initial === false) return entryOf({ entry: config.animate, variants: config.variants, at: "last" })
|
|
219
|
+
return entryOf({ entry: config.initial as object | string | ReadonlyArray<string> | undefined, variants: config.variants, at: "first" })
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* A CSS declaration list (`"color: red; background: url(a;b)"`) as `{ property: value }`.
|
|
224
|
+
* A `;` inside parentheses or quotes doesn't end a declaration. Internal (not in the
|
|
225
|
+
* package index): `own-values.ts`'s style lookup shares it.
|
|
226
|
+
*/
|
|
227
|
+
export const parseCssText = (text: string): Record<string, string> => {
|
|
228
|
+
const out: Record<string, string> = {}
|
|
229
|
+
// A counted scan over UTF-16 units, as `slice` counts: a `;` outside parentheses and quotes ends a
|
|
230
|
+
// declaration, and a backslash in a quote escapes the next unit (#618, F6: a reduce built a scan
|
|
231
|
+
// state and a matcher each character, 15-25 times as slow).
|
|
232
|
+
let depth = 0
|
|
233
|
+
let quote = 0
|
|
234
|
+
let escaped = false
|
|
235
|
+
let start = 0
|
|
236
|
+
for (let i = 0; i < text.length; i++) {
|
|
237
|
+
const c = text.charCodeAt(i)
|
|
238
|
+
if (escaped) {
|
|
239
|
+
escaped = false
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
if (quote !== 0) {
|
|
243
|
+
if (c === BACKSLASH) escaped = true
|
|
244
|
+
if (c === quote) quote = 0
|
|
245
|
+
continue
|
|
246
|
+
}
|
|
247
|
+
if (c === DOUBLE_QUOTE || c === SINGLE_QUOTE) quote = c
|
|
248
|
+
if (c === OPEN) depth++
|
|
249
|
+
if (c === CLOSE) depth = Math.max(0, depth - 1)
|
|
250
|
+
if (c === SEMICOLON && depth === 0) {
|
|
251
|
+
takeDeclaration(text, start, i, out)
|
|
252
|
+
start = i + 1
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
takeDeclaration(text, start, text.length, out)
|
|
256
|
+
return out
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const BACKSLASH = 92
|
|
260
|
+
const DOUBLE_QUOTE = 34
|
|
261
|
+
const SINGLE_QUOTE = 39
|
|
262
|
+
const OPEN = 40
|
|
263
|
+
const CLOSE = 41
|
|
264
|
+
const SEMICOLON = 59
|
|
265
|
+
|
|
266
|
+
/** The declaration in `text` from `start` to `end` into `out`: its property and value, trimmed, when it has both. */
|
|
267
|
+
const takeDeclaration = (text: string, start: number, end: number, out: Record<string, string>): void => {
|
|
268
|
+
const declaration = text.slice(start, end)
|
|
269
|
+
const colon = declaration.indexOf(":")
|
|
270
|
+
if (colon <= 0) return
|
|
271
|
+
const key = declaration.slice(0, colon).trim()
|
|
272
|
+
const value = declaration.slice(colon + 1).trim()
|
|
273
|
+
if (key !== "" && value !== "") out[key] = value
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Where {@link parseCssText} is: the parentheses open, the string's quote, a backslash just read in it, the declaration's start. */
|
|
277
|
+
/**
|
|
278
|
+
* A style as a binding hands it over: a record of CSS properties, CSS text (`"color: red"`, which
|
|
279
|
+
* JSX allows), or none.
|
|
280
|
+
*/
|
|
281
|
+
export type StyleInput = object | string | undefined
|
|
282
|
+
|
|
283
|
+
/** A style record: CSS properties to their values. */
|
|
284
|
+
export type StyleRecord = Readonly<Record<string, unknown>>
|
|
285
|
+
|
|
286
|
+
/** A style given as CSS text, parsed; a record as it is. */
|
|
287
|
+
const recordOf = (input: object | string): StyleRecord => {
|
|
288
|
+
// Per element at mount (#540): statements.
|
|
289
|
+
if (Predicate.isString(input)) return parseCssText(input)
|
|
290
|
+
return input as StyleRecord
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** A style given as CSS text, parsed; a record as it is; none, an empty record. */
|
|
294
|
+
const recordOfInput = (input: StyleInput): StyleRecord => {
|
|
295
|
+
// Per element at mount (#540): statements.
|
|
296
|
+
if (Predicate.isUndefined(input)) return {}
|
|
297
|
+
return recordOf(input)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const setsKey =
|
|
301
|
+
(key: string): Predicate.Predicate<StyleRecord> =>
|
|
302
|
+
(style) =>
|
|
303
|
+
Predicate.isNotUndefined(style[key])
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Whether a style sets `user-select` and not `-webkit-user-select`, and the reverse. Chromium has
|
|
307
|
+
* one property under both names, so a value set under one is the value for both.
|
|
308
|
+
*/
|
|
309
|
+
const setsOnlyUserSelect = Predicate.and(setsKey("user-select"), Predicate.not(setsKey("-webkit-user-select")))
|
|
310
|
+
const setsOnlyWebkitUserSelect = Predicate.and(setsKey("-webkit-user-select"), Predicate.not(setsKey("user-select")))
|
|
311
|
+
|
|
312
|
+
/** `self` laid over `that`: both present. */
|
|
313
|
+
const layered = (self: StyleRecord, that: StyleRecord): StyleRecord => {
|
|
314
|
+
const merged: Record<string, unknown> = { ...that, ...self }
|
|
315
|
+
// `self`'s value for either name is the value for both, or `that`'s left on the other wins.
|
|
316
|
+
if (setsOnlyUserSelect(self)) merged["-webkit-user-select"] = self["user-select"]
|
|
317
|
+
if (setsOnlyWebkitUserSelect(self)) merged["user-select"] = self["-webkit-user-select"]
|
|
318
|
+
return merged
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* `self` laid over `that`: `self`'s keys win, and `that`'s others stay. A chain reads from the top layer
|
|
323
|
+
* down: an element's style is its drag defaults, under its base, under the app's, under what Wardic
|
|
324
|
+
* animates. A side given as CSS text is parsed, so a framework never writes `cssText` over the keys
|
|
325
|
+
* Wardic holds, and a side that's `undefined` leaves the other as it is, the same object. Whatever `self`
|
|
326
|
+
* sets for `user-select` or `-webkit-user-select` it sets for both: one property in Chromium.
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* // Before the first commit: the starting style over the app's, over a base and a drag element's defaults.
|
|
330
|
+
* pipe(starting, Style.over(user), Style.over(base), Style.over(Style.dragDefaults("x")))
|
|
331
|
+
*/
|
|
332
|
+
// `dual`, `self` first (ADR-0041, decision 5). The layering: ADR-0019 *Amendment (Reorder)*.
|
|
333
|
+
export const over: {
|
|
334
|
+
(that: StyleInput): (self: StyleInput) => StyleRecord
|
|
335
|
+
(self: StyleInput, that: StyleInput): StyleRecord
|
|
336
|
+
} = dual(2, (self: StyleInput, that: StyleInput): StyleRecord => {
|
|
337
|
+
// Per element at mount (#540): statements.
|
|
338
|
+
if (Predicate.isUndefined(that)) return recordOfInput(self)
|
|
339
|
+
if (Predicate.isUndefined(self)) return recordOf(that)
|
|
340
|
+
return layered(recordOf(self), recordOf(that))
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
/** Whether `key`, a CSS property, is one of the gate's held patch keys: by its name, or its name dash-cased. */
|
|
344
|
+
const isHeld = (held: ReadonlyArray<string>, key: string): boolean => {
|
|
345
|
+
// Per element at mount (#540): a loop.
|
|
346
|
+
for (const patchKey of held) {
|
|
347
|
+
if (patchKey === key || dashCase(patchKey) === key) return true
|
|
348
|
+
}
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* `self` without the keys the flush gate holds on the element (`held`, by patch key, matched by CSS
|
|
354
|
+
* property): the style once its first frame is committed. Wardic writes those while it drives them, and
|
|
355
|
+
* the app's `style` is their fallback, so a key the gate lets go of comes back.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* pipe(user, Style.over(base), Style.without(held))
|
|
359
|
+
*/
|
|
360
|
+
// ADR-0034.
|
|
361
|
+
export const without: {
|
|
362
|
+
(held: ReadonlyArray<string>): (self: StyleInput) => StyleRecord
|
|
363
|
+
(self: StyleInput, held: ReadonlyArray<string>): StyleRecord
|
|
364
|
+
} = dual(2, (self: StyleInput, held: ReadonlyArray<string>): StyleRecord => {
|
|
365
|
+
// Per element at mount (#540): a loop.
|
|
366
|
+
const record = recordOfInput(self)
|
|
367
|
+
const out: Record<string, unknown> = {}
|
|
368
|
+
for (const key of Object.keys(record)) {
|
|
369
|
+
if (!isHeld(held, key)) out[key] = record[key]
|
|
370
|
+
}
|
|
371
|
+
return out
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Whether the keys the flush gate holds, changing from `prev` to `next`, change `self`'s `without`:
|
|
376
|
+
* whether a key taken or let go of is one `self` sets, by its name or its name dash-cased. Most changes
|
|
377
|
+
* touch none, and a binding then leaves its `style` alone.
|
|
378
|
+
*/
|
|
379
|
+
// ADR-0034.
|
|
380
|
+
export const heldTouches: {
|
|
381
|
+
(prev: ReadonlyArray<string>, next: ReadonlyArray<string>): (self: StyleRecord) => boolean
|
|
382
|
+
(self: StyleRecord, prev: ReadonlyArray<string>, next: ReadonlyArray<string>): boolean
|
|
383
|
+
} = dual(3, (self: StyleRecord, prev: ReadonlyArray<string>, next: ReadonlyArray<string>): boolean =>
|
|
384
|
+
Arr.some([...Arr.difference(prev, next), ...Arr.difference(next, prev)], (key) =>
|
|
385
|
+
Arr.some([key, dashCase(key)], (name) => Object.hasOwn(self, name)),
|
|
386
|
+
),
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The starting style of a `tag` element starting from `values`: projected as the renderer projects a
|
|
391
|
+
* Frame, then split by tag, an SVG element's keys inside an `<svg>` as attributes, and the rest as style.
|
|
392
|
+
* `undefined` is an HTML element, as for a spread that doesn't know its tag.
|
|
393
|
+
*/
|
|
394
|
+
// The Starting style; routed by tag (ADR-0014). The spread: a `createWard` spread.
|
|
395
|
+
export const startingStyle = (tag: string | undefined, values: Readonly<Record<string, unknown>>): SplitPatch =>
|
|
396
|
+
splitPatchForTag(tag, project(values))
|