kuinetic 0.1.4 → 0.2.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/README.md +93 -10
- package/dist/esm/advanced/index.mjs +4727 -0
- package/dist/esm/chunk-AEQGC2KM.mjs +189 -0
- package/dist/esm/chunk-MQSNMYRP.mjs +9332 -0
- package/dist/esm/chunk-RVTYN6NZ.mjs +3446 -0
- package/dist/esm/chunk-U43BX5O2.mjs +2984 -0
- package/dist/esm/core/index.mjs +16 -9
- package/dist/esm/effects/index.mjs +19 -4
- package/dist/esm/index.mjs +17 -10
- package/dist/kuinetic.advanced.js +5070 -0
- package/dist/kuinetic.advanced.min.js +749 -0
- package/dist/kuinetic.all.js +15147 -6039
- package/dist/kuinetic.all.min.js +2 -0
- package/dist/kuinetic.css +2801 -318
- package/dist/kuinetic.js +15147 -6038
- package/dist/kuinetic.min.css +1 -0
- package/dist/kuinetic.min.js +7 -0
- package/dist/types/3d/angles.d.ts +30 -0
- package/dist/types/3d/flattening.d.ts +49 -0
- package/dist/types/3d/index.d.ts +36 -0
- package/dist/types/3d/model-3d.d.ts +115 -0
- package/dist/types/3d/register.d.ts +26 -0
- package/dist/types/3d/webgl.d.ts +24 -0
- package/dist/types/advanced/audio.d.ts +322 -0
- package/dist/types/advanced/base.d.ts +233 -0
- package/dist/types/advanced/camera-3d.d.ts +236 -0
- package/dist/types/advanced/fluid-cursor.d.ts +149 -0
- package/dist/types/advanced/gl-utils.d.ts +52 -0
- package/dist/types/advanced/glsl.d.ts +60 -0
- package/dist/types/advanced/index.d.ts +23 -0
- package/dist/types/advanced/particles.d.ts +108 -0
- package/dist/types/advanced/scenes.d.ts +207 -0
- package/dist/types/advanced/shaders.d.ts +972 -0
- package/dist/types/browser/boot.d.ts +72 -0
- package/dist/types/core/activation.d.ts +227 -2
- package/dist/types/core/animator.d.ts +446 -11
- package/dist/types/core/attrs.d.ts +9 -0
- package/dist/types/core/breakpoints.d.ts +260 -0
- package/dist/types/core/bundles.d.ts +71 -0
- package/dist/types/core/callback.d.ts +64 -0
- package/dist/types/core/capabilities.d.ts +47 -0
- package/dist/types/core/channels.d.ts +120 -2
- package/dist/types/core/cloak-selectors.d.ts +47 -0
- package/dist/types/core/compile.d.ts +207 -2
- package/dist/types/core/control.d.ts +218 -0
- package/dist/types/core/declarations.d.ts +128 -0
- package/dist/types/core/derived/aggregate.d.ts +43 -0
- package/dist/types/core/derived/book.d.ts +30 -0
- package/dist/types/core/derived/diagnostics.d.ts +51 -0
- package/dist/types/core/derived/group-gate.d.ts +62 -0
- package/dist/types/core/derived/install.d.ts +110 -0
- package/dist/types/core/derived/late-matches.d.ts +19 -0
- package/dist/types/core/derived/types.d.ts +108 -0
- package/dist/types/core/easing.d.ts +57 -0
- package/dist/types/core/element-config.d.ts +47 -6
- package/dist/types/core/element-size.d.ts +23 -0
- package/dist/types/core/event-sources.d.ts +39 -0
- package/dist/types/core/flip.d.ts +34 -1
- package/dist/types/core/gesture.d.ts +12 -0
- package/dist/types/core/host-facts.d.ts +155 -0
- package/dist/types/core/index.d.ts +7 -3
- package/dist/types/core/owned-styles.d.ts +129 -2
- package/dist/types/core/params.d.ts +48 -0
- package/dist/types/core/parse.d.ts +28 -1
- package/dist/types/core/register-into.d.ts +29 -0
- package/dist/types/core/registry.d.ts +10 -3
- package/dist/types/core/repeat.d.ts +91 -0
- package/dist/types/core/sequence.d.ts +126 -0
- package/dist/types/core/stagger-config.d.ts +199 -0
- package/dist/types/core/stagger-keys.d.ts +63 -0
- package/dist/types/core/stagger.d.ts +187 -6
- package/dist/types/core/style-plan.d.ts +21 -16
- package/dist/types/core/target.d.ts +128 -0
- package/dist/types/core/threshold-reachability.d.ts +45 -0
- package/dist/types/core/time-scale.d.ts +9 -0
- package/dist/types/core/toggle-actions.d.ts +141 -0
- package/dist/types/core/travel.d.ts +59 -0
- package/dist/types/core/types.d.ts +699 -13
- package/dist/types/core/unquoted-selectors.d.ts +53 -0
- package/dist/types/effects/carousel/drag.d.ts +80 -0
- package/dist/types/effects/carousel/index.d.ts +149 -0
- package/dist/types/effects/catalog/discrete.d.ts +13 -0
- package/dist/types/effects/catalog/interaction-proximity.d.ts +24 -0
- package/dist/types/effects/catalog/interaction-reveal.d.ts +248 -0
- package/dist/types/effects/catalog/interaction-shared.d.ts +42 -0
- package/dist/types/effects/catalog/interaction-states.d.ts +68 -0
- package/dist/types/effects/catalog/materials.d.ts +67 -0
- package/dist/types/effects/catalog/media-shared.d.ts +3 -2
- package/dist/types/effects/catalog/numbers-shared.d.ts +8 -5
- package/dist/types/effects/catalog/shared.d.ts +1 -1
- package/dist/types/effects/catalog/text-shared.d.ts +86 -10
- package/dist/types/effects/catalog/transforms.d.ts +13 -0
- package/dist/types/effects/catalog/view-transitions.d.ts +13 -0
- package/dist/types/effects/forms/primitives.d.ts +15 -4
- package/dist/types/effects/gestures/index.d.ts +53 -0
- package/dist/types/effects/index.d.ts +5 -0
- package/dist/types/effects/layout/presets.d.ts +35 -0
- package/dist/types/effects/motion-path/index.d.ts +35 -0
- package/dist/types/effects/scroll-mechanics/params.d.ts +23 -0
- package/dist/types/effects/scroll-mechanics/presets.d.ts +51 -0
- package/dist/types/effects/scroll-mechanics/section-index.d.ts +47 -0
- package/dist/types/effects/shared.d.ts +124 -4
- package/dist/types/effects/step-index.d.ts +121 -0
- package/dist/types/effects/step-marking.d.ts +41 -33
- package/dist/types/effects/svg/icon-parts.d.ts +23 -0
- package/dist/types/effects/three-d/flip-parts.d.ts +46 -0
- package/dist/types/effects/tween/index.d.ts +57 -0
- package/dist/types/effects/tween/properties.d.ts +107 -0
- package/dist/types/effects/tween/waypoints.d.ts +106 -0
- package/dist/types/showcase/compare.d.ts +4 -0
- package/dist/types/showcase/device-frame.d.ts +3 -0
- package/dist/types/showcase/hotspots.d.ts +3 -0
- package/dist/types/showcase/index.d.ts +30 -0
- package/dist/types/showcase/lightbox.d.ts +3 -0
- package/dist/types/showcase/media-source.d.ts +13 -0
- package/dist/types/showcase/modal-shell.d.ts +22 -0
- package/dist/types/showcase/scroll-story.d.ts +4 -0
- package/dist/types/showcase/shared.d.ts +47 -0
- package/dist/types/showcase/slideshow.d.ts +3 -0
- package/dist/types/showcase/slow-mo.d.ts +5 -0
- package/package.json +14 -7
- package/dist/esm/chunk-7WMNPIOZ.mjs +0 -4883
- package/dist/esm/chunk-JT4PZL3A.mjs +0 -773
- package/dist/esm/chunk-TJDDIQRG.mjs +0 -1290
|
@@ -1,4883 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ABSOLUTE_BASIS,
|
|
3
|
-
CHANNEL,
|
|
4
|
-
Registry,
|
|
5
|
-
clamp01,
|
|
6
|
-
continuousSetup,
|
|
7
|
-
createAttributeLedger,
|
|
8
|
-
createMeasureCache,
|
|
9
|
-
createStyleLedger,
|
|
10
|
-
deferPrepare,
|
|
11
|
-
effectDurationMs,
|
|
12
|
-
inertInstance,
|
|
13
|
-
isSameOriginPath,
|
|
14
|
-
toPixels
|
|
15
|
-
} from "./chunk-JT4PZL3A.mjs";
|
|
16
|
-
|
|
17
|
-
// src/effects/shared.ts
|
|
18
|
-
var TRIGGER_DELAY_PARAM = {
|
|
19
|
-
delay: { type: "time", default: "0ms", cssProperty: "--kui-delay" }
|
|
20
|
-
};
|
|
21
|
-
var TIMELINE_AGNOSTIC = ["time", "view", "scroll", "pin"];
|
|
22
|
-
var COMMON = {
|
|
23
|
-
duration: { type: "time", default: "600ms", cssProperty: "--kui-duration" },
|
|
24
|
-
...TRIGGER_DELAY_PARAM,
|
|
25
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" },
|
|
26
|
-
stagger: { type: "time", default: "0ms", cssProperty: "--kui-stagger" }
|
|
27
|
-
};
|
|
28
|
-
function cssPrimitive(id, channels, options = {}) {
|
|
29
|
-
return {
|
|
30
|
-
id,
|
|
31
|
-
renderer: "css-keyframes",
|
|
32
|
-
channels,
|
|
33
|
-
parameters: { ...COMMON, ...options.parameters },
|
|
34
|
-
supportedTimelines: options.timelines ?? ["time"],
|
|
35
|
-
supportedActivations: options.activations ?? [
|
|
36
|
-
"load",
|
|
37
|
-
"enter",
|
|
38
|
-
"hover",
|
|
39
|
-
"focus",
|
|
40
|
-
"click",
|
|
41
|
-
"manual"
|
|
42
|
-
],
|
|
43
|
-
perfClass: options.perfClass ?? "compositor",
|
|
44
|
-
reducedMotion: options.reducedMotion ?? "shorten",
|
|
45
|
-
...options.defaultActivation ? { defaultActivation: options.defaultActivation } : {}
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// src/effects/catalog/ambient.ts
|
|
50
|
-
var drift = {
|
|
51
|
-
duration: { type: "time", default: "10s", cssProperty: "--kui-duration" },
|
|
52
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" },
|
|
53
|
-
from: { type: "color", default: "", cssProperty: "--kui-ambient-c1" },
|
|
54
|
-
to: { type: "color", default: "", cssProperty: "--kui-ambient-c2" }
|
|
55
|
-
};
|
|
56
|
-
var tint = {
|
|
57
|
-
duration: { type: "time", default: "10s", cssProperty: "--kui-duration" },
|
|
58
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" },
|
|
59
|
-
from: { type: "color", default: "", cssProperty: "--kui-ambient-c1" }
|
|
60
|
-
};
|
|
61
|
-
var float = {
|
|
62
|
-
duration: { type: "time", default: "4s", cssProperty: "--kui-duration" },
|
|
63
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" },
|
|
64
|
-
distance: { type: "length", default: "14px", cssProperty: "--kui-distance" }
|
|
65
|
-
};
|
|
66
|
-
var orbit = {
|
|
67
|
-
duration: { type: "time", default: "3.5s", cssProperty: "--kui-duration" },
|
|
68
|
-
// `linear` by default, unlike every other ambient primitive: a continuous rotation that eases
|
|
69
|
-
// visibly stutters once per revolution, because the ease restarts at each iteration boundary.
|
|
70
|
-
ease: { type: "easing", default: "linear", cssProperty: "--kui-ease" },
|
|
71
|
-
angle: { type: "angle", default: "360deg", cssProperty: "--kui-to-angle" }
|
|
72
|
-
};
|
|
73
|
-
var pulse = {
|
|
74
|
-
duration: { type: "time", default: "2.2s", cssProperty: "--kui-duration" },
|
|
75
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" },
|
|
76
|
-
scale: { type: "number", default: "1.15", cssProperty: "--kui-pulse-scale", finite: true, minimum: 1 }
|
|
77
|
-
};
|
|
78
|
-
var AMBIENT_PRIMITIVES = [
|
|
79
|
-
cssPrimitive("ambient-gradient", [CHANNEL.background], {
|
|
80
|
-
parameters: drift,
|
|
81
|
-
defaultActivation: "load",
|
|
82
|
-
reducedMotion: "disable",
|
|
83
|
-
perfClass: "continuous"
|
|
84
|
-
}),
|
|
85
|
-
/*
|
|
86
|
-
* `gradient-rotate-border` and `gradient-border` are not background fills, and a second
|
|
87
|
-
* primitive is how this catalog says so — the same reason `ambient-tint` exists beside
|
|
88
|
-
* `ambient-gradient` above. Channels are per-primitive, so a different channel set means a
|
|
89
|
-
* different primitive; folding these into `ambient-gradient` would make `gradient-mesh` and
|
|
90
|
-
* `aurora` claim a mask and a box they never touch, and `aurora, pin` would start reporting a
|
|
91
|
-
* conflict that isn't there.
|
|
92
|
-
*
|
|
93
|
-
* What the ring rules actually write (`ambient.css`) beyond the gradient: `mask` +
|
|
94
|
-
* `mask-composite`, which subtract the element's own content box to leave a ring — the same
|
|
95
|
-
* physical property `media-mask` claims under the `'mask'` channel — and `position: relative`
|
|
96
|
-
* plus a `padding` that *is* the ring's thickness, which is a claim on the host's box in the
|
|
97
|
-
* sense `pin` and `background-media` already use `'layout'` for. Declared only as
|
|
98
|
-
* `background`, a `gradient-border, pin-section` pair composed silently while both decided the
|
|
99
|
-
* host's `position`, and `gradient-border, mask-reveal` while both wrote `mask`.
|
|
100
|
-
*/
|
|
101
|
-
cssPrimitive("ambient-gradient-ring", [CHANNEL.background, "mask", "layout"], {
|
|
102
|
-
parameters: drift,
|
|
103
|
-
defaultActivation: "load",
|
|
104
|
-
reducedMotion: "disable",
|
|
105
|
-
perfClass: "continuous"
|
|
106
|
-
}),
|
|
107
|
-
cssPrimitive("ambient-tint", [CHANNEL.background], {
|
|
108
|
-
parameters: tint,
|
|
109
|
-
defaultActivation: "load",
|
|
110
|
-
reducedMotion: "disable",
|
|
111
|
-
perfClass: "continuous"
|
|
112
|
-
}),
|
|
113
|
-
cssPrimitive("ambient-float", [CHANNEL.translate], {
|
|
114
|
-
parameters: float,
|
|
115
|
-
defaultActivation: "load",
|
|
116
|
-
reducedMotion: "disable",
|
|
117
|
-
perfClass: "continuous"
|
|
118
|
-
}),
|
|
119
|
-
cssPrimitive("ambient-orbit", [CHANNEL.rotate], {
|
|
120
|
-
parameters: orbit,
|
|
121
|
-
defaultActivation: "load",
|
|
122
|
-
reducedMotion: "disable",
|
|
123
|
-
perfClass: "continuous"
|
|
124
|
-
}),
|
|
125
|
-
cssPrimitive("ambient-pulse", [CHANNEL.scale, CHANNEL.opacity], {
|
|
126
|
-
parameters: pulse,
|
|
127
|
-
defaultActivation: "load",
|
|
128
|
-
reducedMotion: "disable",
|
|
129
|
-
perfClass: "continuous"
|
|
130
|
-
})
|
|
131
|
-
];
|
|
132
|
-
var AMBIENT_PRESETS = [
|
|
133
|
-
{ name: "gradient-mesh", primitive: "ambient-gradient", keyframes: "kui-gradient-mesh" },
|
|
134
|
-
{ name: "aurora", primitive: "ambient-gradient", keyframes: "kui-aurora" },
|
|
135
|
-
{
|
|
136
|
-
name: "gradient-rotate-border",
|
|
137
|
-
primitive: "ambient-gradient-ring",
|
|
138
|
-
keyframes: "kui-gradient-rotate-border",
|
|
139
|
-
params: { duration: "6s", ease: "linear" }
|
|
140
|
-
},
|
|
141
|
-
{
|
|
142
|
-
// Not `gradient`: this rule masks its own content box away (`mask-composite: exclude`) to leave
|
|
143
|
-
// a ring, so putting the name on real content deletes the content. `-border` says that out loud,
|
|
144
|
-
// matching `gradient-rotate-border` and `beam-border`.
|
|
145
|
-
name: "gradient-border",
|
|
146
|
-
primitive: "ambient-gradient-ring",
|
|
147
|
-
keyframes: "kui-gradient-border",
|
|
148
|
-
params: { duration: "6s", ease: "linear" }
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
name: "noise-overlay",
|
|
152
|
-
primitive: "ambient-tint",
|
|
153
|
-
keyframes: "kui-noise-overlay",
|
|
154
|
-
params: { duration: "650ms", ease: "steps(6)" }
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: "scanline",
|
|
158
|
-
primitive: "ambient-tint",
|
|
159
|
-
keyframes: "kui-scanline",
|
|
160
|
-
params: { duration: "3.5s", ease: "linear" }
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
name: "dot-grid-drift",
|
|
164
|
-
primitive: "ambient-tint",
|
|
165
|
-
keyframes: "kui-dot-grid-drift",
|
|
166
|
-
params: { duration: "16s", ease: "linear" }
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
name: "line-grid-drift",
|
|
170
|
-
primitive: "ambient-tint",
|
|
171
|
-
keyframes: "kui-line-grid-drift",
|
|
172
|
-
params: { duration: "16s", ease: "linear" }
|
|
173
|
-
},
|
|
174
|
-
{
|
|
175
|
-
name: "starfield",
|
|
176
|
-
primitive: "ambient-tint",
|
|
177
|
-
keyframes: "kui-starfield",
|
|
178
|
-
params: { duration: "40s", ease: "linear" }
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: "spotlight-follow",
|
|
182
|
-
primitive: "ambient-tint",
|
|
183
|
-
keyframes: "kui-spotlight-follow",
|
|
184
|
-
params: { duration: "9s" }
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "wave-blob",
|
|
188
|
-
primitive: "ambient-tint",
|
|
189
|
-
keyframes: "kui-wave-blob",
|
|
190
|
-
params: { duration: "12s" }
|
|
191
|
-
},
|
|
192
|
-
{ name: "float", primitive: "ambient-float", keyframes: "kui-float" },
|
|
193
|
-
{
|
|
194
|
-
name: "bob",
|
|
195
|
-
primitive: "ambient-float",
|
|
196
|
-
keyframes: "kui-bob",
|
|
197
|
-
params: { duration: "2s", distance: "8px" }
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
name: "floating-shapes",
|
|
201
|
-
primitive: "ambient-float",
|
|
202
|
-
keyframes: "kui-floating-shapes",
|
|
203
|
-
params: { duration: "6s", distance: "10px" }
|
|
204
|
-
},
|
|
205
|
-
{ name: "orbit", primitive: "ambient-orbit", keyframes: "kui-orbit" },
|
|
206
|
-
{ name: "glow-pulse", primitive: "ambient-pulse", keyframes: "kui-glow-pulse" }
|
|
207
|
-
];
|
|
208
|
-
function registerAmbient(registry) {
|
|
209
|
-
return registry.registerPrimitives(AMBIENT_PRIMITIVES).registerPresets(AMBIENT_PRESETS);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// src/effects/catalog/feedback.ts
|
|
213
|
-
var loop = {
|
|
214
|
-
duration: { type: "time", default: "1.6s", cssProperty: "--kui-duration" },
|
|
215
|
-
ease: { type: "easing", default: "linear", cssProperty: "--kui-ease" }
|
|
216
|
-
};
|
|
217
|
-
var spin = {
|
|
218
|
-
duration: { type: "time", default: "900ms", cssProperty: "--kui-duration" },
|
|
219
|
-
ease: { type: "easing", default: "linear", cssProperty: "--kui-ease" }
|
|
220
|
-
};
|
|
221
|
-
var dotPulse = {
|
|
222
|
-
duration: { type: "time", default: "1.2s", cssProperty: "--kui-duration" },
|
|
223
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" },
|
|
224
|
-
dotSize: { type: "length", default: "8px", cssProperty: "--kui-dot-size" }
|
|
225
|
-
};
|
|
226
|
-
var progressTrack = {
|
|
227
|
-
duration: { type: "time", default: "1.4s", cssProperty: "--kui-duration" },
|
|
228
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" }
|
|
229
|
-
};
|
|
230
|
-
var toast = {
|
|
231
|
-
duration: { type: "time", default: "420ms", cssProperty: "--kui-duration" },
|
|
232
|
-
ease: { type: "easing", default: "back-out", cssProperty: "--kui-ease" },
|
|
233
|
-
distance: { type: "length", default: "24px", cssProperty: "--kui-distance" }
|
|
234
|
-
};
|
|
235
|
-
var shake = {
|
|
236
|
-
duration: { type: "time", default: "500ms", cssProperty: "--kui-duration" },
|
|
237
|
-
ease: { type: "easing", default: "linear", cssProperty: "--kui-ease" }
|
|
238
|
-
};
|
|
239
|
-
var pop = {
|
|
240
|
-
duration: { type: "time", default: "420ms", cssProperty: "--kui-duration" },
|
|
241
|
-
ease: { type: "easing", default: "back-out", cssProperty: "--kui-ease" },
|
|
242
|
-
scale: { type: "number", default: "1.18", cssProperty: "--kui-pop-scale", finite: true, minimum: 1 }
|
|
243
|
-
};
|
|
244
|
-
var ripple = {
|
|
245
|
-
duration: { type: "time", default: "600ms", cssProperty: "--kui-duration" },
|
|
246
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" },
|
|
247
|
-
spread: { type: "number", default: "4", cssProperty: "--kui-ripple-scale", finite: true, minimum: 1 }
|
|
248
|
-
};
|
|
249
|
-
var confirm = {
|
|
250
|
-
duration: { type: "time", default: "1400ms", cssProperty: "--kui-duration" },
|
|
251
|
-
ease: { type: "easing", default: "linear", cssProperty: "--kui-ease" }
|
|
252
|
-
};
|
|
253
|
-
var pull = {
|
|
254
|
-
distance: { type: "length", default: "36px", cssProperty: "--kui-pull-distance" }
|
|
255
|
-
};
|
|
256
|
-
var FEEDBACK_PRIMITIVES = [
|
|
257
|
-
cssPrimitive("feedback-shimmer", [CHANNEL.background], {
|
|
258
|
-
parameters: loop,
|
|
259
|
-
defaultActivation: "load",
|
|
260
|
-
reducedMotion: "disable",
|
|
261
|
-
perfClass: "continuous"
|
|
262
|
-
}),
|
|
263
|
-
cssPrimitive("feedback-fade", [CHANNEL.opacity], {
|
|
264
|
-
defaultActivation: "manual"
|
|
265
|
-
}),
|
|
266
|
-
cssPrimitive("feedback-spin", [CHANNEL.rotate], {
|
|
267
|
-
parameters: spin,
|
|
268
|
-
defaultActivation: "load",
|
|
269
|
-
reducedMotion: "disable",
|
|
270
|
-
perfClass: "continuous"
|
|
271
|
-
}),
|
|
272
|
-
// Declares every channel the preset's CSS actually paints, not just what the shared keyframe
|
|
273
|
-
// animates: spinner-dots' `[data-kui-fx~='spinner-dots']` rule also sets `background:
|
|
274
|
-
// currentColor` (the dot itself) and `box-shadow` (the other two dots), entirely outside
|
|
275
|
-
// `@keyframes kui-spinner-dots`. Declaring only [scale, opacity] let a composed
|
|
276
|
-
// `background`-writing effect (e.g. gradient-mesh) pass channel-collision detection and then
|
|
277
|
-
// have its gradient silently overwritten by this rule — see css-invariants.test.ts's "CSS
|
|
278
|
-
// static rules" describe block, which now catches this class of omission directly.
|
|
279
|
-
cssPrimitive("feedback-dot-pulse", [CHANNEL.scale, CHANNEL.opacity, CHANNEL.background, "shadow"], {
|
|
280
|
-
parameters: dotPulse,
|
|
281
|
-
defaultActivation: "load",
|
|
282
|
-
reducedMotion: "disable",
|
|
283
|
-
perfClass: "continuous"
|
|
284
|
-
}),
|
|
285
|
-
// Same shape as feedback-dot-pulse above: `[data-kui-fx~='progress-indeterminate']` sets
|
|
286
|
-
// `background: currentColor` unconditionally for the bar itself, outside the keyframe.
|
|
287
|
-
cssPrimitive("feedback-progress-track", [CHANNEL.translate, CHANNEL.scale, CHANNEL.background], {
|
|
288
|
-
parameters: progressTrack,
|
|
289
|
-
defaultActivation: "load",
|
|
290
|
-
reducedMotion: "disable",
|
|
291
|
-
perfClass: "continuous"
|
|
292
|
-
}),
|
|
293
|
-
cssPrimitive("feedback-toast", [CHANNEL.opacity, CHANNEL.translate], {
|
|
294
|
-
parameters: toast,
|
|
295
|
-
defaultActivation: "manual"
|
|
296
|
-
}),
|
|
297
|
-
cssPrimitive("feedback-shake", [CHANNEL.translate], {
|
|
298
|
-
parameters: shake,
|
|
299
|
-
defaultActivation: "manual"
|
|
300
|
-
}),
|
|
301
|
-
cssPrimitive("feedback-wobble", [CHANNEL.translate, CHANNEL.rotate], {
|
|
302
|
-
defaultActivation: "click"
|
|
303
|
-
}),
|
|
304
|
-
// `[data-kui-fx~='ripple']` sets `background: currentColor` unconditionally for the ripple
|
|
305
|
-
// disc itself, outside the keyframe — same gap as feedback-dot-pulse/feedback-progress-track.
|
|
306
|
-
cssPrimitive("feedback-ripple", [CHANNEL.scale, CHANNEL.opacity, CHANNEL.background], {
|
|
307
|
-
parameters: ripple,
|
|
308
|
-
defaultActivation: "click"
|
|
309
|
-
}),
|
|
310
|
-
cssPrimitive("feedback-pop", [CHANNEL.scale], {
|
|
311
|
-
parameters: pop,
|
|
312
|
-
defaultActivation: "manual"
|
|
313
|
-
}),
|
|
314
|
-
// Declares every channel any preset built on it actually paints, not just what the shared
|
|
315
|
-
// keyframes animate: heart-burst's CSS also sets `color`, confetti-burst's also sets
|
|
316
|
-
// `background-image`. Understating this let a composed `background`-writing effect (e.g.
|
|
317
|
-
// gradient-mesh) pass channel-collision detection and silently overwrite confetti-burst's dots.
|
|
318
|
-
cssPrimitive("feedback-burst", [CHANNEL.scale, CHANNEL.opacity, CHANNEL.background, CHANNEL.color], {
|
|
319
|
-
parameters: pop,
|
|
320
|
-
defaultActivation: "click"
|
|
321
|
-
}),
|
|
322
|
-
cssPrimitive("feedback-confirm", [CHANNEL.opacity], {
|
|
323
|
-
parameters: confirm,
|
|
324
|
-
defaultActivation: "click"
|
|
325
|
-
}),
|
|
326
|
-
cssPrimitive("feedback-pull", [CHANNEL.translate], {
|
|
327
|
-
parameters: pull,
|
|
328
|
-
defaultActivation: "manual"
|
|
329
|
-
})
|
|
330
|
-
];
|
|
331
|
-
var FEEDBACK_PRESETS = [
|
|
332
|
-
{ name: "skeleton-shimmer", primitive: "feedback-shimmer", keyframes: "kui-skeleton-shimmer" },
|
|
333
|
-
{ name: "skeleton-to-content", primitive: "feedback-fade", keyframes: "kui-skeleton-to-content" },
|
|
334
|
-
{ name: "spinner", primitive: "feedback-spin", keyframes: "kui-spinner-spin" },
|
|
335
|
-
{ name: "spinner-dots", primitive: "feedback-dot-pulse", keyframes: "kui-spinner-dots" },
|
|
336
|
-
{ name: "spinner-ring", primitive: "feedback-spin", keyframes: "kui-spinner-ring-spin" },
|
|
337
|
-
{
|
|
338
|
-
name: "progress-indeterminate",
|
|
339
|
-
primitive: "feedback-progress-track",
|
|
340
|
-
keyframes: "kui-progress-indeterminate"
|
|
341
|
-
},
|
|
342
|
-
{ name: "toast-slide-in", primitive: "feedback-toast", keyframes: "kui-toast-slide-in" },
|
|
343
|
-
{
|
|
344
|
-
name: "toast-slide-out",
|
|
345
|
-
primitive: "feedback-toast",
|
|
346
|
-
keyframes: "kui-toast-slide-out",
|
|
347
|
-
params: { ease: "ease-in" }
|
|
348
|
-
},
|
|
349
|
-
{ name: "shake-error", primitive: "feedback-shake", keyframes: "kui-shake-error" },
|
|
350
|
-
{
|
|
351
|
-
name: "wobble",
|
|
352
|
-
primitive: "feedback-wobble",
|
|
353
|
-
keyframes: "kui-wobble",
|
|
354
|
-
params: { duration: "600ms", ease: "ease-in-out" }
|
|
355
|
-
},
|
|
356
|
-
{ name: "ripple", primitive: "feedback-ripple", keyframes: "kui-ripple" },
|
|
357
|
-
{ name: "badge-pop", primitive: "feedback-pop", keyframes: "kui-badge-pop" },
|
|
358
|
-
{
|
|
359
|
-
name: "count-bump",
|
|
360
|
-
primitive: "feedback-pop",
|
|
361
|
-
keyframes: "kui-count-bump",
|
|
362
|
-
params: { duration: "280ms", scale: "1.3" }
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
name: "heart-burst",
|
|
366
|
-
primitive: "feedback-burst",
|
|
367
|
-
keyframes: "kui-heart-burst",
|
|
368
|
-
params: { duration: "700ms", scale: "1.4" }
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
name: "confetti-burst",
|
|
372
|
-
primitive: "feedback-burst",
|
|
373
|
-
keyframes: "kui-confetti-burst",
|
|
374
|
-
params: { duration: "900ms", scale: "1.15" }
|
|
375
|
-
},
|
|
376
|
-
{ name: "copy-confirm", primitive: "feedback-confirm", keyframes: "kui-copy-confirm" },
|
|
377
|
-
{
|
|
378
|
-
name: "pull-to-refresh",
|
|
379
|
-
primitive: "feedback-pull",
|
|
380
|
-
keyframes: "kui-pull-to-refresh",
|
|
381
|
-
params: { duration: "900ms", ease: "ease-out" }
|
|
382
|
-
}
|
|
383
|
-
];
|
|
384
|
-
function registerFeedback(registry) {
|
|
385
|
-
return registry.registerPrimitives(FEEDBACK_PRIMITIVES).registerPresets(FEEDBACK_PRESETS);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// src/core/spring.ts
|
|
389
|
-
var DEFAULT_SPRING = {
|
|
390
|
-
stiffness: 180,
|
|
391
|
-
damping: 24,
|
|
392
|
-
mass: 1,
|
|
393
|
-
restVelocity: 0.05,
|
|
394
|
-
restDisplacement: 0.05
|
|
395
|
-
};
|
|
396
|
-
var SUBSTEP = 1 / 240;
|
|
397
|
-
var MAX_STEP = 0.25;
|
|
398
|
-
function stepSpring(state, target, config, dt) {
|
|
399
|
-
let { value, velocity } = state;
|
|
400
|
-
let remaining = Math.min(Math.max(dt, 0), MAX_STEP);
|
|
401
|
-
while (remaining > 0) {
|
|
402
|
-
const step = Math.min(SUBSTEP, remaining);
|
|
403
|
-
const force = -config.stiffness * (value - target) - config.damping * velocity;
|
|
404
|
-
velocity += force / config.mass * step;
|
|
405
|
-
value += velocity * step;
|
|
406
|
-
remaining -= step;
|
|
407
|
-
}
|
|
408
|
-
return { value, velocity };
|
|
409
|
-
}
|
|
410
|
-
function isSettled(state, target, config) {
|
|
411
|
-
return Math.abs(state.velocity) < config.restVelocity && Math.abs(state.value - target) < config.restDisplacement;
|
|
412
|
-
}
|
|
413
|
-
var MAX_SETTLE_MS = 1e4;
|
|
414
|
-
function createSpringRunner(config, onChange, deps) {
|
|
415
|
-
const safeConfig = validConfig(config) ? config : DEFAULT_SPRING;
|
|
416
|
-
if (safeConfig !== config) deps.warn?.("invalid spring configuration; using defaults");
|
|
417
|
-
let state = { value: 0, velocity: 0 };
|
|
418
|
-
let target = 0;
|
|
419
|
-
let handle = null;
|
|
420
|
-
let lastTime = 0;
|
|
421
|
-
let startedAt = 0;
|
|
422
|
-
function frame(time) {
|
|
423
|
-
handle = null;
|
|
424
|
-
const dt = (time - lastTime) / 1e3;
|
|
425
|
-
lastTime = time;
|
|
426
|
-
state = stepSpring(state, target, safeConfig, dt);
|
|
427
|
-
if (!finiteState(state) || !Number.isFinite(target)) {
|
|
428
|
-
abortRun("spring produced non-finite state");
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
if (time - startedAt >= MAX_SETTLE_MS) {
|
|
432
|
-
abortRun(`spring exceeded ${MAX_SETTLE_MS}ms settle budget`);
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
if (isSettled(state, target, safeConfig)) {
|
|
436
|
-
state = { value: target, velocity: 0 };
|
|
437
|
-
onChange(state.value, true);
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
onChange(state.value, false);
|
|
441
|
-
schedule();
|
|
442
|
-
}
|
|
443
|
-
function schedule() {
|
|
444
|
-
if (handle !== null) return;
|
|
445
|
-
handle = deps.requestFrame(frame);
|
|
446
|
-
}
|
|
447
|
-
function start() {
|
|
448
|
-
lastTime = deps.now();
|
|
449
|
-
startedAt = lastTime;
|
|
450
|
-
schedule();
|
|
451
|
-
}
|
|
452
|
-
function abortRun(message) {
|
|
453
|
-
state = { value: Number.isFinite(target) ? target : 0, velocity: 0 };
|
|
454
|
-
deps.warn?.(message);
|
|
455
|
-
onChange(state.value, true);
|
|
456
|
-
}
|
|
457
|
-
return {
|
|
458
|
-
to(next, velocity) {
|
|
459
|
-
target = next;
|
|
460
|
-
if (velocity !== void 0) state = { ...state, velocity };
|
|
461
|
-
start();
|
|
462
|
-
},
|
|
463
|
-
set(value, velocity = 0) {
|
|
464
|
-
state = { value, velocity };
|
|
465
|
-
},
|
|
466
|
-
current: () => ({ ...state }),
|
|
467
|
-
stop() {
|
|
468
|
-
if (handle !== null) deps.cancelFrame(handle);
|
|
469
|
-
handle = null;
|
|
470
|
-
}
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
function validConfig(config) {
|
|
474
|
-
return Object.values(config).every(Number.isFinite) && config.stiffness > 0 && config.damping > 0 && config.mass > 0 && config.restVelocity > 0 && config.restDisplacement > 0;
|
|
475
|
-
}
|
|
476
|
-
function finiteState(state) {
|
|
477
|
-
return Number.isFinite(state.value) && Number.isFinite(state.velocity);
|
|
478
|
-
}
|
|
479
|
-
function defaultSpringDeps() {
|
|
480
|
-
const raf = globalThis.requestAnimationFrame;
|
|
481
|
-
if (typeof raf !== "function") {
|
|
482
|
-
return {
|
|
483
|
-
requestFrame: (callback) => globalThis.setTimeout(() => callback(Date.now()), 16),
|
|
484
|
-
cancelFrame: (handle) => globalThis.clearTimeout(handle),
|
|
485
|
-
now: () => Date.now()
|
|
486
|
-
};
|
|
487
|
-
}
|
|
488
|
-
return {
|
|
489
|
-
requestFrame: (callback) => raf(callback),
|
|
490
|
-
cancelFrame: (handle) => globalThis.cancelAnimationFrame(handle),
|
|
491
|
-
now: () => performance.now()
|
|
492
|
-
};
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// src/effects/catalog/interaction-shared.ts
|
|
496
|
-
function centeredOffset(point, size) {
|
|
497
|
-
return {
|
|
498
|
-
x: size.width > 0 ? point.x / size.width - 0.5 : 0,
|
|
499
|
-
y: size.height > 0 ? point.y / size.height - 0.5 : 0
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
function tiltAngles(point, size, maxAngleDeg) {
|
|
503
|
-
const centered = centeredOffset(point, size);
|
|
504
|
-
return { rotateY: centered.x * maxAngleDeg * 2, rotateX: -centered.y * maxAngleDeg * 2 };
|
|
505
|
-
}
|
|
506
|
-
function parallaxOffset(point, size, strengthPx) {
|
|
507
|
-
const centered = centeredOffset(point, size);
|
|
508
|
-
return { x: centered.x * strengthPx * 2, y: centered.y * strengthPx * 2 };
|
|
509
|
-
}
|
|
510
|
-
function supportsFineHover(win) {
|
|
511
|
-
return win.matchMedia?.("(hover: hover) and (pointer: fine)").matches ?? true;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// src/effects/catalog/interaction.ts
|
|
515
|
-
var hoverTiming = {
|
|
516
|
-
duration: { type: "time", default: "220ms", cssProperty: "--kui-duration" },
|
|
517
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" }
|
|
518
|
-
};
|
|
519
|
-
var liftParams = {
|
|
520
|
-
distance: { type: "length", default: "6px", cssProperty: "--kui-lift-distance" }
|
|
521
|
-
};
|
|
522
|
-
var popParams = {
|
|
523
|
-
scale: { type: "number", default: "1.06", cssProperty: "--kui-pop-scale", finite: true, minimum: 0 }
|
|
524
|
-
};
|
|
525
|
-
var beamParams = {
|
|
526
|
-
color: { type: "color", default: "", cssProperty: "--kui-beam-border-c1" },
|
|
527
|
-
outset: { type: "length", default: "", cssProperty: "--kui-beam-border-outset" }
|
|
528
|
-
};
|
|
529
|
-
function hoverPrimitive(id, channels, extraParams = {}) {
|
|
530
|
-
return {
|
|
531
|
-
id,
|
|
532
|
-
renderer: "javascript",
|
|
533
|
-
channels,
|
|
534
|
-
parameters: { ...hoverTiming, ...extraParams },
|
|
535
|
-
supportedTimelines: ["time"],
|
|
536
|
-
supportedActivations: ["load"],
|
|
537
|
-
defaultActivation: "load",
|
|
538
|
-
perfClass: "compositor",
|
|
539
|
-
// Not 'disable': a translate/box-shadow/rotate hover micro-interaction at this scale is not a
|
|
540
|
-
// vestibular trigger the way parallax or continuous ambient motion is. The real motion lives in
|
|
541
|
-
// CSS transitions and `:hover`-scoped pseudo-element animations rather than this primitive's
|
|
542
|
-
// (here unused) compiled `animation-*` path, so the policy layer shortens it via the
|
|
543
|
-
// `transition-duration` and `::before`/`::after` rules in base.css.
|
|
544
|
-
reducedMotion: "shorten",
|
|
545
|
-
prepare: () => inertInstance()
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
var HOVER_PRIMITIVES = [
|
|
549
|
-
hoverPrimitive("lift", ["translate"], liftParams),
|
|
550
|
-
hoverPrimitive("pop", ["scale"], popParams),
|
|
551
|
-
hoverPrimitive("lift-shadow", ["translate", "shadow"], liftParams),
|
|
552
|
-
hoverPrimitive("shine-sweep", ["sweep"]),
|
|
553
|
-
hoverPrimitive("split-flap", ["rotate"]),
|
|
554
|
-
hoverPrimitive("border-draw", ["border"]),
|
|
555
|
-
hoverPrimitive("border-glow", ["shadow"]),
|
|
556
|
-
hoverPrimitive("beam-border", ["border"], beamParams),
|
|
557
|
-
hoverPrimitive("underline-slide", ["scale"]),
|
|
558
|
-
hoverPrimitive("underline-center", ["scale"]),
|
|
559
|
-
hoverPrimitive("icon-wiggle", ["rotate"]),
|
|
560
|
-
hoverPrimitive("icon-spin", ["rotate"]),
|
|
561
|
-
hoverPrimitive("icon-bounce", ["translate"])
|
|
562
|
-
];
|
|
563
|
-
var HOVER_PRESETS = HOVER_PRIMITIVES.map((primitive) => ({
|
|
564
|
-
name: primitive.id,
|
|
565
|
-
primitive: primitive.id
|
|
566
|
-
}));
|
|
567
|
-
var CONTINUOUS_BORDER_PRIMITIVES = [
|
|
568
|
-
{
|
|
569
|
-
id: "beam-border-auto",
|
|
570
|
-
renderer: "javascript",
|
|
571
|
-
channels: ["border"],
|
|
572
|
-
parameters: { ...hoverTiming, ...beamParams },
|
|
573
|
-
supportedTimelines: ["time"],
|
|
574
|
-
supportedActivations: ["load"],
|
|
575
|
-
defaultActivation: "load",
|
|
576
|
-
perfClass: "compositor",
|
|
577
|
-
reducedMotion: "disable",
|
|
578
|
-
prepare: () => inertInstance()
|
|
579
|
-
}
|
|
580
|
-
];
|
|
581
|
-
var CONTINUOUS_BORDER_PRESETS = [
|
|
582
|
-
{ name: "beam-border-auto", primitive: "beam-border-auto" }
|
|
583
|
-
];
|
|
584
|
-
var springParams = {
|
|
585
|
-
stiffness: {
|
|
586
|
-
type: "number",
|
|
587
|
-
default: "260",
|
|
588
|
-
cssProperty: "--kui-stiffness",
|
|
589
|
-
finite: true,
|
|
590
|
-
minimum: 1,
|
|
591
|
-
maximum: 1e4
|
|
592
|
-
},
|
|
593
|
-
damping: {
|
|
594
|
-
type: "number",
|
|
595
|
-
default: "26",
|
|
596
|
-
cssProperty: "--kui-damping",
|
|
597
|
-
finite: true,
|
|
598
|
-
minimum: 0.1,
|
|
599
|
-
maximum: 1e3
|
|
600
|
-
},
|
|
601
|
-
mass: {
|
|
602
|
-
type: "number",
|
|
603
|
-
default: "1",
|
|
604
|
-
cssProperty: "--kui-mass",
|
|
605
|
-
finite: true,
|
|
606
|
-
minimum: 0.1
|
|
607
|
-
}
|
|
608
|
-
};
|
|
609
|
-
function pointerPrimitive(id, channels, parameters, prepare) {
|
|
610
|
-
return {
|
|
611
|
-
id,
|
|
612
|
-
renderer: "javascript",
|
|
613
|
-
channels,
|
|
614
|
-
parameters,
|
|
615
|
-
supportedTimelines: ["time", "pointer"],
|
|
616
|
-
supportedActivations: ["load", "manual"],
|
|
617
|
-
defaultActivation: "load",
|
|
618
|
-
perfClass: "continuous",
|
|
619
|
-
// A tilt or cursor-follow effect only exists while the pointer is present; there is no
|
|
620
|
-
// meaningful "shortened" version of tracking a position. `prepare` itself checks
|
|
621
|
-
// `supportsFineHover` and no-ops on touch, which is the coarse-pointer half of this rule.
|
|
622
|
-
reducedMotion: "disable",
|
|
623
|
-
prepare
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
function springFrom(params) {
|
|
627
|
-
return {
|
|
628
|
-
...DEFAULT_SPRING,
|
|
629
|
-
stiffness: params.num("stiffness", DEFAULT_SPRING.stiffness),
|
|
630
|
-
damping: params.num("damping", DEFAULT_SPRING.damping),
|
|
631
|
-
mass: params.num("mass", DEFAULT_SPRING.mass)
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
function springDepsFor(ctx) {
|
|
635
|
-
return { ...defaultSpringDeps(), warn: ctx.warn };
|
|
636
|
-
}
|
|
637
|
-
function prepareTilt3d(el, params, ctx) {
|
|
638
|
-
if (!supportsFineHover(ctx.win)) return () => {
|
|
639
|
-
};
|
|
640
|
-
const node = el;
|
|
641
|
-
const maxAngle = params.num("maxAngle", 14);
|
|
642
|
-
const perspective = params.text("perspective", "800px");
|
|
643
|
-
function writeAngles(rotateX, rotateY, transitionMs) {
|
|
644
|
-
ctx.style.set("transition", `transform ${transitionMs}ms ease-out`);
|
|
645
|
-
ctx.style.set(
|
|
646
|
-
"transform",
|
|
647
|
-
`perspective(${perspective}) rotateX(${rotateX.toFixed(2)}deg) rotateY(${rotateY.toFixed(2)}deg)`
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
function onMove(event) {
|
|
651
|
-
const rect = node.getBoundingClientRect();
|
|
652
|
-
const point = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
653
|
-
const { rotateX, rotateY } = tiltAngles(point, rect, maxAngle);
|
|
654
|
-
writeAngles(rotateX, rotateY, 100);
|
|
655
|
-
}
|
|
656
|
-
function reset() {
|
|
657
|
-
writeAngles(0, 0, 400);
|
|
658
|
-
}
|
|
659
|
-
function onFocus() {
|
|
660
|
-
writeAngles(-maxAngle / 2, maxAngle / 2, 250);
|
|
661
|
-
}
|
|
662
|
-
node.addEventListener("pointermove", onMove, { passive: true });
|
|
663
|
-
node.addEventListener("pointerleave", reset, { passive: true });
|
|
664
|
-
node.addEventListener("focus", onFocus);
|
|
665
|
-
node.addEventListener("blur", reset);
|
|
666
|
-
return () => {
|
|
667
|
-
node.removeEventListener("pointermove", onMove);
|
|
668
|
-
node.removeEventListener("pointerleave", reset);
|
|
669
|
-
node.removeEventListener("focus", onFocus);
|
|
670
|
-
node.removeEventListener("blur", reset);
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
function collectParallaxLayers(node) {
|
|
674
|
-
return [...node.querySelectorAll("[data-depth]")].map((layer) => ({
|
|
675
|
-
ledger: createStyleLedger(layer),
|
|
676
|
-
depth: Number(layer.dataset.depth) || 0
|
|
677
|
-
}));
|
|
678
|
-
}
|
|
679
|
-
function writeParallaxLayers(layers, x, y, transitionMs) {
|
|
680
|
-
for (const layer of layers) {
|
|
681
|
-
layer.ledger.set("transition", `translate ${transitionMs}ms ease-out`);
|
|
682
|
-
layer.ledger.set("translate", `${(x * layer.depth).toFixed(2)}px ${(y * layer.depth).toFixed(2)}px`);
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
function prepareTiltParallax(el, params, ctx) {
|
|
686
|
-
if (!supportsFineHover(ctx.win)) return () => {
|
|
687
|
-
};
|
|
688
|
-
const node = el;
|
|
689
|
-
const strength = params.num("strength", 24);
|
|
690
|
-
const layers = collectParallaxLayers(node);
|
|
691
|
-
function onMove(event) {
|
|
692
|
-
const rect = node.getBoundingClientRect();
|
|
693
|
-
const point = { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
694
|
-
const offset = parallaxOffset(point, rect, strength);
|
|
695
|
-
writeParallaxLayers(layers, offset.x, offset.y, 120);
|
|
696
|
-
}
|
|
697
|
-
function reset() {
|
|
698
|
-
writeParallaxLayers(layers, 0, 0, 400);
|
|
699
|
-
}
|
|
700
|
-
node.addEventListener("pointermove", onMove, { passive: true });
|
|
701
|
-
node.addEventListener("pointerleave", reset, { passive: true });
|
|
702
|
-
node.addEventListener("focus", reset);
|
|
703
|
-
node.addEventListener("blur", reset);
|
|
704
|
-
return () => {
|
|
705
|
-
node.removeEventListener("pointermove", onMove);
|
|
706
|
-
node.removeEventListener("pointerleave", reset);
|
|
707
|
-
node.removeEventListener("focus", reset);
|
|
708
|
-
node.removeEventListener("blur", reset);
|
|
709
|
-
for (const layer of layers) layer.ledger.restore();
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
function writeDotPosition(dot, x, y) {
|
|
713
|
-
dot.style.translate = `${x.toFixed(1)}px ${y.toFixed(1)}px`;
|
|
714
|
-
}
|
|
715
|
-
function createCursorDot(doc, dotClass, labelText) {
|
|
716
|
-
const dot = doc.createElement("span");
|
|
717
|
-
dot.className = `kui-cursor-dot ${dotClass}`;
|
|
718
|
-
dot.setAttribute("aria-hidden", "true");
|
|
719
|
-
if (labelText) dot.textContent = labelText;
|
|
720
|
-
doc.body.append(dot);
|
|
721
|
-
return dot;
|
|
722
|
-
}
|
|
723
|
-
function createCursorRunners(dot, config, deps) {
|
|
724
|
-
const position = { x: 0, y: 0 };
|
|
725
|
-
const onAxis = (axis) => (value) => {
|
|
726
|
-
position[axis] = value;
|
|
727
|
-
writeDotPosition(dot, position.x, position.y);
|
|
728
|
-
};
|
|
729
|
-
return {
|
|
730
|
-
x: createSpringRunner(config, onAxis("x"), deps),
|
|
731
|
-
y: createSpringRunner(config, onAxis("y"), deps)
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
function prepareCursorDot(el, params, ctx, dotClass) {
|
|
735
|
-
if (!supportsFineHover(ctx.win)) return () => {
|
|
736
|
-
};
|
|
737
|
-
const doc = el.ownerDocument;
|
|
738
|
-
const node = el;
|
|
739
|
-
const dot = createCursorDot(doc, dotClass, params.text("label", ""));
|
|
740
|
-
const runners = createCursorRunners(dot, springFrom(params), springDepsFor(ctx));
|
|
741
|
-
function show() {
|
|
742
|
-
dot.classList.add("kui-cursor-dot-active");
|
|
743
|
-
}
|
|
744
|
-
function hide() {
|
|
745
|
-
dot.classList.remove("kui-cursor-dot-active");
|
|
746
|
-
}
|
|
747
|
-
function onMove(event) {
|
|
748
|
-
show();
|
|
749
|
-
runners.x.to(event.clientX);
|
|
750
|
-
runners.y.to(event.clientY);
|
|
751
|
-
}
|
|
752
|
-
function onFocus() {
|
|
753
|
-
const rect = node.getBoundingClientRect();
|
|
754
|
-
const cx = rect.left + rect.width / 2;
|
|
755
|
-
const cy = rect.top + rect.height / 2;
|
|
756
|
-
show();
|
|
757
|
-
runners.x.set(cx);
|
|
758
|
-
runners.y.set(cy);
|
|
759
|
-
writeDotPosition(dot, cx, cy);
|
|
760
|
-
}
|
|
761
|
-
node.addEventListener("pointermove", onMove, { passive: true });
|
|
762
|
-
node.addEventListener("pointerleave", hide, { passive: true });
|
|
763
|
-
node.addEventListener("focus", onFocus);
|
|
764
|
-
node.addEventListener("blur", hide);
|
|
765
|
-
return () => {
|
|
766
|
-
node.removeEventListener("pointermove", onMove);
|
|
767
|
-
node.removeEventListener("pointerleave", hide);
|
|
768
|
-
node.removeEventListener("focus", onFocus);
|
|
769
|
-
node.removeEventListener("blur", hide);
|
|
770
|
-
runners.x.stop();
|
|
771
|
-
runners.y.stop();
|
|
772
|
-
dot.remove();
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
function prepareSpotlight(el, params, ctx) {
|
|
776
|
-
if (!supportsFineHover(ctx.win)) return () => {
|
|
777
|
-
};
|
|
778
|
-
const node = el;
|
|
779
|
-
if (ctx.win.getComputedStyle(node).position === "static") ctx.style.set("position", "relative");
|
|
780
|
-
function writeSpot(x, y, on) {
|
|
781
|
-
ctx.style.set("--kui-x", x);
|
|
782
|
-
ctx.style.set("--kui-y", y);
|
|
783
|
-
ctx.style.set("--kui-spotlight-opacity", on ? "1" : "0");
|
|
784
|
-
}
|
|
785
|
-
function onMove(event) {
|
|
786
|
-
const rect = node.getBoundingClientRect();
|
|
787
|
-
writeSpot(`${(event.clientX - rect.left).toFixed(1)}px`, `${(event.clientY - rect.top).toFixed(1)}px`, true);
|
|
788
|
-
}
|
|
789
|
-
function hide() {
|
|
790
|
-
writeSpot("50%", "50%", false);
|
|
791
|
-
}
|
|
792
|
-
function onFocus() {
|
|
793
|
-
writeSpot("50%", "50%", true);
|
|
794
|
-
}
|
|
795
|
-
node.addEventListener("pointermove", onMove, { passive: true });
|
|
796
|
-
node.addEventListener("pointerleave", hide, { passive: true });
|
|
797
|
-
node.addEventListener("focus", onFocus);
|
|
798
|
-
node.addEventListener("blur", hide);
|
|
799
|
-
return () => {
|
|
800
|
-
node.removeEventListener("pointermove", onMove);
|
|
801
|
-
node.removeEventListener("pointerleave", hide);
|
|
802
|
-
node.removeEventListener("focus", onFocus);
|
|
803
|
-
node.removeEventListener("blur", hide);
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
|
-
var tiltParams = {
|
|
807
|
-
maxAngle: { type: "number", default: "14", cssProperty: "--kui-max-angle" },
|
|
808
|
-
perspective: { type: "length", default: "800px", cssProperty: "--kui-perspective" }
|
|
809
|
-
};
|
|
810
|
-
var parallaxParams = {
|
|
811
|
-
strength: { type: "number", default: "24", cssProperty: "--kui-strength" }
|
|
812
|
-
};
|
|
813
|
-
var cursorDotParams = {
|
|
814
|
-
...springParams,
|
|
815
|
-
label: { type: "text", default: "", cssProperty: "--kui-label" }
|
|
816
|
-
};
|
|
817
|
-
var POINTER_PRIMITIVES = [
|
|
818
|
-
pointerPrimitive("tilt-3d", ["rotate"], tiltParams, deferPrepare(prepareTilt3d)),
|
|
819
|
-
pointerPrimitive("tilt-parallax", ["translate"], parallaxParams, deferPrepare(prepareTiltParallax)),
|
|
820
|
-
pointerPrimitive("cursor-follow", ["translate"], cursorDotParams, deferPrepare(prepareCursorFollow)),
|
|
821
|
-
pointerPrimitive("cursor-lag", ["translate"], cursorDotParams, deferPrepare(prepareCursorLag)),
|
|
822
|
-
pointerPrimitive("cursor-label", ["translate"], cursorDotParams, deferPrepare(prepareCursorLabel)),
|
|
823
|
-
pointerPrimitive("cursor-invert", ["translate"], cursorDotParams, deferPrepare(prepareCursorInvert)),
|
|
824
|
-
pointerPrimitive("cursor-spotlight", ["spotlight"], {}, deferPrepare(prepareSpotlight))
|
|
825
|
-
];
|
|
826
|
-
function prepareCursorFollow(el, params, ctx) {
|
|
827
|
-
return prepareCursorDot(el, params, ctx, "kui-cursor-dot-follow");
|
|
828
|
-
}
|
|
829
|
-
function prepareCursorLag(el, params, ctx) {
|
|
830
|
-
return prepareCursorDot(el, params, ctx, "kui-cursor-dot-lag");
|
|
831
|
-
}
|
|
832
|
-
function prepareCursorLabel(el, params, ctx) {
|
|
833
|
-
return prepareCursorDot(el, params, ctx, "kui-cursor-dot-label");
|
|
834
|
-
}
|
|
835
|
-
function prepareCursorInvert(el, params, ctx) {
|
|
836
|
-
return prepareCursorDot(el, params, ctx, "kui-cursor-dot-invert");
|
|
837
|
-
}
|
|
838
|
-
var POINTER_PRESETS = [
|
|
839
|
-
{ name: "tilt-3d", primitive: "tilt-3d" },
|
|
840
|
-
{ name: "tilt-parallax", primitive: "tilt-parallax" },
|
|
841
|
-
{ name: "cursor-follow", primitive: "cursor-follow", params: { stiffness: "300", damping: "30" } },
|
|
842
|
-
{ name: "cursor-lag", primitive: "cursor-lag", params: { stiffness: "80", damping: "14" } },
|
|
843
|
-
{ name: "cursor-label", primitive: "cursor-label", params: { stiffness: "260", damping: "26", label: "View" } },
|
|
844
|
-
{ name: "cursor-spotlight", primitive: "cursor-spotlight" },
|
|
845
|
-
{ name: "cursor-invert", primitive: "cursor-invert", params: { stiffness: "260", damping: "26" } }
|
|
846
|
-
];
|
|
847
|
-
var INTERACTION_PRIMITIVES = [
|
|
848
|
-
...HOVER_PRIMITIVES,
|
|
849
|
-
...POINTER_PRIMITIVES,
|
|
850
|
-
...CONTINUOUS_BORDER_PRIMITIVES
|
|
851
|
-
];
|
|
852
|
-
var INTERACTION_PRESETS = [
|
|
853
|
-
...HOVER_PRESETS,
|
|
854
|
-
...POINTER_PRESETS,
|
|
855
|
-
...CONTINUOUS_BORDER_PRESETS
|
|
856
|
-
];
|
|
857
|
-
function registerInteraction(registry) {
|
|
858
|
-
return registry.registerPrimitives(INTERACTION_PRIMITIVES).registerPresets(INTERACTION_PRESETS);
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
// src/effects/catalog/background-media.ts
|
|
862
|
-
var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set(["mp4", "webm", "mov", "m4v", "ogv"]);
|
|
863
|
-
var VIDEO_PAGE_HOSTS = /* @__PURE__ */ new Set([
|
|
864
|
-
"youtube.com",
|
|
865
|
-
"www.youtube.com",
|
|
866
|
-
"m.youtube.com",
|
|
867
|
-
"youtube-nocookie.com",
|
|
868
|
-
"www.youtube-nocookie.com",
|
|
869
|
-
"youtu.be",
|
|
870
|
-
"www.youtu.be"
|
|
871
|
-
]);
|
|
872
|
-
function normalizeUrl(value) {
|
|
873
|
-
const stripped = value.replace(/[\t\n\r]/g, "");
|
|
874
|
-
let start = 0;
|
|
875
|
-
while (start < stripped.length && stripped.charCodeAt(start) <= 32) start += 1;
|
|
876
|
-
return stripped.slice(start);
|
|
877
|
-
}
|
|
878
|
-
function hostOf(value) {
|
|
879
|
-
const withoutScheme = normalizeUrl(value).replace(/^[a-z][a-z0-9+.-]*:/i, "").replace(/^\/\//, "");
|
|
880
|
-
const end = withoutScheme.search(/[/?#]/);
|
|
881
|
-
const authority = end === -1 ? withoutScheme : withoutScheme.slice(0, end);
|
|
882
|
-
return authority.slice(authority.lastIndexOf("@") + 1).toLowerCase();
|
|
883
|
-
}
|
|
884
|
-
function isVideoPageUrl(value) {
|
|
885
|
-
return VIDEO_PAGE_HOSTS.has(hostOf(value));
|
|
886
|
-
}
|
|
887
|
-
var FOCAL_POINTS = {
|
|
888
|
-
center: "50% 50%",
|
|
889
|
-
top: "50% 0%",
|
|
890
|
-
bottom: "50% 100%",
|
|
891
|
-
left: "0% 50%",
|
|
892
|
-
right: "100% 50%",
|
|
893
|
-
"top-left": "0% 0%",
|
|
894
|
-
"top-right": "100% 0%",
|
|
895
|
-
"bottom-left": "0% 100%",
|
|
896
|
-
"bottom-right": "100% 100%"
|
|
897
|
-
};
|
|
898
|
-
var FOCAL_POINT_NAMES = Object.keys(FOCAL_POINTS);
|
|
899
|
-
function focalPosition(focus) {
|
|
900
|
-
return FOCAL_POINTS[focus] ?? FOCAL_POINTS.center;
|
|
901
|
-
}
|
|
902
|
-
function paintsOverlay(options) {
|
|
903
|
-
return options.overlay !== "transparent" && options.overlayOpacity > 0;
|
|
904
|
-
}
|
|
905
|
-
function isVideoSource(src) {
|
|
906
|
-
const path = src.split("?")[0].split("#")[0];
|
|
907
|
-
const dot = path.lastIndexOf(".");
|
|
908
|
-
if (dot <= path.lastIndexOf("/")) return false;
|
|
909
|
-
return VIDEO_EXTENSIONS.has(path.slice(dot + 1).toLowerCase());
|
|
910
|
-
}
|
|
911
|
-
var ALLOWED_SCHEMES = /* @__PURE__ */ new Set(["http", "https"]);
|
|
912
|
-
function schemeOf(value) {
|
|
913
|
-
const match = /^([a-z][a-z0-9+.-]*):/i.exec(normalizeUrl(value));
|
|
914
|
-
return match ? match[1].toLowerCase() : "";
|
|
915
|
-
}
|
|
916
|
-
function mediaSource(authored, name, ctx) {
|
|
917
|
-
if (!authored) return authored;
|
|
918
|
-
if (isVideoPageUrl(authored)) {
|
|
919
|
-
ctx.warn(
|
|
920
|
-
`background-media "${name}": "${authored}" is a video *page*, not a media file \u2014 a YouTube URL cannot be played by <video> and needs an iframe embed instead. Point "${name}" at an .mp4/.webm file, or use a YouTube facade alongside this effect.`
|
|
921
|
-
);
|
|
922
|
-
return "";
|
|
923
|
-
}
|
|
924
|
-
const scheme = schemeOf(authored);
|
|
925
|
-
if (!scheme || ALLOWED_SCHEMES.has(scheme)) return authored;
|
|
926
|
-
ctx.warn(
|
|
927
|
-
`background-media "${name}": "${scheme}:" URLs are not allowed \u2014 use https:, http:, or a path such as "/media/hero.mp4".`
|
|
928
|
-
);
|
|
929
|
-
return "";
|
|
930
|
-
}
|
|
931
|
-
function styleLayer(node) {
|
|
932
|
-
const { style } = node;
|
|
933
|
-
style.setProperty("position", "absolute");
|
|
934
|
-
style.setProperty("top", "0");
|
|
935
|
-
style.setProperty("left", "0");
|
|
936
|
-
style.setProperty("width", "100%");
|
|
937
|
-
style.setProperty("height", "100%");
|
|
938
|
-
style.setProperty("border-radius", "inherit");
|
|
939
|
-
style.setProperty("pointer-events", "none");
|
|
940
|
-
style.setProperty("z-index", "-1");
|
|
941
|
-
}
|
|
942
|
-
function createOverlay(doc, options) {
|
|
943
|
-
const scrim = doc.createElement("div");
|
|
944
|
-
styleLayer(scrim);
|
|
945
|
-
scrim.style.setProperty("background", options.overlay);
|
|
946
|
-
scrim.style.setProperty("opacity", String(options.overlayOpacity));
|
|
947
|
-
scrim.setAttribute("aria-hidden", "true");
|
|
948
|
-
return scrim;
|
|
949
|
-
}
|
|
950
|
-
function createVideo(doc, options) {
|
|
951
|
-
const video = doc.createElement("video");
|
|
952
|
-
video.muted = true;
|
|
953
|
-
video.setAttribute("muted", "");
|
|
954
|
-
video.playsInline = true;
|
|
955
|
-
video.setAttribute("playsinline", "");
|
|
956
|
-
video.loop = options.loop;
|
|
957
|
-
video.defaultPlaybackRate = options.rate;
|
|
958
|
-
video.playbackRate = options.rate;
|
|
959
|
-
video.preload = "metadata";
|
|
960
|
-
if (options.poster) video.poster = options.poster;
|
|
961
|
-
video.src = options.src;
|
|
962
|
-
return video;
|
|
963
|
-
}
|
|
964
|
-
function play(video) {
|
|
965
|
-
const started = video.play();
|
|
966
|
-
if (started) void started.catch(() => {
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
function startPlayback(video, win, options) {
|
|
970
|
-
if (options.reducedMotion || options.autoplay === "never") return () => {
|
|
971
|
-
};
|
|
972
|
-
if (options.autoplay === "always") {
|
|
973
|
-
play(video);
|
|
974
|
-
return () => {
|
|
975
|
-
if (!video.paused) video.pause();
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
return autoplayInView(video, win);
|
|
979
|
-
}
|
|
980
|
-
function createImage(doc, options) {
|
|
981
|
-
const image = doc.createElement("img");
|
|
982
|
-
image.alt = "";
|
|
983
|
-
image.decoding = "async";
|
|
984
|
-
image.src = options.src;
|
|
985
|
-
return image;
|
|
986
|
-
}
|
|
987
|
-
function autoplayInView(video, win) {
|
|
988
|
-
const Observer = win.IntersectionObserver;
|
|
989
|
-
if (!Observer) return () => {
|
|
990
|
-
};
|
|
991
|
-
const observer = new Observer(
|
|
992
|
-
(entries) => {
|
|
993
|
-
for (const entry of entries) {
|
|
994
|
-
if (entry.isIntersecting) play(video);
|
|
995
|
-
else if (!video.paused) video.pause();
|
|
996
|
-
}
|
|
997
|
-
},
|
|
998
|
-
{ threshold: 0 }
|
|
999
|
-
);
|
|
1000
|
-
observer.observe(video);
|
|
1001
|
-
return () => {
|
|
1002
|
-
observer.disconnect();
|
|
1003
|
-
if (!video.paused) video.pause();
|
|
1004
|
-
};
|
|
1005
|
-
}
|
|
1006
|
-
function installBackgroundMedia(el, ctx, options) {
|
|
1007
|
-
const doc = el.ownerDocument;
|
|
1008
|
-
const video = isVideoSource(options.src) ? createVideo(doc, options) : null;
|
|
1009
|
-
const node = video ?? createImage(doc, options);
|
|
1010
|
-
styleLayer(node);
|
|
1011
|
-
node.style.setProperty("object-fit", options.fit);
|
|
1012
|
-
node.style.setProperty("object-position", options.position);
|
|
1013
|
-
node.setAttribute("aria-hidden", "true");
|
|
1014
|
-
node.setAttribute("data-kui-background", "");
|
|
1015
|
-
el.append(node);
|
|
1016
|
-
const overlay = paintsOverlay(options) ? createOverlay(doc, options) : null;
|
|
1017
|
-
if (overlay) {
|
|
1018
|
-
overlay.setAttribute("data-kui-background-overlay", "");
|
|
1019
|
-
el.append(overlay);
|
|
1020
|
-
}
|
|
1021
|
-
const stopPlayback = video ? startPlayback(video, ctx.win, options) : () => {
|
|
1022
|
-
};
|
|
1023
|
-
return {
|
|
1024
|
-
remove: () => {
|
|
1025
|
-
stopPlayback();
|
|
1026
|
-
node.remove();
|
|
1027
|
-
overlay?.remove();
|
|
1028
|
-
}
|
|
1029
|
-
};
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// src/effects/catalog/media-shared.ts
|
|
1033
|
-
var AXIS_DEGREES = { vertical: 0, horizontal: 90 };
|
|
1034
|
-
var UNIT_DEGREES = { deg: 1, grad: 0.9, rad: 180 / Math.PI, turn: 360 };
|
|
1035
|
-
var BAND_OVERLAP_PX = 1;
|
|
1036
|
-
var STAGE_CLASS = "kui-slat-stage";
|
|
1037
|
-
var SLAT_CLASS = "kui-slat-item";
|
|
1038
|
-
var GOLDEN_RATIO_CONJUGATE = 0.6180339887498949;
|
|
1039
|
-
function slatOrder(index, count, from) {
|
|
1040
|
-
if (count <= 1) return 0;
|
|
1041
|
-
switch (from) {
|
|
1042
|
-
case "start":
|
|
1043
|
-
return index;
|
|
1044
|
-
case "end":
|
|
1045
|
-
return count - 1 - index;
|
|
1046
|
-
case "edges":
|
|
1047
|
-
return Math.min(index, count - 1 - index);
|
|
1048
|
-
case "random-ish":
|
|
1049
|
-
return Math.floor(index * GOLDEN_RATIO_CONJUGATE % 1 * count);
|
|
1050
|
-
case "alternate":
|
|
1051
|
-
default:
|
|
1052
|
-
return zigzagRank(index, count);
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
function zigzagRank(index, count) {
|
|
1056
|
-
const fromStart = index;
|
|
1057
|
-
const fromEnd = count - 1 - index;
|
|
1058
|
-
const pair = Math.min(fromStart, fromEnd);
|
|
1059
|
-
return fromStart <= fromEnd ? pair * 2 : pair * 2 + 1;
|
|
1060
|
-
}
|
|
1061
|
-
function slatAngleDegrees(authored, axis) {
|
|
1062
|
-
const trimmed = authored.trim();
|
|
1063
|
-
if (!trimmed) return AXIS_DEGREES[axis];
|
|
1064
|
-
const match = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(deg|rad|grad|turn)?$/.exec(trimmed);
|
|
1065
|
-
if (!match) return AXIS_DEGREES[axis];
|
|
1066
|
-
const value = Number(match[1]);
|
|
1067
|
-
if (!Number.isFinite(value)) return AXIS_DEGREES[axis];
|
|
1068
|
-
const unit = match[2] ?? "deg";
|
|
1069
|
-
const degrees = value * UNIT_DEGREES[unit];
|
|
1070
|
-
return (degrees % 180 + 180) % 180;
|
|
1071
|
-
}
|
|
1072
|
-
function slatTravelVector(angleDegrees) {
|
|
1073
|
-
const theta = angleDegrees * Math.PI / 180;
|
|
1074
|
-
return { x: -Math.sin(theta), y: Math.cos(theta) };
|
|
1075
|
-
}
|
|
1076
|
-
function slatBandClip(index, count, angleDegrees, box) {
|
|
1077
|
-
const { width, height } = box;
|
|
1078
|
-
const theta = angleDegrees * Math.PI / 180;
|
|
1079
|
-
const nx = Math.cos(theta);
|
|
1080
|
-
const ny = Math.sin(theta);
|
|
1081
|
-
const { x: dx, y: dy } = slatTravelVector(angleDegrees);
|
|
1082
|
-
const span = Math.abs(nx) * width + Math.abs(ny) * height;
|
|
1083
|
-
const centreX = width / 2;
|
|
1084
|
-
const centreY = height / 2;
|
|
1085
|
-
const step = span / count;
|
|
1086
|
-
const near = index * step - span / 2 - BAND_OVERLAP_PX;
|
|
1087
|
-
const far = (index + 1) * step - span / 2 + BAND_OVERLAP_PX;
|
|
1088
|
-
const reach = width + height;
|
|
1089
|
-
const corner = (along, across) => `${(centreX + along * nx + across * dx).toFixed(2)}px ${(centreY + along * ny + across * dy).toFixed(2)}px`;
|
|
1090
|
-
return `polygon(${corner(near, reach)}, ${corner(near, -reach)}, ${corner(far, -reach)}, ${corner(far, reach)})`;
|
|
1091
|
-
}
|
|
1092
|
-
function axisLabel(angleDegrees) {
|
|
1093
|
-
if (angleDegrees === 0) return "vertical";
|
|
1094
|
-
if (angleDegrees === 90) return "horizontal";
|
|
1095
|
-
return "diagonal";
|
|
1096
|
-
}
|
|
1097
|
-
function syncStageToImage(stage, img, bands) {
|
|
1098
|
-
const { slats, angleDegrees } = bands;
|
|
1099
|
-
const width = img.offsetWidth;
|
|
1100
|
-
const height = img.offsetHeight;
|
|
1101
|
-
stage.style.top = `${img.offsetTop}px`;
|
|
1102
|
-
stage.style.left = `${img.offsetLeft}px`;
|
|
1103
|
-
stage.style.width = `${width}px`;
|
|
1104
|
-
stage.style.height = `${height}px`;
|
|
1105
|
-
slats.forEach((slat, index) => {
|
|
1106
|
-
slat.style.clipPath = slatBandClip(index, slats.length, angleDegrees, { width, height });
|
|
1107
|
-
});
|
|
1108
|
-
}
|
|
1109
|
-
function watchImageBox(stage, img, win, bands) {
|
|
1110
|
-
const handler = () => syncStageToImage(stage, img, bands);
|
|
1111
|
-
win.addEventListener("resize", handler, { passive: true });
|
|
1112
|
-
const stopWindow = () => win.removeEventListener("resize", handler);
|
|
1113
|
-
const ResizeObserverCtor = win.ResizeObserver;
|
|
1114
|
-
if (!ResizeObserverCtor) return stopWindow;
|
|
1115
|
-
const observer = new ResizeObserverCtor(handler);
|
|
1116
|
-
observer.observe(img);
|
|
1117
|
-
return () => {
|
|
1118
|
-
stopWindow();
|
|
1119
|
-
observer.disconnect();
|
|
1120
|
-
};
|
|
1121
|
-
}
|
|
1122
|
-
function imagePaintStyle(img, win) {
|
|
1123
|
-
const computed = win.getComputedStyle(img);
|
|
1124
|
-
const position = computed.objectPosition || "50% 50%";
|
|
1125
|
-
const fit = computed.objectFit;
|
|
1126
|
-
if (fit === "cover") return { size: "cover", position };
|
|
1127
|
-
if (fit === "contain" || fit === "scale-down") return { size: "contain", position };
|
|
1128
|
-
if (fit === "none") return { size: "auto", position };
|
|
1129
|
-
return { size: "100% 100%", position };
|
|
1130
|
-
}
|
|
1131
|
-
function installSlatStage(el, doc, win, options) {
|
|
1132
|
-
const img = el.querySelector("img");
|
|
1133
|
-
if (!img) return null;
|
|
1134
|
-
const url = img.currentSrc || img.getAttribute("src") || "";
|
|
1135
|
-
if (!url) return null;
|
|
1136
|
-
const { count, angleDegrees, from, fold } = options;
|
|
1137
|
-
const travel = slatTravelVector(angleDegrees);
|
|
1138
|
-
const stage = doc.createElement("div");
|
|
1139
|
-
stage.className = STAGE_CLASS;
|
|
1140
|
-
stage.setAttribute("aria-hidden", "true");
|
|
1141
|
-
stage.dataset.kuiSlatAxis = axisLabel(angleDegrees);
|
|
1142
|
-
stage.dataset.kuiSlatAngle = String(angleDegrees);
|
|
1143
|
-
stage.dataset.kuiSlatFold = String(fold);
|
|
1144
|
-
stage.style.setProperty("--kui-slat-count", String(count));
|
|
1145
|
-
stage.style.setProperty("--kui-slat-dx", travel.x.toFixed(4));
|
|
1146
|
-
stage.style.setProperty("--kui-slat-dy", travel.y.toFixed(4));
|
|
1147
|
-
const paint = imagePaintStyle(img, win);
|
|
1148
|
-
const slats = [];
|
|
1149
|
-
for (let index = 0; index < count; index++) {
|
|
1150
|
-
const slat = doc.createElement("div");
|
|
1151
|
-
slat.className = SLAT_CLASS;
|
|
1152
|
-
slat.style.setProperty("--kui-slat-index", String(index));
|
|
1153
|
-
slat.style.setProperty("--kui-i", String(slatOrder(index, count, from)));
|
|
1154
|
-
slat.style.backgroundImage = `url("${url}")`;
|
|
1155
|
-
slat.style.backgroundSize = paint.size;
|
|
1156
|
-
slat.style.backgroundPosition = paint.position;
|
|
1157
|
-
stage.append(slat);
|
|
1158
|
-
slats.push(slat);
|
|
1159
|
-
}
|
|
1160
|
-
el.append(stage);
|
|
1161
|
-
const bands = { slats, angleDegrees };
|
|
1162
|
-
syncStageToImage(stage, img, bands);
|
|
1163
|
-
const stopWatching = watchImageBox(stage, img, win, bands);
|
|
1164
|
-
const imageStyles = createStyleLedger(img);
|
|
1165
|
-
imageStyles.set("visibility", "hidden");
|
|
1166
|
-
return {
|
|
1167
|
-
stage,
|
|
1168
|
-
slats,
|
|
1169
|
-
restore: () => {
|
|
1170
|
-
stopWatching();
|
|
1171
|
-
imageStyles.restore();
|
|
1172
|
-
stage.remove();
|
|
1173
|
-
}
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
function applySlatTimingVars(stage, params) {
|
|
1177
|
-
const { durationMs, delayMs, easing } = params.timing;
|
|
1178
|
-
const duration = durationMs === void 0 ? params.text("duration", "500ms") : `${durationMs}ms`;
|
|
1179
|
-
const delay = delayMs === void 0 ? params.text("delay", "0ms") : `${delayMs}ms`;
|
|
1180
|
-
stage.style.setProperty("--kui-duration", duration);
|
|
1181
|
-
stage.style.setProperty("--kui-delay", delay);
|
|
1182
|
-
stage.style.setProperty("--kui-ease", easing ?? params.text("ease", "ease-out"));
|
|
1183
|
-
stage.style.setProperty("--kui-stagger", params.text("stagger", "60ms"));
|
|
1184
|
-
}
|
|
1185
|
-
function slatAssembleFinishMs(params, count) {
|
|
1186
|
-
if (count === 0) return 0;
|
|
1187
|
-
const durationMs = params.timing.durationMs ?? params.ms("duration", 500);
|
|
1188
|
-
const delayMs = params.timing.delayMs ?? params.ms("delay", 0);
|
|
1189
|
-
const staggerMs = params.ms("stagger", 60);
|
|
1190
|
-
return delayMs + (count - 1) * staggerMs + durationMs;
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// src/effects/catalog/media.ts
|
|
1194
|
-
var geometry = {
|
|
1195
|
-
distance: { type: "length", default: "24px", cssProperty: "--kui-distance" },
|
|
1196
|
-
scale: { type: "number", default: "1.12", cssProperty: "--kui-to-scale" }
|
|
1197
|
-
};
|
|
1198
|
-
var MEDIA_CSS_PRIMITIVES = [
|
|
1199
|
-
cssPrimitive("media-wipe", [CHANNEL.clip]),
|
|
1200
|
-
cssPrimitive("media-mask", ["mask"], { perfClass: "paint" }),
|
|
1201
|
-
// Not `reducedMotion: 'disable'` — that policy means "no finite duration would make sense,
|
|
1202
|
-
// because the animation never ends" (see `ambient.ts`/`feedback.ts`), and a Ken Burns pan/zoom
|
|
1203
|
-
// is the opposite of that: a one-shot cinematic move with a real, shortenable duration. The demo
|
|
1204
|
-
// authors it as `ken-burns 9000ms` (a still image, one slow zoom, then it holds) and
|
|
1205
|
-
// `ken-burns 3000ms on:hover` (zooms in while hovered), and its complement `ken-burns-out` is a
|
|
1206
|
-
// second one-shot preset for the reverse move — not a `-loop` variant the way `typewriter-loop`
|
|
1207
|
-
// or `marquee`/`marquee-scroll-linked` are. `kui-ken-burns`'s keyframe (`scale: 1` to `1.12`,
|
|
1208
|
-
// no loop-safe midpoint) is shaped for exactly that: run once, land on the zoomed frame, stay
|
|
1209
|
-
// there. `'disable'` here previously looked like the same missing-`--kui-fx-*-iterations` bug as
|
|
1210
|
-
// `marquee`/`gradient-shimmer`, but the actual defect was this policy — the default `'shorten'`
|
|
1211
|
-
// is correct, so no `--kui-fx-ken-burns-iterations` is needed at all.
|
|
1212
|
-
cssPrimitive("media-ken-burns", [CHANNEL.translate, CHANNEL.scale], {
|
|
1213
|
-
parameters: geometry
|
|
1214
|
-
}),
|
|
1215
|
-
cssPrimitive("media-filter", [CHANNEL.filter], {
|
|
1216
|
-
defaultActivation: "hover",
|
|
1217
|
-
perfClass: "paint"
|
|
1218
|
-
}),
|
|
1219
|
-
// `geometry.distance` only, not `...geometry`: `kui-blur-up` (media.css) reads `--kui-distance`
|
|
1220
|
-
// and `--kui-blur` but never `--kui-to-scale` — this primitive doesn't even declare
|
|
1221
|
-
// `CHANNEL.scale`. Spreading the whole shared object used to expose `scale:` as an
|
|
1222
|
-
// apparently-valid, silently-inert parameter, the same shape `flip-3d`'s dead `perspective`
|
|
1223
|
-
// parameter was (`entrance.css`'s comment on `kui-flip-in-x`).
|
|
1224
|
-
cssPrimitive("media-blur-up", [CHANNEL.translate, CHANNEL.filter], {
|
|
1225
|
-
parameters: {
|
|
1226
|
-
distance: geometry.distance,
|
|
1227
|
-
blur: { type: "length", default: "16px", cssProperty: "--kui-blur" }
|
|
1228
|
-
},
|
|
1229
|
-
perfClass: "paint"
|
|
1230
|
-
}),
|
|
1231
|
-
// No `defaultActivation` — same convention `core.ts`'s `parallax`/`parallax-scale`/
|
|
1232
|
-
// `parallax-rotate`/`scroll-fade`/`desaturate`/`skew`/`progress`/`progress-stroke` already use
|
|
1233
|
-
// for every other `timelines: ['view', 'scroll', ...]` primitive. `resolveActivation`
|
|
1234
|
-
// (`animator.ts`) only consults `defaultActivation` when the author named no activation, and
|
|
1235
|
-
// falls through to `element-config.ts`'s hardcoded `'enter'` when a primitive declares none.
|
|
1236
|
-
// Setting it to `'manual'` here (matching `activations: ['manual']`) looked like the obviously
|
|
1237
|
-
// correct pairing, but it is what actually broke the effect: `effectiveActivation`
|
|
1238
|
-
// (`style-plan.ts`) only converts a stuck `'manual'` into `'enter'` when `config.timeline !==
|
|
1239
|
-
// 'time'` — i.e. only once the author has actually written `timeline:view`/`timeline:scroll`.
|
|
1240
|
-
// Authored bare (no `timeline:`, the sweep's own probe and the likely first thing anyone
|
|
1241
|
-
// tries), `config.timeline` stays the default `'time'`, that conversion never fires, and the
|
|
1242
|
-
// element sits at `data-kui-state="ready"` forever — which is also the state
|
|
1243
|
-
// `entrance.css`'s `[data-kui-state='ready'] { --kui-distance: 0px !important; }` targets, so
|
|
1244
|
-
// every sample read the same permanently-zeroed `--kui-distance`: not a paused animation, a
|
|
1245
|
-
// zeroed one. `timeline:view`/`timeline:scroll` usage is unaffected either way, since a native
|
|
1246
|
-
// timeline resolves to the `'native-timeline'` gate before activation is even consulted.
|
|
1247
|
-
// `geometry.distance` only: `kui-image-parallax-frame` never reads `--kui-to-scale` and this
|
|
1248
|
-
// primitive doesn't declare `CHANNEL.scale` — same dead-parameter shape as `media-blur-up` above.
|
|
1249
|
-
cssPrimitive("media-parallax-frame", [CHANNEL.translate], {
|
|
1250
|
-
parameters: { distance: geometry.distance },
|
|
1251
|
-
timelines: ["view", "scroll"],
|
|
1252
|
-
activations: ["manual"],
|
|
1253
|
-
reducedMotion: "disable"
|
|
1254
|
-
}),
|
|
1255
|
-
// Its own `scale` parameter, not `geometry`: `kui-lightbox-open` opens from a fixed 0.92, never
|
|
1256
|
-
// reading `--kui-to-scale` (or any `distance`, since this primitive doesn't animate position at
|
|
1257
|
-
// all) — the whole shared object was dead weight here. Unlike `media-blur-up`/
|
|
1258
|
-
// `media-parallax-frame`, this primitive *does* declare `CHANNEL.scale`, so the fix is to wire
|
|
1259
|
-
// the keyframe up to a real parameter rather than remove the promise: `--kui-from-scale`, the
|
|
1260
|
-
// same name and "starts at this scale, animates to 1" meaning `scale`/`scale-move`
|
|
1261
|
-
// (`catalog/core.ts`) already use, so `lightbox-open scale:0.8` now does what it looks like it
|
|
1262
|
-
// should. See `kui-lightbox-open` in `media.css`.
|
|
1263
|
-
cssPrimitive("media-lightbox", [CHANNEL.opacity, CHANNEL.scale], {
|
|
1264
|
-
parameters: { scale: { type: "number", default: "0.92", cssProperty: "--kui-from-scale" } }
|
|
1265
|
-
})
|
|
1266
|
-
];
|
|
1267
|
-
var MEDIA_CSS_PRESETS = [
|
|
1268
|
-
{ name: "wipe-up", primitive: "media-wipe", keyframes: "kui-wipe-up", cloak: true },
|
|
1269
|
-
{ name: "wipe-down", primitive: "media-wipe", keyframes: "kui-wipe-down", cloak: true },
|
|
1270
|
-
{ name: "wipe-left", primitive: "media-wipe", keyframes: "kui-wipe-left", cloak: true },
|
|
1271
|
-
{ name: "wipe-right", primitive: "media-wipe", keyframes: "kui-wipe-right", cloak: true },
|
|
1272
|
-
{ name: "wipe-circle", primitive: "media-wipe", keyframes: "kui-wipe-circle", cloak: true },
|
|
1273
|
-
{ name: "wipe-diagonal", primitive: "media-wipe", keyframes: "kui-wipe-diagonal", cloak: true },
|
|
1274
|
-
{ name: "mask-reveal", primitive: "media-mask", keyframes: "kui-mask-reveal", cloak: true },
|
|
1275
|
-
{ name: "curtain-reveal", primitive: "media-wipe", keyframes: "kui-curtain-reveal", cloak: true },
|
|
1276
|
-
{ name: "ken-burns", primitive: "media-ken-burns", keyframes: "kui-ken-burns" },
|
|
1277
|
-
{ name: "ken-burns-out", primitive: "media-ken-burns", keyframes: "kui-ken-burns-out" },
|
|
1278
|
-
{ name: "blur-up", primitive: "media-blur-up", keyframes: "kui-blur-up", cloak: true },
|
|
1279
|
-
{ name: "duotone-hover", primitive: "media-filter", keyframes: "kui-duotone-hover" },
|
|
1280
|
-
{ name: "grayscale-hover", primitive: "media-filter", keyframes: "kui-grayscale-hover" },
|
|
1281
|
-
{ name: "saturate-hover", primitive: "media-filter", keyframes: "kui-saturate-hover" },
|
|
1282
|
-
{
|
|
1283
|
-
name: "image-parallax-frame",
|
|
1284
|
-
primitive: "media-parallax-frame",
|
|
1285
|
-
keyframes: "kui-image-parallax-frame"
|
|
1286
|
-
},
|
|
1287
|
-
{ name: "before-after-wipe", primitive: "media-wipe", keyframes: "kui-before-after-wipe" },
|
|
1288
|
-
{ name: "lightbox-open", primitive: "media-lightbox", keyframes: "kui-lightbox-open" }
|
|
1289
|
-
];
|
|
1290
|
-
var slatParams = {
|
|
1291
|
-
slats: {
|
|
1292
|
-
type: "number",
|
|
1293
|
-
default: "8",
|
|
1294
|
-
cssProperty: "--kui-slats",
|
|
1295
|
-
minimum: 2,
|
|
1296
|
-
maximum: 24,
|
|
1297
|
-
integer: true
|
|
1298
|
-
},
|
|
1299
|
-
axis: {
|
|
1300
|
-
type: "keyword",
|
|
1301
|
-
default: "vertical",
|
|
1302
|
-
cssProperty: "--kui-axis",
|
|
1303
|
-
values: ["vertical", "horizontal"]
|
|
1304
|
-
},
|
|
1305
|
-
/*
|
|
1306
|
-
* The general form of `axis:`, in degrees: `0deg` is `axis:vertical`, `90deg` is
|
|
1307
|
-
* `axis:horizontal`, and anything between cuts the picture into diagonal bands. An authored
|
|
1308
|
-
* angle wins; leaving it off reads `axis:`, so every existing attribute keeps its meaning.
|
|
1309
|
-
*
|
|
1310
|
-
* `text`, not `angle`, for one reason: `readParams` pre-fills every declared parameter with its
|
|
1311
|
-
* schema default, so a typed default is indistinguishable from an authored value and there would
|
|
1312
|
-
* be no way to tell `angle:0deg` from "no angle, use the axis". An empty default is only possible
|
|
1313
|
-
* on a type that is never written to a stylesheet, which is exactly what `text` is for — and this
|
|
1314
|
-
* value never reaches CSS anyway. `slatAngleDegrees` does the parsing and the range clamp.
|
|
1315
|
-
*/
|
|
1316
|
-
angle: { type: "text", default: "", cssProperty: "--kui-slat-angle" },
|
|
1317
|
-
from: {
|
|
1318
|
-
type: "keyword",
|
|
1319
|
-
default: "alternate",
|
|
1320
|
-
cssProperty: "--kui-from",
|
|
1321
|
-
values: ["alternate", "start", "end", "edges", "random-ish"]
|
|
1322
|
-
},
|
|
1323
|
-
fold: { type: "keyword", default: "false", cssProperty: "--kui-fold", values: ["true", "false"] },
|
|
1324
|
-
duration: { type: "time", default: "500ms", cssProperty: "--kui-duration" },
|
|
1325
|
-
...TRIGGER_DELAY_PARAM,
|
|
1326
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" },
|
|
1327
|
-
stagger: { type: "time", default: "60ms", cssProperty: "--kui-stagger" }
|
|
1328
|
-
};
|
|
1329
|
-
function prepareSlatAssemble(el, params, ctx) {
|
|
1330
|
-
const doc = el.ownerDocument;
|
|
1331
|
-
const count = Math.min(24, Math.max(2, Math.round(params.num("slats", 8))));
|
|
1332
|
-
const axis = params.text("axis", "vertical");
|
|
1333
|
-
const angleDegrees = slatAngleDegrees(params.text("angle", ""), axis);
|
|
1334
|
-
const from = params.text("from", "alternate");
|
|
1335
|
-
const fold = params.is("fold");
|
|
1336
|
-
const node = el;
|
|
1337
|
-
if (ctx.win.getComputedStyle(node).position === "static") ctx.style.set("position", "relative");
|
|
1338
|
-
const built = installSlatStage(el, doc, ctx.win, { count, angleDegrees, from, fold });
|
|
1339
|
-
if (!built) return () => {
|
|
1340
|
-
};
|
|
1341
|
-
const { stage } = built;
|
|
1342
|
-
applySlatTimingVars(stage, params);
|
|
1343
|
-
stage.classList.add("kui-slat-animating");
|
|
1344
|
-
let settle2;
|
|
1345
|
-
const finished = new Promise((resolve) => {
|
|
1346
|
-
settle2 = resolve;
|
|
1347
|
-
});
|
|
1348
|
-
let landed = false;
|
|
1349
|
-
const land = () => {
|
|
1350
|
-
if (landed) return;
|
|
1351
|
-
landed = true;
|
|
1352
|
-
stage.classList.remove("kui-slat-animating");
|
|
1353
|
-
built.restore();
|
|
1354
|
-
settle2();
|
|
1355
|
-
};
|
|
1356
|
-
const timer = ctx.win.setTimeout(land, slatAssembleFinishMs(params, count));
|
|
1357
|
-
return {
|
|
1358
|
-
cleanup: () => {
|
|
1359
|
-
ctx.win.clearTimeout(timer);
|
|
1360
|
-
if (landed) return;
|
|
1361
|
-
landed = true;
|
|
1362
|
-
built.restore();
|
|
1363
|
-
},
|
|
1364
|
-
finished,
|
|
1365
|
-
finish: () => {
|
|
1366
|
-
ctx.win.clearTimeout(timer);
|
|
1367
|
-
land();
|
|
1368
|
-
}
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
var backgroundMediaParams = {
|
|
1372
|
-
/*
|
|
1373
|
-
* `text`, and same-origin-checked at the point of use rather than by the type — identical
|
|
1374
|
-
* shape and identical reasoning to `media-scrub`'s own `src` (`scroll-mechanics/primitives.ts`).
|
|
1375
|
-
* A URL has no lexical shape to validate against, and `type: 'text'` is the one type that never
|
|
1376
|
-
* reaches a stylesheet, which is what makes accepting arbitrary path characters safe.
|
|
1377
|
-
*/
|
|
1378
|
-
src: { type: "text", default: "", cssProperty: "--kui-src" },
|
|
1379
|
-
/*
|
|
1380
|
-
* The still a `<video>` shows before its first frame decodes. Not optional polish: without it a
|
|
1381
|
-
* background clip paints as an empty box for as long as the network takes, and that box is the
|
|
1382
|
-
* backdrop to the author's text — the one place on the page where a flash of nothing is most
|
|
1383
|
-
* visible. Every background video in this repo's own demo pages is authored with one.
|
|
1384
|
-
*/
|
|
1385
|
-
poster: { type: "text", default: "", cssProperty: "--kui-poster" },
|
|
1386
|
-
/*
|
|
1387
|
-
* `fill`, `none` and `scale-down` are deliberately absent. `fill` is the only `object-fit` value
|
|
1388
|
-
* that distorts — it stretches the picture to the box rather than cropping it — and the standing
|
|
1389
|
-
* rule for imagery in this project is to crop, never stretch. The other two leave the media at
|
|
1390
|
-
* its intrinsic size inside a box sized to something else, which for a *backdrop* is a gap, not
|
|
1391
|
-
* a layout. Adding them would be offering three ways to get a broken background.
|
|
1392
|
-
*/
|
|
1393
|
-
fit: {
|
|
1394
|
-
type: "keyword",
|
|
1395
|
-
default: "cover",
|
|
1396
|
-
cssProperty: "--kui-fit",
|
|
1397
|
-
values: ["cover", "contain"]
|
|
1398
|
-
},
|
|
1399
|
-
/*
|
|
1400
|
-
* Which part of the picture a `cover` crop keeps. Nine named points rather than a free
|
|
1401
|
-
* `object-position` string, because a free string would have to be `type: 'text'` — the one type
|
|
1402
|
-
* that is explicitly never written to a stylesheet (see `core/params.ts`) — and this value is
|
|
1403
|
-
* written to one. A keyword list is validated against its own `values`, so the author gets real
|
|
1404
|
-
* focal control and the CSS surface stays closed.
|
|
1405
|
-
*/
|
|
1406
|
-
focus: {
|
|
1407
|
-
type: "keyword",
|
|
1408
|
-
default: "center",
|
|
1409
|
-
cssProperty: "--kui-focus",
|
|
1410
|
-
values: FOCAL_POINT_NAMES
|
|
1411
|
-
},
|
|
1412
|
-
/*
|
|
1413
|
-
* The scrim. This is the parameter that makes the whole effect usable, because the point of a
|
|
1414
|
-
* backdrop here is animated text on top of it, and text over unmodified footage is illegible
|
|
1415
|
-
* about half the time — a light frame arrives and the headline vanishes for those seconds.
|
|
1416
|
-
*
|
|
1417
|
-
* `type: 'color'` so it goes through the same validator every other colour does. `transparent`
|
|
1418
|
-
* as the default rather than an empty string for the same reason: `''` is not a colour, and a
|
|
1419
|
-
* default that its own type would reject is a lie the schema cannot catch. It is also the honest
|
|
1420
|
-
* spelling of "no scrim", and no scrim node is created for it.
|
|
1421
|
-
*/
|
|
1422
|
-
overlay: { type: "color", default: "transparent", cssProperty: "--kui-overlay" },
|
|
1423
|
-
/*
|
|
1424
|
-
* Separate from the colour rather than folded into it. `overlay:rgb(0 0 0 / 45%)` does parse —
|
|
1425
|
-
* the tokenizer is paren-aware — but `overlay:black overlay-opacity:45%` is the spelling someone
|
|
1426
|
-
* reaches for while tuning legibility, and tuning is exactly what this value is for.
|
|
1427
|
-
*/
|
|
1428
|
-
"overlay-opacity": { type: "percentage", default: "100%", cssProperty: "--kui-overlay-opacity" },
|
|
1429
|
-
/*
|
|
1430
|
-
* The opt-out for the play-while-visible behaviour. `in-view` pairs the clip with the viewport
|
|
1431
|
-
* and is right for a long section. `always` is for a short hero clip that must never be caught
|
|
1432
|
-
* mid-stall by a visibility heuristic. `never` installs the clip and leaves it on its poster,
|
|
1433
|
-
* which is also where any mode lands under a reduced-motion preference.
|
|
1434
|
-
*/
|
|
1435
|
-
autoplay: {
|
|
1436
|
-
type: "keyword",
|
|
1437
|
-
default: "in-view",
|
|
1438
|
-
cssProperty: "--kui-autoplay",
|
|
1439
|
-
values: ["in-view", "always", "never"]
|
|
1440
|
-
},
|
|
1441
|
-
/*
|
|
1442
|
-
* Bounded at both ends: `0` is a clip that is loaded, decoding, and permanently frozen — worse
|
|
1443
|
-
* than `autoplay:never`, which at least says so — and browsers stop honouring rates past roughly
|
|
1444
|
-
* 4 anyway, so a larger number is a silent no-op rather than a faster clip.
|
|
1445
|
-
*/
|
|
1446
|
-
rate: {
|
|
1447
|
-
type: "number",
|
|
1448
|
-
default: "1",
|
|
1449
|
-
cssProperty: "--kui-rate",
|
|
1450
|
-
finite: true,
|
|
1451
|
-
minimum: 0.25,
|
|
1452
|
-
maximum: 4
|
|
1453
|
-
},
|
|
1454
|
-
loop: { type: "keyword", default: "true", cssProperty: "--kui-loop", values: ["true", "false"] }
|
|
1455
|
-
/*
|
|
1456
|
-
* There is deliberately no `controls:`. The layer this primitive builds paints at `z-index: -1`
|
|
1457
|
-
* behind the author's own children, so a native control bar there is focusable by keyboard and
|
|
1458
|
-
* occluded by whatever the page happens to put over it — a player you can tab into and cannot
|
|
1459
|
-
* see. A clip meant to be controlled is a content `<video controls>` the author writes, not a
|
|
1460
|
-
* background one.
|
|
1461
|
-
*/
|
|
1462
|
-
};
|
|
1463
|
-
function prepareBackgroundMedia(el, params, ctx) {
|
|
1464
|
-
const authored = params.text("src");
|
|
1465
|
-
if (!authored) {
|
|
1466
|
-
ctx.warn('background-media needs a "src:" \u2014 nothing installed');
|
|
1467
|
-
return () => {
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
const src = mediaSource(authored, "src", ctx);
|
|
1471
|
-
if (!src) return () => {
|
|
1472
|
-
};
|
|
1473
|
-
const node = el;
|
|
1474
|
-
if (ctx.win.getComputedStyle(node).position === "static") ctx.style.set("position", "relative");
|
|
1475
|
-
ctx.style.set("isolation", "isolate");
|
|
1476
|
-
const layer = installBackgroundMedia(el, ctx, {
|
|
1477
|
-
src,
|
|
1478
|
-
poster: mediaSource(params.text("poster"), "poster", ctx),
|
|
1479
|
-
fit: params.is("fit", "contain") ? "contain" : "cover",
|
|
1480
|
-
position: focalPosition(params.text("focus", "center")),
|
|
1481
|
-
overlay: params.text("overlay", "transparent"),
|
|
1482
|
-
// `num` returns a percentage as a 0–1 ratio, which is exactly what `opacity` takes.
|
|
1483
|
-
overlayOpacity: Math.min(1, Math.max(0, params.num("overlay-opacity", 1))),
|
|
1484
|
-
autoplay: params.text("autoplay", "in-view"),
|
|
1485
|
-
rate: params.num("rate", 1),
|
|
1486
|
-
// `!is('loop', 'false')`, not `is('loop')`. Every other read here names its own fallback, and
|
|
1487
|
-
// this one has to as well: `is()` takes no fallback argument, so a bare `is('loop')` is only
|
|
1488
|
-
// true when something already filled the schema default in. That holds on the animator's path
|
|
1489
|
-
// (`readEffectParams` pre-fills every declared parameter) and not on `createParams`, so the
|
|
1490
|
-
// positive spelling silently defaulted a true-by-default parameter to false for any caller
|
|
1491
|
-
// handing over raw values. Reading it as "loop unless explicitly told not to" states the
|
|
1492
|
-
// default at the point of use, where it cannot drift.
|
|
1493
|
-
loop: !params.is("loop", "false"),
|
|
1494
|
-
reducedMotion: ctx.reducedMotion
|
|
1495
|
-
});
|
|
1496
|
-
return continuousSetup(layer.remove);
|
|
1497
|
-
}
|
|
1498
|
-
var MEDIA_JS_PRIMITIVES = [
|
|
1499
|
-
{
|
|
1500
|
-
id: "slat-assemble",
|
|
1501
|
-
renderer: "javascript",
|
|
1502
|
-
channels: [CHANNEL.opacity, CHANNEL.translate, CHANNEL.rotate],
|
|
1503
|
-
parameters: slatParams,
|
|
1504
|
-
supportedTimelines: ["time"],
|
|
1505
|
-
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
1506
|
-
defaultActivation: "enter",
|
|
1507
|
-
perfClass: "dom-transform",
|
|
1508
|
-
// Same reasoning as every JS-rendered primitive in `text.ts`: nothing here declares a CSS
|
|
1509
|
-
// `animation-duration` the reduced-motion policy layer could shorten, and `disable` is what
|
|
1510
|
-
// stops `installSlatStage`'s DOM surgery from ever running at all under reduced motion — the
|
|
1511
|
-
// animator never calls `activate()`, so the wrapped `<img>` is simply left exactly as authored.
|
|
1512
|
-
reducedMotion: "disable",
|
|
1513
|
-
// Land()ing hands the picture back to the real `<img>` and tears down every slat — see
|
|
1514
|
-
// `prepareSlatAssemble`'s `land()`. Declared, not assumed: see `restoresOnFinish`'s own comment
|
|
1515
|
-
// in `core/types.ts` for why the catalog's default is the opposite of this.
|
|
1516
|
-
restoresOnFinish: true,
|
|
1517
|
-
prepare: deferPrepare(prepareSlatAssemble)
|
|
1518
|
-
},
|
|
1519
|
-
{
|
|
1520
|
-
id: "background-media",
|
|
1521
|
-
/*
|
|
1522
|
-
* `media` is the same word `media-scrub` uses for "this effect owns what the element shows",
|
|
1523
|
-
* and it is what makes `background-media, video-scrub` on one element a reported conflict
|
|
1524
|
-
* rather than two effects silently fighting over the same picture.
|
|
1525
|
-
*
|
|
1526
|
-
* `layout` is claimed for the same reason `pin` claims it: preparation writes `position` and
|
|
1527
|
-
* `isolation` on the *host*, which is a stacking-context claim on someone else's element. Left
|
|
1528
|
-
* undeclared, `background-media, pin-section` composed silently while both decided what
|
|
1529
|
-
* `position` the host has — the conflict detector cannot report a claim it was never told about.
|
|
1530
|
-
*/
|
|
1531
|
-
channels: ["media", "layout"],
|
|
1532
|
-
renderer: "javascript",
|
|
1533
|
-
parameters: backgroundMediaParams,
|
|
1534
|
-
// Not a claim to support four timelines — an abstention. A backdrop is not driven by progress
|
|
1535
|
-
// of any kind and this primitive never reads `Timeline`; the list exists only so that
|
|
1536
|
-
// `data-kui="background-media src:/hero.mp4, parallax"` plus a `timeline:view` survives
|
|
1537
|
-
// `compile.ts`'s `intersect`. See `TIMELINE_AGNOSTIC` (`effects/shared.ts`), shared with the
|
|
1538
|
-
// scroll-mechanics drivers, which abstain for the same reason.
|
|
1539
|
-
supportedTimelines: TIMELINE_AGNOSTIC,
|
|
1540
|
-
supportedActivations: ["load", "enter", "manual"],
|
|
1541
|
-
/*
|
|
1542
|
-
* `'load'`, not the catalog's usual `'enter'`, and this is the difference between working and
|
|
1543
|
-
* not. A backdrop is the element's appearance, so gating it on an IntersectionObserver means a
|
|
1544
|
-
* section that is already on screen at page load, one in a background tab (no IO callbacks
|
|
1545
|
-
* fire at all until the tab is foregrounded), or one whose own box is still zero-area waits an
|
|
1546
|
-
* unbounded time to have any background — and unlike a missed reveal, that is a visibly broken
|
|
1547
|
-
* page. An author who *wants* a heavy clip deferred can still write `on:enter`.
|
|
1548
|
-
*/
|
|
1549
|
-
defaultActivation: "load",
|
|
1550
|
-
perfClass: "paint",
|
|
1551
|
-
/*
|
|
1552
|
-
* `'shorten'`, unlike every other JS-rendered primitive in this file and in `text.ts`, and
|
|
1553
|
-
* deliberately so. `'disable'` means the animator never calls `activate()` under a reduced
|
|
1554
|
-
* motion preference, which for an animation is exactly right and for this is not: it would
|
|
1555
|
-
* leave the element with no backdrop at all rather than a calmer one. There is no CSS duration
|
|
1556
|
-
* here for `'shorten'` to shorten, so the policy is inert and the effect installs normally;
|
|
1557
|
-
* `ctx.reducedMotion` is then read inside, where it suppresses the one genuinely motion-y part
|
|
1558
|
-
* — a clip's autoplay — and leaves the poster frame standing. See `autoplayInView`.
|
|
1559
|
-
*/
|
|
1560
|
-
reducedMotion: "shorten",
|
|
1561
|
-
prepare: deferPrepare(prepareBackgroundMedia)
|
|
1562
|
-
}
|
|
1563
|
-
];
|
|
1564
|
-
var MEDIA_JS_PRESETS = [
|
|
1565
|
-
{ name: "slat-assemble", primitive: "slat-assemble", cloak: true },
|
|
1566
|
-
/*
|
|
1567
|
-
* Two names, one primitive — the alias shape the catalog already uses everywhere (`pin-until`,
|
|
1568
|
-
* `pin-spacer` and `stacking-cards` are three names over the one `pin` primitive; six `wipe-*`
|
|
1569
|
-
* names share `media-wipe`). A preset row is the alias mechanism, so a second spelling costs a
|
|
1570
|
-
* table entry and nothing else: no duplicated implementation to keep in sync, and both names
|
|
1571
|
-
* resolve to the same `prepare`.
|
|
1572
|
-
*
|
|
1573
|
-
* No `cloak` on either: the pre-JS cloak rule hides an element until the runtime installs the
|
|
1574
|
-
* effect's from-state, and this element is the author's own content. Cloaking it would blank
|
|
1575
|
-
* their text for as long as the bundle takes to arrive, to hide a backdrop that has no
|
|
1576
|
-
* from-state at all.
|
|
1577
|
-
*/
|
|
1578
|
-
{ name: "bg", primitive: "background-media" },
|
|
1579
|
-
{ name: "background", primitive: "background-media" }
|
|
1580
|
-
];
|
|
1581
|
-
var MEDIA_PRIMITIVES = [...MEDIA_CSS_PRIMITIVES, ...MEDIA_JS_PRIMITIVES];
|
|
1582
|
-
var MEDIA_PRESETS = [...MEDIA_CSS_PRESETS, ...MEDIA_JS_PRESETS];
|
|
1583
|
-
function registerMedia(registry) {
|
|
1584
|
-
return registry.registerPrimitives(MEDIA_PRIMITIVES).registerPresets(MEDIA_PRESETS);
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
// src/effects/catalog/subtree-capture.ts
|
|
1588
|
-
function captureChildren(el) {
|
|
1589
|
-
const authored = Array.from(el.childNodes);
|
|
1590
|
-
return () => {
|
|
1591
|
-
el.replaceChildren(...authored);
|
|
1592
|
-
};
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
// src/effects/catalog/numbers-shared.ts
|
|
1596
|
-
var SR_ONLY_CLASS = "kui-sr-only";
|
|
1597
|
-
var DECORATIVE_CLASS = "kui-count-decorative";
|
|
1598
|
-
function installCountLayers(el, doc) {
|
|
1599
|
-
const restoreChildren = captureChildren(el);
|
|
1600
|
-
const decorative = doc.createElement("span");
|
|
1601
|
-
decorative.setAttribute("aria-hidden", "true");
|
|
1602
|
-
decorative.className = DECORATIVE_CLASS;
|
|
1603
|
-
const srOnly = doc.createElement("span");
|
|
1604
|
-
srOnly.className = SR_ONLY_CLASS;
|
|
1605
|
-
srOnly.setAttribute("aria-live", "polite");
|
|
1606
|
-
el.textContent = "";
|
|
1607
|
-
el.append(decorative, srOnly);
|
|
1608
|
-
return {
|
|
1609
|
-
decorative,
|
|
1610
|
-
srOnly,
|
|
1611
|
-
restore: restoreChildren
|
|
1612
|
-
};
|
|
1613
|
-
}
|
|
1614
|
-
function easeOutCubic(t) {
|
|
1615
|
-
const clamped = Math.min(Math.max(t, 0), 1);
|
|
1616
|
-
return 1 - (1 - clamped) ** 3;
|
|
1617
|
-
}
|
|
1618
|
-
function tweenValue(t, from, to) {
|
|
1619
|
-
return from + (to - from) * t;
|
|
1620
|
-
}
|
|
1621
|
-
function cubicBezier(x1, y1, x2, y2) {
|
|
1622
|
-
const cx = 3 * x1;
|
|
1623
|
-
const bx = 3 * (x2 - x1) - cx;
|
|
1624
|
-
const ax = 1 - cx - bx;
|
|
1625
|
-
const cy = 3 * y1;
|
|
1626
|
-
const by = 3 * (y2 - y1) - cy;
|
|
1627
|
-
const ay = 1 - cy - by;
|
|
1628
|
-
const sampleX = (t) => ((ax * t + bx) * t + cx) * t;
|
|
1629
|
-
const sampleY = (t) => ((ay * t + by) * t + cy) * t;
|
|
1630
|
-
const slopeX = (t) => (3 * ax * t + 2 * bx) * t + cx;
|
|
1631
|
-
function solveT(x) {
|
|
1632
|
-
let t = x;
|
|
1633
|
-
for (let i = 0; i < 8; i++) {
|
|
1634
|
-
const slope = slopeX(t);
|
|
1635
|
-
if (Math.abs(slope) < 1e-6) break;
|
|
1636
|
-
const next = t - (sampleX(t) - x) / slope;
|
|
1637
|
-
if (Math.abs(sampleX(next) - x) < 1e-6) return next;
|
|
1638
|
-
t = next;
|
|
1639
|
-
}
|
|
1640
|
-
let lo = 0;
|
|
1641
|
-
let hi = 1;
|
|
1642
|
-
t = x;
|
|
1643
|
-
while (hi - lo > 1e-6) {
|
|
1644
|
-
if (sampleX(t) < x) lo = t;
|
|
1645
|
-
else hi = t;
|
|
1646
|
-
t = (lo + hi) / 2;
|
|
1647
|
-
}
|
|
1648
|
-
return t;
|
|
1649
|
-
}
|
|
1650
|
-
return (t) => sampleY(solveT(Math.min(Math.max(t, 0), 1)));
|
|
1651
|
-
}
|
|
1652
|
-
var EASING_KEYWORDS = {
|
|
1653
|
-
ease: [0.25, 0.1, 0.25, 1],
|
|
1654
|
-
"ease-in": [0.42, 0, 1, 1],
|
|
1655
|
-
"ease-out": [0, 0, 0.58, 1],
|
|
1656
|
-
"ease-in-out": [0.42, 0, 0.58, 1],
|
|
1657
|
-
"expo-in": [0.7, 0, 0.84, 0],
|
|
1658
|
-
"expo-out": [0.16, 1, 0.3, 1],
|
|
1659
|
-
"expo-in-out": [0.87, 0, 0.13, 1],
|
|
1660
|
-
"back-in": [0.36, 0, 0.66, -0.56],
|
|
1661
|
-
"back-out": [0.34, 1.56, 0.64, 1],
|
|
1662
|
-
"back-in-out": [0.68, -0.6, 0.32, 1.6],
|
|
1663
|
-
"quart-out": [0.25, 1, 0.5, 1],
|
|
1664
|
-
"circ-out": [0, 0.55, 0.45, 1]
|
|
1665
|
-
};
|
|
1666
|
-
var CUBIC_BEZIER_FN = /^cubic-bezier\(\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*,\s*(-?[\d.]+)\s*\)$/i;
|
|
1667
|
-
function resolveEasing(easing, warn) {
|
|
1668
|
-
if (easing === void 0) return easeOutCubic;
|
|
1669
|
-
if (easing === "linear") return (t) => Math.min(Math.max(t, 0), 1);
|
|
1670
|
-
const keyword = EASING_KEYWORDS[easing];
|
|
1671
|
-
if (keyword) return cubicBezier(...keyword);
|
|
1672
|
-
const fn = CUBIC_BEZIER_FN.exec(easing);
|
|
1673
|
-
if (fn) return cubicBezier(Number(fn[1]), Number(fn[2]), Number(fn[3]), Number(fn[4]));
|
|
1674
|
-
warn(`easing "${easing}" has no JS equivalent for a counter tween \u2014 using the default ease-out`);
|
|
1675
|
-
return easeOutCubic;
|
|
1676
|
-
}
|
|
1677
|
-
var COMPACT_OPTIONS = { notation: "compact", maximumFractionDigits: 1 };
|
|
1678
|
-
function formatCount(value, options) {
|
|
1679
|
-
const { format, decimals, currency } = options;
|
|
1680
|
-
if (format === "currency") {
|
|
1681
|
-
return new Intl.NumberFormat(void 0, {
|
|
1682
|
-
style: "currency",
|
|
1683
|
-
currency,
|
|
1684
|
-
minimumFractionDigits: decimals,
|
|
1685
|
-
maximumFractionDigits: decimals
|
|
1686
|
-
}).format(value);
|
|
1687
|
-
}
|
|
1688
|
-
if (format === "percent") {
|
|
1689
|
-
return new Intl.NumberFormat(void 0, {
|
|
1690
|
-
style: "percent",
|
|
1691
|
-
minimumFractionDigits: decimals,
|
|
1692
|
-
maximumFractionDigits: decimals
|
|
1693
|
-
}).format(value);
|
|
1694
|
-
}
|
|
1695
|
-
if (format === "compact") {
|
|
1696
|
-
return new Intl.NumberFormat(void 0, COMPACT_OPTIONS).format(value);
|
|
1697
|
-
}
|
|
1698
|
-
return new Intl.NumberFormat(void 0, {
|
|
1699
|
-
minimumFractionDigits: decimals,
|
|
1700
|
-
maximumFractionDigits: decimals
|
|
1701
|
-
}).format(value);
|
|
1702
|
-
}
|
|
1703
|
-
function paddedDigits(value, width) {
|
|
1704
|
-
return Math.round(Math.max(0, value)).toString().padStart(width, "0");
|
|
1705
|
-
}
|
|
1706
|
-
function groupDigits(digits) {
|
|
1707
|
-
const groups = [];
|
|
1708
|
-
for (let end = digits.length; end > 0; end -= 3) {
|
|
1709
|
-
groups.unshift(digits.slice(Math.max(0, end - 3), end));
|
|
1710
|
-
}
|
|
1711
|
-
return groups.join(",");
|
|
1712
|
-
}
|
|
1713
|
-
function odometerTokens(grouped) {
|
|
1714
|
-
return [...grouped].map((char) => ({ char, digit: char >= "0" && char <= "9" }));
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
// src/effects/catalog/numbers.ts
|
|
1718
|
-
var COUNT_STEP_MS = 16;
|
|
1719
|
-
var REDUCED_MOTION_DURATION_MS = 1;
|
|
1720
|
-
var countParams = {
|
|
1721
|
-
duration: { type: "time", default: "1600ms", cssProperty: "--kui-duration" },
|
|
1722
|
-
...TRIGGER_DELAY_PARAM,
|
|
1723
|
-
from: { type: "number", default: "0", cssProperty: "--kui-from" },
|
|
1724
|
-
to: { type: "number", default: "100", cssProperty: "--kui-to" },
|
|
1725
|
-
decimals: {
|
|
1726
|
-
type: "number",
|
|
1727
|
-
default: "0",
|
|
1728
|
-
cssProperty: "--kui-decimals",
|
|
1729
|
-
integer: true,
|
|
1730
|
-
minimum: 0,
|
|
1731
|
-
maximum: 6
|
|
1732
|
-
},
|
|
1733
|
-
format: {
|
|
1734
|
-
type: "keyword",
|
|
1735
|
-
default: "number",
|
|
1736
|
-
cssProperty: "--kui-format",
|
|
1737
|
-
values: ["number", "currency", "percent", "compact"]
|
|
1738
|
-
},
|
|
1739
|
-
currency: { type: "text", default: "USD", cssProperty: "--kui-currency" }
|
|
1740
|
-
};
|
|
1741
|
-
var odometerParams = {
|
|
1742
|
-
duration: { type: "time", default: "1600ms", cssProperty: "--kui-duration" },
|
|
1743
|
-
...TRIGGER_DELAY_PARAM,
|
|
1744
|
-
from: { type: "number", default: "0", cssProperty: "--kui-from" },
|
|
1745
|
-
to: { type: "number", default: "100", cssProperty: "--kui-to" }
|
|
1746
|
-
};
|
|
1747
|
-
function countPrimitive(id, parameters, prepare) {
|
|
1748
|
-
return {
|
|
1749
|
-
id,
|
|
1750
|
-
renderer: "javascript",
|
|
1751
|
-
channels: ["content"],
|
|
1752
|
-
parameters,
|
|
1753
|
-
supportedTimelines: ["time"],
|
|
1754
|
-
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
1755
|
-
defaultActivation: "enter",
|
|
1756
|
-
perfClass: "dom-transform",
|
|
1757
|
-
// Not 'disable': the animator would then skip activation entirely under reduced motion,
|
|
1758
|
-
// leaving the counter permanently blank. `prepareTween` reads `ctx.reducedMotion` itself and
|
|
1759
|
-
// collapses the ramp to one effectively-instant tick instead, so the final value still lands.
|
|
1760
|
-
reducedMotion: "shorten",
|
|
1761
|
-
prepare
|
|
1762
|
-
};
|
|
1763
|
-
}
|
|
1764
|
-
function tweenDurationMs(params, ctx) {
|
|
1765
|
-
return ctx.reducedMotion ? REDUCED_MOTION_DURATION_MS : Math.max(0, effectDurationMs(params, 1600));
|
|
1766
|
-
}
|
|
1767
|
-
function tweenTimingFor(params, ctx) {
|
|
1768
|
-
return {
|
|
1769
|
-
durationMs: tweenDurationMs(params, ctx),
|
|
1770
|
-
// Positional first, then the same-named parameter — the two-spellings rule `effectDurationMs`
|
|
1771
|
-
// applies to `duration` just above, now applied to `delay` as well.
|
|
1772
|
-
delayMs: Math.max(0, params.timing.delayMs ?? params.ms("delay", 0)),
|
|
1773
|
-
easing: resolveEasing(params.timing.easing, ctx.warn)
|
|
1774
|
-
};
|
|
1775
|
-
}
|
|
1776
|
-
function tweenNumber(ctx, tween) {
|
|
1777
|
-
const { from, to, durationMs, delayMs, easing, onTick } = tween;
|
|
1778
|
-
let elapsed = 0;
|
|
1779
|
-
let handle;
|
|
1780
|
-
let settle2;
|
|
1781
|
-
const finished = new Promise((resolve) => {
|
|
1782
|
-
settle2 = resolve;
|
|
1783
|
-
});
|
|
1784
|
-
const stop = () => {
|
|
1785
|
-
ctx.win.clearTimeout(start);
|
|
1786
|
-
if (handle !== void 0) ctx.win.clearInterval(handle);
|
|
1787
|
-
};
|
|
1788
|
-
const tick = () => {
|
|
1789
|
-
elapsed += COUNT_STEP_MS;
|
|
1790
|
-
const ratio = durationMs <= 0 ? 1 : Math.min(elapsed / durationMs, 1);
|
|
1791
|
-
const done = ratio >= 1;
|
|
1792
|
-
onTick(tweenValue(easing(ratio), from, to), done);
|
|
1793
|
-
if (done) {
|
|
1794
|
-
stop();
|
|
1795
|
-
settle2();
|
|
1796
|
-
}
|
|
1797
|
-
};
|
|
1798
|
-
const start = ctx.win.setTimeout(() => {
|
|
1799
|
-
handle = ctx.win.setInterval(tick, COUNT_STEP_MS);
|
|
1800
|
-
}, delayMs);
|
|
1801
|
-
return {
|
|
1802
|
-
cleanup: stop,
|
|
1803
|
-
finished,
|
|
1804
|
-
finish: () => {
|
|
1805
|
-
stop();
|
|
1806
|
-
onTick(to, true);
|
|
1807
|
-
settle2();
|
|
1808
|
-
}
|
|
1809
|
-
};
|
|
1810
|
-
}
|
|
1811
|
-
function prepareCount(el, params, ctx) {
|
|
1812
|
-
const doc = el.ownerDocument;
|
|
1813
|
-
const from = params.num("from", 0);
|
|
1814
|
-
const to = params.num("to", 100);
|
|
1815
|
-
const decimals = Math.max(0, Math.round(params.num("decimals", 0)));
|
|
1816
|
-
const format = params.text("format", "number");
|
|
1817
|
-
const currency = params.text("currency", "USD");
|
|
1818
|
-
const options = { format, decimals, currency };
|
|
1819
|
-
const layers = installCountLayers(el, doc);
|
|
1820
|
-
layers.decorative.textContent = formatCount(from, options);
|
|
1821
|
-
layers.srOnly.textContent = formatCount(from, options);
|
|
1822
|
-
const tween = tweenNumber(ctx, {
|
|
1823
|
-
from,
|
|
1824
|
-
to,
|
|
1825
|
-
...tweenTimingFor(params, ctx),
|
|
1826
|
-
onTick: (value, done) => {
|
|
1827
|
-
layers.decorative.textContent = formatCount(value, options);
|
|
1828
|
-
if (done) layers.srOnly.textContent = formatCount(to, options);
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
|
-
return {
|
|
1832
|
-
cleanup: () => {
|
|
1833
|
-
tween.cleanup();
|
|
1834
|
-
layers.restore();
|
|
1835
|
-
},
|
|
1836
|
-
finished: tween.finished,
|
|
1837
|
-
finish: tween.finish
|
|
1838
|
-
};
|
|
1839
|
-
}
|
|
1840
|
-
function buildOdometerColumns(container, doc, grouped) {
|
|
1841
|
-
const strips = [];
|
|
1842
|
-
for (const token of odometerTokens(grouped)) {
|
|
1843
|
-
if (!token.digit) {
|
|
1844
|
-
container.append(doc.createTextNode(token.char));
|
|
1845
|
-
continue;
|
|
1846
|
-
}
|
|
1847
|
-
const column = doc.createElement("span");
|
|
1848
|
-
column.className = "kui-odometer-col";
|
|
1849
|
-
const strip = doc.createElement("span");
|
|
1850
|
-
strip.className = "kui-odometer-strip";
|
|
1851
|
-
for (let digit = 0; digit <= 9; digit++) {
|
|
1852
|
-
const row = doc.createElement("span");
|
|
1853
|
-
row.textContent = String(digit);
|
|
1854
|
-
strip.append(row);
|
|
1855
|
-
}
|
|
1856
|
-
strip.style.setProperty("--kui-o", token.char);
|
|
1857
|
-
column.append(strip);
|
|
1858
|
-
container.append(column);
|
|
1859
|
-
strips.push(strip);
|
|
1860
|
-
}
|
|
1861
|
-
return strips;
|
|
1862
|
-
}
|
|
1863
|
-
function updateOdometerColumns(strips, grouped) {
|
|
1864
|
-
let index = 0;
|
|
1865
|
-
for (const token of odometerTokens(grouped)) {
|
|
1866
|
-
if (!token.digit) continue;
|
|
1867
|
-
strips[index]?.style.setProperty("--kui-o", token.char);
|
|
1868
|
-
index++;
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
function prepareOdometer(el, params, ctx) {
|
|
1872
|
-
const doc = el.ownerDocument;
|
|
1873
|
-
const from = Math.max(0, params.num("from", 0));
|
|
1874
|
-
const to = Math.max(0, params.num("to", 100));
|
|
1875
|
-
const width = Math.max(String(Math.round(from)).length, String(Math.round(to)).length);
|
|
1876
|
-
const toGrouped = groupDigits(paddedDigits(to, width));
|
|
1877
|
-
const fromGrouped = groupDigits(paddedDigits(from, width));
|
|
1878
|
-
const layers = installCountLayers(el, doc);
|
|
1879
|
-
layers.srOnly.textContent = fromGrouped;
|
|
1880
|
-
const strips = buildOdometerColumns(layers.decorative, doc, fromGrouped);
|
|
1881
|
-
const tween = tweenNumber(ctx, {
|
|
1882
|
-
from,
|
|
1883
|
-
to,
|
|
1884
|
-
...tweenTimingFor(params, ctx),
|
|
1885
|
-
onTick: (value, done) => {
|
|
1886
|
-
updateOdometerColumns(strips, groupDigits(paddedDigits(value, width)));
|
|
1887
|
-
if (done) layers.srOnly.textContent = toGrouped;
|
|
1888
|
-
}
|
|
1889
|
-
});
|
|
1890
|
-
return {
|
|
1891
|
-
cleanup: () => {
|
|
1892
|
-
tween.cleanup();
|
|
1893
|
-
layers.restore();
|
|
1894
|
-
},
|
|
1895
|
-
finished: tween.finished,
|
|
1896
|
-
finish: tween.finish
|
|
1897
|
-
};
|
|
1898
|
-
}
|
|
1899
|
-
var COUNT_PRIMITIVES = [
|
|
1900
|
-
countPrimitive("count", countParams, deferPrepare(prepareCount)),
|
|
1901
|
-
countPrimitive("count-odometer", odometerParams, deferPrepare(prepareOdometer))
|
|
1902
|
-
];
|
|
1903
|
-
var COUNT_PRESETS = [
|
|
1904
|
-
{ name: "count-up", primitive: "count", params: { from: "0", to: "100" } },
|
|
1905
|
-
{ name: "count-down", primitive: "count", params: { from: "100", to: "0" } },
|
|
1906
|
-
{
|
|
1907
|
-
name: "count-currency",
|
|
1908
|
-
primitive: "count",
|
|
1909
|
-
params: { from: "0", to: "4820", format: "currency", currency: "USD", decimals: "0" }
|
|
1910
|
-
},
|
|
1911
|
-
{
|
|
1912
|
-
name: "count-percent",
|
|
1913
|
-
primitive: "count",
|
|
1914
|
-
params: { from: "0", to: "0.82", format: "percent", decimals: "0" }
|
|
1915
|
-
},
|
|
1916
|
-
{
|
|
1917
|
-
name: "count-compact",
|
|
1918
|
-
primitive: "count",
|
|
1919
|
-
params: { from: "0", to: "128400", format: "compact" }
|
|
1920
|
-
},
|
|
1921
|
-
{ name: "odometer-roll", primitive: "count-odometer", params: { from: "0", to: "4820" } }
|
|
1922
|
-
];
|
|
1923
|
-
var METER_PRIMITIVES = [
|
|
1924
|
-
cssPrimitive("stroke-sweep", [CHANNEL.stroke]),
|
|
1925
|
-
// `from` gives `progress-bar` a real knob on its start scale — it had none before. `--kui-bar-from`
|
|
1926
|
-
// is also what the `[data-kui-fx~='progress-bar'][data-kui-state='ready']` gate in numbers.css
|
|
1927
|
-
// neutralizes; see that rule's comment for the on:enter fix this parameter doubles as.
|
|
1928
|
-
cssPrimitive("meter-bar", [CHANNEL.scale], {
|
|
1929
|
-
parameters: { from: { type: "number", default: "0", cssProperty: "--kui-bar-from" } }
|
|
1930
|
-
}),
|
|
1931
|
-
cssPrimitive("meter-segments", [CHANNEL.opacity]),
|
|
1932
|
-
cssPrimitive("meter-stars", [CHANNEL.clip])
|
|
1933
|
-
];
|
|
1934
|
-
var METER_PRESETS = [
|
|
1935
|
-
{ name: "progress-ring", primitive: "stroke-sweep", keyframes: "kui-progress-ring" },
|
|
1936
|
-
{ name: "gauge-sweep", primitive: "stroke-sweep", keyframes: "kui-gauge-sweep" },
|
|
1937
|
-
{ name: "donut-sweep", primitive: "stroke-sweep", keyframes: "kui-donut-sweep" },
|
|
1938
|
-
{ name: "sparkline-draw", primitive: "stroke-sweep", keyframes: "kui-sparkline-draw" },
|
|
1939
|
-
// `cloak: true`: `kui-progress-bar`'s `from { scale: 0 1 }` (numbers.css) is a zero-width box
|
|
1940
|
-
// while paused, not just an invisible one — so while it waits it occupies no space in layout at
|
|
1941
|
-
// all. Not, despite the tidier story, because an observer refuses to fire on it: Chromium was
|
|
1942
|
-
// measured resolving a zero-area target's `intersectionRatio` to `1`. See numbers.css's
|
|
1943
|
-
// `[data-kui-fx~='progress-bar'][data-kui-state='ready']` rule for the geometry half of the fix;
|
|
1944
|
-
// `cloak` keeps the pre-JS and post-JS "waiting" look the same (invisible) either side of it.
|
|
1945
|
-
{ name: "progress-bar", primitive: "meter-bar", keyframes: "kui-progress-bar", cloak: true },
|
|
1946
|
-
{ name: "progress-segments", primitive: "meter-segments", keyframes: "kui-progress-segments" },
|
|
1947
|
-
{ name: "star-rating-fill", primitive: "meter-stars", keyframes: "kui-star-rating-fill" }
|
|
1948
|
-
];
|
|
1949
|
-
var NUMBERS_PRIMITIVES = [...COUNT_PRIMITIVES, ...METER_PRIMITIVES];
|
|
1950
|
-
var NUMBERS_PRESETS = [...COUNT_PRESETS, ...METER_PRESETS];
|
|
1951
|
-
function registerNumbers(registry) {
|
|
1952
|
-
return registry.registerPrimitives(NUMBERS_PRIMITIVES).registerPresets(NUMBERS_PRESETS);
|
|
1953
|
-
}
|
|
1954
|
-
|
|
1955
|
-
// src/effects/catalog/text-shared.ts
|
|
1956
|
-
var SR_ONLY_CLASS2 = "kui-sr-only";
|
|
1957
|
-
var DECORATIVE_CLASS2 = "kui-split-decorative";
|
|
1958
|
-
function installSplitLayers(el, doc) {
|
|
1959
|
-
const originalText = el.textContent.trim();
|
|
1960
|
-
const restoreChildren = captureChildren(el);
|
|
1961
|
-
const decorative = doc.createElement("span");
|
|
1962
|
-
decorative.setAttribute("aria-hidden", "true");
|
|
1963
|
-
decorative.className = DECORATIVE_CLASS2;
|
|
1964
|
-
const srOnly = doc.createElement("span");
|
|
1965
|
-
srOnly.className = SR_ONLY_CLASS2;
|
|
1966
|
-
srOnly.textContent = originalText;
|
|
1967
|
-
el.textContent = "";
|
|
1968
|
-
el.append(decorative, srOnly);
|
|
1969
|
-
return {
|
|
1970
|
-
decorative,
|
|
1971
|
-
originalText,
|
|
1972
|
-
restore: restoreChildren
|
|
1973
|
-
};
|
|
1974
|
-
}
|
|
1975
|
-
function segmentGraphemes(text) {
|
|
1976
|
-
const segmenter = new Intl.Segmenter(void 0, { granularity: "grapheme" });
|
|
1977
|
-
return Array.from(segmenter.segment(text), (entry) => entry.segment);
|
|
1978
|
-
}
|
|
1979
|
-
function segmentWords(text) {
|
|
1980
|
-
const segmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
1981
|
-
return Array.from(segmenter.segment(text), (entry) => ({
|
|
1982
|
-
text: entry.segment,
|
|
1983
|
-
isWord: entry.isWordLike === true
|
|
1984
|
-
}));
|
|
1985
|
-
}
|
|
1986
|
-
function markItem(el, index, extraClass) {
|
|
1987
|
-
el.className = extraClass ? `kui-split-item ${extraClass}` : "kui-split-item";
|
|
1988
|
-
el.style.setProperty("--kui-i", String(index));
|
|
1989
|
-
}
|
|
1990
|
-
function applyStaggerVars(el, params) {
|
|
1991
|
-
const { durationMs, delayMs, easing } = params.timing;
|
|
1992
|
-
const duration = durationMs === void 0 ? params.text("duration", "500ms") : `${durationMs}ms`;
|
|
1993
|
-
const delay = delayMs === void 0 ? params.text("delay", "0ms") : `${delayMs}ms`;
|
|
1994
|
-
el.style.setProperty("--kui-duration", duration);
|
|
1995
|
-
el.style.setProperty("--kui-delay", delay);
|
|
1996
|
-
el.style.setProperty("--kui-ease", easing ?? params.text("ease", "ease-out"));
|
|
1997
|
-
el.style.setProperty("--kui-stagger", params.text("stagger", "30ms"));
|
|
1998
|
-
}
|
|
1999
|
-
function stepMsFor(params, ticks, fallback) {
|
|
2000
|
-
const total = params.timing.durationMs;
|
|
2001
|
-
if (total === void 0 || ticks <= 0) return params.ms("step", fallback);
|
|
2002
|
-
return Math.max(1, total / ticks);
|
|
2003
|
-
}
|
|
2004
|
-
function splitRevealFinishMs(params, itemCount) {
|
|
2005
|
-
if (itemCount === 0) return 0;
|
|
2006
|
-
const durationMs = params.timing.durationMs ?? params.ms("duration", 500);
|
|
2007
|
-
const delayMs = params.timing.delayMs ?? params.ms("delay", 0);
|
|
2008
|
-
const staggerMs = params.ms("stagger", 30);
|
|
2009
|
-
return delayMs + (itemCount - 1) * staggerMs + durationMs;
|
|
2010
|
-
}
|
|
2011
|
-
function createStepRunner(win, options) {
|
|
2012
|
-
let settle2;
|
|
2013
|
-
const finished = new Promise((resolve) => {
|
|
2014
|
-
settle2 = resolve;
|
|
2015
|
-
});
|
|
2016
|
-
let interval;
|
|
2017
|
-
function stop() {
|
|
2018
|
-
win.clearTimeout(start);
|
|
2019
|
-
if (interval !== void 0) win.clearInterval(interval);
|
|
2020
|
-
settle2?.();
|
|
2021
|
-
}
|
|
2022
|
-
const start = win.setTimeout(() => {
|
|
2023
|
-
interval = win.setInterval(() => {
|
|
2024
|
-
if (options.tick()) stop();
|
|
2025
|
-
}, options.stepMs);
|
|
2026
|
-
}, options.delayMs);
|
|
2027
|
-
return { finished, stop };
|
|
2028
|
-
}
|
|
2029
|
-
function appendCharSpans(container, doc, text) {
|
|
2030
|
-
const spans = [];
|
|
2031
|
-
let index = 0;
|
|
2032
|
-
let wordWrapper = null;
|
|
2033
|
-
for (const grapheme of segmentGraphemes(text)) {
|
|
2034
|
-
if (grapheme.trim() === "") {
|
|
2035
|
-
container.append(doc.createTextNode(grapheme));
|
|
2036
|
-
wordWrapper = null;
|
|
2037
|
-
continue;
|
|
2038
|
-
}
|
|
2039
|
-
if (!wordWrapper) {
|
|
2040
|
-
wordWrapper = doc.createElement("span");
|
|
2041
|
-
wordWrapper.className = "kui-split-word";
|
|
2042
|
-
container.append(wordWrapper);
|
|
2043
|
-
}
|
|
2044
|
-
const span = doc.createElement("span");
|
|
2045
|
-
markItem(span, index);
|
|
2046
|
-
span.textContent = grapheme;
|
|
2047
|
-
wordWrapper.append(span);
|
|
2048
|
-
spans.push(span);
|
|
2049
|
-
index++;
|
|
2050
|
-
}
|
|
2051
|
-
return spans;
|
|
2052
|
-
}
|
|
2053
|
-
function appendWordSpans(container, doc, text) {
|
|
2054
|
-
const spans = [];
|
|
2055
|
-
let index = 0;
|
|
2056
|
-
for (const token of segmentWords(text)) {
|
|
2057
|
-
if (!token.isWord) {
|
|
2058
|
-
container.append(doc.createTextNode(token.text));
|
|
2059
|
-
continue;
|
|
2060
|
-
}
|
|
2061
|
-
const span = doc.createElement("span");
|
|
2062
|
-
markItem(span, index);
|
|
2063
|
-
span.textContent = token.text;
|
|
2064
|
-
container.append(span);
|
|
2065
|
-
spans.push(span);
|
|
2066
|
-
index++;
|
|
2067
|
-
}
|
|
2068
|
-
return spans;
|
|
2069
|
-
}
|
|
2070
|
-
function bucketByLine(container) {
|
|
2071
|
-
const buckets = [];
|
|
2072
|
-
let currentTop = null;
|
|
2073
|
-
for (const node of Array.from(container.childNodes)) {
|
|
2074
|
-
if (node instanceof HTMLElement) {
|
|
2075
|
-
const top = node.offsetTop;
|
|
2076
|
-
if (currentTop === null || Math.abs(top - currentTop) > 1) {
|
|
2077
|
-
buckets.push([]);
|
|
2078
|
-
currentTop = top;
|
|
2079
|
-
}
|
|
2080
|
-
} else if (buckets.length === 0) {
|
|
2081
|
-
buckets.push([]);
|
|
2082
|
-
}
|
|
2083
|
-
buckets.at(-1).push(node);
|
|
2084
|
-
}
|
|
2085
|
-
return buckets;
|
|
2086
|
-
}
|
|
2087
|
-
function appendLineSpans(container, doc, text) {
|
|
2088
|
-
appendWordSpans(container, doc, text);
|
|
2089
|
-
const buckets = bucketByLine(container);
|
|
2090
|
-
container.replaceChildren();
|
|
2091
|
-
return buckets.map((nodes, index) => {
|
|
2092
|
-
const line = doc.createElement("span");
|
|
2093
|
-
markItem(line, index, "kui-split-line");
|
|
2094
|
-
for (const node of nodes) {
|
|
2095
|
-
if (node instanceof HTMLElement) {
|
|
2096
|
-
node.removeAttribute("class");
|
|
2097
|
-
node.style.removeProperty("--kui-i");
|
|
2098
|
-
}
|
|
2099
|
-
line.append(node);
|
|
2100
|
-
}
|
|
2101
|
-
container.append(line);
|
|
2102
|
-
return line;
|
|
2103
|
-
});
|
|
2104
|
-
}
|
|
2105
|
-
function appendSpansFor(unit, container, doc, text) {
|
|
2106
|
-
if (unit === "words") return appendWordSpans(container, doc, text);
|
|
2107
|
-
if (unit === "lines") return appendLineSpans(container, doc, text);
|
|
2108
|
-
return appendCharSpans(container, doc, text);
|
|
2109
|
-
}
|
|
2110
|
-
function nextTypeState(state, total, loop2) {
|
|
2111
|
-
if (!state.deleting) {
|
|
2112
|
-
const index2 = state.index + 1;
|
|
2113
|
-
if (index2 < total) return { index: index2, deleting: false, done: false };
|
|
2114
|
-
return loop2 ? { index: total, deleting: true, done: false } : { index: total, deleting: false, done: true };
|
|
2115
|
-
}
|
|
2116
|
-
const index = state.index - 1;
|
|
2117
|
-
if (index > 0) return { index, deleting: true, done: false };
|
|
2118
|
-
return { index: 0, deleting: false, done: false };
|
|
2119
|
-
}
|
|
2120
|
-
var SCRAMBLE_CHARSETS = {
|
|
2121
|
-
upper: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
|
|
2122
|
-
binary: "01",
|
|
2123
|
-
symbols: "!<>-_\\/[]{}=+*^?#~"
|
|
2124
|
-
};
|
|
2125
|
-
function scrambledFrame(graphemes, resolved, charset, random) {
|
|
2126
|
-
return graphemes.map((grapheme, index) => {
|
|
2127
|
-
if (index < resolved || grapheme.trim() === "") return grapheme;
|
|
2128
|
-
return charset[Math.floor(random() * charset.length)] ?? charset[0] ?? grapheme;
|
|
2129
|
-
}).join("");
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
// src/effects/catalog/text.ts
|
|
2133
|
-
var fontWeightParams = {
|
|
2134
|
-
from: { type: "number", default: "100", cssProperty: "--kui-from-weight", minimum: 1, maximum: 1e3 },
|
|
2135
|
-
to: { type: "number", default: "800", cssProperty: "--kui-to-weight", minimum: 1, maximum: 1e3 }
|
|
2136
|
-
};
|
|
2137
|
-
var fontWidthParams = {
|
|
2138
|
-
from: { type: "percentage", default: "75%", cssProperty: "--kui-from-width" },
|
|
2139
|
-
to: { type: "percentage", default: "125%", cssProperty: "--kui-to-width" }
|
|
2140
|
-
};
|
|
2141
|
-
var fontSlantParams = {
|
|
2142
|
-
from: { type: "angle", default: "0deg", cssProperty: "--kui-from-slant" },
|
|
2143
|
-
to: { type: "angle", default: "10deg", cssProperty: "--kui-to-slant" }
|
|
2144
|
-
};
|
|
2145
|
-
var textSweepParams = {
|
|
2146
|
-
color: { type: "color", default: "currentColor", cssProperty: "--kui-sweep-color" }
|
|
2147
|
-
};
|
|
2148
|
-
var extrudeParams = {
|
|
2149
|
-
angle: { type: "angle", default: "-20deg", cssProperty: "--kui-from-angle" },
|
|
2150
|
-
distance: { type: "length", default: "32px", cssProperty: "--kui-distance" }
|
|
2151
|
-
};
|
|
2152
|
-
var TEXT_CSS_PRIMITIVES = [
|
|
2153
|
-
// `color`, alongside `background`: both presets' unconditional rule sets `-webkit-text-fill-color:
|
|
2154
|
-
// transparent` so the `background-image` gradient shows through the glyphs (the standard
|
|
2155
|
-
// gradient-text technique). That is a real claim on the glyph fill, the same physical property
|
|
2156
|
-
// `text-outline-fill` animates on its own `color` channel below — without this, the two looked
|
|
2157
|
-
// disjoint to the compiler (`background` vs `stroke`+`color`) and composing them would let
|
|
2158
|
-
// whichever applied last silently win the glyph fill instead of being flagged as a conflict.
|
|
2159
|
-
cssPrimitive("text-shimmer", [CHANNEL.background, CHANNEL.color], { reducedMotion: "disable" }),
|
|
2160
|
-
// `gradient-sweep` is the only one of the three `text-sweep`-family presets whose keyframe
|
|
2161
|
-
// touches `-webkit-text-fill-color` (see text.css) — `highlight-sweep` and `underline-draw` only
|
|
2162
|
-
// paint a `background-image`. Claiming `color` for all three made the compiler reject compositions
|
|
2163
|
-
// like `underline-draw, text-outline-fill` as a glyph-fill conflict even though they touch disjoint
|
|
2164
|
-
// properties. Split so only the preset that actually claims the fill declares the channel.
|
|
2165
|
-
cssPrimitive("text-gradient-sweep", [CHANNEL.background, CHANNEL.color], { parameters: textSweepParams }),
|
|
2166
|
-
cssPrimitive("text-sweep", [CHANNEL.background], { parameters: textSweepParams }),
|
|
2167
|
-
cssPrimitive("text-outline-fill", [CHANNEL.stroke, CHANNEL.color]),
|
|
2168
|
-
cssPrimitive("var-weight", ["font"], { parameters: fontWeightParams }),
|
|
2169
|
-
cssPrimitive("var-width", ["font"], { parameters: fontWidthParams }),
|
|
2170
|
-
cssPrimitive("var-slant", ["font"], { parameters: fontSlantParams }),
|
|
2171
|
-
/*
|
|
2172
|
-
* `defaultActivation: 'load'` for the same reason every ambient primitive declares it, and it
|
|
2173
|
-
* was missing here: a marquee is continuous motion, so `reducedMotion: 'disable'` is only half
|
|
2174
|
-
* the rule `ambient.ts` spells out — the other half is starting on `load` rather than waiting on
|
|
2175
|
-
* a scroll-triggered `enter`.
|
|
2176
|
-
*
|
|
2177
|
-
* Without it, a bare `data-kui="marquee 42s"` resolved to `enter`, which `style-plan.ts`'s
|
|
2178
|
-
* `resolveGate` sends down the `deferred` path and stamps `animation-play-state: paused`. It
|
|
2179
|
-
* compiled correctly, reported `data-kui-state="ready"`, and never ran — measured on a marquee
|
|
2180
|
-
* fully in view, so this was not an observer that had simply not fired yet. Every page carrying
|
|
2181
|
-
* one had to know to write `on:load`, which is exactly the kind of thing an author cannot be
|
|
2182
|
-
* expected to guess.
|
|
2183
|
-
*
|
|
2184
|
-
* `marquee-scroll-linked` is unaffected: its position comes from `animation-timeline: scroll()`,
|
|
2185
|
-
* not from an activation.
|
|
2186
|
-
*/
|
|
2187
|
-
cssPrimitive("text-marquee", [CHANNEL.translate], {
|
|
2188
|
-
timelines: ["time", "scroll"],
|
|
2189
|
-
defaultActivation: "load",
|
|
2190
|
-
reducedMotion: "disable"
|
|
2191
|
-
}),
|
|
2192
|
-
cssPrimitive("redaction-reveal", [CHANNEL.clip]),
|
|
2193
|
-
cssPrimitive("text-3d-extrude", [CHANNEL.rotate, CHANNEL.translate], { parameters: extrudeParams })
|
|
2194
|
-
];
|
|
2195
|
-
var TEXT_CSS_PRESETS = [
|
|
2196
|
-
{ name: "gradient-shimmer", primitive: "text-shimmer", keyframes: "kui-gradient-shimmer" },
|
|
2197
|
-
{ name: "gradient-sweep", primitive: "text-gradient-sweep", keyframes: "kui-gradient-sweep" },
|
|
2198
|
-
{
|
|
2199
|
-
name: "highlight-sweep",
|
|
2200
|
-
primitive: "text-sweep",
|
|
2201
|
-
keyframes: "kui-highlight-sweep",
|
|
2202
|
-
params: { color: "gold" }
|
|
2203
|
-
},
|
|
2204
|
-
{ name: "underline-draw", primitive: "text-sweep", keyframes: "kui-underline-draw" },
|
|
2205
|
-
{ name: "text-outline-fill", primitive: "text-outline-fill", keyframes: "kui-text-outline-fill" },
|
|
2206
|
-
{ name: "var-weight", primitive: "var-weight", keyframes: "kui-var-weight" },
|
|
2207
|
-
{ name: "var-width", primitive: "var-width", keyframes: "kui-var-width" },
|
|
2208
|
-
{ name: "var-slant", primitive: "var-slant", keyframes: "kui-var-slant" },
|
|
2209
|
-
{ name: "marquee", primitive: "text-marquee", keyframes: "kui-marquee" },
|
|
2210
|
-
{ name: "marquee-scroll-linked", primitive: "text-marquee", keyframes: "kui-marquee" },
|
|
2211
|
-
{ name: "redaction-reveal", primitive: "redaction-reveal", keyframes: "kui-redaction-reveal" },
|
|
2212
|
-
{ name: "text-3d-extrude", primitive: "text-3d-extrude", keyframes: "kui-text-3d-extrude" }
|
|
2213
|
-
];
|
|
2214
|
-
function jsTextPrimitive(id, channels, options) {
|
|
2215
|
-
return {
|
|
2216
|
-
id,
|
|
2217
|
-
renderer: "javascript",
|
|
2218
|
-
channels,
|
|
2219
|
-
parameters: options.parameters,
|
|
2220
|
-
supportedTimelines: ["time"],
|
|
2221
|
-
supportedActivations: ["load", "enter", "hover", "focus", "click", "manual"],
|
|
2222
|
-
defaultActivation: "enter",
|
|
2223
|
-
perfClass: options.perfClass ?? "dom-transform",
|
|
2224
|
-
// Every JS-rendered primitive in this catalog is `disable`: none of them declare a CSS
|
|
2225
|
-
// `animation-duration` the reduced-motion policy layer could shorten, so `shorten` would be a
|
|
2226
|
-
// silent no-op. `disable` is enforced upstream — the animator never calls `activate()` at all
|
|
2227
|
-
// — which is the only place that actually works for a timer- or DOM-surgery-driven effect.
|
|
2228
|
-
reducedMotion: "disable",
|
|
2229
|
-
prepare: options.prepare
|
|
2230
|
-
};
|
|
2231
|
-
}
|
|
2232
|
-
var splitTiming = {
|
|
2233
|
-
duration: { type: "time", default: "500ms", cssProperty: "--kui-duration" },
|
|
2234
|
-
...TRIGGER_DELAY_PARAM,
|
|
2235
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" },
|
|
2236
|
-
stagger: { type: "time", default: "30ms", cssProperty: "--kui-stagger" },
|
|
2237
|
-
unit: {
|
|
2238
|
-
type: "keyword",
|
|
2239
|
-
default: "chars",
|
|
2240
|
-
cssProperty: "--kui-unit",
|
|
2241
|
-
values: ["chars", "words", "lines"]
|
|
2242
|
-
},
|
|
2243
|
-
direction: {
|
|
2244
|
-
type: "keyword",
|
|
2245
|
-
default: "fade",
|
|
2246
|
-
cssProperty: "--kui-direction",
|
|
2247
|
-
values: ["fade", "up", "down", "mask"]
|
|
2248
|
-
}
|
|
2249
|
-
};
|
|
2250
|
-
var motionParams = {
|
|
2251
|
-
stagger: { type: "time", default: "40ms", cssProperty: "--kui-stagger" },
|
|
2252
|
-
// `duration`/`ease` are deliberately *not* declared beside the delay: text.css pins both for
|
|
2253
|
-
// wave and jitter on a higher-specificity `[data-kui-split-fx='wave'] .kui-split-item` rule, so
|
|
2254
|
-
// declaring them would advertise two knobs that the stylesheet then overrides. `animation-delay`
|
|
2255
|
-
// is the one the phase-start rule leaves alone, which is what lets `applyStaggerVars` honour it.
|
|
2256
|
-
...TRIGGER_DELAY_PARAM,
|
|
2257
|
-
motion: {
|
|
2258
|
-
type: "keyword",
|
|
2259
|
-
default: "wave",
|
|
2260
|
-
cssProperty: "--kui-motion",
|
|
2261
|
-
values: ["wave", "jitter"]
|
|
2262
|
-
}
|
|
2263
|
-
};
|
|
2264
|
-
var typewriterParams = {
|
|
2265
|
-
step: { type: "time", default: "55ms", cssProperty: "--kui-step" },
|
|
2266
|
-
loop: { type: "keyword", default: "false", cssProperty: "--kui-loop", values: ["true", "false"] },
|
|
2267
|
-
...TRIGGER_DELAY_PARAM
|
|
2268
|
-
};
|
|
2269
|
-
var scrambleParams = {
|
|
2270
|
-
step: { type: "time", default: "40ms", cssProperty: "--kui-step" },
|
|
2271
|
-
// `duration` gets no such shared declaration: `stepMsFor` reads its authored-or-not distinction
|
|
2272
|
-
// to decide between a whole-effect time and a per-tick `step:`, and a schema default would erase
|
|
2273
|
-
// that distinction. A `0ms` delay default has no equivalent problem.
|
|
2274
|
-
...TRIGGER_DELAY_PARAM,
|
|
2275
|
-
revealEvery: {
|
|
2276
|
-
type: "number",
|
|
2277
|
-
default: "2",
|
|
2278
|
-
cssProperty: "--kui-reveal-every",
|
|
2279
|
-
minimum: 1,
|
|
2280
|
-
integer: true
|
|
2281
|
-
},
|
|
2282
|
-
charset: {
|
|
2283
|
-
type: "keyword",
|
|
2284
|
-
default: "upper",
|
|
2285
|
-
cssProperty: "--kui-charset",
|
|
2286
|
-
values: ["upper", "binary", "symbols"]
|
|
2287
|
-
}
|
|
2288
|
-
};
|
|
2289
|
-
var wordCyclerParams = {
|
|
2290
|
-
words: { type: "text", default: "", cssProperty: "--kui-words" },
|
|
2291
|
-
interval: { type: "time", default: "2200ms", cssProperty: "--kui-interval" },
|
|
2292
|
-
// Load-bearing here, not just for symmetry: a cycler has no authored `duration` — `interval:`
|
|
2293
|
-
// paces it — so the positional "duration then delay" slot only reached a delay when the author
|
|
2294
|
-
// wrote a throwaway first value, `word-cycler 0ms 300ms`.
|
|
2295
|
-
...TRIGGER_DELAY_PARAM
|
|
2296
|
-
};
|
|
2297
|
-
function prepareSplitText(el, params, ctx) {
|
|
2298
|
-
const doc = el.ownerDocument;
|
|
2299
|
-
const unit = params.text("unit", "chars");
|
|
2300
|
-
const direction = params.text("direction", "fade");
|
|
2301
|
-
const layers = installSplitLayers(el, doc);
|
|
2302
|
-
layers.decorative.setAttribute("data-kui-split-fx", direction);
|
|
2303
|
-
applyStaggerVars(layers.decorative, params);
|
|
2304
|
-
const items = appendSpansFor(unit, layers.decorative, doc, layers.originalText);
|
|
2305
|
-
let settle2;
|
|
2306
|
-
const finished = new Promise((resolve) => {
|
|
2307
|
-
settle2 = resolve;
|
|
2308
|
-
});
|
|
2309
|
-
const timer = ctx.win.setTimeout(settle2, splitRevealFinishMs(params, items.length));
|
|
2310
|
-
return {
|
|
2311
|
-
cleanup: () => {
|
|
2312
|
-
ctx.win.clearTimeout(timer);
|
|
2313
|
-
layers.restore();
|
|
2314
|
-
},
|
|
2315
|
-
finished,
|
|
2316
|
-
finish: () => {
|
|
2317
|
-
ctx.win.clearTimeout(timer);
|
|
2318
|
-
settle2();
|
|
2319
|
-
}
|
|
2320
|
-
};
|
|
2321
|
-
}
|
|
2322
|
-
function prepareSplitMotion(el, params) {
|
|
2323
|
-
const doc = el.ownerDocument;
|
|
2324
|
-
const motion = params.text("motion", "wave");
|
|
2325
|
-
const layers = installSplitLayers(el, doc);
|
|
2326
|
-
layers.decorative.setAttribute("data-kui-split-fx", motion);
|
|
2327
|
-
applyStaggerVars(layers.decorative, params);
|
|
2328
|
-
appendCharSpans(layers.decorative, doc, layers.originalText);
|
|
2329
|
-
return layers.restore;
|
|
2330
|
-
}
|
|
2331
|
-
function prepareTypewriter(el, params, ctx) {
|
|
2332
|
-
const loop2 = params.is("loop");
|
|
2333
|
-
const layers = installSplitLayers(el, el.ownerDocument);
|
|
2334
|
-
layers.decorative.classList.add("kui-typewriter");
|
|
2335
|
-
const graphemes = segmentGraphemes(layers.originalText);
|
|
2336
|
-
let state = { index: 0, deleting: false };
|
|
2337
|
-
const render = (count) => {
|
|
2338
|
-
layers.decorative.textContent = graphemes.slice(0, count).join("");
|
|
2339
|
-
};
|
|
2340
|
-
const run = createStepRunner(ctx.win, {
|
|
2341
|
-
delayMs: params.timing.delayMs ?? params.ms("delay", 0),
|
|
2342
|
-
stepMs: stepMsFor(params, graphemes.length, 55),
|
|
2343
|
-
tick: () => {
|
|
2344
|
-
const step = nextTypeState(state, graphemes.length, loop2);
|
|
2345
|
-
state = step;
|
|
2346
|
-
render(step.index);
|
|
2347
|
-
return step.done;
|
|
2348
|
-
}
|
|
2349
|
-
});
|
|
2350
|
-
return {
|
|
2351
|
-
cleanup: () => {
|
|
2352
|
-
run.stop();
|
|
2353
|
-
layers.restore();
|
|
2354
|
-
},
|
|
2355
|
-
finished: run.finished,
|
|
2356
|
-
finish: () => {
|
|
2357
|
-
run.stop();
|
|
2358
|
-
render(graphemes.length);
|
|
2359
|
-
}
|
|
2360
|
-
};
|
|
2361
|
-
}
|
|
2362
|
-
function prepareScramble(el, params, ctx) {
|
|
2363
|
-
const charset = SCRAMBLE_CHARSETS[params.text("charset", "upper")];
|
|
2364
|
-
const revealEvery = Math.max(1, Math.round(params.num("revealEvery", 2)));
|
|
2365
|
-
const node = el;
|
|
2366
|
-
const authoredMinWidth = node.style.getPropertyValue("min-width");
|
|
2367
|
-
const authoredMinHeight = node.style.getPropertyValue("min-height");
|
|
2368
|
-
const restRect = el.getBoundingClientRect();
|
|
2369
|
-
ctx.style.set("min-width", `${restRect.width}px`);
|
|
2370
|
-
ctx.style.set("min-height", `${restRect.height}px`);
|
|
2371
|
-
const releaseSizeLock = () => {
|
|
2372
|
-
ctx.style.set("min-width", authoredMinWidth);
|
|
2373
|
-
ctx.style.set("min-height", authoredMinHeight);
|
|
2374
|
-
};
|
|
2375
|
-
const layers = installSplitLayers(el, el.ownerDocument);
|
|
2376
|
-
layers.decorative.classList.add("kui-scramble");
|
|
2377
|
-
const graphemes = segmentGraphemes(layers.originalText);
|
|
2378
|
-
let resolved = 0;
|
|
2379
|
-
let ticks = 0;
|
|
2380
|
-
const render = () => {
|
|
2381
|
-
layers.decorative.textContent = scrambledFrame(graphemes, resolved, charset, Math.random);
|
|
2382
|
-
};
|
|
2383
|
-
render();
|
|
2384
|
-
const totalTicks = Math.max(1, graphemes.length * revealEvery);
|
|
2385
|
-
const run = createStepRunner(ctx.win, {
|
|
2386
|
-
delayMs: params.timing.delayMs ?? params.ms("delay", 0),
|
|
2387
|
-
stepMs: stepMsFor(params, totalTicks, Math.max(1, 700 / totalTicks)),
|
|
2388
|
-
tick: () => {
|
|
2389
|
-
ticks++;
|
|
2390
|
-
if (ticks % revealEvery === 0) resolved++;
|
|
2391
|
-
render();
|
|
2392
|
-
const done = resolved >= graphemes.length;
|
|
2393
|
-
if (done) releaseSizeLock();
|
|
2394
|
-
return done;
|
|
2395
|
-
}
|
|
2396
|
-
});
|
|
2397
|
-
return {
|
|
2398
|
-
cleanup: () => {
|
|
2399
|
-
run.stop();
|
|
2400
|
-
layers.restore();
|
|
2401
|
-
releaseSizeLock();
|
|
2402
|
-
},
|
|
2403
|
-
finished: run.finished,
|
|
2404
|
-
finish: () => {
|
|
2405
|
-
run.stop();
|
|
2406
|
-
resolved = graphemes.length;
|
|
2407
|
-
render();
|
|
2408
|
-
releaseSizeLock();
|
|
2409
|
-
}
|
|
2410
|
-
};
|
|
2411
|
-
}
|
|
2412
|
-
function prepareWordCycler(el, params, ctx) {
|
|
2413
|
-
const words = params.text("words", "").split("|").map((word) => word.trim()).filter(Boolean);
|
|
2414
|
-
if (words.length === 0) return () => {
|
|
2415
|
-
};
|
|
2416
|
-
const restoreChildren = captureChildren(el);
|
|
2417
|
-
const swapMs = 150;
|
|
2418
|
-
let index = 0;
|
|
2419
|
-
el.textContent = words[0];
|
|
2420
|
-
const run = createStepRunner(ctx.win, {
|
|
2421
|
-
delayMs: params.timing.delayMs ?? params.ms("delay", 0),
|
|
2422
|
-
stepMs: params.ms("interval", 2200),
|
|
2423
|
-
tick: () => {
|
|
2424
|
-
el.classList.add("kui-word-cycler-swap");
|
|
2425
|
-
ctx.win.setTimeout(() => {
|
|
2426
|
-
index = (index + 1) % words.length;
|
|
2427
|
-
el.textContent = words[index];
|
|
2428
|
-
el.classList.remove("kui-word-cycler-swap");
|
|
2429
|
-
}, swapMs);
|
|
2430
|
-
return false;
|
|
2431
|
-
}
|
|
2432
|
-
});
|
|
2433
|
-
return {
|
|
2434
|
-
cleanup: () => {
|
|
2435
|
-
run.stop();
|
|
2436
|
-
el.classList.remove("kui-word-cycler-swap");
|
|
2437
|
-
restoreChildren();
|
|
2438
|
-
},
|
|
2439
|
-
finished: run.finished,
|
|
2440
|
-
finish: () => run.stop()
|
|
2441
|
-
};
|
|
2442
|
-
}
|
|
2443
|
-
var TEXT_JS_PRIMITIVES = [
|
|
2444
|
-
jsTextPrimitive("split-text", [CHANNEL.opacity, CHANNEL.translate, CHANNEL.clip], {
|
|
2445
|
-
parameters: splitTiming,
|
|
2446
|
-
prepare: deferPrepare(prepareSplitText)
|
|
2447
|
-
}),
|
|
2448
|
-
jsTextPrimitive("split-text-motion", [CHANNEL.translate, CHANNEL.rotate], {
|
|
2449
|
-
parameters: motionParams,
|
|
2450
|
-
prepare: deferPrepare(prepareSplitMotion),
|
|
2451
|
-
perfClass: "continuous"
|
|
2452
|
-
}),
|
|
2453
|
-
jsTextPrimitive("typewriter", [CHANNEL.clip], {
|
|
2454
|
-
parameters: typewriterParams,
|
|
2455
|
-
prepare: deferPrepare(prepareTypewriter),
|
|
2456
|
-
perfClass: "continuous"
|
|
2457
|
-
}),
|
|
2458
|
-
jsTextPrimitive("scramble-text", ["content"], {
|
|
2459
|
-
parameters: scrambleParams,
|
|
2460
|
-
prepare: deferPrepare(prepareScramble),
|
|
2461
|
-
perfClass: "continuous"
|
|
2462
|
-
}),
|
|
2463
|
-
jsTextPrimitive("word-cycler", ["content"], {
|
|
2464
|
-
parameters: wordCyclerParams,
|
|
2465
|
-
prepare: deferPrepare(prepareWordCycler),
|
|
2466
|
-
perfClass: "continuous"
|
|
2467
|
-
})
|
|
2468
|
-
];
|
|
2469
|
-
var CHARS_STAGGER = "30ms";
|
|
2470
|
-
var WORDS_STAGGER = "90ms";
|
|
2471
|
-
var LINES_STAGGER = "160ms";
|
|
2472
|
-
var TEXT_JS_PRESETS = [
|
|
2473
|
-
{ name: "split-chars", primitive: "split-text", params: { unit: "chars", direction: "fade", stagger: CHARS_STAGGER } },
|
|
2474
|
-
{ name: "split-words", primitive: "split-text", params: { unit: "words", direction: "fade", stagger: WORDS_STAGGER } },
|
|
2475
|
-
{ name: "split-lines", primitive: "split-text", params: { unit: "lines", direction: "fade", stagger: LINES_STAGGER } },
|
|
2476
|
-
{ name: "text-reveal-up", primitive: "split-text", params: { unit: "words", direction: "up", stagger: WORDS_STAGGER }, cloak: true },
|
|
2477
|
-
{ name: "text-reveal-down", primitive: "split-text", params: { unit: "words", direction: "down", stagger: WORDS_STAGGER }, cloak: true },
|
|
2478
|
-
{ name: "text-reveal-mask", primitive: "split-text", params: { unit: "lines", direction: "mask", stagger: LINES_STAGGER }, cloak: true },
|
|
2479
|
-
{ name: "text-wave", primitive: "split-text-motion", params: { motion: "wave" } },
|
|
2480
|
-
{ name: "text-jitter", primitive: "split-text-motion", params: { motion: "jitter" } },
|
|
2481
|
-
{ name: "typewriter", primitive: "typewriter", params: { loop: "false" } },
|
|
2482
|
-
{ name: "typewriter-loop", primitive: "typewriter", params: { loop: "true" } },
|
|
2483
|
-
{ name: "scramble", primitive: "scramble-text", params: { charset: "upper" } },
|
|
2484
|
-
{ name: "decode", primitive: "scramble-text", params: { charset: "binary" } },
|
|
2485
|
-
{ name: "glitch", primitive: "scramble-text", params: { charset: "symbols" } },
|
|
2486
|
-
{ name: "word-cycler", primitive: "word-cycler" }
|
|
2487
|
-
];
|
|
2488
|
-
var TEXT_PRIMITIVES = [...TEXT_CSS_PRIMITIVES, ...TEXT_JS_PRIMITIVES];
|
|
2489
|
-
var TEXT_PRESETS = [...TEXT_CSS_PRESETS, ...TEXT_JS_PRESETS];
|
|
2490
|
-
function registerText(registry) {
|
|
2491
|
-
return registry.registerPrimitives(TEXT_PRIMITIVES).registerPresets(TEXT_PRESETS);
|
|
2492
|
-
}
|
|
2493
|
-
|
|
2494
|
-
// src/effects/catalog/index.ts
|
|
2495
|
-
function registerCatalog(registry) {
|
|
2496
|
-
registerMedia(registry);
|
|
2497
|
-
registerText(registry);
|
|
2498
|
-
registerAmbient(registry);
|
|
2499
|
-
registerFeedback(registry);
|
|
2500
|
-
registerNumbers(registry);
|
|
2501
|
-
registerInteraction(registry);
|
|
2502
|
-
return registry;
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
// src/effects/catalog/core.ts
|
|
2506
|
-
var distance = {
|
|
2507
|
-
distance: { type: "length", default: "24px", cssProperty: "--kui-distance" },
|
|
2508
|
-
opacity: { type: "number", default: "0", cssProperty: "--kui-from-opacity" }
|
|
2509
|
-
};
|
|
2510
|
-
var ENTRANCE_TIMELINES = ["time", "view", "scroll", "pin"];
|
|
2511
|
-
var PRIMITIVES = [
|
|
2512
|
-
// --- entrance / exit -------------------------------------------------------------------
|
|
2513
|
-
cssPrimitive("reveal", [CHANNEL.opacity, CHANNEL.translate], { timelines: ENTRANCE_TIMELINES, parameters: distance }),
|
|
2514
|
-
cssPrimitive("scale", [CHANNEL.scale], {
|
|
2515
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2516
|
-
parameters: { scale: { type: "number", default: "0.92", cssProperty: "--kui-from-scale" } }
|
|
2517
|
-
}),
|
|
2518
|
-
// Separate from `scale` because it claims translate as well and so composes differently.
|
|
2519
|
-
// `distance.distance` only, not `...distance`: `kui-zoom-in-up`/`-down` (entrance.css) read
|
|
2520
|
-
// `--kui-distance` and `--kui-from-scale` but never `--kui-from-opacity` — this primitive
|
|
2521
|
-
// doesn't declare `CHANNEL.opacity`. Spreading the whole shared `distance` object used to expose
|
|
2522
|
-
// `opacity:` as an apparently-valid, silently-inert parameter (same dead-parameter shape as
|
|
2523
|
-
// `flip-3d`'s old `perspective`, fixed above).
|
|
2524
|
-
cssPrimitive("scale-move", [CHANNEL.scale, CHANNEL.translate], {
|
|
2525
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2526
|
-
parameters: {
|
|
2527
|
-
distance: distance.distance,
|
|
2528
|
-
scale: { type: "number", default: "0.92", cssProperty: "--kui-from-scale" }
|
|
2529
|
-
}
|
|
2530
|
-
}),
|
|
2531
|
-
cssPrimitive("rotate", [CHANNEL.rotate], {
|
|
2532
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2533
|
-
parameters: { angle: { type: "angle", default: "-8deg", cssProperty: "--kui-from-angle" } }
|
|
2534
|
-
}),
|
|
2535
|
-
// `distance.distance` only, not `...distance`: `kui-roll-in`/`-out` (entrance.css) write
|
|
2536
|
-
// `rotate`/`translate`, never `opacity` — this primitive doesn't declare `CHANNEL.opacity`. Same
|
|
2537
|
-
// dead-parameter shape as `scale-move` above.
|
|
2538
|
-
cssPrimitive("roll", [CHANNEL.rotate, CHANNEL.translate], {
|
|
2539
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2540
|
-
parameters: {
|
|
2541
|
-
distance: distance.distance,
|
|
2542
|
-
angle: { type: "angle", default: "-120deg", cssProperty: "--kui-from-angle" }
|
|
2543
|
-
}
|
|
2544
|
-
}),
|
|
2545
|
-
// `skew`, not `rotate`: entrance.css's keyframes write `transform: perspective(...)
|
|
2546
|
-
// rotateX/Y(...)`, not the individual `rotate:` property — the `perspective` parameter below
|
|
2547
|
-
// used to compile cleanly and do nothing, because nothing read it. See entrance.css's own
|
|
2548
|
-
// comment on `kui-flip-in-x` for the fix; `CHANNEL.skew` is this catalog's name for "claims the
|
|
2549
|
-
// whole `transform` shorthand" (`core/types.ts`), shared with `scroll-skew` and `flip-face`.
|
|
2550
|
-
cssPrimitive("flip-3d", [CHANNEL.skew], {
|
|
2551
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2552
|
-
parameters: {
|
|
2553
|
-
angle: { type: "angle", default: "90deg", cssProperty: "--kui-from-angle" },
|
|
2554
|
-
perspective: { type: "length", default: "1200px", cssProperty: "--kui-perspective" }
|
|
2555
|
-
}
|
|
2556
|
-
}),
|
|
2557
|
-
cssPrimitive("blur", [CHANNEL.filter], {
|
|
2558
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2559
|
-
parameters: { blur: { type: "length", default: "12px", cssProperty: "--kui-blur" } }
|
|
2560
|
-
}),
|
|
2561
|
-
// Purpose-built combination: one keyframe, so opacity is written once instead of twice.
|
|
2562
|
-
cssPrimitive("reveal-blur", [CHANNEL.opacity, CHANNEL.translate, CHANNEL.filter], {
|
|
2563
|
-
timelines: ENTRANCE_TIMELINES,
|
|
2564
|
-
parameters: { ...distance, blur: { type: "length", default: "12px", cssProperty: "--kui-blur" } }
|
|
2565
|
-
}),
|
|
2566
|
-
// --- scroll-linked ---------------------------------------------------------------------
|
|
2567
|
-
// These are progress-linked, not time-triggered: they reverse as the user scrolls back.
|
|
2568
|
-
// That is by design and is why `timeline:` is a different axis from `on:`.
|
|
2569
|
-
// `distance.distance` only, not the whole `distance` object: `kui-parallax-y`/`-x` (scroll.css)
|
|
2570
|
-
// write only `translate` — this primitive doesn't declare `CHANNEL.opacity`. Same dead-parameter
|
|
2571
|
-
// shape as `scale-move`/`roll` above; `parallax-y`/`parallax-x`/`depth-layer` never read
|
|
2572
|
-
// `--kui-from-opacity`.
|
|
2573
|
-
cssPrimitive("parallax", [CHANNEL.translate], {
|
|
2574
|
-
parameters: { distance: distance.distance },
|
|
2575
|
-
timelines: ["view", "scroll", "pin"],
|
|
2576
|
-
activations: ["manual"],
|
|
2577
|
-
reducedMotion: "disable",
|
|
2578
|
-
perfClass: "compositor"
|
|
2579
|
-
}),
|
|
2580
|
-
// `from` exists because the resting end was hardcoded (scale 1 / rotate 0deg), which fixed
|
|
2581
|
-
// these to "grow slightly" and "tilt slightly" — a scroll-driven element that should sweep in
|
|
2582
|
-
// from a quarter-size or from half a turn away had no way to say so. Same `--kui-from-*`
|
|
2583
|
-
// properties the entrance primitives already use, so the two stay spellable the same way.
|
|
2584
|
-
cssPrimitive("parallax-scale", [CHANNEL.scale], {
|
|
2585
|
-
parameters: {
|
|
2586
|
-
scale: { type: "number", default: "1.2", cssProperty: "--kui-to-scale" },
|
|
2587
|
-
from: { type: "number", default: "1", cssProperty: "--kui-from-scale" }
|
|
2588
|
-
},
|
|
2589
|
-
timelines: ["view", "scroll", "pin"],
|
|
2590
|
-
activations: ["manual"],
|
|
2591
|
-
reducedMotion: "disable"
|
|
2592
|
-
}),
|
|
2593
|
-
cssPrimitive("parallax-rotate", [CHANNEL.rotate], {
|
|
2594
|
-
parameters: {
|
|
2595
|
-
angle: { type: "angle", default: "12deg", cssProperty: "--kui-to-angle" },
|
|
2596
|
-
from: { type: "angle", default: "0deg", cssProperty: "--kui-from-angle" }
|
|
2597
|
-
},
|
|
2598
|
-
timelines: ["view", "scroll", "pin"],
|
|
2599
|
-
activations: ["manual"],
|
|
2600
|
-
reducedMotion: "disable"
|
|
2601
|
-
}),
|
|
2602
|
-
cssPrimitive("scroll-fade", [CHANNEL.opacity], {
|
|
2603
|
-
parameters: { opacity: { type: "number", default: "0", cssProperty: "--kui-from-opacity" } },
|
|
2604
|
-
timelines: ["view", "scroll", "pin"],
|
|
2605
|
-
activations: ["manual"],
|
|
2606
|
-
reducedMotion: "disable"
|
|
2607
|
-
}),
|
|
2608
|
-
// `filter`, not `opacity` — it collides with `blur`, and declaring the real channel is what
|
|
2609
|
-
// makes `channels.ts` say so instead of letting the two silently overwrite each other's
|
|
2610
|
-
// `filter` declaration.
|
|
2611
|
-
cssPrimitive("desaturate", [CHANNEL.filter], {
|
|
2612
|
-
parameters: {
|
|
2613
|
-
from: { type: "percentage", default: "100%", cssProperty: "--kui-from-grayscale" },
|
|
2614
|
-
to: { type: "percentage", default: "0%", cssProperty: "--kui-to-grayscale" }
|
|
2615
|
-
},
|
|
2616
|
-
timelines: ["view", "scroll", "pin"],
|
|
2617
|
-
activations: ["manual"],
|
|
2618
|
-
reducedMotion: "disable",
|
|
2619
|
-
perfClass: "paint"
|
|
2620
|
-
}),
|
|
2621
|
-
/*
|
|
2622
|
-
* `transform`, not one of the independent transform properties, because CSS never shipped a
|
|
2623
|
-
* standalone `skew:`. That is also why `skew` is its own channel rather than folded in with
|
|
2624
|
-
* `rotate`: writing `transform` replaces the entire shorthand, so a skew composed with anything
|
|
2625
|
-
* else that wrote `transform` would silently win. Nothing else in the catalog does — every other
|
|
2626
|
-
* transform in the library goes through `translate`/`rotate`/`scale` — so the shorthand is free.
|
|
2627
|
-
*/
|
|
2628
|
-
cssPrimitive("skew", [CHANNEL.skew], {
|
|
2629
|
-
parameters: {
|
|
2630
|
-
from: { type: "angle", default: "8deg", cssProperty: "--kui-from-skew" },
|
|
2631
|
-
to: { type: "angle", default: "0deg", cssProperty: "--kui-to-skew" }
|
|
2632
|
-
},
|
|
2633
|
-
timelines: ["view", "scroll", "pin"],
|
|
2634
|
-
activations: ["manual"],
|
|
2635
|
-
reducedMotion: "disable"
|
|
2636
|
-
}),
|
|
2637
|
-
cssPrimitive("progress", [CHANNEL.scale], {
|
|
2638
|
-
timelines: ["scroll", "view"],
|
|
2639
|
-
activations: ["manual"],
|
|
2640
|
-
reducedMotion: "disable"
|
|
2641
|
-
}),
|
|
2642
|
-
// Separate primitive because it writes `stroke-dashoffset`, not `scale` — declaring the
|
|
2643
|
-
// wrong channel would let it silently compose with an effect it actually collides with.
|
|
2644
|
-
cssPrimitive("progress-stroke", [CHANNEL.stroke], {
|
|
2645
|
-
parameters: { length: { type: "number", default: "100", cssProperty: "--kui-path-length" } },
|
|
2646
|
-
timelines: ["scroll", "view"],
|
|
2647
|
-
activations: ["manual"],
|
|
2648
|
-
reducedMotion: "disable",
|
|
2649
|
-
perfClass: "paint"
|
|
2650
|
-
})
|
|
2651
|
-
];
|
|
2652
|
-
var p = (name, primitive, keyframes, params) => ({ name, primitive, keyframes, ...params ? { params } : {} });
|
|
2653
|
-
var pIn = (name, primitive, keyframes, params) => ({ ...p(name, primitive, keyframes, params), cloak: true });
|
|
2654
|
-
var FADE = [
|
|
2655
|
-
pIn("fade-in", "reveal", "kui-in"),
|
|
2656
|
-
p("fade-out", "reveal", "kui-out"),
|
|
2657
|
-
pIn("fade-up", "reveal", "kui-in-up"),
|
|
2658
|
-
pIn("fade-down", "reveal", "kui-in-down"),
|
|
2659
|
-
pIn("fade-left", "reveal", "kui-in-left"),
|
|
2660
|
-
pIn("fade-right", "reveal", "kui-in-right"),
|
|
2661
|
-
p("fade-out-up", "reveal", "kui-out-up"),
|
|
2662
|
-
p("fade-out-down", "reveal", "kui-out-down"),
|
|
2663
|
-
p("fade-out-left", "reveal", "kui-out-left"),
|
|
2664
|
-
p("fade-out-right", "reveal", "kui-out-right")
|
|
2665
|
-
];
|
|
2666
|
-
var SLIDE_PARAMS = { distance: "100px", opacity: "1" };
|
|
2667
|
-
var SLIDE = [
|
|
2668
|
-
pIn("slide-up", "reveal", "kui-in-up", SLIDE_PARAMS),
|
|
2669
|
-
pIn("slide-down", "reveal", "kui-in-down", SLIDE_PARAMS),
|
|
2670
|
-
pIn("slide-left", "reveal", "kui-in-left", SLIDE_PARAMS),
|
|
2671
|
-
pIn("slide-right", "reveal", "kui-in-right", SLIDE_PARAMS),
|
|
2672
|
-
p("slide-out-up", "reveal", "kui-out-up", SLIDE_PARAMS),
|
|
2673
|
-
p("slide-out-down", "reveal", "kui-out-down", SLIDE_PARAMS),
|
|
2674
|
-
p("slide-out-left", "reveal", "kui-out-left", SLIDE_PARAMS),
|
|
2675
|
-
p("slide-out-right", "reveal", "kui-out-right", SLIDE_PARAMS)
|
|
2676
|
-
];
|
|
2677
|
-
var LOGICAL = [
|
|
2678
|
-
pIn("slide-inline-start", "reveal", "kui-in-inline-start", SLIDE_PARAMS),
|
|
2679
|
-
pIn("slide-inline-end", "reveal", "kui-in-inline-end", SLIDE_PARAMS),
|
|
2680
|
-
pIn("slide-block-start", "reveal", "kui-in-up", SLIDE_PARAMS),
|
|
2681
|
-
pIn("slide-block-end", "reveal", "kui-in-down", SLIDE_PARAMS)
|
|
2682
|
-
];
|
|
2683
|
-
var ZOOM = [
|
|
2684
|
-
pIn("zoom-in", "scale", "kui-zoom-in"),
|
|
2685
|
-
p("zoom-out", "scale", "kui-zoom-out"),
|
|
2686
|
-
pIn("pop-in", "scale", "kui-zoom-in", { scale: "0.6", ease: "back-out" }),
|
|
2687
|
-
p("pop-out", "scale", "kui-zoom-out", { scale: "0.6", ease: "back-in" }),
|
|
2688
|
-
pIn("zoom-in-up", "scale-move", "kui-zoom-in-up"),
|
|
2689
|
-
pIn("zoom-in-down", "scale-move", "kui-zoom-in-down")
|
|
2690
|
-
];
|
|
2691
|
-
var FLIP = [
|
|
2692
|
-
pIn("flip-in-x", "flip-3d", "kui-flip-in-x"),
|
|
2693
|
-
pIn("flip-in-y", "flip-3d", "kui-flip-in-y"),
|
|
2694
|
-
p("flip-out-x", "flip-3d", "kui-flip-out-x"),
|
|
2695
|
-
p("flip-out-y", "flip-3d", "kui-flip-out-y")
|
|
2696
|
-
];
|
|
2697
|
-
var ROTATE = [
|
|
2698
|
-
pIn("rotate-in", "rotate", "kui-rotate-in"),
|
|
2699
|
-
p("rotate-out", "rotate", "kui-rotate-out"),
|
|
2700
|
-
pIn("rotate-in-left", "rotate", "kui-rotate-in", { angle: "-45deg" }),
|
|
2701
|
-
pIn("rotate-in-right", "rotate", "kui-rotate-in", { angle: "45deg" }),
|
|
2702
|
-
pIn("roll-in", "roll", "kui-roll-in"),
|
|
2703
|
-
p("roll-out", "roll", "kui-roll-out"),
|
|
2704
|
-
pIn("swing-in", "rotate", "kui-swing-in", { angle: "-15deg", ease: "back-out" })
|
|
2705
|
-
];
|
|
2706
|
-
var BLUR = [
|
|
2707
|
-
pIn("blur-in", "blur", "kui-blur-in"),
|
|
2708
|
-
p("blur-out", "blur", "kui-blur-out"),
|
|
2709
|
-
pIn("fade-blur-up", "reveal-blur", "kui-fade-blur-up"),
|
|
2710
|
-
pIn("fade-blur-in", "reveal-blur", "kui-fade-blur-in")
|
|
2711
|
-
];
|
|
2712
|
-
var CHARACTER = [
|
|
2713
|
-
pIn("bounce-in", "scale", "kui-zoom-in", { scale: "0.3", ease: "bounce" }),
|
|
2714
|
-
pIn("bounce-in-up", "reveal", "kui-in-up", { distance: "60px", ease: "back-out" }),
|
|
2715
|
-
pIn("bounce-in-down", "reveal", "kui-in-down", { distance: "60px", ease: "back-out" }),
|
|
2716
|
-
pIn("back-in-up", "reveal", "kui-in-up", { distance: "120px", ease: "expo-out" }),
|
|
2717
|
-
pIn("back-in-down", "reveal", "kui-in-down", { distance: "120px", ease: "expo-out" })
|
|
2718
|
-
];
|
|
2719
|
-
var SCROLL = [
|
|
2720
|
-
p("parallax-y", "parallax", "kui-parallax-y"),
|
|
2721
|
-
p("parallax-x", "parallax", "kui-parallax-x"),
|
|
2722
|
-
p("parallax-scale", "parallax-scale", "kui-parallax-scale"),
|
|
2723
|
-
p("parallax-rotate", "parallax-rotate", "kui-parallax-rotate"),
|
|
2724
|
-
p("depth-layer", "parallax", "kui-parallax-y", { distance: "200px" }),
|
|
2725
|
-
p("scroll-fade", "scroll-fade", "kui-scroll-fade"),
|
|
2726
|
-
p("scroll-desaturate", "desaturate", "kui-desaturate"),
|
|
2727
|
-
p("scroll-skew", "skew", "kui-scroll-skew"),
|
|
2728
|
-
p("scroll-progress-bar", "progress", "kui-progress-x"),
|
|
2729
|
-
p("scroll-progress-bar-y", "progress", "kui-progress-y"),
|
|
2730
|
-
p("scroll-progress-ring", "progress-stroke", "kui-progress-ring"),
|
|
2731
|
-
// `reveal-repeat` was removed: it was byte-identical to `reveal-once`, and the activation
|
|
2732
|
-
// binder unobserves after first entry, so a repeating reveal is not implementable yet.
|
|
2733
|
-
pIn("reveal-once", "reveal", "kui-in-up")
|
|
2734
|
-
];
|
|
2735
|
-
var PRESETS = [
|
|
2736
|
-
...FADE,
|
|
2737
|
-
...SLIDE,
|
|
2738
|
-
...LOGICAL,
|
|
2739
|
-
...ZOOM,
|
|
2740
|
-
...FLIP,
|
|
2741
|
-
...ROTATE,
|
|
2742
|
-
...BLUR,
|
|
2743
|
-
...CHARACTER,
|
|
2744
|
-
...SCROLL
|
|
2745
|
-
];
|
|
2746
|
-
var COMBOS = [
|
|
2747
|
-
[["fade-up", "blur-in"], "fade-blur-up"],
|
|
2748
|
-
[["fade-in", "blur-in"], "fade-blur-in"]
|
|
2749
|
-
];
|
|
2750
|
-
function registerCore(registry) {
|
|
2751
|
-
registry.registerPrimitives(PRIMITIVES).registerPresets(PRESETS);
|
|
2752
|
-
for (const [names, preset] of COMBOS) registry.registerCombo(names, preset);
|
|
2753
|
-
return registry;
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
// src/effects/step-marking.ts
|
|
2757
|
-
var STEP_STATE_ATTR = "data-kui-step-state";
|
|
2758
|
-
function selectorBreadth(selector, doc) {
|
|
2759
|
-
try {
|
|
2760
|
-
if (doc.documentElement.matches(selector)) return "document-wide";
|
|
2761
|
-
if (doc.body?.matches(selector)) return "document-wide";
|
|
2762
|
-
return "ok";
|
|
2763
|
-
} catch {
|
|
2764
|
-
return "invalid";
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
function resolveTarget(selector, ctx, effect) {
|
|
2768
|
-
if (!selector) return selector;
|
|
2769
|
-
const breadth = selectorBreadth(selector, ctx.doc);
|
|
2770
|
-
if (breadth === "invalid") {
|
|
2771
|
-
ctx.warn(`${effect} target "${selector}" is not a valid selector and will be ignored`);
|
|
2772
|
-
return "";
|
|
2773
|
-
}
|
|
2774
|
-
if (breadth === "document-wide") {
|
|
2775
|
-
ctx.warn(`${effect} target "${selector}" matches the whole document and will be ignored`);
|
|
2776
|
-
return "";
|
|
2777
|
-
}
|
|
2778
|
-
return selector;
|
|
2779
|
-
}
|
|
2780
|
-
function createStepMarker(resolve) {
|
|
2781
|
-
const ledgers = /* @__PURE__ */ new Map();
|
|
2782
|
-
return {
|
|
2783
|
-
mark(index) {
|
|
2784
|
-
const seen = /* @__PURE__ */ new Map();
|
|
2785
|
-
for (const node of resolve()) {
|
|
2786
|
-
const parent = node.parentElement;
|
|
2787
|
-
const position = seen.get(parent) ?? 0;
|
|
2788
|
-
seen.set(parent, position + 1);
|
|
2789
|
-
let ledger = ledgers.get(node);
|
|
2790
|
-
if (!ledger) {
|
|
2791
|
-
ledger = createAttributeLedger(node);
|
|
2792
|
-
ledgers.set(node, ledger);
|
|
2793
|
-
}
|
|
2794
|
-
ledger.set(STEP_STATE_ATTR, stepStateFor(position, index));
|
|
2795
|
-
}
|
|
2796
|
-
},
|
|
2797
|
-
restore() {
|
|
2798
|
-
for (const ledger of ledgers.values()) ledger.restore();
|
|
2799
|
-
ledgers.clear();
|
|
2800
|
-
}
|
|
2801
|
-
};
|
|
2802
|
-
}
|
|
2803
|
-
function stepStateFor(position, index) {
|
|
2804
|
-
if (position < index) return "before";
|
|
2805
|
-
if (position === index) return "active";
|
|
2806
|
-
return "after";
|
|
2807
|
-
}
|
|
2808
|
-
|
|
2809
|
-
// src/effects/forms/primitives.ts
|
|
2810
|
-
var timing = {
|
|
2811
|
-
duration: { type: "time", default: "400ms", cssProperty: "--kui-duration" },
|
|
2812
|
-
...TRIGGER_DELAY_PARAM,
|
|
2813
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" }
|
|
2814
|
-
};
|
|
2815
|
-
var NATIVE_STATE_PRIMITIVE = {
|
|
2816
|
-
id: "native-state",
|
|
2817
|
-
renderer: "javascript",
|
|
2818
|
-
channels: [CHANNEL.translate, CHANNEL.scale, CHANNEL.opacity, CHANNEL.stroke, CHANNEL.color],
|
|
2819
|
-
parameters: {},
|
|
2820
|
-
supportedTimelines: ["time"],
|
|
2821
|
-
supportedActivations: ["load"],
|
|
2822
|
-
defaultActivation: "load",
|
|
2823
|
-
perfClass: "compositor",
|
|
2824
|
-
// Native pseudo-classes drive these and the motion lands on a sibling, not on the control that
|
|
2825
|
-
// carries the attribute, so base.css's policy layer enforces this through its sibling
|
|
2826
|
-
// `transition-duration` rules rather than the `animation-*` ones.
|
|
2827
|
-
reducedMotion: "disable",
|
|
2828
|
-
prepare: () => inertInstance()
|
|
2829
|
-
};
|
|
2830
|
-
function prepareSiblingScale(cssProperty) {
|
|
2831
|
-
return (el, params) => {
|
|
2832
|
-
const sibling = el.nextElementSibling;
|
|
2833
|
-
sibling?.style.setProperty(cssProperty, String(params.num("scale", 1)));
|
|
2834
|
-
return () => sibling?.style.removeProperty(cssProperty);
|
|
2835
|
-
};
|
|
2836
|
-
}
|
|
2837
|
-
function siblingScalePrimitive(id, cssProperty) {
|
|
2838
|
-
return {
|
|
2839
|
-
id,
|
|
2840
|
-
renderer: "javascript",
|
|
2841
|
-
channels: [CHANNEL.translate, CHANNEL.scale, CHANNEL.opacity, CHANNEL.stroke, CHANNEL.color],
|
|
2842
|
-
parameters: { scale: { type: "number", default: "1", cssProperty } },
|
|
2843
|
-
supportedTimelines: ["time"],
|
|
2844
|
-
supportedActivations: ["load"],
|
|
2845
|
-
defaultActivation: "load",
|
|
2846
|
-
perfClass: "compositor",
|
|
2847
|
-
reducedMotion: "disable",
|
|
2848
|
-
prepare: deferPrepare(prepareSiblingScale(cssProperty))
|
|
2849
|
-
};
|
|
2850
|
-
}
|
|
2851
|
-
var TOGGLE_MORPH_PRIMITIVE = siblingScalePrimitive("toggle-morph", "--kui-toggle-scale");
|
|
2852
|
-
var RADIO_FILL_PRIMITIVE = siblingScalePrimitive("radio-fill", "--kui-radio-scale");
|
|
2853
|
-
var FOCUS_RING_PRIMITIVE = cssPrimitive("focus-ring", ["shadow"], {
|
|
2854
|
-
activations: ["focus", "manual"],
|
|
2855
|
-
defaultActivation: "focus"
|
|
2856
|
-
});
|
|
2857
|
-
var VALIDATE_SHAKE_PRIMITIVE = cssPrimitive("validate-shake", [CHANNEL.translate], {
|
|
2858
|
-
activations: ["click", "manual"],
|
|
2859
|
-
defaultActivation: "click"
|
|
2860
|
-
});
|
|
2861
|
-
var VALIDATE_CHECK_PRIMITIVE = cssPrimitive("validate-check", [CHANNEL.stroke], {
|
|
2862
|
-
activations: ["click", "manual"],
|
|
2863
|
-
defaultActivation: "click"
|
|
2864
|
-
});
|
|
2865
|
-
function jsInputPrimitive(id, channels, parameters, prepare) {
|
|
2866
|
-
return {
|
|
2867
|
-
id,
|
|
2868
|
-
renderer: "javascript",
|
|
2869
|
-
channels,
|
|
2870
|
-
parameters: { ...timing, ...parameters },
|
|
2871
|
-
supportedTimelines: ["time"],
|
|
2872
|
-
supportedActivations: ["load"],
|
|
2873
|
-
defaultActivation: "load",
|
|
2874
|
-
perfClass: "compositor",
|
|
2875
|
-
reducedMotion: "disable",
|
|
2876
|
-
prepare
|
|
2877
|
-
};
|
|
2878
|
-
}
|
|
2879
|
-
function computeStrength(value) {
|
|
2880
|
-
let score = 0;
|
|
2881
|
-
if (value.length >= 6) score++;
|
|
2882
|
-
if (value.length >= 10) score++;
|
|
2883
|
-
if (/[a-z]/.test(value) && /[A-Z]/.test(value)) score++;
|
|
2884
|
-
if (/\d/.test(value)) score++;
|
|
2885
|
-
if (/[^A-Za-z0-9]/.test(value)) score++;
|
|
2886
|
-
return Math.min(4, score);
|
|
2887
|
-
}
|
|
2888
|
-
function prepareStrengthMeter(el) {
|
|
2889
|
-
const input = el;
|
|
2890
|
-
const update = () => {
|
|
2891
|
-
el.setAttribute("data-kui-strength-level", String(computeStrength(input.value)));
|
|
2892
|
-
};
|
|
2893
|
-
input.addEventListener("input", update);
|
|
2894
|
-
update();
|
|
2895
|
-
return () => {
|
|
2896
|
-
input.removeEventListener("input", update);
|
|
2897
|
-
el.removeAttribute("data-kui-strength-level");
|
|
2898
|
-
};
|
|
2899
|
-
}
|
|
2900
|
-
function prepareRangeFill(el, params, ctx) {
|
|
2901
|
-
const input = el;
|
|
2902
|
-
const update = () => {
|
|
2903
|
-
const min = Number(input.min || "0");
|
|
2904
|
-
const max = Number(input.max || "100");
|
|
2905
|
-
const pct = max > min ? (Number(input.value) - min) / (max - min) * 100 : 0;
|
|
2906
|
-
ctx.style.set("--kui-fill", `${pct.toFixed(2)}%`);
|
|
2907
|
-
};
|
|
2908
|
-
input.addEventListener("input", update);
|
|
2909
|
-
update();
|
|
2910
|
-
return () => input.removeEventListener("input", update);
|
|
2911
|
-
}
|
|
2912
|
-
var STRENGTH_METER_PRIMITIVE = jsInputPrimitive(
|
|
2913
|
-
"strength-meter",
|
|
2914
|
-
["meter"],
|
|
2915
|
-
{},
|
|
2916
|
-
deferPrepare(prepareStrengthMeter)
|
|
2917
|
-
);
|
|
2918
|
-
var RANGE_FILL_PRIMITIVE = jsInputPrimitive(
|
|
2919
|
-
"range-fill",
|
|
2920
|
-
[CHANNEL.background],
|
|
2921
|
-
{},
|
|
2922
|
-
deferPrepare(prepareRangeFill)
|
|
2923
|
-
);
|
|
2924
|
-
function nextStep(step, total) {
|
|
2925
|
-
return total > 0 ? (step + 1) % total : 0;
|
|
2926
|
-
}
|
|
2927
|
-
function prepareStepProgress(el, params, ctx) {
|
|
2928
|
-
const total = Math.max(1, Math.round(params.num("steps", 4)));
|
|
2929
|
-
const selector = resolveTarget(params.text("target"), ctx, "step-progress");
|
|
2930
|
-
const marker = createStepMarker(
|
|
2931
|
-
() => selector ? ctx.doc.querySelectorAll(selector) : el.children
|
|
2932
|
-
);
|
|
2933
|
-
const self = createAttributeLedger(el);
|
|
2934
|
-
let step = 0;
|
|
2935
|
-
const render = () => {
|
|
2936
|
-
self.set("data-kui-step", String(step));
|
|
2937
|
-
marker.mark(step);
|
|
2938
|
-
};
|
|
2939
|
-
const advance = () => {
|
|
2940
|
-
step = nextStep(step, total);
|
|
2941
|
-
render();
|
|
2942
|
-
};
|
|
2943
|
-
el.addEventListener("click", advance);
|
|
2944
|
-
render();
|
|
2945
|
-
return () => {
|
|
2946
|
-
el.removeEventListener("click", advance);
|
|
2947
|
-
self.restore();
|
|
2948
|
-
marker.restore();
|
|
2949
|
-
};
|
|
2950
|
-
}
|
|
2951
|
-
var STEP_PROGRESS_PRIMITIVE = jsInputPrimitive(
|
|
2952
|
-
"step-progress",
|
|
2953
|
-
["state"],
|
|
2954
|
-
{
|
|
2955
|
-
steps: { type: "number", default: "4", cssProperty: "--kui-steps", minimum: 1, maximum: 20, integer: true },
|
|
2956
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" }
|
|
2957
|
-
},
|
|
2958
|
-
deferPrepare(prepareStepProgress)
|
|
2959
|
-
);
|
|
2960
|
-
function nextSubmitStage(stage) {
|
|
2961
|
-
if (stage === "idle") return "loading";
|
|
2962
|
-
if (stage === "loading") return "done";
|
|
2963
|
-
return "idle";
|
|
2964
|
-
}
|
|
2965
|
-
function prepareSubmitFlow(el, params, ctx) {
|
|
2966
|
-
const loadMs = params.ms("load", 1200);
|
|
2967
|
-
const holdMs = params.ms("hold", 1500);
|
|
2968
|
-
let stage = "idle";
|
|
2969
|
-
let handle;
|
|
2970
|
-
const render = () => el.setAttribute("data-kui-stage", stage);
|
|
2971
|
-
const toIdle = () => {
|
|
2972
|
-
stage = nextSubmitStage(stage);
|
|
2973
|
-
render();
|
|
2974
|
-
};
|
|
2975
|
-
const toDone = () => {
|
|
2976
|
-
stage = nextSubmitStage(stage);
|
|
2977
|
-
render();
|
|
2978
|
-
handle = ctx.win.setTimeout(toIdle, holdMs);
|
|
2979
|
-
};
|
|
2980
|
-
const advance = () => {
|
|
2981
|
-
if (stage !== "idle") return;
|
|
2982
|
-
stage = nextSubmitStage(stage);
|
|
2983
|
-
render();
|
|
2984
|
-
handle = ctx.win.setTimeout(toDone, loadMs);
|
|
2985
|
-
};
|
|
2986
|
-
el.addEventListener("click", advance);
|
|
2987
|
-
render();
|
|
2988
|
-
return () => {
|
|
2989
|
-
el.removeEventListener("click", advance);
|
|
2990
|
-
if (handle !== void 0) ctx.win.clearTimeout(handle);
|
|
2991
|
-
el.removeAttribute("data-kui-stage");
|
|
2992
|
-
};
|
|
2993
|
-
}
|
|
2994
|
-
var SUBMIT_FLOW_PRIMITIVE = jsInputPrimitive(
|
|
2995
|
-
"submit-flow",
|
|
2996
|
-
["state"],
|
|
2997
|
-
{
|
|
2998
|
-
load: { type: "time", default: "1200ms", cssProperty: "--kui-load" },
|
|
2999
|
-
hold: { type: "time", default: "1500ms", cssProperty: "--kui-hold" }
|
|
3000
|
-
},
|
|
3001
|
-
deferPrepare(prepareSubmitFlow)
|
|
3002
|
-
);
|
|
3003
|
-
|
|
3004
|
-
// src/effects/forms/index.ts
|
|
3005
|
-
var FORMS_PRIMITIVES = [
|
|
3006
|
-
NATIVE_STATE_PRIMITIVE,
|
|
3007
|
-
FOCUS_RING_PRIMITIVE,
|
|
3008
|
-
VALIDATE_SHAKE_PRIMITIVE,
|
|
3009
|
-
VALIDATE_CHECK_PRIMITIVE,
|
|
3010
|
-
STRENGTH_METER_PRIMITIVE,
|
|
3011
|
-
TOGGLE_MORPH_PRIMITIVE,
|
|
3012
|
-
RADIO_FILL_PRIMITIVE,
|
|
3013
|
-
RANGE_FILL_PRIMITIVE,
|
|
3014
|
-
STEP_PROGRESS_PRIMITIVE,
|
|
3015
|
-
SUBMIT_FLOW_PRIMITIVE
|
|
3016
|
-
];
|
|
3017
|
-
var FORMS_PRESETS = [
|
|
3018
|
-
{ name: "label-float", primitive: "native-state" },
|
|
3019
|
-
{ name: "input-underline-grow", primitive: "native-state" },
|
|
3020
|
-
{ name: "focus-ring-grow", primitive: "focus-ring", keyframes: "kui-focus-ring-grow" },
|
|
3021
|
-
{ name: "validate-shake", primitive: "validate-shake", keyframes: "kui-validate-shake" },
|
|
3022
|
-
{ name: "validate-check", primitive: "validate-check", keyframes: "kui-validate-check" },
|
|
3023
|
-
{ name: "strength-meter", primitive: "strength-meter" },
|
|
3024
|
-
{ name: "toggle-morph", primitive: "toggle-morph" },
|
|
3025
|
-
{ name: "checkbox-draw", primitive: "native-state" },
|
|
3026
|
-
{ name: "radio-fill", primitive: "radio-fill" },
|
|
3027
|
-
{ name: "range-fill", primitive: "range-fill" },
|
|
3028
|
-
{ name: "submit-to-spinner-to-check", primitive: "submit-flow" },
|
|
3029
|
-
{ name: "step-progress", primitive: "step-progress" }
|
|
3030
|
-
];
|
|
3031
|
-
function registerForms(registry) {
|
|
3032
|
-
return registry.registerPrimitives(FORMS_PRIMITIVES).registerPresets(FORMS_PRESETS);
|
|
3033
|
-
}
|
|
3034
|
-
|
|
3035
|
-
// src/core/gesture.ts
|
|
3036
|
-
var VELOCITY_WINDOW_MS = 100;
|
|
3037
|
-
var MAX_SAMPLES = 12;
|
|
3038
|
-
function velocityFrom(samples) {
|
|
3039
|
-
const last = samples[samples.length - 1];
|
|
3040
|
-
if (!last || samples.length < 2) return { vx: 0, vy: 0 };
|
|
3041
|
-
const cutoff = last.time - VELOCITY_WINDOW_MS;
|
|
3042
|
-
const first = samples.find((sample) => sample.time >= cutoff) ?? samples[0];
|
|
3043
|
-
const span = (last.time - first.time) / 1e3;
|
|
3044
|
-
if (span <= 0) return { vx: 0, vy: 0 };
|
|
3045
|
-
return { vx: (last.x - first.x) / span, vy: (last.y - first.y) / span };
|
|
3046
|
-
}
|
|
3047
|
-
function swipeDirection(vector, minVelocity) {
|
|
3048
|
-
const { vx, vy } = vector;
|
|
3049
|
-
if (Math.abs(vx) < minVelocity && Math.abs(vy) < minVelocity) return null;
|
|
3050
|
-
if (Math.abs(vx) >= Math.abs(vy)) return vx > 0 ? "right" : "left";
|
|
3051
|
-
return vy > 0 ? "down" : "up";
|
|
3052
|
-
}
|
|
3053
|
-
function applyAxis(vector, axis) {
|
|
3054
|
-
if (axis === "x") return { ...vector, dy: 0, vy: 0 };
|
|
3055
|
-
if (axis === "y") return { ...vector, dx: 0, vx: 0 };
|
|
3056
|
-
return vector;
|
|
3057
|
-
}
|
|
3058
|
-
function recognise(el, handlers, options = {}, deps = defaultGestureDeps()) {
|
|
3059
|
-
const threshold = options.threshold ?? 4;
|
|
3060
|
-
const axis = options.axis ?? "both";
|
|
3061
|
-
const swipeVelocity = options.swipeVelocity ?? 300;
|
|
3062
|
-
const longPressMs = options.longPressMs ?? 0;
|
|
3063
|
-
let samples = [];
|
|
3064
|
-
let origin = null;
|
|
3065
|
-
let active = false;
|
|
3066
|
-
let longPressTimer = null;
|
|
3067
|
-
let longPressFired = false;
|
|
3068
|
-
function sampleOf(event) {
|
|
3069
|
-
return { x: event.clientX, y: event.clientY, time: deps.now() };
|
|
3070
|
-
}
|
|
3071
|
-
function vectorNow(sample) {
|
|
3072
|
-
const start = origin;
|
|
3073
|
-
const { vx, vy } = velocityFrom(samples);
|
|
3074
|
-
return applyAxis({ dx: sample.x - start.x, dy: sample.y - start.y, vx, vy }, axis);
|
|
3075
|
-
}
|
|
3076
|
-
function clearLongPress() {
|
|
3077
|
-
if (longPressTimer !== null) deps.clearTimer(longPressTimer);
|
|
3078
|
-
longPressTimer = null;
|
|
3079
|
-
}
|
|
3080
|
-
function onDown(event) {
|
|
3081
|
-
origin = sampleOf(event);
|
|
3082
|
-
samples = [origin];
|
|
3083
|
-
active = false;
|
|
3084
|
-
longPressFired = false;
|
|
3085
|
-
el.setPointerCapture?.(event.pointerId);
|
|
3086
|
-
if (longPressMs > 0) {
|
|
3087
|
-
longPressTimer = deps.setTimer(() => {
|
|
3088
|
-
longPressFired = true;
|
|
3089
|
-
handlers.onLongPress?.(origin);
|
|
3090
|
-
}, longPressMs);
|
|
3091
|
-
}
|
|
3092
|
-
}
|
|
3093
|
-
function onMove(event) {
|
|
3094
|
-
if (!origin) return;
|
|
3095
|
-
const sample = sampleOf(event);
|
|
3096
|
-
samples.push(sample);
|
|
3097
|
-
if (samples.length > MAX_SAMPLES) samples.shift();
|
|
3098
|
-
const vector = vectorNow(sample);
|
|
3099
|
-
if (!active && Math.hypot(vector.dx, vector.dy) < threshold) return;
|
|
3100
|
-
if (!active) {
|
|
3101
|
-
active = true;
|
|
3102
|
-
clearLongPress();
|
|
3103
|
-
handlers.onStart?.(origin);
|
|
3104
|
-
}
|
|
3105
|
-
handlers.onMove?.(vector, sample);
|
|
3106
|
-
}
|
|
3107
|
-
function onUp(event) {
|
|
3108
|
-
clearLongPress();
|
|
3109
|
-
el.releasePointerCapture?.(event.pointerId);
|
|
3110
|
-
if (!origin) return;
|
|
3111
|
-
const sample = sampleOf(event);
|
|
3112
|
-
samples.push(sample);
|
|
3113
|
-
const vector = vectorNow(sample);
|
|
3114
|
-
if (active) {
|
|
3115
|
-
handlers.onEnd?.(vector, sample);
|
|
3116
|
-
const direction = swipeDirection(vector, swipeVelocity);
|
|
3117
|
-
if (direction) handlers.onSwipe?.(direction, vector);
|
|
3118
|
-
} else if (longPressFired) {
|
|
3119
|
-
handlers.onEnd?.(vector, sample);
|
|
3120
|
-
}
|
|
3121
|
-
origin = null;
|
|
3122
|
-
active = false;
|
|
3123
|
-
longPressFired = false;
|
|
3124
|
-
samples = [];
|
|
3125
|
-
}
|
|
3126
|
-
el.addEventListener("pointerdown", onDown);
|
|
3127
|
-
el.addEventListener("pointermove", onMove, { passive: true });
|
|
3128
|
-
el.addEventListener("pointerup", onUp, { passive: true });
|
|
3129
|
-
el.addEventListener("pointercancel", onUp, { passive: true });
|
|
3130
|
-
return () => {
|
|
3131
|
-
clearLongPress();
|
|
3132
|
-
el.removeEventListener("pointerdown", onDown);
|
|
3133
|
-
el.removeEventListener("pointermove", onMove);
|
|
3134
|
-
el.removeEventListener("pointerup", onUp);
|
|
3135
|
-
el.removeEventListener("pointercancel", onUp);
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
function defaultGestureDeps() {
|
|
3139
|
-
return {
|
|
3140
|
-
now: () => typeof performance === "undefined" ? Date.now() : performance.now(),
|
|
3141
|
-
setTimer: (callback, ms) => globalThis.setTimeout(callback, ms),
|
|
3142
|
-
clearTimer: (handle) => globalThis.clearTimeout(handle)
|
|
3143
|
-
};
|
|
3144
|
-
}
|
|
3145
|
-
function rubberBand(offset, limit, tension = 0.55) {
|
|
3146
|
-
if (limit <= 0) return 0;
|
|
3147
|
-
const sign = Math.sign(offset);
|
|
3148
|
-
const magnitude = Math.abs(offset);
|
|
3149
|
-
return sign * (1 - 1 / (magnitude / limit / tension + 1)) * limit;
|
|
3150
|
-
}
|
|
3151
|
-
|
|
3152
|
-
// src/effects/gestures/primitives.ts
|
|
3153
|
-
var springParams2 = {
|
|
3154
|
-
stiffness: {
|
|
3155
|
-
type: "number",
|
|
3156
|
-
default: "180",
|
|
3157
|
-
cssProperty: "--kui-stiffness",
|
|
3158
|
-
finite: true,
|
|
3159
|
-
minimum: 1,
|
|
3160
|
-
maximum: 1e4
|
|
3161
|
-
},
|
|
3162
|
-
damping: {
|
|
3163
|
-
type: "number",
|
|
3164
|
-
default: "24",
|
|
3165
|
-
cssProperty: "--kui-damping",
|
|
3166
|
-
finite: true,
|
|
3167
|
-
minimum: 0.1,
|
|
3168
|
-
maximum: 1e3
|
|
3169
|
-
},
|
|
3170
|
-
mass: {
|
|
3171
|
-
type: "number",
|
|
3172
|
-
default: "1",
|
|
3173
|
-
cssProperty: "--kui-mass",
|
|
3174
|
-
finite: true,
|
|
3175
|
-
minimum: 0.1
|
|
3176
|
-
}
|
|
3177
|
-
};
|
|
3178
|
-
function gesturePrimitive(id, channels, parameters, prepare) {
|
|
3179
|
-
return {
|
|
3180
|
-
id,
|
|
3181
|
-
renderer: "javascript",
|
|
3182
|
-
channels,
|
|
3183
|
-
parameters,
|
|
3184
|
-
supportedTimelines: ["time", "pointer"],
|
|
3185
|
-
supportedActivations: ["load", "manual"],
|
|
3186
|
-
defaultActivation: "load",
|
|
3187
|
-
perfClass: "continuous",
|
|
3188
|
-
reducedMotion: "disable",
|
|
3189
|
-
prepare
|
|
3190
|
-
};
|
|
3191
|
-
}
|
|
3192
|
-
function springFrom2(params) {
|
|
3193
|
-
return {
|
|
3194
|
-
...DEFAULT_SPRING,
|
|
3195
|
-
stiffness: params.num("stiffness", DEFAULT_SPRING.stiffness),
|
|
3196
|
-
damping: params.num("damping", DEFAULT_SPRING.damping),
|
|
3197
|
-
mass: params.num("mass", DEFAULT_SPRING.mass)
|
|
3198
|
-
};
|
|
3199
|
-
}
|
|
3200
|
-
function springDeps(ctx) {
|
|
3201
|
-
return { ...defaultSpringDeps(), warn: ctx.warn };
|
|
3202
|
-
}
|
|
3203
|
-
function writeOffset(ctx, x, y) {
|
|
3204
|
-
ctx.style.set("translate", `${x.toFixed(2)}px ${y.toFixed(2)}px`);
|
|
3205
|
-
}
|
|
3206
|
-
function prepareDraggable(el, params, ctx) {
|
|
3207
|
-
const config = springFrom2(params);
|
|
3208
|
-
const bounds = params.num("bounds", 0);
|
|
3209
|
-
const returns = params.is("return");
|
|
3210
|
-
const inertia = params.is("inertia");
|
|
3211
|
-
const resistance = params.num("resistance", 0.55);
|
|
3212
|
-
const momentum = params.num("momentum", 0.2);
|
|
3213
|
-
const offset = { x: 0, y: 0 };
|
|
3214
|
-
const pickup = { x: 0, y: 0 };
|
|
3215
|
-
const deps = springDeps(ctx);
|
|
3216
|
-
const runners = {
|
|
3217
|
-
x: createSpringRunner(config, (value) => write({ ...offset, x: value }), deps),
|
|
3218
|
-
y: createSpringRunner(config, (value) => write({ ...offset, y: value }), deps)
|
|
3219
|
-
};
|
|
3220
|
-
function write(next) {
|
|
3221
|
-
offset.x = next.x;
|
|
3222
|
-
offset.y = next.y;
|
|
3223
|
-
writeOffset(ctx, offset.x, offset.y);
|
|
3224
|
-
}
|
|
3225
|
-
const stopRecognising = recognise(
|
|
3226
|
-
el,
|
|
3227
|
-
{
|
|
3228
|
-
onStart() {
|
|
3229
|
-
runners.x.stop();
|
|
3230
|
-
runners.y.stop();
|
|
3231
|
-
pickup.x = offset.x;
|
|
3232
|
-
pickup.y = offset.y;
|
|
3233
|
-
el.setAttribute("data-kui-dragging", "true");
|
|
3234
|
-
},
|
|
3235
|
-
onMove(vector) {
|
|
3236
|
-
write({
|
|
3237
|
-
x: resist(pickup.x + vector.dx, bounds, resistance),
|
|
3238
|
-
y: resist(pickup.y + vector.dy, bounds, resistance)
|
|
3239
|
-
});
|
|
3240
|
-
},
|
|
3241
|
-
onEnd(vector) {
|
|
3242
|
-
el.setAttribute("data-kui-dragging", "false");
|
|
3243
|
-
settle(runners, offset, vector, { returns, inertia, momentum });
|
|
3244
|
-
}
|
|
3245
|
-
},
|
|
3246
|
-
{ axis: params.text("axis", "both") }
|
|
3247
|
-
);
|
|
3248
|
-
ctx.invalidate();
|
|
3249
|
-
return () => {
|
|
3250
|
-
stopRecognising();
|
|
3251
|
-
runners.x.stop();
|
|
3252
|
-
runners.y.stop();
|
|
3253
|
-
el.removeAttribute("data-kui-dragging");
|
|
3254
|
-
};
|
|
3255
|
-
}
|
|
3256
|
-
function resist(delta, bounds, tension) {
|
|
3257
|
-
return bounds > 0 ? rubberBand(delta, bounds, tension) : delta;
|
|
3258
|
-
}
|
|
3259
|
-
function settle(runners, offset, vector, mode) {
|
|
3260
|
-
runners.x.set(offset.x, mode.inertia ? vector.vx : 0);
|
|
3261
|
-
runners.y.set(offset.y, mode.inertia ? vector.vy : 0);
|
|
3262
|
-
if (mode.returns) {
|
|
3263
|
-
runners.x.to(0);
|
|
3264
|
-
runners.y.to(0);
|
|
3265
|
-
return;
|
|
3266
|
-
}
|
|
3267
|
-
const carry = mode.inertia ? mode.momentum : 0;
|
|
3268
|
-
runners.x.to(offset.x + vector.vx * carry);
|
|
3269
|
-
runners.y.to(offset.y + vector.vy * carry);
|
|
3270
|
-
}
|
|
3271
|
-
function prepareSwipeable(el, params) {
|
|
3272
|
-
const stop = recognise(
|
|
3273
|
-
el,
|
|
3274
|
-
{
|
|
3275
|
-
onSwipe(direction) {
|
|
3276
|
-
el.setAttribute("data-kui-swipe", direction);
|
|
3277
|
-
}
|
|
3278
|
-
},
|
|
3279
|
-
{
|
|
3280
|
-
axis: params.text("axis", "both"),
|
|
3281
|
-
swipeVelocity: params.num("velocity", 300)
|
|
3282
|
-
}
|
|
3283
|
-
);
|
|
3284
|
-
return () => {
|
|
3285
|
-
stop();
|
|
3286
|
-
el.removeAttribute("data-kui-swipe");
|
|
3287
|
-
};
|
|
3288
|
-
}
|
|
3289
|
-
function preparePressable(el, params) {
|
|
3290
|
-
const stop = recognise(
|
|
3291
|
-
el,
|
|
3292
|
-
{
|
|
3293
|
-
onLongPress() {
|
|
3294
|
-
el.setAttribute("data-kui-pressed", "true");
|
|
3295
|
-
},
|
|
3296
|
-
onEnd() {
|
|
3297
|
-
el.setAttribute("data-kui-pressed", "false");
|
|
3298
|
-
}
|
|
3299
|
-
},
|
|
3300
|
-
{ longPressMs: effectDurationMs(params, 500) }
|
|
3301
|
-
);
|
|
3302
|
-
return () => {
|
|
3303
|
-
stop();
|
|
3304
|
-
el.removeAttribute("data-kui-pressed");
|
|
3305
|
-
};
|
|
3306
|
-
}
|
|
3307
|
-
function prepareMagnetic(el, params, ctx) {
|
|
3308
|
-
const node = el;
|
|
3309
|
-
const radius = params.num("radius", 120);
|
|
3310
|
-
const strength = params.num("strength", 0.35);
|
|
3311
|
-
const config = springFrom2(params);
|
|
3312
|
-
const deps = springDeps(ctx);
|
|
3313
|
-
const offset = { x: 0, y: 0 };
|
|
3314
|
-
const runnerX = createSpringRunner(config, (value) => {
|
|
3315
|
-
offset.x = value;
|
|
3316
|
-
writeOffset(ctx, offset.x, offset.y);
|
|
3317
|
-
}, deps);
|
|
3318
|
-
const runnerY = createSpringRunner(config, (value) => {
|
|
3319
|
-
offset.y = value;
|
|
3320
|
-
writeOffset(ctx, offset.x, offset.y);
|
|
3321
|
-
}, deps);
|
|
3322
|
-
const onPointerMove = (event) => {
|
|
3323
|
-
const box = node.getBoundingClientRect();
|
|
3324
|
-
const dx = event.clientX - (box.left + box.width / 2);
|
|
3325
|
-
const dy = event.clientY - (box.top + box.height / 2);
|
|
3326
|
-
const inRange = Math.hypot(dx, dy) < radius;
|
|
3327
|
-
runnerX.to(inRange ? dx * strength : 0);
|
|
3328
|
-
runnerY.to(inRange ? dy * strength : 0);
|
|
3329
|
-
};
|
|
3330
|
-
ctx.win.addEventListener("pointermove", onPointerMove, { passive: true });
|
|
3331
|
-
return () => {
|
|
3332
|
-
ctx.win.removeEventListener("pointermove", onPointerMove);
|
|
3333
|
-
runnerX.stop();
|
|
3334
|
-
runnerY.stop();
|
|
3335
|
-
};
|
|
3336
|
-
}
|
|
3337
|
-
var GESTURE_PRIMITIVES = [
|
|
3338
|
-
gesturePrimitive(
|
|
3339
|
-
"draggable",
|
|
3340
|
-
["translate"],
|
|
3341
|
-
{
|
|
3342
|
-
...springParams2,
|
|
3343
|
-
axis: { type: "keyword", default: "both", cssProperty: "--kui-axis", values: ["x", "y", "both"] },
|
|
3344
|
-
bounds: { type: "number", default: "0", cssProperty: "--kui-bounds" },
|
|
3345
|
-
return: {
|
|
3346
|
-
type: "keyword",
|
|
3347
|
-
default: "false",
|
|
3348
|
-
cssProperty: "--kui-return",
|
|
3349
|
-
values: ["true", "false"]
|
|
3350
|
-
},
|
|
3351
|
-
inertia: {
|
|
3352
|
-
type: "keyword",
|
|
3353
|
-
default: "false",
|
|
3354
|
-
cssProperty: "--kui-inertia",
|
|
3355
|
-
values: ["true", "false"]
|
|
3356
|
-
},
|
|
3357
|
-
resistance: {
|
|
3358
|
-
type: "number",
|
|
3359
|
-
default: "0.55",
|
|
3360
|
-
cssProperty: "--kui-resistance",
|
|
3361
|
-
finite: true,
|
|
3362
|
-
minimum: 0,
|
|
3363
|
-
maximum: 1
|
|
3364
|
-
},
|
|
3365
|
-
momentum: { type: "number", default: "0.2", cssProperty: "--kui-momentum", finite: true }
|
|
3366
|
-
},
|
|
3367
|
-
deferPrepare(prepareDraggable)
|
|
3368
|
-
),
|
|
3369
|
-
gesturePrimitive(
|
|
3370
|
-
"swipeable",
|
|
3371
|
-
["state"],
|
|
3372
|
-
{
|
|
3373
|
-
axis: { type: "keyword", default: "both", cssProperty: "--kui-axis", values: ["x", "y", "both"] },
|
|
3374
|
-
velocity: { type: "number", default: "300", cssProperty: "--kui-velocity" }
|
|
3375
|
-
},
|
|
3376
|
-
deferPrepare(prepareSwipeable)
|
|
3377
|
-
),
|
|
3378
|
-
gesturePrimitive(
|
|
3379
|
-
"pressable",
|
|
3380
|
-
["state"],
|
|
3381
|
-
{ duration: { type: "time", default: "500ms", cssProperty: "--kui-duration" } },
|
|
3382
|
-
deferPrepare(preparePressable)
|
|
3383
|
-
),
|
|
3384
|
-
gesturePrimitive(
|
|
3385
|
-
"magnetic",
|
|
3386
|
-
["translate"],
|
|
3387
|
-
{
|
|
3388
|
-
...springParams2,
|
|
3389
|
-
radius: { type: "number", default: "120", cssProperty: "--kui-radius" },
|
|
3390
|
-
strength: { type: "number", default: "0.35", cssProperty: "--kui-strength" }
|
|
3391
|
-
},
|
|
3392
|
-
deferPrepare(prepareMagnetic)
|
|
3393
|
-
)
|
|
3394
|
-
];
|
|
3395
|
-
|
|
3396
|
-
// src/effects/gestures/index.ts
|
|
3397
|
-
var GESTURE_PRESETS = [
|
|
3398
|
-
{ name: "drag", primitive: "draggable" },
|
|
3399
|
-
{ name: "drag-x", primitive: "draggable", params: { axis: "x" } },
|
|
3400
|
-
{ name: "drag-y", primitive: "draggable", params: { axis: "y" } },
|
|
3401
|
-
{ name: "drag-inertia", primitive: "draggable", params: { inertia: "true" } },
|
|
3402
|
-
{ name: "throwable", primitive: "draggable", params: { inertia: "true", damping: "18" } },
|
|
3403
|
-
{ name: "elastic-pull", primitive: "draggable", params: { return: "true", bounds: "80" } },
|
|
3404
|
-
{ name: "rubber-band", primitive: "draggable", params: { return: "true", bounds: "120" } },
|
|
3405
|
-
{ name: "snap-back", primitive: "draggable", params: { return: "true", stiffness: "260" } },
|
|
3406
|
-
{ name: "swipe", primitive: "swipeable" },
|
|
3407
|
-
{ name: "swipe-x", primitive: "swipeable", params: { axis: "x" } },
|
|
3408
|
-
{ name: "long-press", primitive: "pressable" },
|
|
3409
|
-
{ name: "magnetic", primitive: "magnetic" },
|
|
3410
|
-
{ name: "magnetic-snap", primitive: "magnetic", params: { strength: "0.6", radius: "160" } }
|
|
3411
|
-
];
|
|
3412
|
-
function registerGestures(registry) {
|
|
3413
|
-
return registry.registerPrimitives(GESTURE_PRIMITIVES).registerPresets(GESTURE_PRESETS);
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
// src/core/flip.ts
|
|
3417
|
-
var EPSILON = 0.5;
|
|
3418
|
-
function domMeasure(el) {
|
|
3419
|
-
const rect = el.getBoundingClientRect();
|
|
3420
|
-
return { x: rect.left, y: rect.top, width: rect.width, height: rect.height };
|
|
3421
|
-
}
|
|
3422
|
-
function domAnimate(el, keyframes, options) {
|
|
3423
|
-
const animate = el.animate;
|
|
3424
|
-
return typeof animate === "function" ? animate.call(el, keyframes, options) : null;
|
|
3425
|
-
}
|
|
3426
|
-
function createFlipEngine(deps = {}) {
|
|
3427
|
-
const measure = deps.measure ?? domMeasure;
|
|
3428
|
-
const animate = deps.animate ?? domAnimate;
|
|
3429
|
-
return {
|
|
3430
|
-
snapshot(elements) {
|
|
3431
|
-
const boxes = /* @__PURE__ */ new Map();
|
|
3432
|
-
for (const el of elements) boxes.set(el, measure(el));
|
|
3433
|
-
return { boxes };
|
|
3434
|
-
},
|
|
3435
|
-
play(before, elements, options = {}) {
|
|
3436
|
-
const deltas = collectDeltas(before, elements, measure, options.scale ?? false);
|
|
3437
|
-
if (deltas.length === 0) {
|
|
3438
|
-
return { moved: [], finished: Promise.resolve(), cancel: () => {
|
|
3439
|
-
} };
|
|
3440
|
-
}
|
|
3441
|
-
return runDeltas(deltas, animate, options);
|
|
3442
|
-
}
|
|
3443
|
-
};
|
|
3444
|
-
}
|
|
3445
|
-
function collectDeltas(before, elements, measure, scale) {
|
|
3446
|
-
const deltas = [];
|
|
3447
|
-
for (const el of elements) {
|
|
3448
|
-
const first = before.boxes.get(el);
|
|
3449
|
-
if (!first) continue;
|
|
3450
|
-
const last = measure(el);
|
|
3451
|
-
const delta = deltaFor(el, first, last, scale);
|
|
3452
|
-
if (delta) deltas.push(delta);
|
|
3453
|
-
}
|
|
3454
|
-
return deltas;
|
|
3455
|
-
}
|
|
3456
|
-
function deltaFor(el, first, last, scale) {
|
|
3457
|
-
const dx = first.x - last.x;
|
|
3458
|
-
const dy = first.y - last.y;
|
|
3459
|
-
const sx = scale && last.width > 0 ? first.width / last.width : 1;
|
|
3460
|
-
const sy = scale && last.height > 0 ? first.height / last.height : 1;
|
|
3461
|
-
const still = Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON && Math.abs(sx - 1) < 1e-3 && Math.abs(sy - 1) < 1e-3;
|
|
3462
|
-
return still ? null : { el, dx, dy, sx, sy };
|
|
3463
|
-
}
|
|
3464
|
-
function runDeltas(deltas, animate, options) {
|
|
3465
|
-
const duration = options.durationMs ?? 400;
|
|
3466
|
-
const easing = options.easing ?? "cubic-bezier(0.2, 0, 0, 1)";
|
|
3467
|
-
const animations = [];
|
|
3468
|
-
for (const { el, dx, dy, sx, sy } of deltas) {
|
|
3469
|
-
const animation = animate(
|
|
3470
|
-
el,
|
|
3471
|
-
[
|
|
3472
|
-
{ translate: `${dx}px ${dy}px`, scale: `${sx} ${sy}` },
|
|
3473
|
-
{ translate: "0px 0px", scale: "1 1" }
|
|
3474
|
-
],
|
|
3475
|
-
{ duration, easing, fill: "none" }
|
|
3476
|
-
);
|
|
3477
|
-
if (animation) animations.push(animation);
|
|
3478
|
-
}
|
|
3479
|
-
const finished = Promise.all(
|
|
3480
|
-
animations.map((animation) => animation.finished.catch(() => void 0))
|
|
3481
|
-
).then(() => void 0);
|
|
3482
|
-
return {
|
|
3483
|
-
moved: deltas.map((delta) => delta.el),
|
|
3484
|
-
finished,
|
|
3485
|
-
cancel() {
|
|
3486
|
-
for (const animation of animations) animation.cancel();
|
|
3487
|
-
}
|
|
3488
|
-
};
|
|
3489
|
-
}
|
|
3490
|
-
function observeLayout(container, engine, options, observe) {
|
|
3491
|
-
let before = engine.snapshot(container.children);
|
|
3492
|
-
const cleanup = observe(() => {
|
|
3493
|
-
engine.play(before, container.children, options);
|
|
3494
|
-
before = engine.snapshot(container.children);
|
|
3495
|
-
});
|
|
3496
|
-
return cleanup;
|
|
3497
|
-
}
|
|
3498
|
-
function mutationWatcher(container) {
|
|
3499
|
-
return (callback) => {
|
|
3500
|
-
if (typeof MutationObserver === "undefined") return () => {
|
|
3501
|
-
};
|
|
3502
|
-
const observer = new MutationObserver(callback);
|
|
3503
|
-
observer.observe(container, {
|
|
3504
|
-
childList: true,
|
|
3505
|
-
subtree: true,
|
|
3506
|
-
attributes: true,
|
|
3507
|
-
attributeFilter: ["hidden"]
|
|
3508
|
-
});
|
|
3509
|
-
return () => observer.disconnect();
|
|
3510
|
-
};
|
|
3511
|
-
}
|
|
3512
|
-
|
|
3513
|
-
// src/effects/layout/primitives.ts
|
|
3514
|
-
var timing2 = {
|
|
3515
|
-
duration: { type: "time", default: "400ms", cssProperty: "--kui-duration" },
|
|
3516
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" }
|
|
3517
|
-
};
|
|
3518
|
-
function layoutPrimitive(id, channels, parameters, prepare) {
|
|
3519
|
-
return {
|
|
3520
|
-
id,
|
|
3521
|
-
renderer: "javascript",
|
|
3522
|
-
channels,
|
|
3523
|
-
parameters: { ...timing2, ...parameters },
|
|
3524
|
-
supportedTimelines: ["time"],
|
|
3525
|
-
supportedActivations: ["load", "manual", "click"],
|
|
3526
|
-
defaultActivation: "load",
|
|
3527
|
-
perfClass: "layout",
|
|
3528
|
-
// A layout transition that is merely faster is still a layout transition; under reduced
|
|
3529
|
-
// motion the correct behaviour is for elements to appear in place.
|
|
3530
|
-
reducedMotion: "disable",
|
|
3531
|
-
prepare
|
|
3532
|
-
};
|
|
3533
|
-
}
|
|
3534
|
-
function prepareFlipContainer(el, params) {
|
|
3535
|
-
const engine = createFlipEngine();
|
|
3536
|
-
return observeLayout(
|
|
3537
|
-
el,
|
|
3538
|
-
engine,
|
|
3539
|
-
{
|
|
3540
|
-
durationMs: effectDurationMs(params, 400),
|
|
3541
|
-
easing: params.text("ease"),
|
|
3542
|
-
scale: params.is("scale")
|
|
3543
|
-
},
|
|
3544
|
-
mutationWatcher(el)
|
|
3545
|
-
);
|
|
3546
|
-
}
|
|
3547
|
-
function prepareAutoHeight(el, params, ctx) {
|
|
3548
|
-
const node = el;
|
|
3549
|
-
ctx.style.set("overflow", "hidden");
|
|
3550
|
-
ctx.style.claim("height");
|
|
3551
|
-
const duration = effectDurationMs(params, 400);
|
|
3552
|
-
let animation = null;
|
|
3553
|
-
let previous = node.getBoundingClientRect().height;
|
|
3554
|
-
const observer = watchAttribute(node, params.text("attribute"), () => {
|
|
3555
|
-
animation?.cancel();
|
|
3556
|
-
const endpoints = heightEndpoints(node, previous);
|
|
3557
|
-
previous = endpoints.to;
|
|
3558
|
-
animation = animateHeight(node, endpoints, duration, params.text("ease"));
|
|
3559
|
-
});
|
|
3560
|
-
ctx.invalidate();
|
|
3561
|
-
return () => {
|
|
3562
|
-
observer();
|
|
3563
|
-
animation?.cancel();
|
|
3564
|
-
};
|
|
3565
|
-
}
|
|
3566
|
-
function heightEndpoints(node, previous) {
|
|
3567
|
-
node.style.removeProperty("height");
|
|
3568
|
-
const to = node.getBoundingClientRect().height;
|
|
3569
|
-
return { from: previous, to };
|
|
3570
|
-
}
|
|
3571
|
-
function animateHeight(node, endpoints, duration, easing) {
|
|
3572
|
-
const animate = node.animate;
|
|
3573
|
-
if (typeof animate !== "function") return null;
|
|
3574
|
-
return animate.call(
|
|
3575
|
-
node,
|
|
3576
|
-
[{ height: `${endpoints.from}px` }, { height: `${endpoints.to}px` }],
|
|
3577
|
-
{ duration, easing, fill: "none" }
|
|
3578
|
-
);
|
|
3579
|
-
}
|
|
3580
|
-
function prepareIndicator(el, params, ctx) {
|
|
3581
|
-
const node = el;
|
|
3582
|
-
const engine = createFlipEngine();
|
|
3583
|
-
const selector = params.text("follow");
|
|
3584
|
-
let currentShift = 0;
|
|
3585
|
-
const move = () => {
|
|
3586
|
-
if (!selector) return;
|
|
3587
|
-
const target = ctx.doc.querySelector(selector);
|
|
3588
|
-
if (!(target instanceof Element)) return;
|
|
3589
|
-
const before = engine.snapshot([node]);
|
|
3590
|
-
const box = target.getBoundingClientRect();
|
|
3591
|
-
const current = node.getBoundingClientRect();
|
|
3592
|
-
const shift = box.left - current.left + currentShift;
|
|
3593
|
-
currentShift = shift;
|
|
3594
|
-
ctx.style.set("width", `${box.width}px`);
|
|
3595
|
-
ctx.style.set("translate", `${shift}px 0`);
|
|
3596
|
-
engine.play(before, [node], {
|
|
3597
|
-
durationMs: effectDurationMs(params, 400),
|
|
3598
|
-
easing: params.text("ease"),
|
|
3599
|
-
scale: true
|
|
3600
|
-
});
|
|
3601
|
-
};
|
|
3602
|
-
move();
|
|
3603
|
-
return watchAttribute(ctx.doc.documentElement, params.text("attribute"), move);
|
|
3604
|
-
}
|
|
3605
|
-
function watchAttribute(root, attribute, onChange) {
|
|
3606
|
-
if (typeof MutationObserver === "undefined" || !attribute) return () => {
|
|
3607
|
-
};
|
|
3608
|
-
const observer = new MutationObserver(onChange);
|
|
3609
|
-
observer.observe(root, { subtree: true, attributes: true, attributeFilter: [attribute] });
|
|
3610
|
-
return () => observer.disconnect();
|
|
3611
|
-
}
|
|
3612
|
-
var LAYOUT_PRIMITIVES = [
|
|
3613
|
-
layoutPrimitive(
|
|
3614
|
-
"flip-container",
|
|
3615
|
-
["translate", "scale"],
|
|
3616
|
-
{
|
|
3617
|
-
scale: {
|
|
3618
|
-
type: "keyword",
|
|
3619
|
-
default: "false",
|
|
3620
|
-
cssProperty: "--kui-flip-scale",
|
|
3621
|
-
values: ["true", "false"]
|
|
3622
|
-
}
|
|
3623
|
-
},
|
|
3624
|
-
deferPrepare(prepareFlipContainer)
|
|
3625
|
-
),
|
|
3626
|
-
layoutPrimitive(
|
|
3627
|
-
"auto-height",
|
|
3628
|
-
["layout"],
|
|
3629
|
-
{ attribute: { type: "text", default: "data-open", cssProperty: "--kui-attribute" } },
|
|
3630
|
-
deferPrepare(prepareAutoHeight)
|
|
3631
|
-
),
|
|
3632
|
-
layoutPrimitive(
|
|
3633
|
-
"flip-indicator",
|
|
3634
|
-
["translate", "layout"],
|
|
3635
|
-
{
|
|
3636
|
-
follow: { type: "text", default: "", cssProperty: "--kui-follow" },
|
|
3637
|
-
attribute: { type: "text", default: "aria-selected", cssProperty: "--kui-attribute" }
|
|
3638
|
-
},
|
|
3639
|
-
deferPrepare(prepareIndicator)
|
|
3640
|
-
)
|
|
3641
|
-
];
|
|
3642
|
-
|
|
3643
|
-
// src/effects/layout/presets.ts
|
|
3644
|
-
var LAYOUT_PRESETS = [
|
|
3645
|
-
{ name: "flip-reorder", primitive: "flip-container" },
|
|
3646
|
-
{ name: "flip-filter", primitive: "flip-container" },
|
|
3647
|
-
{ name: "flip-sort", primitive: "flip-container" },
|
|
3648
|
-
{ name: "flip-shuffle", primitive: "flip-container" },
|
|
3649
|
-
// Cards changing aspect between layouts need their size interpolated, not just their position.
|
|
3650
|
-
{ name: "grid-to-list", primitive: "flip-container", params: { scale: "true" } },
|
|
3651
|
-
{ name: "masonry-reflow", primitive: "flip-container" },
|
|
3652
|
-
{ name: "expand-to-modal", primitive: "flip-container", params: { scale: "true", duration: "500ms" } },
|
|
3653
|
-
{ name: "accordion-height", primitive: "auto-height" },
|
|
3654
|
-
{ name: "tab-indicator-slide", primitive: "flip-indicator", params: { duration: "300ms" } }
|
|
3655
|
-
];
|
|
3656
|
-
|
|
3657
|
-
// src/effects/layout/index.ts
|
|
3658
|
-
function registerLayout(registry) {
|
|
3659
|
-
return registry.registerPrimitives(LAYOUT_PRIMITIVES).registerPresets(LAYOUT_PRESETS);
|
|
3660
|
-
}
|
|
3661
|
-
|
|
3662
|
-
// src/effects/navigation/index.ts
|
|
3663
|
-
var NAV_CSS_PRIMITIVES = [
|
|
3664
|
-
cssPrimitive("nav-reveal", [CHANNEL.opacity, CHANNEL.translate]),
|
|
3665
|
-
cssPrimitive("menu-fullscreen", [CHANNEL.clip, CHANNEL.opacity]),
|
|
3666
|
-
cssPrimitive("panel-reveal", [CHANNEL.opacity, CHANNEL.translate]),
|
|
3667
|
-
cssPrimitive("drawer-slide", [CHANNEL.translate], {
|
|
3668
|
-
defaultActivation: "click"
|
|
3669
|
-
})
|
|
3670
|
-
];
|
|
3671
|
-
var NAV_CSS_PRESETS = [
|
|
3672
|
-
{ name: "menu-stagger-open", primitive: "nav-reveal", keyframes: "kui-nav-reveal" },
|
|
3673
|
-
{ name: "menu-fullscreen", primitive: "menu-fullscreen", keyframes: "kui-menu-fullscreen" },
|
|
3674
|
-
{ name: "dropdown-open", primitive: "panel-reveal", keyframes: "kui-panel-reveal" },
|
|
3675
|
-
{
|
|
3676
|
-
name: "mega-menu-drop",
|
|
3677
|
-
primitive: "panel-reveal",
|
|
3678
|
-
keyframes: "kui-panel-reveal",
|
|
3679
|
-
params: { duration: "550ms" }
|
|
3680
|
-
},
|
|
3681
|
-
{
|
|
3682
|
-
name: "drawer-slide",
|
|
3683
|
-
primitive: "drawer-slide",
|
|
3684
|
-
keyframes: "kui-drawer-slide-right"
|
|
3685
|
-
}
|
|
3686
|
-
];
|
|
3687
|
-
function navPrimitive(id, channels, parameters, prepare) {
|
|
3688
|
-
return {
|
|
3689
|
-
id,
|
|
3690
|
-
renderer: "javascript",
|
|
3691
|
-
channels,
|
|
3692
|
-
parameters,
|
|
3693
|
-
supportedTimelines: ["time"],
|
|
3694
|
-
supportedActivations: ["load"],
|
|
3695
|
-
defaultActivation: "load",
|
|
3696
|
-
perfClass: "compositor",
|
|
3697
|
-
// A scroll-position reaction, like the scroll-mechanics category: shortening its "duration"
|
|
3698
|
-
// is meaningless because the position, not a clock, drives it.
|
|
3699
|
-
reducedMotion: "disable",
|
|
3700
|
-
prepare
|
|
3701
|
-
};
|
|
3702
|
-
}
|
|
3703
|
-
function subscribeScrollTop(el, ctx, onScrollTop) {
|
|
3704
|
-
return ctx.scheduler.subscribe(ctx.rootFor(el), (frame) => onScrollTop(frame.metrics.scrollTop));
|
|
3705
|
-
}
|
|
3706
|
-
function prepareHeaderShrink(el, params, ctx) {
|
|
3707
|
-
const offset = params.num("offset", 120);
|
|
3708
|
-
const state = createAttributeLedger(el);
|
|
3709
|
-
const unsubscribe = subscribeScrollTop(el, ctx, (top) => {
|
|
3710
|
-
const progress = offset > 0 ? Math.min(1, Math.max(0, top / offset)) : 1;
|
|
3711
|
-
ctx.style.set("--kui-shrink", progress.toFixed(4));
|
|
3712
|
-
state.set("data-kui-shrunk", String(progress >= 1));
|
|
3713
|
-
});
|
|
3714
|
-
return () => {
|
|
3715
|
-
unsubscribe();
|
|
3716
|
-
state.restore();
|
|
3717
|
-
};
|
|
3718
|
-
}
|
|
3719
|
-
function prepareHeaderHide(el, params, ctx) {
|
|
3720
|
-
const minDelta = params.num("offset", 8);
|
|
3721
|
-
let last = 0;
|
|
3722
|
-
const state = createAttributeLedger(el);
|
|
3723
|
-
const unsubscribe = subscribeScrollTop(el, ctx, (top) => {
|
|
3724
|
-
const delta = top - last;
|
|
3725
|
-
if (Math.abs(delta) < minDelta) return;
|
|
3726
|
-
state.set("data-kui-hidden", String(delta > 0 && top > minDelta));
|
|
3727
|
-
last = top;
|
|
3728
|
-
});
|
|
3729
|
-
return () => {
|
|
3730
|
-
unsubscribe();
|
|
3731
|
-
state.restore();
|
|
3732
|
-
};
|
|
3733
|
-
}
|
|
3734
|
-
function prepareBackToTop(el, params, ctx) {
|
|
3735
|
-
const offset = params.num("offset", 400);
|
|
3736
|
-
const state = createAttributeLedger(el);
|
|
3737
|
-
const unsubscribe = subscribeScrollTop(el, ctx, (top) => {
|
|
3738
|
-
state.set("data-kui-visible", String(top > offset));
|
|
3739
|
-
});
|
|
3740
|
-
return () => {
|
|
3741
|
-
unsubscribe();
|
|
3742
|
-
state.restore();
|
|
3743
|
-
};
|
|
3744
|
-
}
|
|
3745
|
-
var NAV_JS_PRIMITIVES = [
|
|
3746
|
-
navPrimitive(
|
|
3747
|
-
"header-shrink",
|
|
3748
|
-
["layout"],
|
|
3749
|
-
{ offset: { type: "number", default: "120", cssProperty: "--kui-offset" } },
|
|
3750
|
-
deferPrepare(prepareHeaderShrink)
|
|
3751
|
-
),
|
|
3752
|
-
navPrimitive(
|
|
3753
|
-
"header-hide-on-scroll",
|
|
3754
|
-
[CHANNEL.translate],
|
|
3755
|
-
{ offset: { type: "number", default: "8", cssProperty: "--kui-offset" } },
|
|
3756
|
-
deferPrepare(prepareHeaderHide)
|
|
3757
|
-
),
|
|
3758
|
-
navPrimitive(
|
|
3759
|
-
"back-to-top-fade",
|
|
3760
|
-
[CHANNEL.opacity, CHANNEL.translate],
|
|
3761
|
-
{ offset: { type: "number", default: "400", cssProperty: "--kui-offset" } },
|
|
3762
|
-
deferPrepare(prepareBackToTop)
|
|
3763
|
-
)
|
|
3764
|
-
];
|
|
3765
|
-
var NAV_JS_PRESETS = [
|
|
3766
|
-
{ name: "header-shrink", primitive: "header-shrink" },
|
|
3767
|
-
{ name: "header-hide-on-scroll", primitive: "header-hide-on-scroll" },
|
|
3768
|
-
{ name: "back-to-top-fade", primitive: "back-to-top-fade" }
|
|
3769
|
-
];
|
|
3770
|
-
var NAVIGATION_PRIMITIVES = [...NAV_CSS_PRIMITIVES, ...NAV_JS_PRIMITIVES];
|
|
3771
|
-
var NAVIGATION_PRESETS = [...NAV_CSS_PRESETS, ...NAV_JS_PRESETS];
|
|
3772
|
-
function registerNavigation(registry) {
|
|
3773
|
-
return registry.registerPrimitives(NAVIGATION_PRIMITIVES).registerPresets(NAVIGATION_PRESETS);
|
|
3774
|
-
}
|
|
3775
|
-
|
|
3776
|
-
// src/effects/scroll-mechanics/tracker.ts
|
|
3777
|
-
var domGeometry = (el) => {
|
|
3778
|
-
const rect = el.getBoundingClientRect();
|
|
3779
|
-
return { top: rect.top, height: rect.height };
|
|
3780
|
-
};
|
|
3781
|
-
var domPosition = (el) => {
|
|
3782
|
-
const view = el.ownerDocument?.defaultView;
|
|
3783
|
-
return view ? view.getComputedStyle(el).position : "static";
|
|
3784
|
-
};
|
|
3785
|
-
var domOffsetTop = (el) => {
|
|
3786
|
-
const view = el.ownerDocument?.defaultView;
|
|
3787
|
-
if (!view) return 0;
|
|
3788
|
-
const parsed = Number.parseFloat(view.getComputedStyle(el).top);
|
|
3789
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
3790
|
-
};
|
|
3791
|
-
function geometrySource(el, positionOf) {
|
|
3792
|
-
let outermostSticky = null;
|
|
3793
|
-
for (let node = el; node; node = node.parentElement) {
|
|
3794
|
-
if (positionOf(node) === "sticky") outermostSticky = node;
|
|
3795
|
-
}
|
|
3796
|
-
return outermostSticky?.parentElement ?? el;
|
|
3797
|
-
}
|
|
3798
|
-
function sourceTop(el, box, measure, positionOf) {
|
|
3799
|
-
const source = geometrySource(el, positionOf);
|
|
3800
|
-
return source === el ? box.top : measure(source).top;
|
|
3801
|
-
}
|
|
3802
|
-
function trackProgress(el, ctx, options, onProgress) {
|
|
3803
|
-
const measure = options.measure ?? domGeometry;
|
|
3804
|
-
const positionOf = options.positionOf ?? domPosition;
|
|
3805
|
-
const offsetOf = options.offsetOf ?? domOffsetTop;
|
|
3806
|
-
let scrollTop = 0;
|
|
3807
|
-
let scrollportTop = 0;
|
|
3808
|
-
const geometry2 = createMeasureCache(() => {
|
|
3809
|
-
const box = measure(el);
|
|
3810
|
-
const top = options.contentAnchor ? measure(options.contentAnchor).top - box.height : sourceTop(el, box, measure, positionOf);
|
|
3811
|
-
const stickyOffset = options.stickyEl ? offsetOf(options.stickyEl) : 0;
|
|
3812
|
-
return { contentTop: top - stickyOffset - scrollportTop + scrollTop, height: box.height };
|
|
3813
|
-
});
|
|
3814
|
-
return ctx.scheduler.subscribe(ctx.rootFor(el), (frame) => {
|
|
3815
|
-
scrollTop = frame.metrics.scrollTop;
|
|
3816
|
-
scrollportTop = frame.metrics.viewportTop;
|
|
3817
|
-
const box = geometry2.read(frame.epoch);
|
|
3818
|
-
const span = resolveDistance(options.distance, { top: 0, height: box.height }, frame);
|
|
3819
|
-
onProgress(progressFrom(box.contentTop - scrollTop, span), frame);
|
|
3820
|
-
});
|
|
3821
|
-
}
|
|
3822
|
-
function resolveDistance(distance2, box, frame) {
|
|
3823
|
-
if (!distance2) return box.height;
|
|
3824
|
-
const basis = {
|
|
3825
|
-
viewportWidth: frame.metrics.viewportWidth,
|
|
3826
|
-
viewportHeight: frame.metrics.viewportHeight,
|
|
3827
|
-
percentBasis: box.height,
|
|
3828
|
-
fontSize: 16,
|
|
3829
|
-
rootFontSize: 16
|
|
3830
|
-
};
|
|
3831
|
-
return toPixels(distance2, basis, box.height);
|
|
3832
|
-
}
|
|
3833
|
-
function progressFrom(top, span) {
|
|
3834
|
-
if (span <= 0) return 0;
|
|
3835
|
-
return clamp01(-top / span);
|
|
3836
|
-
}
|
|
3837
|
-
|
|
3838
|
-
// src/effects/scroll-mechanics/scroll-spy.ts
|
|
3839
|
-
function prepareScrollSpy(el, params, ctx) {
|
|
3840
|
-
const sectionsAuthored = params.text("sections");
|
|
3841
|
-
if (sectionsAuthored) {
|
|
3842
|
-
const sectionsSelector = resolveTarget(sectionsAuthored, ctx, "scroll-spy sections");
|
|
3843
|
-
return prepareScrollSpyContainer(el, params, ctx, sectionsSelector);
|
|
3844
|
-
}
|
|
3845
|
-
return prepareScrollSpySingle(el, params, ctx);
|
|
3846
|
-
}
|
|
3847
|
-
function prepareScrollSpySingle(el, params, ctx) {
|
|
3848
|
-
if (params.text("offset-top", "0px") !== "0px") {
|
|
3849
|
-
ctx.warn('scroll-spy "offset-top" has no effect without sections: and is ignored here');
|
|
3850
|
-
}
|
|
3851
|
-
const selector = resolveTarget(params.text("target"), ctx, "scroll-spy");
|
|
3852
|
-
const links = /* @__PURE__ */ new Map();
|
|
3853
|
-
const self = createAttributeLedger(el);
|
|
3854
|
-
let last;
|
|
3855
|
-
const untrack = trackProgress(el, ctx, { distance: params.text("distance") }, (progress) => {
|
|
3856
|
-
const active = progress > 0 && progress < 1;
|
|
3857
|
-
if (active === last) return;
|
|
3858
|
-
last = active;
|
|
3859
|
-
self.set("data-kui-active", String(active));
|
|
3860
|
-
if (selector) markLinks(ctx.doc, selector, active, links);
|
|
3861
|
-
});
|
|
3862
|
-
return continuousSetup(() => {
|
|
3863
|
-
untrack();
|
|
3864
|
-
self.restore();
|
|
3865
|
-
for (const ledger of links.values()) ledger.restore();
|
|
3866
|
-
});
|
|
3867
|
-
}
|
|
3868
|
-
function markLinks(doc, selector, active, links) {
|
|
3869
|
-
for (const link of doc.querySelectorAll(selector)) {
|
|
3870
|
-
let ledger = links.get(link);
|
|
3871
|
-
if (!ledger) {
|
|
3872
|
-
ledger = createAttributeLedger(link);
|
|
3873
|
-
links.set(link, ledger);
|
|
3874
|
-
}
|
|
3875
|
-
ledger.set("data-kui-active", String(active));
|
|
3876
|
-
}
|
|
3877
|
-
}
|
|
3878
|
-
function hrefHash(link) {
|
|
3879
|
-
const href = link.getAttribute("href") ?? "";
|
|
3880
|
-
const at = href.indexOf("#");
|
|
3881
|
-
return at === -1 ? "" : href.slice(at);
|
|
3882
|
-
}
|
|
3883
|
-
function pairSectionsWithLinks(sections, links, ctx, sectionsSelector) {
|
|
3884
|
-
const linksByHash = /* @__PURE__ */ new Map();
|
|
3885
|
-
for (const link of links) {
|
|
3886
|
-
const hash = hrefHash(link);
|
|
3887
|
-
if (hash) linksByHash.set(hash, link);
|
|
3888
|
-
}
|
|
3889
|
-
const claimed = /* @__PURE__ */ new Set();
|
|
3890
|
-
const pairs = sections.map((section) => {
|
|
3891
|
-
if (!section.id) {
|
|
3892
|
-
ctx.warn(
|
|
3893
|
-
`scroll-spy: a section matched by sections:"${sectionsSelector}" has no id and cannot be paired with a link`
|
|
3894
|
-
);
|
|
3895
|
-
return { section, link: null };
|
|
3896
|
-
}
|
|
3897
|
-
const hash = `#${section.id}`;
|
|
3898
|
-
const link = linksByHash.get(hash) ?? null;
|
|
3899
|
-
if (link) claimed.add(hash);
|
|
3900
|
-
return { section, link };
|
|
3901
|
-
});
|
|
3902
|
-
for (const [hash, link] of linksByHash) {
|
|
3903
|
-
if (!claimed.has(hash)) {
|
|
3904
|
-
ctx.warn(`scroll-spy: link "${link.getAttribute("href")}" matches no section id in sections:"${sectionsSelector}"`);
|
|
3905
|
-
}
|
|
3906
|
-
}
|
|
3907
|
-
return pairs;
|
|
3908
|
-
}
|
|
3909
|
-
function offsetTopPixels(authored, frame) {
|
|
3910
|
-
return toPixels(
|
|
3911
|
-
authored,
|
|
3912
|
-
{
|
|
3913
|
-
viewportWidth: frame.metrics.viewportWidth,
|
|
3914
|
-
viewportHeight: frame.metrics.viewportHeight,
|
|
3915
|
-
percentBasis: 0,
|
|
3916
|
-
fontSize: 16,
|
|
3917
|
-
rootFontSize: 16
|
|
3918
|
-
},
|
|
3919
|
-
0
|
|
3920
|
-
);
|
|
3921
|
-
}
|
|
3922
|
-
function highestReachedIndex(tops, scrollTop, line) {
|
|
3923
|
-
let index = -1;
|
|
3924
|
-
for (let i = 0; i < tops.length; i++) {
|
|
3925
|
-
if (tops[i] - scrollTop - line <= 0) index = i;
|
|
3926
|
-
}
|
|
3927
|
-
return index;
|
|
3928
|
-
}
|
|
3929
|
-
function prepareScrollSpyContainer(el, params, ctx, sectionsSelector) {
|
|
3930
|
-
if (params.text("distance", "100vh") !== "100vh") {
|
|
3931
|
-
ctx.warn('scroll-spy "distance" has no effect with sections: \u2014 each section measures its own height');
|
|
3932
|
-
}
|
|
3933
|
-
const linksSelector = resolveTarget(params.text("target"), ctx, "scroll-spy target");
|
|
3934
|
-
const sections = sectionsSelector ? [...el.querySelectorAll(sectionsSelector)] : [];
|
|
3935
|
-
if (sectionsSelector && sections.length === 0) {
|
|
3936
|
-
ctx.warn(`scroll-spy sections:"${sectionsSelector}" matched nothing inside this element`);
|
|
3937
|
-
}
|
|
3938
|
-
const links = linksSelector ? [...el.querySelectorAll(linksSelector)] : [];
|
|
3939
|
-
const pairs = pairSectionsWithLinks(sections, links, ctx, sectionsSelector);
|
|
3940
|
-
const sectionLedgers = pairs.map((pair) => createAttributeLedger(pair.section));
|
|
3941
|
-
const linkLedgers = /* @__PURE__ */ new Map();
|
|
3942
|
-
for (const { link } of pairs) {
|
|
3943
|
-
if (link && !linkLedgers.has(link)) linkLedgers.set(link, createAttributeLedger(link));
|
|
3944
|
-
}
|
|
3945
|
-
const offsetAuthored = params.text("offset-top", "0px");
|
|
3946
|
-
let scrollTop = 0;
|
|
3947
|
-
let scrollportTop = 0;
|
|
3948
|
-
const contentTops = createMeasureCache(
|
|
3949
|
-
() => pairs.map(({ section }) => domGeometry(section).top - scrollportTop + scrollTop)
|
|
3950
|
-
);
|
|
3951
|
-
let active = -1;
|
|
3952
|
-
function setActive(index, value) {
|
|
3953
|
-
const pair = pairs[index];
|
|
3954
|
-
sectionLedgers[index].set("data-kui-active", String(value));
|
|
3955
|
-
if (pair.link) linkLedgers.get(pair.link).set("data-kui-active", String(value));
|
|
3956
|
-
}
|
|
3957
|
-
const untrack = ctx.scheduler.subscribe(ctx.rootFor(el), (frame) => {
|
|
3958
|
-
scrollTop = frame.metrics.scrollTop;
|
|
3959
|
-
scrollportTop = frame.metrics.viewportTop;
|
|
3960
|
-
const tops = contentTops.read(frame.epoch);
|
|
3961
|
-
const line = offsetTopPixels(offsetAuthored, frame);
|
|
3962
|
-
const next = highestReachedIndex(tops, scrollTop, line);
|
|
3963
|
-
if (next === active) return;
|
|
3964
|
-
if (active !== -1) setActive(active, false);
|
|
3965
|
-
if (next !== -1) setActive(next, true);
|
|
3966
|
-
active = next;
|
|
3967
|
-
});
|
|
3968
|
-
return continuousSetup(() => {
|
|
3969
|
-
untrack();
|
|
3970
|
-
for (const ledger of sectionLedgers) ledger.restore();
|
|
3971
|
-
for (const ledger of linkLedgers.values()) ledger.restore();
|
|
3972
|
-
});
|
|
3973
|
-
}
|
|
3974
|
-
|
|
3975
|
-
// src/effects/scroll-mechanics/primitives.ts
|
|
3976
|
-
var PROGRESS_VAR = "--kui-progress";
|
|
3977
|
-
var distanceParam = {
|
|
3978
|
-
distance: { type: "length", default: "100vh", cssProperty: "--kui-distance" }
|
|
3979
|
-
};
|
|
3980
|
-
var stickyParams = {
|
|
3981
|
-
"offset-top": {
|
|
3982
|
-
type: "length",
|
|
3983
|
-
default: "var(--kui-pin-offset, 0px)",
|
|
3984
|
-
cssProperty: "--kui-offset-top"
|
|
3985
|
-
},
|
|
3986
|
-
spacer: {
|
|
3987
|
-
type: "keyword",
|
|
3988
|
-
default: "false",
|
|
3989
|
-
cssProperty: "--kui-spacer",
|
|
3990
|
-
values: ["true", "false"]
|
|
3991
|
-
}
|
|
3992
|
-
};
|
|
3993
|
-
function installSticky(node, params, ctx) {
|
|
3994
|
-
ctx.style.set("position", "sticky");
|
|
3995
|
-
ctx.style.set("top", params.text("offset-top", "var(--kui-pin-offset, 0px)"));
|
|
3996
|
-
const inserted = params.is("spacer") ? insertSpacer(node, params.text("distance"), ctx) : null;
|
|
3997
|
-
return { spacer: inserted?.spacer ?? null, dispose: () => inserted?.remove() };
|
|
3998
|
-
}
|
|
3999
|
-
function scrollPrimitive(spec) {
|
|
4000
|
-
const { id, channels, parameters, prepare, perfClass = "compositor" } = spec;
|
|
4001
|
-
return {
|
|
4002
|
-
id,
|
|
4003
|
-
renderer: "javascript",
|
|
4004
|
-
channels,
|
|
4005
|
-
parameters,
|
|
4006
|
-
// Accepted, never read: these primitives read scroll position themselves and are never driven
|
|
4007
|
-
// by an `animation-timeline`. The list exists so that composing the driver with the effects it
|
|
4008
|
-
// drives — `data-kui="pin-section distance:200vh, parallax-rotate ... "` plus `timeline:pin` —
|
|
4009
|
-
// survives `compile.ts`'s `intersect`. Without it the intersection empties, `style-plan.ts`
|
|
4010
|
-
// refuses the timeline, and the scrub silently degrades to a one-shot. See `TIMELINE_AGNOSTIC`
|
|
4011
|
-
// (`effects/shared.ts`) for why the name says abstention rather than support.
|
|
4012
|
-
supportedTimelines: TIMELINE_AGNOSTIC,
|
|
4013
|
-
supportedActivations: ["manual", "load", "enter"],
|
|
4014
|
-
defaultActivation: "load",
|
|
4015
|
-
perfClass,
|
|
4016
|
-
reducedMotion: "disable",
|
|
4017
|
-
prepare
|
|
4018
|
-
};
|
|
4019
|
-
}
|
|
4020
|
-
function writeProgress(ctx, progress) {
|
|
4021
|
-
ctx.style.set(PROGRESS_VAR, progress.toFixed(4));
|
|
4022
|
-
}
|
|
4023
|
-
function preparePin(el, params, ctx) {
|
|
4024
|
-
const node = el;
|
|
4025
|
-
const { dispose: unstick } = installSticky(node, params, ctx);
|
|
4026
|
-
const tracked = node.parentElement ?? el;
|
|
4027
|
-
const untrack = trackProgress(tracked, ctx, { distance: params.text("distance"), stickyEl: node }, (progress) => {
|
|
4028
|
-
writeProgress(ctx, progress);
|
|
4029
|
-
el.setAttribute("data-kui-pinned", progress > 0 && progress < 1 ? "true" : "false");
|
|
4030
|
-
});
|
|
4031
|
-
return continuousSetup(() => {
|
|
4032
|
-
untrack();
|
|
4033
|
-
unstick();
|
|
4034
|
-
el.removeAttribute("data-kui-pinned");
|
|
4035
|
-
});
|
|
4036
|
-
}
|
|
4037
|
-
function insertSpacer(node, distance2, ctx) {
|
|
4038
|
-
const spacer = ctx.doc.createElement("div");
|
|
4039
|
-
spacer.setAttribute("data-kui-spacer", "");
|
|
4040
|
-
spacer.setAttribute("aria-hidden", "true");
|
|
4041
|
-
spacer.style.height = distance2;
|
|
4042
|
-
spacer.style.pointerEvents = "none";
|
|
4043
|
-
node.after(spacer);
|
|
4044
|
-
ctx.invalidate();
|
|
4045
|
-
return {
|
|
4046
|
-
spacer,
|
|
4047
|
-
remove: () => {
|
|
4048
|
-
spacer.remove();
|
|
4049
|
-
ctx.invalidate();
|
|
4050
|
-
}
|
|
4051
|
-
};
|
|
4052
|
-
}
|
|
4053
|
-
function prepareProgress(el, params, ctx) {
|
|
4054
|
-
const steps = Math.max(0, Math.round(params.num("steps", 0)));
|
|
4055
|
-
const selector = resolveTarget(params.text("target"), ctx, "scrollytelling-step");
|
|
4056
|
-
const marker = createStepMarker(() => ctx.doc.querySelectorAll(selector));
|
|
4057
|
-
const self = createAttributeLedger(el);
|
|
4058
|
-
let lastIndex;
|
|
4059
|
-
const untrack = trackProgress(el, ctx, { distance: params.text("distance") }, (progress) => {
|
|
4060
|
-
writeProgress(ctx, progress);
|
|
4061
|
-
if (steps === 0) return;
|
|
4062
|
-
const index = Math.min(steps - 1, Math.floor(progress * steps));
|
|
4063
|
-
if (index === lastIndex) return;
|
|
4064
|
-
lastIndex = index;
|
|
4065
|
-
self.set("data-kui-step", String(index));
|
|
4066
|
-
ctx.style.set("--kui-step", String(index));
|
|
4067
|
-
if (selector) marker.mark(index);
|
|
4068
|
-
});
|
|
4069
|
-
return continuousSetup(() => {
|
|
4070
|
-
untrack();
|
|
4071
|
-
self.restore();
|
|
4072
|
-
marker.restore();
|
|
4073
|
-
});
|
|
4074
|
-
}
|
|
4075
|
-
function prepareHorizontal(el, params, ctx) {
|
|
4076
|
-
const selector = resolveTarget(params.text("target"), ctx, "horizontal-scroll");
|
|
4077
|
-
if (!selector) return prepareBareTrack(el, params, ctx);
|
|
4078
|
-
const track = el.querySelector(selector);
|
|
4079
|
-
if (!track) {
|
|
4080
|
-
ctx.warn(`horizontal-scroll target "${selector}" matched nothing inside this element`);
|
|
4081
|
-
return () => {
|
|
4082
|
-
};
|
|
4083
|
-
}
|
|
4084
|
-
return prepareManagedTrack(el, track, params, ctx);
|
|
4085
|
-
}
|
|
4086
|
-
function prepareBareTrack(node, params, ctx) {
|
|
4087
|
-
const authored = params.text("travel", "auto");
|
|
4088
|
-
const travel = createMeasureCache(() => trackTravel(node, authored, node.ownerDocument));
|
|
4089
|
-
return continuousSetup(
|
|
4090
|
-
trackProgress(node, ctx, { distance: params.text("distance") }, (progress, frame) => {
|
|
4091
|
-
ctx.style.set("translate", `${-progress * travel.read(frame.epoch)}px 0`);
|
|
4092
|
-
writeProgress(ctx, progress);
|
|
4093
|
-
})
|
|
4094
|
-
);
|
|
4095
|
-
}
|
|
4096
|
-
function prepareManagedTrack(host, track, params, ctx) {
|
|
4097
|
-
const offsetTop = params.text("offset-top", "var(--kui-pin-offset, 0px)");
|
|
4098
|
-
ctx.style.set("position", "sticky");
|
|
4099
|
-
ctx.style.set("top", offsetTop);
|
|
4100
|
-
ctx.style.set("height", `calc(100vh - ${offsetTop})`);
|
|
4101
|
-
ctx.style.set("overflow", "hidden");
|
|
4102
|
-
ctx.style.set("display", "grid");
|
|
4103
|
-
ctx.style.set("align-content", "center");
|
|
4104
|
-
const { remove: removeSpacer } = insertSpacer(host, params.text("distance"), ctx);
|
|
4105
|
-
const rail = createStyleLedger(track);
|
|
4106
|
-
rail.set("display", "flex");
|
|
4107
|
-
rail.set("width", "max-content");
|
|
4108
|
-
const authored = params.text("travel", "auto");
|
|
4109
|
-
const travel = createMeasureCache(() => trackTravel(track, authored, track.ownerDocument));
|
|
4110
|
-
const tracked = host.parentElement ?? host;
|
|
4111
|
-
const untrack = trackProgress(tracked, ctx, { distance: params.text("distance"), stickyEl: host }, (progress, frame) => {
|
|
4112
|
-
rail.set("translate", `${-progress * travel.read(frame.epoch)}px 0`);
|
|
4113
|
-
writeProgress(ctx, progress);
|
|
4114
|
-
});
|
|
4115
|
-
return continuousSetup(() => {
|
|
4116
|
-
untrack();
|
|
4117
|
-
rail.restore();
|
|
4118
|
-
removeSpacer();
|
|
4119
|
-
});
|
|
4120
|
-
}
|
|
4121
|
-
function trackTravel(node, authored, doc) {
|
|
4122
|
-
if (authored && authored !== "auto") return toPixels(authored, ABSOLUTE_BASIS, 0);
|
|
4123
|
-
const selfOverflow = node.scrollWidth - node.clientWidth;
|
|
4124
|
-
if (selfOverflow > 0) return selfOverflow;
|
|
4125
|
-
const viewportWidth = node.parentElement?.clientWidth || doc.documentElement.clientWidth;
|
|
4126
|
-
return Math.max(0, node.scrollWidth - viewportWidth);
|
|
4127
|
-
}
|
|
4128
|
-
function prepareMediaScrub(el, params, ctx) {
|
|
4129
|
-
const managed = params.is("spacer") ? installSticky(el, params, ctx) : null;
|
|
4130
|
-
const contentAnchor = managed?.spacer ?? void 0;
|
|
4131
|
-
const stickyEl = managed ? el : void 0;
|
|
4132
|
-
const selector = resolveTarget(params.text("target"), ctx, "media-scrub");
|
|
4133
|
-
const scrub = selector ? prepareTargetScrub(el, params, ctx, { selector, contentAnchor, stickyEl }) : prepareSrcScrub(el, params, ctx, { contentAnchor, stickyEl });
|
|
4134
|
-
return continuousSetup(() => {
|
|
4135
|
-
scrub();
|
|
4136
|
-
managed?.dispose();
|
|
4137
|
-
});
|
|
4138
|
-
}
|
|
4139
|
-
function prepareTargetScrub(el, params, ctx, authored) {
|
|
4140
|
-
const { selector, contentAnchor, stickyEl } = authored;
|
|
4141
|
-
const marker = createStepMarker(() => ctx.doc.querySelectorAll(selector));
|
|
4142
|
-
const frames = Math.max(1, ctx.doc.querySelectorAll(selector).length);
|
|
4143
|
-
let lastIndex;
|
|
4144
|
-
const untrack = trackProgress(el, ctx, { distance: params.text("distance"), contentAnchor, stickyEl }, (progress) => {
|
|
4145
|
-
writeProgress(ctx, progress);
|
|
4146
|
-
const index = Math.min(frames - 1, Math.floor(progress * frames));
|
|
4147
|
-
if (index === lastIndex) return;
|
|
4148
|
-
lastIndex = index;
|
|
4149
|
-
marker.mark(index);
|
|
4150
|
-
});
|
|
4151
|
-
marker.mark(0);
|
|
4152
|
-
return () => {
|
|
4153
|
-
untrack();
|
|
4154
|
-
marker.restore();
|
|
4155
|
-
};
|
|
4156
|
-
}
|
|
4157
|
-
function prepareSrcScrub(el, params, ctx, anchors) {
|
|
4158
|
-
const { contentAnchor, stickyEl } = anchors;
|
|
4159
|
-
const frames = Math.max(1, Math.round(params.num("frames", 1)));
|
|
4160
|
-
const pattern = mediaSrcPattern(params.text("src"), ctx);
|
|
4161
|
-
const media = el;
|
|
4162
|
-
let lastIndex = -1;
|
|
4163
|
-
const untrack = trackProgress(el, ctx, { distance: params.text("distance"), contentAnchor, stickyEl }, (progress) => {
|
|
4164
|
-
writeProgress(ctx, progress);
|
|
4165
|
-
const index = Math.min(frames - 1, Math.floor(progress * frames));
|
|
4166
|
-
if (index === lastIndex) return;
|
|
4167
|
-
lastIndex = index;
|
|
4168
|
-
applyFrame(media, { index, frames, progress, pattern });
|
|
4169
|
-
});
|
|
4170
|
-
return untrack;
|
|
4171
|
-
}
|
|
4172
|
-
function mediaSrcPattern(pattern, ctx) {
|
|
4173
|
-
if (!pattern || isSameOriginPath(pattern)) return pattern;
|
|
4174
|
-
ctx.warn(`media-scrub "src" must be a same-origin path, got "${pattern}" \u2014 ignoring`);
|
|
4175
|
-
return "";
|
|
4176
|
-
}
|
|
4177
|
-
function applyFrame(media, write) {
|
|
4178
|
-
const { index, frames, progress, pattern } = write;
|
|
4179
|
-
if (media.tagName === "VIDEO") {
|
|
4180
|
-
const duration = Number.isFinite(media.duration) ? media.duration : 0;
|
|
4181
|
-
if (duration > 0) media.currentTime = duration * progress;
|
|
4182
|
-
return;
|
|
4183
|
-
}
|
|
4184
|
-
if (media.tagName !== "IMG") return;
|
|
4185
|
-
if (pattern) media.src = pattern.replace("{i}", String(index).padStart(String(frames).length, "0"));
|
|
4186
|
-
}
|
|
4187
|
-
function prepareSmoothScroll(el, params, ctx) {
|
|
4188
|
-
ctx.style.set("scroll-behavior", params.text("behavior", "smooth"));
|
|
4189
|
-
return () => {
|
|
4190
|
-
};
|
|
4191
|
-
}
|
|
4192
|
-
function prepareSnap(el, params, ctx) {
|
|
4193
|
-
const axis = params.is("axis", "x") ? "x" : "y";
|
|
4194
|
-
ctx.style.set("scroll-snap-type", `${axis} ${params.text("strictness", "mandatory")}`);
|
|
4195
|
-
const selector = resolveTarget(params.text("target"), ctx, "scroll-snap");
|
|
4196
|
-
const items = selector ? [...el.querySelectorAll(selector)] : [...el.children];
|
|
4197
|
-
if (selector && items.length === 0) {
|
|
4198
|
-
ctx.warn(`scroll-snap target "${selector}" matched nothing inside this element`);
|
|
4199
|
-
}
|
|
4200
|
-
if (selector) installSnapContainer(axis, ctx);
|
|
4201
|
-
const childLedgers = items.map((child) => createStyleLedger(child));
|
|
4202
|
-
for (const ledger of childLedgers) ledger.set("scroll-snap-align", params.text("align", "start"));
|
|
4203
|
-
return () => {
|
|
4204
|
-
for (const ledger of childLedgers) ledger.restore();
|
|
4205
|
-
};
|
|
4206
|
-
}
|
|
4207
|
-
function installSnapContainer(axis, ctx) {
|
|
4208
|
-
ctx.style.set(axis === "x" ? "overflow-x" : "overflow-y", "auto");
|
|
4209
|
-
if (axis === "x") ctx.style.set("display", "flex");
|
|
4210
|
-
}
|
|
4211
|
-
var SCROLL_PRIMITIVES = [
|
|
4212
|
-
scrollPrimitive({
|
|
4213
|
-
id: "pin",
|
|
4214
|
-
channels: ["layout", "progress"],
|
|
4215
|
-
parameters: {
|
|
4216
|
-
...distanceParam,
|
|
4217
|
-
...stickyParams
|
|
4218
|
-
},
|
|
4219
|
-
prepare: deferPrepare(preparePin),
|
|
4220
|
-
perfClass: "layout"
|
|
4221
|
-
}),
|
|
4222
|
-
scrollPrimitive({
|
|
4223
|
-
id: "scroll-progress",
|
|
4224
|
-
channels: ["progress"],
|
|
4225
|
-
parameters: {
|
|
4226
|
-
...distanceParam,
|
|
4227
|
-
steps: { type: "number", default: "0", cssProperty: "--kui-steps" },
|
|
4228
|
-
// Same name, same shape and the same validation as scroll-spy's: one `target:` convention
|
|
4229
|
-
// across the library rather than a second word for "the elements this effect marks".
|
|
4230
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" }
|
|
4231
|
-
},
|
|
4232
|
-
prepare: deferPrepare(prepareProgress)
|
|
4233
|
-
}),
|
|
4234
|
-
scrollPrimitive({
|
|
4235
|
-
id: "horizontal-track",
|
|
4236
|
-
channels: ["translate", "progress"],
|
|
4237
|
-
parameters: {
|
|
4238
|
-
...distanceParam,
|
|
4239
|
-
travel: { type: "text", default: "auto", cssProperty: "--kui-travel" },
|
|
4240
|
-
// Same name, same shape and the same validation as scroll-spy's and media-scrub's: one
|
|
4241
|
-
// `target:` convention across the library. Naming the row that moves is also what opts this
|
|
4242
|
-
// primitive into owning the stage, the sticky window and the row's layout itself.
|
|
4243
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
4244
|
-
"offset-top": {
|
|
4245
|
-
type: "length",
|
|
4246
|
-
default: "var(--kui-pin-offset, 0px)",
|
|
4247
|
-
cssProperty: "--kui-offset-top"
|
|
4248
|
-
}
|
|
4249
|
-
},
|
|
4250
|
-
prepare: deferPrepare(prepareHorizontal)
|
|
4251
|
-
}),
|
|
4252
|
-
scrollPrimitive({
|
|
4253
|
-
id: "media-scrub",
|
|
4254
|
-
channels: ["media", "progress"],
|
|
4255
|
-
parameters: {
|
|
4256
|
-
...distanceParam,
|
|
4257
|
-
// A scrub is a hold, so it needs the same two knobs a pin does. Declaring them here is what
|
|
4258
|
-
// lets `sequence-scrub` become one attribute with no wrapper at all.
|
|
4259
|
-
...stickyParams,
|
|
4260
|
-
frames: { type: "number", default: "1", cssProperty: "--kui-frames" },
|
|
4261
|
-
src: { type: "text", default: "", cssProperty: "--kui-src" },
|
|
4262
|
-
// The preferred form. `frames:`/`src:` remain for sequences too long to author as tags.
|
|
4263
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" }
|
|
4264
|
-
},
|
|
4265
|
-
prepare: deferPrepare(prepareMediaScrub),
|
|
4266
|
-
perfClass: "paint"
|
|
4267
|
-
}),
|
|
4268
|
-
scrollPrimitive({
|
|
4269
|
-
id: "scroll-spy",
|
|
4270
|
-
channels: ["state"],
|
|
4271
|
-
parameters: {
|
|
4272
|
-
// `distance`: the per-section form only. `offset-top`: the container form only. Each is a
|
|
4273
|
-
// no-op — warned, not silent — in the other; see `prepareScrollSpySingle` and
|
|
4274
|
-
// `prepareScrollSpyContainer`.
|
|
4275
|
-
...distanceParam,
|
|
4276
|
-
// Same name and meaning in both forms: the link(s) this instance marks. Per-section, the
|
|
4277
|
-
// one link this section names; with `sections:`, every link `target:` matches, each paired
|
|
4278
|
-
// to its own section by `href`. See `prepareScrollSpyContainer`.
|
|
4279
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" },
|
|
4280
|
-
// Presence, not value, selects the container form: authoring this at all switches
|
|
4281
|
-
// `prepareScrollSpy` from one-section-per-instance to one-instance-on-the-shared-ancestor.
|
|
4282
|
-
sections: { type: "text", default: "", cssProperty: "--kui-sections" },
|
|
4283
|
-
"offset-top": { type: "length", default: "0px", cssProperty: "--kui-offset-top" }
|
|
4284
|
-
},
|
|
4285
|
-
prepare: deferPrepare(prepareScrollSpy)
|
|
4286
|
-
}),
|
|
4287
|
-
scrollPrimitive({
|
|
4288
|
-
id: "smooth-scroll",
|
|
4289
|
-
/*
|
|
4290
|
-
* Its own channel, not the `'layout'` it used to share with `pin`, `stacking-cards` and
|
|
4291
|
-
* `scroll-snap`. The channel model exists to stop two effects fighting over the same CSS
|
|
4292
|
-
* property, and this one writes exactly `scroll-behavior` — a property that describes how a
|
|
4293
|
-
* *user-or-script-initiated* scroll is performed, and that no other primitive touches.
|
|
4294
|
-
*
|
|
4295
|
-
* On `'layout'` it made a legitimate pairing impossible. Both `smooth-scroll` and
|
|
4296
|
-
* `scroll-snap` have to sit on the document element to have any effect at all — neither
|
|
4297
|
-
* `scroll-behavior` nor `scroll-snap-type` is propagated to the viewport from `<body>` — so
|
|
4298
|
-
* "apply them to nested elements", the advice the conflict message gives, has no valid
|
|
4299
|
-
* nesting to offer here. `data-kui="smooth-scroll-to, scroll-snap-y"` on `<html>` is the
|
|
4300
|
-
* ordinary way to ask for smooth anchor jumps on a page that also snaps, and it was refused
|
|
4301
|
-
* for a collision that cannot happen: the two write disjoint properties.
|
|
4302
|
-
*/
|
|
4303
|
-
channels: ["scroll-behavior"],
|
|
4304
|
-
parameters: {
|
|
4305
|
-
behavior: { type: "keyword", default: "smooth", cssProperty: "--kui-scroll-behavior", values: ["smooth", "auto"] }
|
|
4306
|
-
},
|
|
4307
|
-
prepare: deferPrepare(prepareSmoothScroll),
|
|
4308
|
-
perfClass: "layout"
|
|
4309
|
-
}),
|
|
4310
|
-
scrollPrimitive({
|
|
4311
|
-
id: "scroll-snap",
|
|
4312
|
-
channels: ["layout"],
|
|
4313
|
-
parameters: {
|
|
4314
|
-
axis: { type: "keyword", default: "y", cssProperty: "--kui-axis", values: ["x", "y"] },
|
|
4315
|
-
strictness: {
|
|
4316
|
-
type: "keyword",
|
|
4317
|
-
default: "mandatory",
|
|
4318
|
-
cssProperty: "--kui-snap-strictness",
|
|
4319
|
-
values: ["mandatory", "proximity"]
|
|
4320
|
-
},
|
|
4321
|
-
align: {
|
|
4322
|
-
type: "keyword",
|
|
4323
|
-
default: "start",
|
|
4324
|
-
cssProperty: "--kui-snap-align",
|
|
4325
|
-
values: ["start", "center", "end"]
|
|
4326
|
-
},
|
|
4327
|
-
// Names the snap items. Also what opts this primitive into owning the scroll container
|
|
4328
|
-
// itself — see `installSnapContainer`. Without it, the direct children are the items and
|
|
4329
|
-
// the page keeps its own `overflow`, exactly as before.
|
|
4330
|
-
target: { type: "text", default: "", cssProperty: "--kui-target" }
|
|
4331
|
-
},
|
|
4332
|
-
prepare: deferPrepare(prepareSnap),
|
|
4333
|
-
perfClass: "layout"
|
|
4334
|
-
})
|
|
4335
|
-
];
|
|
4336
|
-
|
|
4337
|
-
// src/effects/scroll-mechanics/presets.ts
|
|
4338
|
-
var SCROLL_PRESETS = [
|
|
4339
|
-
// --- pinning ---------------------------------------------------------------------------
|
|
4340
|
-
// The default carries a spacer: a pin longer than its containing block silently does nothing,
|
|
4341
|
-
// and that is the single most common way authors get sticky wrong.
|
|
4342
|
-
{ name: "pin-section", primitive: "pin", params: { distance: "100vh", spacer: "true" } },
|
|
4343
|
-
{ name: "pin-until", primitive: "pin", params: { spacer: "false" } },
|
|
4344
|
-
{ name: "pin-spacer", primitive: "pin", params: { spacer: "true" } },
|
|
4345
|
-
// Applied per card; each card sticks at its own offset and the stack builds up naturally.
|
|
4346
|
-
{ name: "stacking-cards", primitive: "pin", params: { spacer: "false" } },
|
|
4347
|
-
// --- progress publishing ---------------------------------------------------------------
|
|
4348
|
-
{ name: "scroll-progress", primitive: "scroll-progress" },
|
|
4349
|
-
{ name: "scrollytelling-step", primitive: "scroll-progress", params: { steps: "4" } },
|
|
4350
|
-
// --- travel ------------------------------------------------------------------------------
|
|
4351
|
-
{ name: "horizontal-scroll", primitive: "horizontal-track" },
|
|
4352
|
-
// --- media -------------------------------------------------------------------------------
|
|
4353
|
-
/*
|
|
4354
|
-
* `spacer:true` is what deletes the `.scrub-stage` wrapper a page used to hand-write.
|
|
4355
|
-
*
|
|
4356
|
-
* It could not be switched on until the tracker stopped measuring against the parent. A scrub
|
|
4357
|
-
* makes itself sticky, and `geometrySource` escapes a sticky subtree by taking its parent — so
|
|
4358
|
-
* with the wrapper gone the scrub was measured against whatever section contained it. Measured
|
|
4359
|
-
* on `demo/scroll.html`: the parent started 926px above the scrub against a 1817px distance, so
|
|
4360
|
-
* progress reached 51% before the element had even stuck and half the sequence played off
|
|
4361
|
-
* screen. The wrapper was not ceremony; it was the tight box that made the parent honest.
|
|
4362
|
-
*
|
|
4363
|
-
* `trackProgress`'s `contentAnchor` is the fix. Progress is read from the spacer, which the
|
|
4364
|
-
* library inserts, is exactly `distance` tall, is never sticky, and moves with the content — so
|
|
4365
|
-
* there is no wrapper to write and nothing to disagree with.
|
|
4366
|
-
*
|
|
4367
|
-
* `video-scrub` stays off: a video positioned by the page is not asking the library for a box.
|
|
4368
|
-
*/
|
|
4369
|
-
{ name: "sequence-scrub", primitive: "media-scrub", params: { spacer: "true" } },
|
|
4370
|
-
{ name: "video-scrub", primitive: "media-scrub" },
|
|
4371
|
-
// --- navigation --------------------------------------------------------------------------
|
|
4372
|
-
{ name: "scroll-spy", primitive: "scroll-spy" },
|
|
4373
|
-
// --- native CSS passthroughs ---------------------------------------------------------------
|
|
4374
|
-
{ name: "smooth-scroll-to", primitive: "smooth-scroll" },
|
|
4375
|
-
{ name: "scroll-snap-x", primitive: "scroll-snap", params: { axis: "x" } },
|
|
4376
|
-
{ name: "scroll-snap-y", primitive: "scroll-snap", params: { axis: "y" } }
|
|
4377
|
-
];
|
|
4378
|
-
|
|
4379
|
-
// src/effects/scroll-mechanics/index.ts
|
|
4380
|
-
function registerScrollMechanics(registry) {
|
|
4381
|
-
return registry.registerPrimitives(SCROLL_PRIMITIVES).registerPresets(SCROLL_PRESETS);
|
|
4382
|
-
}
|
|
4383
|
-
|
|
4384
|
-
// src/core/path-morph.ts
|
|
4385
|
-
var COMMAND = /([mlhvcz])|(-?(?:\d+(?:\.\d+)?|\.\d+))/gi;
|
|
4386
|
-
var UNSUPPORTED = /[AaSsQqTt]/;
|
|
4387
|
-
function parsePath(d) {
|
|
4388
|
-
if (UNSUPPORTED.test(d)) {
|
|
4389
|
-
return {
|
|
4390
|
-
segments: [],
|
|
4391
|
-
subpaths: [],
|
|
4392
|
-
reason: "arc and shorthand commands (A S Q T) are not supported"
|
|
4393
|
-
};
|
|
4394
|
-
}
|
|
4395
|
-
const tokens = [...d.matchAll(COMMAND)].map((m) => m[1] ?? m[2]);
|
|
4396
|
-
const state = {
|
|
4397
|
-
subpaths: [],
|
|
4398
|
-
open: void 0,
|
|
4399
|
-
current: { x: 0, y: 0 },
|
|
4400
|
-
start: { x: 0, y: 0 },
|
|
4401
|
-
command: ""
|
|
4402
|
-
};
|
|
4403
|
-
let index = 0;
|
|
4404
|
-
while (index < tokens.length) {
|
|
4405
|
-
const token = tokens[index];
|
|
4406
|
-
if (/[a-z]/i.test(token)) {
|
|
4407
|
-
state.command = token;
|
|
4408
|
-
if (token.toLowerCase() === "z") closeSubpath(state);
|
|
4409
|
-
index++;
|
|
4410
|
-
continue;
|
|
4411
|
-
}
|
|
4412
|
-
if (!state.command) {
|
|
4413
|
-
return { segments: [], subpaths: [], reason: "path must start with a command letter" };
|
|
4414
|
-
}
|
|
4415
|
-
index = consume(tokens, index, state);
|
|
4416
|
-
}
|
|
4417
|
-
if (state.subpaths.length === 0) {
|
|
4418
|
-
return { segments: [], subpaths: [], reason: "no drawable segments" };
|
|
4419
|
-
}
|
|
4420
|
-
return { segments: state.subpaths.flatMap((sub) => sub.segments), subpaths: state.subpaths };
|
|
4421
|
-
}
|
|
4422
|
-
function pushSegment(state, segment) {
|
|
4423
|
-
if (!state.open) {
|
|
4424
|
-
state.open = { segments: [], closed: false };
|
|
4425
|
-
state.subpaths.push(state.open);
|
|
4426
|
-
}
|
|
4427
|
-
state.open.segments.push(segment);
|
|
4428
|
-
}
|
|
4429
|
-
var ARITY = { m: 2, l: 2, h: 1, v: 1, c: 6, z: 0 };
|
|
4430
|
-
function closeSubpath(state) {
|
|
4431
|
-
const { current, start, open } = state;
|
|
4432
|
-
if (!open) return;
|
|
4433
|
-
if (Math.abs(current.x - start.x) >= 1e-6 || Math.abs(current.y - start.y) >= 1e-6) {
|
|
4434
|
-
open.segments.push(lineToCubic(current, start));
|
|
4435
|
-
}
|
|
4436
|
-
open.closed = true;
|
|
4437
|
-
state.current = { ...start };
|
|
4438
|
-
state.open = void 0;
|
|
4439
|
-
}
|
|
4440
|
-
function consume(tokens, index, state) {
|
|
4441
|
-
const key = state.command.toLowerCase();
|
|
4442
|
-
const relative = state.command === key;
|
|
4443
|
-
const arity = ARITY[key];
|
|
4444
|
-
const args = tokens.slice(index, index + arity).map(Number);
|
|
4445
|
-
if (args.length < arity) return tokens.length;
|
|
4446
|
-
const next = endpointFor(key, args, state.current, relative);
|
|
4447
|
-
if (key === "m") {
|
|
4448
|
-
state.current = next;
|
|
4449
|
-
state.start = next;
|
|
4450
|
-
state.open = void 0;
|
|
4451
|
-
state.command = relative ? "l" : "L";
|
|
4452
|
-
return index + arity;
|
|
4453
|
-
}
|
|
4454
|
-
pushSegment(state, straightOrCubic({ key, args, from: state.current, relative }, next));
|
|
4455
|
-
state.current = next;
|
|
4456
|
-
return index + arity;
|
|
4457
|
-
}
|
|
4458
|
-
function endpointFor(key, args, current, relative) {
|
|
4459
|
-
const base = relative ? current : { x: 0, y: 0 };
|
|
4460
|
-
if (key === "h") return { x: base.x + args[0], y: current.y };
|
|
4461
|
-
if (key === "v") return { x: current.x, y: base.y + args[0] };
|
|
4462
|
-
if (key === "c") return { x: base.x + args[4], y: base.y + args[5] };
|
|
4463
|
-
return { x: base.x + args[0], y: base.y + args[1] };
|
|
4464
|
-
}
|
|
4465
|
-
function straightOrCubic(command, to) {
|
|
4466
|
-
const { key, args, from, relative } = command;
|
|
4467
|
-
if (key !== "c") return lineToCubic(from, to);
|
|
4468
|
-
const base = relative ? from : { x: 0, y: 0 };
|
|
4469
|
-
return {
|
|
4470
|
-
from,
|
|
4471
|
-
c1: { x: base.x + args[0], y: base.y + args[1] },
|
|
4472
|
-
c2: { x: base.x + args[2], y: base.y + args[3] },
|
|
4473
|
-
to
|
|
4474
|
-
};
|
|
4475
|
-
}
|
|
4476
|
-
function lineToCubic(from, to) {
|
|
4477
|
-
return {
|
|
4478
|
-
from,
|
|
4479
|
-
c1: lerpPoint(from, to, 1 / 3),
|
|
4480
|
-
c2: lerpPoint(from, to, 2 / 3),
|
|
4481
|
-
to
|
|
4482
|
-
};
|
|
4483
|
-
}
|
|
4484
|
-
function lerp(a, b, t) {
|
|
4485
|
-
return a + (b - a) * t;
|
|
4486
|
-
}
|
|
4487
|
-
function lerpPoint(a, b, t) {
|
|
4488
|
-
return { x: lerp(a.x, b.x, t), y: lerp(a.y, b.y, t) };
|
|
4489
|
-
}
|
|
4490
|
-
function splitCubic(segment, t) {
|
|
4491
|
-
const p01 = lerpPoint(segment.from, segment.c1, t);
|
|
4492
|
-
const p12 = lerpPoint(segment.c1, segment.c2, t);
|
|
4493
|
-
const p23 = lerpPoint(segment.c2, segment.to, t);
|
|
4494
|
-
const p012 = lerpPoint(p01, p12, t);
|
|
4495
|
-
const p123 = lerpPoint(p12, p23, t);
|
|
4496
|
-
const mid = lerpPoint(p012, p123, t);
|
|
4497
|
-
return [
|
|
4498
|
-
{ from: segment.from, c1: p01, c2: p012, to: mid },
|
|
4499
|
-
{ from: mid, c1: p123, c2: p23, to: segment.to }
|
|
4500
|
-
];
|
|
4501
|
-
}
|
|
4502
|
-
function normaliseCount(segments, target) {
|
|
4503
|
-
const out = [...segments];
|
|
4504
|
-
while (out.length < target && out.length > 0) {
|
|
4505
|
-
let longest = 0;
|
|
4506
|
-
for (let i = 1; i < out.length; i++) {
|
|
4507
|
-
if (chordLength(out[i]) > chordLength(out[longest])) longest = i;
|
|
4508
|
-
}
|
|
4509
|
-
const [a, b] = splitCubic(out[longest], 0.5);
|
|
4510
|
-
out.splice(longest, 1, a, b);
|
|
4511
|
-
}
|
|
4512
|
-
return out;
|
|
4513
|
-
}
|
|
4514
|
-
function chordLength(segment) {
|
|
4515
|
-
return Math.hypot(segment.to.x - segment.from.x, segment.to.y - segment.from.y);
|
|
4516
|
-
}
|
|
4517
|
-
function round(value) {
|
|
4518
|
-
return Math.round(value * 100) / 100;
|
|
4519
|
-
}
|
|
4520
|
-
function subpathsToPathData(subpaths) {
|
|
4521
|
-
const parts = [];
|
|
4522
|
-
for (const sub of subpaths) {
|
|
4523
|
-
const head = sub.segments[0];
|
|
4524
|
-
parts.push(`M${round(head.from.x)},${round(head.from.y)}`);
|
|
4525
|
-
for (const s of sub.segments) {
|
|
4526
|
-
parts.push(
|
|
4527
|
-
`C${round(s.c1.x)},${round(s.c1.y)} ${round(s.c2.x)},${round(s.c2.y)} ${round(s.to.x)},${round(s.to.y)}`
|
|
4528
|
-
);
|
|
4529
|
-
}
|
|
4530
|
-
if (sub.closed) parts.push("Z");
|
|
4531
|
-
}
|
|
4532
|
-
return parts.join(" ");
|
|
4533
|
-
}
|
|
4534
|
-
function centroidOf(subpath) {
|
|
4535
|
-
let x = 0;
|
|
4536
|
-
let y = 0;
|
|
4537
|
-
for (const s of subpath.segments) {
|
|
4538
|
-
x += s.from.x;
|
|
4539
|
-
y += s.from.y;
|
|
4540
|
-
}
|
|
4541
|
-
return { x: x / subpath.segments.length, y: y / subpath.segments.length };
|
|
4542
|
-
}
|
|
4543
|
-
function collapsedLike(partner) {
|
|
4544
|
-
const at = centroidOf(partner);
|
|
4545
|
-
return {
|
|
4546
|
-
segments: partner.segments.map(() => ({
|
|
4547
|
-
from: { ...at },
|
|
4548
|
-
c1: { ...at },
|
|
4549
|
-
c2: { ...at },
|
|
4550
|
-
to: { ...at }
|
|
4551
|
-
})),
|
|
4552
|
-
closed: partner.closed
|
|
4553
|
-
};
|
|
4554
|
-
}
|
|
4555
|
-
function normaliseSubpaths(a, b) {
|
|
4556
|
-
const count = Math.max(a.length, b.length);
|
|
4557
|
-
const from = [];
|
|
4558
|
-
const to = [];
|
|
4559
|
-
for (let i = 0; i < count; i++) {
|
|
4560
|
-
const left = a[i] ?? collapsedLike(b[i]);
|
|
4561
|
-
const right = b[i] ?? collapsedLike(a[i]);
|
|
4562
|
-
const target = Math.max(left.segments.length, right.segments.length);
|
|
4563
|
-
const closed = left.closed && right.closed;
|
|
4564
|
-
from.push({ segments: normaliseCount(left.segments, target), closed });
|
|
4565
|
-
to.push({ segments: normaliseCount(right.segments, target), closed });
|
|
4566
|
-
}
|
|
4567
|
-
return { from, to };
|
|
4568
|
-
}
|
|
4569
|
-
function createMorph(fromPath, toPath) {
|
|
4570
|
-
const a = parsePath(fromPath);
|
|
4571
|
-
const b = parsePath(toPath);
|
|
4572
|
-
if (a.reason) return { reason: `start path: ${a.reason}` };
|
|
4573
|
-
if (b.reason) return { reason: `end path: ${b.reason}` };
|
|
4574
|
-
const { from, to } = normaliseSubpaths(a.subpaths, b.subpaths);
|
|
4575
|
-
const count = from.reduce((total, sub) => total + sub.segments.length, 0);
|
|
4576
|
-
return {
|
|
4577
|
-
morph: {
|
|
4578
|
-
segmentCount: count,
|
|
4579
|
-
at(t) {
|
|
4580
|
-
const clamped = Math.min(1, Math.max(0, t));
|
|
4581
|
-
return subpathsToPathData(
|
|
4582
|
-
from.map((sub, i) => ({
|
|
4583
|
-
segments: sub.segments.map(
|
|
4584
|
-
(segment, j) => lerpCubic(segment, to[i].segments[j], clamped)
|
|
4585
|
-
),
|
|
4586
|
-
closed: sub.closed
|
|
4587
|
-
}))
|
|
4588
|
-
);
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
};
|
|
4592
|
-
}
|
|
4593
|
-
function lerpCubic(a, b, t) {
|
|
4594
|
-
return {
|
|
4595
|
-
from: lerpPoint(a.from, b.from, t),
|
|
4596
|
-
c1: lerpPoint(a.c1, b.c1, t),
|
|
4597
|
-
c2: lerpPoint(a.c2, b.c2, t),
|
|
4598
|
-
to: lerpPoint(a.to, b.to, t)
|
|
4599
|
-
};
|
|
4600
|
-
}
|
|
4601
|
-
|
|
4602
|
-
// src/effects/svg/index.ts
|
|
4603
|
-
function prepareMorph(el, params, ctx) {
|
|
4604
|
-
const path = el;
|
|
4605
|
-
const startPath = params.text("from") || path.getAttribute("d") || "";
|
|
4606
|
-
const { morph, reason } = createMorph(startPath, params.text("to"));
|
|
4607
|
-
if (!morph) {
|
|
4608
|
-
ctx.warn(`cannot morph: ${reason}`);
|
|
4609
|
-
return () => {
|
|
4610
|
-
};
|
|
4611
|
-
}
|
|
4612
|
-
const duration = effectDurationMs(params, 300);
|
|
4613
|
-
let frame = 0;
|
|
4614
|
-
let cancelled = false;
|
|
4615
|
-
const drive = (target) => {
|
|
4616
|
-
cancelAnimationFrame(frame);
|
|
4617
|
-
const startedAt = performance.now();
|
|
4618
|
-
const startValue = current;
|
|
4619
|
-
const step = (now) => {
|
|
4620
|
-
if (cancelled) return;
|
|
4621
|
-
const t = duration > 0 ? Math.min(1, (now - startedAt) / duration) : 1;
|
|
4622
|
-
current = startValue + (target - startValue) * t;
|
|
4623
|
-
path.setAttribute("d", morph.at(current));
|
|
4624
|
-
if (t < 1) frame = requestAnimationFrame(step);
|
|
4625
|
-
};
|
|
4626
|
-
frame = requestAnimationFrame(step);
|
|
4627
|
-
};
|
|
4628
|
-
let current = 0;
|
|
4629
|
-
const enter = () => drive(1);
|
|
4630
|
-
const leave = () => drive(0);
|
|
4631
|
-
el.addEventListener("pointerenter", enter, { passive: true });
|
|
4632
|
-
el.addEventListener("focusin", enter, { passive: true });
|
|
4633
|
-
el.addEventListener("pointerleave", leave, { passive: true });
|
|
4634
|
-
el.addEventListener("focusout", leave, { passive: true });
|
|
4635
|
-
return () => {
|
|
4636
|
-
cancelled = true;
|
|
4637
|
-
cancelAnimationFrame(frame);
|
|
4638
|
-
el.removeEventListener("pointerenter", enter);
|
|
4639
|
-
el.removeEventListener("focusin", enter);
|
|
4640
|
-
el.removeEventListener("pointerleave", leave);
|
|
4641
|
-
el.removeEventListener("focusout", leave);
|
|
4642
|
-
path.setAttribute("d", startPath);
|
|
4643
|
-
};
|
|
4644
|
-
}
|
|
4645
|
-
var PATH_DRAW_PRIMITIVE = cssPrimitive("path-draw", [CHANNEL.stroke], {
|
|
4646
|
-
parameters: {
|
|
4647
|
-
length: { type: "number", default: "100", cssProperty: "--kui-path-length", finite: true }
|
|
4648
|
-
}
|
|
4649
|
-
});
|
|
4650
|
-
var SHAPE_FILL_PRIMITIVE = cssPrimitive("shape-fill", [CHANNEL.clip]);
|
|
4651
|
-
var BAR_GROW_PRIMITIVE = cssPrimitive("bar-grow", [CHANNEL.scale], {
|
|
4652
|
-
parameters: { from: { type: "number", default: "0", cssProperty: "--kui-bar-from" } }
|
|
4653
|
-
});
|
|
4654
|
-
var LOGO_BUILD_PRIMITIVE = cssPrimitive("logo-assemble", [
|
|
4655
|
-
CHANNEL.opacity,
|
|
4656
|
-
CHANNEL.scale,
|
|
4657
|
-
CHANNEL.rotate
|
|
4658
|
-
]);
|
|
4659
|
-
var ICON_TOGGLE_PRIMITIVE = {
|
|
4660
|
-
id: "icon-toggle",
|
|
4661
|
-
renderer: "javascript",
|
|
4662
|
-
channels: [CHANNEL.translate, CHANNEL.rotate, CHANNEL.scale, CHANNEL.opacity, CHANNEL.clip],
|
|
4663
|
-
parameters: {
|
|
4664
|
-
duration: { type: "time", default: "260ms", cssProperty: "--kui-duration" },
|
|
4665
|
-
ease: { type: "easing", default: "ease-out", cssProperty: "--kui-ease" }
|
|
4666
|
-
},
|
|
4667
|
-
supportedTimelines: ["time"],
|
|
4668
|
-
supportedActivations: ["load"],
|
|
4669
|
-
defaultActivation: "load",
|
|
4670
|
-
perfClass: "compositor",
|
|
4671
|
-
reducedMotion: "disable",
|
|
4672
|
-
prepare: () => inertInstance()
|
|
4673
|
-
};
|
|
4674
|
-
var SVG_PRIMITIVES = [
|
|
4675
|
-
{
|
|
4676
|
-
id: "path-morph",
|
|
4677
|
-
renderer: "javascript",
|
|
4678
|
-
// `d` is its own channel: nothing else in the catalog writes path geometry, so a morph can
|
|
4679
|
-
// safely compose with a fade or a rotation on the same element.
|
|
4680
|
-
channels: ["path"],
|
|
4681
|
-
parameters: {
|
|
4682
|
-
from: { type: "text", default: "", cssProperty: "--kui-path-from" },
|
|
4683
|
-
to: { type: "text", default: "", cssProperty: "--kui-path-to" },
|
|
4684
|
-
duration: { type: "time", default: "300ms", cssProperty: "--kui-duration" }
|
|
4685
|
-
},
|
|
4686
|
-
supportedTimelines: ["time"],
|
|
4687
|
-
supportedActivations: ["hover", "focus", "manual", "load"],
|
|
4688
|
-
// The morph attaches its own hover listeners, so it must be wired up on load, not on enter.
|
|
4689
|
-
defaultActivation: "load",
|
|
4690
|
-
perfClass: "paint",
|
|
4691
|
-
reducedMotion: "disable",
|
|
4692
|
-
prepare: deferPrepare(prepareMorph)
|
|
4693
|
-
},
|
|
4694
|
-
PATH_DRAW_PRIMITIVE,
|
|
4695
|
-
SHAPE_FILL_PRIMITIVE,
|
|
4696
|
-
BAR_GROW_PRIMITIVE,
|
|
4697
|
-
LOGO_BUILD_PRIMITIVE,
|
|
4698
|
-
ICON_TOGGLE_PRIMITIVE
|
|
4699
|
-
];
|
|
4700
|
-
var SVG_PRESETS = [
|
|
4701
|
-
{ name: "icon-morph", primitive: "path-morph" },
|
|
4702
|
-
{ name: "blob-morph", primitive: "path-morph", params: { duration: "800ms" } },
|
|
4703
|
-
// Stroke draws. One keyframe block each rather than one shared block, matching the
|
|
4704
|
-
// progress-ring/gauge-sweep/donut-sweep/sparkline-draw group in numbers.ts: identical bodies
|
|
4705
|
-
// today, but each name is free to diverge and a consumer can restyle one without the others.
|
|
4706
|
-
{ name: "draw-stroke", primitive: "path-draw", keyframes: "kui-draw-stroke", params: { duration: "800ms", ease: "ease-in-out" } },
|
|
4707
|
-
{ name: "draw-signature", primitive: "path-draw", keyframes: "kui-draw-signature", params: { duration: "1600ms", ease: "ease-in-out" } },
|
|
4708
|
-
{ name: "draw-underline", primitive: "path-draw", keyframes: "kui-draw-underline", params: { duration: "420ms" } },
|
|
4709
|
-
{ name: "checkmark-draw", primitive: "path-draw", keyframes: "kui-checkmark-draw", params: { duration: "320ms" } },
|
|
4710
|
-
{ name: "cross-draw", primitive: "path-draw", keyframes: "kui-cross-draw", params: { duration: "260ms" } },
|
|
4711
|
-
{ name: "chart-line-draw", primitive: "path-draw", keyframes: "kui-chart-line-draw", params: { duration: "1200ms", ease: "ease-in-out" } },
|
|
4712
|
-
{ name: "gradient-stroke", primitive: "path-draw", keyframes: "kui-gradient-stroke", params: { duration: "2400ms", ease: "ease-in-out" } },
|
|
4713
|
-
// Fills.
|
|
4714
|
-
{ name: "heart-fill", primitive: "shape-fill", keyframes: "kui-heart-fill", params: { duration: "420ms" } },
|
|
4715
|
-
{ name: "bookmark-fill", primitive: "shape-fill", keyframes: "kui-bookmark-fill", params: { duration: "360ms" } },
|
|
4716
|
-
{ name: "chart-area-fill", primitive: "shape-fill", keyframes: "kui-chart-area-fill", params: { duration: "900ms" } },
|
|
4717
|
-
// `cloak: true`: `kui-chart-bar-grow`'s `from { scale: 1 0 }` (svg.css) is a zero-height box
|
|
4718
|
-
// while paused, not just an invisible one — so it occupies no space in layout for the whole
|
|
4719
|
-
// wait. Same defect `fold-panel` has, and the same fix shape: see svg.css's
|
|
4720
|
-
// `[data-kui-fx~='chart-bar-grow'][data-kui-state='ready']` rule.
|
|
4721
|
-
{
|
|
4722
|
-
name: "chart-bar-grow",
|
|
4723
|
-
primitive: "bar-grow",
|
|
4724
|
-
keyframes: "kui-chart-bar-grow",
|
|
4725
|
-
params: { duration: "700ms", ease: "back-out" },
|
|
4726
|
-
cloak: true
|
|
4727
|
-
},
|
|
4728
|
-
{ name: "logo-build", primitive: "logo-assemble", keyframes: "kui-logo-build", params: { duration: "520ms", ease: "back-out" } },
|
|
4729
|
-
// Icon toggles — no `keyframes`, because their motion is a CSS transition in svg.css keyed off
|
|
4730
|
-
// aria state, not a compiled animation. Same shape as forms.ts's native-state presets.
|
|
4731
|
-
{ name: "hamburger-to-x", primitive: "icon-toggle" },
|
|
4732
|
-
{ name: "play-to-pause", primitive: "icon-toggle" },
|
|
4733
|
-
{ name: "plus-to-minus", primitive: "icon-toggle" }
|
|
4734
|
-
];
|
|
4735
|
-
function registerSvg(registry) {
|
|
4736
|
-
return registry.registerPrimitives(SVG_PRIMITIVES).registerPresets(SVG_PRESETS);
|
|
4737
|
-
}
|
|
4738
|
-
|
|
4739
|
-
// src/effects/three-d/index.ts
|
|
4740
|
-
var FLIP_CONTROL_SELECTOR = ":scope > .kui-flip-control";
|
|
4741
|
-
function prepareCardToggle(el, params, ctx) {
|
|
4742
|
-
const trigger = params.text("trigger", "click");
|
|
4743
|
-
if (trigger === "click") return () => {
|
|
4744
|
-
};
|
|
4745
|
-
if (!supportsFineHover(ctx.win)) return () => {
|
|
4746
|
-
};
|
|
4747
|
-
const control = el.querySelector(FLIP_CONTROL_SELECTOR);
|
|
4748
|
-
if (!control) {
|
|
4749
|
-
ctx.warn(`flip-card trigger:${trigger} found no direct-child .kui-flip-control \u2014 the card will not flip`);
|
|
4750
|
-
return () => {
|
|
4751
|
-
};
|
|
4752
|
-
}
|
|
4753
|
-
const set = (flipped) => control.setAttribute("aria-pressed", String(flipped));
|
|
4754
|
-
const isFlipped = () => control.getAttribute("aria-pressed") === "true";
|
|
4755
|
-
const onEnter = () => {
|
|
4756
|
-
set(trigger === "hover-toggle" ? !isFlipped() : true);
|
|
4757
|
-
};
|
|
4758
|
-
const onLeave = () => set(false);
|
|
4759
|
-
el.addEventListener("pointerenter", onEnter, { passive: true });
|
|
4760
|
-
if (trigger === "hover") el.addEventListener("pointerleave", onLeave, { passive: true });
|
|
4761
|
-
return () => {
|
|
4762
|
-
el.removeEventListener("pointerenter", onEnter);
|
|
4763
|
-
el.removeEventListener("pointerleave", onLeave);
|
|
4764
|
-
};
|
|
4765
|
-
}
|
|
4766
|
-
var CARD_TOGGLE_PRIMITIVE = {
|
|
4767
|
-
id: "card-toggle",
|
|
4768
|
-
renderer: "javascript",
|
|
4769
|
-
channels: [CHANNEL.rotate],
|
|
4770
|
-
parameters: {
|
|
4771
|
-
duration: { type: "time", default: "700ms", cssProperty: "--kui-duration" },
|
|
4772
|
-
ease: { type: "easing", default: "ease-in-out", cssProperty: "--kui-ease" },
|
|
4773
|
-
perspective: { type: "length", default: "1600px", cssProperty: "--kui-perspective" },
|
|
4774
|
-
trigger: {
|
|
4775
|
-
type: "keyword",
|
|
4776
|
-
default: "click",
|
|
4777
|
-
cssProperty: "--kui-flip-trigger",
|
|
4778
|
-
values: ["click", "hover", "hover-latch", "hover-toggle"]
|
|
4779
|
-
}
|
|
4780
|
-
},
|
|
4781
|
-
supportedTimelines: ["time"],
|
|
4782
|
-
supportedActivations: ["load"],
|
|
4783
|
-
defaultActivation: "load",
|
|
4784
|
-
perfClass: "compositor",
|
|
4785
|
-
reducedMotion: "disable",
|
|
4786
|
-
prepare: deferPrepare(prepareCardToggle)
|
|
4787
|
-
};
|
|
4788
|
-
var THREE_D_PRIMITIVES = [
|
|
4789
|
-
// `skew`, not `rotate`: the keyframes in three-d.css write `transform: perspective(...)
|
|
4790
|
-
// rotateX/Y(...)`, not the individual `rotate:` property — `perspective` only creates depth for
|
|
4791
|
-
// an element's *children*, so giving one of these effects its own depth means reaching for the
|
|
4792
|
-
// `perspective()` transform *function*, which only exists inside the `transform` shorthand.
|
|
4793
|
-
// `CHANNEL.skew` is this catalog's name for "claims the whole `transform` shorthand"; see the
|
|
4794
|
-
// comment on it in `core/types.ts`.
|
|
4795
|
-
cssPrimitive("flip-face", [CHANNEL.skew], {
|
|
4796
|
-
parameters: {
|
|
4797
|
-
angle: { type: "angle", default: "180deg", cssProperty: "--kui-from-angle" },
|
|
4798
|
-
perspective: { type: "length", default: "1200px", cssProperty: "--kui-perspective" }
|
|
4799
|
-
}
|
|
4800
|
-
}),
|
|
4801
|
-
cssPrimitive("page-reveal", [CHANNEL.opacity, CHANNEL.translate], {
|
|
4802
|
-
parameters: { distance: { type: "length", default: "40px", cssProperty: "--kui-distance" } }
|
|
4803
|
-
}),
|
|
4804
|
-
cssPrimitive("wipe", [CHANNEL.clip]),
|
|
4805
|
-
// `from` gives `loading-bar` a real knob on its start scale — it had none before, unlike
|
|
4806
|
-
// `flip-face`'s `angle:` two rows up. `--kui-bar-from` is also what the fold-panel-style
|
|
4807
|
-
// `[data-kui-fx~='loading-bar'][data-kui-state='ready']` gate neutralizes in three-d.css; see
|
|
4808
|
-
// that rule's comment for the on:enter fix this parameter doubles as.
|
|
4809
|
-
cssPrimitive("bar", [CHANNEL.scale], {
|
|
4810
|
-
parameters: { from: { type: "number", default: "0", cssProperty: "--kui-bar-from" } }
|
|
4811
|
-
}),
|
|
4812
|
-
CARD_TOGGLE_PRIMITIVE
|
|
4813
|
-
];
|
|
4814
|
-
var THREE_D_PRESETS = [
|
|
4815
|
-
// --- 3D & perspective ---
|
|
4816
|
-
{ name: "card-flip-y", primitive: "flip-face", keyframes: "kui-card-flip-y", cloak: true },
|
|
4817
|
-
{ name: "card-flip-x", primitive: "flip-face", keyframes: "kui-card-flip-x", cloak: true },
|
|
4818
|
-
{ name: "cube-rotate", primitive: "flip-face", keyframes: "kui-cube-rotate", params: { angle: "90deg" } },
|
|
4819
|
-
{
|
|
4820
|
-
name: "book-page-turn",
|
|
4821
|
-
primitive: "flip-face",
|
|
4822
|
-
keyframes: "kui-book-page-turn",
|
|
4823
|
-
params: { angle: "-160deg", duration: "900ms" }
|
|
4824
|
-
},
|
|
4825
|
-
// `cloak: true`, unlike its `flip-face` siblings above: those are `to`-only keyframes, so their
|
|
4826
|
-
// paused/waiting box is the ordinary, untransformed rest state. `fold-panel` is `from`-only —
|
|
4827
|
-
// its `rotateX(-90deg)` (three-d.css) *is* the paused box, edge-on and zero-height, so it holds
|
|
4828
|
-
// no space in layout for the whole wait; see the
|
|
4829
|
-
// `[data-kui-fx~='fold-panel'][data-kui-state='ready']` rule in three-d.css for the other half
|
|
4830
|
-
// of that fix. `cloak` only ever hid the pre-JS flash, not this, but adding it here keeps the
|
|
4831
|
-
// pre-JS and post-JS "ready" appearances the same (invisible) instead of trading one flash for
|
|
4832
|
-
// the other.
|
|
4833
|
-
{
|
|
4834
|
-
name: "fold-panel",
|
|
4835
|
-
primitive: "flip-face",
|
|
4836
|
-
keyframes: "kui-fold-panel",
|
|
4837
|
-
params: { angle: "-90deg" },
|
|
4838
|
-
cloak: true
|
|
4839
|
-
},
|
|
4840
|
-
// --- page transitions ---
|
|
4841
|
-
{ name: "page-fade", primitive: "page-reveal", keyframes: "kui-page-fade" },
|
|
4842
|
-
{ name: "page-slide", primitive: "page-reveal", keyframes: "kui-page-slide" },
|
|
4843
|
-
{ name: "curtain-wipe", primitive: "wipe", keyframes: "kui-curtain-wipe", params: { duration: "800ms" } },
|
|
4844
|
-
// `cloak: true` for the same reason as `fold-panel`: `kui-loading-bar`'s `from { scale: 0 1 }`
|
|
4845
|
-
// (three-d.css) is a zero-width box, not just an invisible one — see that file's
|
|
4846
|
-
// `[data-kui-fx~='loading-bar'][data-kui-state='ready']` rule.
|
|
4847
|
-
{ name: "loading-bar", primitive: "bar", keyframes: "kui-loading-bar", cloak: true },
|
|
4848
|
-
// No `keyframes`: its motion is a CSS transition in three-d.css keyed off the control's
|
|
4849
|
-
// aria-pressed, not a compiled animation. Same shape as the icon toggles in svg.ts.
|
|
4850
|
-
{ name: "flip-card", primitive: "card-toggle" }
|
|
4851
|
-
];
|
|
4852
|
-
function registerThreeD(registry) {
|
|
4853
|
-
return registry.registerPrimitives(THREE_D_PRIMITIVES).registerPresets(THREE_D_PRESETS);
|
|
4854
|
-
}
|
|
4855
|
-
|
|
4856
|
-
// src/effects/index.ts
|
|
4857
|
-
function createRegistry() {
|
|
4858
|
-
const registry = new Registry();
|
|
4859
|
-
registerCore(registry);
|
|
4860
|
-
registerScrollMechanics(registry);
|
|
4861
|
-
registerLayout(registry);
|
|
4862
|
-
registerSvg(registry);
|
|
4863
|
-
registerGestures(registry);
|
|
4864
|
-
registerThreeD(registry);
|
|
4865
|
-
registerCatalog(registry);
|
|
4866
|
-
registerNavigation(registry);
|
|
4867
|
-
registerForms(registry);
|
|
4868
|
-
return registry;
|
|
4869
|
-
}
|
|
4870
|
-
|
|
4871
|
-
export {
|
|
4872
|
-
registerCatalog,
|
|
4873
|
-
PRIMITIVES,
|
|
4874
|
-
PRESETS,
|
|
4875
|
-
COMBOS,
|
|
4876
|
-
registerCore,
|
|
4877
|
-
registerGestures,
|
|
4878
|
-
registerLayout,
|
|
4879
|
-
registerScrollMechanics,
|
|
4880
|
-
registerSvg,
|
|
4881
|
-
registerThreeD,
|
|
4882
|
-
createRegistry
|
|
4883
|
-
};
|