feat: 氧化铝数字孪生系统监控大屏完成

This commit is contained in:
2026-04-08 21:44:08 +08:00
commit a48babc68d
67606 changed files with 3337335 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
import { observeTimeline } from 'motion-dom';
import { canUseNativeTimeline } from './utils/can-use-native-timeline.mjs';
import { getTimeline } from './utils/get-timeline.mjs';
import { offsetToViewTimelineRange } from './utils/offset-to-range.mjs';
function attachToAnimation(animation, options) {
const timeline = getTimeline(options);
const range = options.target
? offsetToViewTimelineRange(options.offset)
: undefined;
/**
* Use native timeline when:
* - No target: ScrollTimeline (existing behaviour)
* - Target with mappable offset: ViewTimeline with named range
* - Target with unmappable offset: fall back to JS observe
*/
const useNative = options.target
? canUseNativeTimeline(options.target) && !!range
: canUseNativeTimeline();
return animation.attachTimeline({
timeline: useNative ? timeline : undefined,
...(range &&
useNative && {
rangeStart: range.rangeStart,
rangeEnd: range.rangeEnd,
}),
observe: (valueAnimation) => {
valueAnimation.pause();
return observeTimeline((progress) => {
valueAnimation.time =
valueAnimation.iterationDuration * progress;
}, timeline);
},
});
}
export { attachToAnimation };
//# sourceMappingURL=attach-animation.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"attach-animation.mjs","sources":["../../../../../src/render/dom/scroll/attach-animation.ts"],"sourcesContent":["import { AnimationPlaybackControls, observeTimeline } from \"motion-dom\"\nimport { ScrollOptionsWithDefaults } from \"./types\"\nimport { canUseNativeTimeline } from \"./utils/can-use-native-timeline\"\nimport { getTimeline } from \"./utils/get-timeline\"\nimport { offsetToViewTimelineRange } from \"./utils/offset-to-range\"\n\nexport function attachToAnimation(\n animation: AnimationPlaybackControls,\n options: ScrollOptionsWithDefaults\n) {\n const timeline = getTimeline(options)\n\n const range = options.target\n ? offsetToViewTimelineRange(options.offset)\n : undefined\n\n /**\n * Use native timeline when:\n * - No target: ScrollTimeline (existing behaviour)\n * - Target with mappable offset: ViewTimeline with named range\n * - Target with unmappable offset: fall back to JS observe\n */\n const useNative = options.target\n ? canUseNativeTimeline(options.target) && !!range\n : canUseNativeTimeline()\n\n return animation.attachTimeline({\n timeline: useNative ? timeline : undefined,\n ...(range &&\n useNative && {\n rangeStart: range.rangeStart,\n rangeEnd: range.rangeEnd,\n }),\n observe: (valueAnimation) => {\n valueAnimation.pause()\n\n return observeTimeline((progress) => {\n valueAnimation.time =\n valueAnimation.iterationDuration * progress\n }, timeline)\n },\n })\n}\n"],"names":[],"mappings":";;;;;AAMM,SAAU,iBAAiB,CAC7B,SAAoC,EACpC,OAAkC,EAAA;AAElC,IAAA,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC;AAErC,IAAA,MAAM,KAAK,GAAG,OAAO,CAAC;AAClB,UAAE,yBAAyB,CAAC,OAAO,CAAC,MAAM;UACxC,SAAS;AAEf;;;;;AAKG;AACH,IAAA,MAAM,SAAS,GAAG,OAAO,CAAC;UACpB,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;UAC1C,oBAAoB,EAAE;IAE5B,OAAO,SAAS,CAAC,cAAc,CAAC;QAC5B,QAAQ,EAAE,SAAS,GAAG,QAAQ,GAAG,SAAS;AAC1C,QAAA,IAAI,KAAK;AACL,YAAA,SAAS,IAAI;YACT,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;SAC3B,CAAC;AACN,QAAA,OAAO,EAAE,CAAC,cAAc,KAAI;YACxB,cAAc,CAAC,KAAK,EAAE;AAEtB,YAAA,OAAO,eAAe,CAAC,CAAC,QAAQ,KAAI;AAChC,gBAAA,cAAc,CAAC,IAAI;AACf,oBAAA,cAAc,CAAC,iBAAiB,GAAG,QAAQ;YACnD,CAAC,EAAE,QAAQ,CAAC;QAChB,CAAC;AACJ,KAAA,CAAC;AACN;;;;"}

View File

@@ -0,0 +1,24 @@
import { observeTimeline } from 'motion-dom';
import { scrollInfo } from './track.mjs';
import { getTimeline } from './utils/get-timeline.mjs';
/**
* If the onScroll function has two arguments, it's expecting
* more specific information about the scroll from scrollInfo.
*/
function isOnScrollWithInfo(onScroll) {
return onScroll.length === 2;
}
function attachToFunction(onScroll, options) {
if (isOnScrollWithInfo(onScroll)) {
return scrollInfo((info) => {
onScroll(info[options.axis].progress, info);
}, options);
}
else {
return observeTimeline(onScroll, getTimeline(options));
}
}
export { attachToFunction };
//# sourceMappingURL=attach-function.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"attach-function.mjs","sources":["../../../../../src/render/dom/scroll/attach-function.ts"],"sourcesContent":["import { observeTimeline } from \"motion-dom\"\nimport { scrollInfo } from \"./track\"\nimport { OnScroll, OnScrollWithInfo, ScrollOptionsWithDefaults } from \"./types\"\nimport { getTimeline } from \"./utils/get-timeline\"\n\n/**\n * If the onScroll function has two arguments, it's expecting\n * more specific information about the scroll from scrollInfo.\n */\nfunction isOnScrollWithInfo(onScroll: OnScroll): onScroll is OnScrollWithInfo {\n return onScroll.length === 2\n}\n\nexport function attachToFunction(\n onScroll: OnScroll,\n options: ScrollOptionsWithDefaults\n) {\n if (isOnScrollWithInfo(onScroll)) {\n return scrollInfo((info) => {\n onScroll(info[options.axis!].progress, info)\n }, options)\n } else {\n return observeTimeline(onScroll, getTimeline(options))\n }\n}\n"],"names":[],"mappings":";;;;AAKA;;;AAGG;AACH,SAAS,kBAAkB,CAAC,QAAkB,EAAA;AAC1C,IAAA,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC;AAChC;AAEM,SAAU,gBAAgB,CAC5B,QAAkB,EAClB,OAAkC,EAAA;AAElC,IAAA,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE;AAC9B,QAAA,OAAO,UAAU,CAAC,CAAC,IAAI,KAAI;AACvB,YAAA,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,IAAK,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC;QAChD,CAAC,EAAE,OAAO,CAAC;IACf;SAAO;QACH,OAAO,eAAe,CAAC,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IAC1D;AACJ;;;;"}

View File

@@ -0,0 +1,15 @@
import { noop } from 'motion-utils';
import { attachToAnimation } from './attach-animation.mjs';
import { attachToFunction } from './attach-function.mjs';
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
if (!container)
return noop;
const optionsWithDefaults = { axis, container, ...options };
return typeof onScroll === "function"
? attachToFunction(onScroll, optionsWithDefaults)
: attachToAnimation(onScroll, optionsWithDefaults);
}
export { scroll };
//# sourceMappingURL=index.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.mjs","sources":["../../../../../src/render/dom/scroll/index.ts"],"sourcesContent":["import { AnimationPlaybackControls } from \"motion-dom\"\nimport { noop } from \"motion-utils\"\nimport { attachToAnimation } from \"./attach-animation\"\nimport { attachToFunction } from \"./attach-function\"\nimport { OnScroll, ScrollOptions } from \"./types\"\n\nexport function scroll(\n onScroll: OnScroll | AnimationPlaybackControls,\n {\n axis = \"y\",\n container = document.scrollingElement as Element,\n ...options\n }: ScrollOptions = {}\n): VoidFunction {\n if (!container) return noop as VoidFunction\n\n const optionsWithDefaults = { axis, container, ...options }\n\n return typeof onScroll === \"function\"\n ? attachToFunction(onScroll, optionsWithDefaults)\n : attachToAnimation(onScroll, optionsWithDefaults)\n}\n"],"names":[],"mappings":";;;;SAMgB,MAAM,CAClB,QAA8C,EAC9C,EACI,IAAI,GAAG,GAAG,EACV,SAAS,GAAG,QAAQ,CAAC,gBAA2B,EAChD,GAAG,OAAO,KACK,EAAE,EAAA;AAErB,IAAA,IAAI,CAAC,SAAS;AAAE,QAAA,OAAO,IAAoB;IAE3C,MAAM,mBAAmB,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,OAAO,EAAE;IAE3D,OAAO,OAAO,QAAQ,KAAK;AACvB,UAAE,gBAAgB,CAAC,QAAQ,EAAE,mBAAmB;AAChD,UAAE,iBAAiB,CAAC,QAAQ,EAAE,mBAAmB,CAAC;AAC1D;;;;"}

