UNPKG

@promokit/react

Version:

React components for PromoKit marketing infrastructure

1,373 lines (1,282 loc) 456 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var React = require('react'); 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); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var extendStatics = function(d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { if (typeof b !== "function" && b !== null) throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __awaiter$1(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator$1(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var WaitlistAPI = /** @class */ (function () { function WaitlistAPI(config, client) { this.config = config; this.client = client; } WaitlistAPI.prototype.create = function (data) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request('/waitlist/create', { method: 'POST', body: JSON.stringify(data), })]; }); }); }; WaitlistAPI.prototype.getStats = function (projectId) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/waitlist/".concat(projectId, "/stats"))]; }); }); }; WaitlistAPI.prototype.export = function (projectId) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/waitlist/".concat(projectId, "/export"))]; }); }); }; WaitlistAPI.prototype.remove = function (projectId, email) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/waitlist/".concat(projectId, "/remove"), { method: 'DELETE', body: JSON.stringify({ email: email }), })]; }); }); }; return WaitlistAPI; }()); var TestimonialAPI = /** @class */ (function () { function TestimonialAPI(config, client) { this.config = config; this.client = client; } TestimonialAPI.prototype.submit = function (data) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request('/testimonial/submit', { method: 'POST', body: JSON.stringify(data), })]; }); }); }; TestimonialAPI.prototype.get = function (productId_1) { return __awaiter$1(this, arguments, void 0, function (productId, options) { var params; if (options === void 0) { options = {}; } return __generator$1(this, function (_a) { params = new URLSearchParams(); if (options.limit) params.append('limit', options.limit.toString()); if (options.offset) params.append('offset', options.offset.toString()); if (options.status) params.append('status', options.status); return [2 /*return*/, this.client.request("/testimonial/".concat(productId, "?").concat(params.toString()))]; }); }); }; TestimonialAPI.prototype.approve = function (testimonialId) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/testimonial/".concat(testimonialId, "/approve"), { method: 'POST', })]; }); }); }; TestimonialAPI.prototype.reject = function (testimonialId) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/testimonial/".concat(testimonialId, "/reject"), { method: 'POST', })]; }); }); }; return TestimonialAPI; }()); var ChangelogAPI = /** @class */ (function () { function ChangelogAPI(config, client) { this.config = config; this.client = client; } ChangelogAPI.prototype.create = function (data) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request('/changelog/create', { method: 'POST', body: JSON.stringify(data), })]; }); }); }; ChangelogAPI.prototype.get = function (projectId_1) { return __awaiter$1(this, arguments, void 0, function (projectId, options) { var params; if (options === void 0) { options = {}; } return __generator$1(this, function (_a) { params = new URLSearchParams(); if (options.limit) params.append('limit', options.limit.toString()); if (options.offset) params.append('offset', options.offset.toString()); return [2 /*return*/, this.client.request("/changelog/".concat(projectId, "?").concat(params.toString()))]; }); }); }; ChangelogAPI.prototype.subscribe = function (projectId, email) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/changelog/".concat(projectId, "/subscribe"), { method: 'POST', body: JSON.stringify({ email: email }), })]; }); }); }; ChangelogAPI.prototype.update = function (entryId, data) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/changelog/".concat(entryId), { method: 'PUT', body: JSON.stringify(data), })]; }); }); }; ChangelogAPI.prototype.delete = function (entryId) { return __awaiter$1(this, void 0, void 0, function () { return __generator$1(this, function (_a) { return [2 /*return*/, this.client.request("/changelog/".concat(entryId), { method: 'DELETE', })]; }); }); }; return ChangelogAPI; }()); var PromoError = /** @class */ (function (_super) { __extends(PromoError, _super); function PromoError(error) { var _this = _super.call(this, error.message) || this; _this.name = 'PromoError'; _this.status = error.status; _this.statusText = error.statusText; _this.details = error.details; _this.endpoint = error.endpoint; _this.timestamp = error.timestamp; return _this; } Object.defineProperty(PromoError.prototype, "isNetworkError", { get: function () { return this.status === 0 || this.status >= 500; }, enumerable: false, configurable: true }); Object.defineProperty(PromoError.prototype, "isClientError", { get: function () { return this.status >= 400 && this.status < 500; }, enumerable: false, configurable: true }); Object.defineProperty(PromoError.prototype, "isConflictError", { get: function () { return this.status === 409; }, enumerable: false, configurable: true }); Object.defineProperty(PromoError.prototype, "isUnauthorizedError", { get: function () { return this.status === 401; }, enumerable: false, configurable: true }); Object.defineProperty(PromoError.prototype, "isForbiddenError", { get: function () { return this.status === 403; }, enumerable: false, configurable: true }); Object.defineProperty(PromoError.prototype, "isNotFoundError", { get: function () { return this.status === 404; }, enumerable: false, configurable: true }); Object.defineProperty(PromoError.prototype, "isRateLimitError", { get: function () { return this.status === 429; }, enumerable: false, configurable: true }); PromoError.prototype.getUserFriendlyMessage = function () { var _a; switch (this.status) { case 409: if ((_a = this.endpoint) === null || _a === void 0 ? void 0 : _a.includes('/waitlist/create')) { return 'This email is already on the waitlist. Check your inbox for your referral link!'; } return 'This resource already exists.'; case 401: return 'Authentication failed. Please check your API key.'; case 403: return 'You don\'t have permission to access this resource.'; case 404: return 'The requested resource was not found.'; case 429: return 'Too many requests. Please try again in a few moments.'; case 0: return 'Network error. Please check your internet connection.'; default: if (this.isNetworkError) { return 'Server error. Please try again later.'; } return this.message || 'An unexpected error occurred.'; } }; return PromoError; }(Error)); var PromoClient = /** @class */ (function () { function PromoClient(config) { this.config = { apiKey: config.apiKey, }; this.waitlist = new WaitlistAPI(this.config, this); this.testimonial = new TestimonialAPI(this.config, this); this.changelog = new ChangelogAPI(this.config, this); } PromoClient.prototype.request = function (endpoint_1) { return __awaiter$1(this, arguments, void 0, function (endpoint, options) { var url, response, errorDetails, errorMessage, contentType, parseError_1, apiError, error_1, apiError; if (options === void 0) { options = {}; } return __generator$1(this, function (_a) { switch (_a.label) { case 0: url = "https://promokit.pro/api".concat(endpoint); _a.label = 1; case 1: _a.trys.push([1, 11, , 12]); return [4 /*yield*/, fetch(url, __assign(__assign({}, options), { headers: __assign({ 'Authorization': "Bearer ".concat(this.config.apiKey), 'Content-Type': 'application/json' }, options.headers) }))]; case 2: response = _a.sent(); if (!!response.ok) return [3 /*break*/, 10]; errorDetails = void 0; errorMessage = response.statusText || 'API request failed'; _a.label = 3; case 3: _a.trys.push([3, 8, , 9]); contentType = response.headers.get('content-type'); if (!(contentType && contentType.includes('application/json'))) return [3 /*break*/, 5]; return [4 /*yield*/, response.json()]; case 4: errorDetails = _a.sent(); errorMessage = errorDetails.message || errorDetails.error || errorMessage; return [3 /*break*/, 7]; case 5: return [4 /*yield*/, response.text()]; case 6: errorDetails = _a.sent(); _a.label = 7; case 7: return [3 /*break*/, 9]; case 8: parseError_1 = _a.sent(); // If we can't parse the error response, use the status text errorDetails = "Failed to parse error response: ".concat(parseError_1); return [3 /*break*/, 9]; case 9: apiError = { status: response.status, statusText: response.statusText, message: errorMessage, details: errorDetails, endpoint: endpoint, timestamp: new Date().toISOString(), }; throw new PromoError(apiError); case 10: return [2 /*return*/, response.json()]; case 11: error_1 = _a.sent(); // Handle network errors (fetch throws) if (error_1 instanceof PromoError) { throw error_1; } apiError = { status: 0, statusText: 'Network Error', message: error_1 instanceof Error ? error_1.message : 'Network request failed', details: error_1, endpoint: endpoint, timestamp: new Date().toISOString(), }; throw new PromoError(apiError); case 12: return [2 /*return*/]; } }); }); }; return PromoClient; }()); var PromoContext = React.createContext(null); function PromoProvider(_a) { var config = _a.config, children = _a.children; var promoClient = React.useMemo(function () { return new PromoClient(config); }, [config]); return (jsxRuntime.jsx(PromoContext.Provider, { value: promoClient, children: children })); } function usePromo() { var context = React.useContext(PromoContext); if (!context) { throw new Error('usePromo must be used within a PromoProvider'); } return context; } /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } function __generator(thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; 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); function addUniqueItem(arr, item) { if (arr.indexOf(item) === -1) arr.push(item); } function removeItem(arr, item) { const index = arr.indexOf(item); if (index > -1) arr.splice(index, 1); } const clamp = (min, max, v) => { if (v > max) return max; if (v < min) return min; return v; }; let warning = () => { }; let invariant = () => { }; if (process.env.NODE_ENV !== "production") { warning = (check, message) => { if (!check && typeof console !== "undefined") { console.warn(message); } }; invariant = (check, message) => { if (!check) { throw new Error(message); } }; } const MotionGlobalConfig = {}; /** * Check if value is a numerical string, ie a string that is purely a number eg "100" or "-100.1" */ const isNumericalString = (v) => /^-?(?:\d+(?:\.\d+)?|\.\d+)$/u.test(v); function isObject(value) { return typeof value === "object" && value !== null; } /** * Check if the value is a zero value string like "0px" or "0%" */ const isZeroValueString = (v) => /^0[^.\s]+$/u.test(v); /*#__NO_SIDE_EFFECTS__*/ function memo(callback) { let result; return () => { if (result === undefined) result = callback(); return result; }; } /*#__NO_SIDE_EFFECTS__*/ const noop = (any) => any; /** * Pipe * Compose other transformers to run linearily * pipe(min(20), max(40)) * @param {...functions} transformers * @return {function} */ const combineFunctions = (a, b) => (v) => b(a(v)); const pipe = (...transformers) => transformers.reduce(combineFunctions); /* Progress within given range Given a lower limit and an upper limit, we return the progress (expressed as a number 0-1) represented by the given value, and limit that progress to within 0-1. @param [number]: Lower limit @param [number]: Upper limit @param [number]: Value to find progress within given range @return [number]: Progress of value within range as expressed 0-1 */ /*#__NO_SIDE_EFFECTS__*/ const progress = (from, to, value) => { const toFromDifference = to - from; return toFromDifference === 0 ? 1 : (value - from) / toFromDifference; }; class SubscriptionManager { constructor() { this.subscriptions = []; } add(handler) { addUniqueItem(this.subscriptions, handler); return () => removeItem(this.subscriptions, handler); } notify(a, b, c) { const numSubscriptions = this.subscriptions.length; if (!numSubscriptions) return; if (numSubscriptions === 1) { /** * If there's only a single handler we can just call it without invoking a loop. */ this.subscriptions[0](a, b, c); } else { for (let i = 0; i < numSubscriptions; i++) { /** * Check whether the handler exists before firing as it's possible * the subscriptions were modified during this loop running. */ const handler = this.subscriptions[i]; handler && handler(a, b, c); } } } getSize() { return this.subscriptions.length; } clear() { this.subscriptions.length = 0; } } /** * Converts seconds to milliseconds * * @param seconds - Time in seconds. * @return milliseconds - Converted time in milliseconds. */ /*#__NO_SIDE_EFFECTS__*/ const secondsToMilliseconds = (seconds) => seconds * 1000; /*#__NO_SIDE_EFFECTS__*/ const millisecondsToSeconds = (milliseconds) => milliseconds / 1000; /* Convert velocity into velocity per second @param [number]: Unit per frame @param [number]: Frame duration in ms */ function velocityPerSecond(velocity, frameDuration) { return frameDuration ? velocity * (1000 / frameDuration) : 0; } const warned = new Set(); function warnOnce(condition, message, element) { if (condition || warned.has(message)) return; console.warn(message); warned.add(message); } /* Bezier function generator This has been modified from Gaëtan Renaudeau's BezierEasing https://github.com/gre/bezier-easing/blob/master/src/index.js https://github.com/gre/bezier-easing/blob/master/LICENSE I've removed the newtonRaphsonIterate algo because in benchmarking it wasn't noticeably faster than binarySubdivision, indeed removing it usually improved times, depending on the curve. I also removed the lookup table, as for the added bundle size and loop we're only cutting ~4 or so subdivision iterations. I bumped the max iterations up to 12 to compensate and this still tended to be faster for no perceivable loss in accuracy. Usage const easeOut = cubicBezier(.17,.67,.83,.67); const x = easeOut(0.5); // returns 0.627... */ // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2. const calcBezier = (t, a1, a2) => (((1.0 - 3.0 * a2 + 3.0 * a1) * t + (3.0 * a2 - 6.0 * a1)) * t + 3.0 * a1) * t; const subdivisionPrecision = 0.0000001; const subdivisionMaxIterations = 12; function binarySubdivide(x, lowerBound, upperBound, mX1, mX2) { let currentX; let currentT; let i = 0; do { currentT = lowerBound + (upperBound - lowerBound) / 2.0; currentX = calcBezier(currentT, mX1, mX2) - x; if (currentX > 0.0) { upperBound = currentT; } else { lowerBound = currentT; } } while (Math.abs(currentX) > subdivisionPrecision && ++i < subdivisionMaxIterations); return currentT; } function cubicBezier(mX1, mY1, mX2, mY2) { // If this is a linear gradient, return linear easing if (mX1 === mY1 && mX2 === mY2) return noop; const getTForX = (aX) => binarySubdivide(aX, 0, 1, mX1, mX2); // If animation is at start/end, return t without easing return (t) => t === 0 || t === 1 ? t : calcBezier(getTForX(t), mY1, mY2); } // Accepts an easing function and returns a new one that outputs mirrored values for // the second half of the animation. Turns easeIn into easeInOut. const mirrorEasing = (easing) => (p) => p <= 0.5 ? easing(2 * p) / 2 : (2 - easing(2 * (1 - p))) / 2; // Accepts an easing function and returns a new one that outputs reversed values. // Turns easeIn into easeOut. const reverseEasing = (easing) => (p) => 1 - easing(1 - p); const backOut = /*@__PURE__*/ cubicBezier(0.33, 1.53, 0.69, 0.99); const backIn = /*@__PURE__*/ reverseEasing(backOut); const backInOut = /*@__PURE__*/ mirrorEasing(backIn); const anticipate = (p) => (p *= 2) < 1 ? 0.5 * backIn(p) : 0.5 * (2 - Math.pow(2, -10 * (p - 1))); const circIn = (p) => 1 - Math.sin(Math.acos(p)); const circOut = reverseEasing(circIn); const circInOut = mirrorEasing(circIn); const easeIn = /*@__PURE__*/ cubicBezier(0.42, 0, 1, 1); const easeOut = /*@__PURE__*/ cubicBezier(0, 0, 0.58, 1); const easeInOut = /*@__PURE__*/ cubicBezier(0.42, 0, 0.58, 1); const isEasingArray = (ease) => { return Array.isArray(ease) && typeof ease[0] !== "number"; }; const isBezierDefinition = (easing) => Array.isArray(easing) && typeof easing[0] === "number"; const easingLookup = { linear: noop, easeIn, easeInOut, easeOut, circIn, circInOut, circOut, backIn, backInOut, backOut, anticipate, }; const isValidEasing = (easing) => { return typeof easing === "string"; }; const easingDefinitionToFunction = (definition) => { if (isBezierDefinition(definition)) { // If cubic bezier definition, create bezier curve invariant(definition.length === 4, `Cubic bezier arrays must contain four numerical values.`); const [x1, y1, x2, y2] = definition; return cubicBezier(x1, y1, x2, y2); } else if (isValidEasing(definition)) { // Else lookup from table invariant(easingLookup[definition] !== undefined, `Invalid easing type '${definition}'`); return easingLookup[definition]; } return definition; }; const stepsOrder = [ "setup", // Compute "read", // Read "resolveKeyframes", // Write/Read/Write/Read "preUpdate", // Compute "update", // Compute "preRender", // Compute "render", // Write "postRender", // Compute ]; const statsBuffer = { value: null}; function createRenderStep(runNextFrame, stepName) { /** * We create and reuse two queues, one to queue jobs for the current frame * and one for the next. We reuse to avoid triggering GC after x frames. */ let thisFrame = new Set(); let nextFrame = new Set(); /** * Track whether we're currently processing jobs in this step. This way * we can decide whether to schedule new jobs for this frame or next. */ let isProcessing = false; let flushNextFrame = false; /** * A set of processes which were marked keepAlive when scheduled. */ const toKeepAlive = new WeakSet(); let latestFrameData = { delta: 0.0, timestamp: 0.0, isProcessing: false, }; let numCalls = 0; function triggerCallback(callback) { if (toKeepAlive.has(callback)) { step.schedule(callback); runNextFrame(); } numCalls++; callback(latestFrameData); } const step = { /** * Schedule a process to run on the next frame. */ schedule: (callback, keepAlive = false, immediate = false) => { const addToCurrentFrame = immediate && isProcessing; const queue = addToCurrentFrame ? thisFrame : nextFrame; if (keepAlive) toKeepAlive.add(callback); if (!queue.has(callback)) queue.add(callback); return callback; }, /** * Cancel the provided callback from running on the next frame. */ cancel: (callback) => { nextFrame.delete(callback); toKeepAlive.delete(callback); }, /** * Execute all schedule callbacks. */ process: (frameData) => { latestFrameData = frameData; /** * If we're already processing we've probably been triggered by a flushSync * inside an existing process. Instead of executing, mark flushNextFrame * as true and ensure we flush the following frame at the end of this one. */ if (isProcessing) { flushNextFrame = true; return; } isProcessing = true; [thisFrame, nextFrame] = [nextFrame, thisFrame]; // Execute this frame thisFrame.forEach(triggerCallback); /** * If we're recording stats then */ if (stepName && statsBuffer.value) { statsBuffer.value.frameloop[stepName].push(numCalls); } numCalls = 0; // Clear the frame so no callbacks remain. This is to avoid // memory leaks should this render step not run for a while. thisFrame.clear(); isProcessing = false; if (flushNextFrame) { flushNextFrame = false; step.process(frameData); } }, }; return step; } const maxElapsed = 40; function createRenderBatcher(scheduleNextBatch, allowKeepAlive) { let runNextFrame = false; let useDefaultElapsed = true; const state = { delta: 0.0, timestamp: 0.0, isProcessing: false, }; const flagRunNextFrame = () => (runNextFrame = true); const steps = stepsOrder.reduce((acc, key) => { acc[key] = createRenderStep(flagRunNextFrame, allowKeepAlive ? key : undefined); return acc; }, {}); const { setup, read, resolveKeyframes, preUpdate, update, preRender, render, postRender, } = steps; const processBatch = () => { const timestamp = MotionGlobalConfig.useManualTiming ? state.timestamp : performance.now(); runNextFrame = false; if (!MotionGlobalConfig.useManualTiming) { state.delta = useDefaultElapsed ? 1000 / 60 : Math.max(Math.min(timestamp - state.timestamp, maxElapsed), 1); } state.timestamp = timestamp; state.isProcessing = true; // Unrolled render loop for better per-frame performance setup.process(state); read.process(state); resolveKeyframes.process(state); preUpdate.process(state); update.process(state); preRender.process(state); render.process(state); postRender.process(state); state.isProcessing = false; if (runNextFrame && allowKeepAlive) { useDefaultElapsed = false; scheduleNextBatch(processBatch); } }; const wake = () => { runNextFrame = true; useDefaultElapsed = true; if (!state.isProcessing) { scheduleNextBatch(processBatch); } }; const schedule = stepsOrder.reduce((acc, key) => { const step = steps[key]; acc[key] = (process, keepAlive = false, immediate = false) => { if (!runNextFrame) wake(); return step.schedule(process, keepAlive, immediate); }; return acc; }, {}); const cancel = (process) => { for (let i = 0; i < stepsOrder.length; i++) { steps[stepsOrder[i]].cancel(process); } }; return { schedule, cancel, state, steps }; } const { schedule: frame, cancel: cancelFrame, state: frameData, steps: frameSteps, } = /* @__PURE__ */ createRenderBatcher(typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : noop, true); let now; function clearTime() { now = undefined; } /** * An eventloop-synchronous alternative to performance.now(). * * Ensures that time measurements remain consistent within a synchronous context. * Usually calling performance.now() twice within the same synchronous context * will return different values which isn't useful for animations when we're usually * trying to sync animations to the same frame. */ const time = { now: () => { if (now === undefined) { time.set(frameData.isProcessing || MotionGlobalConfig.useManualTiming ? frameData.timestamp : performance.now()); } return now; }, set: (newTime) => { now = newTime; queueMicrotask(clearTime); }, }; const checkStringStartsWith = (token) => (key) => typeof key === "string" && key.startsWith(token); const isCSSVariableName = /*@__PURE__*/ checkStringStartsWith("--"); const startsAsVariableToken = /*@__PURE__*/ checkStringStartsWith("var(--"); const isCSSVariableToken = (value) => { const startsWithToken = startsAsVariableToken(value); if (!startsWithToken) return false; // Ensure any comments are stripped from the value as this can harm performance of the regex. return singleCssVariableRegex.test(value.split("/*")[0].trim()); }; const singleCssVariableRegex = /var\(--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)$/iu; const number = { test: (v) => typeof v === "number", parse: parseFloat, transform: (v) => v, }; const alpha = { ...number, transform: (v) => clamp(0, 1, v), }; const scale = { ...number, default: 1, }; // If this number is a decimal, make it just five decimal places // to avoid exponents const sanitize = (v) => Math.round(v * 100000) / 100000; const floatRegex = /-?(?:\d+(?:\.\d+)?|\.\d+)/gu; function isNullish(v) { return v == null; } const singleColorRegex = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))$/iu; /** * Returns true if the provided string is a color, ie rgba(0,0,0,0) or #000, * but false if a number or multiple colors */ const isColorString = (type, testProp) => (v) => { return Boolean((typeof v === "string" && singleColorRegex.test(v) && v.startsWith(type)) || (testProp && !isNullish(v) && Object.prototype.hasOwnProperty.call(v, testProp))); }; const splitColor = (aName, bName, cName) => (v) => { if (typeof v !== "string") return v; const [a, b, c, alpha] = v.match(floatRegex); return { [aName]: parseFloat(a), [bName]: parseFloat(b), [cName]: parseFloat(c), alpha: alpha !== undefined ? parseFloat(alpha) : 1, }; }; const clampRgbUnit = (v) => clamp(0, 255, v); const rgbUnit = { ...number, transform: (v) => Math.round(clampRgbUnit(v)), }; const rgba = { test: /*@__PURE__*/ isColorString("rgb", "red"), parse: /*@__PURE__*/ splitColor("red", "green", "blue"), transform: ({ red, green, blue, alpha: alpha$1 = 1 }) => "rgba(" + rgbUnit.transform(red) + ", " + rgbUnit.transform(green) + ", " + rgbUnit.transform(blue) + ", " + sanitize(alpha.transform(alpha$1)) + ")", }; function parseHex(v) { let r = ""; let g = ""; let b = ""; let a = ""; // If we have 6 characters, ie #FF0000 if (v.length > 5) { r = v.substring(1, 3); g = v.substring(3, 5); b = v.substring(5, 7); a = v.substring(7, 9); // Or we have 3 characters, ie #F00 } else { r = v.substring(1, 2); g = v.substring(2, 3); b = v.substring(3, 4); a = v.substring(4, 5); r += r; g += g; b += b; a += a; } return { red: parseInt(r, 16), green: parseInt(g, 16), blue: parseInt(b, 16), alpha: a ? parseInt(a, 16) / 255 : 1, }; } const hex = { test: /*@__PURE__*/ isColorString("#"), parse: parseHex, transform: rgba.transform, }; /*#__NO_SIDE_EFFECTS__*/ const createUnitType = (unit) => ({ test: (v) => typeof v === "string" && v.endsWith(unit) && v.split(" ").length === 1, parse: parseFloat, transform: (v) => `${v}${unit}`, }); const degrees = /*@__PURE__*/ createUnitType("deg"); const percent = /*@__PURE__*/ createUnitType("%"); const px = /*@__PURE__*/ createUnitType("px"); const vh = /*@__PURE__*/ createUnitType("vh"); const vw = /*@__PURE__*/ createUnitType("vw"); const progressPercentage = /*@__PURE__*/ (() => ({ ...percent, parse: (v) => percent.parse(v) / 100, transform: (v) => percent.transform(v * 100), }))(); const hsla = { test: /*@__PURE__*/ isColorString("hsl", "hue"), parse: /*@__PURE__*/ splitColor("hue", "saturation", "lightness"), transform: ({ hue, saturation, lightness, alpha: alpha$1 = 1 }) => { return ("hsla(" + Math.round(hue) + ", " + percent.transform(sanitize(saturation)) + ", " + percent.transform(sanitize(lightness)) + ", " + sanitize(alpha.transform(alpha$1)) + ")"); }, }; const color = { test: (v) => rgba.test(v) || hex.test(v) || hsla.test(v), parse: (v) => { if (rgba.test(v)) { return rgba.parse(v); } else if (hsla.test(v)) { return hsla.parse(v); } else { return hex.parse(v); } }, transform: (v) => { return typeof v === "string" ? v : v.hasOwnProperty("red") ? rgba.transform(v) : hsla.transform(v); }, getAnimatableNone: (v) => { const parsed = color.parse(v); parsed.alpha = 0; return color.transform(parsed); }, }; const colorRegex = /(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\))/giu; function test(v) { return (isNaN(v) && typeof v === "string" && (v.match(floatRegex)?.length || 0) + (v.match(colorRegex)?.length || 0) > 0); } const NUMBER_TOKEN = "number"; const COLOR_TOKEN = "color"; const VAR_TOKEN = "var"; const VAR_FUNCTION_TOKEN = "var("; const SPLIT_TOKEN = "${}"; // this regex consists of the `singleCssVariableRegex|rgbHSLValueRegex|digitRegex` const complexRegex = /var\s*\(\s*--(?:[\w-]+\s*|[\w-]+\s*,(?:\s*[^)(\s]|\s*\((?:[^)(]|\([^)(]*\))*\))+\s*)\)|#[\da-f]{3,8}|(?:rgb|hsl)a?\((?:-?[\d.]+%?[,\s]+){2}-?[\d.]+%?\s*(?:[,/]\s*)?(?:\b\d+(?:\.\d+)?|\.\d+)?%?\)|-?(?:\d+(?:\.\d+)?|\.\d+)/giu; function analyseComplexValue(value) { const originalValue = value.toString(); const values = []; const indexes = { color: [], number: [], var: [], }; const types = []; let i = 0; const tokenised = originalValue.replace(complexRegex, (parsedValue) => { if (color.test(parsedValue)) { indexes.color.push(i); types.push(COLOR_TOKEN); values.push(color.parse(parsedValue)); } else if (parsedValue.startsWith(VAR_FUNCTION_TOKEN)) { indexes.var.push(i); types.push(VAR_TOKEN); values.push(parsedValue); } else { indexes.number.push(i); types.push(NUMBER_TOKEN); values.push(parseFloat(parsedValue)); } ++i; return SPLIT_TOKEN; }); const split = tokenised.split(SPLIT_TOKEN); return { values, split, indexes, types }; } function parseComplexValue(v) { return analyseComplexValue(v).values; } function createTransformer(source) { const { split, types } = analyseComplexValue(source); const numSections = split.length; return (v) => { let output = ""; for (let i = 0; i < numSections; i++) { output += split[i]; if (v[i] !== undefined) { const type = types[i]; if (type === NUMBER_TOKEN) { output += sanitize(v[i]); } else if (type === COLOR_TOKEN) { output += color.transform(v[i]); } else { output += v[i]; } } } return output; }; } const convertNumbersToZero = (v) => typeof v === "number" ? 0 : color.test(v) ? color.getAnimatableNone(v) : v; function getAnimatableNone$1(v) { const parsed = parseComplexValue(v); const transformer = createTransformer(v); return transformer(parsed.map(convertNumbersToZero)); } const complex = { test, parse: parseComplexValue, createTransformer, getAnimatableNone: getAnimatableNone$1, }; // Adapted from https://gist.github.com/mjackson/5311256 function hueToRgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1 / 6) return p + (q - p) * 6 * t; if (t < 1 / 2) return q; if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6; return p; } function hslaToRgba({ hue, saturation, lightness, alpha }) { hue /= 360; saturation /= 100; lightness /= 100; let red = 0; let green = 0; let blue = 0; if (!saturation) { red = green = blue = lightness; } else { const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation; const p = 2 * lightness - q; red = hueToRgb(p, q, hue + 1 / 3); green = hueToRgb(p, q, hue); blue = hueToRgb(p, q, hue - 1 / 3); } return { red: Math.round(red * 255), green: Math.round(green * 255), blue: Math.round(blue * 255), alpha, }; } function mixImmediate(a, b) { return (p) => (p > 0 ? b : a); } /* Value in range from progress Given a lower limit and an upper limit, we return the value within that range as expressed by progress (usually a number from 0 to 1) So progress = 0.5 would change from -------- to to from ---- to E.g. from = 10, to = 20, progress = 0.5 => 15 @param [number]: Lower limit of range @param [number]: Upper limit of range @param [number]: The progress between lower and upper limits expressed 0-1 @return [number]: Value as calculated from progress within range (not limited within range) */ const mixNumber$1 = (from, to, progress) => { return from + (to - from) * progress; }; // Linear color space blending // Explained https://www.youtube.com/watch?v=LKnqECcg6Gw // Demonstrated http://codepen.io/osublake/pen/xGVVaN const mixLinearColor = (from, to, v) => { const fromExpo = from * from; const expo = v * (to * to - fromExpo) + fromExpo; return expo < 0 ? 0 : Math.sqrt(expo); }; const colorTypes = [hex, rgba, hsla]; const getColorType = (v) => colorTypes.find((type) => type.test(v)); function asRGBA(color) { const type = getColorType(color); warning(Boolean(type), `'${color}' is not an animatable color. Use the equivalent color code instead.`); if (!Boolean(type)) return false; let model = type.parse(color); if (type === hsla) { // TODO Remove this cast - needed since Motion's stricter typing model = hslaToRgba(model); } return model; } const mixColor = (from, to) => { const fromRGBA = asRGBA(from); const toRGBA = asRGBA(to); if (!fromRGBA || !toRGBA) { return mixImmediate(from, to); } const blended = { ...fromRGBA }; return (v) => { blended.red = mixLinearColor(fromRGBA.red, toRGBA.red, v); blended.green = mixLinearColor(fromRGBA.green, toRGBA.green, v); blended.blue = mixLinearColor(fromRGBA.blue, toRGBA.blue, v); blended.alpha = mixNumber$1(fromRGBA.alpha, toRGBA.alpha, v); return rgba.transform(blended); }; }; const invisibleValues = new Set(["none", "hidden"]); /** * Returns a function that, when provided a progress value between 0 and 1, * will return the "none" or "hidden" string only when the progress is