This commit is contained in:
Iliyan Angelov
2025-09-14 23:24:25 +03:00
commit c67067a2a4
71311 changed files with 6800714 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
import { scrapeMotionValuesFromProps } from './utils/scrape-motion-values.mjs';
import { DOMVisualElement } from '../dom/DOMVisualElement.mjs';
import { buildSVGAttrs } from './utils/build-attrs.mjs';
import { camelToDash } from '../dom/utils/camel-to-dash.mjs';
import { camelCaseAttributes } from './utils/camel-case-attrs.mjs';
import { transformProps } from '../html/utils/transform.mjs';
import { renderSVG } from './utils/render.mjs';
import { getDefaultValueType } from '../dom/value-types/defaults.mjs';
import { createBox } from '../../projection/geometry/models.mjs';
import { isSVGTag } from './utils/is-svg-tag.mjs';
class SVGVisualElement extends DOMVisualElement {
constructor() {
super(...arguments);
this.type = "svg";
this.isSVGTag = false;
}
getBaseTargetFromProps(props, key) {
return props[key];
}
readValueFromInstance(instance, key) {
if (transformProps.has(key)) {
const defaultType = getDefaultValueType(key);
return defaultType ? defaultType.default || 0 : 0;
}
key = !camelCaseAttributes.has(key) ? camelToDash(key) : key;
return instance.getAttribute(key);
}
measureInstanceViewportBox() {
return createBox();
}
scrapeMotionValuesFromProps(props, prevProps) {
return scrapeMotionValuesFromProps(props, prevProps);
}
build(renderState, latestValues, options, props) {
buildSVGAttrs(renderState, latestValues, options, this.isSVGTag, props.transformTemplate);
}
renderInstance(instance, renderState, styleProp, projection) {
renderSVG(instance, renderState, styleProp, projection);
}
mount(instance) {
this.isSVGTag = isSVGTag(instance.tagName);
super.mount(instance);
}
}
export { SVGVisualElement };

View File