View File

@@ -0,0 +1,56 @@
import { progress, velocityPerSecond } from 'motion-utils';
/**
* A time in milliseconds, beyond which we consider the scroll velocity to be 0.
*/
const maxElapsed = 50;
const createAxisInfo = () => ({
current: 0,
offset: [],
progress: 0,
scrollLength: 0,
targetOffset: 0,
targetLength: 0,
containerLength: 0,
velocity: 0,
});
const createScrollInfo = () => ({
time: 0,
x: createAxisInfo(),
y: createAxisInfo(),
});
const keys = {
x: {
length: "Width",
position: "Left",
},
y: {
length: "Height",
position: "Top",
},
};
function updateAxisInfo(element, axisName, info, time) {
const axis = info[axisName];
const { length, position } = keys[axisName];
const prev = axis.current;
const prevTime = info.time;
axis.current = Math.abs(element[`scroll${position}`]);
axis.scrollLength = element[`scroll${length}`] - element[`client${length}`];
axis.offset.length = 0;
axis.offset[0] = 0;
axis.offset[1] = axis.scrollLength;
axis.progress = progress(0, axis.scrollLength, axis.current);
const elapsed = time - prevTime;
axis.velocity =
elapsed > maxElapsed
? 0
: velocityPerSecond(axis.current - prev, elapsed);
}
function updateScrollInfo(element, info, time) {
updateAxisInfo(element, "x", info, time);
updateAxisInfo(element, "y", info, time);
info.time = time;
}
export { createScrollInfo, updateScrollInfo };
//# sourceMappingURL=info.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"info.mjs","sources":["../../../../../src/render/dom/scroll/info.ts"],"sourcesContent":["import { progress, velocityPerSecond } from \"motion-utils\"\nimport { AxisScrollInfo, ScrollInfo } from \"./types\"\n\n/**\n * A time in milliseconds, beyond which we consider the scroll velocity to be 0.\n */\nconst maxElapsed = 50\n\nconst createAxisInfo = (): AxisScrollInfo => ({\n current: 0,\n offset: [],\n progress: 0,\n scrollLength: 0,\n targetOffset: 0,\n targetLength: 0,\n containerLength: 0,\n velocity: 0,\n})\n\nexport const createScrollInfo = (): ScrollInfo => ({\n time: 0,\n x: createAxisInfo(),\n y: createAxisInfo(),\n})\n\nconst keys = {\n x: {\n length: \"Width\",\n position: \"Left\",\n },\n y: {\n length: \"Height\",\n position: \"Top\",\n },\n} as const\n\nfunction updateAxisInfo(\n element: Element,\n axisName: \"x\" | \"y\",\n info: ScrollInfo,\n time: number\n) {\n const axis = info[axisName]\n const { length, position } = keys[axisName]\n\n const prev = axis.current\n const prevTime = info.time\n\n axis.current = Math.abs(element[`scroll${position}`])\n axis.scrollLength = element[`scroll${length}`] - element[`client${length}`]\n\n axis.offset.length = 0\n axis.offset[0] = 0\n axis.offset[1] = axis.scrollLength\n axis.progress = progress(0, axis.scrollLength, axis.current)\n\n const elapsed = time - prevTime\n axis.velocity =\n elapsed > maxElapsed\n ? 0\n : velocityPerSecond(axis.current - prev, elapsed)\n}\n\nexport function updateScrollInfo(\n element: Element,\n info: ScrollInfo,\n time: number\n) {\n updateAxisInfo(element, \"x\", info, time)\n updateAxisInfo(element, \"y\", info, time)\n info.time = time\n}\n"],"names":[],"mappings":";;AAGA;;AAEG;AACH,MAAM,UAAU,GAAG,EAAE;AAErB,MAAM,cAAc,GAAG,OAAuB;AAC1C,IAAA,OAAO,EAAE,CAAC;AACV,IAAA,MAAM,EAAE,EAAE;AACV,IAAA,QAAQ,EAAE,CAAC;AACX,IAAA,YAAY,EAAE,CAAC;AACf,IAAA,YAAY,EAAE,CAAC;AACf,IAAA,YAAY,EAAE,CAAC;AACf,IAAA,eAAe,EAAE,CAAC;AAClB,IAAA,QAAQ,EAAE,CAAC;AACd,CAAA,CAAC;AAEK,MAAM,gBAAgB,GAAG,OAAmB;AAC/C,IAAA,IAAI,EAAE,CAAC;IACP,CAAC,EAAE,cAAc,EAAE;IACnB,CAAC,EAAE,cAAc,EAAE;AACtB,CAAA;AAED,MAAM,IAAI,GAAG;AACT,IAAA,CAAC,EAAE;AACC,QAAA,MAAM,EAAE,OAAO;AACf,QAAA,QAAQ,EAAE,MAAM;AACnB,KAAA;AACD,IAAA,CAAC,EAAE;AACC,QAAA,MAAM,EAAE,QAAQ;AAChB,QAAA,QAAQ,EAAE,KAAK;AAClB,KAAA;CACK;AAEV,SAAS,cAAc,CACnB,OAAgB,EAChB,QAAmB,EACnB,IAAgB,EAChB,IAAY,EAAA;AAEZ,IAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC3B,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC;AAE3C,IAAA,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO;AACzB,IAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI;AAE1B,IAAA,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA,MAAA,EAAS,QAAQ,CAAA,CAAE,CAAC,CAAC;AACrD,IAAA,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,SAAS,MAAM,CAAA,CAAE,CAAC,GAAG,OAAO,CAAC,CAAA,MAAA,EAAS,MAAM,CAAA,CAAE,CAAC;AAE3E,IAAA,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;AACtB,IAAA,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC;IAClB,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,YAAY;AAClC,IAAA,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,OAAO,CAAC;AAE5D,IAAA,MAAM,OAAO,GAAG,IAAI,GAAG,QAAQ;AAC/B,IAAA,IAAI,CAAC,QAAQ;AACT,QAAA,OAAO,GAAG;AACN,cAAE;cACA,iBAAiB,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;AAC7D;SAEgB,gBAAgB,CAC5B,OAAgB,EAChB,IAAgB,EAChB,IAAY,EAAA;IAEZ,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;IACxC,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;AACxC,IAAA,IAAI,CAAC,IAAI,GAAG,IAAI;AACpB;;;;"}

View File

