BOC v1.0: RS256 auth, Ledger integration, Prometheus metrics, CI/CD, backup
This commit is contained in:
+370
@@ -0,0 +1,370 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var index = require('./index-6W16WHlG.js');
|
||||
var featureBundle = require('./feature-bundle-riC1cckV.js');
|
||||
require('react/jsx-runtime');
|
||||
require('motion-utils');
|
||||
require('react');
|
||||
require('motion-dom');
|
||||
|
||||
function createMotionComponentWithFeatures(Component, options) {
|
||||
return index.createMotionComponent(Component, options, featureBundle.featureBundle, featureBundle.createDomVisualElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML components
|
||||
*/
|
||||
const MotionA = /*@__PURE__*/ createMotionComponentWithFeatures("a");
|
||||
const MotionAbbr = /*@__PURE__*/ createMotionComponentWithFeatures("abbr");
|
||||
const MotionAddress = /*@__PURE__*/ createMotionComponentWithFeatures("address");
|
||||
const MotionArea = /*@__PURE__*/ createMotionComponentWithFeatures("area");
|
||||
const MotionArticle = /*@__PURE__*/ createMotionComponentWithFeatures("article");
|
||||
const MotionAside = /*@__PURE__*/ createMotionComponentWithFeatures("aside");
|
||||
const MotionAudio = /*@__PURE__*/ createMotionComponentWithFeatures("audio");
|
||||
const MotionB = /*@__PURE__*/ createMotionComponentWithFeatures("b");
|
||||
const MotionBase = /*@__PURE__*/ createMotionComponentWithFeatures("base");
|
||||
const MotionBdi = /*@__PURE__*/ createMotionComponentWithFeatures("bdi");
|
||||
const MotionBdo = /*@__PURE__*/ createMotionComponentWithFeatures("bdo");
|
||||
const MotionBig = /*@__PURE__*/ createMotionComponentWithFeatures("big");
|
||||
const MotionBlockquote =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("blockquote");
|
||||
const MotionBody = /*@__PURE__*/ createMotionComponentWithFeatures("body");
|
||||
const MotionButton = /*@__PURE__*/ createMotionComponentWithFeatures("button");
|
||||
const MotionCanvas = /*@__PURE__*/ createMotionComponentWithFeatures("canvas");
|
||||
const MotionCaption = /*@__PURE__*/ createMotionComponentWithFeatures("caption");
|
||||
const MotionCite = /*@__PURE__*/ createMotionComponentWithFeatures("cite");
|
||||
const MotionCode = /*@__PURE__*/ createMotionComponentWithFeatures("code");
|
||||
const MotionCol = /*@__PURE__*/ createMotionComponentWithFeatures("col");
|
||||
const MotionColgroup = /*@__PURE__*/ createMotionComponentWithFeatures("colgroup");
|
||||
const MotionData = /*@__PURE__*/ createMotionComponentWithFeatures("data");
|
||||
const MotionDatalist = /*@__PURE__*/ createMotionComponentWithFeatures("datalist");
|
||||
const MotionDd = /*@__PURE__*/ createMotionComponentWithFeatures("dd");
|
||||
const MotionDel = /*@__PURE__*/ createMotionComponentWithFeatures("del");
|
||||
const MotionDetails = /*@__PURE__*/ createMotionComponentWithFeatures("details");
|
||||
const MotionDfn = /*@__PURE__*/ createMotionComponentWithFeatures("dfn");
|
||||
const MotionDialog = /*@__PURE__*/ createMotionComponentWithFeatures("dialog");
|
||||
const MotionDiv = /*@__PURE__*/ createMotionComponentWithFeatures("div");
|
||||
const MotionDl = /*@__PURE__*/ createMotionComponentWithFeatures("dl");
|
||||
const MotionDt = /*@__PURE__*/ createMotionComponentWithFeatures("dt");
|
||||
const MotionEm = /*@__PURE__*/ createMotionComponentWithFeatures("em");
|
||||
const MotionEmbed = /*@__PURE__*/ createMotionComponentWithFeatures("embed");
|
||||
const MotionFieldset = /*@__PURE__*/ createMotionComponentWithFeatures("fieldset");
|
||||
const MotionFigcaption =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("figcaption");
|
||||
const MotionFigure = /*@__PURE__*/ createMotionComponentWithFeatures("figure");
|
||||
const MotionFooter = /*@__PURE__*/ createMotionComponentWithFeatures("footer");
|
||||
const MotionForm = /*@__PURE__*/ createMotionComponentWithFeatures("form");
|
||||
const MotionH1 = /*@__PURE__*/ createMotionComponentWithFeatures("h1");
|
||||
const MotionH2 = /*@__PURE__*/ createMotionComponentWithFeatures("h2");
|
||||
const MotionH3 = /*@__PURE__*/ createMotionComponentWithFeatures("h3");
|
||||
const MotionH4 = /*@__PURE__*/ createMotionComponentWithFeatures("h4");
|
||||
const MotionH5 = /*@__PURE__*/ createMotionComponentWithFeatures("h5");
|
||||
const MotionH6 = /*@__PURE__*/ createMotionComponentWithFeatures("h6");
|
||||
const MotionHead = /*@__PURE__*/ createMotionComponentWithFeatures("head");
|
||||
const MotionHeader = /*@__PURE__*/ createMotionComponentWithFeatures("header");
|
||||
const MotionHgroup = /*@__PURE__*/ createMotionComponentWithFeatures("hgroup");
|
||||
const MotionHr = /*@__PURE__*/ createMotionComponentWithFeatures("hr");
|
||||
const MotionHtml = /*@__PURE__*/ createMotionComponentWithFeatures("html");
|
||||
const MotionI = /*@__PURE__*/ createMotionComponentWithFeatures("i");
|
||||
const MotionIframe = /*@__PURE__*/ createMotionComponentWithFeatures("iframe");
|
||||
const MotionImg = /*@__PURE__*/ createMotionComponentWithFeatures("img");
|
||||
const MotionInput = /*@__PURE__*/ createMotionComponentWithFeatures("input");
|
||||
const MotionIns = /*@__PURE__*/ createMotionComponentWithFeatures("ins");
|
||||
const MotionKbd = /*@__PURE__*/ createMotionComponentWithFeatures("kbd");
|
||||
const MotionKeygen = /*@__PURE__*/ createMotionComponentWithFeatures("keygen");
|
||||
const MotionLabel = /*@__PURE__*/ createMotionComponentWithFeatures("label");
|
||||
const MotionLegend = /*@__PURE__*/ createMotionComponentWithFeatures("legend");
|
||||
const MotionLi = /*@__PURE__*/ createMotionComponentWithFeatures("li");
|
||||
const MotionLink = /*@__PURE__*/ createMotionComponentWithFeatures("link");
|
||||
const MotionMain = /*@__PURE__*/ createMotionComponentWithFeatures("main");
|
||||
const MotionMap = /*@__PURE__*/ createMotionComponentWithFeatures("map");
|
||||
const MotionMark = /*@__PURE__*/ createMotionComponentWithFeatures("mark");
|
||||
const MotionMenu = /*@__PURE__*/ createMotionComponentWithFeatures("menu");
|
||||
const MotionMenuitem = /*@__PURE__*/ createMotionComponentWithFeatures("menuitem");
|
||||
const MotionMeter = /*@__PURE__*/ createMotionComponentWithFeatures("meter");
|
||||
const MotionNav = /*@__PURE__*/ createMotionComponentWithFeatures("nav");
|
||||
const MotionObject = /*@__PURE__*/ createMotionComponentWithFeatures("object");
|
||||
const MotionOl = /*@__PURE__*/ createMotionComponentWithFeatures("ol");
|
||||
const MotionOptgroup = /*@__PURE__*/ createMotionComponentWithFeatures("optgroup");
|
||||
const MotionOption = /*@__PURE__*/ createMotionComponentWithFeatures("option");
|
||||
const MotionOutput = /*@__PURE__*/ createMotionComponentWithFeatures("output");
|
||||
const MotionP = /*@__PURE__*/ createMotionComponentWithFeatures("p");
|
||||
const MotionParam = /*@__PURE__*/ createMotionComponentWithFeatures("param");
|
||||
const MotionPicture = /*@__PURE__*/ createMotionComponentWithFeatures("picture");
|
||||
const MotionPre = /*@__PURE__*/ createMotionComponentWithFeatures("pre");
|
||||
const MotionProgress = /*@__PURE__*/ createMotionComponentWithFeatures("progress");
|
||||
const MotionQ = /*@__PURE__*/ createMotionComponentWithFeatures("q");
|
||||
const MotionRp = /*@__PURE__*/ createMotionComponentWithFeatures("rp");
|
||||
const MotionRt = /*@__PURE__*/ createMotionComponentWithFeatures("rt");
|
||||
const MotionRuby = /*@__PURE__*/ createMotionComponentWithFeatures("ruby");
|
||||
const MotionS = /*@__PURE__*/ createMotionComponentWithFeatures("s");
|
||||
const MotionSamp = /*@__PURE__*/ createMotionComponentWithFeatures("samp");
|
||||
const MotionScript = /*@__PURE__*/ createMotionComponentWithFeatures("script");
|
||||
const MotionSection = /*@__PURE__*/ createMotionComponentWithFeatures("section");
|
||||
const MotionSelect = /*@__PURE__*/ createMotionComponentWithFeatures("select");
|
||||
const MotionSmall = /*@__PURE__*/ createMotionComponentWithFeatures("small");
|
||||
const MotionSource = /*@__PURE__*/ createMotionComponentWithFeatures("source");
|
||||
const MotionSpan = /*@__PURE__*/ createMotionComponentWithFeatures("span");
|
||||
const MotionStrong = /*@__PURE__*/ createMotionComponentWithFeatures("strong");
|
||||
const MotionStyle = /*@__PURE__*/ createMotionComponentWithFeatures("style");
|
||||
const MotionSub = /*@__PURE__*/ createMotionComponentWithFeatures("sub");
|
||||
const MotionSummary = /*@__PURE__*/ createMotionComponentWithFeatures("summary");
|
||||
const MotionSup = /*@__PURE__*/ createMotionComponentWithFeatures("sup");
|
||||
const MotionTable = /*@__PURE__*/ createMotionComponentWithFeatures("table");
|
||||
const MotionTbody = /*@__PURE__*/ createMotionComponentWithFeatures("tbody");
|
||||
const MotionTd = /*@__PURE__*/ createMotionComponentWithFeatures("td");
|
||||
const MotionTextarea = /*@__PURE__*/ createMotionComponentWithFeatures("textarea");
|
||||
const MotionTfoot = /*@__PURE__*/ createMotionComponentWithFeatures("tfoot");
|
||||
const MotionTh = /*@__PURE__*/ createMotionComponentWithFeatures("th");
|
||||
const MotionThead = /*@__PURE__*/ createMotionComponentWithFeatures("thead");
|
||||
const MotionTime = /*@__PURE__*/ createMotionComponentWithFeatures("time");
|
||||
const MotionTitle = /*@__PURE__*/ createMotionComponentWithFeatures("title");
|
||||
const MotionTr = /*@__PURE__*/ createMotionComponentWithFeatures("tr");
|
||||
const MotionTrack = /*@__PURE__*/ createMotionComponentWithFeatures("track");
|
||||
const MotionU = /*@__PURE__*/ createMotionComponentWithFeatures("u");
|
||||
const MotionUl = /*@__PURE__*/ createMotionComponentWithFeatures("ul");
|
||||
const MotionVideo = /*@__PURE__*/ createMotionComponentWithFeatures("video");
|
||||
const MotionWbr = /*@__PURE__*/ createMotionComponentWithFeatures("wbr");
|
||||
const MotionWebview = /*@__PURE__*/ createMotionComponentWithFeatures("webview");
|
||||
/**
|
||||
* SVG components
|
||||
*/
|
||||
const MotionAnimate = /*@__PURE__*/ createMotionComponentWithFeatures("animate");
|
||||
const MotionCircle = /*@__PURE__*/ createMotionComponentWithFeatures("circle");
|
||||
const MotionDefs = /*@__PURE__*/ createMotionComponentWithFeatures("defs");
|
||||
const MotionDesc = /*@__PURE__*/ createMotionComponentWithFeatures("desc");
|
||||
const MotionEllipse = /*@__PURE__*/ createMotionComponentWithFeatures("ellipse");
|
||||
const MotionG = /*@__PURE__*/ createMotionComponentWithFeatures("g");
|
||||
const MotionImage = /*@__PURE__*/ createMotionComponentWithFeatures("image");
|
||||
const MotionLine = /*@__PURE__*/ createMotionComponentWithFeatures("line");
|
||||
const MotionFilter = /*@__PURE__*/ createMotionComponentWithFeatures("filter");
|
||||
const MotionMarker = /*@__PURE__*/ createMotionComponentWithFeatures("marker");
|
||||
const MotionMask = /*@__PURE__*/ createMotionComponentWithFeatures("mask");
|
||||
const MotionMetadata = /*@__PURE__*/ createMotionComponentWithFeatures("metadata");
|
||||
const MotionPath = /*@__PURE__*/ createMotionComponentWithFeatures("path");
|
||||
const MotionPattern = /*@__PURE__*/ createMotionComponentWithFeatures("pattern");
|
||||
const MotionPolygon = /*@__PURE__*/ createMotionComponentWithFeatures("polygon");
|
||||
const MotionPolyline = /*@__PURE__*/ createMotionComponentWithFeatures("polyline");
|
||||
const MotionRect = /*@__PURE__*/ createMotionComponentWithFeatures("rect");
|
||||
const MotionStop = /*@__PURE__*/ createMotionComponentWithFeatures("stop");
|
||||
const MotionSvg = /*@__PURE__*/ createMotionComponentWithFeatures("svg");
|
||||
const MotionSymbol = /*@__PURE__*/ createMotionComponentWithFeatures("symbol");
|
||||
const MotionText = /*@__PURE__*/ createMotionComponentWithFeatures("text");
|
||||
const MotionTspan = /*@__PURE__*/ createMotionComponentWithFeatures("tspan");
|
||||
const MotionUse = /*@__PURE__*/ createMotionComponentWithFeatures("use");
|
||||
const MotionView = /*@__PURE__*/ createMotionComponentWithFeatures("view");
|
||||
const MotionClipPath = /*@__PURE__*/ createMotionComponentWithFeatures("clipPath");
|
||||
const MotionFeBlend = /*@__PURE__*/ createMotionComponentWithFeatures("feBlend");
|
||||
const MotionFeColorMatrix =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feColorMatrix");
|
||||
const MotionFeComponentTransfer = /*@__PURE__*/ createMotionComponentWithFeatures("feComponentTransfer");
|
||||
const MotionFeComposite =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feComposite");
|
||||
const MotionFeConvolveMatrix =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feConvolveMatrix");
|
||||
const MotionFeDiffuseLighting =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feDiffuseLighting");
|
||||
const MotionFeDisplacementMap =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feDisplacementMap");
|
||||
const MotionFeDistantLight =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feDistantLight");
|
||||
const MotionFeDropShadow =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feDropShadow");
|
||||
const MotionFeFlood = /*@__PURE__*/ createMotionComponentWithFeatures("feFlood");
|
||||
const MotionFeFuncA = /*@__PURE__*/ createMotionComponentWithFeatures("feFuncA");
|
||||
const MotionFeFuncB = /*@__PURE__*/ createMotionComponentWithFeatures("feFuncB");
|
||||
const MotionFeFuncG = /*@__PURE__*/ createMotionComponentWithFeatures("feFuncG");
|
||||
const MotionFeFuncR = /*@__PURE__*/ createMotionComponentWithFeatures("feFuncR");
|
||||
const MotionFeGaussianBlur =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feGaussianBlur");
|
||||
const MotionFeImage = /*@__PURE__*/ createMotionComponentWithFeatures("feImage");
|
||||
const MotionFeMerge = /*@__PURE__*/ createMotionComponentWithFeatures("feMerge");
|
||||
const MotionFeMergeNode =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feMergeNode");
|
||||
const MotionFeMorphology =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feMorphology");
|
||||
const MotionFeOffset = /*@__PURE__*/ createMotionComponentWithFeatures("feOffset");
|
||||
const MotionFePointLight =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("fePointLight");
|
||||
const MotionFeSpecularLighting =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feSpecularLighting");
|
||||
const MotionFeSpotLight =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feSpotLight");
|
||||
const MotionFeTile = /*@__PURE__*/ createMotionComponentWithFeatures("feTile");
|
||||
const MotionFeTurbulence =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("feTurbulence");
|
||||
const MotionForeignObject =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("foreignObject");
|
||||
const MotionLinearGradient =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("linearGradient");
|
||||
const MotionRadialGradient =
|
||||
/*@__PURE__*/ createMotionComponentWithFeatures("radialGradient");
|
||||
const MotionTextPath = /*@__PURE__*/ createMotionComponentWithFeatures("textPath");
|
||||
|
||||
exports.a = MotionA;
|
||||
exports.abbr = MotionAbbr;
|
||||
exports.address = MotionAddress;
|
||||
exports.animate = MotionAnimate;
|
||||
exports.area = MotionArea;
|
||||
exports.article = MotionArticle;
|
||||
exports.aside = MotionAside;
|
||||
exports.audio = MotionAudio;
|
||||
exports.b = MotionB;
|
||||
exports.base = MotionBase;
|
||||
exports.bdi = MotionBdi;
|
||||
exports.bdo = MotionBdo;
|
||||
exports.big = MotionBig;
|
||||
exports.blockquote = MotionBlockquote;
|
||||
exports.body = MotionBody;
|
||||
exports.button = MotionButton;
|
||||
exports.canvas = MotionCanvas;
|
||||
exports.caption = MotionCaption;
|
||||
exports.circle = MotionCircle;
|
||||
exports.cite = MotionCite;
|
||||
exports.clipPath = MotionClipPath;
|
||||
exports.code = MotionCode;
|
||||
exports.col = MotionCol;
|
||||
exports.colgroup = MotionColgroup;
|
||||
exports.data = MotionData;
|
||||
exports.datalist = MotionDatalist;
|
||||
exports.dd = MotionDd;
|
||||
exports.defs = MotionDefs;
|
||||
exports.del = MotionDel;
|
||||
exports.desc = MotionDesc;
|
||||
exports.details = MotionDetails;
|
||||
exports.dfn = MotionDfn;
|
||||
exports.dialog = MotionDialog;
|
||||
exports.div = MotionDiv;
|
||||
exports.dl = MotionDl;
|
||||
exports.dt = MotionDt;
|
||||
exports.ellipse = MotionEllipse;
|
||||
exports.em = MotionEm;
|
||||
exports.embed = MotionEmbed;
|
||||
exports.feBlend = MotionFeBlend;
|
||||
exports.feColorMatrix = MotionFeColorMatrix;
|
||||
exports.feComponentTransfer = MotionFeComponentTransfer;
|
||||
exports.feComposite = MotionFeComposite;
|
||||
exports.feConvolveMatrix = MotionFeConvolveMatrix;
|
||||
exports.feDiffuseLighting = MotionFeDiffuseLighting;
|
||||
exports.feDisplacementMap = MotionFeDisplacementMap;
|
||||
exports.feDistantLight = MotionFeDistantLight;
|
||||
exports.feDropShadow = MotionFeDropShadow;
|
||||
exports.feFlood = MotionFeFlood;
|
||||
exports.feFuncA = MotionFeFuncA;
|
||||
exports.feFuncB = MotionFeFuncB;
|
||||
exports.feFuncG = MotionFeFuncG;
|
||||
exports.feFuncR = MotionFeFuncR;
|
||||
exports.feGaussianBlur = MotionFeGaussianBlur;
|
||||
exports.feImage = MotionFeImage;
|
||||
exports.feMerge = MotionFeMerge;
|
||||
exports.feMergeNode = MotionFeMergeNode;
|
||||
exports.feMorphology = MotionFeMorphology;
|
||||
exports.feOffset = MotionFeOffset;
|
||||
exports.fePointLight = MotionFePointLight;
|
||||
exports.feSpecularLighting = MotionFeSpecularLighting;
|
||||
exports.feSpotLight = MotionFeSpotLight;
|
||||
exports.feTile = MotionFeTile;
|
||||
exports.feTurbulence = MotionFeTurbulence;
|
||||
exports.fieldset = MotionFieldset;
|
||||
exports.figcaption = MotionFigcaption;
|
||||
exports.figure = MotionFigure;
|
||||
exports.filter = MotionFilter;
|
||||
exports.footer = MotionFooter;
|
||||
exports.foreignObject = MotionForeignObject;
|
||||
exports.form = MotionForm;
|
||||
exports.g = MotionG;
|
||||
exports.h1 = MotionH1;
|
||||
exports.h2 = MotionH2;
|
||||
exports.h3 = MotionH3;
|
||||
exports.h4 = MotionH4;
|
||||
exports.h5 = MotionH5;
|
||||
exports.h6 = MotionH6;
|
||||
exports.head = MotionHead;
|
||||
exports.header = MotionHeader;
|
||||
exports.hgroup = MotionHgroup;
|
||||
exports.hr = MotionHr;
|
||||
exports.html = MotionHtml;
|
||||
exports.i = MotionI;
|
||||
exports.iframe = MotionIframe;
|
||||
exports.image = MotionImage;
|
||||
exports.img = MotionImg;
|
||||
exports.input = MotionInput;
|
||||
exports.ins = MotionIns;
|
||||
exports.kbd = MotionKbd;
|
||||
exports.keygen = MotionKeygen;
|
||||
exports.label = MotionLabel;
|
||||
exports.legend = MotionLegend;
|
||||
exports.li = MotionLi;
|
||||
exports.line = MotionLine;
|
||||
exports.linearGradient = MotionLinearGradient;
|
||||
exports.link = MotionLink;
|
||||
exports.main = MotionMain;
|
||||
exports.map = MotionMap;
|
||||
exports.mark = MotionMark;
|
||||
exports.marker = MotionMarker;
|
||||
exports.mask = MotionMask;
|
||||
exports.menu = MotionMenu;
|
||||
exports.menuitem = MotionMenuitem;
|
||||
exports.metadata = MotionMetadata;
|
||||
exports.meter = MotionMeter;
|
||||
exports.nav = MotionNav;
|
||||
exports.object = MotionObject;
|
||||
exports.ol = MotionOl;
|
||||
exports.optgroup = MotionOptgroup;
|
||||
exports.option = MotionOption;
|
||||
exports.output = MotionOutput;
|
||||
exports.p = MotionP;
|
||||
exports.param = MotionParam;
|
||||
exports.path = MotionPath;
|
||||
exports.pattern = MotionPattern;
|
||||
exports.picture = MotionPicture;
|
||||
exports.polygon = MotionPolygon;
|
||||
exports.polyline = MotionPolyline;
|
||||
exports.pre = MotionPre;
|
||||
exports.progress = MotionProgress;
|
||||
exports.q = MotionQ;
|
||||
exports.radialGradient = MotionRadialGradient;
|
||||
exports.rect = MotionRect;
|
||||
exports.rp = MotionRp;
|
||||
exports.rt = MotionRt;
|
||||
exports.ruby = MotionRuby;
|
||||
exports.s = MotionS;
|
||||
exports.samp = MotionSamp;
|
||||
exports.script = MotionScript;
|
||||
exports.section = MotionSection;
|
||||
exports.select = MotionSelect;
|
||||
exports.small = MotionSmall;
|
||||
exports.source = MotionSource;
|
||||
exports.span = MotionSpan;
|
||||
exports.stop = MotionStop;
|
||||
exports.strong = MotionStrong;
|
||||
exports.style = MotionStyle;
|
||||
exports.sub = MotionSub;
|
||||
exports.summary = MotionSummary;
|
||||
exports.sup = MotionSup;
|
||||
exports.svg = MotionSvg;
|
||||
exports.symbol = MotionSymbol;
|
||||
exports.table = MotionTable;
|
||||
exports.tbody = MotionTbody;
|
||||
exports.td = MotionTd;
|
||||
exports.text = MotionText;
|
||||
exports.textPath = MotionTextPath;
|
||||
exports.textarea = MotionTextarea;
|
||||
exports.tfoot = MotionTfoot;
|
||||
exports.th = MotionTh;
|
||||
exports.thead = MotionThead;
|
||||
exports.time = MotionTime;
|
||||
exports.title = MotionTitle;
|
||||
exports.tr = MotionTr;
|
||||
exports.track = MotionTrack;
|
||||
exports.tspan = MotionTspan;
|
||||
exports.u = MotionU;
|
||||
exports.ul = MotionUl;
|
||||
exports.use = MotionUse;
|
||||
exports.video = MotionVideo;
|
||||
exports.view = MotionView;
|
||||
exports.wbr = MotionWbr;
|
||||
exports.webview = MotionWebview;
|
||||
//# sourceMappingURL=client.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+13
@@ -0,0 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var motionDom = require('motion-dom');
|
||||
|
||||
|
||||
|
||||
Object.defineProperty(exports, "recordStats", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.recordStats; }
|
||||
});
|
||||
//# sourceMappingURL=debug.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"debug.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
|
||||
+531
@@ -0,0 +1,531 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var motionDom = require('motion-dom');
|
||||
var motionUtils = require('motion-utils');
|
||||
|
||||
function isDOMKeyframes(keyframes) {
|
||||
return typeof keyframes === "object" && !Array.isArray(keyframes);
|
||||
}
|
||||
|
||||
function resolveSubjects(subject, keyframes, scope, selectorCache) {
|
||||
if (subject == null) {
|
||||
return [];
|
||||
}
|
||||
if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
|
||||
return motionDom.resolveElements(subject, scope, selectorCache);
|
||||
}
|
||||
else if (subject instanceof NodeList) {
|
||||
return Array.from(subject);
|
||||
}
|
||||
else if (Array.isArray(subject)) {
|
||||
return subject.filter((s) => s != null);
|
||||
}
|
||||
else {
|
||||
return [subject];
|
||||
}
|
||||
}
|
||||
|
||||
function calculateRepeatDuration(duration, repeat, repeatDelay) {
|
||||
return duration * (repeat + 1) + repeatDelay * repeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a absolute or relative time definition and current/prev time state of the sequence,
|
||||
* calculate an absolute time for the next keyframes.
|
||||
*/
|
||||
function calcNextTime(current, next, prev, labels) {
|
||||
if (typeof next === "number") {
|
||||
return next;
|
||||
}
|
||||
else if (next.startsWith("-") || next.startsWith("+")) {
|
||||
return Math.max(0, current + parseFloat(next));
|
||||
}
|
||||
else if (next === "<") {
|
||||
return prev;
|
||||
}
|
||||
else if (next.startsWith("<")) {
|
||||
return Math.max(0, prev + parseFloat(next.slice(1)));
|
||||
}
|
||||
else {
|
||||
return labels.get(next) ?? current;
|
||||
}
|
||||
}
|
||||
|
||||
function eraseKeyframes(sequence, startTime, endTime) {
|
||||
for (let i = 0; i < sequence.length; i++) {
|
||||
const keyframe = sequence[i];
|
||||
if (keyframe.at > startTime && keyframe.at < endTime) {
|
||||
motionUtils.removeItem(sequence, keyframe);
|
||||
// If we remove this item we have to push the pointer back one
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
|
||||
/**
|
||||
* Erase every existing value between currentTime and targetTime,
|
||||
* this will essentially splice this timeline into any currently
|
||||
* defined ones.
|
||||
*/
|
||||
eraseKeyframes(sequence, startTime, endTime);
|
||||
for (let i = 0; i < keyframes.length; i++) {
|
||||
sequence.push({
|
||||
value: keyframes[i],
|
||||
at: motionDom.mixNumber(startTime, endTime, offset[i]),
|
||||
easing: motionUtils.getEasingForSegment(easing, i),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an array of times that represent repeated keyframes. For instance
|
||||
* if we have original times of [0, 0.5, 1] then our repeated times will
|
||||
* be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
|
||||
* down to a 0-1 scale.
|
||||
*
|
||||
* `repeatDelayUnits` is the repeatDelay expressed in units of a single
|
||||
* iteration's duration, so the total span equals `(repeat + 1) + repeat * repeatDelayUnits`.
|
||||
*/
|
||||
function normalizeTimes(times, repeat, repeatDelayUnits = 0) {
|
||||
const totalUnits = repeat + 1 + repeat * repeatDelayUnits;
|
||||
for (let i = 0; i < times.length; i++) {
|
||||
times[i] = times[i] / totalUnits;
|
||||
}
|
||||
}
|
||||
|
||||
function compareByTime(a, b) {
|
||||
if (a.at === b.at) {
|
||||
if (a.value === null)
|
||||
return 1;
|
||||
if (b.value === null)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return a.at - b.at;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSegmentEasing = "easeInOut";
|
||||
const MAX_REPEAT = 20;
|
||||
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
|
||||
const defaultDuration = defaultTransition.duration || 0.3;
|
||||
const animationDefinitions = new Map();
|
||||
const sequences = new Map();
|
||||
const elementCache = {};
|
||||
const timeLabels = new Map();
|
||||
let prevTime = 0;
|
||||
let currentTime = 0;
|
||||
let totalDuration = 0;
|
||||
/**
|
||||
* Build the timeline by mapping over the sequence array and converting
|
||||
* the definitions into keyframes and offsets with absolute time values.
|
||||
* These will later get converted into relative offsets in a second pass.
|
||||
*/
|
||||
for (let i = 0; i < sequence.length; i++) {
|
||||
const segment = sequence[i];
|
||||
/**
|
||||
* If this is a timeline label, mark it and skip the rest of this iteration.
|
||||
*/
|
||||
if (typeof segment === "string") {
|
||||
timeLabels.set(segment, currentTime);
|
||||
continue;
|
||||
}
|
||||
else if (!Array.isArray(segment)) {
|
||||
timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
|
||||
continue;
|
||||
}
|
||||
let [subject, keyframes, transition = {}] = segment;
|
||||
/**
|
||||
* If a relative or absolute time value has been specified we need to resolve
|
||||
* it in relation to the currentTime.
|
||||
*/
|
||||
if (transition.at !== undefined) {
|
||||
currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
|
||||
}
|
||||
/**
|
||||
* Keep track of the maximum duration in this definition. This will be
|
||||
* applied to currentTime once the definition has been parsed.
|
||||
*/
|
||||
let maxDuration = 0;
|
||||
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
|
||||
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
|
||||
const { delay = 0, times = motionDom.defaultOffset(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
|
||||
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
|
||||
/**
|
||||
* Resolve stagger() if defined.
|
||||
*/
|
||||
const calculatedDelay = typeof delay === "function"
|
||||
? delay(elementIndex, numSubjects)
|
||||
: delay;
|
||||
/**
|
||||
* If this animation should and can use a spring, generate a spring easing function.
|
||||
*/
|
||||
const numKeyframes = valueKeyframesAsList.length;
|
||||
const createGenerator = motionDom.isGenerator(type)
|
||||
? type
|
||||
: generators?.[type || "keyframes"];
|
||||
if (numKeyframes <= 2 && createGenerator) {
|
||||
/**
|
||||
* As we're creating an easing function from a spring,
|
||||
* ideally we want to generate it using the real distance
|
||||
* between the two keyframes. However this isn't always
|
||||
* possible - in these situations we use 0-100.
|
||||
*/
|
||||
let absoluteDelta = 100;
|
||||
if (numKeyframes === 2 &&
|
||||
isNumberKeyframesArray(valueKeyframesAsList)) {
|
||||
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
|
||||
absoluteDelta = Math.abs(delta);
|
||||
}
|
||||
const springTransition = {
|
||||
...defaultTransition,
|
||||
...remainingTransition,
|
||||
};
|
||||
if (duration !== undefined) {
|
||||
springTransition.duration = motionUtils.secondsToMilliseconds(duration);
|
||||
}
|
||||
const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
|
||||
ease = springEasing.ease;
|
||||
duration = springEasing.duration;
|
||||
}
|
||||
duration ?? (duration = defaultDuration);
|
||||
const startTime = currentTime + calculatedDelay;
|
||||
/**
|
||||
* If there's only one time offset of 0, fill in a second with length 1
|
||||
*/
|
||||
if (times.length === 1 && times[0] === 0) {
|
||||
times[1] = 1;
|
||||
}
|
||||
/**
|
||||
* Fill out if offset if fewer offsets than keyframes
|
||||
*/
|
||||
const remainder = times.length - valueKeyframesAsList.length;
|
||||
remainder > 0 && motionDom.fillOffset(times, remainder);
|
||||
/**
|
||||
* If only one value has been set, ie [1], push a null to the start of
|
||||
* the keyframe array. This will let us mark a keyframe at this point
|
||||
* that will later be hydrated with the previous value.
|
||||
*/
|
||||
valueKeyframesAsList.length === 1 &&
|
||||
valueKeyframesAsList.unshift(null);
|
||||
/**
|
||||
* Segments can't express `repeat: Infinity` or very large
|
||||
* counts — they'd leave dead time after the segment or
|
||||
* explode the keyframe array. Ignore with a warning.
|
||||
*/
|
||||
if (repeat) {
|
||||
motionUtils.warning(repeat < MAX_REPEAT, `Sequence segments can't repeat ${repeat} times — ignoring repeat option. Use a value below ${MAX_REPEAT} or apply repeat at the sequence level instead.`);
|
||||
}
|
||||
if (repeat && repeat < MAX_REPEAT) {
|
||||
/**
|
||||
* Express repeatDelay in units of a single iteration's duration
|
||||
* so it can be added to the per-iteration time offsets below
|
||||
* before they're normalized to 0-1.
|
||||
*/
|
||||
const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
|
||||
duration = calculateRepeatDuration(duration, repeat, repeatDelay);
|
||||
const originalKeyframes = [...valueKeyframesAsList];
|
||||
const originalTimes = [...times];
|
||||
ease = Array.isArray(ease) ? [...ease] : [ease];
|
||||
const originalEase = [...ease];
|
||||
/**
|
||||
* For reverse/mirror, alternate iterations play the segment
|
||||
* backwards. mirror matches JSAnimation's mirroredGenerator:
|
||||
* reversed keyframes, easings unchanged. reverse matches
|
||||
* JSAnimation's iterationProgress = 1 - p: reversed
|
||||
* keyframes, easing array reversed AND each function easing
|
||||
* mapped through reverseEasing (string easings unchanged —
|
||||
* they're resolved later by the keyframes engine).
|
||||
*/
|
||||
const isFlipping = repeatType === "reverse" || repeatType === "mirror";
|
||||
let flippedKeyframes = originalKeyframes;
|
||||
let flippedEases = originalEase;
|
||||
if (isFlipping) {
|
||||
flippedKeyframes = [...originalKeyframes].reverse();
|
||||
if (repeatType === "reverse") {
|
||||
flippedEases = [...originalEase]
|
||||
.reverse()
|
||||
.map((e) => typeof e === "function"
|
||||
? motionUtils.reverseEasing(e)
|
||||
: e);
|
||||
}
|
||||
}
|
||||
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
|
||||
const isFlipped = isFlipping && repeatIndex % 2 === 0;
|
||||
const iterKeyframes = isFlipped
|
||||
? flippedKeyframes
|
||||
: originalKeyframes;
|
||||
const iterEase = isFlipped ? flippedEases : originalEase;
|
||||
const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
|
||||
/**
|
||||
* If repeatDelay is set, hold the previous iteration's
|
||||
* final value through the delay by inserting a keyframe
|
||||
* at the moment the next iteration begins.
|
||||
*/
|
||||
if (repeatDelayUnits > 0) {
|
||||
valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
|
||||
times.push(iterStartOffset);
|
||||
ease.push("linear");
|
||||
}
|
||||
valueKeyframesAsList.push(...iterKeyframes);
|
||||
for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
|
||||
times.push(originalTimes[keyframeIndex] + iterStartOffset);
|
||||
ease.push(keyframeIndex === 0
|
||||
? "linear"
|
||||
: motionUtils.getEasingForSegment(iterEase, keyframeIndex - 1));
|
||||
}
|
||||
}
|
||||
normalizeTimes(times, repeat, repeatDelayUnits);
|
||||
}
|
||||
const targetTime = startTime + duration;
|
||||
/**
|
||||
* Add keyframes, mapping offsets to absolute time.
|
||||
*/
|
||||
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
|
||||
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
|
||||
totalDuration = Math.max(targetTime, totalDuration);
|
||||
};
|
||||
if (motionDom.isMotionValue(subject)) {
|
||||
const subjectSequence = getSubjectSequence(subject, sequences);
|
||||
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
|
||||
}
|
||||
else {
|
||||
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
|
||||
const numSubjects = subjects.length;
|
||||
/**
|
||||
* For every element in this segment, process the defined values.
|
||||
*/
|
||||
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
|
||||
/**
|
||||
* Cast necessary, but we know these are of this type
|
||||
*/
|
||||
keyframes = keyframes;
|
||||
transition = transition;
|
||||
const thisSubject = subjects[subjectIndex];
|
||||
const subjectSequence = getSubjectSequence(thisSubject, sequences);
|
||||
for (const key in keyframes) {
|
||||
resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
prevTime = currentTime;
|
||||
currentTime += maxDuration;
|
||||
}
|
||||
/**
|
||||
* For every element and value combination create a new animation.
|
||||
*/
|
||||
sequences.forEach((valueSequences, element) => {
|
||||
for (const key in valueSequences) {
|
||||
const valueSequence = valueSequences[key];
|
||||
/**
|
||||
* Arrange all the keyframes in ascending time order.
|
||||
*/
|
||||
valueSequence.sort(compareByTime);
|
||||
const keyframes = [];
|
||||
const valueOffset = [];
|
||||
const valueEasing = [];
|
||||
/**
|
||||
* For each keyframe, translate absolute times into
|
||||
* relative offsets based on the total duration of the timeline.
|
||||
*/
|
||||
for (let i = 0; i < valueSequence.length; i++) {
|
||||
const { at, value, easing } = valueSequence[i];
|
||||
keyframes.push(value);
|
||||
valueOffset.push(motionUtils.progress(0, totalDuration, at));
|
||||
valueEasing.push(easing || "easeOut");
|
||||
}
|
||||
/**
|
||||
* If the first keyframe doesn't land on offset: 0
|
||||
* provide one by duplicating the initial keyframe. This ensures
|
||||
* it snaps to the first keyframe when the animation starts.
|
||||
*/
|
||||
if (valueOffset[0] !== 0) {
|
||||
valueOffset.unshift(0);
|
||||
keyframes.unshift(keyframes[0]);
|
||||
valueEasing.unshift(defaultSegmentEasing);
|
||||
}
|
||||
/**
|
||||
* If the last keyframe doesn't land on offset: 1
|
||||
* provide one with a null wildcard value. This will ensure it
|
||||
* stays static until the end of the animation.
|
||||
*/
|
||||
if (valueOffset[valueOffset.length - 1] !== 1) {
|
||||
valueOffset.push(1);
|
||||
keyframes.push(null);
|
||||
}
|
||||
if (!animationDefinitions.has(element)) {
|
||||
animationDefinitions.set(element, {
|
||||
keyframes: {},
|
||||
transition: {},
|
||||
});
|
||||
}
|
||||
const definition = animationDefinitions.get(element);
|
||||
definition.keyframes[key] = keyframes;
|
||||
/**
|
||||
* Exclude `type` from defaultTransition since springs have been
|
||||
* converted to duration-based easing functions in resolveValueSequence.
|
||||
* Including `type: "spring"` would cause JSAnimation to error when
|
||||
* the merged keyframes array has more than 2 keyframes.
|
||||
*/
|
||||
const { type: _type, ...remainingDefaultTransition } = defaultTransition;
|
||||
definition.transition[key] = {
|
||||
...remainingDefaultTransition,
|
||||
duration: totalDuration,
|
||||
ease: valueEasing,
|
||||
times: valueOffset,
|
||||
...sequenceTransition,
|
||||
};
|
||||
}
|
||||
});
|
||||
return animationDefinitions;
|
||||
}
|
||||
function getSubjectSequence(subject, sequences) {
|
||||
!sequences.has(subject) && sequences.set(subject, {});
|
||||
return sequences.get(subject);
|
||||
}
|
||||
function getValueSequence(name, sequences) {
|
||||
if (!sequences[name])
|
||||
sequences[name] = [];
|
||||
return sequences[name];
|
||||
}
|
||||
function keyframesAsList(keyframes) {
|
||||
return Array.isArray(keyframes) ? keyframes : [keyframes];
|
||||
}
|
||||
function getValueTransition(transition, key) {
|
||||
return transition && transition[key]
|
||||
? {
|
||||
...transition,
|
||||
...transition[key],
|
||||
}
|
||||
: { ...transition };
|
||||
}
|
||||
const isNumber = (keyframe) => typeof keyframe === "number";
|
||||
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
|
||||
|
||||
function animateElements(elementOrSelector, keyframes, options, scope) {
|
||||
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
|
||||
if (elementOrSelector == null) {
|
||||
return [];
|
||||
}
|
||||
const elements = motionDom.resolveElements(elementOrSelector, scope);
|
||||
const numElements = elements.length;
|
||||
motionUtils.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
|
||||
/**
|
||||
* WAAPI doesn't support interrupting animations.
|
||||
*
|
||||
* Therefore, starting animations requires a three-step process:
|
||||
* 1. Stop existing animations (write styles to DOM)
|
||||
* 2. Resolve keyframes (read styles from DOM)
|
||||
* 3. Create new animations (write styles to DOM)
|
||||
*
|
||||
* The hybrid `animate()` function uses AsyncAnimation to resolve
|
||||
* keyframes before creating new animations, which removes style
|
||||
* thrashing. Here, we have much stricter filesize constraints.
|
||||
* Therefore we do this in a synchronous way that ensures that
|
||||
* at least within `animate()` calls there is no style thrashing.
|
||||
*
|
||||
* In the motion-native-animate-mini-interrupt benchmark this
|
||||
* was 80% faster than a single loop.
|
||||
*/
|
||||
const animationDefinitions = [];
|
||||
/**
|
||||
* Step 1: Build options and stop existing animations (write)
|
||||
*/
|
||||
for (let i = 0; i < numElements; i++) {
|
||||
const element = elements[i];
|
||||
const elementTransition = { ...options };
|
||||
/**
|
||||
* Resolve stagger function if provided.
|
||||
*/
|
||||
if (typeof elementTransition.delay === "function") {
|
||||
elementTransition.delay = elementTransition.delay(i, numElements);
|
||||
}
|
||||
for (const valueName in keyframes) {
|
||||
let valueKeyframes = keyframes[valueName];
|
||||
if (!Array.isArray(valueKeyframes)) {
|
||||
valueKeyframes = [valueKeyframes];
|
||||
}
|
||||
const valueOptions = {
|
||||
...motionDom.getValueTransition(elementTransition, valueName),
|
||||
};
|
||||
valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
|
||||
valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
|
||||
/**
|
||||
* If there's an existing animation playing on this element then stop it
|
||||
* before creating a new one.
|
||||
*/
|
||||
const map = motionDom.getAnimationMap(element);
|
||||
const key = motionDom.animationMapKey(valueName, valueOptions.pseudoElement || "");
|
||||
const currentAnimation = map.get(key);
|
||||
currentAnimation && currentAnimation.stop();
|
||||
animationDefinitions.push({
|
||||
map,
|
||||
key,
|
||||
unresolvedKeyframes: valueKeyframes,
|
||||
options: {
|
||||
...valueOptions,
|
||||
element,
|
||||
name: valueName,
|
||||
allowFlatten: !elementTransition.type && !elementTransition.ease,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Step 2: Resolve keyframes (read)
|
||||
*/
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
|
||||
const { element, name, pseudoElement } = animationOptions;
|
||||
if (!pseudoElement && unresolvedKeyframes[0] === null) {
|
||||
unresolvedKeyframes[0] = motionDom.getComputedStyle(element, name);
|
||||
}
|
||||
motionDom.fillWildcards(unresolvedKeyframes);
|
||||
motionDom.applyPxDefaults(unresolvedKeyframes, name);
|
||||
/**
|
||||
* If we only have one keyframe, explicitly read the initial keyframe
|
||||
* from the computed style. This is to ensure consistency with WAAPI behaviour
|
||||
* for restarting animations, for instance .play() after finish, when it
|
||||
* has one vs two keyframes.
|
||||
*/
|
||||
if (!pseudoElement && unresolvedKeyframes.length < 2) {
|
||||
unresolvedKeyframes.unshift(motionDom.getComputedStyle(element, name));
|
||||
}
|
||||
animationOptions.keyframes = unresolvedKeyframes;
|
||||
}
|
||||
/**
|
||||
* Step 3: Create new animations (write)
|
||||
*/
|
||||
const animations = [];
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { map, key, options: animationOptions } = animationDefinitions[i];
|
||||
const animation = new motionDom.NativeAnimation(animationOptions);
|
||||
map.set(key, animation);
|
||||
animation.finished.finally(() => map.delete(key));
|
||||
animations.push(animation);
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
function animateSequence(definition, options) {
|
||||
const animations = [];
|
||||
createAnimationsFromSequence(definition, options).forEach(({ keyframes, transition }, element) => {
|
||||
animations.push(...animateElements(element, keyframes, transition));
|
||||
});
|
||||
return new motionDom.GroupAnimationWithThen(animations);
|
||||
}
|
||||
|
||||
const createScopedWaapiAnimate = (scope) => {
|
||||
function scopedAnimate(elementOrSelector, keyframes, options) {
|
||||
return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
|
||||
}
|
||||
return scopedAnimate;
|
||||
};
|
||||
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
|
||||
|
||||
exports.animate = animateMini;
|
||||
exports.animateSequence = animateSequence;
|
||||
//# sourceMappingURL=dom-mini.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+1354
@@ -0,0 +1,1354 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var motionDom = require('motion-dom');
|
||||
var motionUtils = require('motion-utils');
|
||||
|
||||
function isDOMKeyframes(keyframes) {
|
||||
return typeof keyframes === "object" && !Array.isArray(keyframes);
|
||||
}
|
||||
|
||||
function resolveSubjects(subject, keyframes, scope, selectorCache) {
|
||||
if (subject == null) {
|
||||
return [];
|
||||
}
|
||||
if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
|
||||
return motionDom.resolveElements(subject, scope, selectorCache);
|
||||
}
|
||||
else if (subject instanceof NodeList) {
|
||||
return Array.from(subject);
|
||||
}
|
||||
else if (Array.isArray(subject)) {
|
||||
return subject.filter((s) => s != null);
|
||||
}
|
||||
else {
|
||||
return [subject];
|
||||
}
|
||||
}
|
||||
|
||||
function calculateRepeatDuration(duration, repeat, repeatDelay) {
|
||||
return duration * (repeat + 1) + repeatDelay * repeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a absolute or relative time definition and current/prev time state of the sequence,
|
||||
* calculate an absolute time for the next keyframes.
|
||||
*/
|
||||
function calcNextTime(current, next, prev, labels) {
|
||||
if (typeof next === "number") {
|
||||
return next;
|
||||
}
|
||||
else if (next.startsWith("-") || next.startsWith("+")) {
|
||||
return Math.max(0, current + parseFloat(next));
|
||||
}
|
||||
else if (next === "<") {
|
||||
return prev;
|
||||
}
|
||||
else if (next.startsWith("<")) {
|
||||
return Math.max(0, prev + parseFloat(next.slice(1)));
|
||||
}
|
||||
else {
|
||||
return labels.get(next) ?? current;
|
||||
}
|
||||
}
|
||||
|
||||
function eraseKeyframes(sequence, startTime, endTime) {
|
||||
for (let i = 0; i < sequence.length; i++) {
|
||||
const keyframe = sequence[i];
|
||||
if (keyframe.at > startTime && keyframe.at < endTime) {
|
||||
motionUtils.removeItem(sequence, keyframe);
|
||||
// If we remove this item we have to push the pointer back one
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
|
||||
/**
|
||||
* Erase every existing value between currentTime and targetTime,
|
||||
* this will essentially splice this timeline into any currently
|
||||
* defined ones.
|
||||
*/
|
||||
eraseKeyframes(sequence, startTime, endTime);
|
||||
for (let i = 0; i < keyframes.length; i++) {
|
||||
sequence.push({
|
||||
value: keyframes[i],
|
||||
at: motionDom.mixNumber(startTime, endTime, offset[i]),
|
||||
easing: motionUtils.getEasingForSegment(easing, i),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an array of times that represent repeated keyframes. For instance
|
||||
* if we have original times of [0, 0.5, 1] then our repeated times will
|
||||
* be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
|
||||
* down to a 0-1 scale.
|
||||
*
|
||||
* `repeatDelayUnits` is the repeatDelay expressed in units of a single
|
||||
* iteration's duration, so the total span equals `(repeat + 1) + repeat * repeatDelayUnits`.
|
||||
*/
|
||||
function normalizeTimes(times, repeat, repeatDelayUnits = 0) {
|
||||
const totalUnits = repeat + 1 + repeat * repeatDelayUnits;
|
||||
for (let i = 0; i < times.length; i++) {
|
||||
times[i] = times[i] / totalUnits;
|
||||
}
|
||||
}
|
||||
|
||||
function compareByTime(a, b) {
|
||||
if (a.at === b.at) {
|
||||
if (a.value === null)
|
||||
return 1;
|
||||
if (b.value === null)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return a.at - b.at;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSegmentEasing = "easeInOut";
|
||||
const MAX_REPEAT = 20;
|
||||
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
|
||||
const defaultDuration = defaultTransition.duration || 0.3;
|
||||
const animationDefinitions = new Map();
|
||||
const sequences = new Map();
|
||||
const elementCache = {};
|
||||
const timeLabels = new Map();
|
||||
let prevTime = 0;
|
||||
let currentTime = 0;
|
||||
let totalDuration = 0;
|
||||
/**
|
||||
* Build the timeline by mapping over the sequence array and converting
|
||||
* the definitions into keyframes and offsets with absolute time values.
|
||||
* These will later get converted into relative offsets in a second pass.
|
||||
*/
|
||||
for (let i = 0; i < sequence.length; i++) {
|
||||
const segment = sequence[i];
|
||||
/**
|
||||
* If this is a timeline label, mark it and skip the rest of this iteration.
|
||||
*/
|
||||
if (typeof segment === "string") {
|
||||
timeLabels.set(segment, currentTime);
|
||||
continue;
|
||||
}
|
||||
else if (!Array.isArray(segment)) {
|
||||
timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
|
||||
continue;
|
||||
}
|
||||
let [subject, keyframes, transition = {}] = segment;
|
||||
/**
|
||||
* If a relative or absolute time value has been specified we need to resolve
|
||||
* it in relation to the currentTime.
|
||||
*/
|
||||
if (transition.at !== undefined) {
|
||||
currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
|
||||
}
|
||||
/**
|
||||
* Keep track of the maximum duration in this definition. This will be
|
||||
* applied to currentTime once the definition has been parsed.
|
||||
*/
|
||||
let maxDuration = 0;
|
||||
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
|
||||
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
|
||||
const { delay = 0, times = motionDom.defaultOffset(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
|
||||
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
|
||||
/**
|
||||
* Resolve stagger() if defined.
|
||||
*/
|
||||
const calculatedDelay = typeof delay === "function"
|
||||
? delay(elementIndex, numSubjects)
|
||||
: delay;
|
||||
/**
|
||||
* If this animation should and can use a spring, generate a spring easing function.
|
||||
*/
|
||||
const numKeyframes = valueKeyframesAsList.length;
|
||||
const createGenerator = motionDom.isGenerator(type)
|
||||
? type
|
||||
: generators?.[type || "keyframes"];
|
||||
if (numKeyframes <= 2 && createGenerator) {
|
||||
/**
|
||||
* As we're creating an easing function from a spring,
|
||||
* ideally we want to generate it using the real distance
|
||||
* between the two keyframes. However this isn't always
|
||||
* possible - in these situations we use 0-100.
|
||||
*/
|
||||
let absoluteDelta = 100;
|
||||
if (numKeyframes === 2 &&
|
||||
isNumberKeyframesArray(valueKeyframesAsList)) {
|
||||
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
|
||||
absoluteDelta = Math.abs(delta);
|
||||
}
|
||||
const springTransition = {
|
||||
...defaultTransition,
|
||||
...remainingTransition,
|
||||
};
|
||||
if (duration !== undefined) {
|
||||
springTransition.duration = motionUtils.secondsToMilliseconds(duration);
|
||||
}
|
||||
const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
|
||||
ease = springEasing.ease;
|
||||
duration = springEasing.duration;
|
||||
}
|
||||
duration ?? (duration = defaultDuration);
|
||||
const startTime = currentTime + calculatedDelay;
|
||||
/**
|
||||
* If there's only one time offset of 0, fill in a second with length 1
|
||||
*/
|
||||
if (times.length === 1 && times[0] === 0) {
|
||||
times[1] = 1;
|
||||
}
|
||||
/**
|
||||
* Fill out if offset if fewer offsets than keyframes
|
||||
*/
|
||||
const remainder = times.length - valueKeyframesAsList.length;
|
||||
remainder > 0 && motionDom.fillOffset(times, remainder);
|
||||
/**
|
||||
* If only one value has been set, ie [1], push a null to the start of
|
||||
* the keyframe array. This will let us mark a keyframe at this point
|
||||
* that will later be hydrated with the previous value.
|
||||
*/
|
||||
valueKeyframesAsList.length === 1 &&
|
||||
valueKeyframesAsList.unshift(null);
|
||||
/**
|
||||
* Segments can't express `repeat: Infinity` or very large
|
||||
* counts — they'd leave dead time after the segment or
|
||||
* explode the keyframe array. Ignore with a warning.
|
||||
*/
|
||||
if (repeat) {
|
||||
motionUtils.warning(repeat < MAX_REPEAT, `Sequence segments can't repeat ${repeat} times — ignoring repeat option. Use a value below ${MAX_REPEAT} or apply repeat at the sequence level instead.`);
|
||||
}
|
||||
if (repeat && repeat < MAX_REPEAT) {
|
||||
/**
|
||||
* Express repeatDelay in units of a single iteration's duration
|
||||
* so it can be added to the per-iteration time offsets below
|
||||
* before they're normalized to 0-1.
|
||||
*/
|
||||
const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
|
||||
duration = calculateRepeatDuration(duration, repeat, repeatDelay);
|
||||
const originalKeyframes = [...valueKeyframesAsList];
|
||||
const originalTimes = [...times];
|
||||
ease = Array.isArray(ease) ? [...ease] : [ease];
|
||||
const originalEase = [...ease];
|
||||
/**
|
||||
* For reverse/mirror, alternate iterations play the segment
|
||||
* backwards. mirror matches JSAnimation's mirroredGenerator:
|
||||
* reversed keyframes, easings unchanged. reverse matches
|
||||
* JSAnimation's iterationProgress = 1 - p: reversed
|
||||
* keyframes, easing array reversed AND each function easing
|
||||
* mapped through reverseEasing (string easings unchanged —
|
||||
* they're resolved later by the keyframes engine).
|
||||
*/
|
||||
const isFlipping = repeatType === "reverse" || repeatType === "mirror";
|
||||
let flippedKeyframes = originalKeyframes;
|
||||
let flippedEases = originalEase;
|
||||
if (isFlipping) {
|
||||
flippedKeyframes = [...originalKeyframes].reverse();
|
||||
if (repeatType === "reverse") {
|
||||
flippedEases = [...originalEase]
|
||||
.reverse()
|
||||
.map((e) => typeof e === "function"
|
||||
? motionUtils.reverseEasing(e)
|
||||
: e);
|
||||
}
|
||||
}
|
||||
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
|
||||
const isFlipped = isFlipping && repeatIndex % 2 === 0;
|
||||
const iterKeyframes = isFlipped
|
||||
? flippedKeyframes
|
||||
: originalKeyframes;
|
||||
const iterEase = isFlipped ? flippedEases : originalEase;
|
||||
const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
|
||||
/**
|
||||
* If repeatDelay is set, hold the previous iteration's
|
||||
* final value through the delay by inserting a keyframe
|
||||
* at the moment the next iteration begins.
|
||||
*/
|
||||
if (repeatDelayUnits > 0) {
|
||||
valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
|
||||
times.push(iterStartOffset);
|
||||
ease.push("linear");
|
||||
}
|
||||
valueKeyframesAsList.push(...iterKeyframes);
|
||||
for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
|
||||
times.push(originalTimes[keyframeIndex] + iterStartOffset);
|
||||
ease.push(keyframeIndex === 0
|
||||
? "linear"
|
||||
: motionUtils.getEasingForSegment(iterEase, keyframeIndex - 1));
|
||||
}
|
||||
}
|
||||
normalizeTimes(times, repeat, repeatDelayUnits);
|
||||
}
|
||||
const targetTime = startTime + duration;
|
||||
/**
|
||||
* Add keyframes, mapping offsets to absolute time.
|
||||
*/
|
||||
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
|
||||
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
|
||||
totalDuration = Math.max(targetTime, totalDuration);
|
||||
};
|
||||
if (motionDom.isMotionValue(subject)) {
|
||||
const subjectSequence = getSubjectSequence(subject, sequences);
|
||||
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
|
||||
}
|
||||
else {
|
||||
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
|
||||
const numSubjects = subjects.length;
|
||||
/**
|
||||
* For every element in this segment, process the defined values.
|
||||
*/
|
||||
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
|
||||
/**
|
||||
* Cast necessary, but we know these are of this type
|
||||
*/
|
||||
keyframes = keyframes;
|
||||
transition = transition;
|
||||
const thisSubject = subjects[subjectIndex];
|
||||
const subjectSequence = getSubjectSequence(thisSubject, sequences);
|
||||
for (const key in keyframes) {
|
||||
resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
prevTime = currentTime;
|
||||
currentTime += maxDuration;
|
||||
}
|
||||
/**
|
||||
* For every element and value combination create a new animation.
|
||||
*/
|
||||
sequences.forEach((valueSequences, element) => {
|
||||
for (const key in valueSequences) {
|
||||
const valueSequence = valueSequences[key];
|
||||
/**
|
||||
* Arrange all the keyframes in ascending time order.
|
||||
*/
|
||||
valueSequence.sort(compareByTime);
|
||||
const keyframes = [];
|
||||
const valueOffset = [];
|
||||
const valueEasing = [];
|
||||
/**
|
||||
* For each keyframe, translate absolute times into
|
||||
* relative offsets based on the total duration of the timeline.
|
||||
*/
|
||||
for (let i = 0; i < valueSequence.length; i++) {
|
||||
const { at, value, easing } = valueSequence[i];
|
||||
keyframes.push(value);
|
||||
valueOffset.push(motionUtils.progress(0, totalDuration, at));
|
||||
valueEasing.push(easing || "easeOut");
|
||||
}
|
||||
/**
|
||||
* If the first keyframe doesn't land on offset: 0
|
||||
* provide one by duplicating the initial keyframe. This ensures
|
||||
* it snaps to the first keyframe when the animation starts.
|
||||
*/
|
||||
if (valueOffset[0] !== 0) {
|
||||
valueOffset.unshift(0);
|
||||
keyframes.unshift(keyframes[0]);
|
||||
valueEasing.unshift(defaultSegmentEasing);
|
||||
}
|
||||
/**
|
||||
* If the last keyframe doesn't land on offset: 1
|
||||
* provide one with a null wildcard value. This will ensure it
|
||||
* stays static until the end of the animation.
|
||||
*/
|
||||
if (valueOffset[valueOffset.length - 1] !== 1) {
|
||||
valueOffset.push(1);
|
||||
keyframes.push(null);
|
||||
}
|
||||
if (!animationDefinitions.has(element)) {
|
||||
animationDefinitions.set(element, {
|
||||
keyframes: {},
|
||||
transition: {},
|
||||
});
|
||||
}
|
||||
const definition = animationDefinitions.get(element);
|
||||
definition.keyframes[key] = keyframes;
|
||||
/**
|
||||
* Exclude `type` from defaultTransition since springs have been
|
||||
* converted to duration-based easing functions in resolveValueSequence.
|
||||
* Including `type: "spring"` would cause JSAnimation to error when
|
||||
* the merged keyframes array has more than 2 keyframes.
|
||||
*/
|
||||
const { type: _type, ...remainingDefaultTransition } = defaultTransition;
|
||||
definition.transition[key] = {
|
||||
...remainingDefaultTransition,
|
||||
duration: totalDuration,
|
||||
ease: valueEasing,
|
||||
times: valueOffset,
|
||||
...sequenceTransition,
|
||||
};
|
||||
}
|
||||
});
|
||||
return animationDefinitions;
|
||||
}
|
||||
function getSubjectSequence(subject, sequences) {
|
||||
!sequences.has(subject) && sequences.set(subject, {});
|
||||
return sequences.get(subject);
|
||||
}
|
||||
function getValueSequence(name, sequences) {
|
||||
if (!sequences[name])
|
||||
sequences[name] = [];
|
||||
return sequences[name];
|
||||
}
|
||||
function keyframesAsList(keyframes) {
|
||||
return Array.isArray(keyframes) ? keyframes : [keyframes];
|
||||
}
|
||||
function getValueTransition(transition, key) {
|
||||
return transition && transition[key]
|
||||
? {
|
||||
...transition,
|
||||
...transition[key],
|
||||
}
|
||||
: { ...transition };
|
||||
}
|
||||
const isNumber = (keyframe) => typeof keyframe === "number";
|
||||
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
|
||||
|
||||
function createDOMVisualElement(element) {
|
||||
const options = {
|
||||
presenceContext: null,
|
||||
props: {},
|
||||
visualState: {
|
||||
renderState: {
|
||||
transform: {},
|
||||
transformOrigin: {},
|
||||
style: {},
|
||||
vars: {},
|
||||
attrs: {},
|
||||
},
|
||||
latestValues: {},
|
||||
},
|
||||
};
|
||||
const node = motionDom.isSVGElement(element) && !motionDom.isSVGSVGElement(element)
|
||||
? new motionDom.SVGVisualElement(options)
|
||||
: new motionDom.HTMLVisualElement(options);
|
||||
node.mount(element);
|
||||
motionDom.visualElementStore.set(element, node);
|
||||
}
|
||||
function createObjectVisualElement(subject) {
|
||||
const options = {
|
||||
presenceContext: null,
|
||||
props: {},
|
||||
visualState: {
|
||||
renderState: {
|
||||
output: {},
|
||||
},
|
||||
latestValues: {},
|
||||
},
|
||||
};
|
||||
const node = new motionDom.ObjectVisualElement(options);
|
||||
node.mount(subject);
|
||||
motionDom.visualElementStore.set(subject, node);
|
||||
}
|
||||
|
||||
function isSingleValue(subject, keyframes) {
|
||||
return (motionDom.isMotionValue(subject) ||
|
||||
typeof subject === "number" ||
|
||||
(typeof subject === "string" && !isDOMKeyframes(keyframes)));
|
||||
}
|
||||
/**
|
||||
* Implementation
|
||||
*/
|
||||
function animateSubject(subject, keyframes, options, scope) {
|
||||
const animations = [];
|
||||
if (isSingleValue(subject, keyframes)) {
|
||||
animations.push(motionDom.animateSingleValue(subject, isDOMKeyframes(keyframes)
|
||||
? keyframes.default || keyframes
|
||||
: keyframes, options ? options.default || options : options));
|
||||
}
|
||||
else {
|
||||
// Gracefully handle null/undefined subjects (e.g., from querySelector returning null)
|
||||
if (subject == null) {
|
||||
return animations;
|
||||
}
|
||||
const subjects = resolveSubjects(subject, keyframes, scope);
|
||||
const numSubjects = subjects.length;
|
||||
motionUtils.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
|
||||
for (let i = 0; i < numSubjects; i++) {
|
||||
const thisSubject = subjects[i];
|
||||
const createVisualElement = thisSubject instanceof Element
|
||||
? createDOMVisualElement
|
||||
: createObjectVisualElement;
|
||||
if (!motionDom.visualElementStore.has(thisSubject)) {
|
||||
createVisualElement(thisSubject);
|
||||
}
|
||||
const visualElement = motionDom.visualElementStore.get(thisSubject);
|
||||
const transition = { ...options };
|
||||
/**
|
||||
* Resolve stagger function if provided.
|
||||
*/
|
||||
if ("delay" in transition &&
|
||||
typeof transition.delay === "function") {
|
||||
transition.delay = transition.delay(i, numSubjects);
|
||||
}
|
||||
animations.push(...motionDom.animateTarget(visualElement, { ...keyframes, transition }, {}));
|
||||
}
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
function animateSequence(sequence, options, scope) {
|
||||
const animations = [];
|
||||
/**
|
||||
* Pre-process: replace function segments with MotionValue segments,
|
||||
* subscribe callbacks immediately
|
||||
*/
|
||||
const processedSequence = sequence.map((segment) => {
|
||||
if (Array.isArray(segment) && typeof segment[0] === "function") {
|
||||
const callback = segment[0];
|
||||
const mv = motionDom.motionValue(0);
|
||||
mv.on("change", callback);
|
||||
if (segment.length === 1) {
|
||||
return [mv, [0, 1]];
|
||||
}
|
||||
else if (segment.length === 2) {
|
||||
return [mv, [0, 1], segment[1]];
|
||||
}
|
||||
else {
|
||||
return [mv, segment[1], segment[2]];
|
||||
}
|
||||
}
|
||||
return segment;
|
||||
});
|
||||
const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring: motionDom.spring });
|
||||
animationDefinitions.forEach(({ keyframes, transition }, subject) => {
|
||||
animations.push(...animateSubject(subject, keyframes, transition));
|
||||
});
|
||||
return animations;
|
||||
}
|
||||
|
||||
function isSequence(value) {
|
||||
return Array.isArray(value) && value.some(Array.isArray);
|
||||
}
|
||||
/**
|
||||
* Creates an animation function that is optionally scoped
|
||||
* to a specific element.
|
||||
*/
|
||||
function createScopedAnimate(options = {}) {
|
||||
const { scope, reduceMotion, skipAnimations } = options;
|
||||
/**
|
||||
* Implementation
|
||||
*/
|
||||
function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
|
||||
let animations = [];
|
||||
let animationOnComplete;
|
||||
const inherited = {};
|
||||
if (reduceMotion !== undefined)
|
||||
inherited.reduceMotion = reduceMotion;
|
||||
if (skipAnimations !== undefined)
|
||||
inherited.skipAnimations = skipAnimations;
|
||||
if (isSequence(subjectOrSequence)) {
|
||||
const { onComplete, ...sequenceOptions } = optionsOrKeyframes || {};
|
||||
if (typeof onComplete === "function") {
|
||||
animationOnComplete = onComplete;
|
||||
}
|
||||
animations = animateSequence(subjectOrSequence, { ...inherited, ...sequenceOptions }, scope);
|
||||
}
|
||||
else {
|
||||
// Extract top-level onComplete so it doesn't get applied per-value
|
||||
const { onComplete, ...rest } = options || {};
|
||||
if (typeof onComplete === "function") {
|
||||
animationOnComplete = onComplete;
|
||||
}
|
||||
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, { ...inherited, ...rest }, scope);
|
||||
}
|
||||
const animation = new motionDom.GroupAnimationWithThen(animations);
|
||||
if (animationOnComplete) {
|
||||
animation.finished.then(animationOnComplete);
|
||||
}
|
||||
if (scope) {
|
||||
scope.animations.push(animation);
|
||||
animation.finished.then(() => {
|
||||
motionUtils.removeItem(scope.animations, animation);
|
||||
});
|
||||
}
|
||||
return animation;
|
||||
}
|
||||
return scopedAnimate;
|
||||
}
|
||||
const animate = createScopedAnimate();
|
||||
|
||||
function animateElements(elementOrSelector, keyframes, options, scope) {
|
||||
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
|
||||
if (elementOrSelector == null) {
|
||||
return [];
|
||||
}
|
||||
const elements = motionDom.resolveElements(elementOrSelector, scope);
|
||||
const numElements = elements.length;
|
||||
motionUtils.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
|
||||
/**
|
||||
* WAAPI doesn't support interrupting animations.
|
||||
*
|
||||
* Therefore, starting animations requires a three-step process:
|
||||
* 1. Stop existing animations (write styles to DOM)
|
||||
* 2. Resolve keyframes (read styles from DOM)
|
||||
* 3. Create new animations (write styles to DOM)
|
||||
*
|
||||
* The hybrid `animate()` function uses AsyncAnimation to resolve
|
||||
* keyframes before creating new animations, which removes style
|
||||
* thrashing. Here, we have much stricter filesize constraints.
|
||||
* Therefore we do this in a synchronous way that ensures that
|
||||
* at least within `animate()` calls there is no style thrashing.
|
||||
*
|
||||
* In the motion-native-animate-mini-interrupt benchmark this
|
||||
* was 80% faster than a single loop.
|
||||
*/
|
||||
const animationDefinitions = [];
|
||||
/**
|
||||
* Step 1: Build options and stop existing animations (write)
|
||||
*/
|
||||
for (let i = 0; i < numElements; i++) {
|
||||
const element = elements[i];
|
||||
const elementTransition = { ...options };
|
||||
/**
|
||||
* Resolve stagger function if provided.
|
||||
*/
|
||||
if (typeof elementTransition.delay === "function") {
|
||||
elementTransition.delay = elementTransition.delay(i, numElements);
|
||||
}
|
||||
for (const valueName in keyframes) {
|
||||
let valueKeyframes = keyframes[valueName];
|
||||
if (!Array.isArray(valueKeyframes)) {
|
||||
valueKeyframes = [valueKeyframes];
|
||||
}
|
||||
const valueOptions = {
|
||||
...motionDom.getValueTransition(elementTransition, valueName),
|
||||
};
|
||||
valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
|
||||
valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
|
||||
/**
|
||||
* If there's an existing animation playing on this element then stop it
|
||||
* before creating a new one.
|
||||
*/
|
||||
const map = motionDom.getAnimationMap(element);
|
||||
const key = motionDom.animationMapKey(valueName, valueOptions.pseudoElement || "");
|
||||
const currentAnimation = map.get(key);
|
||||
currentAnimation && currentAnimation.stop();
|
||||
animationDefinitions.push({
|
||||
map,
|
||||
key,
|
||||
unresolvedKeyframes: valueKeyframes,
|
||||
options: {
|
||||
...valueOptions,
|
||||
element,
|
||||
name: valueName,
|
||||
allowFlatten: !elementTransition.type && !elementTransition.ease,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Step 2: Resolve keyframes (read)
|
||||
*/
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
|
||||
const { element, name, pseudoElement } = animationOptions;
|
||||
if (!pseudoElement && unresolvedKeyframes[0] === null) {
|
||||
unresolvedKeyframes[0] = motionDom.getComputedStyle(element, name);
|
||||
}
|
||||
motionDom.fillWildcards(unresolvedKeyframes);
|
||||
motionDom.applyPxDefaults(unresolvedKeyframes, name);
|
||||
/**
|
||||
* If we only have one keyframe, explicitly read the initial keyframe
|
||||
* from the computed style. This is to ensure consistency with WAAPI behaviour
|
||||
* for restarting animations, for instance .play() after finish, when it
|
||||
* has one vs two keyframes.
|
||||
*/
|
||||
if (!pseudoElement && unresolvedKeyframes.length < 2) {
|
||||
unresolvedKeyframes.unshift(motionDom.getComputedStyle(element, name));
|
||||
}
|
||||
animationOptions.keyframes = unresolvedKeyframes;
|
||||
}
|
||||
/**
|
||||
* Step 3: Create new animations (write)
|
||||
*/
|
||||
const animations = [];
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { map, key, options: animationOptions } = animationDefinitions[i];
|
||||
const animation = new motionDom.NativeAnimation(animationOptions);
|
||||
map.set(key, animation);
|
||||
animation.finished.finally(() => map.delete(key));
|
||||
animations.push(animation);
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
const createScopedWaapiAnimate = (scope) => {
|
||||
function scopedAnimate(elementOrSelector, keyframes, options) {
|
||||
return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
|
||||
}
|
||||
return scopedAnimate;
|
||||
};
|
||||
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
|
||||
|
||||
function canUseNativeTimeline(target) {
|
||||
if (typeof window === "undefined")
|
||||
return false;
|
||||
return target ? motionDom.supportsViewTimeline() : motionDom.supportsScrollTimeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = motionUtils.progress(0, axis.scrollLength, axis.current);
|
||||
const elapsed = time - prevTime;
|
||||
axis.velocity =
|
||||
elapsed > maxElapsed
|
||||
? 0
|
||||
: motionUtils.velocityPerSecond(axis.current - prev, elapsed);
|
||||
}
|
||||
function updateScrollInfo(element, info, time) {
|
||||
updateAxisInfo(element, "x", info, time);
|
||||
updateAxisInfo(element, "y", info, time);
|
||||
info.time = time;
|
||||
}
|
||||
|
||||
function calcInset(element, container) {
|
||||
const inset = { x: 0, y: 0 };
|
||||
let current = element;
|
||||
while (current && current !== container) {
|
||||
if (motionDom.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const ScrollOffset = {
|
||||
Enter: [
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
],
|
||||
Exit: [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
],
|
||||
Any: [
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
],
|
||||
All: [
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
],
|
||||
};
|
||||
|
||||
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 = motionDom.interpolate(info[axis].offset, motionDom.defaultOffset(offsetDefinition), { clamp: false });
|
||||
info[axis].interpolatorOffsets = [...info[axis].offset];
|
||||
}
|
||||
info[axis].progress = motionUtils.clamp(0, 1, info[axis].interpolate(info[axis].current));
|
||||
}
|
||||
|
||||
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. The document scrolling element
|
||||
* is exempt: offsetParent measurements naturally resolve relative to the document.
|
||||
*/
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (container &&
|
||||
target &&
|
||||
target !== container &&
|
||||
container !== document.documentElement &&
|
||||
container !== document.scrollingElement &&
|
||||
container !== document.body) {
|
||||
motionUtils.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),
|
||||
};
|
||||
}
|
||||
|
||||
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 motionUtils.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(motionDom.frameData.timestamp);
|
||||
}
|
||||
motionDom.frame.preUpdate(notifyAll);
|
||||
};
|
||||
const notifyAll = () => {
|
||||
for (const handler of containerHandlers) {
|
||||
handler.notify();
|
||||
}
|
||||
};
|
||||
const listener = () => motionDom.frame.read(measureAll);
|
||||
scrollListeners.set(container, listener);
|
||||
const target = getEventTarget(container);
|
||||
window.addEventListener("resize", listener);
|
||||
if (container !== document.documentElement) {
|
||||
resizeListeners.set(container, motionDom.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 = motionDom.frame.read(checkScrollDimensions, true);
|
||||
dimensionCheckProcesses.set(container, dimensionCheckProcess);
|
||||
}
|
||||
const listener = scrollListeners.get(container);
|
||||
motionDom.frame.read(listener, false, true);
|
||||
return () => {
|
||||
motionDom.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) {
|
||||
motionDom.cancelFrame(dimensionCheckProcess);
|
||||
dimensionCheckProcesses.delete(container);
|
||||
}
|
||||
scrollSize.delete(container);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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 motionDom.observeTimeline((progress) => {
|
||||
valueAnimation.time =
|
||||
valueAnimation.iterationDuration * progress;
|
||||
}, timeline);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we only support element tracking with `scrollInfo`, though in
|
||||
* the future we can also offer ViewTimeline support.
|
||||
*/
|
||||
function isElementTracking(options) {
|
||||
return options && (options.target || options.offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) || isElementTracking(options)) {
|
||||
return scrollInfo((info) => {
|
||||
onScroll(info[options.axis].progress, info);
|
||||
}, options);
|
||||
}
|
||||
else {
|
||||
return motionDom.observeTimeline(onScroll, getTimeline(options));
|
||||
}
|
||||
}
|
||||
|
||||
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
|
||||
if (!container)
|
||||
return motionUtils.noop;
|
||||
const optionsWithDefaults = { axis, container, ...options };
|
||||
return typeof onScroll === "function"
|
||||
? attachToFunction(onScroll, optionsWithDefaults)
|
||||
: attachToAnimation(onScroll, optionsWithDefaults);
|
||||
}
|
||||
|
||||
const thresholds = {
|
||||
some: 0,
|
||||
all: 1,
|
||||
};
|
||||
function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) {
|
||||
const elements = motionDom.resolveElements(elementOrSelector);
|
||||
const activeIntersections = new WeakMap();
|
||||
const onIntersectionChange = (entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const onEnd = activeIntersections.get(entry.target);
|
||||
/**
|
||||
* If there's no change to the intersection, we don't need to
|
||||
* do anything here.
|
||||
*/
|
||||
if (entry.isIntersecting === Boolean(onEnd))
|
||||
return;
|
||||
if (entry.isIntersecting) {
|
||||
const newOnEnd = onStart(entry.target, entry);
|
||||
if (typeof newOnEnd === "function") {
|
||||
activeIntersections.set(entry.target, newOnEnd);
|
||||
}
|
||||
else {
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
else if (typeof onEnd === "function") {
|
||||
onEnd(entry);
|
||||
activeIntersections.delete(entry.target);
|
||||
}
|
||||
});
|
||||
};
|
||||
const observer = new IntersectionObserver(onIntersectionChange, {
|
||||
root,
|
||||
rootMargin,
|
||||
threshold: typeof amount === "number" ? amount : thresholds[amount],
|
||||
});
|
||||
elements.forEach((element) => observer.observe(element));
|
||||
return () => observer.disconnect();
|
||||
}
|
||||
|
||||
const distance = (a, b) => Math.abs(a - b);
|
||||
function distance2D(a, b) {
|
||||
// Multi-dimensional
|
||||
const xDelta = distance(a.x, b.x);
|
||||
const yDelta = distance(a.y, b.y);
|
||||
return Math.sqrt(xDelta ** 2 + yDelta ** 2);
|
||||
}
|
||||
|
||||
Object.defineProperty(exports, "delay", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.delayInSeconds; }
|
||||
});
|
||||
exports.animate = animate;
|
||||
exports.animateMini = animateMini;
|
||||
exports.createScopedAnimate = createScopedAnimate;
|
||||
exports.distance = distance;
|
||||
exports.distance2D = distance2D;
|
||||
exports.inView = inView;
|
||||
exports.scroll = scroll;
|
||||
exports.scrollInfo = scrollInfo;
|
||||
Object.keys(motionDom).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom[k]; }
|
||||
});
|
||||
});
|
||||
Object.keys(motionUtils).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
||||
enumerable: true,
|
||||
get: function () { return motionUtils[k]; }
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=dom.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+1660
@@ -0,0 +1,1660 @@
|
||||
'use strict';
|
||||
|
||||
var motionDom = require('motion-dom');
|
||||
var React = require('react');
|
||||
var index = require('./index-6W16WHlG.js');
|
||||
var motionUtils = require('motion-utils');
|
||||
var jsxRuntime = require('react/jsx-runtime');
|
||||
|
||||
/**
|
||||
* When a component is the child of `AnimatePresence`, it can use `usePresence`
|
||||
* to access information about whether it's still present in the React tree.
|
||||
*
|
||||
* ```jsx
|
||||
* import { usePresence } from "framer-motion"
|
||||
*
|
||||
* export const Component = () => {
|
||||
* const [isPresent, safeToRemove] = usePresence()
|
||||
*
|
||||
* useEffect(() => {
|
||||
* !isPresent && setTimeout(safeToRemove, 1000)
|
||||
* }, [isPresent])
|
||||
*
|
||||
* return <div />
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* If `isPresent` is `false`, it means that a component has been removed from the tree,
|
||||
* but `AnimatePresence` won't really remove it until `safeToRemove` has been called.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function usePresence(subscribe = true) {
|
||||
const context = React.useContext(index.PresenceContext);
|
||||
if (context === null)
|
||||
return [true, null];
|
||||
const { isPresent, onExitComplete, register } = context;
|
||||
// It's safe to call the following hooks conditionally (after an early return) because the context will always
|
||||
// either be null or non-null for the lifespan of the component.
|
||||
const id = React.useId();
|
||||
React.useEffect(() => {
|
||||
if (subscribe) {
|
||||
return register(id);
|
||||
}
|
||||
}, [subscribe]);
|
||||
const safeToRemove = React.useCallback(() => subscribe && onExitComplete && onExitComplete(id), [id, onExitComplete, subscribe]);
|
||||
return !isPresent && onExitComplete ? [false, safeToRemove] : [true];
|
||||
}
|
||||
/**
|
||||
* Similar to `usePresence`, except `useIsPresent` simply returns whether or not the component is present.
|
||||
* There is no `safeToRemove` function.
|
||||
*
|
||||
* ```jsx
|
||||
* import { useIsPresent } from "framer-motion"
|
||||
*
|
||||
* export const Component = () => {
|
||||
* const isPresent = useIsPresent()
|
||||
*
|
||||
* useEffect(() => {
|
||||
* !isPresent && console.log("I've been removed!")
|
||||
* }, [isPresent])
|
||||
*
|
||||
* return <div />
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useIsPresent() {
|
||||
return isPresent(React.useContext(index.PresenceContext));
|
||||
}
|
||||
function isPresent(context) {
|
||||
return context === null ? true : context.isPresent;
|
||||
}
|
||||
|
||||
const createDomVisualElement = (Component, options) => {
|
||||
/**
|
||||
* Use explicit isSVG override if provided, otherwise auto-detect
|
||||
*/
|
||||
const isSVG = options.isSVG ?? index.isSVGComponent(Component);
|
||||
return isSVG
|
||||
? new motionDom.SVGVisualElement(options)
|
||||
: new motionDom.HTMLVisualElement(options, {
|
||||
allowProjection: Component !== React.Fragment,
|
||||
});
|
||||
};
|
||||
|
||||
class AnimationFeature extends motionDom.Feature {
|
||||
/**
|
||||
* We dynamically generate the AnimationState manager as it contains a reference
|
||||
* to the underlying animation library. We only want to load that if we load this,
|
||||
* so people can optionally code split it out using the `m` component.
|
||||
*/
|
||||
constructor(node) {
|
||||
super(node);
|
||||
node.animationState || (node.animationState = motionDom.createAnimationState(node));
|
||||
}
|
||||
updateAnimationControlsSubscription() {
|
||||
const { animate } = this.node.getProps();
|
||||
if (motionDom.isAnimationControls(animate)) {
|
||||
this.unmountControls = animate.subscribe(this.node);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Subscribe any provided AnimationControls to the component's VisualElement
|
||||
*/
|
||||
mount() {
|
||||
this.updateAnimationControlsSubscription();
|
||||
}
|
||||
update() {
|
||||
const { animate } = this.node.getProps();
|
||||
const { animate: prevAnimate } = this.node.prevProps || {};
|
||||
if (animate !== prevAnimate) {
|
||||
this.updateAnimationControlsSubscription();
|
||||
}
|
||||
}
|
||||
unmount() {
|
||||
this.node.animationState.reset();
|
||||
this.unmountControls?.();
|
||||
}
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
class ExitAnimationFeature extends motionDom.Feature {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.id = id++;
|
||||
this.isExitComplete = false;
|
||||
}
|
||||
update() {
|
||||
if (!this.node.presenceContext)
|
||||
return;
|
||||
const { isPresent, onExitComplete } = this.node.presenceContext;
|
||||
const { isPresent: prevIsPresent } = this.node.prevPresenceContext || {};
|
||||
if (!this.node.animationState || isPresent === prevIsPresent) {
|
||||
return;
|
||||
}
|
||||
if (isPresent && prevIsPresent === false) {
|
||||
/**
|
||||
* When re-entering, if the exit animation already completed
|
||||
* (element is at rest), reset to initial values so the enter
|
||||
* animation replays from the correct position.
|
||||
*/
|
||||
if (this.isExitComplete) {
|
||||
const { initial, custom } = this.node.getProps();
|
||||
if (typeof initial === "string" ||
|
||||
(typeof initial === "object" &&
|
||||
initial !== null &&
|
||||
!Array.isArray(initial))) {
|
||||
const resolved = motionDom.resolveVariant(this.node, initial, custom);
|
||||
if (resolved) {
|
||||
const { transition, transitionEnd, ...target } = resolved;
|
||||
for (const key in target) {
|
||||
this.node
|
||||
.getValue(key)
|
||||
?.jump(target[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.node.animationState.reset();
|
||||
this.node.animationState.animateChanges();
|
||||
}
|
||||
else {
|
||||
this.node.animationState.setActive("exit", false);
|
||||
}
|
||||
this.isExitComplete = false;
|
||||
return;
|
||||
}
|
||||
const exitAnimation = this.node.animationState.setActive("exit", !isPresent);
|
||||
if (onExitComplete && !isPresent) {
|
||||
exitAnimation.then(() => {
|
||||
this.isExitComplete = true;
|
||||
onExitComplete(this.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
mount() {
|
||||
const { register, onExitComplete } = this.node.presenceContext || {};
|
||||
if (onExitComplete) {
|
||||
onExitComplete(this.id);
|
||||
}
|
||||
if (register) {
|
||||
this.unmount = register(this.id);
|
||||
}
|
||||
}
|
||||
unmount() { }
|
||||
}
|
||||
|
||||
const animations = {
|
||||
animation: {
|
||||
Feature: AnimationFeature,
|
||||
},
|
||||
exit: {
|
||||
Feature: ExitAnimationFeature,
|
||||
},
|
||||
};
|
||||
|
||||
function extractEventInfo(event) {
|
||||
return {
|
||||
point: {
|
||||
x: event.pageX,
|
||||
y: event.pageY,
|
||||
},
|
||||
};
|
||||
}
|
||||
const addPointerInfo = (handler) => (event) => motionDom.isPrimaryPointer(event) && handler(event, extractEventInfo(event));
|
||||
|
||||
function addPointerEvent(target, eventName, handler, options) {
|
||||
return motionDom.addDomEvent(target, eventName, addPointerInfo(handler), options);
|
||||
}
|
||||
|
||||
// Fixes https://github.com/motiondivision/motion/issues/2270
|
||||
const getContextWindow = ({ current }) => {
|
||||
return current ? current.ownerDocument.defaultView : null;
|
||||
};
|
||||
|
||||
const distance = (a, b) => Math.abs(a - b);
|
||||
function distance2D(a, b) {
|
||||
// Multi-dimensional
|
||||
const xDelta = distance(a.x, b.x);
|
||||
const yDelta = distance(a.y, b.y);
|
||||
return Math.sqrt(xDelta ** 2 + yDelta ** 2);
|
||||
}
|
||||
|
||||
const overflowStyles = /*#__PURE__*/ new Set(["auto", "scroll"]);
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PanSession {
|
||||
constructor(event, handlers, { transformPagePoint, contextWindow = window, dragSnapToOrigin = false, distanceThreshold = 3, element, } = {}) {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
this.startEvent = null;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
this.lastMoveEvent = null;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
this.lastMoveEventInfo = null;
|
||||
/**
|
||||
* Raw (untransformed) event info, re-transformed each frame
|
||||
* so transformPagePoint sees the current parent matrix.
|
||||
* @internal
|
||||
*/
|
||||
this.lastRawMoveEventInfo = null;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
this.handlers = {};
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
this.contextWindow = window;
|
||||
/**
|
||||
* Scroll positions of scrollable ancestors and window.
|
||||
* @internal
|
||||
*/
|
||||
this.scrollPositions = new Map();
|
||||
/**
|
||||
* Cleanup function for scroll listeners.
|
||||
* @internal
|
||||
*/
|
||||
this.removeScrollListeners = null;
|
||||
this.onElementScroll = (event) => {
|
||||
this.handleScroll(event.target);
|
||||
};
|
||||
this.onWindowScroll = () => {
|
||||
this.handleScroll(window);
|
||||
};
|
||||
this.updatePoint = () => {
|
||||
if (!(this.lastMoveEvent && this.lastMoveEventInfo))
|
||||
return;
|
||||
// Re-transform raw point through current transformPagePoint so
|
||||
// animated parent transforms (e.g. rotation) are picked up each frame
|
||||
if (this.lastRawMoveEventInfo) {
|
||||
this.lastMoveEventInfo = transformPoint(this.lastRawMoveEventInfo, this.transformPagePoint);
|
||||
}
|
||||
const info = getPanInfo(this.lastMoveEventInfo, this.history);
|
||||
const isPanStarted = this.startEvent !== null;
|
||||
// Only start panning if the offset is larger than 3 pixels. If we make it
|
||||
// any larger than this we'll want to reset the pointer history
|
||||
// on the first update to avoid visual snapping to the cursor.
|
||||
const isDistancePastThreshold = distance2D(info.offset, { x: 0, y: 0 }) >= this.distanceThreshold;
|
||||
if (!isPanStarted && !isDistancePastThreshold)
|
||||
return;
|
||||
const { point } = info;
|
||||
const { timestamp } = motionDom.frameData;
|
||||
this.history.push({ ...point, timestamp });
|
||||
const { onStart, onMove } = this.handlers;
|
||||
if (!isPanStarted) {
|
||||
onStart && onStart(this.lastMoveEvent, info);
|
||||
this.startEvent = this.lastMoveEvent;
|
||||
}
|
||||
onMove && onMove(this.lastMoveEvent, info);
|
||||
};
|
||||
this.handlePointerMove = (event, info) => {
|
||||
this.lastMoveEvent = event;
|
||||
this.lastRawMoveEventInfo = info;
|
||||
this.lastMoveEventInfo = transformPoint(info, this.transformPagePoint);
|
||||
// Throttle mouse move event to once per frame
|
||||
motionDom.frame.update(this.updatePoint, true);
|
||||
};
|
||||
this.handlePointerUp = (event, info) => {
|
||||
this.end();
|
||||
const { onEnd, onSessionEnd, resumeAnimation } = this.handlers;
|
||||
// Resume animation if dragSnapToOrigin is set OR if no drag started (user just clicked)
|
||||
// This ensures constraint animations continue when interrupted by a click
|
||||
if (this.dragSnapToOrigin || !this.startEvent) {
|
||||
resumeAnimation && resumeAnimation();
|
||||
}
|
||||
if (!(this.lastMoveEvent && this.lastMoveEventInfo))
|
||||
return;
|
||||
const panInfo = getPanInfo(event.type === "pointercancel"
|
||||
? this.lastMoveEventInfo
|
||||
: transformPoint(info, this.transformPagePoint), this.history);
|
||||
if (this.startEvent && onEnd) {
|
||||
onEnd(event, panInfo);
|
||||
}
|
||||
onSessionEnd && onSessionEnd(event, panInfo);
|
||||
};
|
||||
// If we have more than one touch, don't start detecting this gesture
|
||||
if (!motionDom.isPrimaryPointer(event))
|
||||
return;
|
||||
this.dragSnapToOrigin = dragSnapToOrigin;
|
||||
this.handlers = handlers;
|
||||
this.transformPagePoint = transformPagePoint;
|
||||
this.distanceThreshold = distanceThreshold;
|
||||
this.contextWindow = contextWindow || window;
|
||||
const info = extractEventInfo(event);
|
||||
const initialInfo = transformPoint(info, this.transformPagePoint);
|
||||
const { point } = initialInfo;
|
||||
const { timestamp } = motionDom.frameData;
|
||||
this.history = [{ ...point, timestamp }];
|
||||
const { onSessionStart } = handlers;
|
||||
onSessionStart &&
|
||||
onSessionStart(event, getPanInfo(initialInfo, this.history));
|
||||
// Listen in the capture phase so a descendant calling
|
||||
// stopPropagation() (e.g. in its own pointerup handler) can't
|
||||
// prevent the gesture from ending. See #2794.
|
||||
const eventOptions = { passive: true, capture: true };
|
||||
this.removeListeners = motionUtils.pipe(addPointerEvent(this.contextWindow, "pointermove", this.handlePointerMove, eventOptions), addPointerEvent(this.contextWindow, "pointerup", this.handlePointerUp, eventOptions), addPointerEvent(this.contextWindow, "pointercancel", this.handlePointerUp, eventOptions));
|
||||
// Start scroll tracking if element provided
|
||||
if (element) {
|
||||
this.startScrollTracking(element);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start tracking scroll on ancestors and window.
|
||||
*/
|
||||
startScrollTracking(element) {
|
||||
// Store initial scroll positions for scrollable ancestors
|
||||
let current = element.parentElement;
|
||||
while (current) {
|
||||
const style = getComputedStyle(current);
|
||||
if (overflowStyles.has(style.overflowX) ||
|
||||
overflowStyles.has(style.overflowY)) {
|
||||
this.scrollPositions.set(current, {
|
||||
x: current.scrollLeft,
|
||||
y: current.scrollTop,
|
||||
});
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
// Track window scroll
|
||||
this.scrollPositions.set(window, {
|
||||
x: window.scrollX,
|
||||
y: window.scrollY,
|
||||
});
|
||||
// Capture listener catches element scroll events as they bubble
|
||||
window.addEventListener("scroll", this.onElementScroll, {
|
||||
capture: true,
|
||||
});
|
||||
// Direct window scroll listener (window scroll doesn't bubble)
|
||||
window.addEventListener("scroll", this.onWindowScroll);
|
||||
this.removeScrollListeners = () => {
|
||||
window.removeEventListener("scroll", this.onElementScroll, {
|
||||
capture: true,
|
||||
});
|
||||
window.removeEventListener("scroll", this.onWindowScroll);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Handle scroll compensation during drag.
|
||||
*
|
||||
* For element scroll: adjusts history origin since pageX/pageY doesn't change.
|
||||
* For window scroll: adjusts lastMoveEventInfo since pageX/pageY would change.
|
||||
*/
|
||||
handleScroll(target) {
|
||||
const initial = this.scrollPositions.get(target);
|
||||
if (!initial)
|
||||
return;
|
||||
const isWindow = target === window;
|
||||
const current = isWindow
|
||||
? { x: window.scrollX, y: window.scrollY }
|
||||
: {
|
||||
x: target.scrollLeft,
|
||||
y: target.scrollTop,
|
||||
};
|
||||
const delta = { x: current.x - initial.x, y: current.y - initial.y };
|
||||
if (delta.x === 0 && delta.y === 0)
|
||||
return;
|
||||
if (isWindow) {
|
||||
// Window scroll: pageX/pageY changes, so update lastMoveEventInfo
|
||||
if (this.lastMoveEventInfo) {
|
||||
this.lastMoveEventInfo.point.x += delta.x;
|
||||
this.lastMoveEventInfo.point.y += delta.y;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Element scroll: pageX/pageY unchanged, so adjust history origin
|
||||
if (this.history.length > 0) {
|
||||
this.history[0].x -= delta.x;
|
||||
this.history[0].y -= delta.y;
|
||||
}
|
||||
}
|
||||
this.scrollPositions.set(target, current);
|
||||
motionDom.frame.update(this.updatePoint, true);
|
||||
}
|
||||
updateHandlers(handlers) {
|
||||
this.handlers = handlers;
|
||||
}
|
||||
end() {
|
||||
this.removeListeners && this.removeListeners();
|
||||
this.removeScrollListeners && this.removeScrollListeners();
|
||||
this.scrollPositions.clear();
|
||||
motionDom.cancelFrame(this.updatePoint);
|
||||
}
|
||||
}
|
||||
function transformPoint(info, transformPagePoint) {
|
||||
return transformPagePoint ? { point: transformPagePoint(info.point) } : info;
|
||||
}
|
||||
function subtractPoint(a, b) {
|
||||
return { x: a.x - b.x, y: a.y - b.y };
|
||||
}
|
||||
function getPanInfo({ point }, history) {
|
||||
return {
|
||||
point,
|
||||
delta: subtractPoint(point, lastDevicePoint(history)),
|
||||
offset: subtractPoint(point, startDevicePoint(history)),
|
||||
velocity: getVelocity(history, 0.1),
|
||||
};
|
||||
}
|
||||
function startDevicePoint(history) {
|
||||
return history[0];
|
||||
}
|
||||
function lastDevicePoint(history) {
|
||||
return history[history.length - 1];
|
||||
}
|
||||
function getVelocity(history, timeDelta) {
|
||||
if (history.length < 2) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
let i = history.length - 1;
|
||||
let timestampedPoint = null;
|
||||
const lastPoint = lastDevicePoint(history);
|
||||
while (i >= 0) {
|
||||
timestampedPoint = history[i];
|
||||
if (lastPoint.timestamp - timestampedPoint.timestamp >
|
||||
motionUtils.secondsToMilliseconds(timeDelta)) {
|
||||
break;
|
||||
}
|
||||
i--;
|
||||
}
|
||||
if (!timestampedPoint) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
/**
|
||||
* If the selected point is the pointer-down origin (history[0]),
|
||||
* there are better movement points available, and the time gap
|
||||
* is suspiciously large (>2x timeDelta), use the next point instead.
|
||||
* This prevents stale pointer-down points from diluting velocity
|
||||
* in hold-then-flick gestures.
|
||||
*/
|
||||
if (timestampedPoint === history[0] &&
|
||||
history.length > 2 &&
|
||||
lastPoint.timestamp - timestampedPoint.timestamp >
|
||||
motionUtils.secondsToMilliseconds(timeDelta) * 2) {
|
||||
timestampedPoint = history[1];
|
||||
}
|
||||
const time = motionUtils.millisecondsToSeconds(lastPoint.timestamp - timestampedPoint.timestamp);
|
||||
if (time === 0) {
|
||||
return { x: 0, y: 0 };
|
||||
}
|
||||
const currentVelocity = {
|
||||
x: (lastPoint.x - timestampedPoint.x) / time,
|
||||
y: (lastPoint.y - timestampedPoint.y) / time,
|
||||
};
|
||||
if (currentVelocity.x === Infinity) {
|
||||
currentVelocity.x = 0;
|
||||
}
|
||||
if (currentVelocity.y === Infinity) {
|
||||
currentVelocity.y = 0;
|
||||
}
|
||||
return currentVelocity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply constraints to a point. These constraints are both physical along an
|
||||
* axis, and an elastic factor that determines how much to constrain the point
|
||||
* by if it does lie outside the defined parameters.
|
||||
*/
|
||||
function applyConstraints(point, { min, max }, elastic) {
|
||||
if (min !== undefined && point < min) {
|
||||
// If we have a min point defined, and this is outside of that, constrain
|
||||
point = elastic
|
||||
? motionDom.mixNumber(min, point, elastic.min)
|
||||
: Math.max(point, min);
|
||||
}
|
||||
else if (max !== undefined && point > max) {
|
||||
// If we have a max point defined, and this is outside of that, constrain
|
||||
point = elastic
|
||||
? motionDom.mixNumber(max, point, elastic.max)
|
||||
: Math.min(point, max);
|
||||
}
|
||||
return point;
|
||||
}
|
||||
/**
|
||||
* Calculate constraints in terms of the viewport when defined relatively to the
|
||||
* measured axis. This is measured from the nearest edge, so a max constraint of 200
|
||||
* on an axis with a max value of 300 would return a constraint of 500 - axis length
|
||||
*/
|
||||
function calcRelativeAxisConstraints(axis, min, max) {
|
||||
return {
|
||||
min: min !== undefined ? axis.min + min : undefined,
|
||||
max: max !== undefined
|
||||
? axis.max + max - (axis.max - axis.min)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Calculate constraints in terms of the viewport when
|
||||
* defined relatively to the measured bounding box.
|
||||
*/
|
||||
function calcRelativeConstraints(layoutBox, { top, left, bottom, right }) {
|
||||
return {
|
||||
x: calcRelativeAxisConstraints(layoutBox.x, left, right),
|
||||
y: calcRelativeAxisConstraints(layoutBox.y, top, bottom),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Calculate viewport constraints when defined as another viewport-relative axis
|
||||
*/
|
||||
function calcViewportAxisConstraints(layoutAxis, constraintsAxis) {
|
||||
let min = constraintsAxis.min - layoutAxis.min;
|
||||
let max = constraintsAxis.max - layoutAxis.max;
|
||||
// If the constraints axis is actually smaller than the layout axis then we can
|
||||
// flip the constraints
|
||||
if (constraintsAxis.max - constraintsAxis.min <
|
||||
layoutAxis.max - layoutAxis.min) {
|
||||
[min, max] = [max, min];
|
||||
}
|
||||
return { min, max };
|
||||
}
|
||||
/**
|
||||
* Calculate viewport constraints when defined as another viewport-relative box
|
||||
*/
|
||||
function calcViewportConstraints(layoutBox, constraintsBox) {
|
||||
return {
|
||||
x: calcViewportAxisConstraints(layoutBox.x, constraintsBox.x),
|
||||
y: calcViewportAxisConstraints(layoutBox.y, constraintsBox.y),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Calculate a transform origin relative to the source axis, between 0-1, that results
|
||||
* in an asthetically pleasing scale/transform needed to project from source to target.
|
||||
*/
|
||||
function calcOrigin(source, target) {
|
||||
let origin = 0.5;
|
||||
const sourceLength = motionDom.calcLength(source);
|
||||
const targetLength = motionDom.calcLength(target);
|
||||
if (targetLength > sourceLength) {
|
||||
origin = motionUtils.progress(target.min, target.max - sourceLength, source.min);
|
||||
}
|
||||
else if (sourceLength > targetLength) {
|
||||
origin = motionUtils.progress(source.min, source.max - targetLength, target.min);
|
||||
}
|
||||
return motionUtils.clamp(0, 1, origin);
|
||||
}
|
||||
/**
|
||||
* Rebase the calculated viewport constraints relative to the layout.min point.
|
||||
*/
|
||||
function rebaseAxisConstraints(layout, constraints) {
|
||||
const relativeConstraints = {};
|
||||
if (constraints.min !== undefined) {
|
||||
relativeConstraints.min = constraints.min - layout.min;
|
||||
}
|
||||
if (constraints.max !== undefined) {
|
||||
relativeConstraints.max = constraints.max - layout.min;
|
||||
}
|
||||
return relativeConstraints;
|
||||
}
|
||||
const defaultElastic = 0.35;
|
||||
/**
|
||||
* Accepts a dragElastic prop and returns resolved elastic values for each axis.
|
||||
*/
|
||||
function resolveDragElastic(dragElastic = defaultElastic) {
|
||||
if (dragElastic === false) {
|
||||
dragElastic = 0;
|
||||
}
|
||||
else if (dragElastic === true) {
|
||||
dragElastic = defaultElastic;
|
||||
}
|
||||
return {
|
||||
x: resolveAxisElastic(dragElastic, "left", "right"),
|
||||
y: resolveAxisElastic(dragElastic, "top", "bottom"),
|
||||
};
|
||||
}
|
||||
function resolveAxisElastic(dragElastic, minLabel, maxLabel) {
|
||||
return {
|
||||
min: resolvePointElastic(dragElastic, minLabel),
|
||||
max: resolvePointElastic(dragElastic, maxLabel),
|
||||
};
|
||||
}
|
||||
function resolvePointElastic(dragElastic, label) {
|
||||
return typeof dragElastic === "number"
|
||||
? dragElastic
|
||||
: dragElastic[label] || 0;
|
||||
}
|
||||
|
||||
const elementDragControls = new WeakMap();
|
||||
class VisualElementDragControls {
|
||||
constructor(visualElement) {
|
||||
this.openDragLock = null;
|
||||
this.isDragging = false;
|
||||
this.currentDirection = null;
|
||||
this.originPoint = { x: 0, y: 0 };
|
||||
/**
|
||||
* The permitted boundaries of travel, in pixels.
|
||||
*/
|
||||
this.constraints = false;
|
||||
this.hasMutatedConstraints = false;
|
||||
/**
|
||||
* The per-axis resolved elastic values.
|
||||
*/
|
||||
this.elastic = motionDom.createBox();
|
||||
/**
|
||||
* The latest pointer event. Used as fallback when the `cancel` and `stop` functions are called without arguments.
|
||||
*/
|
||||
this.latestPointerEvent = null;
|
||||
/**
|
||||
* The latest pan info. Used as fallback when the `cancel` and `stop` functions are called without arguments.
|
||||
*/
|
||||
this.latestPanInfo = null;
|
||||
this.visualElement = visualElement;
|
||||
}
|
||||
start(originEvent, { snapToCursor = false, distanceThreshold } = {}) {
|
||||
/**
|
||||
* Don't start dragging if this component is exiting
|
||||
*/
|
||||
const { presenceContext } = this.visualElement;
|
||||
if (presenceContext && presenceContext.isPresent === false)
|
||||
return;
|
||||
const onSessionStart = (event) => {
|
||||
if (snapToCursor) {
|
||||
this.snapToCursor(extractEventInfo(event).point);
|
||||
}
|
||||
this.stopAnimation();
|
||||
};
|
||||
const onStart = (event, info) => {
|
||||
// Attempt to grab the global drag gesture lock - maybe make this part of PanSession
|
||||
const { drag, dragPropagation, onDragStart } = this.getProps();
|
||||
if (drag && !dragPropagation) {
|
||||
if (this.openDragLock)
|
||||
this.openDragLock();
|
||||
this.openDragLock = motionDom.setDragLock(drag);
|
||||
// If we don 't have the lock, don't start dragging
|
||||
if (!this.openDragLock)
|
||||
return;
|
||||
}
|
||||
this.latestPointerEvent = event;
|
||||
this.latestPanInfo = info;
|
||||
this.isDragging = true;
|
||||
this.currentDirection = null;
|
||||
this.resolveConstraints();
|
||||
if (this.visualElement.projection) {
|
||||
this.visualElement.projection.isAnimationBlocked = true;
|
||||
this.visualElement.projection.target = undefined;
|
||||
}
|
||||
/**
|
||||
* Record gesture origin and pointer offset
|
||||
*/
|
||||
motionDom.eachAxis((axis) => {
|
||||
let current = this.getAxisMotionValue(axis).get() || 0;
|
||||
/**
|
||||
* If the MotionValue is a percentage value convert to px
|
||||
*/
|
||||
if (motionDom.percent.test(current)) {
|
||||
const { projection } = this.visualElement;
|
||||
if (projection && projection.layout) {
|
||||
const measuredAxis = projection.layout.layoutBox[axis];
|
||||
if (measuredAxis) {
|
||||
const length = motionDom.calcLength(measuredAxis);
|
||||
current = length * (parseFloat(current) / 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
this.originPoint[axis] = current;
|
||||
});
|
||||
// Fire onDragStart event
|
||||
if (onDragStart) {
|
||||
motionDom.frame.update(() => onDragStart(event, info), false, true);
|
||||
}
|
||||
motionDom.addValueToWillChange(this.visualElement, "transform");
|
||||
const { animationState } = this.visualElement;
|
||||
animationState && animationState.setActive("whileDrag", true);
|
||||
};
|
||||
const onMove = (event, info) => {
|
||||
this.latestPointerEvent = event;
|
||||
this.latestPanInfo = info;
|
||||
const { dragPropagation, dragDirectionLock, onDirectionLock, onDrag, } = this.getProps();
|
||||
// If we didn't successfully receive the gesture lock, early return.
|
||||
if (!dragPropagation && !this.openDragLock)
|
||||
return;
|
||||
const { offset } = info;
|
||||
// Attempt to detect drag direction if directionLock is true
|
||||
if (dragDirectionLock && this.currentDirection === null) {
|
||||
this.currentDirection = getCurrentDirection(offset);
|
||||
// If we've successfully set a direction, notify listener
|
||||
if (this.currentDirection !== null) {
|
||||
onDirectionLock && onDirectionLock(this.currentDirection);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Update each point with the latest position
|
||||
this.updateAxis("x", info.point, offset);
|
||||
this.updateAxis("y", info.point, offset);
|
||||
/**
|
||||
* Ideally we would leave the renderer to fire naturally at the end of
|
||||
* this frame but if the element is about to change layout as the result
|
||||
* of a re-render we want to ensure the browser can read the latest
|
||||
* bounding box to ensure the pointer and element don't fall out of sync.
|
||||
*/
|
||||
this.visualElement.render();
|
||||
/**
|
||||
* This must fire after the render call as it might trigger a state
|
||||
* change which itself might trigger a layout update.
|
||||
*/
|
||||
if (onDrag) {
|
||||
motionDom.frame.update(() => onDrag(event, info), false, true);
|
||||
}
|
||||
};
|
||||
const onSessionEnd = (event, info) => {
|
||||
this.latestPointerEvent = event;
|
||||
this.latestPanInfo = info;
|
||||
this.stop(event, info);
|
||||
this.latestPointerEvent = null;
|
||||
this.latestPanInfo = null;
|
||||
};
|
||||
const resumeAnimation = () => {
|
||||
const { dragSnapToOrigin: snap } = this.getProps();
|
||||
if (snap || this.constraints) {
|
||||
this.startAnimation({ x: 0, y: 0 });
|
||||
}
|
||||
};
|
||||
const { dragSnapToOrigin } = this.getProps();
|
||||
this.panSession = new PanSession(originEvent, {
|
||||
onSessionStart,
|
||||
onStart,
|
||||
onMove,
|
||||
onSessionEnd,
|
||||
resumeAnimation,
|
||||
}, {
|
||||
transformPagePoint: this.visualElement.getTransformPagePoint(),
|
||||
dragSnapToOrigin,
|
||||
distanceThreshold,
|
||||
contextWindow: getContextWindow(this.visualElement),
|
||||
element: this.visualElement.current,
|
||||
});
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
stop(event, panInfo) {
|
||||
const finalEvent = event || this.latestPointerEvent;
|
||||
const finalPanInfo = panInfo || this.latestPanInfo;
|
||||
const isDragging = this.isDragging;
|
||||
this.cancel();
|
||||
if (!isDragging || !finalPanInfo || !finalEvent)
|
||||
return;
|
||||
const { velocity } = finalPanInfo;
|
||||
this.startAnimation(velocity);
|
||||
const { onDragEnd } = this.getProps();
|
||||
if (onDragEnd) {
|
||||
motionDom.frame.postRender(() => onDragEnd(finalEvent, finalPanInfo));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
cancel() {
|
||||
this.isDragging = false;
|
||||
const { projection, animationState } = this.visualElement;
|
||||
if (projection) {
|
||||
projection.isAnimationBlocked = false;
|
||||
}
|
||||
this.endPanSession();
|
||||
const { dragPropagation } = this.getProps();
|
||||
if (!dragPropagation && this.openDragLock) {
|
||||
this.openDragLock();
|
||||
this.openDragLock = null;
|
||||
}
|
||||
animationState && animationState.setActive("whileDrag", false);
|
||||
}
|
||||
/**
|
||||
* Clean up the pan session without modifying other drag state.
|
||||
* This is used during unmount to ensure event listeners are removed
|
||||
* without affecting projection animations or drag locks.
|
||||
* @internal
|
||||
*/
|
||||
endPanSession() {
|
||||
this.panSession && this.panSession.end();
|
||||
this.panSession = undefined;
|
||||
}
|
||||
updateAxis(axis, _point, offset) {
|
||||
const { drag } = this.getProps();
|
||||
// If we're not dragging this axis, do an early return.
|
||||
if (!offset || !shouldDrag(axis, drag, this.currentDirection))
|
||||
return;
|
||||
const axisValue = this.getAxisMotionValue(axis);
|
||||
let next = this.originPoint[axis] + offset[axis];
|
||||
// Apply constraints
|
||||
if (this.constraints && this.constraints[axis]) {
|
||||
next = applyConstraints(next, this.constraints[axis], this.elastic[axis]);
|
||||
}
|
||||
axisValue.set(next);
|
||||
}
|
||||
resolveConstraints() {
|
||||
const { dragConstraints, dragElastic } = this.getProps();
|
||||
const layout = this.visualElement.projection &&
|
||||
!this.visualElement.projection.layout
|
||||
? this.visualElement.projection.measure(false)
|
||||
: this.visualElement.projection?.layout;
|
||||
const prevConstraints = this.constraints;
|
||||
if (dragConstraints && index.isRefObject(dragConstraints)) {
|
||||
if (!this.constraints) {
|
||||
this.constraints = this.resolveRefConstraints();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (dragConstraints && layout) {
|
||||
this.constraints = calcRelativeConstraints(layout.layoutBox, dragConstraints);
|
||||
}
|
||||
else {
|
||||
this.constraints = false;
|
||||
}
|
||||
}
|
||||
this.elastic = resolveDragElastic(dragElastic);
|
||||
/**
|
||||
* If we're outputting to external MotionValues, we want to rebase the measured constraints
|
||||
* from viewport-relative to component-relative. This only applies to relative (non-ref)
|
||||
* constraints, as ref-based constraints from calcViewportConstraints are already in the
|
||||
* correct coordinate space for the motion value transform offset.
|
||||
*/
|
||||
if (prevConstraints !== this.constraints &&
|
||||
!index.isRefObject(dragConstraints) &&
|
||||
layout &&
|
||||
this.constraints &&
|
||||
!this.hasMutatedConstraints) {
|
||||
motionDom.eachAxis((axis) => {
|
||||
if (this.constraints !== false &&
|
||||
this.getAxisMotionValue(axis)) {
|
||||
this.constraints[axis] = rebaseAxisConstraints(layout.layoutBox[axis], this.constraints[axis]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
resolveRefConstraints() {
|
||||
const { dragConstraints: constraints, onMeasureDragConstraints } = this.getProps();
|
||||
if (!constraints || !index.isRefObject(constraints))
|
||||
return false;
|
||||
const constraintsElement = constraints.current;
|
||||
motionUtils.invariant(constraintsElement !== null, "If `dragConstraints` is set as a React ref, that ref must be passed to another component's `ref` prop.", "drag-constraints-ref");
|
||||
const { projection } = this.visualElement;
|
||||
// TODO
|
||||
if (!projection || !projection.layout)
|
||||
return false;
|
||||
/**
|
||||
* Refresh the root scroll offset so the constraint's viewport box
|
||||
* translates to correct page coordinates. The scroll captured at
|
||||
* drag mount can be stale if the document was scrolled afterwards —
|
||||
* e.g. via the browser restoring scroll on refresh, or an ancestor
|
||||
* layout effect running after this element's mount (#2829).
|
||||
*
|
||||
* Clear the cached scroll first so `updateScroll` bypasses its
|
||||
* per-animationId cache and re-reads the live value.
|
||||
*/
|
||||
if (projection.root) {
|
||||
projection.root.scroll = undefined;
|
||||
projection.root.updateScroll();
|
||||
}
|
||||
const constraintsBox = motionDom.measurePageBox(constraintsElement, projection.root, this.visualElement.getTransformPagePoint());
|
||||
let measuredConstraints = calcViewportConstraints(projection.layout.layoutBox, constraintsBox);
|
||||
/**
|
||||
* If there's an onMeasureDragConstraints listener we call it and
|
||||
* if different constraints are returned, set constraints to that
|
||||
*/
|
||||
if (onMeasureDragConstraints) {
|
||||
const userConstraints = onMeasureDragConstraints(motionDom.convertBoxToBoundingBox(measuredConstraints));
|
||||
this.hasMutatedConstraints = !!userConstraints;
|
||||
if (userConstraints) {
|
||||
measuredConstraints = motionDom.convertBoundingBoxToBox(userConstraints);
|
||||
}
|
||||
}
|
||||
return measuredConstraints;
|
||||
}
|
||||
startAnimation(velocity) {
|
||||
const { drag, dragMomentum, dragElastic, dragTransition, dragSnapToOrigin, onDragTransitionEnd, } = this.getProps();
|
||||
const constraints = this.constraints || {};
|
||||
const momentumAnimations = motionDom.eachAxis((axis) => {
|
||||
if (!shouldDrag(axis, drag, this.currentDirection)) {
|
||||
return;
|
||||
}
|
||||
let transition = (constraints && constraints[axis]) || {};
|
||||
if (dragSnapToOrigin === true ||
|
||||
dragSnapToOrigin === axis)
|
||||
transition = { min: 0, max: 0 };
|
||||
/**
|
||||
* Overdamp the boundary spring if `dragElastic` is disabled. There's still a frame
|
||||
* of spring animations so we should look into adding a disable spring option to `inertia`.
|
||||
* We could do something here where we affect the `bounceStiffness` and `bounceDamping`
|
||||
* using the value of `dragElastic`.
|
||||
*/
|
||||
const bounceStiffness = dragElastic ? 200 : 1000000;
|
||||
const bounceDamping = dragElastic ? 40 : 10000000;
|
||||
const inertia = {
|
||||
type: "inertia",
|
||||
velocity: dragMomentum ? velocity[axis] : 0,
|
||||
bounceStiffness,
|
||||
bounceDamping,
|
||||
timeConstant: 750,
|
||||
restDelta: 1,
|
||||
restSpeed: 10,
|
||||
...dragTransition,
|
||||
...transition,
|
||||
};
|
||||
// If we're not animating on an externally-provided `MotionValue` we can use the
|
||||
// component's animation controls which will handle interactions with whileHover (etc),
|
||||
// otherwise we just have to animate the `MotionValue` itself.
|
||||
return this.startAxisValueAnimation(axis, inertia);
|
||||
});
|
||||
// Run all animations and then resolve the new drag constraints.
|
||||
return Promise.all(momentumAnimations).then(onDragTransitionEnd);
|
||||
}
|
||||
startAxisValueAnimation(axis, transition) {
|
||||
const axisValue = this.getAxisMotionValue(axis);
|
||||
motionDom.addValueToWillChange(this.visualElement, axis);
|
||||
return axisValue.start(motionDom.animateMotionValue(axis, axisValue, 0, transition, this.visualElement, false));
|
||||
}
|
||||
stopAnimation() {
|
||||
motionDom.eachAxis((axis) => this.getAxisMotionValue(axis).stop());
|
||||
}
|
||||
/**
|
||||
* Drag works differently depending on which props are provided.
|
||||
*
|
||||
* - If _dragX and _dragY are provided, we output the gesture delta directly to those motion values.
|
||||
* - Otherwise, we apply the delta to the x/y motion values.
|
||||
*/
|
||||
getAxisMotionValue(axis) {
|
||||
const dragKey = `_drag${axis.toUpperCase()}`;
|
||||
const props = this.visualElement.getProps();
|
||||
const externalMotionValue = props[dragKey];
|
||||
return externalMotionValue
|
||||
? externalMotionValue
|
||||
: this.visualElement.getValue(axis, this.visualElement.latestValues[axis] ?? 0);
|
||||
}
|
||||
snapToCursor(point) {
|
||||
motionDom.eachAxis((axis) => {
|
||||
const { drag } = this.getProps();
|
||||
// If we're not dragging this axis, do an early return.
|
||||
if (!shouldDrag(axis, drag, this.currentDirection))
|
||||
return;
|
||||
const { projection } = this.visualElement;
|
||||
const axisValue = this.getAxisMotionValue(axis);
|
||||
if (projection && projection.layout) {
|
||||
const { min, max } = projection.layout.layoutBox[axis];
|
||||
/**
|
||||
* The layout measurement includes the current transform value,
|
||||
* so we need to add it back to get the correct snap position.
|
||||
* This fixes an issue where elements with initial coordinates
|
||||
* would snap to the wrong position on the first drag.
|
||||
*/
|
||||
const current = axisValue.get() || 0;
|
||||
axisValue.set(point[axis] - motionDom.mixNumber(min, max, 0.5) + current);
|
||||
}
|
||||
});
|
||||
}
|
||||
/**
|
||||
* When the viewport resizes we want to check if the measured constraints
|
||||
* have changed and, if so, reposition the element within those new constraints
|
||||
* relative to where it was before the resize.
|
||||
*/
|
||||
scalePositionWithinConstraints() {
|
||||
if (!this.visualElement.current)
|
||||
return;
|
||||
const { drag, dragConstraints } = this.getProps();
|
||||
const { projection } = this.visualElement;
|
||||
if (!index.isRefObject(dragConstraints) || !projection || !this.constraints)
|
||||
return;
|
||||
/**
|
||||
* Stop current animations as there can be visual glitching if we try to do
|
||||
* this mid-animation
|
||||
*/
|
||||
this.stopAnimation();
|
||||
/**
|
||||
* Record the relative position of the dragged element relative to the
|
||||
* constraints box and save as a progress value.
|
||||
*/
|
||||
const boxProgress = { x: 0, y: 0 };
|
||||
motionDom.eachAxis((axis) => {
|
||||
const axisValue = this.getAxisMotionValue(axis);
|
||||
if (axisValue && this.constraints !== false) {
|
||||
const latest = axisValue.get();
|
||||
boxProgress[axis] = calcOrigin({ min: latest, max: latest }, this.constraints[axis]);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Update the layout of this element and resolve the latest drag constraints
|
||||
*/
|
||||
const { transformTemplate } = this.visualElement.getProps();
|
||||
this.visualElement.current.style.transform = transformTemplate
|
||||
? transformTemplate({}, "")
|
||||
: "none";
|
||||
projection.root && projection.root.updateScroll();
|
||||
projection.updateLayout();
|
||||
/**
|
||||
* Reset constraints so resolveConstraints() will recalculate them
|
||||
* with the freshly measured layout rather than returning the cached value.
|
||||
*/
|
||||
this.constraints = false;
|
||||
this.resolveConstraints();
|
||||
/**
|
||||
* For each axis, calculate the current progress of the layout axis
|
||||
* within the new constraints.
|
||||
*/
|
||||
motionDom.eachAxis((axis) => {
|
||||
if (!shouldDrag(axis, drag, null))
|
||||
return;
|
||||
/**
|
||||
* Calculate a new transform based on the previous box progress
|
||||
*/
|
||||
const axisValue = this.getAxisMotionValue(axis);
|
||||
const { min, max } = this.constraints[axis];
|
||||
axisValue.set(motionDom.mixNumber(min, max, boxProgress[axis]));
|
||||
});
|
||||
/**
|
||||
* Flush the updated transform to the DOM synchronously to prevent
|
||||
* a visual flash at the element's CSS layout position (0,0) when
|
||||
* the transform was stripped for measurement.
|
||||
*/
|
||||
this.visualElement.render();
|
||||
}
|
||||
addListeners() {
|
||||
if (!this.visualElement.current)
|
||||
return;
|
||||
elementDragControls.set(this.visualElement, this);
|
||||
const element = this.visualElement.current;
|
||||
/**
|
||||
* Attach a pointerdown event listener on this DOM element to initiate drag tracking.
|
||||
*/
|
||||
const stopPointerListener = addPointerEvent(element, "pointerdown", (event) => {
|
||||
const { drag, dragListener = true } = this.getProps();
|
||||
const target = event.target;
|
||||
/**
|
||||
* Only block drag if clicking on a text input child element
|
||||
* (input, textarea, select, contenteditable) where users might
|
||||
* want to select text or interact with the control.
|
||||
*
|
||||
* Buttons and links don't block drag since they don't have
|
||||
* click-and-move actions of their own.
|
||||
*/
|
||||
const isClickingTextInputChild = target !== element && motionDom.isElementTextInput(target);
|
||||
if (drag && dragListener && !isClickingTextInputChild) {
|
||||
this.start(event);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* If using ref-based constraints, observe both the draggable element
|
||||
* and the constraint container for size changes via ResizeObserver.
|
||||
* Setup is deferred because dragConstraints.current is null when
|
||||
* addListeners first runs (React hasn't committed the ref yet).
|
||||
*/
|
||||
let stopResizeObservers;
|
||||
const measureDragConstraints = () => {
|
||||
const { dragConstraints } = this.getProps();
|
||||
if (index.isRefObject(dragConstraints) && dragConstraints.current) {
|
||||
this.constraints = this.resolveRefConstraints();
|
||||
if (!stopResizeObservers) {
|
||||
stopResizeObservers = startResizeObservers(element, dragConstraints.current, () => this.scalePositionWithinConstraints());
|
||||
}
|
||||
}
|
||||
};
|
||||
const { projection } = this.visualElement;
|
||||
const stopMeasureLayoutListener = projection.addEventListener("measure", measureDragConstraints);
|
||||
if (projection && !projection.layout) {
|
||||
projection.root && projection.root.updateScroll();
|
||||
projection.updateLayout();
|
||||
}
|
||||
motionDom.frame.read(measureDragConstraints);
|
||||
/**
|
||||
* Attach a window resize listener to scale the draggable target within its defined
|
||||
* constraints as the window resizes.
|
||||
*/
|
||||
const stopResizeListener = motionDom.addDomEvent(window, "resize", () => this.scalePositionWithinConstraints());
|
||||
/**
|
||||
* If the element's layout changes, calculate the delta and apply that to
|
||||
* the drag gesture's origin point.
|
||||
*/
|
||||
const stopLayoutUpdateListener = projection.addEventListener("didUpdate", (({ delta, hasLayoutChanged }) => {
|
||||
if (this.isDragging && hasLayoutChanged) {
|
||||
motionDom.eachAxis((axis) => {
|
||||
const motionValue = this.getAxisMotionValue(axis);
|
||||
if (!motionValue)
|
||||
return;
|
||||
this.originPoint[axis] += delta[axis].translate;
|
||||
motionValue.set(motionValue.get() + delta[axis].translate);
|
||||
});
|
||||
this.visualElement.render();
|
||||
}
|
||||
}));
|
||||
return () => {
|
||||
stopResizeListener();
|
||||
stopPointerListener();
|
||||
stopMeasureLayoutListener();
|
||||
stopLayoutUpdateListener && stopLayoutUpdateListener();
|
||||
stopResizeObservers && stopResizeObservers();
|
||||
};
|
||||
}
|
||||
getProps() {
|
||||
const props = this.visualElement.getProps();
|
||||
const { drag = false, dragDirectionLock = false, dragPropagation = false, dragConstraints = false, dragElastic = defaultElastic, dragMomentum = true, } = props;
|
||||
return {
|
||||
...props,
|
||||
drag,
|
||||
dragDirectionLock,
|
||||
dragPropagation,
|
||||
dragConstraints,
|
||||
dragElastic,
|
||||
dragMomentum,
|
||||
};
|
||||
}
|
||||
}
|
||||
function skipFirstCall(callback) {
|
||||
let isFirst = true;
|
||||
return () => {
|
||||
if (isFirst) {
|
||||
isFirst = false;
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
};
|
||||
}
|
||||
function startResizeObservers(element, constraintsElement, onResize) {
|
||||
const stopElement = motionDom.resize(element, skipFirstCall(onResize));
|
||||
const stopContainer = motionDom.resize(constraintsElement, skipFirstCall(onResize));
|
||||
return () => {
|
||||
stopElement();
|
||||
stopContainer();
|
||||
};
|
||||
}
|
||||
function shouldDrag(direction, drag, currentDirection) {
|
||||
return ((drag === true || drag === direction) &&
|
||||
(currentDirection === null || currentDirection === direction));
|
||||
}
|
||||
/**
|
||||
* Based on an x/y offset determine the current drag direction. If both axis' offsets are lower
|
||||
* than the provided threshold, return `null`.
|
||||
*
|
||||
* @param offset - The x/y offset from origin.
|
||||
* @param lockThreshold - (Optional) - the minimum absolute offset before we can determine a drag direction.
|
||||
*/
|
||||
function getCurrentDirection(offset, lockThreshold = 10) {
|
||||
let direction = null;
|
||||
if (Math.abs(offset.y) > lockThreshold) {
|
||||
direction = "y";
|
||||
}
|
||||
else if (Math.abs(offset.x) > lockThreshold) {
|
||||
direction = "x";
|
||||
}
|
||||
return direction;
|
||||
}
|
||||
|
||||
class DragGesture extends motionDom.Feature {
|
||||
constructor(node) {
|
||||
super(node);
|
||||
this.removeGroupControls = motionUtils.noop;
|
||||
this.removeListeners = motionUtils.noop;
|
||||
this.controls = new VisualElementDragControls(node);
|
||||
}
|
||||
mount() {
|
||||
// If we've been provided a DragControls for manual control over the drag gesture,
|
||||
// subscribe this component to it on mount.
|
||||
const { dragControls } = this.node.getProps();
|
||||
if (dragControls) {
|
||||
this.removeGroupControls = dragControls.subscribe(this.controls);
|
||||
}
|
||||
this.removeListeners = this.controls.addListeners() || motionUtils.noop;
|
||||
}
|
||||
update() {
|
||||
const { dragControls } = this.node.getProps();
|
||||
const { dragControls: prevDragControls } = this.node.prevProps || {};
|
||||
if (dragControls !== prevDragControls) {
|
||||
this.removeGroupControls();
|
||||
if (dragControls) {
|
||||
this.removeGroupControls = dragControls.subscribe(this.controls);
|
||||
}
|
||||
}
|
||||
}
|
||||
unmount() {
|
||||
this.removeGroupControls();
|
||||
this.removeListeners();
|
||||
/**
|
||||
* In React 19, during list reorder reconciliation, components may
|
||||
* briefly unmount and remount while the drag is still active. If we're
|
||||
* actively dragging, we should NOT end the pan session - it will
|
||||
* continue tracking pointer events via its window-level listeners.
|
||||
*
|
||||
* The pan session will be properly cleaned up when:
|
||||
* 1. The drag ends naturally (pointerup/pointercancel)
|
||||
* 2. The component is truly removed from the DOM
|
||||
*/
|
||||
if (!this.controls.isDragging) {
|
||||
this.controls.endPanSession();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const asyncHandler = (handler) => (event, info) => {
|
||||
if (handler) {
|
||||
motionDom.frame.update(() => handler(event, info), false, true);
|
||||
}
|
||||
};
|
||||
class PanGesture extends motionDom.Feature {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.removePointerDownListener = motionUtils.noop;
|
||||
}
|
||||
onPointerDown(pointerDownEvent) {
|
||||
this.session = new PanSession(pointerDownEvent, this.createPanHandlers(), {
|
||||
transformPagePoint: this.node.getTransformPagePoint(),
|
||||
contextWindow: getContextWindow(this.node),
|
||||
});
|
||||
}
|
||||
createPanHandlers() {
|
||||
const { onPanSessionStart, onPanStart, onPan, onPanEnd } = this.node.getProps();
|
||||
return {
|
||||
onSessionStart: asyncHandler(onPanSessionStart),
|
||||
onStart: asyncHandler(onPanStart),
|
||||
onMove: asyncHandler(onPan),
|
||||
onEnd: (event, info) => {
|
||||
delete this.session;
|
||||
if (onPanEnd) {
|
||||
motionDom.frame.postRender(() => onPanEnd(event, info));
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
mount() {
|
||||
this.removePointerDownListener = addPointerEvent(this.node.current, "pointerdown", (event) => this.onPointerDown(event));
|
||||
}
|
||||
update() {
|
||||
this.session && this.session.updateHandlers(this.createPanHandlers());
|
||||
}
|
||||
unmount() {
|
||||
this.removePointerDownListener();
|
||||
this.session && this.session.end();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track whether we've taken any snapshots yet. If not,
|
||||
* we can safely skip notification of didUpdate.
|
||||
*
|
||||
* Difficult to capture in a test but to prevent flickering
|
||||
* we must set this to true either on update or unmount.
|
||||
* Running `next-env/layout-id` in Safari will show this behaviour if broken.
|
||||
*/
|
||||
let hasTakenAnySnapshot = false;
|
||||
class MeasureLayoutWithContext extends React.Component {
|
||||
/**
|
||||
* This only mounts projection nodes for components that
|
||||
* need measuring, we might want to do it for all components
|
||||
* in order to incorporate transforms
|
||||
*/
|
||||
componentDidMount() {
|
||||
const { visualElement, layoutGroup, switchLayoutGroup, layoutId } = this.props;
|
||||
const { projection } = visualElement;
|
||||
if (projection) {
|
||||
if (layoutGroup.group)
|
||||
layoutGroup.group.add(projection);
|
||||
if (switchLayoutGroup && switchLayoutGroup.register && layoutId) {
|
||||
switchLayoutGroup.register(projection);
|
||||
}
|
||||
if (hasTakenAnySnapshot) {
|
||||
projection.root.didUpdate();
|
||||
}
|
||||
projection.addEventListener("animationComplete", () => {
|
||||
this.safeToRemove();
|
||||
});
|
||||
projection.setOptions({
|
||||
...projection.options,
|
||||
layoutDependency: this.props.layoutDependency,
|
||||
onExitComplete: () => this.safeToRemove(),
|
||||
});
|
||||
}
|
||||
motionDom.globalProjectionState.hasEverUpdated = true;
|
||||
}
|
||||
getSnapshotBeforeUpdate(prevProps) {
|
||||
const { layoutDependency, visualElement, drag, isPresent } = this.props;
|
||||
const { projection } = visualElement;
|
||||
if (!projection)
|
||||
return null;
|
||||
/**
|
||||
* TODO: We use this data in relegate to determine whether to
|
||||
* promote a previous element. There's no guarantee its presence data
|
||||
* will have updated by this point - if a bug like this arises it will
|
||||
* have to be that we markForRelegation and then find a new lead some other way,
|
||||
* perhaps in didUpdate
|
||||
*/
|
||||
projection.isPresent = isPresent;
|
||||
if (prevProps.layoutDependency !== layoutDependency) {
|
||||
projection.setOptions({
|
||||
...projection.options,
|
||||
layoutDependency,
|
||||
});
|
||||
}
|
||||
hasTakenAnySnapshot = true;
|
||||
if (drag ||
|
||||
prevProps.layoutDependency !== layoutDependency ||
|
||||
layoutDependency === undefined ||
|
||||
prevProps.isPresent !== isPresent) {
|
||||
projection.willUpdate();
|
||||
}
|
||||
else {
|
||||
this.safeToRemove();
|
||||
}
|
||||
if (prevProps.isPresent !== isPresent) {
|
||||
if (isPresent) {
|
||||
projection.promote();
|
||||
}
|
||||
else if (!projection.relegate()) {
|
||||
/**
|
||||
* If there's another stack member taking over from this one,
|
||||
* it's in charge of the exit animation and therefore should
|
||||
* be in charge of the safe to remove. Otherwise we call it here.
|
||||
*/
|
||||
motionDom.frame.postRender(() => {
|
||||
const stack = projection.getStack();
|
||||
if (!stack || !stack.members.length) {
|
||||
this.safeToRemove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
componentDidUpdate() {
|
||||
const { visualElement, layoutAnchor } = this.props;
|
||||
const { projection } = visualElement;
|
||||
if (projection) {
|
||||
projection.options.layoutAnchor = layoutAnchor;
|
||||
projection.root.didUpdate();
|
||||
motionDom.microtask.postRender(() => {
|
||||
if (!projection.currentAnimation && projection.isLead()) {
|
||||
this.safeToRemove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
componentWillUnmount() {
|
||||
const { visualElement, layoutGroup, switchLayoutGroup: promoteContext, } = this.props;
|
||||
const { projection } = visualElement;
|
||||
hasTakenAnySnapshot = true;
|
||||
if (projection) {
|
||||
projection.scheduleCheckAfterUnmount();
|
||||
if (layoutGroup && layoutGroup.group)
|
||||
layoutGroup.group.remove(projection);
|
||||
if (promoteContext && promoteContext.deregister)
|
||||
promoteContext.deregister(projection);
|
||||
}
|
||||
}
|
||||
safeToRemove() {
|
||||
const { safeToRemove } = this.props;
|
||||
safeToRemove && safeToRemove();
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function MeasureLayout(props) {
|
||||
const [isPresent, safeToRemove] = usePresence();
|
||||
const layoutGroup = React.useContext(index.LayoutGroupContext);
|
||||
return (jsxRuntime.jsx(MeasureLayoutWithContext, { ...props, layoutGroup: layoutGroup, switchLayoutGroup: React.useContext(index.SwitchLayoutGroupContext), isPresent: isPresent, safeToRemove: safeToRemove }));
|
||||
}
|
||||
|
||||
const drag = {
|
||||
pan: {
|
||||
Feature: PanGesture,
|
||||
},
|
||||
drag: {
|
||||
Feature: DragGesture,
|
||||
ProjectionNode: motionDom.HTMLProjectionNode,
|
||||
MeasureLayout,
|
||||
},
|
||||
};
|
||||
|
||||
function handleHoverEvent(node, event, lifecycle) {
|
||||
const { props } = node;
|
||||
if (node.animationState && props.whileHover) {
|
||||
node.animationState.setActive("whileHover", lifecycle === "Start");
|
||||
}
|
||||
const eventName = ("onHover" + lifecycle);
|
||||
const callback = props[eventName];
|
||||
if (callback) {
|
||||
motionDom.frame.postRender(() => callback(event, extractEventInfo(event)));
|
||||
}
|
||||
}
|
||||
class HoverGesture extends motionDom.Feature {
|
||||
mount() {
|
||||
const { current } = this.node;
|
||||
if (!current)
|
||||
return;
|
||||
this.unmount = motionDom.hover(current, (_element, startEvent) => {
|
||||
handleHoverEvent(this.node, startEvent, "Start");
|
||||
return (endEvent) => handleHoverEvent(this.node, endEvent, "End");
|
||||
});
|
||||
}
|
||||
unmount() { }
|
||||
}
|
||||
|
||||
class FocusGesture extends motionDom.Feature {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.isActive = false;
|
||||
}
|
||||
onFocus() {
|
||||
let isFocusVisible = false;
|
||||
/**
|
||||
* If this element doesn't match focus-visible then don't
|
||||
* apply whileHover. But, if matches throws that focus-visible
|
||||
* is not a valid selector then in that browser outline styles will be applied
|
||||
* to the element by default and we want to match that behaviour with whileFocus.
|
||||
*/
|
||||
try {
|
||||
isFocusVisible = this.node.current.matches(":focus-visible");
|
||||
}
|
||||
catch (e) {
|
||||
isFocusVisible = true;
|
||||
}
|
||||
if (!isFocusVisible || !this.node.animationState)
|
||||
return;
|
||||
this.node.animationState.setActive("whileFocus", true);
|
||||
this.isActive = true;
|
||||
}
|
||||
onBlur() {
|
||||
if (!this.isActive || !this.node.animationState)
|
||||
return;
|
||||
this.node.animationState.setActive("whileFocus", false);
|
||||
this.isActive = false;
|
||||
}
|
||||
mount() {
|
||||
this.unmount = motionUtils.pipe(motionDom.addDomEvent(this.node.current, "focus", () => this.onFocus()), motionDom.addDomEvent(this.node.current, "blur", () => this.onBlur()));
|
||||
}
|
||||
unmount() { }
|
||||
}
|
||||
|
||||
function handlePressEvent(node, event, lifecycle) {
|
||||
const { props } = node;
|
||||
if (node.current instanceof HTMLButtonElement && node.current.disabled) {
|
||||
return;
|
||||
}
|
||||
if (node.animationState && props.whileTap) {
|
||||
node.animationState.setActive("whileTap", lifecycle === "Start");
|
||||
}
|
||||
const eventName = ("onTap" + (lifecycle === "End" ? "" : lifecycle));
|
||||
const callback = props[eventName];
|
||||
if (callback) {
|
||||
motionDom.frame.postRender(() => callback(event, extractEventInfo(event)));
|
||||
}
|
||||
}
|
||||
class PressGesture extends motionDom.Feature {
|
||||
mount() {
|
||||
const { current } = this.node;
|
||||
if (!current)
|
||||
return;
|
||||
const { globalTapTarget, propagate } = this.node.props;
|
||||
this.unmount = motionDom.press(current, (_element, startEvent) => {
|
||||
handlePressEvent(this.node, startEvent, "Start");
|
||||
return (endEvent, { success }) => handlePressEvent(this.node, endEvent, success ? "End" : "Cancel");
|
||||
}, {
|
||||
useGlobalTarget: globalTapTarget,
|
||||
stopPropagation: propagate?.tap === false,
|
||||
});
|
||||
}
|
||||
unmount() { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an IntersectionHandler callback to an element. We only ever make one handler for one
|
||||
* element, so even though these handlers might all be triggered by different
|
||||
* observers, we can keep them in the same map.
|
||||
*/
|
||||
const observerCallbacks = new WeakMap();
|
||||
/**
|
||||
* Multiple observers can be created for multiple element/document roots. Each with
|
||||
* different settings. So here we store dictionaries of observers to each root,
|
||||
* using serialised settings (threshold/margin) as lookup keys.
|
||||
*/
|
||||
const observers = new WeakMap();
|
||||
const fireObserverCallback = (entry) => {
|
||||
const callback = observerCallbacks.get(entry.target);
|
||||
callback && callback(entry);
|
||||
};
|
||||
const fireAllObserverCallbacks = (entries) => {
|
||||
entries.forEach(fireObserverCallback);
|
||||
};
|
||||
function initIntersectionObserver({ root, ...options }) {
|
||||
const lookupRoot = root || document;
|
||||
/**
|
||||
* If we don't have an observer lookup map for this root, create one.
|
||||
*/
|
||||
if (!observers.has(lookupRoot)) {
|
||||
observers.set(lookupRoot, {});
|
||||
}
|
||||
const rootObservers = observers.get(lookupRoot);
|
||||
const key = JSON.stringify(options);
|
||||
/**
|
||||
* If we don't have an observer for this combination of root and settings,
|
||||
* create one.
|
||||
*/
|
||||
if (!rootObservers[key]) {
|
||||
rootObservers[key] = new IntersectionObserver(fireAllObserverCallbacks, { root, ...options });
|
||||
}
|
||||
return rootObservers[key];
|
||||
}
|
||||
function observeIntersection(element, options, callback) {
|
||||
const rootInteresectionObserver = initIntersectionObserver(options);
|
||||
observerCallbacks.set(element, callback);
|
||||
rootInteresectionObserver.observe(element);
|
||||
return () => {
|
||||
observerCallbacks.delete(element);
|
||||
rootInteresectionObserver.unobserve(element);
|
||||
};
|
||||
}
|
||||
|
||||
const thresholdNames = {
|
||||
some: 0,
|
||||
all: 1,
|
||||
};
|
||||
class InViewFeature extends motionDom.Feature {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.hasEnteredView = false;
|
||||
this.isInView = false;
|
||||
}
|
||||
startObserver() {
|
||||
this.stopObserver?.();
|
||||
const { viewport = {} } = this.node.getProps();
|
||||
const { root, margin: rootMargin, amount = "some", once } = viewport;
|
||||
const options = {
|
||||
root: root ? root.current : undefined,
|
||||
rootMargin,
|
||||
threshold: typeof amount === "number" ? amount : thresholdNames[amount],
|
||||
};
|
||||
const onIntersectionUpdate = (entry) => {
|
||||
const { isIntersecting } = entry;
|
||||
/**
|
||||
* If there's been no change in the viewport state, early return.
|
||||
*/
|
||||
if (this.isInView === isIntersecting)
|
||||
return;
|
||||
this.isInView = isIntersecting;
|
||||
/**
|
||||
* Handle hasEnteredView. If this is only meant to run once, and
|
||||
* element isn't visible, early return. Otherwise set hasEnteredView to true.
|
||||
*/
|
||||
if (once && !isIntersecting && this.hasEnteredView) {
|
||||
return;
|
||||
}
|
||||
else if (isIntersecting) {
|
||||
this.hasEnteredView = true;
|
||||
}
|
||||
if (this.node.animationState) {
|
||||
this.node.animationState.setActive("whileInView", isIntersecting);
|
||||
}
|
||||
/**
|
||||
* Use the latest committed props rather than the ones in scope
|
||||
* when this observer is created
|
||||
*/
|
||||
const { onViewportEnter, onViewportLeave } = this.node.getProps();
|
||||
const callback = isIntersecting ? onViewportEnter : onViewportLeave;
|
||||
callback && callback(entry);
|
||||
};
|
||||
this.stopObserver = observeIntersection(this.node.current, options, onIntersectionUpdate);
|
||||
}
|
||||
mount() {
|
||||
this.startObserver();
|
||||
}
|
||||
update() {
|
||||
if (typeof IntersectionObserver === "undefined")
|
||||
return;
|
||||
const { props, prevProps } = this.node;
|
||||
const hasOptionsChanged = ["amount", "margin", "root"].some(hasViewportOptionChanged(props, prevProps));
|
||||
if (hasOptionsChanged) {
|
||||
this.startObserver();
|
||||
}
|
||||
}
|
||||
unmount() {
|
||||
this.stopObserver?.();
|
||||
this.hasEnteredView = false;
|
||||
this.isInView = false;
|
||||
}
|
||||
}
|
||||
function hasViewportOptionChanged({ viewport = {} }, { viewport: prevViewport = {} } = {}) {
|
||||
return (name) => viewport[name] !== prevViewport[name];
|
||||
}
|
||||
|
||||
const gestureAnimations = {
|
||||
inView: {
|
||||
Feature: InViewFeature,
|
||||
},
|
||||
tap: {
|
||||
Feature: PressGesture,
|
||||
},
|
||||
focus: {
|
||||
Feature: FocusGesture,
|
||||
},
|
||||
hover: {
|
||||
Feature: HoverGesture,
|
||||
},
|
||||
};
|
||||
|
||||
const layout = {
|
||||
layout: {
|
||||
ProjectionNode: motionDom.HTMLProjectionNode,
|
||||
MeasureLayout,
|
||||
},
|
||||
};
|
||||
|
||||
const featureBundle = {
|
||||
...animations,
|
||||
...gestureAnimations,
|
||||
...drag,
|
||||
...layout,
|
||||
};
|
||||
|
||||
exports.addPointerEvent = addPointerEvent;
|
||||
exports.addPointerInfo = addPointerInfo;
|
||||
exports.animations = animations;
|
||||
exports.createDomVisualElement = createDomVisualElement;
|
||||
exports.distance = distance;
|
||||
exports.distance2D = distance2D;
|
||||
exports.drag = drag;
|
||||
exports.featureBundle = featureBundle;
|
||||
exports.gestureAnimations = gestureAnimations;
|
||||
exports.layout = layout;
|
||||
exports.useIsPresent = useIsPresent;
|
||||
exports.usePresence = usePresence;
|
||||
//# sourceMappingURL=feature-bundle-riC1cckV.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+802
@@ -0,0 +1,802 @@
|
||||
'use strict';
|
||||
|
||||
var jsxRuntime = require('react/jsx-runtime');
|
||||
var motionUtils = require('motion-utils');
|
||||
var React = require('react');
|
||||
var motionDom = require('motion-dom');
|
||||
|
||||
const LayoutGroupContext = React.createContext({});
|
||||
|
||||
/**
|
||||
* Creates a constant value over the lifecycle of a component.
|
||||
*
|
||||
* Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
|
||||
* a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
|
||||
* you can ensure that initialisers don't execute twice or more.
|
||||
*/
|
||||
function useConstant(init) {
|
||||
const ref = React.useRef(null);
|
||||
if (ref.current === null) {
|
||||
ref.current = init();
|
||||
}
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
const isBrowser = typeof window !== "undefined";
|
||||
|
||||
const useIsomorphicLayoutEffect = isBrowser ? React.useLayoutEffect : React.useEffect;
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const PresenceContext =
|
||||
/* @__PURE__ */ React.createContext(null);
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const MotionConfigContext = React.createContext({
|
||||
transformPagePoint: (p) => p,
|
||||
isStatic: false,
|
||||
reducedMotion: "never",
|
||||
});
|
||||
|
||||
const LazyContext = React.createContext({ strict: false });
|
||||
|
||||
const featureProps = {
|
||||
animation: [
|
||||
"animate",
|
||||
"variants",
|
||||
"whileHover",
|
||||
"whileTap",
|
||||
"exit",
|
||||
"whileInView",
|
||||
"whileFocus",
|
||||
"whileDrag",
|
||||
],
|
||||
exit: ["exit"],
|
||||
drag: ["drag", "dragControls"],
|
||||
focus: ["whileFocus"],
|
||||
hover: ["whileHover", "onHoverStart", "onHoverEnd"],
|
||||
tap: ["whileTap", "onTap", "onTapStart", "onTapCancel"],
|
||||
pan: ["onPan", "onPanStart", "onPanSessionStart", "onPanEnd"],
|
||||
inView: ["whileInView", "onViewportEnter", "onViewportLeave"],
|
||||
layout: ["layout", "layoutId"],
|
||||
};
|
||||
let isInitialized = false;
|
||||
/**
|
||||
* Initialize feature definitions with isEnabled checks.
|
||||
* This must be called before any motion components are rendered.
|
||||
*/
|
||||
function initFeatureDefinitions() {
|
||||
if (isInitialized)
|
||||
return;
|
||||
const initialFeatureDefinitions = {};
|
||||
for (const key in featureProps) {
|
||||
initialFeatureDefinitions[key] = {
|
||||
isEnabled: (props) => featureProps[key].some((name) => !!props[name]),
|
||||
};
|
||||
}
|
||||
motionDom.setFeatureDefinitions(initialFeatureDefinitions);
|
||||
isInitialized = true;
|
||||
}
|
||||
/**
|
||||
* Get the current feature definitions, initializing if needed.
|
||||
*/
|
||||
function getInitializedFeatureDefinitions() {
|
||||
initFeatureDefinitions();
|
||||
return motionDom.getFeatureDefinitions();
|
||||
}
|
||||
|
||||
function loadFeatures(features) {
|
||||
const featureDefinitions = getInitializedFeatureDefinitions();
|
||||
for (const key in features) {
|
||||
featureDefinitions[key] = {
|
||||
...featureDefinitions[key],
|
||||
...features[key],
|
||||
};
|
||||
}
|
||||
motionDom.setFeatureDefinitions(featureDefinitions);
|
||||
}
|
||||
|
||||
/**
|
||||
* A list of all valid MotionProps.
|
||||
*
|
||||
* @privateRemarks
|
||||
* This doesn't throw if a `MotionProp` name is missing - it should.
|
||||
*/
|
||||
const validMotionProps = new Set([
|
||||
"animate",
|
||||
"exit",
|
||||
"variants",
|
||||
"initial",
|
||||
"style",
|
||||
"values",
|
||||
"variants",
|
||||
"transition",
|
||||
"transformTemplate",
|
||||
"custom",
|
||||
"inherit",
|
||||
"onBeforeLayoutMeasure",
|
||||
"onAnimationStart",
|
||||
"onAnimationComplete",
|
||||
"onUpdate",
|
||||
"onDragStart",
|
||||
"onDrag",
|
||||
"onDragEnd",
|
||||
"onMeasureDragConstraints",
|
||||
"onDirectionLock",
|
||||
"onDragTransitionEnd",
|
||||
"_dragX",
|
||||
"_dragY",
|
||||
"onHoverStart",
|
||||
"onHoverEnd",
|
||||
"onViewportEnter",
|
||||
"onViewportLeave",
|
||||
"globalTapTarget",
|
||||
"propagate",
|
||||
"ignoreStrict",
|
||||
"viewport",
|
||||
]);
|
||||
/**
|
||||
* Check whether a prop name is a valid `MotionProp` key.
|
||||
*
|
||||
* @param key - Name of the property to check
|
||||
* @returns `true` is key is a valid `MotionProp`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function isValidMotionProp(key) {
|
||||
return (key.startsWith("while") ||
|
||||
(key.startsWith("drag") && key !== "draggable") ||
|
||||
key.startsWith("layout") ||
|
||||
key.startsWith("onTap") ||
|
||||
key.startsWith("onPan") ||
|
||||
key.startsWith("onLayout") ||
|
||||
validMotionProps.has(key));
|
||||
}
|
||||
|
||||
let shouldForward = (key) => !isValidMotionProp(key);
|
||||
function loadExternalIsValidProp(isValidProp) {
|
||||
if (typeof isValidProp !== "function")
|
||||
return;
|
||||
// Explicitly filter our events
|
||||
shouldForward = (key) => key.startsWith("on") ? !isValidMotionProp(key) : isValidProp(key);
|
||||
}
|
||||
/**
|
||||
* Emotion and Styled Components both allow users to pass through arbitrary props to their components
|
||||
* to dynamically generate CSS. They both use the `@emotion/is-prop-valid` package to determine which
|
||||
* of these should be passed to the underlying DOM node.
|
||||
*
|
||||
* However, when styling a Motion component `styled(motion.div)`, both packages pass through *all* props
|
||||
* as it's seen as an arbitrary component rather than a DOM node. Motion only allows arbitrary props
|
||||
* passed through the `custom` prop so it doesn't *need* the payload or computational overhead of
|
||||
* `@emotion/is-prop-valid`, however to fix this problem we need to use it.
|
||||
*
|
||||
* By making it an optionalDependency we can offer this functionality only in the situations where it's
|
||||
* actually required.
|
||||
*/
|
||||
try {
|
||||
/**
|
||||
* We attempt to import this package but require won't be defined in esm environments, in that case
|
||||
* isPropValid will have to be provided via `MotionContext`. In a 6.0.0 this should probably be removed
|
||||
* in favour of explicit injection.
|
||||
*
|
||||
* String concatenation prevents bundlers like webpack (e.g. Storybook)
|
||||
* from statically resolving this optional dependency at build time.
|
||||
*/
|
||||
const emotionPkg = "@emotion/is-prop-" + "valid";
|
||||
loadExternalIsValidProp(require(emotionPkg).default);
|
||||
}
|
||||
catch {
|
||||
// We don't need to actually do anything here - the fallback is the existing `isPropValid`.
|
||||
}
|
||||
function filterProps(props, isDom, forwardMotionProps) {
|
||||
const filteredProps = {};
|
||||
for (const key in props) {
|
||||
/**
|
||||
* values is considered a valid prop by Emotion, so if it's present
|
||||
* this will be rendered out to the DOM unless explicitly filtered.
|
||||
*
|
||||
* We check the type as it could be used with the `feColorMatrix`
|
||||
* element, which we support.
|
||||
*/
|
||||
if (key === "values" && typeof props.values === "object")
|
||||
continue;
|
||||
if (motionDom.isMotionValue(props[key]))
|
||||
continue;
|
||||
if (shouldForward(key) ||
|
||||
(forwardMotionProps === true && isValidMotionProp(key)) ||
|
||||
(!isDom && !isValidMotionProp(key)) ||
|
||||
// If trying to use native HTML drag events, forward drag listeners
|
||||
(props["draggable"] &&
|
||||
key.startsWith("onDrag"))) {
|
||||
filteredProps[key] =
|
||||
props[key];
|
||||
}
|
||||
}
|
||||
return filteredProps;
|
||||
}
|
||||
|
||||
/**
|
||||
* We keep these listed separately 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",
|
||||
];
|
||||
|
||||
function isSVGComponent(Component) {
|
||||
if (
|
||||
/**
|
||||
* If it's not a string, it's a custom React component. Currently we only support
|
||||
* HTML custom React components.
|
||||
*/
|
||||
typeof Component !== "string" ||
|
||||
/**
|
||||
* If it contains a dash, the element is a custom HTML webcomponent.
|
||||
*/
|
||||
Component.includes("-")) {
|
||||
return false;
|
||||
}
|
||||
else if (
|
||||
/**
|
||||
* If it's in our list of lowercase SVG tags, it's an SVG component
|
||||
*/
|
||||
lowercaseSVGElements.indexOf(Component) > -1 ||
|
||||
/**
|
||||
* If it contains a capital letter, it's an SVG component
|
||||
*/
|
||||
/[A-Z]/u.test(Component)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const MotionContext = /* @__PURE__ */ React.createContext({});
|
||||
|
||||
function getCurrentTreeVariants(props, context) {
|
||||
if (motionDom.isControllingVariants(props)) {
|
||||
const { initial, animate } = props;
|
||||
return {
|
||||
initial: initial === false || motionDom.isVariantLabel(initial)
|
||||
? initial
|
||||
: undefined,
|
||||
animate: motionDom.isVariantLabel(animate) ? animate : undefined,
|
||||
};
|
||||
}
|
||||
return props.inherit !== false ? context : {};
|
||||
}
|
||||
|
||||
function useCreateMotionContext(props) {
|
||||
const { initial, animate } = getCurrentTreeVariants(props, React.useContext(MotionContext));
|
||||
return React.useMemo(() => ({ initial, animate }), [variantLabelsAsDependency(initial), variantLabelsAsDependency(animate)]);
|
||||
}
|
||||
function variantLabelsAsDependency(prop) {
|
||||
return Array.isArray(prop) ? prop.join(" ") : prop;
|
||||
}
|
||||
|
||||
const createHtmlRenderState = () => ({
|
||||
style: {},
|
||||
transform: {},
|
||||
transformOrigin: {},
|
||||
vars: {},
|
||||
});
|
||||
|
||||
function copyRawValuesOnly(target, source, props) {
|
||||
for (const key in source) {
|
||||
if (!motionDom.isMotionValue(source[key]) && !motionDom.isForcedMotionValue(key, props)) {
|
||||
target[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
function useInitialMotionValues({ transformTemplate }, visualState) {
|
||||
return React.useMemo(() => {
|
||||
const state = createHtmlRenderState();
|
||||
motionDom.buildHTMLStyles(state, visualState, transformTemplate);
|
||||
return Object.assign({}, state.vars, state.style);
|
||||
}, [visualState]);
|
||||
}
|
||||
function useStyle(props, visualState) {
|
||||
const styleProp = props.style || {};
|
||||
const style = {};
|
||||
/**
|
||||
* Copy non-Motion Values straight into style
|
||||
*/
|
||||
copyRawValuesOnly(style, styleProp, props);
|
||||
Object.assign(style, useInitialMotionValues(props, visualState));
|
||||
return style;
|
||||
}
|
||||
function useHTMLProps(props, visualState) {
|
||||
// The `any` isn't ideal but it is the type of createElement props argument
|
||||
const htmlProps = {};
|
||||
const style = useStyle(props, visualState);
|
||||
if (props.drag && props.dragListener !== false) {
|
||||
// Disable the ghost element when a user drags
|
||||
htmlProps.draggable = false;
|
||||
// Disable text selection
|
||||
style.userSelect =
|
||||
style.WebkitUserSelect =
|
||||
style.WebkitTouchCallout =
|
||||
"none";
|
||||
// Disable scrolling on the draggable direction
|
||||
style.touchAction =
|
||||
props.drag === true
|
||||
? "none"
|
||||
: `pan-${props.drag === "x" ? "y" : "x"}`;
|
||||
}
|
||||
if (props.tabIndex === undefined &&
|
||||
(props.onTap || props.onTapStart || props.whileTap)) {
|
||||
htmlProps.tabIndex = 0;
|
||||
}
|
||||
htmlProps.style = style;
|
||||
return htmlProps;
|
||||
}
|
||||
|
||||
const createSvgRenderState = () => ({
|
||||
...createHtmlRenderState(),
|
||||
attrs: {},
|
||||
});
|
||||
|
||||
function useSVGProps(props, visualState, _isStatic, Component) {
|
||||
const visualProps = React.useMemo(() => {
|
||||
const state = createSvgRenderState();
|
||||
motionDom.buildSVGAttrs(state, visualState, motionDom.isSVGTag(Component), props.transformTemplate, props.style);
|
||||
return {
|
||||
...state.attrs,
|
||||
style: { ...state.style },
|
||||
};
|
||||
}, [visualState]);
|
||||
if (props.style) {
|
||||
const rawStyles = {};
|
||||
copyRawValuesOnly(rawStyles, props.style, props);
|
||||
visualProps.style = { ...rawStyles, ...visualProps.style };
|
||||
}
|
||||
return visualProps;
|
||||
}
|
||||
|
||||
function useRender(Component, props, ref, { latestValues, }, isStatic, forwardMotionProps = false, isSVG) {
|
||||
const useVisualProps = (isSVG ?? isSVGComponent(Component)) ? useSVGProps : useHTMLProps;
|
||||
const visualProps = useVisualProps(props, latestValues, isStatic, Component);
|
||||
const filteredProps = filterProps(props, typeof Component === "string", forwardMotionProps);
|
||||
const elementProps = Component !== React.Fragment ? { ...filteredProps, ...visualProps, ref } : {};
|
||||
/**
|
||||
* If component has been handed a motion value as its child,
|
||||
* memoise its initial value and render that. Subsequent updates
|
||||
* will be handled by the onChange handler
|
||||
*/
|
||||
const { children } = props;
|
||||
const renderedChildren = React.useMemo(() => (motionDom.isMotionValue(children) ? children.get() : children), [children]);
|
||||
return React.createElement(Component, {
|
||||
...elementProps,
|
||||
children: renderedChildren,
|
||||
});
|
||||
}
|
||||
|
||||
function makeState({ scrapeMotionValuesFromProps, createRenderState, }, props, context, presenceContext) {
|
||||
const state = {
|
||||
latestValues: makeLatestValues(props, context, presenceContext, scrapeMotionValuesFromProps),
|
||||
renderState: createRenderState(),
|
||||
};
|
||||
return state;
|
||||
}
|
||||
function makeLatestValues(props, context, presenceContext, scrapeMotionValues) {
|
||||
const values = {};
|
||||
const motionValues = scrapeMotionValues(props, {});
|
||||
for (const key in motionValues) {
|
||||
values[key] = motionDom.resolveMotionValue(motionValues[key]);
|
||||
}
|
||||
let { initial, animate } = props;
|
||||
const isControllingVariants = motionDom.isControllingVariants(props);
|
||||
const isVariantNode = motionDom.isVariantNode(props);
|
||||
if (context &&
|
||||
isVariantNode &&
|
||||
!isControllingVariants &&
|
||||
props.inherit !== false) {
|
||||
if (initial === undefined)
|
||||
initial = context.initial;
|
||||
if (animate === undefined)
|
||||
animate = context.animate;
|
||||
}
|
||||
let isInitialAnimationBlocked = presenceContext
|
||||
? presenceContext.initial === false
|
||||
: false;
|
||||
isInitialAnimationBlocked = isInitialAnimationBlocked || initial === false;
|
||||
const variantToSet = isInitialAnimationBlocked ? animate : initial;
|
||||
if (variantToSet &&
|
||||
typeof variantToSet !== "boolean" &&
|
||||
!motionDom.isAnimationControls(variantToSet)) {
|
||||
const list = Array.isArray(variantToSet) ? variantToSet : [variantToSet];
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const resolved = motionDom.resolveVariantFromProps(props, list[i]);
|
||||
if (resolved) {
|
||||
const { transitionEnd, transition, ...target } = resolved;
|
||||
for (const key in target) {
|
||||
let valueTarget = target[key];
|
||||
if (Array.isArray(valueTarget)) {
|
||||
/**
|
||||
* Take final keyframe if the initial animation is blocked because
|
||||
* we want to initialise at the end of that blocked animation.
|
||||
*/
|
||||
const index = isInitialAnimationBlocked
|
||||
? valueTarget.length - 1
|
||||
: 0;
|
||||
valueTarget = valueTarget[index];
|
||||
}
|
||||
if (valueTarget !== null) {
|
||||
values[key] = valueTarget;
|
||||
}
|
||||
}
|
||||
for (const key in transitionEnd) {
|
||||
values[key] = transitionEnd[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return values;
|
||||
}
|
||||
const makeUseVisualState = (config) => (props, isStatic) => {
|
||||
const context = React.useContext(MotionContext);
|
||||
const presenceContext = React.useContext(PresenceContext);
|
||||
const make = () => makeState(config, props, context, presenceContext);
|
||||
return isStatic ? make() : useConstant(make);
|
||||
};
|
||||
|
||||
const useHTMLVisualState = /*@__PURE__*/ makeUseVisualState({
|
||||
scrapeMotionValuesFromProps: motionDom.scrapeHTMLMotionValuesFromProps,
|
||||
createRenderState: createHtmlRenderState,
|
||||
});
|
||||
|
||||
const useSVGVisualState = /*@__PURE__*/ makeUseVisualState({
|
||||
scrapeMotionValuesFromProps: motionDom.scrapeSVGMotionValuesFromProps,
|
||||
createRenderState: createSvgRenderState,
|
||||
});
|
||||
|
||||
const motionComponentSymbol = Symbol.for("motionComponentSymbol");
|
||||
|
||||
/**
|
||||
* Creates a ref function that, when called, hydrates the provided
|
||||
* external ref and VisualElement.
|
||||
*/
|
||||
function useMotionRef(visualState, visualElement, externalRef) {
|
||||
/**
|
||||
* Store externalRef in a ref to avoid including it in the useCallback
|
||||
* dependency array. Including externalRef in dependencies causes issues
|
||||
* with libraries like Radix UI that create new callback refs on each render
|
||||
* when using asChild - this would cause the callback to be recreated,
|
||||
* triggering element remounts and breaking AnimatePresence exit animations.
|
||||
*/
|
||||
const externalRefContainer = React.useRef(externalRef);
|
||||
React.useInsertionEffect(() => {
|
||||
externalRefContainer.current = externalRef;
|
||||
});
|
||||
// Store cleanup function returned by callback refs (React 19 feature)
|
||||
const refCleanup = React.useRef(null);
|
||||
return React.useCallback((instance) => {
|
||||
if (instance) {
|
||||
visualState.onMount?.(instance);
|
||||
}
|
||||
if (visualElement) {
|
||||
instance ? visualElement.mount(instance) : visualElement.unmount();
|
||||
}
|
||||
const ref = externalRefContainer.current;
|
||||
if (typeof ref === "function") {
|
||||
if (instance) {
|
||||
const cleanup = ref(instance);
|
||||
if (typeof cleanup === "function") {
|
||||
refCleanup.current = cleanup;
|
||||
}
|
||||
}
|
||||
else if (refCleanup.current) {
|
||||
refCleanup.current();
|
||||
refCleanup.current = null;
|
||||
}
|
||||
else {
|
||||
ref(instance);
|
||||
}
|
||||
}
|
||||
else if (ref) {
|
||||
ref.current = instance;
|
||||
}
|
||||
}, [visualElement]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal, exported only for usage in Framer
|
||||
*/
|
||||
const SwitchLayoutGroupContext = React.createContext({});
|
||||
|
||||
function isRefObject(ref) {
|
||||
return (ref &&
|
||||
typeof ref === "object" &&
|
||||
Object.prototype.hasOwnProperty.call(ref, "current"));
|
||||
}
|
||||
|
||||
function useVisualElement(Component, visualState, props, createVisualElement, ProjectionNodeConstructor, isSVG) {
|
||||
const { visualElement: parent } = React.useContext(MotionContext);
|
||||
const lazyContext = React.useContext(LazyContext);
|
||||
const presenceContext = React.useContext(PresenceContext);
|
||||
const motionConfig = React.useContext(MotionConfigContext);
|
||||
const reducedMotionConfig = motionConfig.reducedMotion;
|
||||
const skipAnimations = motionConfig.skipAnimations;
|
||||
const visualElementRef = React.useRef(null);
|
||||
/**
|
||||
* Track whether the component has been through React's commit phase.
|
||||
* Used to detect when LazyMotion features load after the component has mounted.
|
||||
*/
|
||||
const hasMountedOnce = React.useRef(false);
|
||||
/**
|
||||
* If we haven't preloaded a renderer, check to see if we have one lazy-loaded
|
||||
*/
|
||||
createVisualElement =
|
||||
createVisualElement ||
|
||||
lazyContext.renderer;
|
||||
if (!visualElementRef.current && createVisualElement) {
|
||||
visualElementRef.current = createVisualElement(Component, {
|
||||
visualState,
|
||||
parent,
|
||||
props,
|
||||
presenceContext,
|
||||
blockInitialAnimation: presenceContext
|
||||
? presenceContext.initial === false
|
||||
: false,
|
||||
reducedMotionConfig,
|
||||
skipAnimations,
|
||||
isSVG,
|
||||
});
|
||||
/**
|
||||
* If the component has already mounted before features loaded (e.g. via
|
||||
* LazyMotion with async feature loading), we need to force the initial
|
||||
* animation to run. Otherwise state changes that occurred before features
|
||||
* loaded will be lost and the element will snap to its final state.
|
||||
*/
|
||||
if (hasMountedOnce.current && visualElementRef.current) {
|
||||
visualElementRef.current.manuallyAnimateOnMount = true;
|
||||
}
|
||||
}
|
||||
const visualElement = visualElementRef.current;
|
||||
/**
|
||||
* Load Motion gesture and animation features. These are rendered as renderless
|
||||
* components so each feature can optionally make use of React lifecycle methods.
|
||||
*/
|
||||
const initialLayoutGroupConfig = React.useContext(SwitchLayoutGroupContext);
|
||||
if (visualElement &&
|
||||
!visualElement.projection &&
|
||||
ProjectionNodeConstructor &&
|
||||
(visualElement.type === "html" || visualElement.type === "svg")) {
|
||||
createProjectionNode(visualElementRef.current, props, ProjectionNodeConstructor, initialLayoutGroupConfig);
|
||||
}
|
||||
const isMounted = React.useRef(false);
|
||||
React.useInsertionEffect(() => {
|
||||
/**
|
||||
* Check the component has already mounted before calling
|
||||
* `update` unnecessarily. This ensures we skip the initial update.
|
||||
*/
|
||||
if (visualElement && isMounted.current) {
|
||||
visualElement.update(props, presenceContext);
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Cache this value as we want to know whether HandoffAppearAnimations
|
||||
* was present on initial render - it will be deleted after this.
|
||||
*/
|
||||
const optimisedAppearId = props[motionDom.optimizedAppearDataAttribute];
|
||||
const wantsHandoff = React.useRef(Boolean(optimisedAppearId) &&
|
||||
typeof window !== "undefined" &&
|
||||
!window.MotionHandoffIsComplete?.(optimisedAppearId) &&
|
||||
window.MotionHasOptimisedAnimation?.(optimisedAppearId));
|
||||
useIsomorphicLayoutEffect(() => {
|
||||
/**
|
||||
* Track that this component has mounted. This is used to detect when
|
||||
* LazyMotion features load after the component has already committed.
|
||||
*/
|
||||
hasMountedOnce.current = true;
|
||||
if (!visualElement)
|
||||
return;
|
||||
isMounted.current = true;
|
||||
window.MotionIsMounted = true;
|
||||
visualElement.updateFeatures();
|
||||
visualElement.scheduleRenderMicrotask();
|
||||
/**
|
||||
* Ideally this function would always run in a useEffect.
|
||||
*
|
||||
* However, if we have optimised appear animations to handoff from,
|
||||
* it needs to happen synchronously to ensure there's no flash of
|
||||
* incorrect styles in the event of a hydration error.
|
||||
*
|
||||
* So if we detect a situtation where optimised appear animations
|
||||
* are running, we use useLayoutEffect to trigger animations.
|
||||
*/
|
||||
if (wantsHandoff.current && visualElement.animationState) {
|
||||
visualElement.animationState.animateChanges();
|
||||
}
|
||||
});
|
||||
React.useEffect(() => {
|
||||
if (!visualElement)
|
||||
return;
|
||||
if (!wantsHandoff.current && visualElement.animationState) {
|
||||
visualElement.animationState.animateChanges();
|
||||
}
|
||||
if (wantsHandoff.current) {
|
||||
// This ensures all future calls to animateChanges() in this component will run in useEffect
|
||||
queueMicrotask(() => {
|
||||
window.MotionHandoffMarkAsComplete?.(optimisedAppearId);
|
||||
});
|
||||
wantsHandoff.current = false;
|
||||
}
|
||||
/**
|
||||
* Now we've finished triggering animations for this element we
|
||||
* can wipe the enteringChildren set for the next render.
|
||||
*/
|
||||
visualElement.enteringChildren = undefined;
|
||||
});
|
||||
return visualElement;
|
||||
}
|
||||
function createProjectionNode(visualElement, props, ProjectionNodeConstructor, initialPromotionConfig) {
|
||||
const { layoutId, layout, drag, dragConstraints, layoutScroll, layoutRoot, layoutAnchor, layoutCrossfade, } = props;
|
||||
visualElement.projection = new ProjectionNodeConstructor(visualElement.latestValues, props["data-framer-portal-id"]
|
||||
? undefined
|
||||
: getClosestProjectingNode(visualElement.parent));
|
||||
visualElement.projection.setOptions({
|
||||
layoutId,
|
||||
layout,
|
||||
alwaysMeasureLayout: Boolean(drag) || (dragConstraints && isRefObject(dragConstraints)),
|
||||
visualElement,
|
||||
/**
|
||||
* TODO: Update options in an effect. This could be tricky as it'll be too late
|
||||
* to update by the time layout animations run.
|
||||
* We also need to fix this safeToRemove by linking it up to the one returned by usePresence,
|
||||
* ensuring it gets called if there's no potential layout animations.
|
||||
*
|
||||
*/
|
||||
animationType: typeof layout === "string" ? layout : "both",
|
||||
initialPromotionConfig,
|
||||
crossfade: layoutCrossfade,
|
||||
layoutScroll,
|
||||
layoutRoot,
|
||||
layoutAnchor,
|
||||
});
|
||||
}
|
||||
function getClosestProjectingNode(visualElement) {
|
||||
if (!visualElement)
|
||||
return undefined;
|
||||
return visualElement.options.allowProjection !== false
|
||||
? visualElement.projection
|
||||
: getClosestProjectingNode(visualElement.parent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `motion` component.
|
||||
*
|
||||
* This function accepts a Component argument, which can be either a string (ie "div"
|
||||
* for `motion.div`), or an actual React component.
|
||||
*
|
||||
* Alongside this is a config option which provides a way of rendering the provided
|
||||
* component "offline", or outside the React render cycle.
|
||||
*/
|
||||
function createMotionComponent(Component, { forwardMotionProps = false, type } = {}, preloadedFeatures, createVisualElement) {
|
||||
preloadedFeatures && loadFeatures(preloadedFeatures);
|
||||
/**
|
||||
* Determine whether to use SVG or HTML rendering based on:
|
||||
* 1. Explicit `type` option (highest priority)
|
||||
* 2. Auto-detection via `isSVGComponent`
|
||||
*/
|
||||
const isSVG = type ? type === "svg" : isSVGComponent(Component);
|
||||
const useVisualState = isSVG ? useSVGVisualState : useHTMLVisualState;
|
||||
function MotionDOMComponent(props, externalRef) {
|
||||
/**
|
||||
* If we need to measure the element we load this functionality in a
|
||||
* separate class component in order to gain access to getSnapshotBeforeUpdate.
|
||||
*/
|
||||
let MeasureLayout;
|
||||
const configAndProps = {
|
||||
...React.useContext(MotionConfigContext),
|
||||
...props,
|
||||
layoutId: useLayoutId(props),
|
||||
};
|
||||
const { isStatic } = configAndProps;
|
||||
const context = useCreateMotionContext(props);
|
||||
const visualState = useVisualState(props, isStatic);
|
||||
if (!isStatic && typeof window !== "undefined") {
|
||||
useStrictMode(configAndProps, preloadedFeatures);
|
||||
const layoutProjection = getProjectionFunctionality(configAndProps);
|
||||
MeasureLayout = layoutProjection.MeasureLayout;
|
||||
/**
|
||||
* Create a VisualElement for this component. A VisualElement provides a common
|
||||
* interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
|
||||
* providing a way of rendering to these APIs outside of the React render loop
|
||||
* for more performant animations and interactions
|
||||
*/
|
||||
context.visualElement = useVisualElement(Component, visualState, configAndProps, createVisualElement, layoutProjection.ProjectionNode, isSVG);
|
||||
}
|
||||
/**
|
||||
* The mount order and hierarchy is specific to ensure our element ref
|
||||
* is hydrated by the time features fire their effects.
|
||||
*/
|
||||
return (jsxRuntime.jsxs(MotionContext.Provider, { value: context, children: [MeasureLayout && context.visualElement ? (jsxRuntime.jsx(MeasureLayout, { visualElement: context.visualElement, ...configAndProps })) : null, useRender(Component, props, useMotionRef(visualState, context.visualElement, externalRef), visualState, isStatic, forwardMotionProps, isSVG)] }));
|
||||
}
|
||||
MotionDOMComponent.displayName = `motion.${typeof Component === "string"
|
||||
? Component
|
||||
: `create(${Component.displayName ?? Component.name ?? ""})`}`;
|
||||
const ForwardRefMotionComponent = React.forwardRef(MotionDOMComponent);
|
||||
ForwardRefMotionComponent[motionComponentSymbol] = Component;
|
||||
return ForwardRefMotionComponent;
|
||||
}
|
||||
function useLayoutId({ layoutId }) {
|
||||
const layoutGroupId = React.useContext(LayoutGroupContext).id;
|
||||
return layoutGroupId && layoutId !== undefined
|
||||
? layoutGroupId + "-" + layoutId
|
||||
: layoutId;
|
||||
}
|
||||
function useStrictMode(configAndProps, preloadedFeatures) {
|
||||
const isStrict = React.useContext(LazyContext).strict;
|
||||
/**
|
||||
* If we're in development mode, check to make sure we're not rendering a motion component
|
||||
* as a child of LazyMotion, as this will break the file-size benefits of using it.
|
||||
*/
|
||||
if (process.env.NODE_ENV !== "production" &&
|
||||
preloadedFeatures &&
|
||||
isStrict) {
|
||||
const strictMessage = "You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.";
|
||||
configAndProps.ignoreStrict
|
||||
? motionUtils.warning(false, strictMessage, "lazy-strict-mode")
|
||||
: motionUtils.invariant(false, strictMessage, "lazy-strict-mode");
|
||||
}
|
||||
}
|
||||
function getProjectionFunctionality(props) {
|
||||
const featureDefinitions = getInitializedFeatureDefinitions();
|
||||
const { drag, layout } = featureDefinitions;
|
||||
if (!drag && !layout)
|
||||
return {};
|
||||
const combined = { ...drag, ...layout };
|
||||
return {
|
||||
MeasureLayout: drag?.isEnabled(props) || layout?.isEnabled(props)
|
||||
? combined.MeasureLayout
|
||||
: undefined,
|
||||
ProjectionNode: combined.ProjectionNode,
|
||||
};
|
||||
}
|
||||
|
||||
exports.LayoutGroupContext = LayoutGroupContext;
|
||||
exports.LazyContext = LazyContext;
|
||||
exports.MotionConfigContext = MotionConfigContext;
|
||||
exports.MotionContext = MotionContext;
|
||||
exports.PresenceContext = PresenceContext;
|
||||
exports.SwitchLayoutGroupContext = SwitchLayoutGroupContext;
|
||||
exports.createMotionComponent = createMotionComponent;
|
||||
exports.filterProps = filterProps;
|
||||
exports.isBrowser = isBrowser;
|
||||
exports.isRefObject = isRefObject;
|
||||
exports.isSVGComponent = isSVGComponent;
|
||||
exports.isValidMotionProp = isValidMotionProp;
|
||||
exports.loadExternalIsValidProp = loadExternalIsValidProp;
|
||||
exports.loadFeatures = loadFeatures;
|
||||
exports.makeUseVisualState = makeUseVisualState;
|
||||
exports.motionComponentSymbol = motionComponentSymbol;
|
||||
exports.useConstant = useConstant;
|
||||
exports.useIsomorphicLayoutEffect = useIsomorphicLayoutEffect;
|
||||
//# sourceMappingURL=index-6W16WHlG.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+3719
@@ -0,0 +1,3719 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var jsxRuntime = require('react/jsx-runtime');
|
||||
var React = require('react');
|
||||
var index = require('./index-6W16WHlG.js');
|
||||
var motionDom = require('motion-dom');
|
||||
var featureBundle = require('./feature-bundle-riC1cckV.js');
|
||||
var motionUtils = require('motion-utils');
|
||||
|
||||
function _interopNamespaceDefault(e) {
|
||||
var n = Object.create(null);
|
||||
if (e) {
|
||||
Object.keys(e).forEach(function (k) {
|
||||
if (k !== 'default') {
|
||||
var d = Object.getOwnPropertyDescriptor(e, k);
|
||||
Object.defineProperty(n, k, d.get ? d : {
|
||||
enumerable: true,
|
||||
get: function () { return e[k]; }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
n.default = e;
|
||||
return Object.freeze(n);
|
||||
}
|
||||
|
||||
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
|
||||
|
||||
/**
|
||||
* Taken from https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx
|
||||
*/
|
||||
/**
|
||||
* Set a given ref to a given value
|
||||
* This utility takes care of different types of refs: callback refs and RefObject(s)
|
||||
*/
|
||||
function setRef(ref, value) {
|
||||
if (typeof ref === "function") {
|
||||
return ref(value);
|
||||
}
|
||||
else if (ref !== null && ref !== undefined) {
|
||||
ref.current = value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A utility to compose multiple refs together
|
||||
* Accepts callback refs and RefObject(s)
|
||||
*/
|
||||
function composeRefs(...refs) {
|
||||
return (node) => {
|
||||
let hasCleanup = false;
|
||||
const cleanups = refs.map((ref) => {
|
||||
const cleanup = setRef(ref, node);
|
||||
if (!hasCleanup && typeof cleanup === "function") {
|
||||
hasCleanup = true;
|
||||
}
|
||||
return cleanup;
|
||||
});
|
||||
// React <19 will log an error to the console if a callback ref returns a
|
||||
// value. We don't use ref cleanups internally so this will only happen if a
|
||||
// user's ref callback returns a value, which we only expect if they are
|
||||
// using the cleanup functionality added in React 19.
|
||||
if (hasCleanup) {
|
||||
return () => {
|
||||
for (let i = 0; i < cleanups.length; i++) {
|
||||
const cleanup = cleanups[i];
|
||||
if (typeof cleanup === "function") {
|
||||
cleanup();
|
||||
}
|
||||
else {
|
||||
setRef(refs[i], null);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A custom hook that composes multiple refs
|
||||
* Accepts callback refs and RefObject(s)
|
||||
*/
|
||||
function useComposedRefs(...refs) {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
return React__namespace.useCallback(composeRefs(...refs), refs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Measurement functionality has to be within a separate component
|
||||
* to leverage snapshot lifecycle.
|
||||
*/
|
||||
class PopChildMeasure extends React__namespace.Component {
|
||||
getSnapshotBeforeUpdate(prevProps) {
|
||||
const element = this.props.childRef.current;
|
||||
if (motionDom.isHTMLElement(element) && prevProps.isPresent && !this.props.isPresent && this.props.pop !== false) {
|
||||
const parent = element.offsetParent;
|
||||
const parentWidth = motionDom.isHTMLElement(parent)
|
||||
? parent.offsetWidth || 0
|
||||
: 0;
|
||||
const parentHeight = motionDom.isHTMLElement(parent)
|
||||
? parent.offsetHeight || 0
|
||||
: 0;
|
||||
const computedStyle = getComputedStyle(element);
|
||||
const size = this.props.sizeRef.current;
|
||||
size.height = parseFloat(computedStyle.height);
|
||||
size.width = parseFloat(computedStyle.width);
|
||||
size.top = element.offsetTop;
|
||||
size.left = element.offsetLeft;
|
||||
size.right = parentWidth - size.width - size.left;
|
||||
size.bottom = parentHeight - size.height - size.top;
|
||||
size.direction = computedStyle.direction;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Required with getSnapshotBeforeUpdate to stop React complaining.
|
||||
*/
|
||||
componentDidUpdate() { }
|
||||
render() {
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
function PopChild({ children, isPresent, anchorX, anchorY, root, pop }) {
|
||||
const id = React.useId();
|
||||
const ref = React.useRef(null);
|
||||
const size = React.useRef({
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
direction: "ltr",
|
||||
});
|
||||
const { nonce } = React.useContext(index.MotionConfigContext);
|
||||
/**
|
||||
* In React 19, refs are passed via props.ref instead of element.ref.
|
||||
* We check props.ref first (React 19) and fall back to element.ref (React 18).
|
||||
*/
|
||||
const childRef = children.props?.ref ??
|
||||
children?.ref;
|
||||
const composedRef = useComposedRefs(ref, childRef);
|
||||
/**
|
||||
* We create and inject a style block so we can apply this explicit
|
||||
* sizing in a non-destructive manner by just deleting the style block.
|
||||
*
|
||||
* We can't apply size via render as the measurement happens
|
||||
* in getSnapshotBeforeUpdate (post-render), likewise if we apply the
|
||||
* styles directly on the DOM node, we might be overwriting
|
||||
* styles set via the style prop.
|
||||
*/
|
||||
React.useInsertionEffect(() => {
|
||||
const { width, height, top, left, right, bottom, direction } = size.current;
|
||||
if (isPresent || pop === false || !ref.current || !width || !height)
|
||||
return;
|
||||
const isRTL = direction === "rtl";
|
||||
const x = anchorX === "left"
|
||||
? (isRTL ? `right: ${right}` : `left: ${left}`)
|
||||
: (isRTL ? `left: ${left}` : `right: ${right}`);
|
||||
const y = anchorY === "bottom" ? `bottom: ${bottom}` : `top: ${top}`;
|
||||
ref.current.dataset.motionPopId = id;
|
||||
const style = document.createElement("style");
|
||||
if (nonce)
|
||||
style.nonce = nonce;
|
||||
const parent = root ?? document.head;
|
||||
parent.appendChild(style);
|
||||
if (style.sheet) {
|
||||
style.sheet.insertRule(`
|
||||
[data-motion-pop-id="${id}"] {
|
||||
position: absolute !important;
|
||||
width: ${width}px !important;
|
||||
height: ${height}px !important;
|
||||
${x}px !important;
|
||||
${y}px !important;
|
||||
}
|
||||
`);
|
||||
}
|
||||
return () => {
|
||||
ref.current?.removeAttribute("data-motion-pop-id");
|
||||
if (parent.contains(style)) {
|
||||
parent.removeChild(style);
|
||||
}
|
||||
};
|
||||
}, [isPresent]);
|
||||
return (jsxRuntime.jsx(PopChildMeasure, { isPresent: isPresent, childRef: ref, sizeRef: size, pop: pop, children: pop === false
|
||||
? children
|
||||
: React__namespace.cloneElement(children, { ref: composedRef }) }));
|
||||
}
|
||||
|
||||
const PresenceChild = ({ children, initial, isPresent, onExitComplete, custom, presenceAffectsLayout, mode, anchorX, anchorY, root }) => {
|
||||
const presenceChildren = index.useConstant(newChildrenMap);
|
||||
const id = React.useId();
|
||||
// Written in a layout effect (not render) so discarded concurrent
|
||||
// renders can't leave the refs pointing at uncommitted state.
|
||||
const isPresentRef = React.useRef(isPresent);
|
||||
const onExitCompleteRef = React.useRef(onExitComplete);
|
||||
index.useIsomorphicLayoutEffect(() => {
|
||||
isPresentRef.current = isPresent;
|
||||
onExitCompleteRef.current = onExitComplete;
|
||||
});
|
||||
let isReusedContext = true;
|
||||
let context = React.useMemo(() => {
|
||||
isReusedContext = false;
|
||||
return {
|
||||
id,
|
||||
initial,
|
||||
isPresent,
|
||||
custom,
|
||||
onExitComplete: (childId) => {
|
||||
presenceChildren.set(childId, true);
|
||||
for (const isComplete of presenceChildren.values()) {
|
||||
if (!isComplete)
|
||||
return; // can stop searching when any is incomplete
|
||||
}
|
||||
onExitComplete && onExitComplete();
|
||||
},
|
||||
register: (childId) => {
|
||||
presenceChildren.set(childId, false);
|
||||
return () => {
|
||||
presenceChildren.delete(childId);
|
||||
!isPresentRef.current &&
|
||||
!presenceChildren.size &&
|
||||
onExitCompleteRef.current?.();
|
||||
};
|
||||
},
|
||||
};
|
||||
}, [isPresent, presenceChildren, onExitComplete]);
|
||||
/**
|
||||
* If the presence of a child affects the layout of the components around it,
|
||||
* we want to make a new context value to ensure they get re-rendered
|
||||
* so they can detect that layout change.
|
||||
*/
|
||||
if (presenceAffectsLayout && isReusedContext) {
|
||||
context = { ...context };
|
||||
}
|
||||
React.useMemo(() => {
|
||||
presenceChildren.forEach((_, key) => presenceChildren.set(key, false));
|
||||
}, [isPresent]);
|
||||
/**
|
||||
* If there's no `motion` components to fire exit animations, we want to remove this
|
||||
* component immediately.
|
||||
*/
|
||||
React__namespace.useEffect(() => {
|
||||
!isPresent &&
|
||||
!presenceChildren.size &&
|
||||
onExitComplete &&
|
||||
onExitComplete();
|
||||
}, [isPresent]);
|
||||
children = (jsxRuntime.jsx(PopChild, { pop: mode === "popLayout", isPresent: isPresent, anchorX: anchorX, anchorY: anchorY, root: root, children: children }));
|
||||
return (jsxRuntime.jsx(index.PresenceContext.Provider, { value: context, children: children }));
|
||||
};
|
||||
function newChildrenMap() {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
const getChildKey = (child) => child.key || "";
|
||||
function onlyElements(children) {
|
||||
const filtered = [];
|
||||
// We use forEach here instead of map as map mutates the component key by preprending `.$`
|
||||
React.Children.forEach(children, (child) => {
|
||||
if (React.isValidElement(child))
|
||||
filtered.push(child);
|
||||
});
|
||||
return filtered;
|
||||
}
|
||||
|
||||
/**
|
||||
* `AnimatePresence` enables the animation of components that have been removed from the tree.
|
||||
*
|
||||
* When adding/removing more than a single child, every child **must** be given a unique `key` prop.
|
||||
*
|
||||
* Any `motion` components that have an `exit` property defined will animate out when removed from
|
||||
* the tree.
|
||||
*
|
||||
* ```jsx
|
||||
* import { motion, AnimatePresence } from 'framer-motion'
|
||||
*
|
||||
* export const Items = ({ items }) => (
|
||||
* <AnimatePresence>
|
||||
* {items.map(item => (
|
||||
* <motion.div
|
||||
* key={item.id}
|
||||
* initial={{ opacity: 0 }}
|
||||
* animate={{ opacity: 1 }}
|
||||
* exit={{ opacity: 0 }}
|
||||
* />
|
||||
* ))}
|
||||
* </AnimatePresence>
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* You can sequence exit animations throughout a tree using variants.
|
||||
*
|
||||
* If a child contains multiple `motion` components with `exit` props, it will only unmount the child
|
||||
* once all `motion` components have finished animating out. Likewise, any components using
|
||||
* `usePresence` all need to call `safeToRemove`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
const AnimatePresence = ({ children, custom, initial = true, onExitComplete, presenceAffectsLayout = true, mode = "sync", propagate = false, anchorX = "left", anchorY = "top", root }) => {
|
||||
const [isParentPresent, safeToRemove] = featureBundle.usePresence(propagate);
|
||||
/**
|
||||
* Filter any children that aren't ReactElements. We can only track components
|
||||
* between renders with a props.key.
|
||||
*/
|
||||
const presentChildren = React.useMemo(() => onlyElements(children), [children]);
|
||||
/**
|
||||
* Track the keys of the currently rendered children. This is used to
|
||||
* determine which children are exiting.
|
||||
*/
|
||||
const presentKeys = propagate && !isParentPresent ? [] : presentChildren.map(getChildKey);
|
||||
/**
|
||||
* If `initial={false}` we only want to pass this to components in the first render.
|
||||
*/
|
||||
const isInitialRender = React.useRef(true);
|
||||
/**
|
||||
* A ref containing the currently present children. When all exit animations
|
||||
* are complete, we use this to re-render the component with the latest children
|
||||
* *committed* rather than the latest children *rendered*.
|
||||
*/
|
||||
const pendingPresentChildren = React.useRef(presentChildren);
|
||||
/**
|
||||
* Track which exiting children have finished animating out.
|
||||
*/
|
||||
const exitComplete = index.useConstant(() => new Map());
|
||||
/**
|
||||
* Track which components are currently processing exit to prevent duplicate processing.
|
||||
*/
|
||||
const exitingComponents = React.useRef(new Set());
|
||||
/**
|
||||
* Save children to render as React state. To ensure this component is concurrent-safe,
|
||||
* we check for exiting children via an effect.
|
||||
*/
|
||||
const [diffedChildren, setDiffedChildren] = React.useState(presentChildren);
|
||||
const [renderedChildren, setRenderedChildren] = React.useState(presentChildren);
|
||||
index.useIsomorphicLayoutEffect(() => {
|
||||
isInitialRender.current = false;
|
||||
pendingPresentChildren.current = presentChildren;
|
||||
/**
|
||||
* Update complete status of exiting children.
|
||||
*/
|
||||
for (let i = 0; i < renderedChildren.length; i++) {
|
||||
const key = getChildKey(renderedChildren[i]);
|
||||
if (!presentKeys.includes(key)) {
|
||||
if (exitComplete.get(key) !== true) {
|
||||
exitComplete.set(key, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
exitComplete.delete(key);
|
||||
exitingComponents.current.delete(key);
|
||||
}
|
||||
}
|
||||
}, [renderedChildren, presentKeys.length, presentKeys.join("-")]);
|
||||
const exitingChildren = [];
|
||||
if (presentChildren !== diffedChildren) {
|
||||
let nextChildren = [...presentChildren];
|
||||
/**
|
||||
* Loop through all the currently rendered components and decide which
|
||||
* are exiting.
|
||||
*/
|
||||
for (let i = 0; i < renderedChildren.length; i++) {
|
||||
const child = renderedChildren[i];
|
||||
const key = getChildKey(child);
|
||||
if (!presentKeys.includes(key)) {
|
||||
nextChildren.splice(i, 0, child);
|
||||
exitingChildren.push(child);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If we're in "wait" mode, and we have exiting children, we want to
|
||||
* only render these until they've all exited.
|
||||
*/
|
||||
if (mode === "wait" && exitingChildren.length) {
|
||||
nextChildren = exitingChildren;
|
||||
}
|
||||
setRenderedChildren(onlyElements(nextChildren));
|
||||
setDiffedChildren(presentChildren);
|
||||
/**
|
||||
* Early return to ensure once we've set state with the latest diffed
|
||||
* children, we can immediately re-render.
|
||||
*/
|
||||
return null;
|
||||
}
|
||||
if (process.env.NODE_ENV !== "production" &&
|
||||
mode === "wait" &&
|
||||
renderedChildren.length > 1) {
|
||||
console.warn(`You're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.`);
|
||||
}
|
||||
/**
|
||||
* If we've been provided a forceRender function by the LayoutGroupContext,
|
||||
* we can use it to force a re-render amongst all surrounding components once
|
||||
* all components have finished animating out.
|
||||
*/
|
||||
const { forceRender } = React.useContext(index.LayoutGroupContext);
|
||||
return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: renderedChildren.map((child) => {
|
||||
const key = getChildKey(child);
|
||||
const isPresent = propagate && !isParentPresent
|
||||
? false
|
||||
: presentChildren === renderedChildren ||
|
||||
presentKeys.includes(key);
|
||||
const onExit = () => {
|
||||
if (exitingComponents.current.has(key)) {
|
||||
return;
|
||||
}
|
||||
if (exitComplete.has(key)) {
|
||||
exitingComponents.current.add(key);
|
||||
exitComplete.set(key, true);
|
||||
}
|
||||
else {
|
||||
return;
|
||||
}
|
||||
let isEveryExitComplete = true;
|
||||
exitComplete.forEach((isExitComplete) => {
|
||||
if (!isExitComplete)
|
||||
isEveryExitComplete = false;
|
||||
});
|
||||
if (isEveryExitComplete) {
|
||||
forceRender?.();
|
||||
setRenderedChildren(pendingPresentChildren.current);
|
||||
propagate && safeToRemove?.();
|
||||
onExitComplete && onExitComplete();
|
||||
}
|
||||
};
|
||||
return (jsxRuntime.jsx(PresenceChild, { isPresent: isPresent, initial: !isInitialRender.current || initial
|
||||
? undefined
|
||||
: false, custom: custom, presenceAffectsLayout: presenceAffectsLayout, mode: mode, root: root, onExitComplete: isPresent ? undefined : onExit, anchorX: anchorX, anchorY: anchorY, children: child }, key));
|
||||
}) }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Note: Still used by components generated by old versions of Framer
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
const DeprecatedLayoutGroupContext = React.createContext(null);
|
||||
|
||||
function useIsMounted() {
|
||||
const isMounted = React.useRef(false);
|
||||
index.useIsomorphicLayoutEffect(() => {
|
||||
isMounted.current = true;
|
||||
return () => {
|
||||
isMounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
return isMounted;
|
||||
}
|
||||
|
||||
function useForceUpdate() {
|
||||
const isMounted = useIsMounted();
|
||||
const [forcedRenderCount, setForcedRenderCount] = React.useState(0);
|
||||
const forceRender = React.useCallback(() => {
|
||||
isMounted.current && setForcedRenderCount(forcedRenderCount + 1);
|
||||
}, [forcedRenderCount]);
|
||||
/**
|
||||
* Defer this to the end of the next animation frame in case there are multiple
|
||||
* synchronous calls.
|
||||
*/
|
||||
const deferredForceRender = React.useCallback(() => motionDom.frame.postRender(forceRender), [forceRender]);
|
||||
return [deferredForceRender, forcedRenderCount];
|
||||
}
|
||||
|
||||
const shouldInheritGroup = (inherit) => inherit === true;
|
||||
const shouldInheritId = (inherit) => shouldInheritGroup(inherit === true) || inherit === "id";
|
||||
const LayoutGroup = ({ children, id, inherit = true }) => {
|
||||
const layoutGroupContext = React.useContext(index.LayoutGroupContext);
|
||||
const deprecatedLayoutGroupContext = React.useContext(DeprecatedLayoutGroupContext);
|
||||
const [forceRender, key] = useForceUpdate();
|
||||
const context = React.useRef(null);
|
||||
const upstreamId = layoutGroupContext.id || deprecatedLayoutGroupContext;
|
||||
if (context.current === null) {
|
||||
if (shouldInheritId(inherit) && upstreamId) {
|
||||
id = id ? upstreamId + "-" + id : upstreamId;
|
||||
}
|
||||
context.current = {
|
||||
id,
|
||||
group: shouldInheritGroup(inherit)
|
||||
? layoutGroupContext.group || motionDom.nodeGroup()
|
||||
: motionDom.nodeGroup(),
|
||||
};
|
||||
}
|
||||
const memoizedContext = React.useMemo(() => ({ ...context.current, forceRender }), [key]);
|
||||
return (jsxRuntime.jsx(index.LayoutGroupContext.Provider, { value: memoizedContext, children: children }));
|
||||
};
|
||||
|
||||
/**
|
||||
* Used in conjunction with the `m` component to reduce bundle size.
|
||||
*
|
||||
* `m` is a version of the `motion` component that only loads functionality
|
||||
* critical for the initial render.
|
||||
*
|
||||
* `LazyMotion` can then be used to either synchronously or asynchronously
|
||||
* load animation and gesture support.
|
||||
*
|
||||
* ```jsx
|
||||
* // Synchronous loading
|
||||
* import { LazyMotion, m, domAnimation } from "framer-motion"
|
||||
*
|
||||
* function App() {
|
||||
* return (
|
||||
* <LazyMotion features={domAnimation}>
|
||||
* <m.div animate={{ scale: 2 }} />
|
||||
* </LazyMotion>
|
||||
* )
|
||||
* }
|
||||
*
|
||||
* // Asynchronous loading
|
||||
* import { LazyMotion, m } from "framer-motion"
|
||||
*
|
||||
* function App() {
|
||||
* return (
|
||||
* <LazyMotion features={() => import('./path/to/domAnimation')}>
|
||||
* <m.div animate={{ scale: 2 }} />
|
||||
* </LazyMotion>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function LazyMotion({ children, features, strict = false }) {
|
||||
const [, setIsLoaded] = React.useState(!isLazyBundle(features));
|
||||
const loadedRenderer = React.useRef(undefined);
|
||||
/**
|
||||
* If this is a synchronous load, load features immediately
|
||||
*/
|
||||
if (!isLazyBundle(features)) {
|
||||
const { renderer, ...loadedFeatures } = features;
|
||||
loadedRenderer.current = renderer;
|
||||
index.loadFeatures(loadedFeatures);
|
||||
}
|
||||
React.useEffect(() => {
|
||||
if (isLazyBundle(features)) {
|
||||
features().then(({ renderer, ...loadedFeatures }) => {
|
||||
index.loadFeatures(loadedFeatures);
|
||||
loadedRenderer.current = renderer;
|
||||
setIsLoaded(true);
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
return (jsxRuntime.jsx(index.LazyContext.Provider, { value: { renderer: loadedRenderer.current, strict }, children: children }));
|
||||
}
|
||||
function isLazyBundle(features) {
|
||||
return typeof features === "function";
|
||||
}
|
||||
|
||||
/**
|
||||
* `MotionConfig` is used to set configuration options for all children `motion` components.
|
||||
*
|
||||
* ```jsx
|
||||
* import { motion, MotionConfig } from "framer-motion"
|
||||
*
|
||||
* export function App() {
|
||||
* return (
|
||||
* <MotionConfig transition={{ type: "spring" }}>
|
||||
* <motion.div animate={{ x: 100 }} />
|
||||
* </MotionConfig>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function MotionConfig({ children, isValidProp, ...config }) {
|
||||
isValidProp && index.loadExternalIsValidProp(isValidProp);
|
||||
/**
|
||||
* Inherit props from any parent MotionConfig components
|
||||
*/
|
||||
const parentConfig = React.useContext(index.MotionConfigContext);
|
||||
config = { ...parentConfig, ...config };
|
||||
config.transition = motionDom.resolveTransition(config.transition, parentConfig.transition);
|
||||
/**
|
||||
* Don't allow isStatic to change between renders as it affects how many hooks
|
||||
* motion components fire.
|
||||
*/
|
||||
config.isStatic = index.useConstant(() => config.isStatic);
|
||||
/**
|
||||
* Creating a new config context object will re-render every `motion` component
|
||||
* every time it renders. So we only want to create a new one sparingly.
|
||||
*/
|
||||
const context = React.useMemo(() => config, [
|
||||
JSON.stringify(config.transition),
|
||||
config.transformPagePoint,
|
||||
config.reducedMotion,
|
||||
config.skipAnimations,
|
||||
]);
|
||||
return (jsxRuntime.jsx(index.MotionConfigContext.Provider, { value: context, children: children }));
|
||||
}
|
||||
|
||||
const ReorderContext = React.createContext(null);
|
||||
|
||||
function createMotionProxy(preloadedFeatures, createVisualElement) {
|
||||
if (typeof Proxy === "undefined") {
|
||||
return index.createMotionComponent;
|
||||
}
|
||||
/**
|
||||
* A cache of generated `motion` components, e.g `motion.div`, `motion.input` etc.
|
||||
* Rather than generating them anew every render.
|
||||
*/
|
||||
const componentCache = new Map();
|
||||
const factory = (Component, options) => {
|
||||
return index.createMotionComponent(Component, options, preloadedFeatures, createVisualElement);
|
||||
};
|
||||
/**
|
||||
* Support for deprecated`motion(Component)` pattern
|
||||
*/
|
||||
const deprecatedFactoryFunction = (Component, options) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
motionUtils.warnOnce(false, "motion() is deprecated. Use motion.create() instead.");
|
||||
}
|
||||
return factory(Component, options);
|
||||
};
|
||||
return new Proxy(deprecatedFactoryFunction, {
|
||||
/**
|
||||
* Called when `motion` is referenced with a prop: `motion.div`, `motion.input` etc.
|
||||
* The prop name is passed through as `key` and we can use that to generate a `motion`
|
||||
* DOM component with that name.
|
||||
*/
|
||||
get: (_target, key) => {
|
||||
if (key === "create")
|
||||
return factory;
|
||||
/**
|
||||
* If this element doesn't exist in the component cache, create it and cache.
|
||||
*/
|
||||
if (!componentCache.has(key)) {
|
||||
componentCache.set(key, index.createMotionComponent(key, undefined, preloadedFeatures, createVisualElement));
|
||||
}
|
||||
return componentCache.get(key);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const motion = /*@__PURE__*/ createMotionProxy(featureBundle.featureBundle, featureBundle.createDomVisualElement);
|
||||
|
||||
function checkReorder(order, value, offset, velocity) {
|
||||
if (!velocity)
|
||||
return order;
|
||||
const index = order.findIndex((item) => item.value === value);
|
||||
if (index === -1)
|
||||
return order;
|
||||
const nextOffset = velocity > 0 ? 1 : -1;
|
||||
const nextItem = order[index + nextOffset];
|
||||
if (!nextItem)
|
||||
return order;
|
||||
const item = order[index];
|
||||
const nextLayout = nextItem.layout;
|
||||
const nextItemCenter = motionDom.mixNumber(nextLayout.min, nextLayout.max, 0.5);
|
||||
if ((nextOffset === 1 && item.layout.max + offset > nextItemCenter) ||
|
||||
(nextOffset === -1 && item.layout.min + offset < nextItemCenter)) {
|
||||
return motionUtils.moveItem(order, index, index + nextOffset);
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
function ReorderGroupComponent({ children, as = "ul", axis = "y", onReorder, values, ...props }, externalRef) {
|
||||
const Component = index.useConstant(() => motion[as]);
|
||||
const order = [];
|
||||
const isReordering = React.useRef(false);
|
||||
const groupRef = React.useRef(null);
|
||||
motionUtils.invariant(Boolean(values), "Reorder.Group must be provided a values prop", "reorder-values");
|
||||
const context = {
|
||||
axis,
|
||||
groupRef,
|
||||
registerItem: (value, layout) => {
|
||||
// If the entry was already added, update it rather than adding it again
|
||||
const idx = order.findIndex((entry) => value === entry.value);
|
||||
if (idx !== -1) {
|
||||
order[idx].layout = layout[axis];
|
||||
}
|
||||
else {
|
||||
order.push({ value: value, layout: layout[axis] });
|
||||
}
|
||||
order.sort(compareMin);
|
||||
},
|
||||
updateOrder: (item, offset, velocity) => {
|
||||
if (isReordering.current)
|
||||
return;
|
||||
const newOrder = checkReorder(order, item, offset, velocity);
|
||||
if (order !== newOrder) {
|
||||
isReordering.current = true;
|
||||
// Find which two values swapped and apply that swap
|
||||
// to the full values array. This preserves unmeasured
|
||||
// items (e.g. in virtualized lists).
|
||||
const newValues = [...values];
|
||||
for (let i = 0; i < newOrder.length; i++) {
|
||||
if (order[i].value !== newOrder[i].value) {
|
||||
const a = values.indexOf(order[i].value);
|
||||
const b = values.indexOf(newOrder[i].value);
|
||||
if (a !== -1 && b !== -1) {
|
||||
[newValues[a], newValues[b]] = [newValues[b], newValues[a]];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
onReorder(newValues);
|
||||
}
|
||||
},
|
||||
};
|
||||
React.useEffect(() => {
|
||||
isReordering.current = false;
|
||||
});
|
||||
// Combine refs if external ref is provided
|
||||
const setRef = (element) => {
|
||||
groupRef.current = element;
|
||||
if (typeof externalRef === "function") {
|
||||
externalRef(element);
|
||||
}
|
||||
else if (externalRef) {
|
||||
externalRef.current = element;
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Disable browser scroll anchoring on the group container.
|
||||
* When items reorder, scroll anchoring can cause the browser to adjust
|
||||
* the scroll position, which interferes with drag position calculations.
|
||||
*/
|
||||
const groupStyle = {
|
||||
overflowAnchor: "none",
|
||||
...props.style,
|
||||
};
|
||||
return (jsxRuntime.jsx(Component, { ...props, style: groupStyle, ref: setRef, ignoreStrict: true, children: jsxRuntime.jsx(ReorderContext.Provider, { value: context, children: children }) }));
|
||||
}
|
||||
const ReorderGroup = /*@__PURE__*/ React.forwardRef(ReorderGroupComponent);
|
||||
function compareMin(a, b) {
|
||||
return a.layout.min - b.layout.min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `MotionValue` to track the state and velocity of a value.
|
||||
*
|
||||
* Usually, these are created automatically. For advanced use-cases, like use with `useTransform`, you can create `MotionValue`s externally and pass them into the animated component via the `style` prop.
|
||||
*
|
||||
* ```jsx
|
||||
* export const MyComponent = () => {
|
||||
* const scale = useMotionValue(1)
|
||||
*
|
||||
* return <motion.div style={{ scale }} />
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param initial - The initial state.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useMotionValue(initial) {
|
||||
const value = index.useConstant(() => motionDom.motionValue(initial));
|
||||
/**
|
||||
* If this motion value is being used in static mode, like on
|
||||
* the Framer canvas, force components to rerender when the motion
|
||||
* value is updated.
|
||||
*/
|
||||
const { isStatic } = React.useContext(index.MotionConfigContext);
|
||||
if (isStatic) {
|
||||
const [, setLatest] = React.useState(initial);
|
||||
React.useEffect(() => value.on("change", setLatest), []);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function useCombineMotionValues(values, combineValues) {
|
||||
/**
|
||||
* Initialise the returned motion value. This remains the same between renders.
|
||||
*/
|
||||
const value = useMotionValue(combineValues());
|
||||
/**
|
||||
* Create a function that will update the template motion value with the latest values.
|
||||
* This is pre-bound so whenever a motion value updates it can schedule its
|
||||
* execution in Framesync. If it's already been scheduled it won't be fired twice
|
||||
* in a single frame.
|
||||
*/
|
||||
const updateValue = () => value.set(combineValues());
|
||||
/**
|
||||
* Synchronously update the motion value with the latest values during the render.
|
||||
* This ensures that within a React render, the styles applied to the DOM are up-to-date.
|
||||
*/
|
||||
updateValue();
|
||||
/**
|
||||
* Subscribe to all motion values found within the template. Whenever any of them change,
|
||||
* schedule an update.
|
||||
*/
|
||||
index.useIsomorphicLayoutEffect(() => {
|
||||
const scheduleUpdate = () => motionDom.frame.preRender(updateValue, false, true);
|
||||
const subscriptions = values.map((v) => v.on("change", scheduleUpdate));
|
||||
return () => {
|
||||
subscriptions.forEach((unsubscribe) => unsubscribe());
|
||||
motionDom.cancelFrame(updateValue);
|
||||
};
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
function useComputed(compute) {
|
||||
/**
|
||||
* Open session of collectMotionValues. Any MotionValue that calls get()
|
||||
* will be saved into this array.
|
||||
*/
|
||||
motionDom.collectMotionValues.current = [];
|
||||
compute();
|
||||
const value = useCombineMotionValues(motionDom.collectMotionValues.current, compute);
|
||||
/**
|
||||
* Synchronously close session of collectMotionValues.
|
||||
*/
|
||||
motionDom.collectMotionValues.current = undefined;
|
||||
return value;
|
||||
}
|
||||
|
||||
function useTransform(input, inputRangeOrTransformer, outputRangeOrMap, options) {
|
||||
if (typeof input === "function") {
|
||||
return useComputed(input);
|
||||
}
|
||||
/**
|
||||
* Detect if outputRangeOrMap is an output map (object with keys)
|
||||
* rather than an output range (array).
|
||||
*/
|
||||
const isOutputMap = outputRangeOrMap !== undefined &&
|
||||
!Array.isArray(outputRangeOrMap) &&
|
||||
typeof inputRangeOrTransformer !== "function";
|
||||
if (isOutputMap) {
|
||||
return useMapTransform(input, inputRangeOrTransformer, outputRangeOrMap, options);
|
||||
}
|
||||
const outputRange = outputRangeOrMap;
|
||||
const transformer = typeof inputRangeOrTransformer === "function"
|
||||
? inputRangeOrTransformer
|
||||
: motionDom.transform(inputRangeOrTransformer, outputRange, options);
|
||||
const result = Array.isArray(input)
|
||||
? useListTransform(input, transformer)
|
||||
: useListTransform([input], ([latest]) => transformer(latest));
|
||||
const inputAccelerate = !Array.isArray(input)
|
||||
? input.accelerate
|
||||
: undefined;
|
||||
if (inputAccelerate &&
|
||||
!inputAccelerate.isTransformed &&
|
||||
typeof inputRangeOrTransformer !== "function" &&
|
||||
Array.isArray(outputRangeOrMap) &&
|
||||
options?.clamp !== false) {
|
||||
result.accelerate = {
|
||||
...inputAccelerate,
|
||||
times: inputRangeOrTransformer,
|
||||
keyframes: outputRangeOrMap,
|
||||
isTransformed: true,
|
||||
...(options?.ease ? { ease: options.ease } : {}),
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
function useListTransform(values, transformer) {
|
||||
const latest = index.useConstant(() => []);
|
||||
return useCombineMotionValues(values, () => {
|
||||
latest.length = 0;
|
||||
const numValues = values.length;
|
||||
for (let i = 0; i < numValues; i++) {
|
||||
latest[i] = values[i].get();
|
||||
}
|
||||
return transformer(latest);
|
||||
});
|
||||
}
|
||||
function useMapTransform(inputValue, inputRange, outputMap, options) {
|
||||
/**
|
||||
* Capture keys once to ensure hooks are called in consistent order.
|
||||
*/
|
||||
const keys = index.useConstant(() => Object.keys(outputMap));
|
||||
const output = index.useConstant(() => ({}));
|
||||
for (const key of keys) {
|
||||
output[key] = useTransform(inputValue, inputRange, outputMap[key], options);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
const threshold = 50;
|
||||
const maxSpeed = 25;
|
||||
const overflowStyles = new Set(["auto", "scroll"]);
|
||||
// Track initial scroll limits per scrollable element (Bug 1 fix)
|
||||
const initialScrollLimits = new WeakMap();
|
||||
const activeScrollEdge = new WeakMap();
|
||||
// Track which group element is currently dragging to clear state on end
|
||||
let currentGroupElement = null;
|
||||
function resetAutoScrollState() {
|
||||
if (currentGroupElement) {
|
||||
const scrollableAncestor = findScrollableAncestor(currentGroupElement, "y");
|
||||
if (scrollableAncestor) {
|
||||
activeScrollEdge.delete(scrollableAncestor);
|
||||
initialScrollLimits.delete(scrollableAncestor);
|
||||
}
|
||||
// Also try x axis
|
||||
const scrollableAncestorX = findScrollableAncestor(currentGroupElement, "x");
|
||||
if (scrollableAncestorX && scrollableAncestorX !== scrollableAncestor) {
|
||||
activeScrollEdge.delete(scrollableAncestorX);
|
||||
initialScrollLimits.delete(scrollableAncestorX);
|
||||
}
|
||||
currentGroupElement = null;
|
||||
}
|
||||
}
|
||||
function isScrollableElement(element, axis) {
|
||||
const style = getComputedStyle(element);
|
||||
const overflow = axis === "x" ? style.overflowX : style.overflowY;
|
||||
const isDocumentScroll = element === document.body ||
|
||||
element === document.documentElement;
|
||||
return overflowStyles.has(overflow) || isDocumentScroll;
|
||||
}
|
||||
function findScrollableAncestor(element, axis) {
|
||||
let current = element?.parentElement;
|
||||
while (current) {
|
||||
if (isScrollableElement(current, axis)) {
|
||||
return current;
|
||||
}
|
||||
current = current.parentElement;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function getScrollAmount(pointerPosition, scrollElement, axis) {
|
||||
const rect = scrollElement.getBoundingClientRect();
|
||||
const start = axis === "x" ? Math.max(0, rect.left) : Math.max(0, rect.top);
|
||||
const end = axis === "x" ? Math.min(window.innerWidth, rect.right) : Math.min(window.innerHeight, rect.bottom);
|
||||
const distanceFromStart = pointerPosition - start;
|
||||
const distanceFromEnd = end - pointerPosition;
|
||||
if (distanceFromStart < threshold) {
|
||||
const intensity = 1 - distanceFromStart / threshold;
|
||||
return { amount: -maxSpeed * intensity * intensity, edge: "start" };
|
||||
}
|
||||
else if (distanceFromEnd < threshold) {
|
||||
const intensity = 1 - distanceFromEnd / threshold;
|
||||
return { amount: maxSpeed * intensity * intensity, edge: "end" };
|
||||
}
|
||||
return { amount: 0, edge: null };
|
||||
}
|
||||
function autoScrollIfNeeded(groupElement, pointerPosition, axis, velocity) {
|
||||
if (!groupElement)
|
||||
return;
|
||||
// Track the group element for cleanup
|
||||
currentGroupElement = groupElement;
|
||||
const scrollableAncestor = findScrollableAncestor(groupElement, axis);
|
||||
if (!scrollableAncestor)
|
||||
return;
|
||||
// Convert pointer position from page coordinates to viewport coordinates.
|
||||
// The gesture system uses pageX/pageY but getBoundingClientRect() returns
|
||||
// viewport-relative coordinates, so we need to account for page scroll.
|
||||
const viewportPointerPosition = pointerPosition - (axis === "x" ? window.scrollX : window.scrollY);
|
||||
const { amount: scrollAmount, edge } = getScrollAmount(viewportPointerPosition, scrollableAncestor, axis);
|
||||
// If not in any threshold zone, clear all state
|
||||
if (edge === null) {
|
||||
activeScrollEdge.delete(scrollableAncestor);
|
||||
initialScrollLimits.delete(scrollableAncestor);
|
||||
return;
|
||||
}
|
||||
const currentActiveEdge = activeScrollEdge.get(scrollableAncestor);
|
||||
const isDocumentScroll = scrollableAncestor === document.body ||
|
||||
scrollableAncestor === document.documentElement;
|
||||
// If not currently scrolling this edge, check velocity to see if we should start
|
||||
if (currentActiveEdge !== edge) {
|
||||
// Only start scrolling if velocity is towards the edge
|
||||
const shouldStart = (edge === "start" && velocity < 0) ||
|
||||
(edge === "end" && velocity > 0);
|
||||
if (!shouldStart)
|
||||
return;
|
||||
// Activate this edge
|
||||
activeScrollEdge.set(scrollableAncestor, edge);
|
||||
// Record initial scroll limit (prevents infinite scroll)
|
||||
const maxScroll = axis === "x"
|
||||
? scrollableAncestor.scrollWidth - (isDocumentScroll ? window.innerWidth : scrollableAncestor.clientWidth)
|
||||
: scrollableAncestor.scrollHeight - (isDocumentScroll ? window.innerHeight : scrollableAncestor.clientHeight);
|
||||
initialScrollLimits.set(scrollableAncestor, maxScroll);
|
||||
}
|
||||
// Cap scrolling at initial limit (prevents infinite scroll)
|
||||
if (scrollAmount > 0) {
|
||||
const initialLimit = initialScrollLimits.get(scrollableAncestor);
|
||||
const currentScroll = axis === "x"
|
||||
? (isDocumentScroll ? window.scrollX : scrollableAncestor.scrollLeft)
|
||||
: (isDocumentScroll ? window.scrollY : scrollableAncestor.scrollTop);
|
||||
if (currentScroll >= initialLimit)
|
||||
return;
|
||||
}
|
||||
// Apply scroll
|
||||
if (axis === "x") {
|
||||
if (isDocumentScroll) {
|
||||
window.scrollBy({ left: scrollAmount });
|
||||
}
|
||||
else {
|
||||
scrollableAncestor.scrollLeft += scrollAmount;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (isDocumentScroll) {
|
||||
window.scrollBy({ top: scrollAmount });
|
||||
}
|
||||
else {
|
||||
scrollableAncestor.scrollTop += scrollAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useDefaultMotionValue(value, defaultValue = 0) {
|
||||
return motionDom.isMotionValue(value) ? value : useMotionValue(defaultValue);
|
||||
}
|
||||
function ReorderItemComponent({ children, style = {}, value, as = "li", onDrag, onDragEnd, layout = true, ...props }, externalRef) {
|
||||
const Component = index.useConstant(() => motion[as]);
|
||||
const context = React.useContext(ReorderContext);
|
||||
const point = {
|
||||
x: useDefaultMotionValue(style.x),
|
||||
y: useDefaultMotionValue(style.y),
|
||||
};
|
||||
const zIndex = useTransform([point.x, point.y], ([latestX, latestY]) => latestX || latestY ? 1 : "unset");
|
||||
motionUtils.invariant(Boolean(context), "Reorder.Item must be a child of Reorder.Group", "reorder-item-child");
|
||||
const { axis, registerItem, updateOrder, groupRef } = context;
|
||||
return (jsxRuntime.jsx(Component, { drag: axis, ...props, dragSnapToOrigin: true, style: { ...style, x: point.x, y: point.y, zIndex }, layout: layout, onDrag: (event, gesturePoint) => {
|
||||
const { velocity, point: pointerPoint } = gesturePoint;
|
||||
const offset = point[axis].get();
|
||||
// Always attempt to update order - checkReorder handles the logic
|
||||
updateOrder(value, offset, velocity[axis]);
|
||||
autoScrollIfNeeded(groupRef.current, pointerPoint[axis], axis, velocity[axis]);
|
||||
onDrag && onDrag(event, gesturePoint);
|
||||
}, onDragEnd: (event, gesturePoint) => {
|
||||
resetAutoScrollState();
|
||||
onDragEnd && onDragEnd(event, gesturePoint);
|
||||
}, onLayoutMeasure: (measured) => {
|
||||
registerItem(value, measured);
|
||||
}, ref: externalRef, ignoreStrict: true, children: children }));
|
||||
}
|
||||
const ReorderItem = /*@__PURE__*/ React.forwardRef(ReorderItemComponent);
|
||||
|
||||
var namespace = /*#__PURE__*/Object.freeze({
|
||||
__proto__: null,
|
||||
Group: ReorderGroup,
|
||||
Item: ReorderItem
|
||||
});
|
||||
|
||||
function isDOMKeyframes(keyframes) {
|
||||
return typeof keyframes === "object" && !Array.isArray(keyframes);
|
||||
}
|
||||
|
||||
function resolveSubjects(subject, keyframes, scope, selectorCache) {
|
||||
if (subject == null) {
|
||||
return [];
|
||||
}
|
||||
if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
|
||||
return motionDom.resolveElements(subject, scope, selectorCache);
|
||||
}
|
||||
else if (subject instanceof NodeList) {
|
||||
return Array.from(subject);
|
||||
}
|
||||
else if (Array.isArray(subject)) {
|
||||
return subject.filter((s) => s != null);
|
||||
}
|
||||
else {
|
||||
return [subject];
|
||||
}
|
||||
}
|
||||
|
||||
function calculateRepeatDuration(duration, repeat, repeatDelay) {
|
||||
return duration * (repeat + 1) + repeatDelay * repeat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a absolute or relative time definition and current/prev time state of the sequence,
|
||||
* calculate an absolute time for the next keyframes.
|
||||
*/
|
||||
function calcNextTime(current, next, prev, labels) {
|
||||
if (typeof next === "number") {
|
||||
return next;
|
||||
}
|
||||
else if (next.startsWith("-") || next.startsWith("+")) {
|
||||
return Math.max(0, current + parseFloat(next));
|
||||
}
|
||||
else if (next === "<") {
|
||||
return prev;
|
||||
}
|
||||
else if (next.startsWith("<")) {
|
||||
return Math.max(0, prev + parseFloat(next.slice(1)));
|
||||
}
|
||||
else {
|
||||
return labels.get(next) ?? current;
|
||||
}
|
||||
}
|
||||
|
||||
function eraseKeyframes(sequence, startTime, endTime) {
|
||||
for (let i = 0; i < sequence.length; i++) {
|
||||
const keyframe = sequence[i];
|
||||
if (keyframe.at > startTime && keyframe.at < endTime) {
|
||||
motionUtils.removeItem(sequence, keyframe);
|
||||
// If we remove this item we have to push the pointer back one
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
function addKeyframes(sequence, keyframes, easing, offset, startTime, endTime) {
|
||||
/**
|
||||
* Erase every existing value between currentTime and targetTime,
|
||||
* this will essentially splice this timeline into any currently
|
||||
* defined ones.
|
||||
*/
|
||||
eraseKeyframes(sequence, startTime, endTime);
|
||||
for (let i = 0; i < keyframes.length; i++) {
|
||||
sequence.push({
|
||||
value: keyframes[i],
|
||||
at: motionDom.mixNumber(startTime, endTime, offset[i]),
|
||||
easing: motionUtils.getEasingForSegment(easing, i),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Take an array of times that represent repeated keyframes. For instance
|
||||
* if we have original times of [0, 0.5, 1] then our repeated times will
|
||||
* be [0, 0.5, 1, 1, 1.5, 2]. Loop over the times and scale them back
|
||||
* down to a 0-1 scale.
|
||||
*
|
||||
* `repeatDelayUnits` is the repeatDelay expressed in units of a single
|
||||
* iteration's duration, so the total span equals `(repeat + 1) + repeat * repeatDelayUnits`.
|
||||
*/
|
||||
function normalizeTimes(times, repeat, repeatDelayUnits = 0) {
|
||||
const totalUnits = repeat + 1 + repeat * repeatDelayUnits;
|
||||
for (let i = 0; i < times.length; i++) {
|
||||
times[i] = times[i] / totalUnits;
|
||||
}
|
||||
}
|
||||
|
||||
function compareByTime(a, b) {
|
||||
if (a.at === b.at) {
|
||||
if (a.value === null)
|
||||
return 1;
|
||||
if (b.value === null)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return a.at - b.at;
|
||||
}
|
||||
}
|
||||
|
||||
const defaultSegmentEasing = "easeInOut";
|
||||
const MAX_REPEAT = 20;
|
||||
function createAnimationsFromSequence(sequence, { defaultTransition = {}, ...sequenceTransition } = {}, scope, generators) {
|
||||
const defaultDuration = defaultTransition.duration || 0.3;
|
||||
const animationDefinitions = new Map();
|
||||
const sequences = new Map();
|
||||
const elementCache = {};
|
||||
const timeLabels = new Map();
|
||||
let prevTime = 0;
|
||||
let currentTime = 0;
|
||||
let totalDuration = 0;
|
||||
/**
|
||||
* Build the timeline by mapping over the sequence array and converting
|
||||
* the definitions into keyframes and offsets with absolute time values.
|
||||
* These will later get converted into relative offsets in a second pass.
|
||||
*/
|
||||
for (let i = 0; i < sequence.length; i++) {
|
||||
const segment = sequence[i];
|
||||
/**
|
||||
* If this is a timeline label, mark it and skip the rest of this iteration.
|
||||
*/
|
||||
if (typeof segment === "string") {
|
||||
timeLabels.set(segment, currentTime);
|
||||
continue;
|
||||
}
|
||||
else if (!Array.isArray(segment)) {
|
||||
timeLabels.set(segment.name, calcNextTime(currentTime, segment.at, prevTime, timeLabels));
|
||||
continue;
|
||||
}
|
||||
let [subject, keyframes, transition = {}] = segment;
|
||||
/**
|
||||
* If a relative or absolute time value has been specified we need to resolve
|
||||
* it in relation to the currentTime.
|
||||
*/
|
||||
if (transition.at !== undefined) {
|
||||
currentTime = calcNextTime(currentTime, transition.at, prevTime, timeLabels);
|
||||
}
|
||||
/**
|
||||
* Keep track of the maximum duration in this definition. This will be
|
||||
* applied to currentTime once the definition has been parsed.
|
||||
*/
|
||||
let maxDuration = 0;
|
||||
const resolveValueSequence = (valueKeyframes, valueTransition, valueSequence, elementIndex = 0, numSubjects = 0) => {
|
||||
const valueKeyframesAsList = keyframesAsList(valueKeyframes);
|
||||
const { delay = 0, times = motionDom.defaultOffset(valueKeyframesAsList), type = defaultTransition.type || "keyframes", repeat, repeatType, repeatDelay = 0, ...remainingTransition } = valueTransition;
|
||||
let { ease = defaultTransition.ease || "easeOut", duration } = valueTransition;
|
||||
/**
|
||||
* Resolve stagger() if defined.
|
||||
*/
|
||||
const calculatedDelay = typeof delay === "function"
|
||||
? delay(elementIndex, numSubjects)
|
||||
: delay;
|
||||
/**
|
||||
* If this animation should and can use a spring, generate a spring easing function.
|
||||
*/
|
||||
const numKeyframes = valueKeyframesAsList.length;
|
||||
const createGenerator = motionDom.isGenerator(type)
|
||||
? type
|
||||
: generators?.[type || "keyframes"];
|
||||
if (numKeyframes <= 2 && createGenerator) {
|
||||
/**
|
||||
* As we're creating an easing function from a spring,
|
||||
* ideally we want to generate it using the real distance
|
||||
* between the two keyframes. However this isn't always
|
||||
* possible - in these situations we use 0-100.
|
||||
*/
|
||||
let absoluteDelta = 100;
|
||||
if (numKeyframes === 2 &&
|
||||
isNumberKeyframesArray(valueKeyframesAsList)) {
|
||||
const delta = valueKeyframesAsList[1] - valueKeyframesAsList[0];
|
||||
absoluteDelta = Math.abs(delta);
|
||||
}
|
||||
const springTransition = {
|
||||
...defaultTransition,
|
||||
...remainingTransition,
|
||||
};
|
||||
if (duration !== undefined) {
|
||||
springTransition.duration = motionUtils.secondsToMilliseconds(duration);
|
||||
}
|
||||
const springEasing = motionDom.createGeneratorEasing(springTransition, absoluteDelta, createGenerator);
|
||||
ease = springEasing.ease;
|
||||
duration = springEasing.duration;
|
||||
}
|
||||
duration ?? (duration = defaultDuration);
|
||||
const startTime = currentTime + calculatedDelay;
|
||||
/**
|
||||
* If there's only one time offset of 0, fill in a second with length 1
|
||||
*/
|
||||
if (times.length === 1 && times[0] === 0) {
|
||||
times[1] = 1;
|
||||
}
|
||||
/**
|
||||
* Fill out if offset if fewer offsets than keyframes
|
||||
*/
|
||||
const remainder = times.length - valueKeyframesAsList.length;
|
||||
remainder > 0 && motionDom.fillOffset(times, remainder);
|
||||
/**
|
||||
* If only one value has been set, ie [1], push a null to the start of
|
||||
* the keyframe array. This will let us mark a keyframe at this point
|
||||
* that will later be hydrated with the previous value.
|
||||
*/
|
||||
valueKeyframesAsList.length === 1 &&
|
||||
valueKeyframesAsList.unshift(null);
|
||||
/**
|
||||
* Segments can't express `repeat: Infinity` or very large
|
||||
* counts — they'd leave dead time after the segment or
|
||||
* explode the keyframe array. Ignore with a warning.
|
||||
*/
|
||||
if (repeat) {
|
||||
motionUtils.warning(repeat < MAX_REPEAT, `Sequence segments can't repeat ${repeat} times — ignoring repeat option. Use a value below ${MAX_REPEAT} or apply repeat at the sequence level instead.`);
|
||||
}
|
||||
if (repeat && repeat < MAX_REPEAT) {
|
||||
/**
|
||||
* Express repeatDelay in units of a single iteration's duration
|
||||
* so it can be added to the per-iteration time offsets below
|
||||
* before they're normalized to 0-1.
|
||||
*/
|
||||
const repeatDelayUnits = duration > 0 ? repeatDelay / duration : 0;
|
||||
duration = calculateRepeatDuration(duration, repeat, repeatDelay);
|
||||
const originalKeyframes = [...valueKeyframesAsList];
|
||||
const originalTimes = [...times];
|
||||
ease = Array.isArray(ease) ? [...ease] : [ease];
|
||||
const originalEase = [...ease];
|
||||
/**
|
||||
* For reverse/mirror, alternate iterations play the segment
|
||||
* backwards. mirror matches JSAnimation's mirroredGenerator:
|
||||
* reversed keyframes, easings unchanged. reverse matches
|
||||
* JSAnimation's iterationProgress = 1 - p: reversed
|
||||
* keyframes, easing array reversed AND each function easing
|
||||
* mapped through reverseEasing (string easings unchanged —
|
||||
* they're resolved later by the keyframes engine).
|
||||
*/
|
||||
const isFlipping = repeatType === "reverse" || repeatType === "mirror";
|
||||
let flippedKeyframes = originalKeyframes;
|
||||
let flippedEases = originalEase;
|
||||
if (isFlipping) {
|
||||
flippedKeyframes = [...originalKeyframes].reverse();
|
||||
if (repeatType === "reverse") {
|
||||
flippedEases = [...originalEase]
|
||||
.reverse()
|
||||
.map((e) => typeof e === "function"
|
||||
? motionUtils.reverseEasing(e)
|
||||
: e);
|
||||
}
|
||||
}
|
||||
for (let repeatIndex = 0; repeatIndex < repeat; repeatIndex++) {
|
||||
const isFlipped = isFlipping && repeatIndex % 2 === 0;
|
||||
const iterKeyframes = isFlipped
|
||||
? flippedKeyframes
|
||||
: originalKeyframes;
|
||||
const iterEase = isFlipped ? flippedEases : originalEase;
|
||||
const iterStartOffset = (repeatIndex + 1) * (1 + repeatDelayUnits);
|
||||
/**
|
||||
* If repeatDelay is set, hold the previous iteration's
|
||||
* final value through the delay by inserting a keyframe
|
||||
* at the moment the next iteration begins.
|
||||
*/
|
||||
if (repeatDelayUnits > 0) {
|
||||
valueKeyframesAsList.push(valueKeyframesAsList[valueKeyframesAsList.length - 1]);
|
||||
times.push(iterStartOffset);
|
||||
ease.push("linear");
|
||||
}
|
||||
valueKeyframesAsList.push(...iterKeyframes);
|
||||
for (let keyframeIndex = 0; keyframeIndex < iterKeyframes.length; keyframeIndex++) {
|
||||
times.push(originalTimes[keyframeIndex] + iterStartOffset);
|
||||
ease.push(keyframeIndex === 0
|
||||
? "linear"
|
||||
: motionUtils.getEasingForSegment(iterEase, keyframeIndex - 1));
|
||||
}
|
||||
}
|
||||
normalizeTimes(times, repeat, repeatDelayUnits);
|
||||
}
|
||||
const targetTime = startTime + duration;
|
||||
/**
|
||||
* Add keyframes, mapping offsets to absolute time.
|
||||
*/
|
||||
addKeyframes(valueSequence, valueKeyframesAsList, ease, times, startTime, targetTime);
|
||||
maxDuration = Math.max(calculatedDelay + duration, maxDuration);
|
||||
totalDuration = Math.max(targetTime, totalDuration);
|
||||
};
|
||||
if (motionDom.isMotionValue(subject)) {
|
||||
const subjectSequence = getSubjectSequence(subject, sequences);
|
||||
resolveValueSequence(keyframes, transition, getValueSequence("default", subjectSequence));
|
||||
}
|
||||
else {
|
||||
const subjects = resolveSubjects(subject, keyframes, scope, elementCache);
|
||||
const numSubjects = subjects.length;
|
||||
/**
|
||||
* For every element in this segment, process the defined values.
|
||||
*/
|
||||
for (let subjectIndex = 0; subjectIndex < numSubjects; subjectIndex++) {
|
||||
/**
|
||||
* Cast necessary, but we know these are of this type
|
||||
*/
|
||||
keyframes = keyframes;
|
||||
transition = transition;
|
||||
const thisSubject = subjects[subjectIndex];
|
||||
const subjectSequence = getSubjectSequence(thisSubject, sequences);
|
||||
for (const key in keyframes) {
|
||||
resolveValueSequence(keyframes[key], getValueTransition(transition, key), getValueSequence(key, subjectSequence), subjectIndex, numSubjects);
|
||||
}
|
||||
}
|
||||
}
|
||||
prevTime = currentTime;
|
||||
currentTime += maxDuration;
|
||||
}
|
||||
/**
|
||||
* For every element and value combination create a new animation.
|
||||
*/
|
||||
sequences.forEach((valueSequences, element) => {
|
||||
for (const key in valueSequences) {
|
||||
const valueSequence = valueSequences[key];
|
||||
/**
|
||||
* Arrange all the keyframes in ascending time order.
|
||||
*/
|
||||
valueSequence.sort(compareByTime);
|
||||
const keyframes = [];
|
||||
const valueOffset = [];
|
||||
const valueEasing = [];
|
||||
/**
|
||||
* For each keyframe, translate absolute times into
|
||||
* relative offsets based on the total duration of the timeline.
|
||||
*/
|
||||
for (let i = 0; i < valueSequence.length; i++) {
|
||||
const { at, value, easing } = valueSequence[i];
|
||||
keyframes.push(value);
|
||||
valueOffset.push(motionUtils.progress(0, totalDuration, at));
|
||||
valueEasing.push(easing || "easeOut");
|
||||
}
|
||||
/**
|
||||
* If the first keyframe doesn't land on offset: 0
|
||||
* provide one by duplicating the initial keyframe. This ensures
|
||||
* it snaps to the first keyframe when the animation starts.
|
||||
*/
|
||||
if (valueOffset[0] !== 0) {
|
||||
valueOffset.unshift(0);
|
||||
keyframes.unshift(keyframes[0]);
|
||||
valueEasing.unshift(defaultSegmentEasing);
|
||||
}
|
||||
/**
|
||||
* If the last keyframe doesn't land on offset: 1
|
||||
* provide one with a null wildcard value. This will ensure it
|
||||
* stays static until the end of the animation.
|
||||
*/
|
||||
if (valueOffset[valueOffset.length - 1] !== 1) {
|
||||
valueOffset.push(1);
|
||||
keyframes.push(null);
|
||||
}
|
||||
if (!animationDefinitions.has(element)) {
|
||||
animationDefinitions.set(element, {
|
||||
keyframes: {},
|
||||
transition: {},
|
||||
});
|
||||
}
|
||||
const definition = animationDefinitions.get(element);
|
||||
definition.keyframes[key] = keyframes;
|
||||
/**
|
||||
* Exclude `type` from defaultTransition since springs have been
|
||||
* converted to duration-based easing functions in resolveValueSequence.
|
||||
* Including `type: "spring"` would cause JSAnimation to error when
|
||||
* the merged keyframes array has more than 2 keyframes.
|
||||
*/
|
||||
const { type: _type, ...remainingDefaultTransition } = defaultTransition;
|
||||
definition.transition[key] = {
|
||||
...remainingDefaultTransition,
|
||||
duration: totalDuration,
|
||||
ease: valueEasing,
|
||||
times: valueOffset,
|
||||
...sequenceTransition,
|
||||
};
|
||||
}
|
||||
});
|
||||
return animationDefinitions;
|
||||
}
|
||||
function getSubjectSequence(subject, sequences) {
|
||||
!sequences.has(subject) && sequences.set(subject, {});
|
||||
return sequences.get(subject);
|
||||
}
|
||||
function getValueSequence(name, sequences) {
|
||||
if (!sequences[name])
|
||||
sequences[name] = [];
|
||||
return sequences[name];
|
||||
}
|
||||
function keyframesAsList(keyframes) {
|
||||
return Array.isArray(keyframes) ? keyframes : [keyframes];
|
||||
}
|
||||
function getValueTransition(transition, key) {
|
||||
return transition && transition[key]
|
||||
? {
|
||||
...transition,
|
||||
...transition[key],
|
||||
}
|
||||
: { ...transition };
|
||||
}
|
||||
const isNumber = (keyframe) => typeof keyframe === "number";
|
||||
const isNumberKeyframesArray = (keyframes) => keyframes.every(isNumber);
|
||||
|
||||
function createDOMVisualElement(element) {
|
||||
const options = {
|
||||
presenceContext: null,
|
||||
props: {},
|
||||
visualState: {
|
||||
renderState: {
|
||||
transform: {},
|
||||
transformOrigin: {},
|
||||
style: {},
|
||||
vars: {},
|
||||
attrs: {},
|
||||
},
|
||||
latestValues: {},
|
||||
},
|
||||
};
|
||||
const node = motionDom.isSVGElement(element) && !motionDom.isSVGSVGElement(element)
|
||||
? new motionDom.SVGVisualElement(options)
|
||||
: new motionDom.HTMLVisualElement(options);
|
||||
node.mount(element);
|
||||
motionDom.visualElementStore.set(element, node);
|
||||
}
|
||||
function createObjectVisualElement(subject) {
|
||||
const options = {
|
||||
presenceContext: null,
|
||||
props: {},
|
||||
visualState: {
|
||||
renderState: {
|
||||
output: {},
|
||||
},
|
||||
latestValues: {},
|
||||
},
|
||||
};
|
||||
const node = new motionDom.ObjectVisualElement(options);
|
||||
node.mount(subject);
|
||||
motionDom.visualElementStore.set(subject, node);
|
||||
}
|
||||
|
||||
function isSingleValue(subject, keyframes) {
|
||||
return (motionDom.isMotionValue(subject) ||
|
||||
typeof subject === "number" ||
|
||||
(typeof subject === "string" && !isDOMKeyframes(keyframes)));
|
||||
}
|
||||
/**
|
||||
* Implementation
|
||||
*/
|
||||
function animateSubject(subject, keyframes, options, scope) {
|
||||
const animations = [];
|
||||
if (isSingleValue(subject, keyframes)) {
|
||||
animations.push(motionDom.animateSingleValue(subject, isDOMKeyframes(keyframes)
|
||||
? keyframes.default || keyframes
|
||||
: keyframes, options ? options.default || options : options));
|
||||
}
|
||||
else {
|
||||
// Gracefully handle null/undefined subjects (e.g., from querySelector returning null)
|
||||
if (subject == null) {
|
||||
return animations;
|
||||
}
|
||||
const subjects = resolveSubjects(subject, keyframes, scope);
|
||||
const numSubjects = subjects.length;
|
||||
motionUtils.invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
|
||||
for (let i = 0; i < numSubjects; i++) {
|
||||
const thisSubject = subjects[i];
|
||||
const createVisualElement = thisSubject instanceof Element
|
||||
? createDOMVisualElement
|
||||
: createObjectVisualElement;
|
||||
if (!motionDom.visualElementStore.has(thisSubject)) {
|
||||
createVisualElement(thisSubject);
|
||||
}
|
||||
const visualElement = motionDom.visualElementStore.get(thisSubject);
|
||||
const transition = { ...options };
|
||||
/**
|
||||
* Resolve stagger function if provided.
|
||||
*/
|
||||
if ("delay" in transition &&
|
||||
typeof transition.delay === "function") {
|
||||
transition.delay = transition.delay(i, numSubjects);
|
||||
}
|
||||
animations.push(...motionDom.animateTarget(visualElement, { ...keyframes, transition }, {}));
|
||||
}
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
function animateSequence(sequence, options, scope) {
|
||||
const animations = [];
|
||||
/**
|
||||
* Pre-process: replace function segments with MotionValue segments,
|
||||
* subscribe callbacks immediately
|
||||
*/
|
||||
const processedSequence = sequence.map((segment) => {
|
||||
if (Array.isArray(segment) && typeof segment[0] === "function") {
|
||||
const callback = segment[0];
|
||||
const mv = motionDom.motionValue(0);
|
||||
mv.on("change", callback);
|
||||
if (segment.length === 1) {
|
||||
return [mv, [0, 1]];
|
||||
}
|
||||
else if (segment.length === 2) {
|
||||
return [mv, [0, 1], segment[1]];
|
||||
}
|
||||
else {
|
||||
return [mv, segment[1], segment[2]];
|
||||
}
|
||||
}
|
||||
return segment;
|
||||
});
|
||||
const animationDefinitions = createAnimationsFromSequence(processedSequence, options, scope, { spring: motionDom.spring });
|
||||
animationDefinitions.forEach(({ keyframes, transition }, subject) => {
|
||||
animations.push(...animateSubject(subject, keyframes, transition));
|
||||
});
|
||||
return animations;
|
||||
}
|
||||
|
||||
function isSequence(value) {
|
||||
return Array.isArray(value) && value.some(Array.isArray);
|
||||
}
|
||||
/**
|
||||
* Creates an animation function that is optionally scoped
|
||||
* to a specific element.
|
||||
*/
|
||||
function createScopedAnimate(options = {}) {
|
||||
const { scope, reduceMotion, skipAnimations } = options;
|
||||
/**
|
||||
* Implementation
|
||||
*/
|
||||
function scopedAnimate(subjectOrSequence, optionsOrKeyframes, options) {
|
||||
let animations = [];
|
||||
let animationOnComplete;
|
||||
const inherited = {};
|
||||
if (reduceMotion !== undefined)
|
||||
inherited.reduceMotion = reduceMotion;
|
||||
if (skipAnimations !== undefined)
|
||||
inherited.skipAnimations = skipAnimations;
|
||||
if (isSequence(subjectOrSequence)) {
|
||||
const { onComplete, ...sequenceOptions } = optionsOrKeyframes || {};
|
||||
if (typeof onComplete === "function") {
|
||||
animationOnComplete = onComplete;
|
||||
}
|
||||
animations = animateSequence(subjectOrSequence, { ...inherited, ...sequenceOptions }, scope);
|
||||
}
|
||||
else {
|
||||
// Extract top-level onComplete so it doesn't get applied per-value
|
||||
const { onComplete, ...rest } = options || {};
|
||||
if (typeof onComplete === "function") {
|
||||
animationOnComplete = onComplete;
|
||||
}
|
||||
animations = animateSubject(subjectOrSequence, optionsOrKeyframes, { ...inherited, ...rest }, scope);
|
||||
}
|
||||
const animation = new motionDom.GroupAnimationWithThen(animations);
|
||||
if (animationOnComplete) {
|
||||
animation.finished.then(animationOnComplete);
|
||||
}
|
||||
if (scope) {
|
||||
scope.animations.push(animation);
|
||||
animation.finished.then(() => {
|
||||
motionUtils.removeItem(scope.animations, animation);
|
||||
});
|
||||
}
|
||||
return animation;
|
||||
}
|
||||
return scopedAnimate;
|
||||
}
|
||||
const animate = createScopedAnimate();
|
||||
|
||||
function animateElements(elementOrSelector, keyframes, options, scope) {
|
||||
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
|
||||
if (elementOrSelector == null) {
|
||||
return [];
|
||||
}
|
||||
const elements = motionDom.resolveElements(elementOrSelector, scope);
|
||||
const numElements = elements.length;
|
||||
motionUtils.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
|
||||
/**
|
||||
* WAAPI doesn't support interrupting animations.
|
||||
*
|
||||
* Therefore, starting animations requires a three-step process:
|
||||
* 1. Stop existing animations (write styles to DOM)
|
||||
* 2. Resolve keyframes (read styles from DOM)
|
||||
* 3. Create new animations (write styles to DOM)
|
||||
*
|
||||
* The hybrid `animate()` function uses AsyncAnimation to resolve
|
||||
* keyframes before creating new animations, which removes style
|
||||
* thrashing. Here, we have much stricter filesize constraints.
|
||||
* Therefore we do this in a synchronous way that ensures that
|
||||
* at least within `animate()` calls there is no style thrashing.
|
||||
*
|
||||
* In the motion-native-animate-mini-interrupt benchmark this
|
||||
* was 80% faster than a single loop.
|
||||
*/
|
||||
const animationDefinitions = [];
|
||||
/**
|
||||
* Step 1: Build options and stop existing animations (write)
|
||||
*/
|
||||
for (let i = 0; i < numElements; i++) {
|
||||
const element = elements[i];
|
||||
const elementTransition = { ...options };
|
||||
/**
|
||||
* Resolve stagger function if provided.
|
||||
*/
|
||||
if (typeof elementTransition.delay === "function") {
|
||||
elementTransition.delay = elementTransition.delay(i, numElements);
|
||||
}
|
||||
for (const valueName in keyframes) {
|
||||
let valueKeyframes = keyframes[valueName];
|
||||
if (!Array.isArray(valueKeyframes)) {
|
||||
valueKeyframes = [valueKeyframes];
|
||||
}
|
||||
const valueOptions = {
|
||||
...motionDom.getValueTransition(elementTransition, valueName),
|
||||
};
|
||||
valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
|
||||
valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
|
||||
/**
|
||||
* If there's an existing animation playing on this element then stop it
|
||||
* before creating a new one.
|
||||
*/
|
||||
const map = motionDom.getAnimationMap(element);
|
||||
const key = motionDom.animationMapKey(valueName, valueOptions.pseudoElement || "");
|
||||
const currentAnimation = map.get(key);
|
||||
currentAnimation && currentAnimation.stop();
|
||||
animationDefinitions.push({
|
||||
map,
|
||||
key,
|
||||
unresolvedKeyframes: valueKeyframes,
|
||||
options: {
|
||||
...valueOptions,
|
||||
element,
|
||||
name: valueName,
|
||||
allowFlatten: !elementTransition.type && !elementTransition.ease,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Step 2: Resolve keyframes (read)
|
||||
*/
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
|
||||
const { element, name, pseudoElement } = animationOptions;
|
||||
if (!pseudoElement && unresolvedKeyframes[0] === null) {
|
||||
unresolvedKeyframes[0] = motionDom.getComputedStyle(element, name);
|
||||
}
|
||||
motionDom.fillWildcards(unresolvedKeyframes);
|
||||
motionDom.applyPxDefaults(unresolvedKeyframes, name);
|
||||
/**
|
||||
* If we only have one keyframe, explicitly read the initial keyframe
|
||||
* from the computed style. This is to ensure consistency with WAAPI behaviour
|
||||
* for restarting animations, for instance .play() after finish, when it
|
||||
* has one vs two keyframes.
|
||||
*/
|
||||
if (!pseudoElement && unresolvedKeyframes.length < 2) {
|
||||
unresolvedKeyframes.unshift(motionDom.getComputedStyle(element, name));
|
||||
}
|
||||
animationOptions.keyframes = unresolvedKeyframes;
|
||||
}
|
||||
/**
|
||||
* Step 3: Create new animations (write)
|
||||
*/
|
||||
const animations = [];
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { map, key, options: animationOptions } = animationDefinitions[i];
|
||||
const animation = new motionDom.NativeAnimation(animationOptions);
|
||||
map.set(key, animation);
|
||||
animation.finished.finally(() => map.delete(key));
|
||||
animations.push(animation);
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
const createScopedWaapiAnimate = (scope) => {
|
||||
function scopedAnimate(elementOrSelector, keyframes, options) {
|
||||
return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
|
||||
}
|
||||
return scopedAnimate;
|
||||
};
|
||||
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
|
||||
|
||||
function canUseNativeTimeline(target) {
|
||||
if (typeof window === "undefined")
|
||||
return false;
|
||||
return target ? motionDom.supportsViewTimeline() : motionDom.supportsScrollTimeline();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = motionUtils.progress(0, axis.scrollLength, axis.current);
|
||||
const elapsed = time - prevTime;
|
||||
axis.velocity =
|
||||
elapsed > maxElapsed
|
||||
? 0
|
||||
: motionUtils.velocityPerSecond(axis.current - prev, elapsed);
|
||||
}
|
||||
function updateScrollInfo(element, info, time) {
|
||||
updateAxisInfo(element, "x", info, time);
|
||||
updateAxisInfo(element, "y", info, time);
|
||||
info.time = time;
|
||||
}
|
||||
|
||||
function calcInset(element, container) {
|
||||
const inset = { x: 0, y: 0 };
|
||||
let current = element;
|
||||
while (current && current !== container) {
|
||||
if (motionDom.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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
const ScrollOffset = {
|
||||
Enter: [
|
||||
[0, 1],
|
||||
[1, 1],
|
||||
],
|
||||
Exit: [
|
||||
[0, 0],
|
||||
[1, 0],
|
||||
],
|
||||
Any: [
|
||||
[1, 0],
|
||||
[0, 1],
|
||||
],
|
||||
All: [
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
],
|
||||
};
|
||||
|
||||
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 = motionDom.interpolate(info[axis].offset, motionDom.defaultOffset(offsetDefinition), { clamp: false });
|
||||
info[axis].interpolatorOffsets = [...info[axis].offset];
|
||||
}
|
||||
info[axis].progress = motionUtils.clamp(0, 1, info[axis].interpolate(info[axis].current));
|
||||
}
|
||||
|
||||
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. The document scrolling element
|
||||
* is exempt: offsetParent measurements naturally resolve relative to the document.
|
||||
*/
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (container &&
|
||||
target &&
|
||||
target !== container &&
|
||||
container !== document.documentElement &&
|
||||
container !== document.scrollingElement &&
|
||||
container !== document.body) {
|
||||
motionUtils.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),
|
||||
};
|
||||
}
|
||||
|
||||
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 motionUtils.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(motionDom.frameData.timestamp);
|
||||
}
|
||||
motionDom.frame.preUpdate(notifyAll);
|
||||
};
|
||||
const notifyAll = () => {
|
||||
for (const handler of containerHandlers) {
|
||||
handler.notify();
|
||||
}
|
||||
};
|
||||
const listener = () => motionDom.frame.read(measureAll);
|
||||
scrollListeners.set(container, listener);
|
||||
const target = getEventTarget(container);
|
||||
window.addEventListener("resize", listener);
|
||||
if (container !== document.documentElement) {
|
||||
resizeListeners.set(container, motionDom.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 = motionDom.frame.read(checkScrollDimensions, true);
|
||||
dimensionCheckProcesses.set(container, dimensionCheckProcess);
|
||||
}
|
||||
const listener = scrollListeners.get(container);
|
||||
motionDom.frame.read(listener, false, true);
|
||||
return () => {
|
||||
motionDom.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) {
|
||||
motionDom.cancelFrame(dimensionCheckProcess);
|
||||
dimensionCheckProcesses.delete(container);
|
||||
}
|
||||
scrollSize.delete(container);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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 motionDom.observeTimeline((progress) => {
|
||||
valueAnimation.time =
|
||||
valueAnimation.iterationDuration * progress;
|
||||
}, timeline);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Currently, we only support element tracking with `scrollInfo`, though in
|
||||
* the future we can also offer ViewTimeline support.
|
||||
*/
|
||||
function isElementTracking(options) {
|
||||
return options && (options.target || options.offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) || isElementTracking(options)) {
|
||||
return scrollInfo((info) => {
|
||||
onScroll(info[options.axis].progress, info);
|
||||
}, options);
|
||||
}
|
||||
else {
|
||||
return motionDom.observeTimeline(onScroll, getTimeline(options));
|
||||
}
|
||||
}
|
||||
|
||||
function scroll(onScroll, { axis = "y", container = document.scrollingElement, ...options } = {}) {
|
||||
if (!container)
|
||||
return motionUtils.noop;
|
||||
const optionsWithDefaults = { axis, container, ...options };
|
||||
return typeof onScroll === "function"
|
||||
? attachToFunction(onScroll, optionsWithDefaults)
|
||||
: attachToAnimation(onScroll, optionsWithDefaults);
|
||||
}
|
||||
|
||||
const thresholds = {
|
||||
some: 0,
|
||||
all: 1,
|
||||
};
|
||||
function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) {
|
||||
const elements = motionDom.resolveElements(elementOrSelector);
|
||||
const activeIntersections = new WeakMap();
|
||||
const onIntersectionChange = (entries) => {
|
||||
entries.forEach((entry) => {
|
||||
const onEnd = activeIntersections.get(entry.target);
|
||||
/**
|
||||
* If there's no change to the intersection, we don't need to
|
||||
* do anything here.
|
||||
*/
|
||||
if (entry.isIntersecting === Boolean(onEnd))
|
||||
return;
|
||||
if (entry.isIntersecting) {
|
||||
const newOnEnd = onStart(entry.target, entry);
|
||||
if (typeof newOnEnd === "function") {
|
||||
activeIntersections.set(entry.target, newOnEnd);
|
||||
}
|
||||
else {
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
}
|
||||
else if (typeof onEnd === "function") {
|
||||
onEnd(entry);
|
||||
activeIntersections.delete(entry.target);
|
||||
}
|
||||
});
|
||||
};
|
||||
const observer = new IntersectionObserver(onIntersectionChange, {
|
||||
root,
|
||||
rootMargin,
|
||||
threshold: typeof amount === "number" ? amount : thresholds[amount],
|
||||
});
|
||||
elements.forEach((element) => observer.observe(element));
|
||||
return () => observer.disconnect();
|
||||
}
|
||||
|
||||
const m = /*@__PURE__*/ createMotionProxy();
|
||||
|
||||
function useUnmountEffect(callback) {
|
||||
return React.useEffect(() => () => callback(), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const domAnimation = {
|
||||
renderer: featureBundle.createDomVisualElement,
|
||||
...featureBundle.animations,
|
||||
...featureBundle.gestureAnimations,
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const domMax = {
|
||||
...domAnimation,
|
||||
...featureBundle.drag,
|
||||
...featureBundle.layout,
|
||||
};
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
const domMin = {
|
||||
renderer: featureBundle.createDomVisualElement,
|
||||
...featureBundle.animations,
|
||||
};
|
||||
|
||||
function useMotionValueEvent(value, event, callback) {
|
||||
/**
|
||||
* useInsertionEffect will create subscriptions before any other
|
||||
* effects will run. Effects run upwards through the tree so it
|
||||
* can be that binding a useLayoutEffect higher up the tree can
|
||||
* miss changes from lower down the tree.
|
||||
*/
|
||||
React.useInsertionEffect(() => value.on(event, callback), [value, event, callback]);
|
||||
}
|
||||
|
||||
const createScrollMotionValues = () => ({
|
||||
scrollX: motionDom.motionValue(0),
|
||||
scrollY: motionDom.motionValue(0),
|
||||
scrollXProgress: motionDom.motionValue(0),
|
||||
scrollYProgress: motionDom.motionValue(0),
|
||||
});
|
||||
const isRefPending = (ref) => {
|
||||
if (!ref)
|
||||
return false;
|
||||
return !ref.current;
|
||||
};
|
||||
function makeAccelerateConfig(axis, options, container, target) {
|
||||
return {
|
||||
// Refs attach child-first; defer so target.current is populated
|
||||
// before scroll() reads it.
|
||||
factory: (animation) => {
|
||||
let cleanup;
|
||||
const start = () => {
|
||||
// A provided ref may be hydrated by an effect declared after
|
||||
// useScroll (or in a parent). Don't attach to the window
|
||||
// scroll in the meantime — that result gets cached and would
|
||||
// permanently mistrack. Wait until the ref resolves.
|
||||
if (isRefPending(container) || isRefPending(target)) {
|
||||
motionDom.microtask.read(start);
|
||||
return;
|
||||
}
|
||||
cleanup = scroll(animation, {
|
||||
...options,
|
||||
axis,
|
||||
container: container?.current || undefined,
|
||||
target: target?.current || undefined,
|
||||
});
|
||||
};
|
||||
motionDom.microtask.read(start);
|
||||
return () => {
|
||||
motionDom.cancelMicrotask(start);
|
||||
cleanup?.();
|
||||
};
|
||||
},
|
||||
times: [0, 1],
|
||||
keyframes: [0, 1],
|
||||
ease: (v) => v,
|
||||
duration: 1,
|
||||
};
|
||||
}
|
||||
function canAccelerateScroll(target, offset) {
|
||||
if (typeof window === "undefined")
|
||||
return false;
|
||||
return target
|
||||
? motionDom.supportsViewTimeline() && !!offsetToViewTimelineRange(offset)
|
||||
: motionDom.supportsScrollTimeline();
|
||||
}
|
||||
function useScroll({ container, target, ...options } = {}) {
|
||||
const values = index.useConstant(createScrollMotionValues);
|
||||
if (canAccelerateScroll(target, options.offset)) {
|
||||
values.scrollXProgress.accelerate = makeAccelerateConfig("x", options, container, target);
|
||||
values.scrollYProgress.accelerate = makeAccelerateConfig("y", options, container, target);
|
||||
}
|
||||
const scrollAnimation = React.useRef(null);
|
||||
const needsStart = React.useRef(false);
|
||||
const start = React.useCallback(() => {
|
||||
scrollAnimation.current = scroll((_progress, { x, y, }) => {
|
||||
values.scrollX.set(x.current);
|
||||
values.scrollXProgress.set(x.progress);
|
||||
values.scrollY.set(y.current);
|
||||
values.scrollYProgress.set(y.progress);
|
||||
}, {
|
||||
...options,
|
||||
container: container?.current || undefined,
|
||||
target: target?.current || undefined,
|
||||
});
|
||||
return () => {
|
||||
scrollAnimation.current?.();
|
||||
};
|
||||
}, [container, target, JSON.stringify(options.offset)]);
|
||||
index.useIsomorphicLayoutEffect(() => {
|
||||
needsStart.current = false;
|
||||
if (isRefPending(container) || isRefPending(target)) {
|
||||
needsStart.current = true;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
return start();
|
||||
}
|
||||
}, [start]);
|
||||
React.useEffect(() => {
|
||||
if (!needsStart.current)
|
||||
return;
|
||||
// Defer to a microtask so any sibling/parent effect that hydrates the
|
||||
// ref has a chance to run first.
|
||||
let cleanup;
|
||||
const tryStart = () => {
|
||||
const containerPending = isRefPending(container);
|
||||
const targetPending = isRefPending(target);
|
||||
motionUtils.invariant(!containerPending, "Container ref is defined but not hydrated", "use-scroll-ref");
|
||||
motionUtils.invariant(!targetPending, "Target ref is defined but not hydrated", "use-scroll-ref");
|
||||
if (!containerPending && !targetPending)
|
||||
cleanup = start();
|
||||
};
|
||||
motionDom.microtask.read(tryStart);
|
||||
return () => {
|
||||
motionDom.cancelMicrotask(tryStart);
|
||||
cleanup?.();
|
||||
};
|
||||
}, [start]);
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated useElementScroll is deprecated. Convert to useScroll({ container: ref })
|
||||
*/
|
||||
function useElementScroll(ref) {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
motionUtils.warnOnce(false, "useElementScroll is deprecated. Convert to useScroll({ container: ref }).");
|
||||
}
|
||||
return useScroll({ container: ref });
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated useViewportScroll is deprecated. Convert to useScroll()
|
||||
*/
|
||||
function useViewportScroll() {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
motionUtils.warnOnce(false, "useViewportScroll is deprecated. Convert to useScroll().");
|
||||
}
|
||||
return useScroll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine multiple motion values into a new one using a string template literal.
|
||||
*
|
||||
* ```jsx
|
||||
* import {
|
||||
* motion,
|
||||
* useSpring,
|
||||
* useMotionValue,
|
||||
* useMotionTemplate
|
||||
* } from "framer-motion"
|
||||
*
|
||||
* function Component() {
|
||||
* const shadowX = useSpring(0)
|
||||
* const shadowY = useMotionValue(0)
|
||||
* const shadow = useMotionTemplate`drop-shadow(${shadowX}px ${shadowY}px 20px rgba(0,0,0,0.3))`
|
||||
*
|
||||
* return <motion.div style={{ filter: shadow }} />
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useMotionTemplate(fragments, ...values) {
|
||||
/**
|
||||
* Create a function that will build a string from the latest motion values.
|
||||
*/
|
||||
const numFragments = fragments.length;
|
||||
function buildValue() {
|
||||
let output = ``;
|
||||
for (let i = 0; i < numFragments; i++) {
|
||||
output += fragments[i];
|
||||
const value = values[i];
|
||||
if (value) {
|
||||
output += motionDom.isMotionValue(value) ? value.get() : value;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
return useCombineMotionValues(values.filter(motionDom.isMotionValue), buildValue);
|
||||
}
|
||||
|
||||
function useFollowValue(source, options = {}) {
|
||||
const { isStatic } = React.useContext(index.MotionConfigContext);
|
||||
const getFromSource = () => (motionDom.isMotionValue(source) ? source.get() : source);
|
||||
// isStatic will never change, allowing early hooks return
|
||||
if (isStatic) {
|
||||
return useTransform(getFromSource);
|
||||
}
|
||||
const value = useMotionValue(getFromSource());
|
||||
React.useInsertionEffect(() => {
|
||||
return motionDom.attachFollow(value, source, options);
|
||||
}, [value, JSON.stringify(options)]);
|
||||
return value;
|
||||
}
|
||||
|
||||
function useSpring(source, options = {}) {
|
||||
return useFollowValue(source, { type: "spring", ...options });
|
||||
}
|
||||
|
||||
function useAnimationFrame(callback) {
|
||||
const initialTimestamp = React.useRef(0);
|
||||
const { isStatic } = React.useContext(index.MotionConfigContext);
|
||||
React.useEffect(() => {
|
||||
if (isStatic)
|
||||
return;
|
||||
const provideTimeSinceStart = ({ timestamp, delta }) => {
|
||||
if (!initialTimestamp.current)
|
||||
initialTimestamp.current = timestamp;
|
||||
callback(timestamp - initialTimestamp.current, delta);
|
||||
};
|
||||
motionDom.frame.update(provideTimeSinceStart, true);
|
||||
return () => motionDom.cancelFrame(provideTimeSinceStart);
|
||||
}, [callback]);
|
||||
}
|
||||
|
||||
function useTime() {
|
||||
const time = useMotionValue(0);
|
||||
useAnimationFrame((t) => time.set(t));
|
||||
return time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `MotionValue` that updates when the velocity of the provided `MotionValue` changes.
|
||||
*
|
||||
* ```javascript
|
||||
* const x = useMotionValue(0)
|
||||
* const xVelocity = useVelocity(x)
|
||||
* const xAcceleration = useVelocity(xVelocity)
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useVelocity(value) {
|
||||
const velocity = useMotionValue(value.getVelocity());
|
||||
const updateVelocity = () => {
|
||||
const latest = value.getVelocity();
|
||||
velocity.set(latest);
|
||||
/**
|
||||
* If we still have velocity, schedule an update for the next frame
|
||||
* to keep checking until it is zero.
|
||||
*/
|
||||
if (latest)
|
||||
motionDom.frame.update(updateVelocity);
|
||||
};
|
||||
useMotionValueEvent(value, "change", () => {
|
||||
// Schedule an update to this value at the end of the current frame.
|
||||
motionDom.frame.update(updateVelocity, false, true);
|
||||
});
|
||||
return velocity;
|
||||
}
|
||||
|
||||
class WillChangeMotionValue extends motionDom.MotionValue {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.isEnabled = false;
|
||||
}
|
||||
add(name) {
|
||||
if (motionDom.transformProps.has(name) || motionDom.acceleratedValues.has(name)) {
|
||||
this.isEnabled = true;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
update() {
|
||||
this.set(this.isEnabled ? "transform" : "auto");
|
||||
}
|
||||
}
|
||||
|
||||
function useWillChange() {
|
||||
return index.useConstant(() => new WillChangeMotionValue("auto"));
|
||||
}
|
||||
|
||||
/**
|
||||
* A hook that returns `true` if we should be using reduced motion based on the current device's Reduced Motion setting.
|
||||
*
|
||||
* This can be used to implement changes to your UI based on Reduced Motion. For instance, replacing motion-sickness inducing
|
||||
* `x`/`y` animations with `opacity`, disabling the autoplay of background videos, or turning off parallax motion.
|
||||
*
|
||||
* It will actively respond to changes and re-render your components with the latest setting.
|
||||
*
|
||||
* ```jsx
|
||||
* export function Sidebar({ isOpen }) {
|
||||
* const shouldReduceMotion = useReducedMotion()
|
||||
* const closedX = shouldReduceMotion ? 0 : "-100%"
|
||||
*
|
||||
* return (
|
||||
* <motion.div animate={{
|
||||
* opacity: isOpen ? 1 : 0,
|
||||
* x: isOpen ? 0 : closedX
|
||||
* }} />
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useReducedMotion() {
|
||||
/**
|
||||
* Lazy initialisation of prefersReducedMotion
|
||||
*/
|
||||
!motionDom.hasReducedMotionListener.current && motionDom.initPrefersReducedMotion();
|
||||
const [shouldReduceMotion] = React.useState(motionDom.prefersReducedMotion.current);
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
motionUtils.warnOnce(shouldReduceMotion !== true, "You have Reduced Motion enabled on your device. Animations may not appear as expected.", "reduced-motion-disabled");
|
||||
}
|
||||
/**
|
||||
* TODO See if people miss automatically updating shouldReduceMotion setting
|
||||
*/
|
||||
return shouldReduceMotion;
|
||||
}
|
||||
|
||||
function useReducedMotionConfig() {
|
||||
const reducedMotionPreference = useReducedMotion();
|
||||
const { reducedMotion } = React.useContext(index.MotionConfigContext);
|
||||
if (reducedMotion === "never") {
|
||||
return false;
|
||||
}
|
||||
else if (reducedMotion === "always") {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
return reducedMotionPreference;
|
||||
}
|
||||
}
|
||||
|
||||
function stopAnimation(visualElement) {
|
||||
visualElement.values.forEach((value) => value.stop());
|
||||
}
|
||||
function setVariants(visualElement, variantLabels) {
|
||||
const reversedLabels = [...variantLabels].reverse();
|
||||
reversedLabels.forEach((key) => {
|
||||
const variant = visualElement.getVariant(key);
|
||||
variant && motionDom.setTarget(visualElement, variant);
|
||||
if (visualElement.variantChildren) {
|
||||
visualElement.variantChildren.forEach((child) => {
|
||||
setVariants(child, variantLabels);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function setValues(visualElement, definition) {
|
||||
if (Array.isArray(definition)) {
|
||||
return setVariants(visualElement, definition);
|
||||
}
|
||||
else if (typeof definition === "string") {
|
||||
return setVariants(visualElement, [definition]);
|
||||
}
|
||||
else {
|
||||
motionDom.setTarget(visualElement, definition);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
function animationControls() {
|
||||
/**
|
||||
* Track whether the host component has mounted.
|
||||
*/
|
||||
let hasMounted = false;
|
||||
/**
|
||||
* A collection of linked component animation controls.
|
||||
*/
|
||||
const subscribers = new Set();
|
||||
const controls = {
|
||||
subscribe(visualElement) {
|
||||
subscribers.add(visualElement);
|
||||
return () => void subscribers.delete(visualElement);
|
||||
},
|
||||
start(definition, transitionOverride) {
|
||||
motionUtils.invariant(hasMounted, "controls.start() should only be called after a component has mounted. Consider calling within a useEffect hook.");
|
||||
const animations = [];
|
||||
subscribers.forEach((visualElement) => {
|
||||
animations.push(motionDom.animateVisualElement(visualElement, definition, {
|
||||
transitionOverride,
|
||||
}));
|
||||
});
|
||||
return Promise.all(animations);
|
||||
},
|
||||
set(definition) {
|
||||
motionUtils.invariant(hasMounted, "controls.set() should only be called after a component has mounted. Consider calling within a useEffect hook.");
|
||||
return subscribers.forEach((visualElement) => {
|
||||
setValues(visualElement, definition);
|
||||
});
|
||||
},
|
||||
stop() {
|
||||
subscribers.forEach((visualElement) => {
|
||||
stopAnimation(visualElement);
|
||||
});
|
||||
},
|
||||
mount() {
|
||||
hasMounted = true;
|
||||
return () => {
|
||||
hasMounted = false;
|
||||
controls.stop();
|
||||
};
|
||||
},
|
||||
};
|
||||
return controls;
|
||||
}
|
||||
|
||||
function useAnimate() {
|
||||
const scope = index.useConstant(() => ({
|
||||
current: null, // Will be hydrated by React
|
||||
animations: [],
|
||||
}));
|
||||
const reduceMotion = useReducedMotionConfig() ?? undefined;
|
||||
const { skipAnimations } = React.useContext(index.MotionConfigContext);
|
||||
const animate = React.useMemo(() => createScopedAnimate({ scope, reduceMotion, skipAnimations }), [scope, reduceMotion, skipAnimations]);
|
||||
useUnmountEffect(() => {
|
||||
scope.animations.forEach((animation) => animation.stop());
|
||||
scope.animations.length = 0;
|
||||
});
|
||||
return [scope, animate];
|
||||
}
|
||||
|
||||
function useAnimateMini() {
|
||||
const scope = index.useConstant(() => ({
|
||||
current: null, // Will be hydrated by React
|
||||
animations: [],
|
||||
}));
|
||||
const animate = index.useConstant(() => createScopedWaapiAnimate(scope));
|
||||
useUnmountEffect(() => {
|
||||
scope.animations.forEach((animation) => animation.stop());
|
||||
});
|
||||
return [scope, animate];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates `LegacyAnimationControls`, which can be used to manually start, stop
|
||||
* and sequence animations on one or more components.
|
||||
*
|
||||
* The returned `LegacyAnimationControls` should be passed to the `animate` property
|
||||
* of the components you want to animate.
|
||||
*
|
||||
* These components can then be animated with the `start` method.
|
||||
*
|
||||
* ```jsx
|
||||
* import * as React from 'react'
|
||||
* import { motion, useAnimation } from 'framer-motion'
|
||||
*
|
||||
* export function MyComponent(props) {
|
||||
* const controls = useAnimation()
|
||||
*
|
||||
* controls.start({
|
||||
* x: 100,
|
||||
* transition: { duration: 0.5 },
|
||||
* })
|
||||
*
|
||||
* return <motion.div animate={controls} />
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns Animation controller with `start` and `stop` methods
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useAnimationControls() {
|
||||
const controls = index.useConstant(animationControls);
|
||||
index.useIsomorphicLayoutEffect(controls.mount, []);
|
||||
return controls;
|
||||
}
|
||||
const useAnimation = useAnimationControls;
|
||||
|
||||
function usePresenceData() {
|
||||
const context = React.useContext(index.PresenceContext);
|
||||
return context ? context.custom : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches an event listener directly to the provided DOM element.
|
||||
*
|
||||
* Bypassing React's event system can be desirable, for instance when attaching non-passive
|
||||
* event handlers.
|
||||
*
|
||||
* ```jsx
|
||||
* const ref = useRef(null)
|
||||
*
|
||||
* useDomEvent(ref, 'wheel', onWheel, { passive: false })
|
||||
*
|
||||
* return <div ref={ref} />
|
||||
* ```
|
||||
*
|
||||
* @param ref - React.RefObject that's been provided to the element you want to bind the listener to.
|
||||
* @param eventName - Name of the event you want listen for.
|
||||
* @param handler - Function to fire when receiving the event.
|
||||
* @param options - Options to pass to `Event.addEventListener`.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useDomEvent(ref, eventName, handler, options) {
|
||||
React.useEffect(() => {
|
||||
const element = ref.current;
|
||||
if (handler && element) {
|
||||
return motionDom.addDomEvent(element, eventName, handler, options);
|
||||
}
|
||||
}, [ref, eventName, handler, options]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Can manually trigger a drag gesture on one or more `drag`-enabled `motion` components.
|
||||
*
|
||||
* ```jsx
|
||||
* const dragControls = useDragControls()
|
||||
*
|
||||
* function startDrag(event) {
|
||||
* dragControls.start(event, { snapToCursor: true })
|
||||
* }
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* <div onPointerDown={startDrag} />
|
||||
* <motion.div drag="x" dragControls={dragControls} />
|
||||
* </>
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
class DragControls {
|
||||
constructor() {
|
||||
this.componentControls = new Set();
|
||||
}
|
||||
/**
|
||||
* Subscribe a component's internal `VisualElementDragControls` to the user-facing API.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
subscribe(controls) {
|
||||
this.componentControls.add(controls);
|
||||
return () => this.componentControls.delete(controls);
|
||||
}
|
||||
/**
|
||||
* Start a drag gesture on every `motion` component that has this set of drag controls
|
||||
* passed into it via the `dragControls` prop.
|
||||
*
|
||||
* ```jsx
|
||||
* dragControls.start(e, {
|
||||
* snapToCursor: true
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param event - PointerEvent
|
||||
* @param options - Options
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
start(event, options) {
|
||||
this.componentControls.forEach((controls) => {
|
||||
controls.start(event.nativeEvent || event, options);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Cancels a drag gesture.
|
||||
*
|
||||
* ```jsx
|
||||
* dragControls.cancel()
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
cancel() {
|
||||
this.componentControls.forEach((controls) => {
|
||||
controls.cancel();
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Stops a drag gesture.
|
||||
*
|
||||
* ```jsx
|
||||
* dragControls.stop()
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
stop() {
|
||||
this.componentControls.forEach((controls) => {
|
||||
controls.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
const createDragControls = () => new DragControls();
|
||||
/**
|
||||
* Usually, dragging is initiated by pressing down on a `motion` component with a `drag` prop
|
||||
* and moving it. For some use-cases, for instance clicking at an arbitrary point on a video scrubber, we
|
||||
* might want to initiate that dragging from a different component than the draggable one.
|
||||
*
|
||||
* By creating a `dragControls` using the `useDragControls` hook, we can pass this into
|
||||
* the draggable component's `dragControls` prop. It exposes a `start` method
|
||||
* that can start dragging from pointer events on other components.
|
||||
*
|
||||
* ```jsx
|
||||
* const dragControls = useDragControls()
|
||||
*
|
||||
* function startDrag(event) {
|
||||
* dragControls.start(event, { snapToCursor: true })
|
||||
* }
|
||||
*
|
||||
* return (
|
||||
* <>
|
||||
* <div onPointerDown={startDrag} />
|
||||
* <motion.div drag="x" dragControls={dragControls} />
|
||||
* </>
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useDragControls() {
|
||||
return index.useConstant(createDragControls);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a component is a `motion` component.
|
||||
*/
|
||||
function isMotionComponent(component) {
|
||||
return (component !== null &&
|
||||
typeof component === "object" &&
|
||||
index.motionComponentSymbol in component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwraps a `motion` component and returns either a string for `motion.div` or
|
||||
* the React component for `motion(Component)`.
|
||||
*
|
||||
* If the component is not a `motion` component it returns undefined.
|
||||
*/
|
||||
function unwrapMotionComponent(component) {
|
||||
if (isMotionComponent(component)) {
|
||||
return component[index.motionComponentSymbol];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function useInstantLayoutTransition() {
|
||||
return startTransition;
|
||||
}
|
||||
function startTransition(callback) {
|
||||
if (!motionDom.rootProjectionNode.current)
|
||||
return;
|
||||
motionDom.rootProjectionNode.current.isUpdating = false;
|
||||
motionDom.rootProjectionNode.current.blockUpdate();
|
||||
callback && callback();
|
||||
}
|
||||
|
||||
function useResetProjection() {
|
||||
const reset = React.useCallback(() => {
|
||||
const root = motionDom.rootProjectionNode.current;
|
||||
if (!root)
|
||||
return;
|
||||
root.resetTree();
|
||||
}, []);
|
||||
return reset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cycles through a series of visual properties. Can be used to toggle between or cycle through animations. It works similar to `useState` in React. It is provided an initial array of possible states, and returns an array of two arguments.
|
||||
*
|
||||
* An index value can be passed to the returned `cycle` function to cycle to a specific index.
|
||||
*
|
||||
* ```jsx
|
||||
* import * as React from "react"
|
||||
* import { motion, useCycle } from "framer-motion"
|
||||
*
|
||||
* export const MyComponent = () => {
|
||||
* const [x, cycleX] = useCycle(0, 50, 100)
|
||||
*
|
||||
* return (
|
||||
* <motion.div
|
||||
* animate={{ x: x }}
|
||||
* onTap={() => cycleX()}
|
||||
* />
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param items - items to cycle through
|
||||
* @returns [currentState, cycleState]
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function useCycle(...items) {
|
||||
const index = React.useRef(0);
|
||||
const [item, setItem] = React.useState(items[index.current]);
|
||||
const runCycle = React.useCallback((next) => {
|
||||
index.current =
|
||||
typeof next !== "number"
|
||||
? motionUtils.wrap(0, items.length, index.current + 1)
|
||||
: next;
|
||||
setItem(items[index.current]);
|
||||
},
|
||||
// The array will change on each call, but by putting items.length at
|
||||
// the front of this array, we guarantee the dependency comparison will match up
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[items.length, ...items]);
|
||||
return [item, runCycle];
|
||||
}
|
||||
|
||||
function useInView(ref, { root, margin, amount, once = false, initial = false, } = {}) {
|
||||
const [isInView, setInView] = React.useState(initial);
|
||||
React.useEffect(() => {
|
||||
if (!ref.current || (once && isInView))
|
||||
return;
|
||||
const onEnter = () => {
|
||||
setInView(true);
|
||||
return once ? undefined : () => setInView(false);
|
||||
};
|
||||
const options = {
|
||||
root: (root && root.current) || undefined,
|
||||
margin,
|
||||
amount,
|
||||
};
|
||||
return inView(ref.current, onEnter, options);
|
||||
}, [root, ref, margin, once, amount]);
|
||||
return isInView;
|
||||
}
|
||||
|
||||
function useInstantTransition() {
|
||||
const [forceUpdate, forcedRenderCount] = useForceUpdate();
|
||||
const startInstantLayoutTransition = useInstantLayoutTransition();
|
||||
const unlockOnFrameRef = React.useRef(-1);
|
||||
React.useEffect(() => {
|
||||
/**
|
||||
* Unblock after two animation frames, otherwise this will unblock too soon.
|
||||
*/
|
||||
motionDom.frame.postRender(() => motionDom.frame.postRender(() => {
|
||||
/**
|
||||
* If the callback has been called again after the effect
|
||||
* triggered this 2 frame delay, don't unblock animations. This
|
||||
* prevents the previous effect from unblocking the current
|
||||
* instant transition too soon. This becomes more likely when
|
||||
* used in conjunction with React.startTransition().
|
||||
*/
|
||||
if (forcedRenderCount !== unlockOnFrameRef.current)
|
||||
return;
|
||||
motionUtils.MotionGlobalConfig.instantAnimations = false;
|
||||
}));
|
||||
}, [forcedRenderCount]);
|
||||
return (callback) => {
|
||||
startInstantLayoutTransition(() => {
|
||||
motionUtils.MotionGlobalConfig.instantAnimations = true;
|
||||
forceUpdate();
|
||||
callback();
|
||||
unlockOnFrameRef.current = forcedRenderCount + 1;
|
||||
});
|
||||
};
|
||||
}
|
||||
function disableInstantTransitions() {
|
||||
motionUtils.MotionGlobalConfig.instantAnimations = false;
|
||||
}
|
||||
|
||||
function usePageInView() {
|
||||
const [isInView, setIsInView] = React.useState(true);
|
||||
React.useEffect(() => {
|
||||
const handleVisibilityChange = () => setIsInView(!document.hidden);
|
||||
if (document.hidden) {
|
||||
handleVisibilityChange();
|
||||
}
|
||||
document.addEventListener("visibilitychange", handleVisibilityChange);
|
||||
return () => {
|
||||
document.removeEventListener("visibilitychange", handleVisibilityChange);
|
||||
};
|
||||
}, []);
|
||||
return isInView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `transformPagePoint` function that accounts for SVG viewBox scaling.
|
||||
*
|
||||
* When dragging SVG elements inside an SVG with a viewBox that differs from
|
||||
* the rendered dimensions (e.g., `viewBox="0 0 100 100"` but rendered at 500x500 pixels),
|
||||
* pointer coordinates need to be transformed to match the SVG's coordinate system.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* function App() {
|
||||
* const svgRef = useRef<SVGSVGElement>(null)
|
||||
*
|
||||
* return (
|
||||
* <MotionConfig transformPagePoint={transformViewBoxPoint(svgRef)}>
|
||||
* <svg ref={svgRef} viewBox="0 0 100 100" width={500} height={500}>
|
||||
* <motion.rect drag width={10} height={10} />
|
||||
* </svg>
|
||||
* </MotionConfig>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param svgRef - A React ref to the SVG element
|
||||
* @returns A transformPagePoint function for use with MotionConfig
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function transformViewBoxPoint(svgRef) {
|
||||
return (point) => {
|
||||
const svg = svgRef.current;
|
||||
if (!svg) {
|
||||
return point;
|
||||
}
|
||||
// Get the viewBox attribute
|
||||
const viewBox = svg.viewBox?.baseVal;
|
||||
if (!viewBox || (viewBox.width === 0 && viewBox.height === 0)) {
|
||||
// No viewBox or empty viewBox - no transformation needed
|
||||
return point;
|
||||
}
|
||||
// Get the rendered dimensions of the SVG
|
||||
const bbox = svg.getBoundingClientRect();
|
||||
if (bbox.width === 0 || bbox.height === 0) {
|
||||
return point;
|
||||
}
|
||||
// Calculate scale factors
|
||||
const scaleX = viewBox.width / bbox.width;
|
||||
const scaleY = viewBox.height / bbox.height;
|
||||
// Get the SVG's position on the page
|
||||
const svgX = bbox.left + window.scrollX;
|
||||
const svgY = bbox.top + window.scrollY;
|
||||
// Transform the point:
|
||||
// 1. Calculate position relative to SVG
|
||||
// 2. Scale by viewBox/viewport ratio
|
||||
// 3. Add back the SVG position (but in SVG coordinates)
|
||||
return {
|
||||
x: (point.x - svgX) * scaleX + svgX,
|
||||
y: (point.y - svgY) * scaleY + svgY,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `transformPagePoint` function that corrects pointer coordinates
|
||||
* for a parent container with CSS transforms (rotation, scale, skew).
|
||||
*
|
||||
* When dragging elements inside a transformed parent, pointer coordinates
|
||||
* need to be transformed through the inverse of the parent's transform
|
||||
* so the drag offset is in local space.
|
||||
*
|
||||
* Works with both static and continuously animating transforms.
|
||||
*
|
||||
* @example
|
||||
* ```jsx
|
||||
* function App() {
|
||||
* const ref = useRef(null)
|
||||
*
|
||||
* return (
|
||||
* <motion.div ref={ref} style={{ rotate: 90 }}>
|
||||
* <MotionConfig transformPagePoint={correctParentTransform(ref)}>
|
||||
* <motion.div drag />
|
||||
* </MotionConfig>
|
||||
* </motion.div>
|
||||
* )
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param parentRef - A React ref to the transformed parent element
|
||||
* @returns A transformPagePoint function for use with MotionConfig
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
function correctParentTransform(parentRef) {
|
||||
return (point) => {
|
||||
const parent = parentRef.current;
|
||||
if (!parent)
|
||||
return point;
|
||||
const inv = getInverseMatrix(parent);
|
||||
if (!inv)
|
||||
return point;
|
||||
// Get center of rotation in page space
|
||||
const rect = parent.getBoundingClientRect();
|
||||
const cx = rect.left + window.scrollX + rect.width / 2;
|
||||
const cy = rect.top + window.scrollY + rect.height / 2;
|
||||
// Transform (point - center) through inverse, then add center back
|
||||
const dx = point.x - cx;
|
||||
const dy = point.y - cy;
|
||||
return {
|
||||
x: cx + inv.a * dx + inv.c * dy,
|
||||
y: cy + inv.b * dx + inv.d * dy,
|
||||
};
|
||||
};
|
||||
}
|
||||
function getInverseMatrix(element) {
|
||||
const { transform } = getComputedStyle(element);
|
||||
if (!transform || transform === "none")
|
||||
return null;
|
||||
const match = transform.match(/^matrix3d\((.*)\)$/u) ||
|
||||
transform.match(/^matrix\((.*)\)$/u);
|
||||
if (!match)
|
||||
return null;
|
||||
const v = match[1].split(",").map(Number);
|
||||
const is3d = transform.startsWith("matrix3d");
|
||||
const a = v[0], b = v[1];
|
||||
const c = is3d ? v[4] : v[2];
|
||||
const d = is3d ? v[5] : v[3];
|
||||
const det = a * d - b * c;
|
||||
if (Math.abs(det) < 1e-10)
|
||||
return null;
|
||||
return { a: d / det, b: -b / det, c: -c / det, d: a / det };
|
||||
}
|
||||
|
||||
const appearAnimationStore = new Map();
|
||||
const appearComplete = new Map();
|
||||
|
||||
const appearStoreId = (elementId, valueName) => {
|
||||
const key = motionDom.transformProps.has(valueName) ? "transform" : valueName;
|
||||
return `${elementId}: ${key}`;
|
||||
};
|
||||
|
||||
function handoffOptimizedAppearAnimation(elementId, valueName, frame) {
|
||||
const storeId = appearStoreId(elementId, valueName);
|
||||
const optimisedAnimation = appearAnimationStore.get(storeId);
|
||||
if (!optimisedAnimation) {
|
||||
return null;
|
||||
}
|
||||
const { animation, startTime } = optimisedAnimation;
|
||||
function cancelAnimation() {
|
||||
window.MotionCancelOptimisedAnimation?.(elementId, valueName, frame);
|
||||
}
|
||||
/**
|
||||
* We can cancel the animation once it's finished now that we've synced
|
||||
* with Motion.
|
||||
*
|
||||
* Prefer onfinish over finished as onfinish is backwards compatible with
|
||||
* older browsers.
|
||||
*/
|
||||
animation.onfinish = cancelAnimation;
|
||||
if (startTime === null || window.MotionHandoffIsComplete?.(elementId)) {
|
||||
/**
|
||||
* If the startTime is null, this animation is the Paint Ready detection animation
|
||||
* and we can cancel it immediately without handoff.
|
||||
*
|
||||
* Or if we've already handed off the animation then we're now interrupting it.
|
||||
* In which case we need to cancel it.
|
||||
*/
|
||||
cancelAnimation();
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return startTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single time to use across all animations to manually set startTime
|
||||
* and ensure they're all in sync.
|
||||
*/
|
||||
let startFrameTime;
|
||||
/**
|
||||
* A dummy animation to detect when Chrome is ready to start
|
||||
* painting the page and hold off from triggering the real animation
|
||||
* until then. We only need one animation to detect paint ready.
|
||||
*
|
||||
* https://bugs.chromium.org/p/chromium/issues/detail?id=1406850
|
||||
*/
|
||||
let readyAnimation;
|
||||
/**
|
||||
* Keep track of animations that were suspended vs cancelled so we
|
||||
* can easily resume them when we're done measuring layout.
|
||||
*/
|
||||
const suspendedAnimations = new Set();
|
||||
function resumeSuspendedAnimations() {
|
||||
suspendedAnimations.forEach((data) => {
|
||||
data.animation.play();
|
||||
data.animation.startTime = data.startTime;
|
||||
});
|
||||
suspendedAnimations.clear();
|
||||
}
|
||||
function startOptimizedAppearAnimation(element, name, keyframes, options, onReady) {
|
||||
// Prevent optimised appear animations if Motion has already started animating.
|
||||
if (window.MotionIsMounted) {
|
||||
return;
|
||||
}
|
||||
const id = element.dataset[motionDom.optimizedAppearDataId];
|
||||
if (!id)
|
||||
return;
|
||||
window.MotionHandoffAnimation = handoffOptimizedAppearAnimation;
|
||||
const storeId = appearStoreId(id, name);
|
||||
if (!readyAnimation) {
|
||||
readyAnimation = motionDom.startWaapiAnimation(element, name, [keyframes[0], keyframes[0]],
|
||||
/**
|
||||
* 10 secs is basically just a super-safe duration to give Chrome
|
||||
* long enough to get the animation ready.
|
||||
*/
|
||||
{ duration: 10000, ease: "linear" });
|
||||
appearAnimationStore.set(storeId, {
|
||||
animation: readyAnimation,
|
||||
startTime: null,
|
||||
});
|
||||
/**
|
||||
* If there's no readyAnimation then there's been no instantiation
|
||||
* of handoff animations.
|
||||
*/
|
||||
window.MotionHandoffAnimation = handoffOptimizedAppearAnimation;
|
||||
window.MotionHasOptimisedAnimation = (elementId, valueName) => {
|
||||
if (!elementId)
|
||||
return false;
|
||||
/**
|
||||
* Keep a map of elementIds that have started animating. We check
|
||||
* via ID instead of Element because of hydration errors and
|
||||
* pre-hydration checks. We also actively record IDs as they start
|
||||
* animating rather than simply checking for data-appear-id as
|
||||
* this attrbute might be present but not lead to an animation, for
|
||||
* instance if the element's appear animation is on a different
|
||||
* breakpoint.
|
||||
*/
|
||||
if (!valueName) {
|
||||
return appearComplete.has(elementId);
|
||||
}
|
||||
const animationId = appearStoreId(elementId, valueName);
|
||||
return Boolean(appearAnimationStore.get(animationId));
|
||||
};
|
||||
window.MotionHandoffMarkAsComplete = (elementId) => {
|
||||
if (appearComplete.has(elementId)) {
|
||||
appearComplete.set(elementId, true);
|
||||
}
|
||||
};
|
||||
window.MotionHandoffIsComplete = (elementId) => {
|
||||
return appearComplete.get(elementId) === true;
|
||||
};
|
||||
/**
|
||||
* We only need to cancel transform animations as
|
||||
* they're the ones that will interfere with the
|
||||
* layout animation measurements.
|
||||
*/
|
||||
window.MotionCancelOptimisedAnimation = (elementId, valueName, frame, canResume) => {
|
||||
const animationId = appearStoreId(elementId, valueName);
|
||||
const data = appearAnimationStore.get(animationId);
|
||||
if (!data)
|
||||
return;
|
||||
if (frame && canResume === undefined) {
|
||||
/**
|
||||
* Wait until the end of the subsequent frame to cancel the animation
|
||||
* to ensure we don't remove the animation before the main thread has
|
||||
* had a chance to resolve keyframes and render.
|
||||
*/
|
||||
frame.postRender(() => {
|
||||
frame.postRender(() => {
|
||||
data.animation.cancel();
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
data.animation.cancel();
|
||||
}
|
||||
if (frame && canResume) {
|
||||
suspendedAnimations.add(data);
|
||||
frame.render(resumeSuspendedAnimations);
|
||||
}
|
||||
else {
|
||||
appearAnimationStore.delete(animationId);
|
||||
/**
|
||||
* If there are no more animations left, we can remove the cancel function.
|
||||
* This will let us know when we can stop checking for conflicting layout animations.
|
||||
*/
|
||||
if (!appearAnimationStore.size) {
|
||||
window.MotionCancelOptimisedAnimation = undefined;
|
||||
}
|
||||
}
|
||||
};
|
||||
window.MotionCheckAppearSync = (visualElement, valueName, value) => {
|
||||
const appearId = motionDom.getOptimisedAppearId(visualElement);
|
||||
if (!appearId)
|
||||
return;
|
||||
const valueIsOptimised = window.MotionHasOptimisedAnimation?.(appearId, valueName);
|
||||
const externalAnimationValue = visualElement.props.values?.[valueName];
|
||||
if (!valueIsOptimised || !externalAnimationValue)
|
||||
return;
|
||||
const removeSyncCheck = value.on("change", (latestValue) => {
|
||||
if (externalAnimationValue.get() !== latestValue) {
|
||||
window.MotionCancelOptimisedAnimation?.(appearId, valueName);
|
||||
removeSyncCheck();
|
||||
}
|
||||
});
|
||||
return removeSyncCheck;
|
||||
};
|
||||
}
|
||||
const startAnimation = () => {
|
||||
readyAnimation.cancel();
|
||||
const appearAnimation = motionDom.startWaapiAnimation(element, name, keyframes, options);
|
||||
/**
|
||||
* Record the time of the first started animation. We call performance.now() once
|
||||
* here and once in handoff to ensure we're getting
|
||||
* close to a frame-locked time. This keeps all animations in sync.
|
||||
*/
|
||||
if (startFrameTime === undefined) {
|
||||
startFrameTime = performance.now();
|
||||
}
|
||||
appearAnimation.startTime = startFrameTime;
|
||||
appearAnimationStore.set(storeId, {
|
||||
animation: appearAnimation,
|
||||
startTime: startFrameTime,
|
||||
});
|
||||
if (onReady)
|
||||
onReady(appearAnimation);
|
||||
};
|
||||
appearComplete.set(id, false);
|
||||
if (readyAnimation.ready) {
|
||||
readyAnimation.ready.then(startAnimation).catch(motionUtils.noop);
|
||||
}
|
||||
else {
|
||||
startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
const createObject = () => ({});
|
||||
class StateVisualElement extends motionDom.VisualElement {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.measureInstanceViewportBox = motionDom.createBox;
|
||||
}
|
||||
build() { }
|
||||
resetTransform() { }
|
||||
restoreTransform() { }
|
||||
removeValueFromRenderState() { }
|
||||
renderInstance() { }
|
||||
scrapeMotionValuesFromProps() {
|
||||
return createObject();
|
||||
}
|
||||
getBaseTargetFromProps() {
|
||||
return undefined;
|
||||
}
|
||||
readValueFromInstance(_state, key, options) {
|
||||
return options.initialState[key] || 0;
|
||||
}
|
||||
sortInstanceNodePosition() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
const useVisualState = index.makeUseVisualState({
|
||||
scrapeMotionValuesFromProps: createObject,
|
||||
createRenderState: createObject,
|
||||
});
|
||||
/**
|
||||
* This is not an officially supported API and may be removed
|
||||
* on any version.
|
||||
*/
|
||||
function useAnimatedState(initialState) {
|
||||
const [animationState, setAnimationState] = React.useState(initialState);
|
||||
const visualState = useVisualState({}, false);
|
||||
const element = index.useConstant(() => {
|
||||
return new StateVisualElement({
|
||||
props: {
|
||||
onUpdate: (v) => {
|
||||
setAnimationState({ ...v });
|
||||
},
|
||||
},
|
||||
visualState,
|
||||
presenceContext: null,
|
||||
}, { initialState });
|
||||
});
|
||||
React.useLayoutEffect(() => {
|
||||
element.mount({});
|
||||
return () => element.unmount();
|
||||
}, [element]);
|
||||
const startAnimation = index.useConstant(() => (animationDefinition) => {
|
||||
return motionDom.animateVisualElement(element, animationDefinition);
|
||||
});
|
||||
return [animationState, startAnimation];
|
||||
}
|
||||
|
||||
let id = 0;
|
||||
const AnimateSharedLayout = ({ children }) => {
|
||||
React__namespace.useEffect(() => {
|
||||
motionUtils.invariant(false, "AnimateSharedLayout is deprecated: https://www.framer.com/docs/guide-upgrade/##shared-layout-animations");
|
||||
}, []);
|
||||
return (jsxRuntime.jsx(LayoutGroup, { id: index.useConstant(() => `asl-${id++}`), children: children }));
|
||||
};
|
||||
|
||||
// Keep things reasonable and avoid scale: Infinity. In practise we might need
|
||||
// to add another value, opacity, that could interpolate scaleX/Y [0,0.01] => [0,1]
|
||||
// to simply hide content at unreasonable scales.
|
||||
const maxScale = 100000;
|
||||
const invertScale = (scale) => scale > 0.001 ? 1 / scale : maxScale;
|
||||
let hasWarned = false;
|
||||
/**
|
||||
* Returns a `MotionValue` each for `scaleX` and `scaleY` that update with the inverse
|
||||
* of their respective parent scales.
|
||||
*
|
||||
* This is useful for undoing the distortion of content when scaling a parent component.
|
||||
*
|
||||
* By default, `useInvertedScale` will automatically fetch `scaleX` and `scaleY` from the nearest parent.
|
||||
* By passing other `MotionValue`s in as `useInvertedScale({ scaleX, scaleY })`, it will invert the output
|
||||
* of those instead.
|
||||
*
|
||||
* ```jsx
|
||||
* const MyComponent = () => {
|
||||
* const { scaleX, scaleY } = useInvertedScale()
|
||||
* return <motion.div style={{ scaleX, scaleY }} />
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
function useInvertedScale(scale) {
|
||||
let parentScaleX = useMotionValue(1);
|
||||
let parentScaleY = useMotionValue(1);
|
||||
const { visualElement } = React.useContext(index.MotionContext);
|
||||
motionUtils.invariant(!!(scale || visualElement), "If no scale values are provided, useInvertedScale must be used within a child of another motion component.");
|
||||
motionUtils.warning(hasWarned, "useInvertedScale is deprecated and will be removed in 3.0. Use the layout prop instead.");
|
||||
hasWarned = true;
|
||||
if (scale) {
|
||||
parentScaleX = scale.scaleX || parentScaleX;
|
||||
parentScaleY = scale.scaleY || parentScaleY;
|
||||
}
|
||||
else if (visualElement) {
|
||||
parentScaleX = visualElement.getValue("scaleX", 1);
|
||||
parentScaleY = visualElement.getValue("scaleY", 1);
|
||||
}
|
||||
const scaleX = useTransform(parentScaleX, invertScale);
|
||||
const scaleY = useTransform(parentScaleY, invertScale);
|
||||
return { scaleX, scaleY };
|
||||
}
|
||||
|
||||
exports.LayoutGroupContext = index.LayoutGroupContext;
|
||||
exports.MotionConfigContext = index.MotionConfigContext;
|
||||
exports.MotionContext = index.MotionContext;
|
||||
exports.PresenceContext = index.PresenceContext;
|
||||
exports.SwitchLayoutGroupContext = index.SwitchLayoutGroupContext;
|
||||
exports.filterProps = index.filterProps;
|
||||
exports.isBrowser = index.isBrowser;
|
||||
exports.isValidMotionProp = index.isValidMotionProp;
|
||||
exports.makeUseVisualState = index.makeUseVisualState;
|
||||
exports.useIsomorphicLayoutEffect = index.useIsomorphicLayoutEffect;
|
||||
Object.defineProperty(exports, "VisualElement", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.VisualElement; }
|
||||
});
|
||||
Object.defineProperty(exports, "addScaleCorrector", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.addScaleCorrector; }
|
||||
});
|
||||
Object.defineProperty(exports, "animateVisualElement", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.animateVisualElement; }
|
||||
});
|
||||
Object.defineProperty(exports, "arc", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.arc; }
|
||||
});
|
||||
Object.defineProperty(exports, "buildTransform", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.buildTransform; }
|
||||
});
|
||||
Object.defineProperty(exports, "calcLength", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.calcLength; }
|
||||
});
|
||||
Object.defineProperty(exports, "createBox", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.createBox; }
|
||||
});
|
||||
Object.defineProperty(exports, "delay", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.delay; }
|
||||
});
|
||||
Object.defineProperty(exports, "optimizedAppearDataAttribute", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.optimizedAppearDataAttribute; }
|
||||
});
|
||||
Object.defineProperty(exports, "resolveMotionValue", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.resolveMotionValue; }
|
||||
});
|
||||
Object.defineProperty(exports, "visualElementStore", {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom.visualElementStore; }
|
||||
});
|
||||
exports.addPointerEvent = featureBundle.addPointerEvent;
|
||||
exports.addPointerInfo = featureBundle.addPointerInfo;
|
||||
exports.animations = featureBundle.animations;
|
||||
exports.distance = featureBundle.distance;
|
||||
exports.distance2D = featureBundle.distance2D;
|
||||
exports.useIsPresent = featureBundle.useIsPresent;
|
||||
exports.usePresence = featureBundle.usePresence;
|
||||
Object.defineProperty(exports, "MotionGlobalConfig", {
|
||||
enumerable: true,
|
||||
get: function () { return motionUtils.MotionGlobalConfig; }
|
||||
});
|
||||
exports.AnimatePresence = AnimatePresence;
|
||||
exports.AnimateSharedLayout = AnimateSharedLayout;
|
||||
exports.DeprecatedLayoutGroupContext = DeprecatedLayoutGroupContext;
|
||||
exports.DragControls = DragControls;
|
||||
exports.LayoutGroup = LayoutGroup;
|
||||
exports.LazyMotion = LazyMotion;
|
||||
exports.MotionConfig = MotionConfig;
|
||||
exports.PopChild = PopChild;
|
||||
exports.PresenceChild = PresenceChild;
|
||||
exports.Reorder = namespace;
|
||||
exports.WillChangeMotionValue = WillChangeMotionValue;
|
||||
exports.animate = animate;
|
||||
exports.animateMini = animateMini;
|
||||
exports.animationControls = animationControls;
|
||||
exports.correctParentTransform = correctParentTransform;
|
||||
exports.createScopedAnimate = createScopedAnimate;
|
||||
exports.disableInstantTransitions = disableInstantTransitions;
|
||||
exports.domAnimation = domAnimation;
|
||||
exports.domMax = domMax;
|
||||
exports.domMin = domMin;
|
||||
exports.inView = inView;
|
||||
exports.isMotionComponent = isMotionComponent;
|
||||
exports.m = m;
|
||||
exports.motion = motion;
|
||||
exports.scroll = scroll;
|
||||
exports.scrollInfo = scrollInfo;
|
||||
exports.startOptimizedAppearAnimation = startOptimizedAppearAnimation;
|
||||
exports.transformViewBoxPoint = transformViewBoxPoint;
|
||||
exports.unwrapMotionComponent = unwrapMotionComponent;
|
||||
exports.useAnimate = useAnimate;
|
||||
exports.useAnimateMini = useAnimateMini;
|
||||
exports.useAnimation = useAnimation;
|
||||
exports.useAnimationControls = useAnimationControls;
|
||||
exports.useAnimationFrame = useAnimationFrame;
|
||||
exports.useComposedRefs = useComposedRefs;
|
||||
exports.useCycle = useCycle;
|
||||
exports.useDeprecatedAnimatedState = useAnimatedState;
|
||||
exports.useDeprecatedInvertedScale = useInvertedScale;
|
||||
exports.useDomEvent = useDomEvent;
|
||||
exports.useDragControls = useDragControls;
|
||||
exports.useElementScroll = useElementScroll;
|
||||
exports.useFollowValue = useFollowValue;
|
||||
exports.useForceUpdate = useForceUpdate;
|
||||
exports.useInView = useInView;
|
||||
exports.useInstantLayoutTransition = useInstantLayoutTransition;
|
||||
exports.useInstantTransition = useInstantTransition;
|
||||
exports.useMotionTemplate = useMotionTemplate;
|
||||
exports.useMotionValue = useMotionValue;
|
||||
exports.useMotionValueEvent = useMotionValueEvent;
|
||||
exports.usePageInView = usePageInView;
|
||||
exports.usePresenceData = usePresenceData;
|
||||
exports.useReducedMotion = useReducedMotion;
|
||||
exports.useReducedMotionConfig = useReducedMotionConfig;
|
||||
exports.useResetProjection = useResetProjection;
|
||||
exports.useScroll = useScroll;
|
||||
exports.useSpring = useSpring;
|
||||
exports.useTime = useTime;
|
||||
exports.useTransform = useTransform;
|
||||
exports.useUnmountEffect = useUnmountEffect;
|
||||
exports.useVelocity = useVelocity;
|
||||
exports.useViewportScroll = useViewportScroll;
|
||||
exports.useWillChange = useWillChange;
|
||||
Object.keys(motionDom).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
||||
enumerable: true,
|
||||
get: function () { return motionDom[k]; }
|
||||
});
|
||||
});
|
||||
Object.keys(motionUtils).forEach(function (k) {
|
||||
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
||||
enumerable: true,
|
||||
get: function () { return motionUtils[k]; }
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+403
@@ -0,0 +1,403 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var index = require('./index-6W16WHlG.js');
|
||||
require('react/jsx-runtime');
|
||||
require('motion-utils');
|
||||
require('react');
|
||||
require('motion-dom');
|
||||
|
||||
function createMinimalMotionComponent(Component, options) {
|
||||
return index.createMotionComponent(Component, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* HTML components
|
||||
*/
|
||||
const MotionA = /*@__PURE__*/ createMinimalMotionComponent("a");
|
||||
const MotionAbbr = /*@__PURE__*/ createMinimalMotionComponent("abbr");
|
||||
const MotionAddress =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("address");
|
||||
const MotionArea = /*@__PURE__*/ createMinimalMotionComponent("area");
|
||||
const MotionArticle =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("article");
|
||||
const MotionAside = /*@__PURE__*/ createMinimalMotionComponent("aside");
|
||||
const MotionAudio = /*@__PURE__*/ createMinimalMotionComponent("audio");
|
||||
const MotionB = /*@__PURE__*/ createMinimalMotionComponent("b");
|
||||
const MotionBase = /*@__PURE__*/ createMinimalMotionComponent("base");
|
||||
const MotionBdi = /*@__PURE__*/ createMinimalMotionComponent("bdi");
|
||||
const MotionBdo = /*@__PURE__*/ createMinimalMotionComponent("bdo");
|
||||
const MotionBig = /*@__PURE__*/ createMinimalMotionComponent("big");
|
||||
const MotionBlockquote =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("blockquote");
|
||||
const MotionBody = /*@__PURE__*/ createMinimalMotionComponent("body");
|
||||
const MotionButton = /*@__PURE__*/ createMinimalMotionComponent("button");
|
||||
const MotionCanvas = /*@__PURE__*/ createMinimalMotionComponent("canvas");
|
||||
const MotionCaption =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("caption");
|
||||
const MotionCite = /*@__PURE__*/ createMinimalMotionComponent("cite");
|
||||
const MotionCode = /*@__PURE__*/ createMinimalMotionComponent("code");
|
||||
const MotionCol = /*@__PURE__*/ createMinimalMotionComponent("col");
|
||||
const MotionColgroup =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("colgroup");
|
||||
const MotionData = /*@__PURE__*/ createMinimalMotionComponent("data");
|
||||
const MotionDatalist =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("datalist");
|
||||
const MotionDd = /*@__PURE__*/ createMinimalMotionComponent("dd");
|
||||
const MotionDel = /*@__PURE__*/ createMinimalMotionComponent("del");
|
||||
const MotionDetails =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("details");
|
||||
const MotionDfn = /*@__PURE__*/ createMinimalMotionComponent("dfn");
|
||||
const MotionDialog = /*@__PURE__*/ createMinimalMotionComponent("dialog");
|
||||
const MotionDiv = /*@__PURE__*/ createMinimalMotionComponent("div");
|
||||
const MotionDl = /*@__PURE__*/ createMinimalMotionComponent("dl");
|
||||
const MotionDt = /*@__PURE__*/ createMinimalMotionComponent("dt");
|
||||
const MotionEm = /*@__PURE__*/ createMinimalMotionComponent("em");
|
||||
const MotionEmbed = /*@__PURE__*/ createMinimalMotionComponent("embed");
|
||||
const MotionFieldset =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("fieldset");
|
||||
const MotionFigcaption =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("figcaption");
|
||||
const MotionFigure = /*@__PURE__*/ createMinimalMotionComponent("figure");
|
||||
const MotionFooter = /*@__PURE__*/ createMinimalMotionComponent("footer");
|
||||
const MotionForm = /*@__PURE__*/ createMinimalMotionComponent("form");
|
||||
const MotionH1 = /*@__PURE__*/ createMinimalMotionComponent("h1");
|
||||
const MotionH2 = /*@__PURE__*/ createMinimalMotionComponent("h2");
|
||||
const MotionH3 = /*@__PURE__*/ createMinimalMotionComponent("h3");
|
||||
const MotionH4 = /*@__PURE__*/ createMinimalMotionComponent("h4");
|
||||
const MotionH5 = /*@__PURE__*/ createMinimalMotionComponent("h5");
|
||||
const MotionH6 = /*@__PURE__*/ createMinimalMotionComponent("h6");
|
||||
const MotionHead = /*@__PURE__*/ createMinimalMotionComponent("head");
|
||||
const MotionHeader = /*@__PURE__*/ createMinimalMotionComponent("header");
|
||||
const MotionHgroup = /*@__PURE__*/ createMinimalMotionComponent("hgroup");
|
||||
const MotionHr = /*@__PURE__*/ createMinimalMotionComponent("hr");
|
||||
const MotionHtml = /*@__PURE__*/ createMinimalMotionComponent("html");
|
||||
const MotionI = /*@__PURE__*/ createMinimalMotionComponent("i");
|
||||
const MotionIframe = /*@__PURE__*/ createMinimalMotionComponent("iframe");
|
||||
const MotionImg = /*@__PURE__*/ createMinimalMotionComponent("img");
|
||||
const MotionInput = /*@__PURE__*/ createMinimalMotionComponent("input");
|
||||
const MotionIns = /*@__PURE__*/ createMinimalMotionComponent("ins");
|
||||
const MotionKbd = /*@__PURE__*/ createMinimalMotionComponent("kbd");
|
||||
const MotionKeygen = /*@__PURE__*/ createMinimalMotionComponent("keygen");
|
||||
const MotionLabel = /*@__PURE__*/ createMinimalMotionComponent("label");
|
||||
const MotionLegend = /*@__PURE__*/ createMinimalMotionComponent("legend");
|
||||
const MotionLi = /*@__PURE__*/ createMinimalMotionComponent("li");
|
||||
const MotionLink = /*@__PURE__*/ createMinimalMotionComponent("link");
|
||||
const MotionMain = /*@__PURE__*/ createMinimalMotionComponent("main");
|
||||
const MotionMap = /*@__PURE__*/ createMinimalMotionComponent("map");
|
||||
const MotionMark = /*@__PURE__*/ createMinimalMotionComponent("mark");
|
||||
const MotionMenu = /*@__PURE__*/ createMinimalMotionComponent("menu");
|
||||
const MotionMenuitem =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("menuitem");
|
||||
const MotionMeter = /*@__PURE__*/ createMinimalMotionComponent("meter");
|
||||
const MotionNav = /*@__PURE__*/ createMinimalMotionComponent("nav");
|
||||
const MotionObject = /*@__PURE__*/ createMinimalMotionComponent("object");
|
||||
const MotionOl = /*@__PURE__*/ createMinimalMotionComponent("ol");
|
||||
const MotionOptgroup =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("optgroup");
|
||||
const MotionOption = /*@__PURE__*/ createMinimalMotionComponent("option");
|
||||
const MotionOutput = /*@__PURE__*/ createMinimalMotionComponent("output");
|
||||
const MotionP = /*@__PURE__*/ createMinimalMotionComponent("p");
|
||||
const MotionParam = /*@__PURE__*/ createMinimalMotionComponent("param");
|
||||
const MotionPicture =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("picture");
|
||||
const MotionPre = /*@__PURE__*/ createMinimalMotionComponent("pre");
|
||||
const MotionProgress =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("progress");
|
||||
const MotionQ = /*@__PURE__*/ createMinimalMotionComponent("q");
|
||||
const MotionRp = /*@__PURE__*/ createMinimalMotionComponent("rp");
|
||||
const MotionRt = /*@__PURE__*/ createMinimalMotionComponent("rt");
|
||||
const MotionRuby = /*@__PURE__*/ createMinimalMotionComponent("ruby");
|
||||
const MotionS = /*@__PURE__*/ createMinimalMotionComponent("s");
|
||||
const MotionSamp = /*@__PURE__*/ createMinimalMotionComponent("samp");
|
||||
const MotionScript = /*@__PURE__*/ createMinimalMotionComponent("script");
|
||||
const MotionSection =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("section");
|
||||
const MotionSelect = /*@__PURE__*/ createMinimalMotionComponent("select");
|
||||
const MotionSmall = /*@__PURE__*/ createMinimalMotionComponent("small");
|
||||
const MotionSource = /*@__PURE__*/ createMinimalMotionComponent("source");
|
||||
const MotionSpan = /*@__PURE__*/ createMinimalMotionComponent("span");
|
||||
const MotionStrong = /*@__PURE__*/ createMinimalMotionComponent("strong");
|
||||
const MotionStyle = /*@__PURE__*/ createMinimalMotionComponent("style");
|
||||
const MotionSub = /*@__PURE__*/ createMinimalMotionComponent("sub");
|
||||
const MotionSummary =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("summary");
|
||||
const MotionSup = /*@__PURE__*/ createMinimalMotionComponent("sup");
|
||||
const MotionTable = /*@__PURE__*/ createMinimalMotionComponent("table");
|
||||
const MotionTbody = /*@__PURE__*/ createMinimalMotionComponent("tbody");
|
||||
const MotionTd = /*@__PURE__*/ createMinimalMotionComponent("td");
|
||||
const MotionTextarea =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("textarea");
|
||||
const MotionTfoot = /*@__PURE__*/ createMinimalMotionComponent("tfoot");
|
||||
const MotionTh = /*@__PURE__*/ createMinimalMotionComponent("th");
|
||||
const MotionThead = /*@__PURE__*/ createMinimalMotionComponent("thead");
|
||||
const MotionTime = /*@__PURE__*/ createMinimalMotionComponent("time");
|
||||
const MotionTitle = /*@__PURE__*/ createMinimalMotionComponent("title");
|
||||
const MotionTr = /*@__PURE__*/ createMinimalMotionComponent("tr");
|
||||
const MotionTrack = /*@__PURE__*/ createMinimalMotionComponent("track");
|
||||
const MotionU = /*@__PURE__*/ createMinimalMotionComponent("u");
|
||||
const MotionUl = /*@__PURE__*/ createMinimalMotionComponent("ul");
|
||||
const MotionVideo = /*@__PURE__*/ createMinimalMotionComponent("video");
|
||||
const MotionWbr = /*@__PURE__*/ createMinimalMotionComponent("wbr");
|
||||
const MotionWebview =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("webview");
|
||||
/**
|
||||
* SVG components
|
||||
*/
|
||||
const MotionAnimate =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("animate");
|
||||
const MotionCircle = /*@__PURE__*/ createMinimalMotionComponent("circle");
|
||||
const MotionDefs = /*@__PURE__*/ createMinimalMotionComponent("defs");
|
||||
const MotionDesc = /*@__PURE__*/ createMinimalMotionComponent("desc");
|
||||
const MotionEllipse =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("ellipse");
|
||||
const MotionG = /*@__PURE__*/ createMinimalMotionComponent("g");
|
||||
const MotionImage = /*@__PURE__*/ createMinimalMotionComponent("image");
|
||||
const MotionLine = /*@__PURE__*/ createMinimalMotionComponent("line");
|
||||
const MotionFilter = /*@__PURE__*/ createMinimalMotionComponent("filter");
|
||||
const MotionMarker = /*@__PURE__*/ createMinimalMotionComponent("marker");
|
||||
const MotionMask = /*@__PURE__*/ createMinimalMotionComponent("mask");
|
||||
const MotionMetadata =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("metadata");
|
||||
const MotionPath = /*@__PURE__*/ createMinimalMotionComponent("path");
|
||||
const MotionPattern =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("pattern");
|
||||
const MotionPolygon =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("polygon");
|
||||
const MotionPolyline =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("polyline");
|
||||
const MotionRect = /*@__PURE__*/ createMinimalMotionComponent("rect");
|
||||
const MotionStop = /*@__PURE__*/ createMinimalMotionComponent("stop");
|
||||
const MotionSvg = /*@__PURE__*/ createMinimalMotionComponent("svg");
|
||||
const MotionSymbol = /*@__PURE__*/ createMinimalMotionComponent("symbol");
|
||||
const MotionText = /*@__PURE__*/ createMinimalMotionComponent("text");
|
||||
const MotionTspan = /*@__PURE__*/ createMinimalMotionComponent("tspan");
|
||||
const MotionUse = /*@__PURE__*/ createMinimalMotionComponent("use");
|
||||
const MotionView = /*@__PURE__*/ createMinimalMotionComponent("view");
|
||||
const MotionClipPath =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("clipPath");
|
||||
const MotionFeBlend =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feBlend");
|
||||
const MotionFeColorMatrix =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feColorMatrix");
|
||||
const MotionFeComponentTransfer =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feComponentTransfer");
|
||||
const MotionFeComposite =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feComposite");
|
||||
const MotionFeConvolveMatrix =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feConvolveMatrix");
|
||||
const MotionFeDiffuseLighting =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feDiffuseLighting");
|
||||
const MotionFeDisplacementMap =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feDisplacementMap");
|
||||
const MotionFeDistantLight =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feDistantLight");
|
||||
const MotionFeDropShadow =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feDropShadow");
|
||||
const MotionFeFlood =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feFlood");
|
||||
const MotionFeFuncA =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feFuncA");
|
||||
const MotionFeFuncB =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feFuncB");
|
||||
const MotionFeFuncG =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feFuncG");
|
||||
const MotionFeFuncR =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feFuncR");
|
||||
const MotionFeGaussianBlur =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feGaussianBlur");
|
||||
const MotionFeImage =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feImage");
|
||||
const MotionFeMerge =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feMerge");
|
||||
const MotionFeMergeNode =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feMergeNode");
|
||||
const MotionFeMorphology =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feMorphology");
|
||||
const MotionFeOffset =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feOffset");
|
||||
const MotionFePointLight =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("fePointLight");
|
||||
const MotionFeSpecularLighting =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feSpecularLighting");
|
||||
const MotionFeSpotLight =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feSpotLight");
|
||||
const MotionFeTile = /*@__PURE__*/ createMinimalMotionComponent("feTile");
|
||||
const MotionFeTurbulence =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("feTurbulence");
|
||||
const MotionForeignObject =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("foreignObject");
|
||||
const MotionLinearGradient =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("linearGradient");
|
||||
const MotionRadialGradient =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("radialGradient");
|
||||
const MotionTextPath =
|
||||
/*@__PURE__*/ createMinimalMotionComponent("textPath");
|
||||
|
||||
exports.a = MotionA;
|
||||
exports.abbr = MotionAbbr;
|
||||
exports.address = MotionAddress;
|
||||
exports.animate = MotionAnimate;
|
||||
exports.area = MotionArea;
|
||||
exports.article = MotionArticle;
|
||||
exports.aside = MotionAside;
|
||||
exports.audio = MotionAudio;
|
||||
exports.b = MotionB;
|
||||
exports.base = MotionBase;
|
||||
exports.bdi = MotionBdi;
|
||||
exports.bdo = MotionBdo;
|
||||
exports.big = MotionBig;
|
||||
exports.blockquote = MotionBlockquote;
|
||||
exports.body = MotionBody;
|
||||
exports.button = MotionButton;
|
||||
exports.canvas = MotionCanvas;
|
||||
exports.caption = MotionCaption;
|
||||
exports.circle = MotionCircle;
|
||||
exports.cite = MotionCite;
|
||||
exports.clipPath = MotionClipPath;
|
||||
exports.code = MotionCode;
|
||||
exports.col = MotionCol;
|
||||
exports.colgroup = MotionColgroup;
|
||||
exports.create = createMinimalMotionComponent;
|
||||
exports.data = MotionData;
|
||||
exports.datalist = MotionDatalist;
|
||||
exports.dd = MotionDd;
|
||||
exports.defs = MotionDefs;
|
||||
exports.del = MotionDel;
|
||||
exports.desc = MotionDesc;
|
||||
exports.details = MotionDetails;
|
||||
exports.dfn = MotionDfn;
|
||||
exports.dialog = MotionDialog;
|
||||
exports.div = MotionDiv;
|
||||
exports.dl = MotionDl;
|
||||
exports.dt = MotionDt;
|
||||
exports.ellipse = MotionEllipse;
|
||||
exports.em = MotionEm;
|
||||
exports.embed = MotionEmbed;
|
||||
exports.feBlend = MotionFeBlend;
|
||||
exports.feColorMatrix = MotionFeColorMatrix;
|
||||
exports.feComponentTransfer = MotionFeComponentTransfer;
|
||||
exports.feComposite = MotionFeComposite;
|
||||
exports.feConvolveMatrix = MotionFeConvolveMatrix;
|
||||
exports.feDiffuseLighting = MotionFeDiffuseLighting;
|
||||
exports.feDisplacementMap = MotionFeDisplacementMap;
|
||||
exports.feDistantLight = MotionFeDistantLight;
|
||||
exports.feDropShadow = MotionFeDropShadow;
|
||||
exports.feFlood = MotionFeFlood;
|
||||
exports.feFuncA = MotionFeFuncA;
|
||||
exports.feFuncB = MotionFeFuncB;
|
||||
exports.feFuncG = MotionFeFuncG;
|
||||
exports.feFuncR = MotionFeFuncR;
|
||||
exports.feGaussianBlur = MotionFeGaussianBlur;
|
||||
exports.feImage = MotionFeImage;
|
||||
exports.feMerge = MotionFeMerge;
|
||||
exports.feMergeNode = MotionFeMergeNode;
|
||||
exports.feMorphology = MotionFeMorphology;
|
||||
exports.feOffset = MotionFeOffset;
|
||||
exports.fePointLight = MotionFePointLight;
|
||||
exports.feSpecularLighting = MotionFeSpecularLighting;
|
||||
exports.feSpotLight = MotionFeSpotLight;
|
||||
exports.feTile = MotionFeTile;
|
||||
exports.feTurbulence = MotionFeTurbulence;
|
||||
exports.fieldset = MotionFieldset;
|
||||
exports.figcaption = MotionFigcaption;
|
||||
exports.figure = MotionFigure;
|
||||
exports.filter = MotionFilter;
|
||||
exports.footer = MotionFooter;
|
||||
exports.foreignObject = MotionForeignObject;
|
||||
exports.form = MotionForm;
|
||||
exports.g = MotionG;
|
||||
exports.h1 = MotionH1;
|
||||
exports.h2 = MotionH2;
|
||||
exports.h3 = MotionH3;
|
||||
exports.h4 = MotionH4;
|
||||
exports.h5 = MotionH5;
|
||||
exports.h6 = MotionH6;
|
||||
exports.head = MotionHead;
|
||||
exports.header = MotionHeader;
|
||||
exports.hgroup = MotionHgroup;
|
||||
exports.hr = MotionHr;
|
||||
exports.html = MotionHtml;
|
||||
exports.i = MotionI;
|
||||
exports.iframe = MotionIframe;
|
||||
exports.image = MotionImage;
|
||||
exports.img = MotionImg;
|
||||
exports.input = MotionInput;
|
||||
exports.ins = MotionIns;
|
||||
exports.kbd = MotionKbd;
|
||||
exports.keygen = MotionKeygen;
|
||||
exports.label = MotionLabel;
|
||||
exports.legend = MotionLegend;
|
||||
exports.li = MotionLi;
|
||||
exports.line = MotionLine;
|
||||
exports.linearGradient = MotionLinearGradient;
|
||||
exports.link = MotionLink;
|
||||
exports.main = MotionMain;
|
||||
exports.map = MotionMap;
|
||||
exports.mark = MotionMark;
|
||||
exports.marker = MotionMarker;
|
||||
exports.mask = MotionMask;
|
||||
exports.menu = MotionMenu;
|
||||
exports.menuitem = MotionMenuitem;
|
||||
exports.metadata = MotionMetadata;
|
||||
exports.meter = MotionMeter;
|
||||
exports.nav = MotionNav;
|
||||
exports.object = MotionObject;
|
||||
exports.ol = MotionOl;
|
||||
exports.optgroup = MotionOptgroup;
|
||||
exports.option = MotionOption;
|
||||
exports.output = MotionOutput;
|
||||
exports.p = MotionP;
|
||||
exports.param = MotionParam;
|
||||
exports.path = MotionPath;
|
||||
exports.pattern = MotionPattern;
|
||||
exports.picture = MotionPicture;
|
||||
exports.polygon = MotionPolygon;
|
||||
exports.polyline = MotionPolyline;
|
||||
exports.pre = MotionPre;
|
||||
exports.progress = MotionProgress;
|
||||
exports.q = MotionQ;
|
||||
exports.radialGradient = MotionRadialGradient;
|
||||
exports.rect = MotionRect;
|
||||
exports.rp = MotionRp;
|
||||
exports.rt = MotionRt;
|
||||
exports.ruby = MotionRuby;
|
||||
exports.s = MotionS;
|
||||
exports.samp = MotionSamp;
|
||||
exports.script = MotionScript;
|
||||
exports.section = MotionSection;
|
||||
exports.select = MotionSelect;
|
||||
exports.small = MotionSmall;
|
||||
exports.source = MotionSource;
|
||||
exports.span = MotionSpan;
|
||||
exports.stop = MotionStop;
|
||||
exports.strong = MotionStrong;
|
||||
exports.style = MotionStyle;
|
||||
exports.sub = MotionSub;
|
||||
exports.summary = MotionSummary;
|
||||
exports.sup = MotionSup;
|
||||
exports.svg = MotionSvg;
|
||||
exports.symbol = MotionSymbol;
|
||||
exports.table = MotionTable;
|
||||
exports.tbody = MotionTbody;
|
||||
exports.td = MotionTd;
|
||||
exports.text = MotionText;
|
||||
exports.textPath = MotionTextPath;
|
||||
exports.textarea = MotionTextarea;
|
||||
exports.tfoot = MotionTfoot;
|
||||
exports.th = MotionTh;
|
||||
exports.thead = MotionThead;
|
||||
exports.time = MotionTime;
|
||||
exports.title = MotionTitle;
|
||||
exports.tr = MotionTr;
|
||||
exports.track = MotionTrack;
|
||||
exports.tspan = MotionTspan;
|
||||
exports.u = MotionU;
|
||||
exports.ul = MotionUl;
|
||||
exports.use = MotionUse;
|
||||
exports.video = MotionVideo;
|
||||
exports.view = MotionView;
|
||||
exports.wbr = MotionWbr;
|
||||
exports.webview = MotionWebview;
|
||||
//# sourceMappingURL=m.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+153
@@ -0,0 +1,153 @@
|
||||
'use strict';
|
||||
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
var react = require('react');
|
||||
var motionDom = require('motion-dom');
|
||||
var motionUtils = require('motion-utils');
|
||||
|
||||
/**
|
||||
* Creates a constant value over the lifecycle of a component.
|
||||
*
|
||||
* Even if `useMemo` is provided an empty array as its final argument, it doesn't offer
|
||||
* a guarantee that it won't re-run for performance reasons later on. By using `useConstant`
|
||||
* you can ensure that initialisers don't execute twice or more.
|
||||
*/
|
||||
function useConstant(init) {
|
||||
const ref = react.useRef(null);
|
||||
if (ref.current === null) {
|
||||
ref.current = init();
|
||||
}
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
function useUnmountEffect(callback) {
|
||||
return react.useEffect(() => () => callback(), []);
|
||||
}
|
||||
|
||||
function animateElements(elementOrSelector, keyframes, options, scope) {
|
||||
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
|
||||
if (elementOrSelector == null) {
|
||||
return [];
|
||||
}
|
||||
const elements = motionDom.resolveElements(elementOrSelector, scope);
|
||||
const numElements = elements.length;
|
||||
motionUtils.invariant(Boolean(numElements), "No valid elements provided.", "no-valid-elements");
|
||||
/**
|
||||
* WAAPI doesn't support interrupting animations.
|
||||
*
|
||||
* Therefore, starting animations requires a three-step process:
|
||||
* 1. Stop existing animations (write styles to DOM)
|
||||
* 2. Resolve keyframes (read styles from DOM)
|
||||
* 3. Create new animations (write styles to DOM)
|
||||
*
|
||||
* The hybrid `animate()` function uses AsyncAnimation to resolve
|
||||
* keyframes before creating new animations, which removes style
|
||||
* thrashing. Here, we have much stricter filesize constraints.
|
||||
* Therefore we do this in a synchronous way that ensures that
|
||||
* at least within `animate()` calls there is no style thrashing.
|
||||
*
|
||||
* In the motion-native-animate-mini-interrupt benchmark this
|
||||
* was 80% faster than a single loop.
|
||||
*/
|
||||
const animationDefinitions = [];
|
||||
/**
|
||||
* Step 1: Build options and stop existing animations (write)
|
||||
*/
|
||||
for (let i = 0; i < numElements; i++) {
|
||||
const element = elements[i];
|
||||
const elementTransition = { ...options };
|
||||
/**
|
||||
* Resolve stagger function if provided.
|
||||
*/
|
||||
if (typeof elementTransition.delay === "function") {
|
||||
elementTransition.delay = elementTransition.delay(i, numElements);
|
||||
}
|
||||
for (const valueName in keyframes) {
|
||||
let valueKeyframes = keyframes[valueName];
|
||||
if (!Array.isArray(valueKeyframes)) {
|
||||
valueKeyframes = [valueKeyframes];
|
||||
}
|
||||
const valueOptions = {
|
||||
...motionDom.getValueTransition(elementTransition, valueName),
|
||||
};
|
||||
valueOptions.duration && (valueOptions.duration = motionUtils.secondsToMilliseconds(valueOptions.duration));
|
||||
valueOptions.delay && (valueOptions.delay = motionUtils.secondsToMilliseconds(valueOptions.delay));
|
||||
/**
|
||||
* If there's an existing animation playing on this element then stop it
|
||||
* before creating a new one.
|
||||
*/
|
||||
const map = motionDom.getAnimationMap(element);
|
||||
const key = motionDom.animationMapKey(valueName, valueOptions.pseudoElement || "");
|
||||
const currentAnimation = map.get(key);
|
||||
currentAnimation && currentAnimation.stop();
|
||||
animationDefinitions.push({
|
||||
map,
|
||||
key,
|
||||
unresolvedKeyframes: valueKeyframes,
|
||||
options: {
|
||||
...valueOptions,
|
||||
element,
|
||||
name: valueName,
|
||||
allowFlatten: !elementTransition.type && !elementTransition.ease,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Step 2: Resolve keyframes (read)
|
||||
*/
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { unresolvedKeyframes, options: animationOptions } = animationDefinitions[i];
|
||||
const { element, name, pseudoElement } = animationOptions;
|
||||
if (!pseudoElement && unresolvedKeyframes[0] === null) {
|
||||
unresolvedKeyframes[0] = motionDom.getComputedStyle(element, name);
|
||||
}
|
||||
motionDom.fillWildcards(unresolvedKeyframes);
|
||||
motionDom.applyPxDefaults(unresolvedKeyframes, name);
|
||||
/**
|
||||
* If we only have one keyframe, explicitly read the initial keyframe
|
||||
* from the computed style. This is to ensure consistency with WAAPI behaviour
|
||||
* for restarting animations, for instance .play() after finish, when it
|
||||
* has one vs two keyframes.
|
||||
*/
|
||||
if (!pseudoElement && unresolvedKeyframes.length < 2) {
|
||||
unresolvedKeyframes.unshift(motionDom.getComputedStyle(element, name));
|
||||
}
|
||||
animationOptions.keyframes = unresolvedKeyframes;
|
||||
}
|
||||
/**
|
||||
* Step 3: Create new animations (write)
|
||||
*/
|
||||
const animations = [];
|
||||
for (let i = 0; i < animationDefinitions.length; i++) {
|
||||
const { map, key, options: animationOptions } = animationDefinitions[i];
|
||||
const animation = new motionDom.NativeAnimation(animationOptions);
|
||||
map.set(key, animation);
|
||||
animation.finished.finally(() => map.delete(key));
|
||||
animations.push(animation);
|
||||
}
|
||||
return animations;
|
||||
}
|
||||
|
||||
const createScopedWaapiAnimate = (scope) => {
|
||||
function scopedAnimate(elementOrSelector, keyframes, options) {
|
||||
return new motionDom.GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
|
||||
}
|
||||
return scopedAnimate;
|
||||
};
|
||||
|
||||
function useAnimateMini() {
|
||||
const scope = useConstant(() => ({
|
||||
current: null, // Will be hydrated by React
|
||||
animations: [],
|
||||
}));
|
||||
const animate = useConstant(() => createScopedWaapiAnimate(scope));
|
||||
useUnmountEffect(() => {
|
||||
scope.animations.forEach((animation) => animation.stop());
|
||||
});
|
||||
return [scope, animate];
|
||||
}
|
||||
|
||||
exports.useAnimate = useAnimateMini;
|
||||
//# sourceMappingURL=mini.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user