@@ -0,0 +1,40 @@
import { renderSVG } from './utils/render.mjs';
import { scrapeMotionValuesFromProps } from './utils/scrape-motion-values.mjs';
import { makeUseVisualState } from '../../motion/utils/use-visual-state.mjs';
import { createSvgRenderState } from './utils/create-render-state.mjs';
import { buildSVGAttrs } from './utils/build-attrs.mjs';
import { isSVGTag } from './utils/is-svg-tag.mjs';
import { frame } from '../../frameloop/frame.mjs';
const svgMotionConfig = {
useVisualState: makeUseVisualState({
scrapeMotionValuesFromProps: scrapeMotionValuesFromProps,
createRenderState: createSvgRenderState,
onMount: (props, instance, { renderState, latestValues }) => {
frame.read(() => {
try {
renderState.dimensions =
typeof instance.getBBox ===
"function"
? instance.getBBox()
: instance.getBoundingClientRect();
}
catch (e) {
// Most likely trying to measure an unrendered element under Firefox
renderState.dimensions = {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
});
frame.render(() => {
buildSVGAttrs(renderState, latestValues, { enableHardwareAcceleration: false }, isSVGTag(instance.tagName), props.transformTemplate);
renderSVG(instance, renderState);
});
},
}),
};
export { svgMotionConfig };

View File

@@ -0,0 +1,33 @@
/**
* We keep these listed seperately as we use the lowercase tag names as part
* of the runtime bundle to detect SVG components
*/
const lowercaseSVGElements = [
"animate",
"circle",
"defs",
"desc",
"ellipse",
"g",
"image",
"line",
"filter",
"marker",
"mask",
"metadata",
"path",
"pattern",
"polygon",
"polyline",
"rect",
"stop",
"switch",
"symbol",
"svg",
"text",
"tspan",
"use",
"view",
];
export { lowercaseSVGElements };

View File

@@ -0,0 +1,24 @@
import { useMemo } from 'react';
import { copyRawValuesOnly } from '../html/use-props.mjs';
import { buildSVGAttrs } from './utils/build-attrs.mjs';
import { createSvgRenderState } from './utils/create-render-state.mjs';
import { isSVGTag } from './utils/is-svg-tag.mjs';
function useSVGProps(props, visualState, _isStatic, Component) {
const visualProps = useMemo(() => {
const state = createSvgRenderState();
buildSVGAttrs(state, visualState, { enableHardwareAcceleration: false }, isSVGTag(Component), props.transformTemplate);
return {
...state.attrs,
style: { ...state.style },
};
}, [visualState]);
if (props.style) {
const rawStyles = {};
copyRawValuesOnly(rawStyles, props.style, props);
visualProps.style = { ...rawStyles, ...visualProps.style };
}
return visualProps;
}
export { useSVGProps };

View File

@@ -0,0 +1,52 @@
import { buildHTMLStyles } from '../../html/utils/build-styles.mjs';
import { calcSVGTransformOrigin } from './transform-origin.mjs';
import { buildSVGPath } from './path.mjs';
/**
* Build SVG visual attrbutes, like cx and style.transform
*/
function buildSVGAttrs(state, { attrX, attrY, attrScale, originX, originY, pathLength, pathSpacing = 1, pathOffset = 0,
// This is object creation, which we try to avoid per-frame.
...latest }, options, isSVGTag, transformTemplate) {
buildHTMLStyles(state, latest, options, transformTemplate);
/**
* For svg tags we just want to make sure viewBox is animatable and treat all the styles
* as normal HTML tags.
*/
if (isSVGTag) {
if (state.style.viewBox) {
state.attrs.viewBox = state.style.viewBox;
}
return;
}
state.attrs = state.style;
state.style = {};
const { attrs, style, dimensions } = state;
/**
* However, we apply transforms as CSS transforms. So if we detect a transform we take it from attrs
* and copy it into style.
*/
if (attrs.transform) {
if (dimensions)
style.transform = attrs.transform;
delete attrs.transform;
}
// Parse transformOrigin
if (dimensions &&
(originX !== undefined || originY !== undefined || style.transform)) {
style.transformOrigin = calcSVGTransformOrigin(dimensions, originX !== undefined ? originX : 0.5, originY !== undefined ? originY : 0.5);
}
// Render attrX/attrY/attrScale as attributes
if (attrX !== undefined)
attrs.x = attrX;
if (attrY !== undefined)
attrs.y = attrY;
if (attrScale !== undefined)
attrs.scale = attrScale;
// Build SVG path if one has been defined
if (pathLength !== undefined) {
buildSVGPath(attrs, pathLength, pathSpacing, pathOffset, false);
}
}
export { buildSVGAttrs };

View File

@@ -0,0 +1,30 @@
/**
* A set of attribute names that are always read/written as camel case.
*/
const camelCaseAttributes = new Set([
"baseFrequency",
"diffuseConstant",
"kernelMatrix",
"kernelUnitLength",
"keySplines",
"keyTimes",
"limitingConeAngle",
"markerHeight",
"markerWidth",
"numOctaves",
"targetX",
"targetY",
"surfaceScale",
"specularConstant",
"specularExponent",
"stdDeviation",
"tableValues",
"viewBox",
"gradientTransform",
"pathLength",
"startOffset",
"textLength",
"lengthAdjust",
]);
export { camelCaseAttributes };

View File

@@ -0,0 +1,8 @@
import { createHtmlRenderState } from '../../html/utils/create-render-state.mjs';
const createSvgRenderState = () => ({
...createHtmlRenderState(),
attrs: {},
});
export { createSvgRenderState };

View File

@@ -0,0 +1,3 @@
const isSVGTag = (tag) => typeof tag === "string" && tag.toLowerCase() === "svg";
export { isSVGTag };

View File

@@ -0,0 +1,32 @@
import { px } from '../../../value/types/numbers/units.mjs';
const dashKeys = {
offset: "stroke-dashoffset",
array: "stroke-dasharray",
};
const camelKeys = {
offset: "strokeDashoffset",
array: "strokeDasharray",
};
/**
* Build SVG path properties. Uses the path's measured length to convert
* our custom pathLength, pathSpacing and pathOffset into stroke-dashoffset
* and stroke-dasharray attributes.
*
* This function is mutative to reduce per-frame GC.
*/
function buildSVGPath(attrs, length, spacing = 1, offset = 0, useDashCase = true) {
// Normalise path length by setting SVG attribute pathLength to 1
attrs.pathLength = 1;
// We use dash case when setting attributes directly to the DOM node and camel case
// when defining props on a React component.
const keys = useDashCase ? dashKeys : camelKeys;
// Build the dash offset
attrs[keys.offset] = px.transform(-offset);
// Build the dash array
const pathLength = px.transform(length);
const pathSpacing = px.transform(spacing);
attrs[keys.array] = `${pathLength} ${pathSpacing}`;
}
export { buildSVGPath };

View File

@@ -0,0 +1,12 @@
import { camelToDash } from '../../dom/utils/camel-to-dash.mjs';
import { renderHTML } from '../../html/utils/render.mjs';
import { camelCaseAttributes } from './camel-case-attrs.mjs';
function renderSVG(element, renderState, _styleProp, projection) {
renderHTML(element, renderState, undefined, projection);
for (const key in renderState.attrs) {
element.setAttribute(!camelCaseAttributes.has(key) ? camelToDash(key) : key, renderState.attrs[key]);
}
}
export { renderSVG };

View File

@@ -0,0 +1,18 @@
import { isMotionValue } from '../../../value/utils/is-motion-value.mjs';
import { scrapeMotionValuesFromProps as scrapeMotionValuesFromProps$1 } from '../../html/utils/scrape-motion-values.mjs';
import { transformPropOrder } from '../../html/utils/transform.mjs';
function scrapeMotionValuesFromProps(props, prevProps) {
const newValues = scrapeMotionValuesFromProps$1(props, prevProps);
for (const key in props) {
if (isMotionValue(props[key]) || isMotionValue(prevProps[key])) {
const targetKey = transformPropOrder.indexOf(key) !== -1
? "attr" + key.charAt(0).toUpperCase() + key.substring(1)
: key;
newValues[targetKey] = props[key];
}
}
return newValues;
}
export { scrapeMotionValuesFromProps };

View File

@@ -0,0 +1,18 @@
import { px } from '../../../value/types/numbers/units.mjs';
function calcOrigin(origin, offset, size) {
return typeof origin === "string"
? origin
: px.transform(offset + size * origin);
}
/**
* The SVG transform origin defaults are different to CSS and is less intuitive,
* so we use the measured dimensions of the SVG to reconcile these.
*/
function calcSVGTransformOrigin(dimensions, originX, originY) {
const pxOriginX = calcOrigin(originX, dimensions.x, dimensions.width);
const pxOriginY = calcOrigin(originY, dimensions.y, dimensions.height);
return `${pxOriginX} ${pxOriginY}`;
}
export { calcSVGTransformOrigin };