165 lines
5.9 KiB
JavaScript
165 lines
5.9 KiB
JavaScript
import { removeNonTranslationalTransform } from '../dom/utils/unit-conversion.mjs';
|
|
import { frame } from '../../frameloop/frame.mjs';
|
|
|
|
const toResolve = new Set();
|
|
let isScheduled = false;
|
|
let anyNeedsMeasurement = false;
|
|
function measureAllKeyframes() {
|
|
if (anyNeedsMeasurement) {
|
|
const resolversToMeasure = Array.from(toResolve).filter((resolver) => resolver.needsMeasurement);
|
|
const elementsToMeasure = new Set(resolversToMeasure.map((resolver) => resolver.element));
|
|
const transformsToRestore = new Map();
|
|
/**
|
|
* Write pass
|
|
* If we're measuring elements we want to remove bounding box-changing transforms.
|
|
*/
|
|
elementsToMeasure.forEach((element) => {
|
|
const removedTransforms = removeNonTranslationalTransform(element);
|
|
if (!removedTransforms.length)
|
|
return;
|
|
transformsToRestore.set(element, removedTransforms);
|
|
element.render();
|
|
});
|
|
// Read
|
|
resolversToMeasure.forEach((resolver) => resolver.measureInitialState());
|
|
// Write
|
|
elementsToMeasure.forEach((element) => {
|
|
element.render();
|
|
const restore = transformsToRestore.get(element);
|
|
if (restore) {
|
|
restore.forEach(([key, value]) => {
|
|
var _a;
|
|
(_a = element.getValue(key)) === null || _a === void 0 ? void 0 : _a.set(value);
|
|
});
|
|
}
|
|
});
|
|
// Read
|
|
resolversToMeasure.forEach((resolver) => resolver.measureEndState());
|
|
// Write
|
|
resolversToMeasure.forEach((resolver) => {
|
|
if (resolver.suspendedScrollY !== undefined) {
|
|
window.scrollTo(0, resolver.suspendedScrollY);
|
|
}
|
|
});
|
|
}
|
|
anyNeedsMeasurement = false;
|
|
isScheduled = false;
|
|
toResolve.forEach((resolver) => resolver.complete());
|
|
toResolve.clear();
|
|
}
|
|
function readAllKeyframes() {
|
|
toResolve.forEach((resolver) => {
|
|
resolver.readKeyframes();
|
|
if (resolver.needsMeasurement) {
|
|
anyNeedsMeasurement = true;
|
|
}
|
|
});
|
|
}
|
|
function flushKeyframeResolvers() {
|
|
readAllKeyframes();
|
|
measureAllKeyframes();
|
|
}
|
|
class KeyframeResolver {
|
|
constructor(unresolvedKeyframes, onComplete, name, motionValue, element, isAsync = false) {
|
|
/**
|
|
* Track whether this resolver has completed. Once complete, it never
|
|
* needs to attempt keyframe resolution again.
|
|
*/
|
|
this.isComplete = false;
|
|
/**
|
|
* Track whether this resolver is async. If it is, it'll be added to the
|
|
* resolver queue and flushed in the next frame. Resolvers that aren't going
|
|
* to trigger read/write thrashing don't need to be async.
|
|
*/
|
|
this.isAsync = false;
|
|
/**
|
|
* Track whether this resolver needs to perform a measurement
|
|
* to resolve its keyframes.
|
|
*/
|
|
this.needsMeasurement = false;
|
|
/**
|
|
* Track whether this resolver is currently scheduled to resolve
|
|
* to allow it to be cancelled and resumed externally.
|
|
*/
|
|
this.isScheduled = false;
|
|
this.unresolvedKeyframes = [...unresolvedKeyframes];
|
|
this.onComplete = onComplete;
|
|
this.name = name;
|
|
this.motionValue = motionValue;
|
|
this.element = element;
|
|
this.isAsync = isAsync;
|
|
}
|
|
scheduleResolve() {
|
|
this.isScheduled = true;
|
|
if (this.isAsync) {
|
|
toResolve.add(this);
|
|
if (!isScheduled) {
|
|
isScheduled = true;
|
|
frame.read(readAllKeyframes);
|
|
frame.resolveKeyframes(measureAllKeyframes);
|
|
}
|
|
}
|
|
else {
|
|
this.readKeyframes();
|
|
this.complete();
|
|
}
|
|
}
|
|
readKeyframes() {
|
|
const { unresolvedKeyframes, name, element, motionValue } = this;
|
|
/**
|
|
* If a keyframe is null, we hydrate it either by reading it from
|
|
* the instance, or propagating from previous keyframes.
|
|
*/
|
|
for (let i = 0; i < unresolvedKeyframes.length; i++) {
|
|
if (unresolvedKeyframes[i] === null) {
|
|
/**
|
|
* If the first keyframe is null, we need to find its value by sampling the element
|
|
*/
|
|
if (i === 0) {
|
|
const currentValue = motionValue === null || motionValue === void 0 ? void 0 : motionValue.get();
|
|
const finalKeyframe = unresolvedKeyframes[unresolvedKeyframes.length - 1];
|
|
if (currentValue !== undefined) {
|
|
unresolvedKeyframes[0] = currentValue;
|
|
}
|
|
else if (element && name) {
|
|
const valueAsRead = element.readValue(name, finalKeyframe);
|
|
if (valueAsRead !== undefined && valueAsRead !== null) {
|
|
unresolvedKeyframes[0] = valueAsRead;
|
|
}
|
|
}
|
|
if (unresolvedKeyframes[0] === undefined) {
|
|
unresolvedKeyframes[0] = finalKeyframe;
|
|
}
|
|
if (motionValue && currentValue === undefined) {
|
|
motionValue.set(unresolvedKeyframes[0]);
|
|
}
|
|
}
|
|
else {
|
|
unresolvedKeyframes[i] = unresolvedKeyframes[i - 1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
setFinalKeyframe() { }
|
|
measureInitialState() { }
|
|
renderEndStyles() { }
|
|
measureEndState() { }
|
|
complete() {
|
|
this.isComplete = true;
|
|
this.onComplete(this.unresolvedKeyframes, this.finalKeyframe);
|
|
toResolve.delete(this);
|
|
}
|
|
cancel() {
|
|
if (!this.isComplete) {
|
|
this.isScheduled = false;
|
|
toResolve.delete(this);
|
|
}
|
|
}
|
|
resume() {
|
|
if (!this.isComplete)
|
|
this.scheduleResolve();
|
|
}
|
|
}
|
|
|
|
export { KeyframeResolver, flushKeyframeResolvers };
|