@@ -0,0 +1,46 @@
const namedEdges = {
start: 0,
center: 0.5,
end: 1,
};
function resolveEdge(edge, length, inset = 0) {
let delta = 0;
/**
* If we have this edge defined as a preset, replace the definition
* with the numerical value.
*/
if (edge in namedEdges) {
edge = namedEdges[edge];
}
/**
* Handle unit values
*/
if (typeof edge === "string") {
const asNumber = parseFloat(edge);
if (edge.endsWith("px")) {
delta = asNumber;
}
else if (edge.endsWith("%")) {
edge = asNumber / 100;
}
else if (edge.endsWith("vw")) {
delta = (asNumber / 100) * document.documentElement.clientWidth;
}
else if (edge.endsWith("vh")) {
delta = (asNumber / 100) * document.documentElement.clientHeight;
}
else {
edge = asNumber;
}
}
/**
* If the edge is defined as a number, handle as a progress value.
*/
if (typeof edge === "number") {
delta = length * edge;
}
return inset + delta;
}
export { namedEdges, resolveEdge };
//# sourceMappingURL=edge.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"edge.mjs","sources":["../../../../../../src/render/dom/scroll/offsets/edge.ts"],"sourcesContent":["import { Edge, NamedEdges } from \"../types\"\n\nexport const namedEdges: Record<NamedEdges, number> = {\n start: 0,\n center: 0.5,\n end: 1,\n}\n\nexport function resolveEdge(edge: Edge, length: number, inset = 0) {\n let delta = 0\n\n /**\n * If we have this edge defined as a preset, replace the definition\n * with the numerical value.\n */\n if (edge in namedEdges) {\n edge = namedEdges[edge as NamedEdges]\n }\n\n /**\n * Handle unit values\n */\n if (typeof edge === \"string\") {\n const asNumber = parseFloat(edge)\n\n if (edge.endsWith(\"px\")) {\n delta = asNumber\n } else if (edge.endsWith(\"%\")) {\n edge = asNumber / 100\n } else if (edge.endsWith(\"vw\")) {\n delta = (asNumber / 100) * document.documentElement.clientWidth\n } else if (edge.endsWith(\"vh\")) {\n delta = (asNumber / 100) * document.documentElement.clientHeight\n } else {\n edge = asNumber\n }\n }\n\n /**\n * If the edge is defined as a number, handle as a progress value.\n */\n if (typeof edge === \"number\") {\n delta = length * edge\n }\n\n return inset + delta\n}\n"],"names":[],"mappings":"AAEO,MAAM,UAAU,GAA+B;AAClD,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,MAAM,EAAE,GAAG;AACX,IAAA,GAAG,EAAE,CAAC;;AAGJ,SAAU,WAAW,CAAC,IAAU,EAAE,MAAc,EAAE,KAAK,GAAG,CAAC,EAAA;IAC7D,IAAI,KAAK,GAAG,CAAC;AAEb;;;AAGG;AACH,IAAA,IAAI,IAAI,IAAI,UAAU,EAAE;AACpB,QAAA,IAAI,GAAG,UAAU,CAAC,IAAkB,CAAC;IACzC;AAEA;;AAEG;AACH,IAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AAC1B,QAAA,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC;AAEjC,QAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;YACrB,KAAK,GAAG,QAAQ;QACpB;AAAO,aAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AAC3B,YAAA,IAAI,GAAG,QAAQ,GAAG,GAAG;QACzB;AAAO,aAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AAC5B,YAAA,KAAK,GAAG,CAAC,QAAQ,GAAG,GAAG,IAAI,QAAQ,CAAC,eAAe,CAAC,WAAW;QACnE;AAAO,aAAA,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE;AAC5B,YAAA,KAAK,GAAG,CAAC,QAAQ,GAAG,GAAG,IAAI,QAAQ,CAAC,eAAe,CAAC,YAAY;QACpE;aAAO;YACH,IAAI,GAAG,QAAQ;QACnB;IACJ;AAEA;;AAEG;AACH,IAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AAC1B,QAAA,KAAK,GAAG,MAAM,GAAG,IAAI;IACzB;IAEA,OAAO,KAAK,GAAG,KAAK;AACxB;;;;"}

View File

@@ -0,0 +1,60 @@
import { interpolate, defaultOffset } from 'motion-dom';
import { clamp } from 'motion-utils';
import { calcInset } from './inset.mjs';
import { resolveOffset } from './offset.mjs';
import { ScrollOffset } from './presets.mjs';
const point = { x: 0, y: 0 };
function getTargetSize(target) {
return "getBBox" in target && target.tagName !== "svg"
? target.getBBox()
: { width: target.clientWidth, height: target.clientHeight };
}
function resolveOffsets(container, info, options) {
const { offset: offsetDefinition = ScrollOffset.All } = options;
const { target = container, axis = "y" } = options;
const lengthLabel = axis === "y" ? "height" : "width";
const inset = target !== container ? calcInset(target, container) : point;
/**
* Measure the target and container. If they're the same thing then we
* use the container's scrollWidth/Height as the target, from there
* all other calculations can remain the same.
*/
const targetSize = target === container
? { width: container.scrollWidth, height: container.scrollHeight }
: getTargetSize(target);
const containerSize = {
width: container.clientWidth,
height: container.clientHeight,
};
/**
* Reset the length of the resolved offset array rather than creating a new one.
* TODO: More reusable data structures for targetSize/containerSize would also be good.
*/
info[axis].offset.length = 0;
/**
* Populate the offset array by resolving the user's offset definition into
* a list of pixel scroll offets.
*/
let hasChanged = !info[axis].interpolate;
const numOffsets = offsetDefinition.length;
for (let i = 0; i < numOffsets; i++) {
const offset = resolveOffset(offsetDefinition[i], containerSize[lengthLabel], targetSize[lengthLabel], inset[axis]);
if (!hasChanged && offset !== info[axis].interpolatorOffsets[i]) {
hasChanged = true;
}
info[axis].offset[i] = offset;
}
/**
* If the pixel scroll offsets have changed, create a new interpolator function
* to map scroll value into a progress.
*/
if (hasChanged) {
info[axis].interpolate = interpolate(info[axis].offset, defaultOffset(offsetDefinition), { clamp: false });
info[axis].interpolatorOffsets = [...info[axis].offset];
}
info[axis].progress = clamp(0, 1, info[axis].interpolate(info[axis].current));
}
export { resolveOffsets };
//# sourceMappingURL=index.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.mjs","sources":["../../../../../../src/render/dom/scroll/offsets/index.ts"],"sourcesContent":["import { defaultOffset, interpolate } from \"motion-dom\"\nimport { clamp } from \"motion-utils\"\nimport { ScrollInfo, ScrollInfoOptions } from \"../types\"\nimport { calcInset } from \"./inset\"\nimport { resolveOffset } from \"./offset\"\nimport { ScrollOffset } from \"./presets\"\n\nconst point = { x: 0, y: 0 }\n\nfunction getTargetSize(target: Element) {\n return \"getBBox\" in target && target.tagName !== \"svg\"\n ? (target as SVGGraphicsElement).getBBox()\n : { width: target.clientWidth, height: target.clientHeight }\n}\n\nexport function resolveOffsets(\n container: Element,\n info: ScrollInfo,\n options: ScrollInfoOptions\n) {\n const { offset: offsetDefinition = ScrollOffset.All } = options\n const { target = container, axis = \"y\" } = options\n const lengthLabel = axis === \"y\" ? \"height\" : \"width\"\n\n const inset = target !== container ? calcInset(target, container) : point\n\n /**\n * Measure the target and container. If they're the same thing then we\n * use the container's scrollWidth/Height as the target, from there\n * all other calculations can remain the same.\n */\n const targetSize =\n target === container\n ? { width: container.scrollWidth, height: container.scrollHeight }\n : getTargetSize(target)\n\n const containerSize = {\n width: container.clientWidth,\n height: container.clientHeight,\n }\n\n /**\n * Reset the length of the resolved offset array rather than creating a new one.\n * TODO: More reusable data structures for targetSize/containerSize would also be good.\n */\n info[axis].offset.length = 0\n\n /**\n * Populate the offset array by resolving the user's offset definition into\n * a list of pixel scroll offets.\n */\n let hasChanged = !info[axis].interpolate\n\n const numOffsets = offsetDefinition.length\n for (let i = 0; i < numOffsets; i++) {\n const offset = resolveOffset(\n offsetDefinition[i],\n containerSize[lengthLabel],\n targetSize[lengthLabel],\n inset[axis]\n )\n\n if (!hasChanged && offset !== info[axis].interpolatorOffsets![i]) {\n hasChanged = true\n }\n\n info[axis].offset[i] = offset\n }\n\n /**\n * If the pixel scroll offsets have changed, create a new interpolator function\n * to map scroll value into a progress.\n */\n if (hasChanged) {\n info[axis].interpolate = interpolate(\n info[axis].offset,\n defaultOffset(offsetDefinition),\n { clamp: false }\n )\n\n info[axis].interpolatorOffsets = [...info[axis].offset]\n }\n\n info[axis].progress = clamp(\n 0,\n 1,\n info[axis].interpolate!(info[axis].current)\n )\n}\n"],"names":[],"mappings":";;;;;;AAOA,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAE5B,SAAS,aAAa,CAAC,MAAe,EAAA;IAClC,OAAO,SAAS,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK;AAC7C,UAAG,MAA6B,CAAC,OAAO;AACxC,UAAE,EAAE,KAAK,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,CAAC,YAAY,EAAE;AACpE;SAEgB,cAAc,CAC1B,SAAkB,EAClB,IAAgB,EAChB,OAA0B,EAAA;IAE1B,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,OAAO;IAC/D,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,GAAG,GAAG,EAAE,GAAG,OAAO;AAClD,IAAA,MAAM,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,QAAQ,GAAG,OAAO;AAErD,IAAA,MAAM,KAAK,GAAG,MAAM,KAAK,SAAS,GAAG,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,KAAK;AAEzE;;;;AAIG;AACH,IAAA,MAAM,UAAU,GACZ,MAAM,KAAK;AACP,UAAE,EAAE,KAAK,EAAE,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,SAAS,CAAC,YAAY;AAChE,UAAE,aAAa,CAAC,MAAM,CAAC;AAE/B,IAAA,MAAM,aAAa,GAAG;QAClB,KAAK,EAAE,SAAS,CAAC,WAAW;QAC5B,MAAM,EAAE,SAAS,CAAC,YAAY;KACjC;AAED;;;AAGG;IACH,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;AAE5B;;;AAGG;IACH,IAAI,UAAU,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW;AAExC,IAAA,MAAM,UAAU,GAAG,gBAAgB,CAAC,MAAM;AAC1C,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;QACjC,MAAM,MAAM,GAAG,aAAa,CACxB,gBAAgB,CAAC,CAAC,CAAC,EACnB,aAAa,CAAC,WAAW,CAAC,EAC1B,UAAU,CAAC,WAAW,CAAC,EACvB,KAAK,CAAC,IAAI,CAAC,CACd;AAED,QAAA,IAAI,CAAC,UAAU,IAAI,MAAM,KAAK,IAAI,CAAC,IAAI,CAAC,CAAC,mBAAoB,CAAC,CAAC,CAAC,EAAE;YAC9D,UAAU,GAAG,IAAI;QACrB;QAEA,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM;IACjC;AAEA;;;AAGG;IACH,IAAI,UAAU,EAAE;QACZ,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,GAAG,WAAW,CAChC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EACjB,aAAa,CAAC,gBAAgB,CAAC,EAC/B,EAAE,KAAK,EAAE,KAAK,EAAE,CACnB;AAED,QAAA,IAAI,CAAC,IAAI,CAAC,CAAC,mBAAmB,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IAC3D;IAEA,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,GAAG,KAAK,CACvB,CAAC,EACD,CAAC,EACD,IAAI,CAAC,IAAI,CAAC,CAAC,WAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAC9C;AACL;;;;"}

