@vune-ui/animation 0.1.20
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/ARCHITECTURE.md +470 -0
- package/CHANGELOG.md +88 -0
- package/LICENSE +21 -0
- package/PERFORMANCE.md +151 -0
- package/README.md +630 -0
- package/dist/index.d.ts +474 -0
- package/dist/src/canvas/index.d.ts +15 -0
- package/dist/src/canvas/index.js +67 -0
- package/dist/src/constraints/index.d.ts +33 -0
- package/dist/src/constraints/index.js +346 -0
- package/dist/src/core/bezier.js +51 -0
- package/dist/src/core/composition.js +17 -0
- package/dist/src/core/controls.js +22 -0
- package/dist/src/core/default-engine.js +20 -0
- package/dist/src/core/easing.js +58 -0
- package/dist/src/core/engine.js +1031 -0
- package/dist/src/core/frame-budget.js +30 -0
- package/dist/src/core/index.d.ts +43 -0
- package/dist/src/core/index.js +17 -0
- package/dist/src/core/js-spring-batch.js +57 -0
- package/dist/src/core/kinetics.js +140 -0
- package/dist/src/core/math.js +20 -0
- package/dist/src/core/motion-value.js +53 -0
- package/dist/src/core/planner.js +72 -0
- package/dist/src/core/specs.js +70 -0
- package/dist/src/dom/index.d.ts +41 -0
- package/dist/src/dom/index.js +364 -0
- package/dist/src/gesture/index.d.ts +66 -0
- package/dist/src/gesture/index.js +376 -0
- package/dist/src/index.js +53 -0
- package/dist/src/interpolate/color.js +223 -0
- package/dist/src/interpolate/css.d.ts +13 -0
- package/dist/src/interpolate/css.js +34 -0
- package/dist/src/interpolate/index.d.ts +13 -0
- package/dist/src/interpolate/index.js +55 -0
- package/dist/src/interpolate/transform.js +247 -0
- package/dist/src/layout/index.d.ts +56 -0
- package/dist/src/layout/index.js +485 -0
- package/dist/src/material/index.d.ts +9 -0
- package/dist/src/material/index.js +70 -0
- package/dist/src/path/index.d.ts +37 -0
- package/dist/src/path/index.js +527 -0
- package/dist/src/render/frame-batcher.js +52 -0
- package/dist/src/scroll/index.d.ts +55 -0
- package/dist/src/scroll/index.js +233 -0
- package/dist/src/timeline/index.d.ts +147 -0
- package/dist/src/timeline/index.js +849 -0
- package/dist/src/transition/index.d.ts +88 -0
- package/dist/src/transition/index.js +369 -0
- package/dist/src/wasm/index.d.ts +29 -0
- package/dist/src/wasm/index.js +8 -0
- package/dist/src/wasm/loader.js +55 -0
- package/dist/src/wasm/shared-wasm-spring-batch.js +52 -0
- package/dist/src/wasm/wasm-spring-batch.js +52 -0
- package/dist/src/webgl/index.d.ts +22 -0
- package/dist/src/webgl/index.js +94 -0
- package/dist/src/webgpu/index.d.ts +35 -0
- package/dist/src/webgpu/index.js +73 -0
- package/dist/src/webgpu/spring-batch.js +218 -0
- package/dist/src/worker/index.d.ts +17 -0
- package/dist/src/worker/index.js +1 -0
- package/dist/src/worker/shared-spring-worker.js +218 -0
- package/dist/src/worker/shared-worker.js +75 -0
- package/dist/wasm/kernel-scalar.wasm +0 -0
- package/dist/wasm/kernel-shared-scalar.wasm +0 -0
- package/dist/wasm/kernel-shared-simd.wasm +0 -0
- package/dist/wasm/kernel-simd.wasm +0 -0
- package/package.json +113 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { animateInterpolated } from '../interpolate/index.js';
|
|
2
|
+
import { materialToCss } from '../material/index.js';
|
|
3
|
+
|
|
4
|
+
const queues = new WeakMap();
|
|
5
|
+
const styleAnimationOwners = new WeakMap();
|
|
6
|
+
const dirtyElements = new Set();
|
|
7
|
+
let globalCommitScheduled = false;
|
|
8
|
+
let commitScheduler = 'microtask';
|
|
9
|
+
let rafId = null;
|
|
10
|
+
let scheduleGeneration = 0;
|
|
11
|
+
|
|
12
|
+
function queueFor(element) {
|
|
13
|
+
let state = queues.get(element);
|
|
14
|
+
if (!state) {
|
|
15
|
+
state = {
|
|
16
|
+
element,
|
|
17
|
+
x: 0, y: 0, z: 0,
|
|
18
|
+
scaleX: 1, scaleY: 1, scaleZ: 1,
|
|
19
|
+
rotateX: 0, rotateY: 0, rotateZ: 0,
|
|
20
|
+
opacity: null,
|
|
21
|
+
transformDirty: false,
|
|
22
|
+
opacityDirty: false,
|
|
23
|
+
originalWillChange: element.style?.willChange ?? '',
|
|
24
|
+
transformBindings: 0,
|
|
25
|
+
opacityBindings: 0,
|
|
26
|
+
direct: new Map(),
|
|
27
|
+
attributes: new Map(),
|
|
28
|
+
scheduled: false,
|
|
29
|
+
};
|
|
30
|
+
queues.set(element, state);
|
|
31
|
+
}
|
|
32
|
+
return state;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function formatTransform(state) {
|
|
36
|
+
return `translate3d(${state.x}px, ${state.y}px, ${state.z}px) rotateX(${state.rotateX}deg) rotateY(${state.rotateY}deg) rotate(${state.rotateZ}deg) scale3d(${state.scaleX}, ${state.scaleY}, ${state.scaleZ})`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function updateWillChange(state) {
|
|
40
|
+
const active = [];
|
|
41
|
+
if (state.transformBindings > 0) active.push('transform');
|
|
42
|
+
if (state.opacityBindings > 0) active.push('opacity');
|
|
43
|
+
state.element.style.willChange = active.length > 0 ? active.join(', ') : state.originalWillChange;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function commitState(state) {
|
|
47
|
+
const element = state.element;
|
|
48
|
+
state.scheduled = false;
|
|
49
|
+
// Only touch compositor-affecting properties that changed. This matters on
|
|
50
|
+
// power-constrained devices: direct style/attribute bindings should not
|
|
51
|
+
// force a transform serialization or an extra style recalculation.
|
|
52
|
+
if (state.transformDirty) {
|
|
53
|
+
element.style.transform = formatTransform(state);
|
|
54
|
+
state.transformDirty = false;
|
|
55
|
+
}
|
|
56
|
+
if (state.opacityDirty) {
|
|
57
|
+
if (state.opacity != null) element.style.opacity = String(state.opacity);
|
|
58
|
+
state.opacityDirty = false;
|
|
59
|
+
}
|
|
60
|
+
for (const [property, value] of state.direct) element.style[property] = value;
|
|
61
|
+
for (const [name, value] of state.attributes) element.setAttribute?.(name, value);
|
|
62
|
+
state.direct.clear();
|
|
63
|
+
state.attributes.clear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function flushDomCommits() {
|
|
67
|
+
scheduleGeneration += 1;
|
|
68
|
+
globalCommitScheduled = false;
|
|
69
|
+
rafId = null;
|
|
70
|
+
if (dirtyElements.size === 0) return 0;
|
|
71
|
+
const batch = Array.from(dirtyElements);
|
|
72
|
+
dirtyElements.clear();
|
|
73
|
+
for (const state of batch) commitState(state);
|
|
74
|
+
return batch.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function scheduleGlobalCommit() {
|
|
78
|
+
if (globalCommitScheduled) return;
|
|
79
|
+
globalCommitScheduled = true;
|
|
80
|
+
const generation = ++scheduleGeneration;
|
|
81
|
+
const run = () => {
|
|
82
|
+
if (generation !== scheduleGeneration) return;
|
|
83
|
+
flushDomCommits();
|
|
84
|
+
};
|
|
85
|
+
if (commitScheduler === 'raf' && typeof globalThis.requestAnimationFrame === 'function') {
|
|
86
|
+
rafId = globalThis.requestAnimationFrame(run);
|
|
87
|
+
} else {
|
|
88
|
+
queueMicrotask(run);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function scheduleCommit(_element, state) {
|
|
93
|
+
if (!state.scheduled) {
|
|
94
|
+
state.scheduled = true;
|
|
95
|
+
dirtyElements.add(state);
|
|
96
|
+
}
|
|
97
|
+
scheduleGlobalCommit();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function configureDomBatching({ scheduler = 'microtask' } = {}) {
|
|
101
|
+
if (scheduler !== 'microtask' && scheduler !== 'raf') throw new TypeError("DOM scheduler must be 'microtask' or 'raf'.");
|
|
102
|
+
if (commitScheduler === scheduler) return;
|
|
103
|
+
if (rafId != null && typeof globalThis.cancelAnimationFrame === 'function') globalThis.cancelAnimationFrame(rafId);
|
|
104
|
+
rafId = null;
|
|
105
|
+
scheduleGeneration += 1;
|
|
106
|
+
globalCommitScheduled = false;
|
|
107
|
+
commitScheduler = scheduler;
|
|
108
|
+
if (dirtyElements.size > 0) scheduleGlobalCommit();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function applyBinding(state, property, value) {
|
|
112
|
+
switch (property) {
|
|
113
|
+
case 'scale': state.scaleX = value; state.scaleY = value; state.scaleZ = value; state.transformDirty = true; break;
|
|
114
|
+
case 'rotate': state.rotateZ = value; state.transformDirty = true; break;
|
|
115
|
+
case 'x': case 'y': case 'z':
|
|
116
|
+
case 'scaleX': case 'scaleY': case 'scaleZ':
|
|
117
|
+
case 'rotateX': case 'rotateY': case 'rotateZ':
|
|
118
|
+
state[property] = value;
|
|
119
|
+
state.transformDirty = true;
|
|
120
|
+
break;
|
|
121
|
+
case 'opacity':
|
|
122
|
+
state.opacity = value;
|
|
123
|
+
state.opacityDirty = true;
|
|
124
|
+
break;
|
|
125
|
+
default: state.direct.set(property, String(value)); break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function bindMotionStyles(element, bindings) {
|
|
130
|
+
const state = queueFor(element);
|
|
131
|
+
const unsubscribers = [];
|
|
132
|
+
const willChange = new Set();
|
|
133
|
+
for (const [property, motion] of Object.entries(bindings)) {
|
|
134
|
+
if (!motion?.subscribe) continue;
|
|
135
|
+
if (property === 'opacity') willChange.add('opacity');
|
|
136
|
+
else if (property === 'scale' || property === 'rotate' || property === 'x' || property === 'y' || property === 'z'
|
|
137
|
+
|| property === 'scaleX' || property === 'scaleY' || property === 'scaleZ'
|
|
138
|
+
|| property === 'rotateX' || property === 'rotateY' || property === 'rotateZ') {
|
|
139
|
+
willChange.add('transform');
|
|
140
|
+
}
|
|
141
|
+
unsubscribers.push((motion.subscribeValue ?? motion.subscribe).call(motion, (value) => {
|
|
142
|
+
applyBinding(state, property, value);
|
|
143
|
+
scheduleCommit(element, state);
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
if (willChange.has('transform')) state.transformBindings += 1;
|
|
147
|
+
if (willChange.has('opacity')) state.opacityBindings += 1;
|
|
148
|
+
if (willChange.size > 0) updateWillChange(state);
|
|
149
|
+
let active = true;
|
|
150
|
+
return () => {
|
|
151
|
+
if (!active) return;
|
|
152
|
+
active = false;
|
|
153
|
+
unsubscribers.forEach((unsubscribe) => unsubscribe());
|
|
154
|
+
if (willChange.has('transform')) state.transformBindings = Math.max(0, state.transformBindings - 1);
|
|
155
|
+
if (willChange.has('opacity')) state.opacityBindings = Math.max(0, state.opacityBindings - 1);
|
|
156
|
+
if (willChange.size > 0) updateWillChange(state);
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function bindStyleValue(element, property, motion, { unit = '' } = {}) {
|
|
161
|
+
if (!motion?.subscribe) throw new TypeError('bindStyleValue() requires a MotionValue-like object.');
|
|
162
|
+
const state = queueFor(element);
|
|
163
|
+
const unsubscribe = (motion.subscribeValue ?? motion.subscribe).call(motion, (value) => {
|
|
164
|
+
state.direct.set(property, `${value}${unit}`);
|
|
165
|
+
scheduleCommit(element, state);
|
|
166
|
+
});
|
|
167
|
+
return unsubscribe;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function animateStyle(element, property, from, to, spec, options = {}) {
|
|
171
|
+
const state = queueFor(element);
|
|
172
|
+
const inferredType = options.type ?? (property === 'transform' ? 'transform' : undefined);
|
|
173
|
+
return animateInterpolated(from, to, spec, (value) => {
|
|
174
|
+
state.direct.set(property, String(value));
|
|
175
|
+
scheduleCommit(element, state);
|
|
176
|
+
}, { ...options, type: inferredType });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function styleOwnerMap(element) {
|
|
180
|
+
let owners = styleAnimationOwners.get(element);
|
|
181
|
+
if (!owners) {
|
|
182
|
+
owners = new Map();
|
|
183
|
+
styleAnimationOwners.set(element, owners);
|
|
184
|
+
}
|
|
185
|
+
return owners;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function cancelStyleAnimations(element, properties) {
|
|
189
|
+
const owners = styleAnimationOwners.get(element);
|
|
190
|
+
if (!owners) return 0;
|
|
191
|
+
const requested = properties == null
|
|
192
|
+
? Array.from(owners.keys())
|
|
193
|
+
: Array.isArray(properties) ? properties : [properties];
|
|
194
|
+
const controls = new Set();
|
|
195
|
+
for (const property of requested) {
|
|
196
|
+
const control = owners.get(property);
|
|
197
|
+
if (control) controls.add(control);
|
|
198
|
+
}
|
|
199
|
+
for (const control of controls) control.cancel?.();
|
|
200
|
+
return controls.size;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Give a control ownership of one or more CSS properties on an element.
|
|
205
|
+
* Replacing opacity does not disturb transform, size, or any other property;
|
|
206
|
+
* replacing a multi-property control cancels it once even if several keys point
|
|
207
|
+
* at the same control. Ownership is released only if the finishing control is
|
|
208
|
+
* still the current owner, so an old completion cannot erase a newer animation.
|
|
209
|
+
*/
|
|
210
|
+
export function ownStyleAnimation(element, properties, control) {
|
|
211
|
+
if (!control || typeof control.cancel !== 'function' || !control.finished) {
|
|
212
|
+
throw new TypeError('ownStyleAnimation() requires an animation control with cancel() and finished.');
|
|
213
|
+
}
|
|
214
|
+
const list = [...new Set((Array.isArray(properties) ? properties : [properties]).filter(Boolean))];
|
|
215
|
+
if (list.length === 0) return control;
|
|
216
|
+
cancelStyleAnimations(element, list);
|
|
217
|
+
const owners = styleOwnerMap(element);
|
|
218
|
+
for (const property of list) owners.set(property, control);
|
|
219
|
+
const release = () => {
|
|
220
|
+
const current = styleAnimationOwners.get(element);
|
|
221
|
+
if (!current) return;
|
|
222
|
+
for (const property of list) {
|
|
223
|
+
if (current.get(property) === control) current.delete(property);
|
|
224
|
+
}
|
|
225
|
+
if (current.size === 0) styleAnimationOwners.delete(element);
|
|
226
|
+
};
|
|
227
|
+
void Promise.resolve(control.finished).then(release, release);
|
|
228
|
+
return control;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export function animateStyleOwned(element, property, from, to, spec, options = {}) {
|
|
232
|
+
return ownStyleAnimation(element, property, animateStyle(element, property, from, to, spec, options));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
export function applyMaterial(element, material, { background = true } = {}) {
|
|
237
|
+
const state = queueFor(element);
|
|
238
|
+
const css = materialToCss(material);
|
|
239
|
+
state.direct.set('backdropFilter', css.backdropFilter);
|
|
240
|
+
state.direct.set('webkitBackdropFilter', css.backdropFilter);
|
|
241
|
+
if (background) state.direct.set('backgroundColor', css.backgroundColor);
|
|
242
|
+
scheduleCommit(element, state);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function animateMaterial(element, from, to, spec, {
|
|
246
|
+
background = true,
|
|
247
|
+
colorSpace = 'oklab',
|
|
248
|
+
...options
|
|
249
|
+
} = {}) {
|
|
250
|
+
return animateInterpolated(from, to, spec, (material) => {
|
|
251
|
+
applyMaterial(element, material, { background });
|
|
252
|
+
}, { ...options, type: 'material', material: { colorSpace } });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export function animateAttribute(element, name, from, to, spec, options = {}) {
|
|
256
|
+
if (typeof element?.setAttribute !== 'function') throw new TypeError('animateAttribute() requires an Element-like object.');
|
|
257
|
+
const state = queueFor(element);
|
|
258
|
+
return animateInterpolated(from, to, spec, (value) => {
|
|
259
|
+
state.attributes.set(name, String(value));
|
|
260
|
+
scheduleCommit(element, state);
|
|
261
|
+
}, options);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function animatePath(element, from, to, spec, options = {}) {
|
|
265
|
+
return animateAttribute(element, 'd', from, to, spec, { ...options, type: 'path' });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function animateNative(element, keyframes, { duration = 0.3, easing = 'cubic-bezier(.22, 1, .36, 1)', fill = 'both' } = {}) {
|
|
269
|
+
if (typeof element.animate !== 'function') throw new Error('Web Animations API is not available.');
|
|
270
|
+
return element.animate(keyframes, { duration: duration * 1000, easing, fill });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export function bindPointerDrag(element, controller, {
|
|
274
|
+
button = 0,
|
|
275
|
+
pointerCapture = true,
|
|
276
|
+
preventDefault = true,
|
|
277
|
+
touchAction = 'none',
|
|
278
|
+
coalesced = true,
|
|
279
|
+
filter,
|
|
280
|
+
} = {}) {
|
|
281
|
+
if (!element?.addEventListener || !controller?.start || !controller?.move || !controller?.end) {
|
|
282
|
+
throw new TypeError('bindPointerDrag() requires an Element-like target and DragController-like object.');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let activePointer = null;
|
|
286
|
+
const style = element.style;
|
|
287
|
+
const previousTouchAction = style?.touchAction;
|
|
288
|
+
if (style && touchAction != null) style.touchAction = touchAction;
|
|
289
|
+
|
|
290
|
+
const stopEvent = (event) => {
|
|
291
|
+
if (preventDefault && event.cancelable) event.preventDefault();
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const onPointerDown = (event) => {
|
|
295
|
+
if (activePointer != null) return;
|
|
296
|
+
if (event.button != null && event.button !== button) return;
|
|
297
|
+
if (filter && !filter(event)) return;
|
|
298
|
+
activePointer = event.pointerId ?? 1;
|
|
299
|
+
stopEvent(event);
|
|
300
|
+
controller.start({ x: event.clientX, y: event.clientY }, event.timeStamp);
|
|
301
|
+
if (pointerCapture && typeof element.setPointerCapture === 'function' && event.pointerId != null) {
|
|
302
|
+
try { element.setPointerCapture(event.pointerId); } catch { /* detached/unsupported target */ }
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const onPointerMove = (event) => {
|
|
307
|
+
if (activePointer == null || (event.pointerId ?? 1) !== activePointer) return;
|
|
308
|
+
stopEvent(event);
|
|
309
|
+
const samples = coalesced && typeof event.getCoalescedEvents === 'function'
|
|
310
|
+
? event.getCoalescedEvents()
|
|
311
|
+
: null;
|
|
312
|
+
if (samples?.length) {
|
|
313
|
+
for (const sample of samples) {
|
|
314
|
+
controller.move({ x: sample.clientX, y: sample.clientY }, sample.timeStamp);
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
controller.move({ x: event.clientX, y: event.clientY }, event.timeStamp);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
const release = (event, cancelled = false) => {
|
|
322
|
+
if (activePointer == null || (event.pointerId ?? 1) !== activePointer) return;
|
|
323
|
+
stopEvent(event);
|
|
324
|
+
const pointerId = activePointer;
|
|
325
|
+
activePointer = null;
|
|
326
|
+
if (cancelled) controller.cancel({ settle: true });
|
|
327
|
+
else {
|
|
328
|
+
controller.move({ x: event.clientX, y: event.clientY }, event.timeStamp);
|
|
329
|
+
controller.end(event.timeStamp);
|
|
330
|
+
}
|
|
331
|
+
if (pointerCapture && typeof element.releasePointerCapture === 'function' && event.pointerId != null) {
|
|
332
|
+
try {
|
|
333
|
+
if (typeof element.hasPointerCapture !== 'function' || element.hasPointerCapture(pointerId)) {
|
|
334
|
+
element.releasePointerCapture(pointerId);
|
|
335
|
+
}
|
|
336
|
+
} catch { /* already released */ }
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const onPointerUp = (event) => release(event, false);
|
|
341
|
+
const onPointerCancel = (event) => release(event, true);
|
|
342
|
+
const onLostPointerCapture = (event) => {
|
|
343
|
+
if (activePointer == null || (event.pointerId ?? 1) !== activePointer) return;
|
|
344
|
+
activePointer = null;
|
|
345
|
+
controller.cancel({ settle: true });
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
element.addEventListener('pointerdown', onPointerDown, { passive: false });
|
|
349
|
+
element.addEventListener('pointermove', onPointerMove, { passive: false });
|
|
350
|
+
element.addEventListener('pointerup', onPointerUp, { passive: false });
|
|
351
|
+
element.addEventListener('pointercancel', onPointerCancel, { passive: false });
|
|
352
|
+
element.addEventListener('lostpointercapture', onLostPointerCapture);
|
|
353
|
+
|
|
354
|
+
return () => {
|
|
355
|
+
element.removeEventListener('pointerdown', onPointerDown);
|
|
356
|
+
element.removeEventListener('pointermove', onPointerMove);
|
|
357
|
+
element.removeEventListener('pointerup', onPointerUp);
|
|
358
|
+
element.removeEventListener('pointercancel', onPointerCancel);
|
|
359
|
+
element.removeEventListener('lostpointercapture', onLostPointerCapture);
|
|
360
|
+
if (activePointer != null) controller.cancel({ settle: false });
|
|
361
|
+
activePointer = null;
|
|
362
|
+
if (style && touchAction != null) style.touchAction = previousTouchAction ?? '';
|
|
363
|
+
};
|
|
364
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AnimationResult, InertiaOptions, MotionEngine, MotionValue } from '../../index.js';
|
|
2
|
+
|
|
3
|
+
export type Point = { x: number; y: number };
|
|
4
|
+
export type DragBounds = { minX?: number; maxX?: number; minY?: number; maxY?: number };
|
|
5
|
+
export type DragAxis = 'x' | 'y' | 'both';
|
|
6
|
+
export type DragState = {
|
|
7
|
+
active: boolean;
|
|
8
|
+
axis: DragAxis;
|
|
9
|
+
lockedAxis: 'x' | 'y' | null;
|
|
10
|
+
point: Point;
|
|
11
|
+
value: { x: number | null; y: number | null };
|
|
12
|
+
velocity: Point;
|
|
13
|
+
};
|
|
14
|
+
export type GroupAnimationControls = {
|
|
15
|
+
cancel(): void;
|
|
16
|
+
finish(): void;
|
|
17
|
+
finished: Promise<Array<AnimationResult>>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class VelocityTracker {
|
|
21
|
+
constructor(options?: { windowMs?: number; maxSamples?: number; maxVelocity?: number });
|
|
22
|
+
readonly velocity: number;
|
|
23
|
+
reset(value: number, time?: number): this;
|
|
24
|
+
add(value: number, time?: number): this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function rubberBandDistance(distance: number, dimension?: number, constant?: number): number;
|
|
28
|
+
export function constrainWithRubberBand(value: number, min?: number, max?: number, options?: {
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
constant?: number;
|
|
31
|
+
dimension?: number;
|
|
32
|
+
}): number;
|
|
33
|
+
|
|
34
|
+
export type DragControllerOptions = {
|
|
35
|
+
x?: MotionValue | null;
|
|
36
|
+
y?: MotionValue | null;
|
|
37
|
+
axis?: DragAxis;
|
|
38
|
+
engine?: MotionEngine;
|
|
39
|
+
bounds?: DragBounds | (() => DragBounds) | null;
|
|
40
|
+
momentum?: boolean;
|
|
41
|
+
inertia?: InertiaOptions;
|
|
42
|
+
rubberBand?: boolean | number;
|
|
43
|
+
rubberBandConstant?: number;
|
|
44
|
+
rubberBandDimension?: number | { x?: number; y?: number };
|
|
45
|
+
directionLock?: boolean;
|
|
46
|
+
directionLockThreshold?: number;
|
|
47
|
+
snapX?: number[] | ((target: number) => number) | null;
|
|
48
|
+
snapY?: number[] | ((target: number) => number) | null;
|
|
49
|
+
settle?: { response?: number; dampingRatio?: number };
|
|
50
|
+
velocity?: { windowMs?: number; maxSamples?: number; maxVelocity?: number };
|
|
51
|
+
onStart?: (state: DragState) => void;
|
|
52
|
+
onMove?: (state: DragState) => void;
|
|
53
|
+
onEnd?: (state: DragState & { controls: GroupAnimationControls }) => void;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export class DragController {
|
|
57
|
+
constructor(options?: DragControllerOptions);
|
|
58
|
+
readonly active: boolean;
|
|
59
|
+
readonly lockedAxis: 'x' | 'y' | null;
|
|
60
|
+
start(point: Point, time?: number): DragState;
|
|
61
|
+
move(point: Point, time?: number): DragState;
|
|
62
|
+
end(time?: number): DragState & { controls: GroupAnimationControls };
|
|
63
|
+
cancel(options?: { settle?: boolean }): DragState;
|
|
64
|
+
getState(): DragState;
|
|
65
|
+
}
|
|
66
|
+
export function createDragController(options?: DragControllerOptions): DragController;
|