View File

@@ -0,0 +1,48 @@
import { isHTMLElement } from 'motion-dom';
function calcInset(element, container) {
const inset = { x: 0, y: 0 };
let current = element;
while (current && current !== container) {
if (isHTMLElement(current)) {
inset.x += current.offsetLeft;
inset.y += current.offsetTop;
current = current.offsetParent;
}
else if (current.tagName === "svg") {
/**
* This isn't an ideal approach to measuring the offset of <svg /> tags.
* It would be preferable, given they behave like HTMLElements in most ways
* to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
* can't use .getBBox() like most SVG elements as these provide the offset
* relative to the SVG itself, which for <svg /> is usually 0x0.
*/
const svgBoundingBox = current.getBoundingClientRect();
current = current.parentElement;
const parentBoundingBox = current.getBoundingClientRect();
inset.x += svgBoundingBox.left - parentBoundingBox.left;
inset.y += svgBoundingBox.top - parentBoundingBox.top;
}
else if (current instanceof SVGGraphicsElement) {
const { x, y } = current.getBBox();
inset.x += x;
inset.y += y;
let svg = null;
let parent = current.parentNode;
while (!svg) {
if (parent.tagName === "svg") {
svg = parent;
}
parent = current.parentNode;
}
current = svg;
}
else {
break;
}
}
return inset;
}
export { calcInset };
//# sourceMappingURL=inset.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"inset.mjs","sources":["../../../../../../src/render/dom/scroll/offsets/inset.ts"],"sourcesContent":["import { isHTMLElement } from \"motion-dom\"\n\nexport function calcInset(element: Element, container: Element) {\n const inset = { x: 0, y: 0 }\n\n let current: Element | null = element\n while (current && current !== container) {\n if (isHTMLElement(current)) {\n inset.x += current.offsetLeft\n inset.y += current.offsetTop\n current = current.offsetParent\n } else if (current.tagName === \"svg\") {\n /**\n * This isn't an ideal approach to measuring the offset of <svg /> tags.\n * It would be preferable, given they behave like HTMLElements in most ways\n * to use offsetLeft/Top. But these don't exist on <svg />. Likewise we\n * can't use .getBBox() like most SVG elements as these provide the offset\n * relative to the SVG itself, which for <svg /> is usually 0x0.\n */\n const svgBoundingBox = current.getBoundingClientRect()\n current = current.parentElement!\n const parentBoundingBox = current.getBoundingClientRect()\n inset.x += svgBoundingBox.left - parentBoundingBox.left\n inset.y += svgBoundingBox.top - parentBoundingBox.top\n } else if (current instanceof SVGGraphicsElement) {\n const { x, y } = current.getBBox()\n inset.x += x\n inset.y += y\n\n let svg: SVGElement | null = null\n let parent: SVGElement = current.parentNode as SVGElement\n while (!svg) {\n if (parent.tagName === \"svg\") {\n svg = parent\n }\n parent = current.parentNode as SVGElement\n }\n current = svg\n } else {\n break\n }\n }\n\n return inset\n}\n"],"names":[],"mappings":";;AAEM,SAAU,SAAS,CAAC,OAAgB,EAAE,SAAkB,EAAA;IAC1D,MAAM,KAAK,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;IAE5B,IAAI,OAAO,GAAmB,OAAO;AACrC,IAAA,OAAO,OAAO,IAAI,OAAO,KAAK,SAAS,EAAE;AACrC,QAAA,IAAI,aAAa,CAAC,OAAO,CAAC,EAAE;AACxB,YAAA,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,UAAU;AAC7B,YAAA,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,SAAS;AAC5B,YAAA,OAAO,GAAG,OAAO,CAAC,YAAY;QAClC;AAAO,aAAA,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK,EAAE;AAClC;;;;;;AAMG;AACH,YAAA,MAAM,cAAc,GAAG,OAAO,CAAC,qBAAqB,EAAE;AACtD,YAAA,OAAO,GAAG,OAAO,CAAC,aAAc;AAChC,YAAA,MAAM,iBAAiB,GAAG,OAAO,CAAC,qBAAqB,EAAE;YACzD,KAAK,CAAC,CAAC,IAAI,cAAc,CAAC,IAAI,GAAG,iBAAiB,CAAC,IAAI;YACvD,KAAK,CAAC,CAAC,IAAI,cAAc,CAAC,GAAG,GAAG,iBAAiB,CAAC,GAAG;QACzD;AAAO,aAAA,IAAI,OAAO,YAAY,kBAAkB,EAAE;YAC9C,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,OAAO,CAAC,OAAO,EAAE;AAClC,YAAA,KAAK,CAAC,CAAC,IAAI,CAAC;AACZ,YAAA,KAAK,CAAC,CAAC,IAAI,CAAC;YAEZ,IAAI,GAAG,GAAsB,IAAI;AACjC,YAAA,IAAI,MAAM,GAAe,OAAO,CAAC,UAAwB;YACzD,OAAO,CAAC,GAAG,EAAE;AACT,gBAAA,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE;oBAC1B,GAAG,GAAG,MAAM;gBAChB;AACA,gBAAA,MAAM,GAAG,OAAO,CAAC,UAAwB;YAC7C;YACA,OAAO,GAAG,GAAG;QACjB;aAAO;YACH;QACJ;IACJ;AAEA,IAAA,OAAO,KAAK;AAChB;;;;"}

View File

@@ -0,0 +1,36 @@
import { resolveEdge, namedEdges } from './edge.mjs';
const defaultOffset = [0, 0];
function resolveOffset(offset, containerLength, targetLength, targetInset) {
let offsetDefinition = Array.isArray(offset) ? offset : defaultOffset;
let targetPoint = 0;
let containerPoint = 0;
if (typeof offset === "number") {
/**
* If we're provided offset: [0, 0.5, 1] then each number x should become
* [x, x], so we default to the behaviour of mapping 0 => 0 of both target
* and container etc.
*/
offsetDefinition = [offset, offset];
}
else if (typeof offset === "string") {
offset = offset.trim();
if (offset.includes(" ")) {
offsetDefinition = offset.split(" ");
}
else {
/**
* If we're provided a definition like "100px" then we want to apply
* that only to the top of the target point, leaving the container at 0.
* Whereas a named offset like "end" should be applied to both.
*/
offsetDefinition = [offset, namedEdges[offset] ? offset : `0`];
}
}
targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset);
containerPoint = resolveEdge(offsetDefinition[1], containerLength);
return targetPoint - containerPoint;
}
export { resolveOffset };
//# sourceMappingURL=offset.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"offset.mjs","sources":["../../../../../../src/render/dom/scroll/offsets/offset.ts"],"sourcesContent":["import { Edge, EdgeString, Intersection, ProgressIntersection } from \"../types\"\nimport { namedEdges, resolveEdge } from \"./edge\"\n\nconst defaultOffset: ProgressIntersection = [0, 0]\n\nexport function resolveOffset(\n offset: Edge | Intersection | ProgressIntersection,\n containerLength: number,\n targetLength: number,\n targetInset: number\n) {\n let offsetDefinition: ProgressIntersection | [EdgeString, EdgeString] =\n Array.isArray(offset) ? offset : defaultOffset\n\n let targetPoint = 0\n let containerPoint = 0\n\n if (typeof offset === \"number\") {\n /**\n * If we're provided offset: [0, 0.5, 1] then each number x should become\n * [x, x], so we default to the behaviour of mapping 0 => 0 of both target\n * and container etc.\n */\n offsetDefinition = [offset, offset]\n } else if (typeof offset === \"string\") {\n offset = offset.trim() as EdgeString\n\n if (offset.includes(\" \")) {\n offsetDefinition = offset.split(\" \") as [EdgeString, EdgeString]\n } else {\n /**\n * If we're provided a definition like \"100px\" then we want to apply\n * that only to the top of the target point, leaving the container at 0.\n * Whereas a named offset like \"end\" should be applied to both.\n */\n offsetDefinition = [offset, namedEdges[offset as keyof typeof namedEdges] ? offset : `0`]\n }\n }\n\n targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset)\n containerPoint = resolveEdge(offsetDefinition[1], containerLength)\n\n return targetPoint - containerPoint\n}\n"],"names":[],"mappings":";;AAGA,MAAM,aAAa,GAAyB,CAAC,CAAC,EAAE,CAAC,CAAC;AAE5C,SAAU,aAAa,CACzB,MAAkD,EAClD,eAAuB,EACvB,YAAoB,EACpB,WAAmB,EAAA;AAEnB,IAAA,IAAI,gBAAgB,GAChB,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,aAAa;IAElD,IAAI,WAAW,GAAG,CAAC;IACnB,IAAI,cAAc,GAAG,CAAC;AAEtB,IAAA,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC5B;;;;AAIG;AACH,QAAA,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;IACvC;AAAO,SAAA,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AACnC,QAAA,MAAM,GAAG,MAAM,CAAC,IAAI,EAAgB;AAEpC,QAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE;AACtB,YAAA,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAA6B;QACpE;aAAO;AACH;;;;AAIG;AACH,YAAA,gBAAgB,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,MAAiC,CAAC,GAAG,MAAM,GAAG,CAAA,CAAA,CAAG,CAAC;QAC7F;IACJ;AAEA,IAAA,WAAW,GAAG,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,CAAC;IACzE,cAAc,GAAG,WAAW,CAAC,gBAAgB,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC;IAElE,OAAO,WAAW,GAAG,cAAc;AACvC;;;;"}

View File

@@ -0,0 +1,21 @@
const ScrollOffset = {
Enter: [
[0, 1],
[1, 1],
],
Exit: [
[0, 0],
[1, 0],
],
Any: [
[1, 0],
[0, 1],
],
All: [
[0, 0],
[1, 1],
],
};
export { ScrollOffset };
//# sourceMappingURL=presets.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"presets.mjs","sources":["../../../../../../src/render/dom/scroll/offsets/presets.ts"],"sourcesContent":["import { ProgressIntersection } from \"../types\"\n\nexport const ScrollOffset: Record<string, ProgressIntersection[]> = {\n Enter: [\n [0, 1],\n [1, 1],\n ],\n Exit: [\n [0, 0],\n [1, 0],\n ],\n Any: [\n [1, 0],\n [0, 1],\n ],\n All: [\n [0, 0],\n [1, 1],\n ],\n}\n"],"names":[],"mappings":"AAEO,MAAM,YAAY,GAA2C;AAClE,IAAA,KAAK,EAAE;QACL,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,CAAC,CAAC,EAAE,CAAC,CAAC;AACP,KAAA;AACD,IAAA,IAAI,EAAE;QACJ,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,CAAC,CAAC,EAAE,CAAC,CAAC;AACP,KAAA;AACD,IAAA,GAAG,EAAE;QACH,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,CAAC,CAAC,EAAE,CAAC,CAAC;AACP,KAAA;AACD,IAAA,GAAG,EAAE;QACH,CAAC,CAAC,EAAE,CAAC,CAAC;QACN,CAAC,CAAC,EAAE,CAAC,CAAC;AACP,KAAA;;;;;"}

View File

@@ -0,0 +1,49 @@
import { warnOnce } from 'motion-utils';
import { updateScrollInfo } from './info.mjs';
import { resolveOffsets } from './offsets/index.mjs';
function measure(container, target = container, info) {
/**
* Find inset of target within scrollable container
*/
info.x.targetOffset = 0;
info.y.targetOffset = 0;
if (target !== container) {
let node = target;
while (node && node !== container) {
info.x.targetOffset += node.offsetLeft;
info.y.targetOffset += node.offsetTop;
node = node.offsetParent;
}
}
info.x.targetLength =
target === container ? target.scrollWidth : target.clientWidth;
info.y.targetLength =
target === container ? target.scrollHeight : target.clientHeight;
info.x.containerLength = container.clientWidth;
info.y.containerLength = container.clientHeight;
/**
* In development mode ensure scroll containers aren't position: static as this makes
* it difficult to measure their relative positions.
*/
if (process.env.NODE_ENV !== "production") {
if (container && target && target !== container) {
warnOnce(getComputedStyle(container).position !== "static", "Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly.");
}
}
}
function createOnScrollHandler(element, onScroll, info, options = {}) {
return {
measure: (time) => {
measure(element, options.target, info);
updateScrollInfo(element, info, time);
if (options.offset || options.target) {
resolveOffsets(element, info, options);
}
},
notify: () => onScroll(info),
};
}
export { createOnScrollHandler };
//# sourceMappingURL=on-scroll-handler.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"on-scroll-handler.mjs","sources":["../../../../../src/render/dom/scroll/on-scroll-handler.ts"],"sourcesContent":["import { warnOnce } from \"motion-utils\"\nimport { updateScrollInfo } from \"./info\"\nimport { resolveOffsets } from \"./offsets/index\"\nimport {\n OnScrollHandler,\n OnScrollInfo,\n ScrollInfo,\n ScrollInfoOptions,\n} from \"./types\"\n\nfunction measure(\n container: Element,\n target: Element = container,\n info: ScrollInfo\n) {\n /**\n * Find inset of target within scrollable container\n */\n info.x.targetOffset = 0\n info.y.targetOffset = 0\n if (target !== container) {\n let node = target as HTMLElement\n while (node && node !== container) {\n info.x.targetOffset += node.offsetLeft\n info.y.targetOffset += node.offsetTop\n node = node.offsetParent as HTMLElement\n }\n }\n\n info.x.targetLength =\n target === container ? target.scrollWidth : target.clientWidth\n info.y.targetLength =\n target === container ? target.scrollHeight : target.clientHeight\n info.x.containerLength = container.clientWidth\n info.y.containerLength = container.clientHeight\n\n /**\n * In development mode ensure scroll containers aren't position: static as this makes\n * it difficult to measure their relative positions.\n */\n if (process.env.NODE_ENV !== \"production\") {\n if (container && target && target !== container) {\n warnOnce(\n getComputedStyle(container).position !== \"static\",\n \"Please ensure that the container has a non-static position, like 'relative', 'fixed', or 'absolute' to ensure scroll offset is calculated correctly.\"\n )\n }\n }\n}\n\nexport function createOnScrollHandler(\n element: Element,\n onScroll: OnScrollInfo,\n info: ScrollInfo,\n options: ScrollInfoOptions = {}\n): OnScrollHandler {\n return {\n measure: (time) => {\n measure(element, options.target, info)\n updateScrollInfo(element, info, time)\n\n if (options.offset || options.target) {\n resolveOffsets(element, info, options)\n }\n },\n notify: () => onScroll(info),\n }\n}\n"],"names":[],"mappings":";;;;AAUA,SAAS,OAAO,CACZ,SAAkB,EAClB,MAAA,GAAkB,SAAS,EAC3B,IAAgB,EAAA;AAEhB;;AAEG;AACH,IAAA,IAAI,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC;AACvB,IAAA,IAAI,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC;AACvB,IAAA,IAAI,MAAM,KAAK,SAAS,EAAE;QACtB,IAAI,IAAI,GAAG,MAAqB;AAChC,QAAA,OAAO,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE;YAC/B,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,UAAU;YACtC,IAAI,CAAC,CAAC,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS;AACrC,YAAA,IAAI,GAAG,IAAI,CAAC,YAA2B;QAC3C;IACJ;IAEA,IAAI,CAAC,CAAC,CAAC,YAAY;AACf,QAAA,MAAM,KAAK,SAAS,GAAG,MAAM,CAAC,WAAW,GAAG,MAAM,CAAC,WAAW;IAClE,IAAI,CAAC,CAAC,CAAC,YAAY;AACf,QAAA,MAAM,KAAK,SAAS,GAAG,MAAM,CAAC,YAAY,GAAG,MAAM,CAAC,YAAY;IACpE,IAAI,CAAC,CAAC,CAAC,eAAe,GAAG,SAAS,CAAC,WAAW;IAC9C,IAAI,CAAC,CAAC,CAAC,eAAe,GAAG,SAAS,CAAC,YAAY;AAE/C;;;AAGG;IACH,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,EAAE;QACvC,IAAI,SAAS,IAAI,MAAM,IAAI,MAAM,KAAK,SAAS,EAAE;AAC7C,YAAA,QAAQ,CACJ,gBAAgB,CAAC,SAAS,CAAC,CAAC,QAAQ,KAAK,QAAQ,EACjD,sJAAsJ,CACzJ;QACL;IACJ;AACJ;AAEM,SAAU,qBAAqB,CACjC,OAAgB,EAChB,QAAsB,EACtB,IAAgB,EAChB,OAAA,GAA6B,EAAE,EAAA;IAE/B,OAAO;AACH,QAAA,OAAO,EAAE,CAAC,IAAI,KAAI;YACd,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;AACtC,YAAA,gBAAgB,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC;YAErC,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE;AAClC,gBAAA,cAAc,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC;YAC1C;QACJ,CAAC;AACD,QAAA,MAAM,EAAE,MAAM,QAAQ,CAAC,IAAI,CAAC;KAC/B;AACL;;;;"}

View File

@@ -0,0 +1,115 @@
import { resize, frame, cancelFrame, frameData } from 'motion-dom';
import { noop } from 'motion-utils';
import { createScrollInfo } from './info.mjs';
import { createOnScrollHandler } from './on-scroll-handler.mjs';
const scrollListeners = new WeakMap();
const resizeListeners = new WeakMap();
const onScrollHandlers = new WeakMap();
const scrollSize = new WeakMap();
const dimensionCheckProcesses = new WeakMap();
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
if (!container)
return noop;
let containerHandlers = onScrollHandlers.get(container);
/**
* Get the onScroll handlers for this container.
* If one isn't found, create a new one.
*/
if (!containerHandlers) {
containerHandlers = new Set();
onScrollHandlers.set(container, containerHandlers);
}
/**
* Create a new onScroll handler for the provided callback.
*/
const info = createScrollInfo();
const containerHandler = createOnScrollHandler(container, onScroll, info, options);
containerHandlers.add(containerHandler);
/**
* Check if there's a scroll event listener for this container.
* If not, create one.
*/
if (!scrollListeners.has(container)) {
const measureAll = () => {
for (const handler of containerHandlers) {
handler.measure(frameData.timestamp);
}
frame.preUpdate(notifyAll);
};
const notifyAll = () => {
for (const handler of containerHandlers) {
handler.notify();
}
};
const listener = () => frame.read(measureAll);
scrollListeners.set(container, listener);
const target = getEventTarget(container);
window.addEventListener("resize", listener);
if (container !== document.documentElement) {
resizeListeners.set(container, resize(container, listener));
}
target.addEventListener("scroll", listener);
listener();
}
/**
* Enable content size tracking if requested and not already enabled.
*/
if (trackContentSize && !dimensionCheckProcesses.has(container)) {
const listener = scrollListeners.get(container);
// Store initial scroll dimensions (object is reused to avoid allocation)
const size = {
width: container.scrollWidth,
height: container.scrollHeight,
};
scrollSize.set(container, size);
// Add frame-based scroll dimension checking to detect content changes
const checkScrollDimensions = () => {
const newWidth = container.scrollWidth;
const newHeight = container.scrollHeight;
if (size.width !== newWidth || size.height !== newHeight) {
listener();
size.width = newWidth;
size.height = newHeight;
}
};
// Schedule with keepAlive=true to run every frame
const dimensionCheckProcess = frame.read(checkScrollDimensions, true);
dimensionCheckProcesses.set(container, dimensionCheckProcess);
}
const listener = scrollListeners.get(container);
frame.read(listener, false, true);
return () => {
cancelFrame(listener);
/**
* Check if we even have any handlers for this container.
*/
const currentHandlers = onScrollHandlers.get(container);
if (!currentHandlers)
return;
currentHandlers.delete(containerHandler);
if (currentHandlers.size)
return;
/**
* If no more handlers, remove the scroll listener too.
*/
const scrollListener = scrollListeners.get(container);
scrollListeners.delete(container);
if (scrollListener) {
getEventTarget(container).removeEventListener("scroll", scrollListener);
resizeListeners.get(container)?.();
window.removeEventListener("resize", scrollListener);
}
// Clean up scroll dimension checking
const dimensionCheckProcess = dimensionCheckProcesses.get(container);
if (dimensionCheckProcess) {
cancelFrame(dimensionCheckProcess);
dimensionCheckProcesses.delete(container);
}
scrollSize.delete(container);
};
}
export { scrollInfo };
//# sourceMappingURL=track.mjs.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
import { supportsViewTimeline, supportsScrollTimeline } from 'motion-dom';
function canUseNativeTimeline(target) {
if (typeof window === "undefined")
return false;
return target ? supportsViewTimeline() : supportsScrollTimeline();
}
export { canUseNativeTimeline };
//# sourceMappingURL=can-use-native-timeline.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"can-use-native-timeline.mjs","sources":["../../../../../../src/render/dom/scroll/utils/can-use-native-timeline.ts"],"sourcesContent":["import { supportsScrollTimeline, supportsViewTimeline } from \"motion-dom\"\n\nexport function canUseNativeTimeline(target?: Element) {\n if (typeof window === \"undefined\") return false\n return target ? supportsViewTimeline() : supportsScrollTimeline()\n}\n"],"names":[],"mappings":";;AAEM,SAAU,oBAAoB,CAAC,MAAgB,EAAA;IACjD,IAAI,OAAO,MAAM,KAAK,WAAW;AAAE,QAAA,OAAO,KAAK;IAC/C,OAAO,MAAM,GAAG,oBAAoB,EAAE,GAAG,sBAAsB,EAAE;AACrE;;;;"}

View File

@@ -0,0 +1,62 @@
import { scrollInfo } from '../track.mjs';
import { canUseNativeTimeline } from './can-use-native-timeline.mjs';
import { offsetToViewTimelineRange } from './offset-to-range.mjs';
const timelineCache = new Map();
function scrollTimelineFallback(options) {
const currentTime = { value: 0 };
const cancel = scrollInfo((info) => {
currentTime.value = info[options.axis].progress * 100;
}, options);
return { currentTime, cancel };
}
function getTimeline({ source, container, ...options }) {
const { axis } = options;
if (source)
container = source;
let containerCache = timelineCache.get(container);
if (!containerCache) {
containerCache = new Map();
timelineCache.set(container, containerCache);
}
const targetKey = options.target ?? "self";
let targetCache = containerCache.get(targetKey);
if (!targetCache) {
targetCache = {};
containerCache.set(targetKey, targetCache);
}
const axisKey = axis + (options.offset ?? []).join(",");
if (!targetCache[axisKey]) {
if (options.target && canUseNativeTimeline(options.target)) {
const range = offsetToViewTimelineRange(options.offset);
if (range) {
targetCache[axisKey] = new ViewTimeline({
subject: options.target,
axis,
});
}
else {
targetCache[axisKey] = scrollTimelineFallback({
container,
...options,
});
}
}
else if (canUseNativeTimeline()) {
targetCache[axisKey] = new ScrollTimeline({
source: container,
axis,
});
}
else {
targetCache[axisKey] = scrollTimelineFallback({
container,
...options,
});
}
}
return targetCache[axisKey];
}
export { getTimeline };
//# sourceMappingURL=get-timeline.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"get-timeline.mjs","sources":["../../../../../../src/render/dom/scroll/utils/get-timeline.ts"],"sourcesContent":["import { ProgressTimeline } from \"motion-dom\"\nimport { scrollInfo } from \"../track\"\nimport { ScrollOptionsWithDefaults } from \"../types\"\nimport { canUseNativeTimeline } from \"./can-use-native-timeline\"\nimport { offsetToViewTimelineRange } from \"./offset-to-range\"\n\ndeclare class ScrollTimeline implements ProgressTimeline {\n constructor(options: ScrollOptions)\n\n currentTime: null | { value: number }\n\n cancel?: VoidFunction\n}\n\ndeclare class ViewTimeline implements ProgressTimeline {\n constructor(options: { subject: Element; axis?: string })\n\n currentTime: null | { value: number }\n\n cancel?: VoidFunction\n}\n\nconst timelineCache = new Map<\n Element,\n Map<Element | \"self\", Record<string, ProgressTimeline>>\n>()\n\nfunction scrollTimelineFallback(options: ScrollOptionsWithDefaults) {\n const currentTime = { value: 0 }\n\n const cancel = scrollInfo((info) => {\n currentTime.value = info[options.axis!].progress * 100\n }, options)\n\n return { currentTime, cancel }\n}\n\nexport function getTimeline({\n source,\n container,\n ...options\n}: ScrollOptionsWithDefaults): ProgressTimeline {\n const { axis } = options\n\n if (source) container = source\n\n let containerCache = timelineCache.get(container)\n if (!containerCache) {\n containerCache = new Map()\n timelineCache.set(container, containerCache)\n }\n\n const targetKey = options.target ?? \"self\"\n let targetCache = containerCache.get(targetKey)\n if (!targetCache) {\n targetCache = {}\n containerCache.set(targetKey, targetCache)\n }\n\n const axisKey = axis + (options.offset ?? []).join(\",\")\n\n if (!targetCache[axisKey]) {\n if (options.target && canUseNativeTimeline(options.target)) {\n const range = offsetToViewTimelineRange(options.offset)\n if (range) {\n targetCache[axisKey] = new ViewTimeline({\n subject: options.target,\n axis,\n })\n } else {\n targetCache[axisKey] = scrollTimelineFallback({\n container,\n ...options,\n })\n }\n } else if (canUseNativeTimeline()) {\n targetCache[axisKey] = new ScrollTimeline({\n source: container,\n axis,\n } as any)\n } else {\n targetCache[axisKey] = scrollTimelineFallback({\n container,\n ...options,\n })\n }\n }\n\n return targetCache[axisKey]!\n}\n"],"names":[],"mappings":";;;;AAsBA,MAAM,aAAa,GAAG,IAAI,GAAG,EAG1B;AAEH,SAAS,sBAAsB,CAAC,OAAkC,EAAA;AAC9D,IAAA,MAAM,WAAW,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE;AAEhC,IAAA,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,IAAI,KAAI;AAC/B,QAAA,WAAW,CAAC,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAK,CAAC,CAAC,QAAQ,GAAG,GAAG;IAC1D,CAAC,EAAE,OAAO,CAAC;AAEX,IAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE;AAClC;AAEM,SAAU,WAAW,CAAC,EACxB,MAAM,EACN,SAAS,EACT,GAAG,OAAO,EACc,EAAA;AACxB,IAAA,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO;AAExB,IAAA,IAAI,MAAM;QAAE,SAAS,GAAG,MAAM;IAE9B,IAAI,cAAc,GAAG,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC;IACjD,IAAI,CAAC,cAAc,EAAE;AACjB,QAAA,cAAc,GAAG,IAAI,GAAG,EAAE;AAC1B,QAAA,aAAa,CAAC,GAAG,CAAC,SAAS,EAAE,cAAc,CAAC;IAChD;AAEA,IAAA,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM;IAC1C,IAAI,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC;IAC/C,IAAI,CAAC,WAAW,EAAE;QACd,WAAW,GAAG,EAAE;AAChB,QAAA,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,WAAW,CAAC;IAC9C;AAEA,IAAA,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,EAAE,IAAI,CAAC,GAAG,CAAC;AAEvD,IAAA,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,EAAE;QACvB,IAAI,OAAO,CAAC,MAAM,IAAI,oBAAoB,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACxD,MAAM,KAAK,GAAG,yBAAyB,CAAC,OAAO,CAAC,MAAM,CAAC;YACvD,IAAI,KAAK,EAAE;AACP,gBAAA,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,YAAY,CAAC;oBACpC,OAAO,EAAE,OAAO,CAAC,MAAM;oBACvB,IAAI;AACP,iBAAA,CAAC;YACN;iBAAO;AACH,gBAAA,WAAW,CAAC,OAAO,CAAC,GAAG,sBAAsB,CAAC;oBAC1C,SAAS;AACT,oBAAA,GAAG,OAAO;AACb,iBAAA,CAAC;YACN;QACJ;aAAO,IAAI,oBAAoB,EAAE,EAAE;AAC/B,YAAA,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,cAAc,CAAC;AACtC,gBAAA,MAAM,EAAE,SAAS;gBACjB,IAAI;AACA,aAAA,CAAC;QACb;aAAO;AACH,YAAA,WAAW,CAAC,OAAO,CAAC,GAAG,sBAAsB,CAAC;gBAC1C,SAAS;AACT,gBAAA,GAAG,OAAO;AACb,aAAA,CAAC;QACN;IACJ;AAEA,IAAA,OAAO,WAAW,CAAC,OAAO,CAAE;AAChC;;;;"}

View File

@@ -0,0 +1,73 @@
import { ScrollOffset } from '../offsets/presets.mjs';
/**
* Maps from ProgressIntersection pairs used by Motion's preset offsets to
* ViewTimeline named ranges. Returns undefined for unrecognised patterns,
* which signals the caller to fall back to JS-based scroll tracking.
*/
const presets = [
[ScrollOffset.Enter, "entry"],
[ScrollOffset.Exit, "exit"],
[ScrollOffset.Any, "cover"],
[ScrollOffset.All, "contain"],
];
const stringToProgress = {
start: 0,
end: 1,
};
function parseStringOffset(s) {
const parts = s.trim().split(/\s+/);
if (parts.length !== 2)
return undefined;
const a = stringToProgress[parts[0]];
const b = stringToProgress[parts[1]];
if (a === undefined || b === undefined)
return undefined;
return [a, b];
}
function normaliseOffset(offset) {
if (offset.length !== 2)
return undefined;
const result = [];
for (const item of offset) {
if (Array.isArray(item)) {
result.push(item);
}
else if (typeof item === "string") {
const parsed = parseStringOffset(item);
if (!parsed)
return undefined;
result.push(parsed);
}
else {
return undefined;
}
}
return result;
}
function matchesPreset(offset, preset) {
const normalised = normaliseOffset(offset);
if (!normalised)
return false;
for (let i = 0; i < 2; i++) {
const o = normalised[i];
const p = preset[i];
if (o[0] !== p[0] || o[1] !== p[1])
return false;
}
return true;
}
function offsetToViewTimelineRange(offset) {
if (!offset) {
return { rangeStart: "contain 0%", rangeEnd: "contain 100%" };
}
for (const [preset, name] of presets) {
if (matchesPreset(offset, preset)) {
return { rangeStart: `${name} 0%`, rangeEnd: `${name} 100%` };
}
}
return undefined;
}
export { offsetToViewTimelineRange };
//# sourceMappingURL=offset-to-range.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"offset-to-range.mjs","sources":["../../../../../../src/render/dom/scroll/utils/offset-to-range.ts"],"sourcesContent":["import { ScrollOffset as ScrollOffsetPresets } from \"../offsets/presets\"\nimport { ProgressIntersection, ScrollOffset } from \"../types\"\n\ninterface ViewTimelineRange {\n rangeStart: string\n rangeEnd: string\n}\n\n/**\n * Maps from ProgressIntersection pairs used by Motion's preset offsets to\n * ViewTimeline named ranges. Returns undefined for unrecognised patterns,\n * which signals the caller to fall back to JS-based scroll tracking.\n */\nconst presets: [ProgressIntersection[], string][] = [\n [ScrollOffsetPresets.Enter, \"entry\"],\n [ScrollOffsetPresets.Exit, \"exit\"],\n [ScrollOffsetPresets.Any, \"cover\"],\n [ScrollOffsetPresets.All, \"contain\"],\n]\n\nconst stringToProgress: Record<string, number> = {\n start: 0,\n end: 1,\n}\n\nfunction parseStringOffset(\n s: string\n): ProgressIntersection | undefined {\n const parts = s.trim().split(/\\s+/)\n if (parts.length !== 2) return undefined\n const a = stringToProgress[parts[0]]\n const b = stringToProgress[parts[1]]\n if (a === undefined || b === undefined) return undefined\n return [a, b]\n}\n\nfunction normaliseOffset(offset: ScrollOffset): ProgressIntersection[] | undefined {\n if (offset.length !== 2) return undefined\n const result: ProgressIntersection[] = []\n for (const item of offset) {\n if (Array.isArray(item)) {\n result.push(item as ProgressIntersection)\n } else if (typeof item === \"string\") {\n const parsed = parseStringOffset(item)\n if (!parsed) return undefined\n result.push(parsed)\n } else {\n return undefined\n }\n }\n return result\n}\n\nfunction matchesPreset(\n offset: ScrollOffset,\n preset: ProgressIntersection[]\n): boolean {\n const normalised = normaliseOffset(offset)\n if (!normalised) return false\n\n for (let i = 0; i < 2; i++) {\n const o = normalised[i]\n const p = preset[i]\n if (o[0] !== p[0] || o[1] !== p[1]) return false\n }\n return true\n}\n\nexport function offsetToViewTimelineRange(\n offset?: ScrollOffset\n): ViewTimelineRange | undefined {\n if (!offset) {\n return { rangeStart: \"contain 0%\", rangeEnd: \"contain 100%\" }\n }\n\n for (const [preset, name] of presets) {\n if (matchesPreset(offset, preset)) {\n return { rangeStart: `${name} 0%`, rangeEnd: `${name} 100%` }\n }\n }\n\n return undefined\n}\n"],"names":["ScrollOffsetPresets"],"mappings":";;AAQA;;;;AAIG;AACH,MAAM,OAAO,GAAuC;AAChD,IAAA,CAACA,YAAmB,CAAC,KAAK,EAAE,OAAO,CAAC;AACpC,IAAA,CAACA,YAAmB,CAAC,IAAI,EAAE,MAAM,CAAC;AAClC,IAAA,CAACA,YAAmB,CAAC,GAAG,EAAE,OAAO,CAAC;AAClC,IAAA,CAACA,YAAmB,CAAC,GAAG,EAAE,SAAS,CAAC;CACvC;AAED,MAAM,gBAAgB,GAA2B;AAC7C,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,GAAG,EAAE,CAAC;CACT;AAED,SAAS,iBAAiB,CACtB,CAAS,EAAA;IAET,MAAM,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC;AACnC,IAAA,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,SAAS;IACxC,MAAM,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACpC,IAAA,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,SAAS;AAAE,QAAA,OAAO,SAAS;AACxD,IAAA,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC;AACjB;AAEA,SAAS,eAAe,CAAC,MAAoB,EAAA;AACzC,IAAA,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;AAAE,QAAA,OAAO,SAAS;IACzC,MAAM,MAAM,GAA2B,EAAE;AACzC,IAAA,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE;AACvB,QAAA,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AACrB,YAAA,MAAM,CAAC,IAAI,CAAC,IAA4B,CAAC;QAC7C;AAAO,aAAA,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;AACjC,YAAA,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC;AACtC,YAAA,IAAI,CAAC,MAAM;AAAE,gBAAA,OAAO,SAAS;AAC7B,YAAA,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QACvB;aAAO;AACH,YAAA,OAAO,SAAS;QACpB;IACJ;AACA,IAAA,OAAO,MAAM;AACjB;AAEA,SAAS,aAAa,CAClB,MAAoB,EACpB,MAA8B,EAAA;AAE9B,IAAA,MAAM,UAAU,GAAG,eAAe,CAAC,MAAM,CAAC;AAC1C,IAAA,IAAI,CAAC,UAAU;AAAE,QAAA,OAAO,KAAK;AAE7B,IAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE;AACxB,QAAA,MAAM,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC;AACvB,QAAA,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;AACnB,QAAA,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAAE,YAAA,OAAO,KAAK;IACpD;AACA,IAAA,OAAO,IAAI;AACf;AAEM,SAAU,yBAAyB,CACrC,MAAqB,EAAA;IAErB,IAAI,CAAC,MAAM,EAAE;QACT,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,cAAc,EAAE;IACjE;IAEA,KAAK,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,OAAO,EAAE;AAClC,QAAA,IAAI,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;AAC/B,YAAA,OAAO,EAAE,UAAU,EAAE,CAAA,EAAG,IAAI,CAAA,GAAA,CAAK,EAAE,QAAQ,EAAE,CAAA,EAAG,IAAI,CAAA,KAAA,CAAO,EAAE;QACjE;IACJ;AAEA,IAAA,OAAO,SAAS;AACpB;;;;"}