build: ship pre-built API bundle; images copy output instead of installing

- API: `pnpm build` bundles dist/ into apps/api/.output (esbuild), with
  Linux musl native packages for sharp and argon2 kept alongside
- docker/api.Dockerfile copies that output; no pnpm install on the server
- add dist/migrate.js (prisma migrate deploy equivalent, same
  _prisma_migrations table and lock) so the Prisma CLI is not needed at runtime
- pnpm-workspace: supportedArchitectures for linux/x64/musl
- admin: apps/admin/.env.production sets VITE_SITE_URL for the production build
- drop NPM_REGISTRY / SITE_URL build args from compose and .env.example
- gitignore: anchor node_modules rules so pre-built node_modules are committed
- README: describe the pre-built deploy flow

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
S. Esmaeili
2026-10-24 14:41:31 +03:30
co-authored by Claude Haiku 5.5
parent ee8258556d
commit 10e453e4be
692 changed files with 303601 additions and 147 deletions
File diff suppressed because it is too large Load Diff
+50
View File
@@ -0,0 +1,50 @@
{
"name": "@babel/parser",
"version": "7.29.9",
"description": "A JavaScript parser",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-parser",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"keywords": [
"babel",
"javascript",
"parser",
"tc39",
"ecmascript",
"@babel/parser"
],
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-parser"
},
"main": "./lib/index.js",
"types": "./typings/babel-parser.d.ts",
"files": [
"bin",
"lib",
"typings/babel-parser.d.ts",
"index.cjs"
],
"engines": {
"node": ">=6.0.0"
},
"# dependencies": "This package doesn't actually have runtime dependencies. @babel/types is only needed for type definitions.",
"dependencies": {
"@babel/types": "^7.29.8"
},
"devDependencies": {
"@babel/code-frame": "^7.29.7",
"@babel/helper-check-duplicate-nodes": "^7.29.7",
"@babel/helper-fixtures": "^7.29.7",
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7",
"charcodes": "^0.2.0"
},
"bin": "./bin/babel-parser.js",
"type": "commonjs"
}
File diff suppressed because it is too large Load Diff
+58
View File
@@ -0,0 +1,58 @@
{
"name": "@vue/compiler-core",
"version": "3.5.44",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
"types": "dist/compiler-core.d.ts",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/compiler-core.d.ts",
"node": {
"production": "./dist/compiler-core.cjs.prod.js",
"development": "./dist/compiler-core.cjs.js",
"default": "./dist/compiler-core.cjs.prod.js"
},
"module": "./dist/compiler-core.esm-bundler.js",
"import": "./dist/compiler-core.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"buildOptions": {
"name": "VueCompilerCore",
"compat": true,
"formats": [
"esm-bundler",
"cjs"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/compiler-core"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
"dependencies": {
"@babel/parser": "^7.29.9",
"@vue/shared": "3.5.44",
"entities": "^7.0.1",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.2"
},
"devDependencies": {
"@babel/types": "^7.29.8"
}
}
@@ -0,0 +1,729 @@
/**
* @vue/compiler-dom v3.5.44
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var compilerCore = require('@vue/compiler-core');
var shared = require('@vue/shared');
const V_MODEL_RADIO = /* @__PURE__ */ Symbol(``);
const V_MODEL_CHECKBOX = /* @__PURE__ */ Symbol(
``
);
const V_MODEL_TEXT = /* @__PURE__ */ Symbol(``);
const V_MODEL_SELECT = /* @__PURE__ */ Symbol(
``
);
const V_MODEL_DYNAMIC = /* @__PURE__ */ Symbol(
``
);
const V_ON_WITH_MODIFIERS = /* @__PURE__ */ Symbol(
``
);
const V_ON_WITH_KEYS = /* @__PURE__ */ Symbol(
``
);
const V_SHOW = /* @__PURE__ */ Symbol(``);
const TRANSITION = /* @__PURE__ */ Symbol(``);
const TRANSITION_GROUP = /* @__PURE__ */ Symbol(
``
);
compilerCore.registerRuntimeHelpers({
[V_MODEL_RADIO]: `vModelRadio`,
[V_MODEL_CHECKBOX]: `vModelCheckbox`,
[V_MODEL_TEXT]: `vModelText`,
[V_MODEL_SELECT]: `vModelSelect`,
[V_MODEL_DYNAMIC]: `vModelDynamic`,
[V_ON_WITH_MODIFIERS]: `withModifiers`,
[V_ON_WITH_KEYS]: `withKeys`,
[V_SHOW]: `vShow`,
[TRANSITION]: `Transition`,
[TRANSITION_GROUP]: `TransitionGroup`
});
const parserOptions = {
parseMode: "html",
isVoidTag: shared.isVoidTag,
isNativeTag: (tag) => shared.isHTMLTag(tag) || shared.isSVGTag(tag) || shared.isMathMLTag(tag),
isPreTag: (tag) => tag === "pre",
isIgnoreNewlineTag: (tag) => tag === "pre" || tag === "textarea",
decodeEntities: void 0,
isBuiltInComponent: (tag) => {
if (tag === "Transition" || tag === "transition") {
return TRANSITION;
} else if (tag === "TransitionGroup" || tag === "transition-group") {
return TRANSITION_GROUP;
}
},
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
getNamespace(tag, parent, rootNamespace) {
let ns = parent ? parent.ns : rootNamespace;
if (parent && ns === 2) {
if (parent.tag === "annotation-xml") {
if (tag === "svg") {
return 1;
}
if (parent.props.some(
(a) => a.type === 6 && a.name === "encoding" && a.value != null && (a.value.content === "text/html" || a.value.content === "application/xhtml+xml")
)) {
ns = 0;
}
} else if (/^m(?:[ions]|text)$/.test(parent.tag) && tag !== "mglyph" && tag !== "malignmark") {
ns = 0;
}
} else if (parent && ns === 1) {
if (parent.tag === "foreignObject" || parent.tag === "desc" || parent.tag === "title") {
ns = 0;
}
}
if (ns === 0) {
if (tag === "svg") {
return 1;
}
if (tag === "math") {
return 2;
}
}
return ns;
}
};
const transformStyle = (node) => {
if (node.type === 1) {
node.props.forEach((p, i) => {
if (p.type === 6 && p.name === "style" && p.value) {
node.props[i] = {
type: 7,
name: `bind`,
arg: compilerCore.createSimpleExpression(`style`, true, p.loc),
exp: parseInlineCSS(p.value.content, p.loc),
modifiers: [],
loc: p.loc
};
}
});
}
};
const parseInlineCSS = (cssText, loc) => {
const normalized = shared.parseStringStyle(cssText);
return compilerCore.createSimpleExpression(
JSON.stringify(normalized),
false,
loc,
3
);
};
function createDOMCompilerError(code, loc) {
return compilerCore.createCompilerError(
code,
loc,
DOMErrorMessages
);
}
const DOMErrorCodes = {
"X_V_HTML_NO_EXPRESSION": 54,
"54": "X_V_HTML_NO_EXPRESSION",
"X_V_HTML_WITH_CHILDREN": 55,
"55": "X_V_HTML_WITH_CHILDREN",
"X_V_TEXT_NO_EXPRESSION": 56,
"56": "X_V_TEXT_NO_EXPRESSION",
"X_V_TEXT_WITH_CHILDREN": 57,
"57": "X_V_TEXT_WITH_CHILDREN",
"X_V_MODEL_ON_INVALID_ELEMENT": 58,
"58": "X_V_MODEL_ON_INVALID_ELEMENT",
"X_V_MODEL_ARG_ON_ELEMENT": 59,
"59": "X_V_MODEL_ARG_ON_ELEMENT",
"X_V_MODEL_ON_FILE_INPUT_ELEMENT": 60,
"60": "X_V_MODEL_ON_FILE_INPUT_ELEMENT",
"X_V_MODEL_UNNECESSARY_VALUE": 61,
"61": "X_V_MODEL_UNNECESSARY_VALUE",
"X_V_SHOW_NO_EXPRESSION": 62,
"62": "X_V_SHOW_NO_EXPRESSION",
"X_TRANSITION_INVALID_CHILDREN": 63,
"63": "X_TRANSITION_INVALID_CHILDREN",
"X_IGNORED_SIDE_EFFECT_TAG": 64,
"64": "X_IGNORED_SIDE_EFFECT_TAG",
"__EXTEND_POINT__": 65,
"65": "__EXTEND_POINT__"
};
const DOMErrorMessages = {
[54]: `v-html is missing expression.`,
[55]: `v-html will override element children.`,
[56]: `v-text is missing expression.`,
[57]: `v-text will override element children.`,
[58]: `v-model can only be used on <input>, <textarea> and <select> elements.`,
[59]: `v-model argument is not supported on plain elements.`,
[60]: `v-model cannot be used on file inputs since they are read-only. Use a v-on:change listener instead.`,
[61]: `Unnecessary value binding used alongside v-model. It will interfere with v-model's behavior.`,
[62]: `v-show is missing expression.`,
[63]: `<Transition> expects exactly one child element or component.`,
[64]: `Tags with side effect (<script> and <style>) are ignored in client component templates.`
};
const transformVHtml = (dir, node, context) => {
const { exp, loc } = dir;
if (!exp) {
context.onError(
createDOMCompilerError(54, loc)
);
}
if (node.children.length) {
context.onError(
createDOMCompilerError(55, loc)
);
node.children.length = 0;
}
return {
props: [
compilerCore.createObjectProperty(
compilerCore.createSimpleExpression(`innerHTML`, true, loc),
exp || compilerCore.createSimpleExpression("", true)
)
]
};
};
const transformVText = (dir, node, context) => {
const { exp, loc } = dir;
if (!exp) {
context.onError(
createDOMCompilerError(56, loc)
);
}
if (node.children.length) {
context.onError(
createDOMCompilerError(57, loc)
);
node.children.length = 0;
}
return {
props: [
compilerCore.createObjectProperty(
compilerCore.createSimpleExpression(`textContent`, true),
exp ? compilerCore.getConstantType(exp, context) > 0 ? exp : compilerCore.createCallExpression(
context.helperString(compilerCore.TO_DISPLAY_STRING),
[exp],
loc
) : compilerCore.createSimpleExpression("", true)
)
]
};
};
const transformModel = (dir, node, context) => {
const baseResult = compilerCore.transformModel(dir, node, context);
if (!baseResult.props.length || node.tagType === 1) {
return baseResult;
}
if (dir.arg) {
context.onError(
createDOMCompilerError(
59,
dir.arg.loc
)
);
}
const { tag } = node;
const isCustomElement = context.isCustomElement(tag);
if (tag === "input" || tag === "textarea" || tag === "select" || isCustomElement) {
let directiveToUse = V_MODEL_TEXT;
let isInvalidType = false;
if (tag === "input" || isCustomElement) {
const type = compilerCore.findProp(node, `type`);
if (type) {
if (type.type === 7) {
directiveToUse = V_MODEL_DYNAMIC;
} else if (type.value) {
switch (type.value.content) {
case "radio":
directiveToUse = V_MODEL_RADIO;
break;
case "checkbox":
directiveToUse = V_MODEL_CHECKBOX;
break;
case "file":
isInvalidType = true;
context.onError(
createDOMCompilerError(
60,
dir.loc
)
);
break;
}
}
} else if (compilerCore.hasDynamicKeyVBind(node)) {
directiveToUse = V_MODEL_DYNAMIC;
} else ;
} else if (tag === "select") {
directiveToUse = V_MODEL_SELECT;
} else ;
if (!isInvalidType) {
baseResult.needRuntime = context.helper(directiveToUse);
}
} else {
context.onError(
createDOMCompilerError(
58,
dir.loc
)
);
}
baseResult.props = baseResult.props.filter(
(p) => !(p.key.type === 4 && p.key.content === "modelValue")
);
return baseResult;
};
const isEventOptionModifier = /* @__PURE__ */ shared.makeMap(`passive,once,capture`);
const isNonKeyModifier = /* @__PURE__ */ shared.makeMap(
// event propagation management
`stop,prevent,self,ctrl,shift,alt,meta,exact,middle`
);
const maybeKeyModifier = /* @__PURE__ */ shared.makeMap("left,right");
const isKeyboardEvent = /* @__PURE__ */ shared.makeMap(`onkeyup,onkeydown,onkeypress`);
const resolveModifiers = (key, modifiers, context, loc) => {
const keyModifiers = [];
const nonKeyModifiers = [];
const eventOptionModifiers = [];
for (let i = 0; i < modifiers.length; i++) {
const modifier = modifiers[i].content;
if (modifier === "native" && compilerCore.checkCompatEnabled(
"COMPILER_V_ON_NATIVE",
context,
loc
)) {
eventOptionModifiers.push(modifier);
} else if (isEventOptionModifier(modifier)) {
eventOptionModifiers.push(modifier);
} else {
if (maybeKeyModifier(modifier)) {
if (compilerCore.isStaticExp(key)) {
if (isKeyboardEvent(key.content.toLowerCase())) {
keyModifiers.push(modifier);
} else {
nonKeyModifiers.push(modifier);
}
} else {
keyModifiers.push(modifier);
nonKeyModifiers.push(modifier);
}
} else {
if (isNonKeyModifier(modifier)) {
nonKeyModifiers.push(modifier);
} else {
keyModifiers.push(modifier);
}
}
}
}
return {
keyModifiers,
nonKeyModifiers,
eventOptionModifiers
};
};
const transformClick = (key, event) => {
const isStaticClick = compilerCore.isStaticExp(key) && key.content.toLowerCase() === "onclick";
return isStaticClick ? compilerCore.createSimpleExpression(event, true) : key.type !== 4 ? compilerCore.createCompoundExpression([
`(`,
key,
`) === "onClick" ? "${event}" : (`,
key,
`)`
]) : key;
};
const transformOn = (dir, node, context) => {
return compilerCore.transformOn(dir, node, context, (baseResult) => {
const { modifiers } = dir;
if (!modifiers.length) return baseResult;
let { key, value: handlerExp } = baseResult.props[0];
const { keyModifiers, nonKeyModifiers, eventOptionModifiers } = resolveModifiers(key, modifiers, context, dir.loc);
if (nonKeyModifiers.includes("right")) {
key = transformClick(key, `onContextmenu`);
}
if (nonKeyModifiers.includes("middle")) {
key = transformClick(key, `onMouseup`);
}
if (nonKeyModifiers.length) {
handlerExp = compilerCore.createCallExpression(context.helper(V_ON_WITH_MODIFIERS), [
handlerExp,
JSON.stringify(nonKeyModifiers)
]);
}
if (keyModifiers.length && // if event name is dynamic, always wrap with keys guard
(!compilerCore.isStaticExp(key) || isKeyboardEvent(key.content.toLowerCase()))) {
handlerExp = compilerCore.createCallExpression(context.helper(V_ON_WITH_KEYS), [
handlerExp,
JSON.stringify(keyModifiers)
]);
}
if (eventOptionModifiers.length) {
const modifierPostfix = eventOptionModifiers.map(shared.capitalize).join("");
key = compilerCore.isStaticExp(key) ? compilerCore.createSimpleExpression(`${key.content}${modifierPostfix}`, true) : compilerCore.createCompoundExpression([`(`, key, `) + "${modifierPostfix}"`]);
}
return {
props: [compilerCore.createObjectProperty(key, handlerExp)]
};
});
};
const transformShow = (dir, node, context) => {
const { exp, loc } = dir;
if (!exp) {
context.onError(
createDOMCompilerError(62, loc)
);
}
return {
props: [],
needRuntime: context.helper(V_SHOW)
};
};
const transformTransition = (node, context) => {
if (node.type === 1 && node.tagType === 1) {
const component = context.isBuiltInComponent(node.tag);
if (component === TRANSITION) {
return () => {
if (!node.children.length) {
return;
}
hasMultipleChildren(node);
const child = node.children[0];
if (child.type === 1) {
for (const p of child.props) {
if (p.type === 7 && p.name === "show") {
node.props.push({
type: 6,
name: "persisted",
nameLoc: node.loc,
value: void 0,
loc: node.loc
});
}
}
}
};
}
}
};
function hasMultipleChildren(node) {
const children = node.children = node.children.filter(
(c) => !compilerCore.isCommentOrWhitespace(c)
);
const child = children[0];
return children.length !== 1 || child.type === 11 || child.type === 9 && child.branches.some(hasMultipleChildren);
}
const expReplaceRE = /__VUE_EXP_START__(.*?)__VUE_EXP_END__/g;
const stringifyStatic = (children, context, parent) => {
if (context.scopes.vSlot > 0) {
return;
}
const isParentCached = parent.type === 1 && parent.codegenNode && parent.codegenNode.type === 13 && parent.codegenNode.children && !shared.isArray(parent.codegenNode.children) && parent.codegenNode.children.type === 20;
let nc = 0;
let ec = 0;
const currentChunk = [];
const stringifyCurrentChunk = (currentIndex) => {
if (nc >= 20 || ec >= 5) {
const staticCall = compilerCore.createCallExpression(context.helper(compilerCore.CREATE_STATIC), [
JSON.stringify(
currentChunk.map((node) => stringifyNode(node, context)).join("")
).replace(expReplaceRE, `" + $1 + "`),
// the 2nd argument indicates the number of DOM nodes this static vnode
// will insert / hydrate
String(currentChunk.length)
]);
const deleteCount = currentChunk.length - 1;
if (isParentCached) {
children.splice(
currentIndex - currentChunk.length,
currentChunk.length,
// @ts-expect-error
staticCall
);
} else {
currentChunk[0].codegenNode.value = staticCall;
if (currentChunk.length > 1) {
children.splice(currentIndex - currentChunk.length + 1, deleteCount);
const cacheIndex = context.cached.indexOf(
currentChunk[currentChunk.length - 1].codegenNode
);
if (cacheIndex > -1) {
for (let i2 = cacheIndex; i2 < context.cached.length; i2++) {
const c = context.cached[i2];
if (c) c.index -= deleteCount;
}
context.cached.splice(cacheIndex - deleteCount + 1, deleteCount);
}
}
}
return deleteCount;
}
return 0;
};
let i = 0;
for (; i < children.length; i++) {
const child = children[i];
const isCached = isParentCached || getCachedNode(child);
if (isCached) {
const result = analyzeNode(child);
if (result) {
nc += result[0];
ec += result[1];
currentChunk.push(child);
continue;
}
}
i -= stringifyCurrentChunk(i);
nc = 0;
ec = 0;
currentChunk.length = 0;
}
stringifyCurrentChunk(i);
};
const getCachedNode = (node) => {
if ((node.type === 1 && node.tagType === 0 || node.type === 12) && node.codegenNode && node.codegenNode.type === 20) {
return node.codegenNode;
}
};
const dataAriaRE = /^(?:data|aria)-/;
const isStringifiableAttr = (name, ns) => {
return (ns === 0 ? shared.isKnownHtmlAttr(name) : ns === 1 ? shared.isKnownSvgAttr(name) : ns === 2 ? shared.isKnownMathMLAttr(name) : false) || dataAriaRE.test(name);
};
const isNonStringifiable = /* @__PURE__ */ shared.makeMap(
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col`
);
function analyzeNode(node) {
if (node.type === 1 && isNonStringifiable(node.tag)) {
return false;
}
if (node.type === 1 && compilerCore.findDir(node, "once", true)) {
return false;
}
if (node.type === 12) {
return [1, 0];
}
let nc = 1;
let ec = node.props.length > 0 ? 1 : 0;
let bailed = false;
const bail = () => {
bailed = true;
return false;
};
function walk(node2) {
const isOptionTag = node2.tag === "option" && node2.ns === 0;
for (let i = 0; i < node2.props.length; i++) {
const p = node2.props[i];
if (p.type === 6 && !isStringifiableAttr(p.name, node2.ns)) {
return bail();
}
if (p.type === 7 && p.name === "bind") {
if (p.arg && (p.arg.type === 8 || p.arg.isStatic && !isStringifiableAttr(p.arg.content, node2.ns))) {
return bail();
}
if (p.exp && (p.exp.type === 8 || p.exp.constType < 3)) {
return bail();
}
if (isOptionTag && compilerCore.isStaticArgOf(p.arg, "value") && p.exp && !p.exp.isStatic) {
return bail();
}
}
}
for (let i = 0; i < node2.children.length; i++) {
nc++;
const child = node2.children[i];
if (child.type === 1) {
if (child.props.length > 0) {
ec++;
}
walk(child);
if (bailed) {
return false;
}
}
}
return true;
}
return walk(node) ? [nc, ec] : false;
}
function stringifyNode(node, context) {
if (shared.isString(node)) {
return node;
}
if (shared.isSymbol(node)) {
return ``;
}
switch (node.type) {
case 1:
return stringifyElement(node, context);
case 2:
return shared.escapeHtml(node.content);
case 3:
return `<!--${shared.escapeHtml(node.content)}-->`;
case 5:
return shared.escapeHtml(shared.toDisplayString(evaluateConstant(node.content)));
case 8:
return shared.escapeHtml(evaluateConstant(node));
case 12:
return stringifyNode(node.content, context);
default:
return "";
}
}
function stringifyElement(node, context) {
var _a;
let res = `<${node.tag}`;
let innerHTML = void 0;
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i];
if (p.type === 6) {
res += ` ${p.name}`;
if (p.value) {
res += `="${shared.escapeHtml(p.value.content)}"`;
}
} else if (p.type === 7) {
if (p.name === "bind") {
const exp = p.exp;
if (exp.content[0] === "_") {
res += ` ${p.arg.content}="__VUE_EXP_START__${exp.content}__VUE_EXP_END__"`;
continue;
}
if ((shared.isBooleanAttr(p.arg.content) || p.arg.content === "hidden") && exp.content === "false") {
continue;
}
let evaluated = evaluateConstant(exp);
if (evaluated != null) {
const arg = p.arg && p.arg.content;
if (arg === "hidden" && typeof evaluated === "number" && !shared.includeBooleanAttr(evaluated)) {
continue;
}
if (arg === "class") {
evaluated = shared.normalizeClass(evaluated);
} else if (arg === "style") {
evaluated = shared.stringifyStyle(shared.normalizeStyle(evaluated));
}
res += ` ${p.arg.content}="${shared.escapeHtml(
evaluated
)}"`;
}
} else if (p.name === "html") {
innerHTML = (_a = evaluateConstant(p.exp)) != null ? _a : "";
} else if (p.name === "text") {
innerHTML = shared.escapeHtml(
shared.toDisplayString(evaluateConstant(p.exp))
);
}
}
}
if (context.scopeId) {
res += ` ${context.scopeId}`;
}
res += `>`;
if (innerHTML !== void 0) {
res += innerHTML;
} else {
for (let i = 0; i < node.children.length; i++) {
res += stringifyNode(node.children[i], context);
}
}
if (!shared.isVoidTag(node.tag)) {
res += `</${node.tag}>`;
}
return res;
}
function evaluateConstant(exp) {
if (exp.type === 4) {
return new Function(`return (${exp.content})`)();
} else {
let res = ``;
exp.children.forEach((c) => {
if (shared.isString(c) || shared.isSymbol(c)) {
return;
}
if (c.type === 2) {
res += c.content;
} else if (c.type === 5) {
res += shared.toDisplayString(evaluateConstant(c.content));
} else {
res += evaluateConstant(c);
}
});
return res;
}
}
const ignoreSideEffectTags = (node, context) => {
if (node.type === 1 && node.tagType === 0 && (node.tag === "script" || node.tag === "style")) {
context.removeNode();
}
};
const DOMNodeTransforms = [
transformStyle,
transformTransition,
...[]
];
const DOMDirectiveTransforms = {
cloak: compilerCore.noopDirectiveTransform,
html: transformVHtml,
text: transformVText,
model: transformModel,
// override compiler-core
on: transformOn,
// override compiler-core
show: transformShow
};
function compile(src, options = {}) {
return compilerCore.baseCompile(
src,
shared.extend({}, parserOptions, options, {
nodeTransforms: [
// ignore <script> and <tag>
// this is not put inside DOMNodeTransforms because that list is used
// by compiler-ssr to generate vnode fallback branches
ignoreSideEffectTags,
...DOMNodeTransforms,
...options.nodeTransforms || []
],
directiveTransforms: shared.extend(
{},
DOMDirectiveTransforms,
options.directiveTransforms || {}
),
transformHoist: stringifyStatic
})
);
}
function parse(template, options = {}) {
return compilerCore.baseParse(template, shared.extend({}, parserOptions, options));
}
exports.DOMDirectiveTransforms = DOMDirectiveTransforms;
exports.DOMErrorCodes = DOMErrorCodes;
exports.DOMErrorMessages = DOMErrorMessages;
exports.DOMNodeTransforms = DOMNodeTransforms;
exports.TRANSITION = TRANSITION;
exports.TRANSITION_GROUP = TRANSITION_GROUP;
exports.V_MODEL_CHECKBOX = V_MODEL_CHECKBOX;
exports.V_MODEL_DYNAMIC = V_MODEL_DYNAMIC;
exports.V_MODEL_RADIO = V_MODEL_RADIO;
exports.V_MODEL_SELECT = V_MODEL_SELECT;
exports.V_MODEL_TEXT = V_MODEL_TEXT;
exports.V_ON_WITH_KEYS = V_ON_WITH_KEYS;
exports.V_ON_WITH_MODIFIERS = V_ON_WITH_MODIFIERS;
exports.V_SHOW = V_SHOW;
exports.compile = compile;
exports.createDOMCompilerError = createDOMCompilerError;
exports.parse = parse;
exports.parserOptions = parserOptions;
exports.transformStyle = transformStyle;
Object.keys(compilerCore).forEach(function (k) {
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) exports[k] = compilerCore[k];
});
+57
View File
@@ -0,0 +1,57 @@
{
"name": "@vue/compiler-dom",
"version": "3.5.44",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
"types": "dist/compiler-dom.d.ts",
"unpkg": "dist/compiler-dom.global.js",
"jsdelivr": "dist/compiler-dom.global.js",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/compiler-dom.d.ts",
"node": {
"production": "./dist/compiler-dom.cjs.prod.js",
"development": "./dist/compiler-dom.cjs.js",
"default": "./dist/compiler-dom.cjs.prod.js"
},
"module": "./dist/compiler-dom.esm-bundler.js",
"import": "./dist/compiler-dom.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"sideEffects": false,
"buildOptions": {
"name": "VueCompilerDOM",
"compat": true,
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/compiler-dom"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
"dependencies": {
"@vue/shared": "3.5.44",
"@vue/compiler-core": "3.5.44"
}
}
File diff suppressed because it is too large Load Diff
+34
View File
@@ -0,0 +1,34 @@
{
"name": "@vue/compiler-ssr",
"version": "3.5.44",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
"files": [
"dist"
],
"buildOptions": {
"prod": false,
"formats": [
"cjs"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/compiler-ssr"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
"dependencies": {
"@vue/shared": "3.5.44",
"@vue/compiler-dom": "3.5.44"
}
}
@@ -0,0 +1,13 @@
//#region src/index-node.ts
function addCustomCommand() {}
function addCustomTab() {}
function onDevToolsClientConnected(_fn) {
return new Promise(() => {});
}
function onDevToolsConnected(_fn) {
return new Promise(() => {});
}
function removeCustomCommand() {}
function setupDevToolsPlugin(_pluginDescriptor, _setupFn) {}
//#endregion
export { addCustomCommand, addCustomTab, onDevToolsClientConnected, onDevToolsConnected, removeCustomCommand, setupDevToolsPlugin, setupDevToolsPlugin as setupDevtoolsPlugin };
+38
View File
@@ -0,0 +1,38 @@
{
"name": "@vue/devtools-api",
"type": "module",
"version": "8.2.1",
"author": "webfansplz",
"license": "MIT",
"repository": {
"directory": "packages/devtools-api",
"type": "git",
"url": "git+https://github.com/vuejs/devtools.git"
},
"sideEffects": false,
"exports": {
".": {
"node": {
"import": "./dist/index-node.js",
"require": "./dist/index-node.cjs"
},
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"unpkg": "dist/vue-devtools-api.global.js",
"jsdelivr": "dist/vue-devtools-api.global.js",
"files": [
"dist"
],
"dependencies": {
"@vue/devtools-kit": "^8.2.1"
},
"scripts": {
"build": "tsdown --clean",
"prepare:type": "tsdown --dts-only",
"stub": "tsdown --watch --onSuccess 'tsdown --dts-only'"
}
}
File diff suppressed because it is too large Load Diff
+55
View File
@@ -0,0 +1,55 @@
{
"name": "@vue/reactivity",
"version": "3.5.44",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
"types": "dist/reactivity.d.ts",
"unpkg": "dist/reactivity.global.js",
"jsdelivr": "dist/reactivity.global.js",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/reactivity.d.ts",
"node": {
"production": "./dist/reactivity.cjs.prod.js",
"development": "./dist/reactivity.cjs.js",
"default": "./dist/reactivity.cjs.prod.js"
},
"module": "./dist/reactivity.esm-bundler.js",
"import": "./dist/reactivity.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"sideEffects": false,
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/reactivity"
},
"buildOptions": {
"name": "VueReactivity",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme",
"dependencies": {
"@vue/shared": "3.5.44"
}
}
File diff suppressed because it is too large Load Diff
+52
View File
@@ -0,0 +1,52 @@
{
"name": "@vue/runtime-core",
"version": "3.5.44",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
"types": "dist/runtime-core.d.ts",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/runtime-core.d.ts",
"node": {
"production": "./dist/runtime-core.cjs.prod.js",
"development": "./dist/runtime-core.cjs.js",
"default": "./dist/runtime-core.cjs.prod.js"
},
"module": "./dist/runtime-core.esm-bundler.js",
"import": "./dist/runtime-core.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"buildOptions": {
"name": "VueRuntimeCore",
"formats": [
"esm-bundler",
"cjs"
]
},
"sideEffects": false,
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/runtime-core"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
"dependencies": {
"@vue/shared": "3.5.44",
"@vue/reactivity": "3.5.44"
}
}
File diff suppressed because it is too large Load Diff
+60
View File
@@ -0,0 +1,60 @@
{
"name": "@vue/runtime-dom",
"version": "3.5.44",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
"types": "dist/runtime-dom.d.ts",
"unpkg": "dist/runtime-dom.global.js",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/runtime-dom.d.ts",
"node": {
"production": "./dist/runtime-dom.cjs.prod.js",
"development": "./dist/runtime-dom.cjs.js",
"default": "./dist/runtime-dom.cjs.prod.js"
},
"module": "./dist/runtime-dom.esm-bundler.js",
"import": "./dist/runtime-dom.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"sideEffects": false,
"buildOptions": {
"name": "VueRuntimeDOM",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/runtime-dom"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme",
"dependencies": {
"@vue/shared": "3.5.44",
"@vue/runtime-core": "3.5.44",
"@vue/reactivity": "3.5.44",
"csstype": "^3.2.3"
},
"devDependencies": {
"@types/trusted-types": "^2.0.7"
}
}
@@ -0,0 +1,951 @@
/**
* @vue/server-renderer v3.5.44
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var Vue = require('@vue/runtime-dom');
var shared = require('@vue/shared');
var compilerSsr = require('@vue/compiler-ssr');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
for (var k in e) {
n[k] = e[k];
}
}
n.default = e;
return Object.freeze(n);
}
var Vue__namespace = /*#__PURE__*/_interopNamespaceDefault(Vue);
const shouldIgnoreProp = /* @__PURE__ */ shared.makeMap(
`,key,ref,innerHTML,textContent,ref_key,ref_for`
);
function ssrRenderAttrs(props, tag) {
let ret = "";
for (let key in props) {
if (shouldIgnoreProp(key) || shared.isOn(key) || tag === "textarea" && key === "value" || // force as property (not rendered in SSR)
key.startsWith(".")) {
continue;
}
const value = props[key];
if (key.startsWith("^")) key = key.slice(1);
if (key === "class") {
ret += ` class="${ssrRenderClass(value)}"`;
} else if (key === "style") {
ret += ` style="${ssrRenderStyle(value)}"`;
} else if (key === "className") {
if (value != null) {
ret += ` class="${shared.escapeHtml(String(value))}"`;
}
} else {
ret += ssrRenderDynamicAttr(key, value, tag);
}
}
return ret;
}
function ssrRenderDynamicAttr(key, value, tag) {
if (!shared.isRenderableAttrValue(value)) {
return ``;
}
const attrKey = tag && (tag.indexOf("-") > 0 || shared.isSVGTag(tag)) ? key : shared.propsToAttrMap[key] || key.toLowerCase();
if (shared.isBooleanAttr(attrKey) || attrKey === "hidden" && (typeof value === "boolean" || typeof value === "number")) {
return shared.includeBooleanAttr(value) ? ` ${attrKey}` : ``;
} else if (shared.isSSRSafeAttrName(attrKey)) {
return value === "" ? ` ${attrKey}` : ` ${attrKey}="${shared.escapeHtml(value)}"`;
} else {
console.warn(
`[@vue/server-renderer] Skipped rendering unsafe attribute name: ${attrKey}`
);
return ``;
}
}
function ssrRenderAttr(key, value) {
if (!shared.isRenderableAttrValue(value)) {
return ``;
}
return ` ${key}="${shared.escapeHtml(value)}"`;
}
function ssrRenderClass(raw) {
return shared.escapeHtml(shared.normalizeClass(raw));
}
function ssrRenderStyle(raw) {
if (!raw) {
return "";
}
if (shared.isString(raw)) {
return shared.escapeHtml(raw);
}
const styles = shared.normalizeStyle(ssrResetCssVars(raw));
return shared.escapeHtml(shared.stringifyStyle(styles));
}
function ssrResetCssVars(raw) {
if (!shared.isArray(raw) && shared.isObject(raw)) {
const res = {};
for (const key in raw) {
if (key.startsWith(":--")) {
res[key.slice(1)] = shared.normalizeCssVarValue(raw[key]);
} else {
res[key] = raw[key];
}
}
return res;
}
return raw;
}
function ssrRenderComponent(comp, props = null, children = null, parentComponent = null, slotScopeId) {
return renderComponentVNode(
Vue.createVNode(comp, props, children),
parentComponent,
slotScopeId
);
}
const { ensureValidVNode } = Vue.ssrUtils;
function ssrRenderSlot(slots, slotName, slotProps, fallbackRenderFn, push, parentComponent, slotScopeId) {
push(`<!--[-->`);
ssrRenderSlotInner(
slots,
slotName,
slotProps,
fallbackRenderFn,
push,
parentComponent,
slotScopeId
);
push(`<!--]-->`);
}
function ssrRenderSlotInner(slots, slotName, slotProps, fallbackRenderFn, push, parentComponent, slotScopeId, transition) {
const slotFn = slots[slotName];
if (slotFn) {
const slotBuffer = [];
const bufferedPush = (item) => {
slotBuffer.push(item);
};
const ret = slotFn(
// keep the slot function's contract in sync with `renderSlot`, which also
// normalizes nullish props before invoking the slot
slotProps || {},
bufferedPush,
parentComponent,
slotScopeId ? " " + slotScopeId : ""
);
if (shared.isArray(ret)) {
const validSlotContent = ensureValidVNode(ret);
if (validSlotContent) {
renderVNodeChildren(
push,
validSlotContent,
parentComponent,
slotScopeId
);
} else if (fallbackRenderFn) {
fallbackRenderFn();
} else if (transition) {
push(`<!---->`);
}
} else {
let isEmptySlot = true;
if (transition) {
isEmptySlot = false;
} else {
for (let i = 0; i < slotBuffer.length; i++) {
if (!isComment(slotBuffer[i])) {
isEmptySlot = false;
break;
}
}
}
if (isEmptySlot) {
if (fallbackRenderFn) {
fallbackRenderFn();
}
} else {
let start = 0;
let end = slotBuffer.length;
if (transition && slotBuffer[0] === "<!--[-->" && slotBuffer[end - 1] === "<!--]-->") {
start++;
end--;
}
if (start < end) {
for (let i = start; i < end; i++) {
push(slotBuffer[i]);
}
} else if (transition) {
push(`<!---->`);
}
}
}
} else if (fallbackRenderFn) {
fallbackRenderFn();
} else if (transition) {
push(`<!---->`);
}
}
const commentTestRE = /^<!--[\s\S]*-->$/;
const commentRE = /<!--[^]*?-->/gm;
function isComment(item) {
if (typeof item !== "string" || !commentTestRE.test(item)) return false;
if (item.length <= 8) return true;
return !item.replace(commentRE, "").trim();
}
function ssrRenderTeleport(parentPush, contentRenderFn, target, disabled, parentComponent) {
parentPush("<!--teleport start-->");
const context = parentComponent.appContext.provides[Vue.ssrContextKey];
const teleportBuffers = context.__teleportBuffers || (context.__teleportBuffers = {});
const targetBuffer = teleportBuffers[target] || (teleportBuffers[target] = []);
const bufferIndex = targetBuffer.length;
let teleportContent;
if (disabled) {
contentRenderFn(parentPush);
teleportContent = `<!--teleport start anchor--><!--teleport anchor-->`;
} else {
const { getBuffer, push } = createBuffer();
push(`<!--teleport start anchor-->`);
contentRenderFn(push);
push(`<!--teleport anchor-->`);
teleportContent = getBuffer();
}
targetBuffer.splice(bufferIndex, 0, teleportContent);
if (shared.isPromise(teleportContent) || shared.isArray(teleportContent) && teleportContent.hasAsync) {
targetBuffer.hasAsync = true;
}
parentPush("<!--teleport end-->");
}
function ssrInterpolate(value) {
return shared.escapeHtml(shared.toDisplayString(value));
}
function ssrRenderList(source, renderItem) {
if (shared.isArray(source) || shared.isString(source)) {
for (let i = 0, l = source.length; i < l; i++) {
renderItem(source[i], i);
}
} else if (typeof source === "number") {
if (!Number.isInteger(source) || source < 0) {
return;
}
for (let i = 0; i < source; i++) {
renderItem(i + 1, i);
}
} else if (shared.isObject(source)) {
if (source[Symbol.iterator]) {
let i = 0;
for (const item of source) {
renderItem(item, i++);
}
} else {
const keys = Object.keys(source);
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i];
renderItem(source[key], key, i);
}
}
}
}
function ssrRenderSuspense(push, { default: renderContent }) {
if (renderContent) {
renderContent();
} else {
push(`<!---->`);
}
}
function ssrGetDirectiveProps(instance, dir, value, arg, modifiers = {}) {
if (typeof dir !== "function" && dir.getSSRProps) {
return dir.getSSRProps(
{
dir,
instance: Vue.ssrUtils.getComponentPublicInstance(instance.$),
value,
oldValue: void 0,
arg,
modifiers
},
null
) || {};
}
return {};
}
const ssrLooseEqual = shared.looseEqual;
function ssrLooseContain(arr, value) {
return shared.looseIndexOf(arr, value) > -1;
}
function ssrRenderDynamicModel(type, model, value) {
switch (type) {
case "radio":
return shared.looseEqual(model, value) ? " checked" : "";
case "checkbox":
return (shared.isArray(model) ? ssrLooseContain(model, value) : model) ? " checked" : "";
default:
return ssrRenderAttr("value", model);
}
}
function ssrGetDynamicModelProps(existingProps = {}, model) {
const { type, value } = existingProps;
switch (type) {
case "radio":
return shared.looseEqual(model, value) ? { checked: true } : null;
case "checkbox":
return (shared.isArray(model) ? ssrLooseContain(model, value) : model) ? { checked: true } : null;
default:
return { value: model };
}
}
var helpers = /*#__PURE__*/Object.freeze({
__proto__: null,
ssrGetDirectiveProps: ssrGetDirectiveProps,
ssrGetDynamicModelProps: ssrGetDynamicModelProps,
ssrIncludeBooleanAttr: shared.includeBooleanAttr,
ssrInterpolate: ssrInterpolate,
ssrLooseContain: ssrLooseContain,
ssrLooseEqual: ssrLooseEqual,
ssrRenderAttr: ssrRenderAttr,
ssrRenderAttrs: ssrRenderAttrs,
ssrRenderClass: ssrRenderClass,
ssrRenderComponent: ssrRenderComponent,
ssrRenderDynamicAttr: ssrRenderDynamicAttr,
ssrRenderDynamicModel: ssrRenderDynamicModel,
ssrRenderList: ssrRenderList,
ssrRenderSlot: ssrRenderSlot,
ssrRenderSlotInner: ssrRenderSlotInner,
ssrRenderStyle: ssrRenderStyle,
ssrRenderSuspense: ssrRenderSuspense,
ssrRenderTeleport: ssrRenderTeleport,
ssrRenderVNode: renderVNode
});
const compileCache = /* @__PURE__ */ Object.create(null);
function ssrCompile(template, instance) {
const Component = instance.type;
const { isCustomElement, compilerOptions } = instance.appContext.config;
const { delimiters, compilerOptions: componentCompilerOptions } = Component;
const finalCompilerOptions = shared.extend(
shared.extend(
{
isCustomElement,
delimiters
},
compilerOptions
),
componentCompilerOptions
);
finalCompilerOptions.isCustomElement = finalCompilerOptions.isCustomElement || shared.NO;
finalCompilerOptions.isNativeTag = finalCompilerOptions.isNativeTag || shared.NO;
const cacheKey = JSON.stringify(
{
template,
compilerOptions: finalCompilerOptions
},
(key, value) => {
return shared.isFunction(value) ? value.toString() : value;
}
);
const cached = compileCache[cacheKey];
if (cached) {
return cached;
}
finalCompilerOptions.onError = (err) => {
{
throw err;
}
};
const { code } = compilerSsr.compile(template, finalCompilerOptions);
const requireMap = {
vue: Vue__namespace,
"vue/server-renderer": helpers
};
const fakeRequire = (id) => requireMap[id];
return compileCache[cacheKey] = Function("require", code)(fakeRequire);
}
const {
createComponentInstance,
setCurrentRenderingInstance,
setupComponent,
renderComponentRoot,
normalizeVNode,
pushWarningContext,
popWarningContext
} = Vue.ssrUtils;
function createBuffer() {
let appendable = false;
const buffer = [];
return {
getBuffer() {
return buffer;
},
push(item) {
const isStringItem = shared.isString(item);
if (appendable && isStringItem) {
buffer[buffer.length - 1] += item;
return;
}
buffer.push(item);
appendable = isStringItem;
if (shared.isPromise(item) || shared.isArray(item) && item.hasAsync) {
buffer.hasAsync = true;
}
}
};
}
function renderComponentVNode(vnode, parentComponent = null, slotScopeId) {
const instance = vnode.component = createComponentInstance(
vnode,
parentComponent,
null
);
const res = setupComponent(
instance,
true
/* isSSR */
);
const hasAsyncSetup = shared.isPromise(res);
let prefetches = instance.sp;
let p;
if (hasAsyncSetup || prefetches) {
p = Promise.resolve(res).then(() => {
if (hasAsyncSetup) prefetches = instance.sp;
if (prefetches) {
try {
return Promise.all(
prefetches.map((prefetch) => prefetch.call(instance.proxy))
).catch(shared.NOOP);
} catch {
}
}
}).then(() => renderComponentSubTree(instance, slotScopeId));
} else {
try {
return renderComponentSubTree(instance, slotScopeId);
} catch (err) {
p = Promise.reject(err);
}
}
p.catch(shared.NOOP);
return p;
}
function renderComponentSubTree(instance, slotScopeId) {
const comp = instance.type;
const { getBuffer, push } = createBuffer();
if (shared.isFunction(comp)) {
let root = renderComponentRoot(instance);
if (!comp.props) {
for (const key in instance.attrs) {
if (key.startsWith(`data-v-`)) {
(root.props || (root.props = {}))[key] = ``;
}
}
}
renderVNode(push, instance.subTree = root, instance, slotScopeId);
} else {
if ((!instance.render || instance.render === shared.NOOP) && !instance.ssrRender && !comp.ssrRender && shared.isString(comp.template)) {
comp.ssrRender = ssrCompile(comp.template, instance);
}
const ssrRender = instance.ssrRender || comp.ssrRender;
if (ssrRender) {
let attrs = instance.inheritAttrs !== false ? instance.attrs : void 0;
let hasCloned = false;
let cur = instance;
while (true) {
const scopeId = cur.vnode.scopeId;
if (scopeId) {
if (!hasCloned) {
attrs = { ...attrs };
hasCloned = true;
}
attrs[scopeId] = "";
}
const parent = cur.parent;
if (parent && parent.subTree && parent.subTree === cur.vnode) {
cur = parent;
} else {
break;
}
}
if (slotScopeId) {
if (!hasCloned) attrs = { ...attrs };
const slotScopeIdList = slotScopeId.trim().split(" ");
for (let i = 0; i < slotScopeIdList.length; i++) {
attrs[slotScopeIdList[i]] = "";
}
}
const prev = setCurrentRenderingInstance(instance);
try {
ssrRender(
instance.proxy,
push,
instance,
attrs,
// compiler-optimized bindings
instance.props,
instance.setupState,
instance.data,
instance.ctx
);
} catch (err) {
Vue.handleError(err, instance, 1);
} finally {
setCurrentRenderingInstance(prev);
}
} else if (instance.render && instance.render !== shared.NOOP) {
renderVNode(
push,
instance.subTree = renderComponentRoot(instance),
instance,
slotScopeId
);
} else {
const componentName = comp.name || comp.__file || `<Anonymous>`;
Vue.warn(`Component ${componentName} is missing template or render function.`);
push(`<!---->`);
}
}
return getBuffer();
}
function renderVNode(push, vnode, parentComponent, slotScopeId) {
const { type, shapeFlag, children, dirs, props } = vnode;
if (dirs) {
vnode.props = applySSRDirectives(vnode, props, dirs);
}
switch (type) {
case Vue.Text:
push(shared.escapeHtml(children));
break;
case Vue.Comment:
push(
children ? `<!--${shared.escapeHtmlComment(children)}-->` : `<!---->`
);
break;
case Vue.Static:
push(children);
break;
case Vue.Fragment:
if (vnode.slotScopeIds) {
slotScopeId = (slotScopeId ? slotScopeId + " " : "") + vnode.slotScopeIds.join(" ");
}
push(`<!--[-->`);
renderVNodeChildren(
push,
children,
parentComponent,
slotScopeId
);
push(`<!--]-->`);
break;
default:
if (shapeFlag & 1) {
renderElementVNode(push, vnode, parentComponent, slotScopeId);
} else if (shapeFlag & 6) {
push(renderComponentVNode(vnode, parentComponent, slotScopeId));
} else if (shapeFlag & 64) {
renderTeleportVNode(push, vnode, parentComponent, slotScopeId);
} else if (shapeFlag & 128) {
renderVNode(push, vnode.ssContent, parentComponent, slotScopeId);
} else {
Vue.warn(
"[@vue/server-renderer] Invalid VNode type:",
type,
`(${typeof type})`
);
}
}
}
function renderVNodeChildren(push, children, parentComponent, slotScopeId) {
for (let i = 0; i < children.length; i++) {
renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId);
}
}
function renderElementVNode(push, vnode, parentComponent, slotScopeId) {
const tag = vnode.type;
let { props, children, shapeFlag, scopeId } = vnode;
let openTag = `<${tag}`;
if (props) {
openTag += ssrRenderAttrs(props, tag);
}
const renderedScopeIds = [];
const appendScopeId = (id) => {
if (id && (!props || !shared.hasOwn(props, id)) && !renderedScopeIds.includes(id)) {
openTag += ` ${id}`;
renderedScopeIds.push(id);
}
};
if (scopeId) {
appendScopeId(scopeId);
}
let curParent = parentComponent;
let curVnode = vnode;
while (curParent && curVnode === curParent.subTree) {
curVnode = curParent.vnode;
if (curVnode.scopeId) {
appendScopeId(curVnode.scopeId);
}
curParent = curParent.parent;
}
if (slotScopeId) {
const slotScopeIdList = slotScopeId.trim().split(" ");
for (let i = 0; i < slotScopeIdList.length; i++) {
appendScopeId(slotScopeIdList[i]);
}
}
push(openTag + `>`);
if (!shared.isVoidTag(tag)) {
let hasChildrenOverride = false;
if (props) {
if (props.innerHTML != null) {
hasChildrenOverride = true;
push(String(props.innerHTML));
} else if (props.textContent != null) {
hasChildrenOverride = true;
push(shared.escapeHtml(props.textContent));
} else if (tag === "textarea" && props.value != null) {
hasChildrenOverride = true;
push(shared.escapeHtml(props.value));
}
}
if (!hasChildrenOverride) {
if (shapeFlag & 8) {
push(shared.escapeHtml(children));
} else if (shapeFlag & 16) {
renderVNodeChildren(
push,
children,
parentComponent,
slotScopeId
);
}
}
push(`</${tag}>`);
}
}
function applySSRDirectives(vnode, rawProps, dirs) {
const toMerge = [];
for (let i = 0; i < dirs.length; i++) {
const binding = dirs[i];
const {
dir: { getSSRProps }
} = binding;
if (getSSRProps) {
const props = getSSRProps(binding, vnode);
if (props) toMerge.push(props);
}
}
return Vue.mergeProps(rawProps || {}, ...toMerge);
}
function renderTeleportVNode(push, vnode, parentComponent, slotScopeId) {
const target = vnode.props && vnode.props.to;
const disabled = vnode.props && vnode.props.disabled;
if (!target) {
if (!disabled) {
Vue.warn(`[@vue/server-renderer] Teleport is missing target prop.`);
}
return [];
}
if (!shared.isString(target)) {
Vue.warn(
`[@vue/server-renderer] Teleport target must be a query selector string.`
);
return [];
}
ssrRenderTeleport(
push,
(push2) => {
renderVNodeChildren(
push2,
vnode.children,
parentComponent,
slotScopeId
);
},
target,
disabled || disabled === "",
parentComponent
);
}
const { isVNode: isVNode$1 } = Vue.ssrUtils;
function nestedUnrollBuffer(buffer, parentRet, startIndex) {
if (!buffer.hasAsync) {
return parentRet + unrollBufferSync$1(buffer);
}
let ret = parentRet;
for (let i = startIndex; i < buffer.length; i += 1) {
const item = buffer[i];
if (shared.isString(item)) {
ret += item;
continue;
}
if (shared.isPromise(item)) {
return item.then((nestedItem) => {
buffer[i] = nestedItem;
return nestedUnrollBuffer(buffer, ret, i);
});
}
const result = nestedUnrollBuffer(item, ret, 0);
if (shared.isPromise(result)) {
return result.then((nestedItem) => {
buffer[i] = nestedItem;
return nestedUnrollBuffer(buffer, "", i);
});
}
ret = result;
}
return ret;
}
function unrollBuffer$1(buffer) {
return nestedUnrollBuffer(buffer, "", 0);
}
function unrollBufferSync$1(buffer) {
let ret = "";
for (let i = 0; i < buffer.length; i++) {
let item = buffer[i];
if (shared.isString(item)) {
ret += item;
} else {
ret += unrollBufferSync$1(item);
}
}
return ret;
}
async function renderToString(input, context = {}) {
if (isVNode$1(input)) {
return renderToString(Vue.createApp({ render: () => input }), context);
}
const vnode = Vue.createVNode(input._component, input._props);
vnode.appContext = input._context;
input.provide(Vue.ssrContextKey, context);
let buffer;
let result;
try {
buffer = await renderComponentVNode(vnode);
result = await unrollBuffer$1(buffer);
await resolveTeleports(context);
} catch (err) {
cleanupFailedRender(buffer, context);
throw err;
}
stopWatchers(context);
return result;
}
function stopWatchers(context, start = 0) {
const handles = context.__watcherHandles;
if (handles) {
for (let i = start; i < handles.length; i++) {
handles[i]();
}
}
}
function cleanupFailedRender(buffer, context) {
const stopped = context.__watcherHandles ? context.__watcherHandles.length : 0;
try {
stopWatchers(context);
} catch {
}
(async () => {
if (buffer) await settleBuffer(buffer);
for (const key in context.__teleportBuffers) {
await settleBuffer(context.__teleportBuffers[key]);
}
stopWatchers(context, stopped);
})().catch(shared.NOOP);
}
async function settleBuffer(buffer) {
if (!buffer.hasAsync) return;
for (let item of buffer) {
try {
if (shared.isPromise(item)) item = await item;
if (!shared.isString(item)) await settleBuffer(item);
} catch {
}
}
}
async function resolveTeleports(context) {
if (context.__teleportBuffers) {
context.teleports = context.teleports || {};
for (const key in context.__teleportBuffers) {
context.teleports[key] = await unrollBuffer$1(
context.__teleportBuffers[key]
);
}
}
}
const { isVNode } = Vue.ssrUtils;
async function unrollBuffer(buffer, stream) {
if (buffer.hasAsync) {
for (let i = 0; i < buffer.length; i++) {
let item = buffer[i];
if (shared.isPromise(item)) {
item = await item;
}
if (shared.isString(item)) {
stream.push(item);
} else {
await unrollBuffer(item, stream);
}
}
} else {
unrollBufferSync(buffer, stream);
}
}
function unrollBufferSync(buffer, stream) {
for (let i = 0; i < buffer.length; i++) {
let item = buffer[i];
if (shared.isString(item)) {
stream.push(item);
} else {
unrollBufferSync(item, stream);
}
}
}
function renderToSimpleStream(input, context, stream) {
if (isVNode(input)) {
return renderToSimpleStream(
Vue.createApp({ render: () => input }),
context,
stream
);
}
const vnode = Vue.createVNode(input._component, input._props);
vnode.appContext = input._context;
input.provide(Vue.ssrContextKey, context);
let buffer;
Promise.resolve(renderComponentVNode(vnode)).then((b) => unrollBuffer(buffer = b, stream)).then(() => resolveTeleports(context)).then(
() => {
stopWatchers(context);
stream.push(null);
},
(error) => {
cleanupFailedRender(buffer, context);
throw error;
}
).catch((error) => {
stream.destroy(error);
});
return stream;
}
function renderToStream(input, context = {}) {
console.warn(
`[@vue/server-renderer] renderToStream is deprecated - use renderToNodeStream instead.`
);
return renderToNodeStream(input, context);
}
function renderToNodeStream(input, context = {}) {
const stream = new (require("node:stream")).Readable({ read() {
} }) ;
if (!stream) {
throw new Error(
`ESM build of renderToStream() does not support renderToNodeStream(). Use pipeToNodeWritable() with an existing Node.js Writable stream instance instead.`
);
}
return renderToSimpleStream(input, context, stream);
}
function pipeToNodeWritable(input, context = {}, writable) {
renderToSimpleStream(input, context, {
push(content) {
if (content != null) {
writable.write(content);
} else {
writable.end();
}
},
destroy(err) {
writable.destroy(err);
}
});
}
function renderToWebStream(input, context = {}) {
if (typeof ReadableStream !== "function") {
throw new Error(
`ReadableStream constructor is not available in the global scope. If the target environment does support web streams, consider using pipeToWebWritable() with an existing WritableStream instance instead.`
);
}
const encoder = new TextEncoder();
let cancelled = false;
return new ReadableStream({
start(controller) {
renderToSimpleStream(input, context, {
push(content) {
if (cancelled) return;
if (content != null) {
controller.enqueue(encoder.encode(content));
} else {
controller.close();
}
},
destroy(err) {
controller.error(err);
}
});
},
cancel() {
cancelled = true;
}
});
}
function pipeToWebWritable(input, context = {}, writable) {
const writer = writable.getWriter();
const encoder = new TextEncoder();
let hasReady = false;
try {
hasReady = shared.isPromise(writer.ready);
} catch (e) {
}
renderToSimpleStream(input, context, {
async push(content) {
if (hasReady) {
await writer.ready;
}
if (content != null) {
return writer.write(encoder.encode(content));
} else {
return writer.close();
}
},
destroy(err) {
console.log(err);
writer.close();
}
});
}
Vue.initDirectivesForSSR();
exports.ssrIncludeBooleanAttr = shared.includeBooleanAttr;
exports.pipeToNodeWritable = pipeToNodeWritable;
exports.pipeToWebWritable = pipeToWebWritable;
exports.renderToNodeStream = renderToNodeStream;
exports.renderToSimpleStream = renderToSimpleStream;
exports.renderToStream = renderToStream;
exports.renderToString = renderToString;
exports.renderToWebStream = renderToWebStream;
exports.ssrGetDirectiveProps = ssrGetDirectiveProps;
exports.ssrGetDynamicModelProps = ssrGetDynamicModelProps;
exports.ssrInterpolate = ssrInterpolate;
exports.ssrLooseContain = ssrLooseContain;
exports.ssrLooseEqual = ssrLooseEqual;
exports.ssrRenderAttr = ssrRenderAttr;
exports.ssrRenderAttrs = ssrRenderAttrs;
exports.ssrRenderClass = ssrRenderClass;
exports.ssrRenderComponent = ssrRenderComponent;
exports.ssrRenderDynamicAttr = ssrRenderDynamicAttr;
exports.ssrRenderDynamicModel = ssrRenderDynamicModel;
exports.ssrRenderList = ssrRenderList;
exports.ssrRenderSlot = ssrRenderSlot;
exports.ssrRenderSlotInner = ssrRenderSlotInner;
exports.ssrRenderStyle = ssrRenderStyle;
exports.ssrRenderSuspense = ssrRenderSuspense;
exports.ssrRenderTeleport = ssrRenderTeleport;
exports.ssrRenderVNode = renderVNode;
+53
View File
@@ -0,0 +1,53 @@
{
"name": "@vue/server-renderer",
"version": "3.5.44",
"description": "@vue/server-renderer",
"main": "index.js",
"module": "dist/server-renderer.esm-bundler.js",
"types": "dist/server-renderer.d.ts",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/server-renderer.d.ts",
"node": {
"production": "./dist/server-renderer.cjs.prod.js",
"development": "./dist/server-renderer.cjs.js",
"default": "./dist/server-renderer.cjs.prod.js"
},
"module": "./dist/server-renderer.esm-bundler.js",
"import": "./dist/server-renderer.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"buildOptions": {
"name": "VueServerRenderer",
"formats": [
"esm-bundler",
"esm-browser",
"cjs"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/server-renderer"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme",
"dependencies": {
"@vue/shared": "3.5.44",
"@vue/compiler-ssr": "3.5.44",
"@vue/runtime-dom": "3.5.44"
}
}
@@ -0,0 +1,655 @@
/**
* @vue/shared v3.5.44
* (c) 2018-present Yuxi (Evan) You and Vue contributors
* @license MIT
**/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
// @__NO_SIDE_EFFECTS__
function makeMap(str) {
const map = /* @__PURE__ */ Object.create(null);
for (const key of str.split(",")) map[key] = 1;
return (val) => val in map;
}
const EMPTY_OBJ = {};
const EMPTY_ARR = [];
const NOOP = () => {
};
const NO = () => false;
const isOn = (key) => key.charCodeAt(0) === 111 && key.charCodeAt(1) === 110 && // uppercase letter
(key.charCodeAt(2) > 122 || key.charCodeAt(2) < 97);
const isModelListener = (key) => key.startsWith("onUpdate:");
const extend = Object.assign;
const remove = (arr, el) => {
const i = arr.indexOf(el);
if (i > -1) {
arr.splice(i, 1);
}
};
const hasOwnProperty = Object.prototype.hasOwnProperty;
const hasOwn = (val, key) => hasOwnProperty.call(val, key);
const isArray = Array.isArray;
const isMap = (val) => toTypeString(val) === "[object Map]";
const isSet = (val) => toTypeString(val) === "[object Set]";
const isDate = (val) => toTypeString(val) === "[object Date]";
const isRegExp = (val) => toTypeString(val) === "[object RegExp]";
const isFunction = (val) => typeof val === "function";
const isString = (val) => typeof val === "string";
const isSymbol = (val) => typeof val === "symbol";
const isObject = (val) => val !== null && typeof val === "object";
const isPromise = (val) => {
return (isObject(val) || isFunction(val)) && isFunction(val.then) && isFunction(val.catch);
};
const objectToString = Object.prototype.toString;
const toTypeString = (value) => objectToString.call(value);
const toRawType = (value) => {
return toTypeString(value).slice(8, -1);
};
const isPlainObject = (val) => toTypeString(val) === "[object Object]";
const isIntegerKey = (key) => isString(key) && key !== "NaN" && key[0] !== "-" && "" + parseInt(key, 10) === key;
const isReservedProp = /* @__PURE__ */ makeMap(
// the leading comma is intentional so empty string "" is also included
",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"
);
const isBuiltInDirective = /* @__PURE__ */ makeMap(
"bind,cloak,else-if,else,for,html,if,model,on,once,pre,show,slot,text,memo"
);
const cacheStringFunction = (fn) => {
const cache = /* @__PURE__ */ Object.create(null);
return ((str) => {
const hit = cache[str];
return hit || (cache[str] = fn(str));
});
};
const camelizeRE = /-\w/g;
const camelize = cacheStringFunction(
(str) => {
return str.replace(camelizeRE, (c) => c.slice(1).toUpperCase());
}
);
const hyphenateRE = /\B([A-Z])/g;
const hyphenate = cacheStringFunction(
(str) => str.replace(hyphenateRE, "-$1").toLowerCase()
);
const capitalize = cacheStringFunction((str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
});
const toHandlerKey = cacheStringFunction(
(str) => {
const s = str ? `on${capitalize(str)}` : ``;
return s;
}
);
const hasChanged = (value, oldValue) => !Object.is(value, oldValue);
const invokeArrayFns = (fns, ...arg) => {
for (let i = 0; i < fns.length; i++) {
fns[i](...arg);
}
};
const def = (obj, key, value, writable = false) => {
Object.defineProperty(obj, key, {
configurable: true,
enumerable: false,
writable,
value
});
};
const looseToNumber = (val) => {
const n = parseFloat(val);
return isNaN(n) ? val : n;
};
const toNumber = (val) => {
const n = isString(val) ? Number(val) : NaN;
return isNaN(n) ? val : n;
};
let _globalThis;
const getGlobalThis = () => {
return _globalThis || (_globalThis = typeof globalThis !== "undefined" ? globalThis : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : {});
};
const identRE = /^[_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*$/;
function genPropsAccessExp(name) {
return identRE.test(name) ? `__props.${name}` : `__props[${JSON.stringify(name)}]`;
}
function genCacheKey(source, options) {
return source + JSON.stringify(
options,
(_, val) => typeof val === "function" ? val.toString() : val
);
}
const PatchFlags = {
"TEXT": 1,
"1": "TEXT",
"CLASS": 2,
"2": "CLASS",
"STYLE": 4,
"4": "STYLE",
"PROPS": 8,
"8": "PROPS",
"FULL_PROPS": 16,
"16": "FULL_PROPS",
"NEED_HYDRATION": 32,
"32": "NEED_HYDRATION",
"STABLE_FRAGMENT": 64,
"64": "STABLE_FRAGMENT",
"KEYED_FRAGMENT": 128,
"128": "KEYED_FRAGMENT",
"UNKEYED_FRAGMENT": 256,
"256": "UNKEYED_FRAGMENT",
"NEED_PATCH": 512,
"512": "NEED_PATCH",
"DYNAMIC_SLOTS": 1024,
"1024": "DYNAMIC_SLOTS",
"DEV_ROOT_FRAGMENT": 2048,
"2048": "DEV_ROOT_FRAGMENT",
"CACHED": -1,
"-1": "CACHED",
"BAIL": -2,
"-2": "BAIL"
};
const PatchFlagNames = {
[1]: `TEXT`,
[2]: `CLASS`,
[4]: `STYLE`,
[8]: `PROPS`,
[16]: `FULL_PROPS`,
[32]: `NEED_HYDRATION`,
[64]: `STABLE_FRAGMENT`,
[128]: `KEYED_FRAGMENT`,
[256]: `UNKEYED_FRAGMENT`,
[512]: `NEED_PATCH`,
[1024]: `DYNAMIC_SLOTS`,
[2048]: `DEV_ROOT_FRAGMENT`,
[-1]: `CACHED`,
[-2]: `BAIL`
};
const ShapeFlags = {
"ELEMENT": 1,
"1": "ELEMENT",
"FUNCTIONAL_COMPONENT": 2,
"2": "FUNCTIONAL_COMPONENT",
"STATEFUL_COMPONENT": 4,
"4": "STATEFUL_COMPONENT",
"TEXT_CHILDREN": 8,
"8": "TEXT_CHILDREN",
"ARRAY_CHILDREN": 16,
"16": "ARRAY_CHILDREN",
"SLOTS_CHILDREN": 32,
"32": "SLOTS_CHILDREN",
"TELEPORT": 64,
"64": "TELEPORT",
"SUSPENSE": 128,
"128": "SUSPENSE",
"COMPONENT_SHOULD_KEEP_ALIVE": 256,
"256": "COMPONENT_SHOULD_KEEP_ALIVE",
"COMPONENT_KEPT_ALIVE": 512,
"512": "COMPONENT_KEPT_ALIVE",
"COMPONENT": 6,
"6": "COMPONENT"
};
const SlotFlags = {
"STABLE": 1,
"1": "STABLE",
"DYNAMIC": 2,
"2": "DYNAMIC",
"FORWARDED": 3,
"3": "FORWARDED"
};
const slotFlagsText = {
[1]: "STABLE",
[2]: "DYNAMIC",
[3]: "FORWARDED"
};
const GLOBALS_ALLOWED = "Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,console,Error,Symbol";
const isGloballyAllowed = /* @__PURE__ */ makeMap(GLOBALS_ALLOWED);
const isGloballyWhitelisted = isGloballyAllowed;
const range = 2;
function generateCodeFrame(source, start = 0, end = source.length) {
start = Math.max(0, Math.min(start, source.length));
end = Math.max(0, Math.min(end, source.length));
if (start > end) return "";
let lines = source.split(/(\r?\n)/);
const newlineSequences = lines.filter((_, idx) => idx % 2 === 1);
lines = lines.filter((_, idx) => idx % 2 === 0);
let count = 0;
const res = [];
for (let i = 0; i < lines.length; i++) {
count += lines[i].length + (newlineSequences[i] && newlineSequences[i].length || 0);
if (count >= start) {
for (let j = i - range; j <= i + range || end > count; j++) {
if (j < 0 || j >= lines.length) continue;
const line = j + 1;
res.push(
`${line}${" ".repeat(Math.max(3 - String(line).length, 0))}| ${lines[j]}`
);
const lineLength = lines[j].length;
const newLineSeqLength = newlineSequences[j] && newlineSequences[j].length || 0;
if (j === i) {
const pad = start - (count - (lineLength + newLineSeqLength));
const length = Math.max(
1,
end > count ? lineLength - pad : end - start
);
res.push(` | ` + " ".repeat(pad) + "^".repeat(length));
} else if (j > i) {
if (end > count) {
const length = Math.max(Math.min(end - count, lineLength), 1);
res.push(` | ` + "^".repeat(length));
}
count += lineLength + newLineSeqLength;
}
}
break;
}
}
return res.join("\n");
}
function normalizeStyle(value) {
if (isArray(value)) {
const res = {};
for (let i = 0; i < value.length; i++) {
const item = value[i];
const normalized = isString(item) ? parseStringStyle(item) : normalizeStyle(item);
if (normalized) {
for (const key in normalized) {
res[key] = normalized[key];
}
}
}
return res;
} else if (isString(value) || isObject(value)) {
return value;
}
}
const listDelimiterRE = /;(?![^(]*\))/g;
const propertyDelimiterRE = /:([^]+)/;
const styleCommentRE = /"(?:[^"\\]|\\[^])*"|'(?:[^'\\]|\\[^])*'|\\[^]|\/\*[^]*?\*\//g;
function parseStringStyle(cssText) {
const ret = {};
cssText.replace(styleCommentRE, (match) => match.startsWith("/*") ? "" : match).split(listDelimiterRE).forEach((item) => {
if (item) {
const tmp = item.split(propertyDelimiterRE);
tmp.length > 1 && (ret[tmp[0].trim()] = tmp[1].trim());
}
});
return ret;
}
function stringifyStyle(styles) {
if (!styles) return "";
if (isString(styles)) return styles;
let ret = "";
for (const key in styles) {
const value = styles[key];
if (isString(value) || typeof value === "number") {
const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key);
ret += `${normalizedKey}:${value};`;
}
}
return ret;
}
function normalizeClass(value) {
let res = "";
if (isString(value)) {
res = value;
} else if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
const normalized = normalizeClass(value[i]);
if (normalized) {
res += normalized + " ";
}
}
} else if (isObject(value)) {
for (const name in value) {
if (value[name]) {
res += name + " ";
}
}
}
return res.trim();
}
function normalizeProps(props) {
if (!props) return null;
let { class: klass, style } = props;
if (klass && !isString(klass)) {
props.class = normalizeClass(klass);
}
if (style) {
props.style = normalizeStyle(style);
}
return props;
}
const HTML_TAGS = "html,body,base,head,link,meta,style,title,address,article,aside,footer,header,hgroup,h1,h2,h3,h4,h5,h6,nav,section,div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,ruby,s,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,embed,object,param,source,canvas,script,noscript,del,ins,caption,col,colgroup,table,thead,tbody,td,th,tr,button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,output,progress,select,textarea,details,dialog,menu,summary,template,blockquote,iframe,tfoot";
const SVG_TAGS = "svg,animate,animateMotion,animateTransform,circle,clipPath,color-profile,defs,desc,discard,ellipse,feBlend,feColorMatrix,feComponentTransfer,feComposite,feConvolveMatrix,feDiffuseLighting,feDisplacementMap,feDistantLight,feDropShadow,feFlood,feFuncA,feFuncB,feFuncG,feFuncR,feGaussianBlur,feImage,feMerge,feMergeNode,feMorphology,feOffset,fePointLight,feSpecularLighting,feSpotLight,feTile,feTurbulence,filter,foreignObject,g,hatch,hatchpath,image,line,linearGradient,marker,mask,mesh,meshgradient,meshpatch,meshrow,metadata,mpath,path,pattern,polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,text,textPath,title,tspan,unknown,use,view";
const MATH_TAGS = "annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics";
const VOID_TAGS = "area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr";
const isHTMLTag = /* @__PURE__ */ makeMap(HTML_TAGS);
const isSVGTag = /* @__PURE__ */ makeMap(SVG_TAGS);
const isMathMLTag = /* @__PURE__ */ makeMap(MATH_TAGS);
const isVoidTag = /* @__PURE__ */ makeMap(VOID_TAGS);
const specialBooleanAttrs = `itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,playsinline,readonly`;
const isSpecialBooleanAttr = /* @__PURE__ */ makeMap(specialBooleanAttrs);
const isBooleanAttr = /* @__PURE__ */ makeMap(
specialBooleanAttrs + `,async,autofocus,autoplay,controls,default,defer,disabled,inert,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected`
);
function includeBooleanAttr(value) {
return !!value || value === "";
}
const unsafeAttrCharRE = /[>/="'\u0009\u000a\u000c\u000d\u0020]/;
const attrValidationCache = {};
function isSSRSafeAttrName(name) {
if (attrValidationCache.hasOwnProperty(name)) {
return attrValidationCache[name];
}
const isUnsafe = unsafeAttrCharRE.test(name);
if (isUnsafe) {
console.error(`unsafe attribute name: ${name}`);
}
return attrValidationCache[name] = !isUnsafe;
}
const propsToAttrMap = {
acceptCharset: "accept-charset",
className: "class",
htmlFor: "for",
httpEquiv: "http-equiv"
};
const isKnownHtmlAttr = /* @__PURE__ */ makeMap(
`accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,inert,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap`
);
const isKnownSvgAttr = /* @__PURE__ */ makeMap(
`xmlns,accent-height,accumulate,additive,alignment-baseline,alphabetic,amplitude,arabic-form,ascent,attributeName,attributeType,azimuth,baseFrequency,baseline-shift,baseProfile,bbox,begin,bias,by,calcMode,cap-height,class,clip,clipPathUnits,clip-path,clip-rule,color,color-interpolation,color-interpolation-filters,color-profile,color-rendering,contentScriptType,contentStyleType,crossorigin,cursor,cx,cy,d,decelerate,descent,diffuseConstant,direction,display,divisor,dominant-baseline,dur,dx,dy,edgeMode,elevation,enable-background,end,exponent,fill,fill-opacity,fill-rule,filter,filterRes,filterUnits,flood-color,flood-opacity,font-family,font-size,font-size-adjust,font-stretch,font-style,font-variant,font-weight,format,from,fr,fx,fy,g1,g2,glyph-name,glyph-orientation-horizontal,glyph-orientation-vertical,glyphRef,gradientTransform,gradientUnits,hanging,height,href,hreflang,horiz-adv-x,horiz-origin-x,id,ideographic,image-rendering,in,in2,intercept,k,k1,k2,k3,k4,kernelMatrix,kernelUnitLength,kerning,keyPoints,keySplines,keyTimes,lang,lengthAdjust,letter-spacing,lighting-color,limitingConeAngle,local,marker-end,marker-mid,marker-start,markerHeight,markerUnits,markerWidth,mask,maskContentUnits,maskUnits,mathematical,max,media,method,min,mode,name,numOctaves,offset,opacity,operator,order,orient,orientation,origin,overflow,overline-position,overline-thickness,panose-1,paint-order,path,pathLength,patternContentUnits,patternTransform,patternUnits,ping,pointer-events,points,pointsAtX,pointsAtY,pointsAtZ,preserveAlpha,preserveAspectRatio,primitiveUnits,r,radius,referrerPolicy,refX,refY,rel,rendering-intent,repeatCount,repeatDur,requiredExtensions,requiredFeatures,restart,result,rotate,rx,ry,scale,seed,shape-rendering,slope,spacing,specularConstant,specularExponent,speed,spreadMethod,startOffset,stdDeviation,stemh,stemv,stitchTiles,stop-color,stop-opacity,strikethrough-position,strikethrough-thickness,string,stroke,stroke-dasharray,stroke-dashoffset,stroke-linecap,stroke-linejoin,stroke-miterlimit,stroke-opacity,stroke-width,style,surfaceScale,systemLanguage,tabindex,tableValues,target,targetX,targetY,text-anchor,text-decoration,text-rendering,textLength,to,transform,transform-origin,type,u1,u2,underline-position,underline-thickness,unicode,unicode-bidi,unicode-range,units-per-em,v-alphabetic,v-hanging,v-ideographic,v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`
);
const isKnownMathMLAttr = /* @__PURE__ */ makeMap(
`accent,accentunder,actiontype,align,alignmentscope,altimg,altimg-height,altimg-valign,altimg-width,alttext,bevelled,close,columnsalign,columnlines,columnspan,denomalign,depth,dir,display,displaystyle,encoding,equalcolumns,equalrows,fence,fontstyle,fontweight,form,frame,framespacing,groupalign,height,href,id,indentalign,indentalignfirst,indentalignlast,indentshift,indentshiftfirst,indentshiftlast,indextype,justify,largetop,largeop,lquote,lspace,mathbackground,mathcolor,mathsize,mathvariant,maxsize,minlabelspacing,mode,other,overflow,position,rowalign,rowlines,rowspan,rquote,rspace,scriptlevel,scriptminsize,scriptsizemultiplier,selection,separator,separators,shift,side,src,stackalign,stretchy,subscriptshift,superscriptshift,symmetric,voffset,width,widths,xlink:href,xlink:show,xlink:type,xmlns`
);
function isRenderableAttrValue(value) {
if (value == null) {
return false;
}
const type = typeof value;
return type === "string" || type === "number" || type === "boolean";
}
const escapeRE = /["'&<>]/;
function escapeHtml(string) {
const str = "" + string;
const match = escapeRE.exec(str);
if (!match) {
return str;
}
let html = "";
let escaped;
let index;
let lastIndex = 0;
for (index = match.index; index < str.length; index++) {
switch (str.charCodeAt(index)) {
case 34:
escaped = "&quot;";
break;
case 38:
escaped = "&amp;";
break;
case 39:
escaped = "&#39;";
break;
case 60:
escaped = "&lt;";
break;
case 62:
escaped = "&gt;";
break;
default:
continue;
}
if (lastIndex !== index) {
html += str.slice(lastIndex, index);
}
lastIndex = index + 1;
html += escaped;
}
return lastIndex !== index ? html + str.slice(lastIndex, index) : html;
}
const commentStripRE = /^(?:-?>)+|<!--|-->|--!>|<!-$/g;
function escapeHtmlComment(src) {
let prev;
do {
prev = src;
src = src.replace(commentStripRE, "");
} while (src !== prev);
return src;
}
const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g;
function getEscapedCssVarName(key, doubleEscape) {
return key.replace(
cssVarNameEscapeSymbolsRE,
(s) => doubleEscape ? s === '"' ? '\\\\\\"' : `\\\\${s}` : `\\${s}`
);
}
function looseCompareArrays(a, b, seen) {
if (a.length !== b.length) return false;
let equal = true;
for (let i = 0; equal && i < a.length; i++) {
equal = looseEqual(a[i], b[i], seen);
}
return equal;
}
function looseCompareCollections(a, b, seen) {
if (a.size !== b.size) return false;
const candidates = Array.from(b);
const matched = new Uint8Array(candidates.length);
for (const item of a) {
let index = -1;
for (let i = 0; i < candidates.length; i++) {
if (!matched[i] && looseEqual(item, candidates[i], seen)) {
index = i;
break;
}
}
if (index < 0) return false;
matched[index] = 1;
}
return true;
}
function looseCompareObjects(a, b, seen) {
let aValidType = isMap(a);
let bValidType = isMap(b);
if (aValidType || bValidType) {
return aValidType && bValidType ? looseCompareCollections(a, b, seen) : false;
}
aValidType = isSet(a);
bValidType = isSet(b);
if (aValidType || bValidType) {
return aValidType && bValidType ? looseCompareCollections(a, b, seen) : false;
}
const aKeysCount = Object.keys(a).length;
const bKeysCount = Object.keys(b).length;
if (aKeysCount !== bKeysCount) {
return false;
}
for (const key in a) {
const aHasKey = a.hasOwnProperty(key);
const bHasKey = b.hasOwnProperty(key);
if (aHasKey && !bHasKey || !aHasKey && bHasKey || !looseEqual(a[key], b[key], seen)) {
return false;
}
}
return String(a) === String(b);
}
function looseCompareNested(a, b, seen, compare) {
if (!seen) {
seen = [/* @__PURE__ */ new Map(), /* @__PURE__ */ new Map()];
}
const [seenA, seenB] = seen;
if (seenA.has(a) || seenB.has(b)) {
return seenA.get(a) === b && seenB.get(b) === a;
}
seenA.set(a, b);
seenB.set(b, a);
const equal = compare(a, b, seen);
seenA.delete(a);
seenB.delete(b);
return equal;
}
function looseEqual(a, b, seen) {
if (a === b) return true;
let aValidType = isDate(a);
let bValidType = isDate(b);
if (aValidType || bValidType) {
return aValidType && bValidType ? a.getTime() === b.getTime() : false;
}
aValidType = isSymbol(a);
bValidType = isSymbol(b);
if (aValidType || bValidType) {
return a === b;
}
aValidType = isArray(a);
bValidType = isArray(b);
if (aValidType || bValidType) {
return aValidType && bValidType ? looseCompareNested(a, b, seen, looseCompareArrays) : false;
}
aValidType = isObject(a);
bValidType = isObject(b);
if (aValidType || bValidType) {
if (!aValidType || !bValidType) {
return false;
}
return looseCompareNested(a, b, seen, looseCompareObjects);
}
return String(a) === String(b);
}
function looseIndexOf(arr, val) {
return arr.findIndex((item) => looseEqual(item, val));
}
const isRef = (val) => {
return !!(val && val["__v_isRef"] === true);
};
const toDisplayString = (val) => {
return isString(val) ? val : val == null ? "" : isArray(val) || isObject(val) && (val.toString === objectToString || !isFunction(val.toString)) ? isRef(val) ? toDisplayString(val.value) : JSON.stringify(val, replacer, 2) : String(val);
};
const replacer = (_key, val) => {
if (isRef(val)) {
return replacer(_key, val.value);
} else if (isMap(val)) {
return {
[`Map(${val.size})`]: [...val.entries()].reduce(
(entries, [key, val2], i) => {
entries[stringifySymbol(key, i) + " =>"] = val2;
return entries;
},
{}
)
};
} else if (isSet(val)) {
return {
[`Set(${val.size})`]: [...val.values()].map((v) => stringifySymbol(v))
};
} else if (isSymbol(val)) {
return stringifySymbol(val);
} else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
return String(val);
}
return val;
};
const stringifySymbol = (v, i = "") => {
var _a;
return (
// Symbol.description in es2019+ so we need to cast here to pass
// the lib: es2016 check
isSymbol(v) ? `Symbol(${(_a = v.description) != null ? _a : i})` : v
);
};
function normalizeCssVarValue(value) {
if (value == null) {
return "initial";
}
if (typeof value === "string") {
return value === "" ? " " : value;
}
return String(value);
}
exports.EMPTY_ARR = EMPTY_ARR;
exports.EMPTY_OBJ = EMPTY_OBJ;
exports.NO = NO;
exports.NOOP = NOOP;
exports.PatchFlagNames = PatchFlagNames;
exports.PatchFlags = PatchFlags;
exports.ShapeFlags = ShapeFlags;
exports.SlotFlags = SlotFlags;
exports.camelize = camelize;
exports.capitalize = capitalize;
exports.cssVarNameEscapeSymbolsRE = cssVarNameEscapeSymbolsRE;
exports.def = def;
exports.escapeHtml = escapeHtml;
exports.escapeHtmlComment = escapeHtmlComment;
exports.extend = extend;
exports.genCacheKey = genCacheKey;
exports.genPropsAccessExp = genPropsAccessExp;
exports.generateCodeFrame = generateCodeFrame;
exports.getEscapedCssVarName = getEscapedCssVarName;
exports.getGlobalThis = getGlobalThis;
exports.hasChanged = hasChanged;
exports.hasOwn = hasOwn;
exports.hyphenate = hyphenate;
exports.includeBooleanAttr = includeBooleanAttr;
exports.invokeArrayFns = invokeArrayFns;
exports.isArray = isArray;
exports.isBooleanAttr = isBooleanAttr;
exports.isBuiltInDirective = isBuiltInDirective;
exports.isDate = isDate;
exports.isFunction = isFunction;
exports.isGloballyAllowed = isGloballyAllowed;
exports.isGloballyWhitelisted = isGloballyWhitelisted;
exports.isHTMLTag = isHTMLTag;
exports.isIntegerKey = isIntegerKey;
exports.isKnownHtmlAttr = isKnownHtmlAttr;
exports.isKnownMathMLAttr = isKnownMathMLAttr;
exports.isKnownSvgAttr = isKnownSvgAttr;
exports.isMap = isMap;
exports.isMathMLTag = isMathMLTag;
exports.isModelListener = isModelListener;
exports.isObject = isObject;
exports.isOn = isOn;
exports.isPlainObject = isPlainObject;
exports.isPromise = isPromise;
exports.isRegExp = isRegExp;
exports.isRenderableAttrValue = isRenderableAttrValue;
exports.isReservedProp = isReservedProp;
exports.isSSRSafeAttrName = isSSRSafeAttrName;
exports.isSVGTag = isSVGTag;
exports.isSet = isSet;
exports.isSpecialBooleanAttr = isSpecialBooleanAttr;
exports.isString = isString;
exports.isSymbol = isSymbol;
exports.isVoidTag = isVoidTag;
exports.looseEqual = looseEqual;
exports.looseIndexOf = looseIndexOf;
exports.looseToNumber = looseToNumber;
exports.makeMap = makeMap;
exports.normalizeClass = normalizeClass;
exports.normalizeCssVarValue = normalizeCssVarValue;
exports.normalizeProps = normalizeProps;
exports.normalizeStyle = normalizeStyle;
exports.objectToString = objectToString;
exports.parseStringStyle = parseStringStyle;
exports.propsToAttrMap = propsToAttrMap;
exports.remove = remove;
exports.slotFlagsText = slotFlagsText;
exports.stringifyStyle = stringifyStyle;
exports.toDisplayString = toDisplayString;
exports.toHandlerKey = toHandlerKey;
exports.toNumber = toNumber;
exports.toRawType = toRawType;
exports.toTypeString = toTypeString;
+47
View File
@@ -0,0 +1,47 @@
{
"name": "@vue/shared",
"version": "3.5.44",
"description": "internal utils shared across @vue packages",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",
"types": "dist/shared.d.ts",
"files": [
"index.js",
"dist"
],
"exports": {
".": {
"types": "./dist/shared.d.ts",
"node": {
"production": "./dist/shared.cjs.prod.js",
"development": "./dist/shared.cjs.js",
"default": "./dist/shared.cjs.prod.js"
},
"module": "./dist/shared.esm-bundler.js",
"import": "./dist/shared.esm-bundler.js",
"require": "./index.js"
},
"./*": "./*"
},
"sideEffects": false,
"buildOptions": {
"formats": [
"esm-bundler",
"cjs"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/vuejs/core.git",
"directory": "packages/shared"
},
"keywords": [
"vue"
],
"author": "Evan You",
"license": "MIT",
"bugs": {
"url": "https://github.com/vuejs/core/issues"
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/shared#readme"
}
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
{
"name": "@vueuse/core",
"type": "module",
"version": "15.0.0",
"description": "Collection of essential Vue Composition Utilities",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/vueuse/vueuse#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/vueuse/vueuse.git",
"directory": "packages/core"
},
"bugs": {
"url": "https://github.com/vueuse/vueuse/issues"
},
"keywords": [
"vue",
"vue-use",
"utils"
],
"sideEffects": false,
"exports": {
".": "./dist/index.js",
"./*": "./dist/*",
"./metadata": "./dist/metadata.mjs",
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"unpkg": "./dist/index.iife.min.js",
"jsdelivr": "./dist/index.iife.min.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=22"
},
"peerDependencies": {
"vue": "^3.5.0"
},
"dependencies": {
"@types/web-bluetooth": "^0.0.21",
"@vueuse/metadata": "15.0.0",
"@vueuse/shared": "15.0.0"
},
"devDependencies": {
"@js-temporal/polyfill": "^0.5.1",
"temporal-polyfill": "^1.0.4"
},
"scripts": {
"build": "tsdown"
}
}
File diff suppressed because it is too large Load Diff
+45
View File
@@ -0,0 +1,45 @@
{
"name": "@vueuse/shared",
"type": "module",
"version": "15.0.0",
"author": "Anthony Fu <https://github.com/antfu>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/vueuse/vueuse/tree/main/packages/shared#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/vueuse/vueuse.git",
"directory": "packages/shared"
},
"bugs": {
"url": "https://github.com/vueuse/vueuse/issues"
},
"keywords": [
"vue",
"vue-use",
"utils"
],
"sideEffects": false,
"exports": {
".": "./dist/index.js",
"./*": "./dist/*",
"./package.json": "./package.json"
},
"main": "./dist/index.js",
"module": "./dist/index.js",
"unpkg": "./dist/index.iife.min.js",
"jsdelivr": "./dist/index.iife.min.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=22"
},
"peerDependencies": {
"vue": "^3.5.0"
},
"scripts": {
"build": "tsdown"
}
}
+47
View File
@@ -0,0 +1,47 @@
{
"name": "devalue",
"description": "Gets the job done when JSON.stringify can't",
"version": "6.0.3",
"repository": "sveltejs/devalue",
"sideEffects": false,
"exports": {
".": {
"types": "./types/types.d.ts",
"import": "./src/index.js",
"default": "./src/index.js"
}
},
"files": [
"src",
"types"
],
"types": "./types/types.d.ts",
"engines": {
"node": ">=18"
},
"devDependencies": {
"@changesets/cli": "^2.29.6",
"@js-temporal/polyfill": "^0.5.1",
"@types/node": "^24.12.0",
"prettier": "^3.2.4",
"publint": "^0.3.12",
"typescript": "^7.0.2",
"vite": "8.3.0",
"vitest": "5.0.1"
},
"license": "MIT",
"type": "module",
"scripts": {
"changeset:version": "changeset version",
"changeset:publish": "changeset publish",
"build": "tsc",
"test": "vitest run --project unit",
"test:unit": "vitest run --project unit",
"test:watch": "vitest --project unit",
"test:types": "tsc -p . --noEmit",
"check:format": "prettier --check .",
"format": "prettier --write .",
"bench": "node --allow-natives-syntax ./benchmarking/run.js",
"bench:compare": "node --allow-natives-syntax ./benchmarking/compare/index.js"
}
}
+64
View File
@@ -0,0 +1,64 @@
/* Baseline 2025 runtimes */
/** @type {(array_buffer: ArrayBuffer) => string} */
export function encode_native(array_buffer) {
return new Uint8Array(array_buffer).toBase64();
}
/** @type {(base64: string) => ArrayBuffer} */
export function decode_native(base64) {
return Uint8Array.fromBase64(base64).buffer;
}
/* Node-compatible runtimes */
/** @type {(array_buffer: ArrayBuffer) => string} */
export function encode_buffer(array_buffer) {
return Buffer.from(array_buffer).toString('base64');
}
/** @type {(base64: string) => ArrayBuffer} */
export function decode_buffer(base64) {
return Uint8Array.from(Buffer.from(base64, 'base64')).buffer;
}
/* Legacy runtimes */
/** @type {(array_buffer: ArrayBuffer) => string} */
export function encode_legacy(array_buffer) {
const array = new Uint8Array(array_buffer);
let binary = '';
// the maximum number of arguments to String.fromCharCode.apply
// should be around 0xFFFF in modern engines
const chunk_size = 0x8000;
for (let i = 0; i < array.length; i += chunk_size) {
const chunk = array.subarray(i, i + chunk_size);
// `apply` accepts any array-like at runtime; lib types only admit `number[]`.
binary += String.fromCharCode.apply(
null,
/** @type {number[]} */ (/** @type {unknown} */ (chunk))
);
}
return btoa(binary);
}
/** @type {(base64: string) => ArrayBuffer} */
export function decode_legacy(base64) {
const binary_string = atob(base64);
const len = binary_string.length;
const array = new Uint8Array(len);
for (let i = 0; i < len; i++) {
array[i] = binary_string.charCodeAt(i);
}
return array.buffer;
}
const native = typeof Uint8Array.fromBase64 === 'function';
const buffer = typeof process === 'object' && process.versions?.node !== undefined;
export const encode64 = native ? encode_native : buffer ? encode_buffer : encode_legacy;
export const decode64 = native ? decode_native : buffer ? decode_buffer : decode_legacy;
+12
View File
@@ -0,0 +1,12 @@
export const UNDEFINED = -1;
export const HOLE = -2;
export const NAN = -3;
export const POSITIVE_INFINITY = -4;
export const NEGATIVE_INFINITY = -5;
export const NEGATIVE_ZERO = -6;
export const SPARSE = -7;
// The largest valid value for a JavaScript array's `length` property,
// and the largest valid array index (one less than the max length).
export const MAX_ARRAY_LEN = 2 ** 32 - 1;
export const MAX_ARRAY_INDEX = MAX_ARRAY_LEN - 1;
+8
View File
@@ -0,0 +1,8 @@
export { uneval } from './uneval.js';
export { parse, unflatten } from './parse.js';
export { stringify, stringifyAsync } from './stringify.js';
export {
default_stringify_operations as defaultStringifyOperations,
default_parse_operations as defaultParseOperations
} from './operations.js';
export { DevalueError, filter_array_indices as filterArrayIndices } from './utils.js';
+98
View File
@@ -0,0 +1,98 @@
const SOURCE = Symbol('JavaScriptSource');
export class JavaScriptSource {
/**
* @param {TemplateStringsArray} strings
* @param {unknown[]} values
*/
constructor(strings, values) {
this.strings = strings;
this.values = values;
}
/**
* @param {(value: unknown) => string} fn
*/
render(fn) {
const { strings, values } = this;
let result = strings[0];
for (let i = 0; i < values.length; i++) {
const value = values[i];
result += JavaScriptSource.is_fragment(value)
? JavaScriptSource.from(value).render(fn)
: fn(value);
result += strings[i + 1];
}
return result;
}
/**
* @param {(value: unknown) => void} fn
* @param {Set<string>} reserved
* @param {Set<readonly string[]>} templates
*/
visit(fn, reserved, templates) {
const { strings, values } = this;
// Each template site reuses its string array, but its interpolations can change.
if (!templates.has(strings)) {
templates.add(strings);
for (const string of strings) {
// Generated names are ASCII, but literal identifiers can use Unicode escapes.
const decoded = string.replace(
/\\u(?:([\da-f]{4})|\{([\da-f]+)\})/gi,
(escape, hex, code_point) => {
const code = parseInt(hex || code_point, 16);
return code < 128 ? String.fromCharCode(code) : escape;
}
);
// Conservatively reserve words even in property names, strings and comments.
for (const name of decoded.match(/[a-zA-Z_$][\w$]*/g) || []) reserved.add(name);
}
}
for (const value of values) {
if (JavaScriptSource.is_fragment(value)) {
JavaScriptSource.from(value).visit(fn, reserved, templates);
} else {
fn(value);
}
}
}
/**
* @param {unknown} value
* @returns {value is JavaScriptFragment}
*/
static is_fragment(value) {
return typeof value === 'object' && value !== null && SOURCE in value;
}
/**
* @param {JavaScriptFragment} fragment
* @returns {JavaScriptSource}
*/
static from(fragment) {
if (!JavaScriptSource.is_fragment(fragment)) {
throw new TypeError('Invalid JavaScript fragment');
}
return fragment[SOURCE];
}
}
/**
* @param {TemplateStringsArray} strings
* @param {...unknown} values
* @returns {JavaScriptFragment}
*/
export function js(strings, ...values) {
if (!Array.isArray(strings) || !Array.isArray(strings.raw)) {
throw new TypeError(
'`js` must be used as a tagged template, but was called as a regular function'
);
}
return { [SOURCE]: new JavaScriptSource(strings, values) };
}
/**
* The opaque result of the `js` tag passed to an `uneval` replacer.
* @typedef {{ readonly [SOURCE]: JavaScriptSource }} JavaScriptFragment
*/
+220
View File
@@ -0,0 +1,220 @@
import { MAX_ARRAY_INDEX } from './constants.js';
import {
enumerable_symbols,
get_type,
is_buffer,
is_plain_object,
valid_array_indices
} from './utils.js';
/**
* Merges caller-provided operation overrides over the defaults. Iterating the
* default keys (rather than the override's own keys) means nullish members
* fall back to the default, and inherited members — e.g. from a class
* instance — are picked up.
*
* @template {Record<string, any>} T
* @param {T} defaults
* @param {Partial<T> | undefined} overrides
* @returns {T}
*/
export function merge_operations(defaults, overrides) {
if (!overrides) return defaults;
const merged = /** @type {T} */ ({});
for (const key of /** @type {(keyof T)[]} */ (Object.keys(defaults))) {
merged[key] = overrides[key] ?? defaults[key];
}
return merged;
}
/** @type {{ kind: 'not-plain' }} */
const NOT_PLAIN = /* @__PURE__ */ Object.freeze({ kind: 'not-plain' });
/** @type {{ kind: 'symbol-keys' }} */
const SYMBOL_KEYS = /* @__PURE__ */ Object.freeze({ kind: 'symbol-keys' });
/**
* The default implementations of every introspection/extraction operation
* `stringify` performs on the value being serialized. Each one uses native
* JavaScript semantics (property access, iteration, prototype methods, etc).
*
* Pass overrides via the `operations` option of `stringify`/`stringifyAsync`
* to customize how values are inspected — e.g. to serialize values without
* triggering getters, proxy traps, or patched prototype methods, or to
* serialize values that live in a different JavaScript runtime (a `node:vm`
* context, a WASM-hosted engine, a remote process) through handle objects.
*
* The object is frozen — it is shared by every `stringify` call that does
* not override a given operation.
*
*/
/** @type {import('./types.js').DefaultStringifyOperations} */
const stringify_operations = {
identify: (value) => value,
typeOf: (value) => (value === null ? 'null' : typeof value),
toPrimitive: (value) => value,
tagOf: (value) => get_type(value),
isThenable: (value) => typeof value.then === 'function',
toPromise: (thenable) => Promise.resolve(thenable),
unbox: (boxed) => boxed.valueOf(),
toISOString: (date) => (isNaN(date.getDate()) ? '' : date.toISOString()),
toStringValue: (value) => value.toString(),
regExpInfo: (regexp) => ({ source: regexp.source, flags: regexp.flags }),
valuesOf: (set) => set,
entriesOf: (map) => map,
viewInfo: (view) => {
// Node Buffers may share a pool containing unrelated, sensitive data.
// Copy only the visible bytes, without using Buffer's pooling or slice.
if (is_buffer(view)) view = new Uint8Array(view);
return {
buffer: view.buffer,
byteOffset: view.byteOffset,
byteLength: view.byteLength,
length: view.length,
bufferByteLength: view.buffer.byteLength
};
},
toArrayBuffer: (buffer) => buffer,
lengthOf: (array) => array.length,
hasOwn: (value, key) => Object.hasOwn(value, key),
indicesOf: (array) => valid_array_indices(array),
shapeOf: (value) => {
if (!is_plain_object(value)) return NOT_PLAIN;
if (enumerable_symbols(value).length > 0) return SYMBOL_KEYS;
return {
kind: Object.getPrototypeOf(value) === null ? 'null-proto' : 'plain',
keys: Object.keys(value)
};
},
get: (value, key) => value[key]
};
export const default_stringify_operations = /* @__PURE__ */ Object.freeze(stringify_operations);
const array_buffer_byte_length = /** @type {() => number} */ (
Object.getOwnPropertyDescriptor(ArrayBuffer.prototype, 'byteLength')?.get
);
const shared_array_buffer_byte_length =
typeof SharedArrayBuffer === 'undefined'
? undefined
: Object.getOwnPropertyDescriptor(SharedArrayBuffer.prototype, 'byteLength')?.get;
/**
* The default implementations of every construction operation `parse` and
* `unflatten` perform while reviving a value. Each one uses native
* JavaScript semantics (built-in constructors, property assignment, etc).
*
* Pass overrides via the `operations` option of `parse`/`unflatten` to
* customize how values are built — e.g. to construct them from the
* intrinsics of a different realm (a `node:vm` context), or to build up
* values inside another JavaScript runtime (a WASM-hosted engine, a remote
* process) through handle objects.
*
* The object is frozen — it is shared by every `parse` call that does not
* override a given operation.
*
*/
/** @type {import('./types.js').DefaultParseOperations} */
const parse_operations = {
fromPrimitive: (primitive) => primitive,
fromISOString: (iso) => new Date(iso),
fromStringValue: (tag, text) => {
if (tag === 'URL') return new URL(text);
if (tag === 'URLSearchParams') return new URLSearchParams(text);
// 'Temporal.Instant', 'Temporal.PlainDate', ...
// @ts-expect-error TS doesn't know about Temporal yet
return Temporal[tag.slice(9)].from(text);
},
fromArrayBuffer: (buffer) => buffer,
fromRegExpInfo: (source, flags) => new RegExp(source, flags),
fromViewInfo: (tag, buffer, byteOffset, length) => {
// A reviver can replace an ArrayBuffer with a length or array-like value,
// which a typed array constructor would use to allocate a new buffer.
// The native getters check internal slots, so they work across realms
// and cannot be fooled by a forged prototype or Symbol.toStringTag.
try {
array_buffer_byte_length.call(buffer);
} catch (error) {
if (!shared_array_buffer_byte_length) throw error;
shared_array_buffer_byte_length.call(buffer);
}
const Constructor = /** @type {any} */ (globalThis)[tag];
return byteOffset !== undefined
? new Constructor(buffer, byteOffset, length)
: new Constructor(buffer);
},
box: (value) => Object(value),
createArray: (length) => new Array(length),
createSparseArray: (length) => {
/** @type {any[]} */
const array = [];
// Setting `array.length = length` (or equivalently calling
// `new Array(length)`) on an untrusted length is a DoS vector: V8
// eagerly allocates a contiguous backing store for array lengths below
// ~10^8, so a small payload with a huge declared length can force
// arbitrary memory allocation. Touching the largest-possible index
// first forces V8 into dictionary-elements mode, where `length` is
// just a number and no contiguous allocation occurs.
array[MAX_ARRAY_INDEX] = undefined;
delete array[MAX_ARRAY_INDEX];
array.length = length;
return array;
},
createObject: () => ({}),
createNullPrototypeObject: () => Object.create(null),
createSet: () => new Set(),
createMap: () => new Map(),
set: (target, key, value) => {
target[key] = value;
},
addValue: (set, value) => {
set.add(value);
},
addEntry: (map, key, value) => {
map.set(key, value);
}
};
export const default_parse_operations = /* @__PURE__ */ Object.freeze(parse_operations);
+312
View File
@@ -0,0 +1,312 @@
import { decode64 } from './base64.js';
import {
HOLE,
NAN,
NEGATIVE_INFINITY,
NEGATIVE_ZERO,
POSITIVE_INFINITY,
SPARSE,
UNDEFINED
} from './constants.js';
import { default_parse_operations, merge_operations } from './operations.js';
import { is_valid_array_index, is_valid_array_len } from './utils.js';
function invalid() {
throw new Error('Invalid input');
}
/**
* Revive a value serialized with `devalue.stringify`
* @param {string} serialized
* @param {Record<string, (value: any) => any>} [revivers]
* @param {import('./types.js').ParseOptions} [options]
*/
export function parse(serialized, revivers, options) {
return unflatten(JSON.parse(serialized), revivers, options);
}
/**
* Revive a value flattened with `devalue.stringify`
* @param {number | any[]} parsed
* @param {Record<string, (value: any) => any>} [revivers]
* @param {import('./types.js').ParseOptions} [options]
*/
export function unflatten(parsed, revivers, options) {
/** @type {import('./types.js').ParseOperations} */
const ops = merge_operations(default_parse_operations, options?.operations);
if (typeof parsed === 'number') return hydrate(parsed, true);
if (!Array.isArray(parsed) || parsed.length === 0) {
invalid();
}
const values = /** @type {any[]} */ (parsed);
const hydrated = Array(values.length);
/**
* A set of values currently being hydrated with custom revivers,
* used to detect invalid cyclical dependencies
* @type {Set<number> | null}
*/
let hydrating = null;
/**
* @param {number} index
* @returns {any}
*/
function get_raw(index) {
if (!is_valid_array_index(index) || index >= values.length) {
invalid();
}
return values[index];
}
/**
* @param {number} index
* @returns {any}
*/
function hydrate(index, standalone = false) {
if (index === UNDEFINED) return ops.fromPrimitive(undefined);
if (index === NAN) return ops.fromPrimitive(NaN);
if (index === POSITIVE_INFINITY) return ops.fromPrimitive(Infinity);
if (index === NEGATIVE_INFINITY) return ops.fromPrimitive(-Infinity);
if (index === NEGATIVE_ZERO) return ops.fromPrimitive(-0);
if (standalone || typeof index !== 'number') {
invalid();
}
if (index in hydrated) return hydrated[index];
if (index >= values.length) {
invalid();
}
const value = values[index];
if (!value || typeof value !== 'object') {
hydrated[index] = ops.fromPrimitive(value);
} else if (Array.isArray(value)) {
if (typeof value[0] === 'string') {
const type = value[0];
const reviver = revivers && Object.hasOwn(revivers, type) ? revivers[type] : undefined;
if (reviver) {
let i = value[1];
if (typeof i !== 'number') {
// if it's not a number, it was serialized by a builtin reviver
// so we need to munge it into the format expected by a custom reviver
i = values.push(value[1]) - 1;
}
// If the payload is already hydrated, its recursion has already
// terminated (e.g. a self-referential object cached itself before
// following its own back-reference), so revive it directly. Falling
// through to the `hydrating` guard here would wrongly reject a valid
// cycle. An actually infinite payload (e.g. `[["Custom", 0]]`) is never
// cached, so it still hits the guard below.
if (Object.hasOwn(hydrated, i)) {
return (hydrated[index] = reviver(hydrated[i]));
}
hydrating ??= new Set();
if (hydrating.has(i)) {
throw new Error('Invalid circular reference');
}
hydrating.add(i);
hydrated[index] = reviver(hydrate(i));
hydrating.delete(i);
return hydrated[index];
}
switch (type) {
case 'Date':
hydrated[index] = ops.fromISOString(value[1]);
break;
case 'Set':
const set = ops.createSet();
hydrated[index] = set;
for (let i = 1; i < value.length; i += 1) {
ops.addValue(set, hydrate(value[i]));
}
break;
case 'Map':
const map = ops.createMap();
hydrated[index] = map;
for (let i = 1; i < value.length; i += 2) {
ops.addEntry(map, hydrate(value[i]), hydrate(value[i + 1]));
}
break;
case 'RegExp':
hydrated[index] = ops.fromRegExpInfo(value[1], value[2]);
break;
case 'Object': {
const wrapped_index = value[1];
// Only a primitive can be boxed. The negative sentinels below are the
// ones `stringify` emits for boxed numbers; every other sentinel
// (`UNDEFINED`, `HOLE`, `SPARSE`) would box `undefined` into a plain
// `{}`, which is not a value `stringify` can produce.
const is_boxable_sentinel =
wrapped_index === NAN ||
wrapped_index === POSITIVE_INFINITY ||
wrapped_index === NEGATIVE_INFINITY ||
wrapped_index === NEGATIVE_ZERO;
if (!is_boxable_sentinel) {
const wrapped = get_raw(wrapped_index);
const is_bigint = Array.isArray(wrapped) && wrapped[0] === 'BigInt';
if ((wrapped === undefined || typeof wrapped === 'object') && !is_bigint) {
// avoid infinite recusion in case of malformed input
invalid();
}
}
hydrated[index] = ops.box(hydrate(wrapped_index));
break;
}
case 'BigInt':
hydrated[index] = ops.fromPrimitive(BigInt(value[1]));
break;
case 'null':
const obj = ops.createNullPrototypeObject();
hydrated[index] = obj;
for (let i = 1; i < value.length; i += 2) {
const key = value[i];
if (typeof key !== 'string') {
throw new Error('Cannot parse an object with a non-string key');
}
if (key === '__proto__') {
throw new Error('Cannot parse an object with a `__proto__` property');
}
ops.set(obj, key, hydrate(value[i + 1]));
}
break;
case 'Int8Array':
case 'Uint8Array':
case 'Uint8ClampedArray':
case 'Int16Array':
case 'Uint16Array':
case 'Float16Array':
case 'Int32Array':
case 'Uint32Array':
case 'Float32Array':
case 'Float64Array':
case 'BigInt64Array':
case 'BigUint64Array':
case 'DataView': {
const buffer_index = value[1];
const raw = get_raw(buffer_index);
if (!Array.isArray(raw) || raw[0] !== 'ArrayBuffer') {
// without this, if we receive malformed input we could
// end up trying to hydrate in a circle or allocate
// huge amounts of memory when we call `new TypedArrayConstructor(buffer)`
invalid();
}
const buffer = hydrate(buffer_index);
hydrated[index] = ops.fromViewInfo(type, buffer, value[2], value[3]);
break;
}
case 'ArrayBuffer': {
const base64 = value[1];
if (typeof base64 !== 'string') {
throw new Error('Invalid ArrayBuffer encoding');
}
hydrated[index] = ops.fromArrayBuffer(decode64(base64));
break;
}
case 'URL':
case 'URLSearchParams':
case 'Temporal.Duration':
case 'Temporal.Instant':
case 'Temporal.PlainDate':
case 'Temporal.PlainTime':
case 'Temporal.PlainDateTime':
case 'Temporal.PlainMonthDay':
case 'Temporal.PlainYearMonth':
case 'Temporal.ZonedDateTime': {
// the same tags `toStringValue` serializes on the stringify side
hydrated[index] = ops.fromStringValue(type, value[1]);
break;
}
default:
throw new Error(`Unknown type ${type}`);
}
} else if (value[0] === SPARSE) {
// Sparse array encoding: [SPARSE, length, idx, val, idx, val, ...]
const len = value[1];
if (!is_valid_array_len(len)) {
invalid();
}
// `len` comes from the input rather than being bounded by it, so
// `createSparseArray` is responsible for not allocating storage
// proportional to it.
const array = ops.createSparseArray(len);
hydrated[index] = array;
for (let i = 2; i < value.length; i += 2) {
const idx = value[i];
if (!is_valid_array_index(idx) || idx >= len) {
invalid();
}
ops.set(array, idx, hydrate(value[i + 1]));
}
} else {
const array = ops.createArray(value.length);
hydrated[index] = array;
for (let i = 0; i < value.length; i += 1) {
const n = value[i];
if (n === HOLE) continue;
ops.set(array, i, hydrate(n));
}
}
} else {
const object = ops.createObject();
hydrated[index] = object;
for (const key of Object.keys(value)) {
if (key === '__proto__') {
throw new Error('Cannot parse an object with a `__proto__` property');
}
ops.set(object, key, hydrate(value[key]));
}
}
return hydrated[index];
}
return hydrate(0);
}
+438
View File
@@ -0,0 +1,438 @@
import { DevalueError, MAP_KEY, format_path, quote_key, stringify_string } from './utils.js';
import {
HOLE,
NAN,
NEGATIVE_INFINITY,
NEGATIVE_ZERO,
POSITIVE_INFINITY,
SPARSE,
UNDEFINED
} from './constants.js';
import { encode64 } from './base64.js';
import { default_stringify_operations, merge_operations } from './operations.js';
/**
* Turn a value into a JSON string that can be parsed with `devalue.parse`
* @param {any} value
* @param {Record<string, (value: any) => any>} [reducers]
* @param {import('./types.js').StringifyOptions} [options]
*/
export function stringify(value, reducers, options) {
const stringified = run(false, value, reducers, options);
return typeof stringified === 'string' ? stringified : `[${stringified.join(',')}]`;
}
/**
* Turn a value into a JSON string that can be parsed with `devalue.parse`
* @param {any} value
* @param {Record<string, (value: any) => any>} [reducers]
* @param {import('./types.js').StringifyOptions} [options]
*/
export async function stringifyAsync(value, reducers, options) {
const stringified = run(true, value, reducers, options);
if (typeof stringified === 'string') {
return stringified;
}
let out = '[';
for (let i = 0; i < stringified.length; i += 1) {
let value = stringified[i];
if (typeof value !== 'string') {
await value;
value = stringified[i];
if (i === 0 && value < 0) {
return `${value}`;
}
}
out += value;
if (i < stringified.length - 1) {
out += ',';
}
}
out += ']';
return out;
}
/**
* @param {boolean} async
* @param {any} value
* @param {Record<string, (value: any) => any>} [reducers]
* @param {import('./types.js').StringifyOptions} [options]
*/
function run(async, value, reducers, options) {
const ops = merge_operations(default_stringify_operations, options?.operations);
/** @type {any[]} */
const stringified = [];
/** @type {Map<any, number>} */
const indexes = new Map();
/** @type {Array<{ key: string, fn: (value: any) => any }>} */
const custom = [];
if (reducers) {
for (const key of Object.getOwnPropertyNames(reducers)) {
custom.push({ key, fn: reducers[key] });
}
}
// the path to the value being serialized, recorded as-is and only
// formatted by `error` — see `format_path`
/** @type {any[]} */
let keys = [];
let p = 0;
/**
* @param {string} message
* @param {any} thing
* @returns {never}
*/
function error(message, thing) {
const path = format_path(keys, (key) => {
const key_type = ops.typeOf(key);
const key_is_primitive =
key_type !== 'object' && key_type !== 'function' && key_type !== 'symbol';
return key_is_primitive ? stringify_primitive(ops.toPrimitive(key)) : '...';
});
throw new DevalueError(message, path, thing, value);
}
/**
* @param {any} thing
* @param {number} [index]
*/
function flatten(thing, index) {
const type = ops.typeOf(thing);
if (type === 'undefined') return UNDEFINED;
/** @type {number | undefined} */
let number;
// `ops.toPrimitive` is the boundary between the value being serialized and
// plain host JavaScript: everything below operates on the extracted host
// primitive, so native comparisons and arithmetic are correct there.
if (type === 'number') {
number = /** @type {number} */ (ops.toPrimitive(thing));
if (Number.isNaN(number)) return NAN;
if (number === Infinity) return POSITIVE_INFINITY;
if (number === -Infinity) return NEGATIVE_INFINITY;
if (number === 0 && 1 / number < 0) return NEGATIVE_ZERO;
}
const id = ops.identify(thing);
if (indexes.has(id)) return /** @type {number} */ (indexes.get(id));
index ??= p++;
indexes.set(id, index);
for (const { key, fn } of custom) {
const value = fn(thing);
if (value) {
stringified[index] = `["${key}",${flatten(value)}]`;
return index;
}
}
if (type === 'function') {
error(`Cannot stringify a function`, thing);
} else if (type === 'symbol') {
error(`Cannot stringify a Symbol primitive`, thing);
}
/** @type {string | Promise<any>} */
let str = '';
if (type !== 'object') {
str = stringify_primitive(type === 'number' ? number : ops.toPrimitive(thing));
} else if (ops.isThenable(thing)) {
if (!async) {
error(`Cannot stringify a Promise or thenable — use stringifyAsync instead`, thing);
}
// the callback runs after the synchronous walk has unwound `keys`,
// so the path to the thenable has to be captured and reinstated
const prev_keys = keys.slice();
str = ops.toPromise(thing).then((value) => {
keys = prev_keys;
const i = flatten(value, index);
if (i < 0) stringified[index] = i;
});
// Promises are awaited sequentially, and traversal may throw before
// reaching them. Handle rejections immediately, but keep the original
// promise so awaiting it still propagates the error to the caller.
str.catch(() => {});
} else {
const tag = ops.tagOf(thing);
switch (tag) {
case 'Number':
case 'String':
case 'Boolean':
case 'BigInt':
str = `["Object",${flatten(ops.unbox(thing))}]`;
break;
case 'Date':
str = `["Date","${ops.toISOString(thing)}"]`;
break;
case 'URL':
str = `["URL",${stringify_string(ops.toStringValue(thing))}]`;
break;
case 'URLSearchParams':
str = `["URLSearchParams",${stringify_string(ops.toStringValue(thing))}]`;
break;
case 'RegExp':
const { source, flags } = ops.regExpInfo(thing);
str = flags
? `["RegExp",${stringify_string(source)},"${flags}"]`
: `["RegExp",${stringify_string(source)}]`;
break;
case 'Array': {
// For dense arrays (no holes), we iterate normally.
// When we encounter the first hole, we call Object.keys
// to determine the sparseness, then decide between:
// - HOLE encoding: [-2, val, -2, ...] (default)
// - Sparse encoding: [-7, length, idx, val, ...] (for very sparse arrays)
// Only the sparse path avoids iterating every slot, which
// is what protects against the DoS of e.g. `arr[1000000] = 1`.
let mostly_dense = false;
const length = ops.lengthOf(thing);
str = '[';
for (let i = 0; i < length; i += 1) {
if (i > 0) str += ',';
if (ops.hasOwn(thing, i)) {
keys.push(i);
str += flatten(ops.get(thing, i));
keys.pop();
} else if (mostly_dense) {
// Use dense encoding. The heuristic guarantees the
// array is only mildly sparse, so iterating over every
// slot is fine.
str += HOLE;
} else {
// Decide between HOLE encoding and sparse encoding.
//
// HOLE encoding: each hole is serialized as the HOLE
// sentinel (-2). For example, [, "a", ,] becomes
// [-2, 0, -2]. Each hole costs 3 chars ("-2" + comma).
//
// Sparse encoding: lists only populated indices.
// For example, [, "a", ,] becomes [-7, 3, 1, 0] — the
// -7 sentinel, the array length (3), then index-value
// pairs. This avoids paying per-hole, but each element
// costs extra chars to write its index.
//
// The values are the same size either way, so the
// choice comes down to structural overhead:
//
// HOLE overhead:
// 3 chars per hole ("-2" + comma)
// = (L - P) * 3
//
// Sparse overhead:
// "-7," — 3 chars (sparse sentinel + comma)
// + length + "," — (d + 1) chars (array length + comma)
// + per element: index + "," — (d + 1) chars
// = (4 + d) + P * (d + 1)
//
// where L is the array length, P is the number of
// populated elements, and d is the number of digits
// in L (an upper bound on the digits in any index).
//
// Sparse encoding is cheaper when:
// (4 + d) + P * (d + 1) < (L - P) * 3
const populated_keys = ops.indicesOf(thing);
const population = populated_keys.length;
const d = String(length).length;
const hole_cost = (length - population) * 3;
const sparse_cost = 4 + d + population * (d + 1);
if (hole_cost > sparse_cost) {
str = '[' + SPARSE + ',' + length;
for (let j = 0; j < populated_keys.length; j++) {
const key = populated_keys[j];
keys.push(+key);
str += ',' + key + ',' + flatten(ops.get(thing, key));
keys.pop();
}
break;
} else {
mostly_dense = true;
str += HOLE;
}
}
}
str += ']';
break;
}
case 'Set':
str = '["Set"';
for (const value of ops.valuesOf(thing)) {
str += `,${flatten(value)}`;
}
str += ']';
break;
case 'Map':
str = '["Map"';
for (const [key, value] of ops.entriesOf(thing)) {
keys.push(MAP_KEY, key);
str += `,${flatten(key)},${flatten(value)}`;
keys.pop();
keys.pop();
}
str += ']';
break;
case 'Int8Array':
case 'Uint8Array':
case 'Uint8ClampedArray':
case 'Int16Array':
case 'Uint16Array':
case 'Float16Array':
case 'Int32Array':
case 'Uint32Array':
case 'Float32Array':
case 'Float64Array':
case 'BigInt64Array':
case 'BigUint64Array': {
const info = ops.viewInfo(thing);
str = '["' + tag + '",' + flatten(info.buffer);
// handle subarrays
if (info.byteLength !== info.bufferByteLength) {
str += `,${info.byteOffset},${info.length}`;
}
str += ']';
break;
}
case 'DataView': {
const info = ops.viewInfo(thing);
str = '["' + tag + '",' + flatten(info.buffer);
if (info.byteLength !== info.bufferByteLength) {
str += `,${info.byteOffset},${info.byteLength}`;
}
str += ']';
break;
}
case 'ArrayBuffer': {
const base64 = encode64(ops.toArrayBuffer(thing));
str = `["ArrayBuffer","${base64}"]`;
break;
}
case 'Temporal.Duration':
case 'Temporal.Instant':
case 'Temporal.PlainDate':
case 'Temporal.PlainTime':
case 'Temporal.PlainDateTime':
case 'Temporal.PlainMonthDay':
case 'Temporal.PlainYearMonth':
case 'Temporal.ZonedDateTime':
str = `["${tag}",${stringify_string(ops.toStringValue(thing))}]`;
break;
default: {
const shape = ops.shapeOf(thing);
if (shape.kind === 'not-plain') {
error(`Cannot stringify arbitrary non-POJOs`, thing);
}
if (shape.kind === 'symbol-keys') {
error(`Cannot stringify POJOs with symbolic keys`, thing);
}
if (shape.kind === 'null-proto') {
str = '["null"';
for (const key of shape.keys) {
if (key === '__proto__') {
error(`Cannot stringify objects with __proto__ keys`, thing);
}
keys.push(key);
str += `,${quote_key(key)},${flatten(ops.get(thing, key))}`;
keys.pop();
}
str += ']';
} else {
str = '{';
let started = false;
for (const key of shape.keys) {
if (key === '__proto__') {
error(`Cannot stringify objects with __proto__ keys`, thing);
}
if (started) str += ',';
started = true;
keys.push(key);
str += `${quote_key(key)}:${flatten(ops.get(thing, key))}`;
keys.pop();
}
str += '}';
}
}
}
}
stringified[index] = str;
return index;
}
const index = flatten(value);
// special case — value is represented as a negative index
if (index < 0) return `${index}`;
return stringified;
}
/**
* @param {any} thing
* @returns {string}
*/
function stringify_primitive(thing) {
const type = typeof thing;
if (type === 'string') return stringify_string(thing);
if (thing === void 0) return UNDEFINED.toString();
if (thing === 0 && 1 / thing < 0) return NEGATIVE_ZERO.toString();
if (type === 'bigint') return `["BigInt","${thing}"]`;
return String(thing);
}
+622
View File
@@ -0,0 +1,622 @@
/**
* @import { UnevalReplacer } from './types.js';
*/
import { js, JavaScriptSource } from './javascript-source.js';
import {
DevalueError,
MAP_KEY,
enumerable_symbols,
escaped,
format_path,
get_name,
get_type,
is_buffer,
is_plain_object,
is_primitive,
stringify_sparse_array,
stringify_string,
valid_array_indices
} from './utils.js';
const unsafe_chars = /[<\b\f\n\r\t\0\u2028\u2029]/g;
// Short strings have bounded escaping/output costs per reference.
const MIN_STRING_LENGTH = 128;
/**
* Turn a value into the JavaScript that creates an equivalent value
* @param {any} value
* @param {UnevalReplacer} [replacer]
*/
export function uneval(value, replacer) {
/** @type {Map<any, string>} */
const names = new Map();
const reserved = new Set();
const templates = new Set();
const seen = new Set();
// the path to the value being walked, recorded as-is and only formatted
// by `error` — see `format_path`
/** @type {any[]} */
const keys = [];
/** @type {Map<any, JavaScriptSource>} */
const custom = new Map();
/**
* @param {string} message
* @param {any} thing
* @returns {never}
*/
function error(message, thing) {
const path = format_path(keys, (key) => (is_primitive(key) ? stringify_primitive(key) : '...'));
throw new DevalueError(message, path, thing, value);
}
/** @type {Map<string | bigint, number> | undefined} */
let primitive_counts;
/** @type {Map<string | bigint, string> | undefined} */
let primitives;
/** @param {any} thing */
function stringify_cached_primitive(thing) {
if (
(typeof thing === 'string' && thing.length >= MIN_STRING_LENGTH) ||
typeof thing === 'bigint'
) {
primitives ??= new Map();
let literal = primitives.get(thing);
if (literal === undefined) {
literal = stringify_primitive(thing);
primitives.set(thing, literal);
}
return literal;
}
return stringify_primitive(thing);
}
/** @param {any} thing */
function walk(thing) {
if (!is_primitive(thing)) {
if (seen.has(thing)) {
if (!names.has(thing)) names.set(thing, '');
return;
}
seen.add(thing);
if (replacer) {
const fragment = replacer(thing, js);
if (fragment) {
const source = JavaScriptSource.from(fragment);
custom.set(thing, source);
source.visit(walk, reserved, templates);
return;
}
if (fragment !== undefined && fragment !== null && fragment !== false) {
throw new TypeError('Invalid uneval replacer result');
}
}
if (typeof thing === 'function') {
error(`Cannot stringify a function`, thing);
}
const type = get_type(thing);
switch (type) {
case 'BigInt':
case 'String':
walk(thing.valueOf());
return;
case 'Number':
case 'Boolean':
case 'Date':
case 'RegExp':
case 'URL':
case 'URLSearchParams':
return;
case 'Array':
// Never scan the logical length of a dictionary-backed sparse array.
for (const i of valid_array_indices(thing)) {
keys.push(+i);
walk(thing[i]);
keys.pop();
}
break;
case 'Set':
for (const value of thing) walk(value);
break;
case 'Map':
for (const [key, value] of thing) {
keys.push(MAP_KEY, key);
walk(key);
walk(value);
keys.pop();
keys.pop();
}
break;
case 'Int8Array':
case 'Uint8Array':
case 'Uint8ClampedArray':
case 'Int16Array':
case 'Uint16Array':
case 'Float16Array':
case 'Int32Array':
case 'Uint32Array':
case 'Float32Array':
case 'Float64Array':
case 'BigInt64Array':
case 'BigUint64Array':
case 'DataView':
// Buffer pools can contain unrelated, sensitive bytes.
if (!is_buffer(thing)) walk(thing.buffer);
return;
case 'ArrayBuffer':
return;
case 'Temporal.Duration':
case 'Temporal.Instant':
case 'Temporal.PlainDate':
case 'Temporal.PlainTime':
case 'Temporal.PlainDateTime':
case 'Temporal.PlainMonthDay':
case 'Temporal.PlainYearMonth':
case 'Temporal.ZonedDateTime':
return;
default:
if (!is_plain_object(thing)) {
error(`Cannot stringify arbitrary non-POJOs`, thing);
}
if (enumerable_symbols(thing).length > 0) {
error(`Cannot stringify POJOs with symbolic keys`, thing);
}
for (const key of Object.keys(thing)) {
if (key === '__proto__') {
error(`Cannot stringify objects with __proto__ keys`, thing);
}
keys.push(key);
walk(thing[key]);
keys.pop();
}
}
} else if (typeof thing === 'symbol') {
error(`Cannot stringify a Symbol primitive`, thing);
} else if (
(typeof thing === 'string' && thing.length >= MIN_STRING_LENGTH) ||
typeof thing === 'bigint'
) {
primitive_counts ??= new Map();
primitive_counts.set(thing, (primitive_counts.get(thing) || 0) + 1);
}
}
walk(value);
let name_index = 0;
function next_name() {
let name;
do {
name = get_name(name_index++);
} while (reserved.has(name));
return name;
}
// Wait until every custom source has been visited before assigning names.
for (const thing of names.keys()) names.set(thing, next_name());
if (primitive_counts) {
for (const [thing, count] of primitive_counts) {
if (count < 2) continue;
const name = next_name();
const length = stringify_cached_primitive(thing).length;
// Include the declaration and IIFE overhead even if one already exists.
if (length * count > length + (count + 1) * name.length + 40) names.set(thing, name);
}
}
// Reuse the traversal set to track declarations during serialization.
seen.clear();
const inlining = custom.size > 0 ? new Set() : null;
/** @type {Map<any, () => void>} */
const initializers = new Map();
/** @type {string[]} */
const statements = [];
/**
* @param {any} thing
* @returns {string}
*/
function stringify(thing) {
const name = names.get(thing);
if (name) {
if (!seen.has(thing)) {
if (is_primitive(thing)) {
seen.add(thing);
statements.push(`let ${name}=${stringify_cached_primitive(thing)}`);
return name;
}
const type = custom.has(thing) ? null : get_type(thing);
switch (type) {
case 'Object':
seen.add(thing);
statements.push(
`let ${name}=${Object.getPrototypeOf(thing) === null ? 'Object.create(null)' : '{}'}`
);
Object.keys(thing).forEach((key) => {
statements.push(`${name}${safe_prop(key)}=${stringify(thing[key])}`);
});
break;
case 'Array': {
seen.add(thing);
const indices = valid_array_indices(thing);
const array =
thing.length > 32 + 2 * indices.length
? stringify_sparse_array(thing.length)
: `Array(${thing.length})`;
statements.push(`let ${name}=${array}`);
for (const i of indices) {
statements.push(`${name}[${i}]=${stringify(thing[i])}`);
}
break;
}
case 'Set':
case 'Map': {
seen.add(thing);
/** @type {string[]} */
const entries = [];
let initialized = false;
let statement_index = -1;
// Collect a ready prefix until a reference needs the collection.
// Emit subsequent entries eagerly, preserving insertion order.
const initialize = () => {
if (initialized) return;
initialized = true;
initializers.delete(thing);
statements.push(
`let ${name}=new ${type}${entries.length ? `([${entries.join(',')}])` : ''}`
);
};
initializers.set(thing, initialize);
for (const entry of thing) {
const args =
type === 'Map' ? `${stringify(entry[0])},${stringify(entry[1])}` : stringify(entry);
if (!initialized) {
entries.push(type === 'Map' ? `[${args}]` : args);
} else {
const call = `.${type === 'Map' ? 'set' : 'add'}(${args})`;
if (statement_index === statements.length - 1) {
statements[statement_index] += call;
} else {
statement_index = statements.push(name + call) - 1;
}
}
}
initialize();
break;
}
default:
const str = actually_stringify(thing);
if (!seen.has(thing)) statements.push(`let ${name}=${str}`);
seen.add(thing);
}
} else {
initializers.get(thing)?.();
}
return name;
}
if (is_primitive(thing)) {
return stringify_cached_primitive(thing);
}
if (inlining === null) return actually_stringify(thing);
// A singly referenced value can still be part of a custom constructor's
// cycle. If inlining it re-enters itself, hoist it to break the cycle.
if (inlining.has(thing)) {
names.set(thing, next_name());
return stringify(thing);
}
inlining.add(thing);
const str = actually_stringify(thing);
inlining.delete(thing);
return names.get(thing) || str;
}
/**
* @param {any} thing
* @returns {string}
*/
function actually_stringify(thing) {
const source = custom.get(thing);
if (source) {
return source.render(stringify);
}
const type = get_type(thing);
switch (type) {
case 'Number':
case 'String':
case 'Boolean':
case 'BigInt':
return `Object(${stringify(thing.valueOf())})`;
case 'RegExp':
const { source, flags } = thing;
return flags
? `new RegExp(${stringify_string(source)},"${flags}")`
: `new RegExp(${stringify_string(source)})`;
case 'Date':
return `new Date(${thing.getTime()})`;
case 'URL':
return `new URL(${stringify_string(thing.toString())})`;
case 'URLSearchParams':
return `new URLSearchParams(${stringify_string(thing.toString())})`;
case 'Array': {
// For dense arrays (no holes), we iterate normally.
// When we encounter the first hole, we collect own indices
// to determine the sparseness, then decide between:
// - Array literal with holes: [,"a",,] (default)
// - Object.assign with a sparse-safe allocator (for very sparse arrays)
// Only the Object.assign path avoids iterating every slot, which
// is what protects against the DoS of e.g. `arr[1000000] = 1`.
let has_holes = false;
let result = '[';
for (let i = 0; i < thing.length; i += 1) {
if (i > 0) result += ',';
if (Object.hasOwn(thing, i)) {
result += stringify(thing[i]);
} else if (!has_holes) {
// Decide between array literal and Object.assign.
//
// Array literal: holes are consecutive commas.
// For example, [, "a", ,] is written as [,"a",,].
// Each hole costs 1 char (a comma).
//
// Object.assign: populated indices are listed explicitly.
// For example, [, "a", ,] would be written as
// Object.assign(sparse(3),{1:"a"}), where sparse(n) stands
// for the expression emitted by stringify_sparse_array(n).
// This avoids paying per-hole, but has fixed overhead for
// the allocator and wrapper, plus each index and colon.
//
// The serialized values are the same size either way, so
// the choice comes down to the structural overhead:
//
// Array literal overhead:
// 1 char per element or hole (comma separators)
// + 2 chars for "[" and "]"
// = L + 2
//
// Object.assign overhead:
// "Object.assign(" — 14 chars
// + allocator expression — A chars
// + ",{" — 2 chars
// + for each populated element:
// index + ":" + "," — (d + 2) chars
// + "})" — 2 chars
// = (18 + A) + P * (d + 2)
//
// where L is the array length, P is the number of
// populated elements, A is the allocator length, and d
// is the number of digits in L (an upper bound per index).
//
// Object.assign is cheaper when:
// (18 + A) + P * (d + 2) < L + 2
const populated_keys = valid_array_indices(/** @type {any[]} */ (thing));
const population = populated_keys.length;
const d = String(thing.length).length;
const array = stringify_sparse_array(thing.length);
const hole_cost = thing.length + 2;
const sparse_cost = array.length + 18 + population * (d + 2);
if (hole_cost > sparse_cost) {
const entries = populated_keys.map((k) => `${k}:${stringify(thing[k])}`).join(',');
return `Object.assign(${array},{${entries}})`;
}
has_holes = true;
}
// else: already decided on array literal, hole is just an empty slot
// (the comma separator is all we need — no content for this position)
}
const tail = thing.length === 0 || Object.hasOwn(thing, thing.length - 1) ? '' : ',';
return result + tail + ']';
}
case 'Set':
return `new Set([${Array.from(thing, stringify).join(',')}])`;
case 'Map': {
const entries = Array.from(thing, ([k, v]) => `[${stringify(k)},${stringify(v)}]`);
return `new Map([${entries.join(',')}])`;
}
case 'Int8Array':
case 'Uint8Array':
case 'Uint8ClampedArray':
case 'Int16Array':
case 'Uint16Array':
case 'Float16Array':
case 'Int32Array':
case 'Uint32Array':
case 'Float32Array':
case 'Float64Array':
case 'BigInt64Array':
case 'BigUint64Array': {
if (is_buffer(thing)) thing = new Uint8Array(thing);
if (thing.buffer.byteLength % thing.BYTES_PER_ELEMENT !== 0) {
return `new ${type}(${stringify(thing.buffer)},${thing.byteOffset},${thing.length})`;
}
let str = `new ${type}`;
if (!names.has(thing.buffer)) {
str += `([${stringify_typed_array_elements(type, thing.buffer)}])`;
} else {
str += `(${stringify(thing.buffer)})`;
}
// handle subarrays
if (thing.byteLength !== thing.buffer.byteLength) {
const start = thing.byteOffset / thing.BYTES_PER_ELEMENT;
const end = start + thing.length;
str += `.subarray(${start},${end})`;
}
return str;
}
case 'DataView': {
let str = `new DataView`;
if (!names.has(thing.buffer)) {
str += `(new Uint8Array([${new Uint8Array(thing.buffer)}]).buffer`;
} else {
str += `(${stringify(thing.buffer)}`;
}
// handle subviews
if (thing.byteLength !== thing.buffer.byteLength) {
str += `,${thing.byteOffset},${thing.byteLength}`;
}
return str + ')';
}
case 'ArrayBuffer': {
const ui8 = new Uint8Array(thing);
return `new Uint8Array([${ui8.toString()}]).buffer`;
}
case 'Temporal.Duration':
case 'Temporal.Instant':
case 'Temporal.PlainDate':
case 'Temporal.PlainTime':
case 'Temporal.PlainDateTime':
case 'Temporal.PlainMonthDay':
case 'Temporal.PlainYearMonth':
case 'Temporal.ZonedDateTime':
return `${type}.from(${stringify_string(thing.toString())})`;
default:
const keys = Object.keys(thing);
const obj = keys.map((key) => `${safe_key(key)}:${stringify(thing[key])}`).join(',');
const proto = Object.getPrototypeOf(thing);
if (proto === null) {
return keys.length > 0 ? `{${obj},__proto__:null}` : `{__proto__:null}`;
}
return `{${obj}}`;
}
}
const str = stringify(value);
if (statements.length === 0) {
return str;
}
return `(function(){${statements.join(';')};return ${str}}())`;
}
/**
* Serialize the elements of `buffer`, read as `type`, as a comma-separated list.
* The view is created from `type` rather than from the serialized value's own
* constructor, which may be a subclass like Node's `Buffer` whose `toString`
* decodes the bytes instead of listing them.
* `BigInt64Array`/`BigUint64Array` elements are bigints and must be written
* with an `n` suffix, otherwise the emitted `new BigInt64Array([...])` throws.
* @param {string} type
* @param {ArrayBufferLike} buffer
*/
function stringify_typed_array_elements(type, buffer) {
const array = new /** @type {any} */ (globalThis)[type](buffer);
if (type === 'BigInt64Array' || type === 'BigUint64Array') {
return Array.from(array, (element) => `${element}n`).join(',');
}
// Float arrays can hold `-0`, which `toString()` collapses to `"0"`, silently
// losing the sign on round-trip. Emit `-0` explicitly for those elements.
if (
array instanceof Float32Array ||
array instanceof Float64Array ||
(typeof Float16Array !== 'undefined' && array instanceof Float16Array)
) {
return Array.from(array, (element) => (Object.is(element, -0) ? '-0' : `${element}`)).join(',');
}
return array.toString();
}
/** @param {string} c */
function escape_unsafe_char(c) {
return escaped[c] || c;
}
/** @param {string} str */
function escape_unsafe_chars(str) {
return str.replace(unsafe_chars, escape_unsafe_char);
}
/** @param {string} key */
function safe_key(key) {
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key) ? key : escape_unsafe_chars(JSON.stringify(key));
}
/** @param {string} key */
function safe_prop(key) {
return /^[_$a-zA-Z][_$a-zA-Z0-9]*$/.test(key)
? `.${key}`
: `[${escape_unsafe_chars(JSON.stringify(key))}]`;
}
/** @param {any} thing */
function stringify_primitive(thing) {
const type = typeof thing;
if (type === 'string') return stringify_string(thing);
if (thing === void 0) return 'void 0';
if (thing === 0 && 1 / thing < 0) return '-0';
const str = String(thing);
if (type === 'number') return str.replace(/^(-)?0\./, '$1.');
if (type === 'bigint') return thing + 'n';
return str;
}
+260
View File
@@ -0,0 +1,260 @@
import { MAX_ARRAY_INDEX, MAX_ARRAY_LEN } from './constants.js';
const name_chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_$';
const reserved_names =
/^(?:do|if|in|for|int|let|new|try|var|byte|case|char|else|enum|goto|long|this|void|with|await|break|catch|class|const|final|float|short|super|throw|while|yield|delete|double|export|import|native|return|switch|throws|typeof|boolean|default|extends|finally|package|private|abstract|continue|debugger|function|volatile|interface|protected|transient|implements|instanceof|synchronized)$/;
/** @type {Record<string, string>} */
export const escaped = {
'<': '\\u003C',
'\\': '\\\\',
'\b': '\\b',
'\f': '\\f',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\u2028': '\\u2028',
'\u2029': '\\u2029'
};
export class DevalueError extends Error {
/**
* @param {string} message
* @param {string[]} keys
* @param {any} [value] - The value that failed to be serialized
* @param {any} [root] - The root value being serialized
*/
constructor(message, keys, value, root) {
super(message);
this.name = 'DevalueError';
this.path = keys.join('');
this.value = value;
this.root = root;
}
}
/**
* Marks a Map key on the path being serialized. The key itself is recorded
* right after the marker, so nothing is formatted or allocated per entry.
*/
export const MAP_KEY = Symbol();
/**
* Formats the path recorded on the way to the value being serialized — a
* property key, an array index, or `MAP_KEY` followed by a Map key — into
* the segments a `DevalueError` joins. Only runs when an error is raised.
* @param {any[]} keys
* @param {(key: any) => string} format_map_key
* @returns {string[]}
*/
export function format_path(keys, format_map_key) {
/** @type {string[]} */
const path = [];
for (let i = 0; i < keys.length; i += 1) {
const segment = keys[i];
if (segment === MAP_KEY) {
path.push(`.get(${format_map_key(keys[++i])})`);
} else if (typeof segment === 'number') {
path.push(`[${segment}]`);
} else {
path.push(stringify_key(segment));
}
}
return path;
}
/** @param {any} thing */
export function is_primitive(thing) {
return thing === null || (typeof thing !== 'object' && typeof thing !== 'function');
}
const object_proto_names = /* @__PURE__ */ Object.getOwnPropertyNames(Object.prototype)
.sort()
.join('\0');
/** @param {any} thing */
export function is_plain_object(thing) {
const proto = Object.getPrototypeOf(thing);
return (
proto === Object.prototype ||
proto === null ||
Object.getPrototypeOf(proto) === null ||
Object.getOwnPropertyNames(proto).sort().join('\0') === object_proto_names
);
}
/** @param {any} thing */
export function get_type(thing) {
return Object.prototype.toString.call(thing).slice(8, -1);
}
/** @param {any} thing */
export function is_buffer(thing) {
return typeof Buffer !== 'undefined' && Buffer.isBuffer(thing);
}
/**
* Emit an array whose storage is not proportional to its declared length.
* Touching and deleting the largest valid index forces dictionary elements
* before setting the length; assigning .length on [] can still eagerly allocate.
* @param {number} length
*/
export function stringify_sparse_array(length) {
return `(function(a){a[${MAX_ARRAY_INDEX}]=0;delete a[${MAX_ARRAY_INDEX}];a.length=${length};return a}([]))`;
}
/** Returns the compact JavaScript identifier at `index`. @param {number} index */
export function get_name(index) {
let name = '';
do {
name = name_chars[index % name_chars.length] + name;
index = ~~(index / name_chars.length) - 1;
} while (index >= 0);
return reserved_names.test(name) ? `${name}0` : name;
}
// characters `stringify_string` always escapes: quote, backslash, `<`, control
// characters and the line/paragraph separators. Surrogates are only escaped
// when unpaired, which `isWellFormed` detects natively, so well-formed pairs
// such as emoji stay on the fast path
const needs_escape = /["<\\\u0000-\u001f\u2028\u2029]/;
// `JSON.stringify` already emits every escape we need except these three,
// which it leaves raw: `<` (so output is safe inside a `<script>` tag) and
// the line/paragraph separators (kept escaped for engines predating ES2019,
// which rejected them in string literals). It also escapes unpaired
// surrogates as `\\udXXX`, so the result survives UTF-8 transport
const json_leaves_raw = /[<\u2028\u2029]/g;
/** @type {Record<string, string>} */
const raw_escapes = { '<': '\\u003C', '\u2028': '\\u2028', '\u2029': '\\u2029' };
/** @param {string} char */
function escape_raw(char) {
return raw_escapes[char];
}
/** @param {string} str */
export function stringify_string(str) {
// the common case: nothing to escape, no per-character work
if (!needs_escape.test(str) && str.isWellFormed()) return `"${str}"`;
return JSON.stringify(str).replace(json_leaves_raw, escape_raw);
}
// Property names repeat across the values of one call and across calls, so
// their quoted form is cached. Names come from the data being serialized, so
// the cache is bounded in both directions: only short names are kept, and the
// cache is emptied once it holds 1024 of them. Longer names are quoted on
// every visit, like every other string.
const MAX_CACHED_KEY_LENGTH = 64;
/** @type {Map<string, string>} */
const quoted_keys = new Map();
/** @param {string} key */
export function quote_key(key) {
if (key.length > MAX_CACHED_KEY_LENGTH) return stringify_string(key);
let quoted = quoted_keys.get(key);
if (quoted === undefined) {
if (quoted_keys.size >= 1024) quoted_keys.clear();
quoted = stringify_string(key);
quoted_keys.set(key, quoted);
}
return quoted;
}
/** @param {Record<string | symbol, any>} object */
export function enumerable_symbols(object) {
const symbols = Object.getOwnPropertySymbols(object);
// Own symbols always have a descriptor; the optional chain only satisfies the lib's
// `PropertyDescriptor | undefined` return type.
return symbols.length === 0
? symbols
: symbols.filter((symbol) => Object.getOwnPropertyDescriptor(object, symbol)?.enumerable);
}
const is_identifier = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;
/** @param {string} key */
function stringify_key(key) {
return is_identifier.test(key) ? '.' + key : '[' + JSON.stringify(key) + ']';
}
/** @param {number} n */
export function is_valid_array_index(n) {
if (!Number.isInteger(n)) return false;
if (n < 0) return false;
if (n > MAX_ARRAY_INDEX) return false;
return true;
}
/** @param {number} n */
export function is_valid_array_len(n) {
if (!Number.isInteger(n)) return false;
if (n < 0) return false;
if (n > MAX_ARRAY_LEN) return false;
return true;
}
/** @param {string} s */
function is_valid_array_index_string(s) {
if (s.length === 0) return false;
if (s.length > 1 && s.charCodeAt(0) === 48) return false; // leading zero
for (let i = 0; i < s.length; i++) {
const c = s.charCodeAt(i);
if (c < 48 || c > 57) return false;
}
// by this point we know it's a string of digits, but it has to be within
// the range of valid array indices
return is_valid_array_index(+s);
}
/**
* Returns the length of the leading run of valid array indices in `keys`.
* @param {readonly string[]} keys
*/
function array_index_cut(keys) {
for (var i = keys.length - 1; i >= 0; i--) {
if (is_valid_array_index_string(keys[i])) {
break;
}
}
return i + 1;
}
/**
* Finds the populated indices of an array.
* @param {unknown[]} array
*/
export function valid_array_indices(array) {
const keys = Object.keys(array);
keys.length = array_index_cut(keys);
return keys;
}
/**
* Given the own enumerable string keys of an array-like value, in property
* order, returns the leading run of them that are valid array indices.
*
* This is the filtering half of the `indicesOf` stringify operation,
* exposed so that custom operations — which typically already have the keys
* in hand, e.g. from a foreign runtime — don't have to reimplement it.
*
* Does not modify `keys`.
*
* @param {readonly string[]} keys
* @returns {string[]}
*/
export function filter_array_indices(keys) {
return keys.slice(0, array_index_cut(keys));
}
@@ -0,0 +1,77 @@
"use strict";
// Adapted from https://github.com/mathiasbynens/he/blob/36afe179392226cf1b6ccdb16ebbb7a5a844d93a/src/he.js#L106-L134
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromCodePoint = void 0;
exports.replaceCodePoint = replaceCodePoint;
exports.decodeCodePoint = decodeCodePoint;
const decodeMap = new Map([
[0, 65533],
// C1 Unicode control character reference replacements
[128, 8364],
[130, 8218],
[131, 402],
[132, 8222],
[133, 8230],
[134, 8224],
[135, 8225],
[136, 710],
[137, 8240],
[138, 352],
[139, 8249],
[140, 338],
[142, 381],
[145, 8216],
[146, 8217],
[147, 8220],
[148, 8221],
[149, 8226],
[150, 8211],
[151, 8212],
[152, 732],
[153, 8482],
[154, 353],
[155, 8250],
[156, 339],
[158, 382],
[159, 376],
]);
/**
* Polyfill for `String.fromCodePoint`. It is used to create a string from a Unicode code point.
*/
exports.fromCodePoint =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, n/no-unsupported-features/es-builtins
(_a = String.fromCodePoint) !== null && _a !== void 0 ? _a : ((codePoint) => {
let output = "";
if (codePoint > 65535) {
codePoint -= 65536;
output += String.fromCharCode(((codePoint >>> 10) & 1023) | 55296);
codePoint = 56320 | (codePoint & 1023);
}
output += String.fromCharCode(codePoint);
return output;
});
/**
* Replace the given code point with a replacement character if it is a
* surrogate or is outside the valid range. Otherwise return the code
* point unchanged.
*/
function replaceCodePoint(codePoint) {
var _a;
if ((codePoint >= 55296 && codePoint <= 57343) ||
codePoint > 1114111) {
return 65533;
}
return (_a = decodeMap.get(codePoint)) !== null && _a !== void 0 ? _a : codePoint;
}
/**
* Replace the code point if relevant, then convert it to a string.
*
* @deprecated Use `fromCodePoint(replaceCodePoint(codePoint))` instead.
* @param codePoint The code point to decode.
* @returns The decoded code point.
*/
function decodeCodePoint(codePoint) {
return (0, exports.fromCodePoint)(replaceCodePoint(codePoint));
}
//# sourceMappingURL=decode-codepoint.js.map
+568
View File
@@ -0,0 +1,568 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.xmlDecodeTree = exports.htmlDecodeTree = exports.replaceCodePoint = exports.fromCodePoint = exports.decodeCodePoint = exports.EntityDecoder = exports.DecodingMode = void 0;
exports.determineBranch = determineBranch;
exports.decodeHTML = decodeHTML;
exports.decodeHTMLAttribute = decodeHTMLAttribute;
exports.decodeHTMLStrict = decodeHTMLStrict;
exports.decodeXML = decodeXML;
const decode_codepoint_js_1 = require("./decode-codepoint.js");
const decode_data_html_js_1 = require("./generated/decode-data-html.js");
const decode_data_xml_js_1 = require("./generated/decode-data-xml.js");
const bin_trie_flags_js_1 = require("./internal/bin-trie-flags.js");
var CharCodes;
(function (CharCodes) {
CharCodes[CharCodes["NUM"] = 35] = "NUM";
CharCodes[CharCodes["SEMI"] = 59] = "SEMI";
CharCodes[CharCodes["EQUALS"] = 61] = "EQUALS";
CharCodes[CharCodes["ZERO"] = 48] = "ZERO";
CharCodes[CharCodes["NINE"] = 57] = "NINE";
CharCodes[CharCodes["LOWER_A"] = 97] = "LOWER_A";
CharCodes[CharCodes["LOWER_F"] = 102] = "LOWER_F";
CharCodes[CharCodes["LOWER_X"] = 120] = "LOWER_X";
CharCodes[CharCodes["LOWER_Z"] = 122] = "LOWER_Z";
CharCodes[CharCodes["UPPER_A"] = 65] = "UPPER_A";
CharCodes[CharCodes["UPPER_F"] = 70] = "UPPER_F";
CharCodes[CharCodes["UPPER_Z"] = 90] = "UPPER_Z";
})(CharCodes || (CharCodes = {}));
/** Bit that needs to be set to convert an upper case ASCII character to lower case */
const TO_LOWER_BIT = 32;
function isNumber(code) {
return code >= CharCodes.ZERO && code <= CharCodes.NINE;
}
function isHexadecimalCharacter(code) {
return ((code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_F) ||
(code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_F));
}
function isAsciiAlphaNumeric(code) {
return ((code >= CharCodes.UPPER_A && code <= CharCodes.UPPER_Z) ||
(code >= CharCodes.LOWER_A && code <= CharCodes.LOWER_Z) ||
isNumber(code));
}
/**
* Checks if the given character is a valid end character for an entity in an attribute.
*
* Attribute values that aren't terminated properly aren't parsed, and shouldn't lead to a parser error.
* See the example in https://html.spec.whatwg.org/multipage/parsing.html#named-character-reference-state
*/
function isEntityInAttributeInvalidEnd(code) {
return code === CharCodes.EQUALS || isAsciiAlphaNumeric(code);
}
var EntityDecoderState;
(function (EntityDecoderState) {
EntityDecoderState[EntityDecoderState["EntityStart"] = 0] = "EntityStart";
EntityDecoderState[EntityDecoderState["NumericStart"] = 1] = "NumericStart";
EntityDecoderState[EntityDecoderState["NumericDecimal"] = 2] = "NumericDecimal";
EntityDecoderState[EntityDecoderState["NumericHex"] = 3] = "NumericHex";
EntityDecoderState[EntityDecoderState["NamedEntity"] = 4] = "NamedEntity";
})(EntityDecoderState || (EntityDecoderState = {}));
var DecodingMode;
(function (DecodingMode) {
/** Entities in text nodes that can end with any character. */
DecodingMode[DecodingMode["Legacy"] = 0] = "Legacy";
/** Only allow entities terminated with a semicolon. */
DecodingMode[DecodingMode["Strict"] = 1] = "Strict";
/** Entities in attributes have limitations on ending characters. */
DecodingMode[DecodingMode["Attribute"] = 2] = "Attribute";
})(DecodingMode || (exports.DecodingMode = DecodingMode = {}));
/**
* Token decoder with support of writing partial entities.
*/
class EntityDecoder {
constructor(
/** The tree used to decode entities. */
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: False positive
decodeTree,
/**
* The function that is called when a codepoint is decoded.
*
* For multi-byte named entities, this will be called multiple times,
* with the second codepoint, and the same `consumed` value.
*
* @param codepoint The decoded codepoint.
* @param consumed The number of bytes consumed by the decoder.
*/
emitCodePoint,
/** An object that is used to produce errors. */
errors) {
this.decodeTree = decodeTree;
this.emitCodePoint = emitCodePoint;
this.errors = errors;
/** The current state of the decoder. */
this.state = EntityDecoderState.EntityStart;
/** Characters that were consumed while parsing an entity. */
this.consumed = 1;
/**
* The result of the entity.
*
* Either the result index of a numeric entity, or the codepoint of a
* numeric entity.
*/
this.result = 0;
/** The current index in the decode tree. */
this.treeIndex = 0;
/** The number of characters that were consumed in excess. */
this.excess = 1;
/** The mode in which the decoder is operating. */
this.decodeMode = DecodingMode.Strict;
/** The number of characters that have been consumed in the current run. */
this.runConsumed = 0;
}
/** Resets the instance to make it reusable. */
startEntity(decodeMode) {
this.decodeMode = decodeMode;
this.state = EntityDecoderState.EntityStart;
this.result = 0;
this.treeIndex = 0;
this.excess = 1;
this.consumed = 1;
this.runConsumed = 0;
}
/**
* Write an entity to the decoder. This can be called multiple times with partial entities.
* If the entity is incomplete, the decoder will return -1.
*
* Mirrors the implementation of `getDecoder`, but with the ability to stop decoding if the
* entity is incomplete, and resume when the next string is written.
*
* @param input The string containing the entity (or a continuation of the entity).
* @param offset The offset at which the entity begins. Should be 0 if this is not the first call.
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
*/
write(input, offset) {
switch (this.state) {
case EntityDecoderState.EntityStart: {
if (input.charCodeAt(offset) === CharCodes.NUM) {
this.state = EntityDecoderState.NumericStart;
this.consumed += 1;
return this.stateNumericStart(input, offset + 1);
}
this.state = EntityDecoderState.NamedEntity;
return this.stateNamedEntity(input, offset);
}
case EntityDecoderState.NumericStart: {
return this.stateNumericStart(input, offset);
}
case EntityDecoderState.NumericDecimal: {
return this.stateNumericDecimal(input, offset);
}
case EntityDecoderState.NumericHex: {
return this.stateNumericHex(input, offset);
}
case EntityDecoderState.NamedEntity: {
return this.stateNamedEntity(input, offset);
}
}
}
/**
* Switches between the numeric decimal and hexadecimal states.
*
* Equivalent to the `Numeric character reference state` in the HTML spec.
*
* @param input The string containing the entity (or a continuation of the entity).
* @param offset The current offset.
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
*/
stateNumericStart(input, offset) {
if (offset >= input.length) {
return -1;
}
if ((input.charCodeAt(offset) | TO_LOWER_BIT) === CharCodes.LOWER_X) {
this.state = EntityDecoderState.NumericHex;
this.consumed += 1;
return this.stateNumericHex(input, offset + 1);
}
this.state = EntityDecoderState.NumericDecimal;
return this.stateNumericDecimal(input, offset);
}
/**
* Parses a hexadecimal numeric entity.
*
* Equivalent to the `Hexademical character reference state` in the HTML spec.
*
* @param input The string containing the entity (or a continuation of the entity).
* @param offset The current offset.
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
*/
stateNumericHex(input, offset) {
while (offset < input.length) {
const char = input.charCodeAt(offset);
if (isNumber(char) || isHexadecimalCharacter(char)) {
// Convert hex digit to value (0-15); 'a'/'A' -> 10.
const digit = char <= CharCodes.NINE
? char - CharCodes.ZERO
: (char | TO_LOWER_BIT) - CharCodes.LOWER_A + 10;
this.result = this.result * 16 + digit;
this.consumed++;
offset++;
}
else {
return this.emitNumericEntity(char, 3);
}
}
return -1; // Incomplete entity
}
/**
* Parses a decimal numeric entity.
*
* Equivalent to the `Decimal character reference state` in the HTML spec.
*
* @param input The string containing the entity (or a continuation of the entity).
* @param offset The current offset.
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
*/
stateNumericDecimal(input, offset) {
while (offset < input.length) {
const char = input.charCodeAt(offset);
if (isNumber(char)) {
this.result = this.result * 10 + (char - CharCodes.ZERO);
this.consumed++;
offset++;
}
else {
return this.emitNumericEntity(char, 2);
}
}
return -1; // Incomplete entity
}
/**
* Validate and emit a numeric entity.
*
* Implements the logic from the `Hexademical character reference start
* state` and `Numeric character reference end state` in the HTML spec.
*
* @param lastCp The last code point of the entity. Used to see if the
* entity was terminated with a semicolon.
* @param expectedLength The minimum number of characters that should be
* consumed. Used to validate that at least one digit
* was consumed.
* @returns The number of characters that were consumed.
*/
emitNumericEntity(lastCp, expectedLength) {
var _a;
// Ensure we consumed at least one digit.
if (this.consumed <= expectedLength) {
(_a = this.errors) === null || _a === void 0 ? void 0 : _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
return 0;
}
// Figure out if this is a legit end of the entity
if (lastCp === CharCodes.SEMI) {
this.consumed += 1;
}
else if (this.decodeMode === DecodingMode.Strict) {
return 0;
}
this.emitCodePoint((0, decode_codepoint_js_1.replaceCodePoint)(this.result), this.consumed);
if (this.errors) {
if (lastCp !== CharCodes.SEMI) {
this.errors.missingSemicolonAfterCharacterReference();
}
this.errors.validateNumericCharacterReference(this.result);
}
return this.consumed;
}
/**
* Parses a named entity.
*
* Equivalent to the `Named character reference state` in the HTML spec.
*
* @param input The string containing the entity (or a continuation of the entity).
* @param offset The current offset.
* @returns The number of characters that were consumed, or -1 if the entity is incomplete.
*/
stateNamedEntity(input, offset) {
const { decodeTree } = this;
let current = decodeTree[this.treeIndex];
// The length is the number of bytes of the value, including the current byte.
let valueLength = (current & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
while (offset < input.length) {
// Handle compact runs (possibly inline): valueLength == 0 and SEMI_REQUIRED bit set.
if (valueLength === 0 && (current & bin_trie_flags_js_1.BinTrieFlags.FLAG13) !== 0) {
const runLength = (current & bin_trie_flags_js_1.BinTrieFlags.BRANCH_LENGTH) >> 7; /* 2..63 */
// If we are starting a run, check the first char.
if (this.runConsumed === 0) {
const firstChar = current & bin_trie_flags_js_1.BinTrieFlags.JUMP_TABLE;
if (input.charCodeAt(offset) !== firstChar) {
return this.result === 0
? 0
: this.emitNotTerminatedNamedEntity();
}
offset++;
this.excess++;
this.runConsumed++;
}
// Check remaining characters in the run.
while (this.runConsumed < runLength) {
if (offset >= input.length) {
return -1;
}
const charIndexInPacked = this.runConsumed - 1;
const packedWord = decodeTree[this.treeIndex + 1 + (charIndexInPacked >> 1)];
const expectedChar = charIndexInPacked % 2 === 0
? packedWord & 0xff
: (packedWord >> 8) & 0xff;
if (input.charCodeAt(offset) !== expectedChar) {
this.runConsumed = 0;
return this.result === 0
? 0
: this.emitNotTerminatedNamedEntity();
}
offset++;
this.excess++;
this.runConsumed++;
}
this.runConsumed = 0;
this.treeIndex += 1 + (runLength >> 1);
current = decodeTree[this.treeIndex];
valueLength = (current & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
}
if (offset >= input.length)
break;
const char = input.charCodeAt(offset);
/*
* Implicit semicolon handling for nodes that require a semicolon but
* don't have an explicit ';' branch stored in the trie. If we have
* a value on the current node, it requires a semicolon, and the
* current input character is a semicolon, emit the entity using the
* current node (without descending further).
*/
if (char === CharCodes.SEMI &&
valueLength !== 0 &&
(current & bin_trie_flags_js_1.BinTrieFlags.FLAG13) !== 0) {
return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
}
this.treeIndex = determineBranch(decodeTree, current, this.treeIndex + Math.max(1, valueLength), char);
if (this.treeIndex < 0) {
return this.result === 0 ||
// If we are parsing an attribute
(this.decodeMode === DecodingMode.Attribute &&
// We shouldn't have consumed any characters after the entity,
(valueLength === 0 ||
// And there should be no invalid characters.
isEntityInAttributeInvalidEnd(char)))
? 0
: this.emitNotTerminatedNamedEntity();
}
current = decodeTree[this.treeIndex];
valueLength = (current & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
// If the branch is a value, store it and continue
if (valueLength !== 0) {
// If the entity is terminated by a semicolon, we are done.
if (char === CharCodes.SEMI) {
return this.emitNamedEntityData(this.treeIndex, valueLength, this.consumed + this.excess);
}
// If we encounter a non-terminated (legacy) entity while parsing strictly, then ignore it.
if (this.decodeMode !== DecodingMode.Strict &&
(current & bin_trie_flags_js_1.BinTrieFlags.FLAG13) === 0) {
this.result = this.treeIndex;
this.consumed += this.excess;
this.excess = 0;
}
}
// Increment offset & excess for next iteration
offset++;
this.excess++;
}
return -1;
}
/**
* Emit a named entity that was not terminated with a semicolon.
*
* @returns The number of characters consumed.
*/
emitNotTerminatedNamedEntity() {
var _a;
const { result, decodeTree } = this;
const valueLength = (decodeTree[result] & bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH) >> 14;
this.emitNamedEntityData(result, valueLength, this.consumed);
(_a = this.errors) === null || _a === void 0 ? void 0 : _a.missingSemicolonAfterCharacterReference();
return this.consumed;
}
/**
* Emit a named entity.
*
* @param result The index of the entity in the decode tree.
* @param valueLength The number of bytes in the entity.
* @param consumed The number of characters consumed.
*
* @returns The number of characters consumed.
*/
emitNamedEntityData(result, valueLength, consumed) {
const { decodeTree } = this;
this.emitCodePoint(valueLength === 1
? decodeTree[result] &
~(bin_trie_flags_js_1.BinTrieFlags.VALUE_LENGTH | bin_trie_flags_js_1.BinTrieFlags.FLAG13)
: decodeTree[result + 1], consumed);
if (valueLength === 3) {
// For multi-byte values, we need to emit the second byte.
this.emitCodePoint(decodeTree[result + 2], consumed);
}
return consumed;
}
/**
* Signal to the parser that the end of the input was reached.
*
* Remaining data will be emitted and relevant errors will be produced.
*
* @returns The number of characters consumed.
*/
end() {
var _a;
switch (this.state) {
case EntityDecoderState.NamedEntity: {
// Emit a named entity if we have one.
return this.result !== 0 &&
(this.decodeMode !== DecodingMode.Attribute ||
this.result === this.treeIndex)
? this.emitNotTerminatedNamedEntity()
: 0;
}
// Otherwise, emit a numeric entity if we have one.
case EntityDecoderState.NumericDecimal: {
return this.emitNumericEntity(0, 2);
}
case EntityDecoderState.NumericHex: {
return this.emitNumericEntity(0, 3);
}
case EntityDecoderState.NumericStart: {
(_a = this.errors) === null || _a === void 0 ? void 0 : _a.absenceOfDigitsInNumericCharacterReference(this.consumed);
return 0;
}
case EntityDecoderState.EntityStart: {
// Return 0 if we have no entity.
return 0;
}
}
}
}
exports.EntityDecoder = EntityDecoder;
/**
* Creates a function that decodes entities in a string.
*
* @param decodeTree The decode tree.
* @returns A function that decodes entities in a string.
*/
function getDecoder(decodeTree) {
let returnValue = "";
const decoder = new EntityDecoder(decodeTree, (data) => (returnValue += (0, decode_codepoint_js_1.fromCodePoint)(data)));
return function decodeWithTrie(input, decodeMode) {
let lastIndex = 0;
let offset = 0;
while ((offset = input.indexOf("&", offset)) >= 0) {
returnValue += input.slice(lastIndex, offset);
decoder.startEntity(decodeMode);
const length = decoder.write(input,
// Skip the "&"
offset + 1);
if (length < 0) {
lastIndex = offset + decoder.end();
break;
}
lastIndex = offset + length;
// If `length` is 0, skip the current `&` and continue.
offset = length === 0 ? lastIndex + 1 : lastIndex;
}
const result = returnValue + input.slice(lastIndex);
// Make sure we don't keep a reference to the final string.
returnValue = "";
return result;
};
}
/**
* Determines the branch of the current node that is taken given the current
* character. This function is used to traverse the trie.
*
* @param decodeTree The trie.
* @param current The current node.
* @param nodeIdx The index right after the current node and its value.
* @param char The current character.
* @returns The index of the next node, or -1 if no branch is taken.
*/
function determineBranch(decodeTree, current, nodeIndex, char) {
const branchCount = (current & bin_trie_flags_js_1.BinTrieFlags.BRANCH_LENGTH) >> 7;
const jumpOffset = current & bin_trie_flags_js_1.BinTrieFlags.JUMP_TABLE;
// Case 1: Single branch encoded in jump offset
if (branchCount === 0) {
return jumpOffset !== 0 && char === jumpOffset ? nodeIndex : -1;
}
// Case 2: Multiple branches encoded in jump table
if (jumpOffset) {
const value = char - jumpOffset;
return value < 0 || value >= branchCount
? -1
: decodeTree[nodeIndex + value] - 1;
}
// Case 3: Multiple branches encoded in packed dictionary (two keys per uint16)
const packedKeySlots = (branchCount + 1) >> 1;
/*
* Treat packed keys as a virtual sorted array of length `branchCount`.
* Key(i) = low byte for even i, high byte for odd i in slot i>>1.
*/
let lo = 0;
let hi = branchCount - 1;
while (lo <= hi) {
const mid = (lo + hi) >>> 1;
const slot = mid >> 1;
const packed = decodeTree[nodeIndex + slot];
const midKey = (packed >> ((mid & 1) * 8)) & 0xff;
if (midKey < char) {
lo = mid + 1;
}
else if (midKey > char) {
hi = mid - 1;
}
else {
return decodeTree[nodeIndex + packedKeySlots + mid];
}
}
return -1;
}
const htmlDecoder = /* #__PURE__ */ getDecoder(decode_data_html_js_1.htmlDecodeTree);
const xmlDecoder = /* #__PURE__ */ getDecoder(decode_data_xml_js_1.xmlDecodeTree);
/**
* Decodes an HTML string.
*
* @param htmlString The string to decode.
* @param mode The decoding mode.
* @returns The decoded string.
*/
function decodeHTML(htmlString, mode = DecodingMode.Legacy) {
return htmlDecoder(htmlString, mode);
}
/**
* Decodes an HTML string in an attribute.
*
* @param htmlAttribute The string to decode.
* @returns The decoded string.
*/
function decodeHTMLAttribute(htmlAttribute) {
return htmlDecoder(htmlAttribute, DecodingMode.Attribute);
}
/**
* Decodes an HTML string, requiring all entities to be terminated by a semicolon.
*
* @param htmlString The string to decode.
* @returns The decoded string.
*/
function decodeHTMLStrict(htmlString) {
return htmlDecoder(htmlString, DecodingMode.Strict);
}
/**
* Decodes an XML string, requiring all entities to be terminated by a semicolon.
*
* @param xmlString The string to decode.
* @returns The decoded string.
*/
function decodeXML(xmlString) {
return xmlDecoder(xmlString, DecodingMode.Strict);
}
var decode_codepoint_js_2 = require("./decode-codepoint.js");
Object.defineProperty(exports, "decodeCodePoint", { enumerable: true, get: function () { return decode_codepoint_js_2.decodeCodePoint; } });
Object.defineProperty(exports, "fromCodePoint", { enumerable: true, get: function () { return decode_codepoint_js_2.fromCodePoint; } });
Object.defineProperty(exports, "replaceCodePoint", { enumerable: true, get: function () { return decode_codepoint_js_2.replaceCodePoint; } });
// Re-export for use by eg. htmlparser2
var decode_data_html_js_2 = require("./generated/decode-data-html.js");
Object.defineProperty(exports, "htmlDecodeTree", { enumerable: true, get: function () { return decode_data_html_js_2.htmlDecodeTree; } });
var decode_data_xml_js_2 = require("./generated/decode-data-xml.js");
Object.defineProperty(exports, "xmlDecodeTree", { enumerable: true, get: function () { return decode_data_xml_js_2.xmlDecodeTree; } });
//# sourceMappingURL=decode.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
"use strict";
// Generated using scripts/write-decode-map.ts
Object.defineProperty(exports, "__esModule", { value: true });
exports.xmlDecodeTree = void 0;
const decode_shared_js_1 = require("../internal/decode-shared.js");
exports.xmlDecodeTree = (0, decode_shared_js_1.decodeBase64)("AAJhZ2xxBwARABMAFQBtAg0AAAAAAA8AcAAmYG8AcwAnYHQAPmB0ADxg9SFvdCJg");
//# sourceMappingURL=decode-data-xml.js.map
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BinTrieFlags = void 0;
/**
* Bit flags & masks for the binary trie encoding used for entity decoding.
*
* Bit layout (16 bits total):
* 15..14 VALUE_LENGTH (+1 encoding; 0 => no value)
* 13 FLAG13. If valueLength>0: semicolon required flag (implicit ';').
* If valueLength==0: compact run flag.
* 12..7 BRANCH_LENGTH Branch length (0 => single branch in 6..0 if jumpOffset==char) OR run length (when compact run)
* 6..0 JUMP_TABLE Jump offset (jump table) OR single-branch char code OR first run char
*/
var BinTrieFlags;
(function (BinTrieFlags) {
BinTrieFlags[BinTrieFlags["VALUE_LENGTH"] = 49152] = "VALUE_LENGTH";
BinTrieFlags[BinTrieFlags["FLAG13"] = 8192] = "FLAG13";
BinTrieFlags[BinTrieFlags["BRANCH_LENGTH"] = 8064] = "BRANCH_LENGTH";
BinTrieFlags[BinTrieFlags["JUMP_TABLE"] = 127] = "JUMP_TABLE";
})(BinTrieFlags || (exports.BinTrieFlags = BinTrieFlags = {}));
//# sourceMappingURL=bin-trie-flags.js.map
@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.decodeBase64 = decodeBase64;
/*
* Shared base64 decode helper for generated decode data.
* Assumes global atob is available.
*/
function decodeBase64(input) {
const binary =
// eslint-disable-next-line n/no-unsupported-features/node-builtins
typeof atob === "function"
? // Browser (and Node >=16)
// eslint-disable-next-line n/no-unsupported-features/node-builtins
atob(input)
: // Older Node versions (<16)
// eslint-disable-next-line n/no-unsupported-features/node-builtins
typeof Buffer.from === "function"
? // eslint-disable-next-line n/no-unsupported-features/node-builtins
Buffer.from(input, "base64").toString("binary")
: // eslint-disable-next-line unicorn/no-new-buffer, n/no-deprecated-api
new Buffer(input, "base64").toString("binary");
const evenLength = binary.length & ~1; // Round down to even length
const out = new Uint16Array(evenLength / 2);
for (let index = 0, outIndex = 0; index < evenLength; index += 2) {
const lo = binary.charCodeAt(index);
const hi = binary.charCodeAt(index + 1);
out[outIndex++] = lo | (hi << 8);
}
return out;
}
//# sourceMappingURL=decode-shared.js.map
@@ -0,0 +1,3 @@
{
"type": "commonjs"
}
+120
View File
@@ -0,0 +1,120 @@
{
"name": "entities",
"version": "7.0.1",
"description": "Encode & decode XML and HTML entities with ease & speed",
"keywords": [
"html entities",
"entity decoder",
"entity encoding",
"html decoding",
"html encoding",
"xml decoding",
"xml encoding"
],
"repository": {
"type": "git",
"url": "https://github.com/fb55/entities.git"
},
"funding": "https://github.com/fb55/entities?sponsor=1",
"license": "BSD-2-Clause",
"author": "Felix Boehm <me@feedic.com>",
"sideEffects": false,
"type": "module",
"exports": {
".": {
"import": {
"types": "./dist/esm/index.d.ts",
"default": "./dist/esm/index.js"
},
"require": {
"types": "./dist/commonjs/index.d.ts",
"default": "./dist/commonjs/index.js"
}
},
"./decode": {
"import": {
"types": "./dist/esm/decode.d.ts",
"default": "./dist/esm/decode.js"
},
"require": {
"types": "./dist/commonjs/decode.d.ts",
"default": "./dist/commonjs/decode.js"
}
},
"./escape": {
"import": {
"types": "./dist/esm/escape.d.ts",
"default": "./dist/esm/escape.js"
},
"require": {
"types": "./dist/commonjs/escape.d.ts",
"default": "./dist/commonjs/escape.js"
}
}
},
"main": "./dist/commonjs/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/commonjs/index.d.ts",
"files": [
"decode.js",
"decode.d.ts",
"escape.js",
"escape.d.ts",
"dist",
"src",
"!**/*.spec.ts"
],
"scripts": {
"build:docs": "typedoc --hideGenerator src/index.ts",
"build:encode-trie": "node --import=tsx scripts/write-encode-map.ts",
"build:trie": "node --import=tsx scripts/write-decode-map.ts",
"benchmark": "node --import=tsx scripts/benchmark.ts",
"format": "npm run format:es && npm run format:biome",
"format:es": "npm run lint:es -- --fix",
"format:biome": "biome check --fix .",
"lint": "npm run lint:es && npm run lint:ts && npm run lint:biome",
"lint:es": "eslint . --ignore-path .gitignore",
"lint:biome": "biome check .",
"lint:ts": "tsc --noEmit",
"prepublishOnly": "tshy",
"test": "npm run test:vi && npm run lint",
"test:vi": "vitest run"
},
"devDependencies": {
"@biomejs/biome": "^2.3.11",
"@types/node": "^25.0.9",
"@typescript-eslint/eslint-plugin": "^8.53.1",
"@typescript-eslint/parser": "^8.53.1",
"@vitest/coverage-v8": "^3.2.4",
"@types/he": "^1.2.3",
"eslint": "^8.57.1",
"eslint-config-biome": "^2.1.3",
"eslint-plugin-n": "^17.23.2",
"eslint-plugin-unicorn": "^56.0.1",
"he": "^1.2.0",
"html-entities": "^2.6.0",
"parse-entities": "^4.0.2",
"tinybench": "^5.1.0",
"tshy": "^3.1.0",
"tsx": "^4.21.0",
"typedoc": "^0.28.16",
"typescript": "^5.9.3",
"vitest": "^3.2.4"
},
"engines": {
"node": ">=0.12"
},
"tshy": {
"exclude": [
"**/*.spec.ts",
"**/__fixtures__/*",
"**/__tests__/*",
"**/__snapshots__/*"
],
"exports": {
".": "./src/index.ts",
"./decode": "./src/decode.ts",
"./escape": "./src/escape.ts"
}
}
}
@@ -0,0 +1,344 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.estreeWalker = {}));
}(this, (function (exports) { 'use strict';
// @ts-check
/** @typedef { import('estree').BaseNode} BaseNode */
/** @typedef {{
skip: () => void;
remove: () => void;
replace: (node: BaseNode) => void;
}} WalkerContext */
class WalkerBase {
constructor() {
/** @type {boolean} */
this.should_skip = false;
/** @type {boolean} */
this.should_remove = false;
/** @type {BaseNode | null} */
this.replacement = null;
/** @type {WalkerContext} */
this.context = {
skip: () => (this.should_skip = true),
remove: () => (this.should_remove = true),
replace: (node) => (this.replacement = node)
};
}
/**
*
* @param {any} parent
* @param {string} prop
* @param {number} index
* @param {BaseNode} node
*/
replace(parent, prop, index, node) {
if (parent) {
if (index !== null) {
parent[prop][index] = node;
} else {
parent[prop] = node;
}
}
}
/**
*
* @param {any} parent
* @param {string} prop
* @param {number} index
*/
remove(parent, prop, index) {
if (parent) {
if (index !== null) {
parent[prop].splice(index, 1);
} else {
delete parent[prop];
}
}
}
}
// @ts-check
/** @typedef { import('estree').BaseNode} BaseNode */
/** @typedef { import('./walker.js').WalkerContext} WalkerContext */
/** @typedef {(
* this: WalkerContext,
* node: BaseNode,
* parent: BaseNode,
* key: string,
* index: number
* ) => void} SyncHandler */
class SyncWalker extends WalkerBase {
/**
*
* @param {SyncHandler} enter
* @param {SyncHandler} leave
*/
constructor(enter, leave) {
super();
/** @type {SyncHandler} */
this.enter = enter;
/** @type {SyncHandler} */
this.leave = leave;
}
/**
*
* @param {BaseNode} node
* @param {BaseNode} parent
* @param {string} [prop]
* @param {number} [index]
* @returns {BaseNode}
*/
visit(node, parent, prop, index) {
if (node) {
if (this.enter) {
const _should_skip = this.should_skip;
const _should_remove = this.should_remove;
const _replacement = this.replacement;
this.should_skip = false;
this.should_remove = false;
this.replacement = null;
this.enter.call(this.context, node, parent, prop, index);
if (this.replacement) {
node = this.replacement;
this.replace(parent, prop, index, node);
}
if (this.should_remove) {
this.remove(parent, prop, index);
}
const skipped = this.should_skip;
const removed = this.should_remove;
this.should_skip = _should_skip;
this.should_remove = _should_remove;
this.replacement = _replacement;
if (skipped) return node;
if (removed) return null;
}
for (const key in node) {
const value = node[key];
if (typeof value !== "object") {
continue;
} else if (Array.isArray(value)) {
for (let i = 0; i < value.length; i += 1) {
if (value[i] !== null && typeof value[i].type === 'string') {
if (!this.visit(value[i], node, key, i)) {
// removed
i--;
}
}
}
} else if (value !== null && typeof value.type === "string") {
this.visit(value, node, key, null);
}
}
if (this.leave) {
const _replacement = this.replacement;
const _should_remove = this.should_remove;
this.replacement = null;
this.should_remove = false;
this.leave.call(this.context, node, parent, prop, index);
if (this.replacement) {
node = this.replacement;
this.replace(parent, prop, index, node);
}
if (this.should_remove) {
this.remove(parent, prop, index);
}
const removed = this.should_remove;
this.replacement = _replacement;
this.should_remove = _should_remove;
if (removed) return null;
}
}
return node;
}
}
// @ts-check
/** @typedef { import('estree').BaseNode} BaseNode */
/** @typedef { import('./walker').WalkerContext} WalkerContext */
/** @typedef {(
* this: WalkerContext,
* node: BaseNode,
* parent: BaseNode,
* key: string,
* index: number
* ) => Promise<void>} AsyncHandler */
class AsyncWalker extends WalkerBase {
/**
*
* @param {AsyncHandler} enter
* @param {AsyncHandler} leave
*/
constructor(enter, leave) {
super();
/** @type {AsyncHandler} */
this.enter = enter;
/** @type {AsyncHandler} */
this.leave = leave;
}
/**
*
* @param {BaseNode} node
* @param {BaseNode} parent
* @param {string} [prop]
* @param {number} [index]
* @returns {Promise<BaseNode>}
*/
async visit(node, parent, prop, index) {
if (node) {
if (this.enter) {
const _should_skip = this.should_skip;
const _should_remove = this.should_remove;
const _replacement = this.replacement;
this.should_skip = false;
this.should_remove = false;
this.replacement = null;
await this.enter.call(this.context, node, parent, prop, index);
if (this.replacement) {
node = this.replacement;
this.replace(parent, prop, index, node);
}
if (this.should_remove) {
this.remove(parent, prop, index);
}
const skipped = this.should_skip;
const removed = this.should_remove;
this.should_skip = _should_skip;
this.should_remove = _should_remove;
this.replacement = _replacement;
if (skipped) return node;
if (removed) return null;
}
for (const key in node) {
const value = node[key];
if (typeof value !== "object") {
continue;
} else if (Array.isArray(value)) {
for (let i = 0; i < value.length; i += 1) {
if (value[i] !== null && typeof value[i].type === 'string') {
if (!(await this.visit(value[i], node, key, i))) {
// removed
i--;
}
}
}
} else if (value !== null && typeof value.type === "string") {
await this.visit(value, node, key, null);
}
}
if (this.leave) {
const _replacement = this.replacement;
const _should_remove = this.should_remove;
this.replacement = null;
this.should_remove = false;
await this.leave.call(this.context, node, parent, prop, index);
if (this.replacement) {
node = this.replacement;
this.replace(parent, prop, index, node);
}
if (this.should_remove) {
this.remove(parent, prop, index);
}
const removed = this.should_remove;
this.replacement = _replacement;
this.should_remove = _should_remove;
if (removed) return null;
}
}
return node;
}
}
// @ts-check
/** @typedef { import('estree').BaseNode} BaseNode */
/** @typedef { import('./sync.js').SyncHandler} SyncHandler */
/** @typedef { import('./async.js').AsyncHandler} AsyncHandler */
/**
*
* @param {BaseNode} ast
* @param {{
* enter?: SyncHandler
* leave?: SyncHandler
* }} walker
* @returns {BaseNode}
*/
function walk(ast, { enter, leave }) {
const instance = new SyncWalker(enter, leave);
return instance.visit(ast, null);
}
/**
*
* @param {BaseNode} ast
* @param {{
* enter?: AsyncHandler
* leave?: AsyncHandler
* }} walker
* @returns {Promise<BaseNode>}
*/
async function asyncWalk(ast, { enter, leave }) {
const instance = new AsyncWalker(enter, leave);
return await instance.visit(ast, null);
}
exports.asyncWalk = asyncWalk;
exports.walk = walk;
Object.defineProperty(exports, '__esModule', { value: true });
})));
+37
View File
@@ -0,0 +1,37 @@
{
"name": "estree-walker",
"description": "Traverse an ESTree-compliant AST",
"version": "2.0.2",
"private": false,
"author": "Rich Harris",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/Rich-Harris/estree-walker"
},
"type": "commonjs",
"main": "./dist/umd/estree-walker.js",
"module": "./dist/esm/estree-walker.js",
"exports": {
"require": "./dist/umd/estree-walker.js",
"import": "./dist/esm/estree-walker.js"
},
"types": "types/index.d.ts",
"scripts": {
"prepublishOnly": "npm run build && npm test",
"build": "tsc && rollup -c",
"test": "uvu test"
},
"devDependencies": {
"@types/estree": "0.0.42",
"rollup": "^2.10.9",
"typescript": "^3.7.5",
"uvu": "^0.5.1"
},
"files": [
"src",
"dist",
"types",
"README.md"
]
}
@@ -0,0 +1,67 @@
import { removeEffect, addEffect, GroupAnimationWithThen } from 'motion-dom';
import { removeItem } from 'motion-utils';
import { animateSequence } from './sequence.mjs';
import { animateSubject } from './subject.mjs';
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 GroupAnimationWithThen(animations);
if (animationOnComplete) {
animation.finished.then(animationOnComplete);
}
if (scope) {
scope.animations.push(animation);
animation.finished.then(() => {
removeItem(scope.animations, animation);
});
}
return animation;
}
return scopedAnimate;
}
const animate = Object.assign(createScopedAnimate(), {
/**
* Register an effect so `animate()` can animate the subjects it
* claims, for instance `animate.addEffect(threeEffect)`. The most
* recently added effect is tested first. DOM elements are always
* animated directly.
*/
addEffect,
removeEffect,
});
export { animate, createScopedAnimate };
//# sourceMappingURL=index.mjs.map
@@ -0,0 +1,23 @@
import { resolveElements } from 'motion-dom';
import { isDOMKeyframes } from '../utils/is-dom-keyframes.mjs';
function resolveSubjects(subject, keyframes, scope, selectorCache) {
if (subject == null) {
return [];
}
if (typeof subject === "string" && isDOMKeyframes(keyframes)) {
return 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];
}
}
export { resolveSubjects };
//# sourceMappingURL=resolve-subjects.mjs.map
@@ -0,0 +1,36 @@
import { motionValue, spring } from 'motion-dom';
import { createAnimationsFromSequence } from '../sequence/create.mjs';
import { animateSubject } from './subject.mjs';
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 = 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 });
animationDefinitions.forEach(({ keyframes, transition }, subject) => {
animations.push(...animateSubject(subject, keyframes, transition));
});
return animations;
}
export { animateSequence };
//# sourceMappingURL=sequence.mjs.map
@@ -0,0 +1,62 @@
import { animateSingleValue, animateElement, visualElementStore, animateEffectSubject, propEffect, findEffect, isMotionValue } from 'motion-dom';
import { invariant } from 'motion-utils';
import { isDOMKeyframes } from '../utils/is-dom-keyframes.mjs';
import { resolveSubjects } from './resolve-subjects.mjs';
function isSingleValue(subject, keyframes) {
return (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(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;
invariant(Boolean(numSubjects), "No valid elements provided.", "no-valid-elements");
for (let i = 0; i < numSubjects; i++) {
const thisSubject = subjects[i];
const transition = { ...options };
/**
* Resolve stagger function if provided.
*/
if ("delay" in transition &&
typeof transition.delay === "function") {
transition.delay = transition.delay(i, numSubjects);
}
if (thisSubject instanceof Element) {
/**
* An element already owned by a VisualElement (a <motion.*>
* component or animateLayout()) animates its values so they
* share one renderer. Anything else is driven through
* styleEffect (or svgEffect) and the DOM keyframe resolver,
* without creating a VisualElement.
*/
animations.push(...animateElement(thisSubject, keyframes, transition, visualElementStore.get(thisSubject)));
}
else {
/**
* Registered effects (animate.addEffect) claim non-DOM
* subjects before we fall back to writing plain properties.
*/
animations.push(...animateEffectSubject(findEffect(thisSubject) ?? propEffect, thisSubject, keyframes, transition));
}
}
}
return animations;
}
export { animateSubject };
//# sourceMappingURL=subject.mjs.map
@@ -0,0 +1,110 @@
import { resolveElements, getValueTransition, getAnimationMap, animationMapKey, getComputedStyle, fillWildcards, applyPxDefaults, NativeAnimation } from 'motion-dom';
import { invariant, secondsToMilliseconds } from 'motion-utils';
function animateElements(elementOrSelector, keyframes, options, scope) {
// Gracefully handle null/undefined elements (e.g., from querySelector returning null)
if (elementOrSelector == null) {
return [];
}
const elements = resolveElements(elementOrSelector, scope);
const numElements = elements.length;
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 = {
...getValueTransition(elementTransition, valueName),
};
valueOptions.duration && (valueOptions.duration = secondsToMilliseconds(valueOptions.duration));
valueOptions.delay && (valueOptions.delay = secondsToMilliseconds(valueOptions.delay));
/**
* If there's an existing animation playing on this element then stop it
* before creating a new one.
*/
const map = getAnimationMap(element);
const key = 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] = getComputedStyle(element, name);
}
fillWildcards(unresolvedKeyframes);
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(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 NativeAnimation(animationOptions);
map.set(key, animation);
animation.finished.finally(() => map.delete(key));
animations.push(animation);
}
return animations;
}
export { animateElements };
//# sourceMappingURL=animate-elements.mjs.map
@@ -0,0 +1,13 @@
import { GroupAnimationWithThen } from 'motion-dom';
import { animateElements } from './animate-elements.mjs';
const createScopedWaapiAnimate = (scope) => {
function scopedAnimate(elementOrSelector, keyframes, options) {
return new GroupAnimationWithThen(animateElements(elementOrSelector, keyframes, options, scope));
}
return scopedAnimate;
};
const animateMini = /*@__PURE__*/ createScopedWaapiAnimate();
export { animateMini, createScopedWaapiAnimate };
//# sourceMappingURL=animate-style.mjs.map
@@ -0,0 +1,308 @@
import { isMotionValue, defaultOffset, isGenerator, createGeneratorEasing, fillOffset } from 'motion-dom';
import { progress, secondsToMilliseconds, warning, reverseEasing, getEasingForSegment } from 'motion-utils';
import { resolveSubjects } from '../animate/resolve-subjects.mjs';
import { calculateRepeatDuration } from './utils/calc-repeat-duration.mjs';
import { calcNextTime } from './utils/calc-time.mjs';
import { addKeyframes } from './utils/edit.mjs';
import { normalizeTimes } from './utils/normalize-times.mjs';
import { compareByTime } from './utils/sort.mjs';
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 = 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 = 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 = secondsToMilliseconds(duration);
}
const springEasing = 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 && 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) {
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"
? 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"
: 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 (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(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);
export { createAnimationsFromSequence, getValueTransition };
//# sourceMappingURL=create.mjs.map
@@ -0,0 +1,6 @@
function calculateRepeatDuration(duration, repeat, repeatDelay) {
return duration * (repeat + 1) + repeatDelay * repeat;
}
export { calculateRepeatDuration };
//# sourceMappingURL=calc-repeat-duration.mjs.map
@@ -0,0 +1,24 @@
/**
* 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;
}
}
export { calcNextTime };
//# sourceMappingURL=calc-time.mjs.map
@@ -0,0 +1,31 @@
import { mixNumber } from 'motion-dom';
import { getEasingForSegment, removeItem } from 'motion-utils';
function eraseKeyframes(sequence, startTime, endTime) {
for (let i = 0; i < sequence.length; i++) {
const keyframe = sequence[i];
if (keyframe.at > startTime && keyframe.at < endTime) {
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: mixNumber(startTime, endTime, offset[i]),
easing: getEasingForSegment(easing, i),
});
}
}
export { addKeyframes, eraseKeyframes };
//# sourceMappingURL=edit.mjs.map
@@ -0,0 +1,18 @@
/**
* 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;
}
}
export { normalizeTimes };
//# sourceMappingURL=normalize-times.mjs.map
@@ -0,0 +1,15 @@
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;
}
}
export { compareByTime };
//# sourceMappingURL=sort.mjs.map
@@ -0,0 +1,6 @@
function isDOMKeyframes(keyframes) {
return typeof keyframes === "object" && !Array.isArray(keyframes);
}
export { isDOMKeyframes };
//# sourceMappingURL=is-dom-keyframes.mjs.map
+10
View File
@@ -0,0 +1,10 @@
export * from 'motion-dom';
export { delayInSeconds as delay } from 'motion-dom';
export * from 'motion-utils';
export { animate, createScopedAnimate } from './animation/animate/index.mjs';
export { animateMini } from './animation/animators/waapi/animate-style.mjs';
export { scroll } from './render/dom/scroll/index.mjs';
export { scrollInfo } from './render/dom/scroll/track.mjs';
export { inView } from './render/dom/viewport/index.mjs';
export { distance, distance2D } from './utils/distance.mjs';
//# sourceMappingURL=dom.mjs.map
@@ -0,0 +1,102 @@
import { resize } from 'motion-dom';
import { scrollInfo } from './track.mjs';
import { canUseNativeTimeline } from './utils/can-use-native-timeline.mjs';
import { getTimeline } from './utils/get-timeline.mjs';
import { offsetToViewTimelineRange } from './utils/offset-to-range.mjs';
function attachToAnimation(animation, options) {
const { target, container, axis } = options;
const range = target && offsetToViewTimelineRange(options.offset);
/**
* Use native timeline when:
* - No target and no offset: ScrollTimeline
* - Target with mappable offset: ViewTimeline with named range
* - Otherwise: fall back to JS observe. A ScrollTimeline has no offset,
* so page offsets are applied by the JS timeline.
*/
const native = canUseNativeTimeline(target) && (target ? !!range : !options.offset);
const animations = new Map();
const observed = new Set();
let stopObserving;
let reverse = false;
/**
* When an offset's second point comes first, its range is swapped and
* the animation played backwards. The range is set after the direction,
* as that's what realigns a running animation with its timeline. Chrome
* skips that realignment when the range changes in the frame the
* animation starts, leaving it offset for good, so it's replayed too.
*/
const apply = (direction, waapi) => {
const [start, end] = range.points;
waapi.effect.updateTiming({
direction: reverse
? direction === "normal"
? "reverse"
: "alternate-reverse"
: direction,
});
Object.assign(waapi, {
rangeStart: reverse ? end : start,
rangeEnd: reverse ? start : end,
});
waapi.play();
};
const stops = [
animation.attachTimeline({
/**
* Read only by WAAPI animations, so values driven from JS don't
* create a native timeline they won't use.
*/
get timeline() {
return native ? getTimeline(options) : undefined;
},
onAttach: range &&
((waapi) => {
const direction = waapi.effect.getTiming().direction;
animations.set(waapi, direction);
apply(direction, waapi);
}),
/**
* Values driven from JS all track the offset with scrollInfo,
* which measures once per frame for all of them.
*/
observe: (valueAnimation) => {
valueAnimation.pause();
observed.add(valueAnimation);
stopObserving || (stopObserving = scrollInfo((info) => {
observed.forEach((observedAnimation) => {
observedAnimation.time =
observedAnimation.iterationDuration *
info[axis].progress;
});
}, options));
return () => observed.delete(valueAnimation);
},
}),
];
if (native && range) {
const { a, b } = range;
const length = axis === "y" ? "clientHeight" : "clientWidth";
/**
* Offsets like All only run forwards when the target is longer than
* the container, so their direction is remeasured on resize.
*/
const update = () => {
if (reverse !==
(reverse = a * target[length] + b * container[length] < 0)) {
animations.forEach(apply);
}
};
update();
if (a * b < 0) {
stops.push(resize(update), resize([target, container], update));
}
}
return () => {
stops.forEach((stop) => stop());
stopObserving?.();
};
}
export { attachToAnimation };
//# sourceMappingURL=attach-animation.mjs.map
@@ -0,0 +1,19 @@
import { noop } from 'motion-utils';
import { attachToAnimation } from './attach-animation.mjs';
import { scrollInfo } from './track.mjs';
function scroll(onScroll, { axis = "y", source, container = document.scrollingElement, ...options } = {}) {
if (!container)
return noop;
const optionsWithDefaults = {
axis,
container: source || container,
...options,
};
return typeof onScroll === "function"
? scrollInfo((info) => onScroll(info[axis].progress, info), optionsWithDefaults)
: attachToAnimation(onScroll, optionsWithDefaults);
}
export { scroll };
//# sourceMappingURL=index.mjs.map
@@ -0,0 +1,102 @@
import { clamp, progress, velocityPerSecond } from 'motion-utils';
/**
* A time in milliseconds, beyond which we consider the scroll velocity to be 0.
*/
const maxElapsed = 50;
const createAxisInfo = () => ({
current: 0,
offset: [],
progress: 0,
scrollLength: 0,
targetOffset: 0,
targetLength: 0,
containerLength: 0,
velocity: 0,
});
const createScrollInfo = () => ({
time: 0,
x: createAxisInfo(),
y: createAxisInfo(),
});
/**
* Also iterated with for...in as the list of axes.
*/
const axisKeys = {
x: {
length: "Width",
position: "Left",
},
y: {
length: "Height",
position: "Top",
},
};
const styles = new WeakMap();
/**
* Whether an axis scrolls from its end, which browsers report as a scroll
* position running from 0 to -scrollLength (#3340). This is read from style
* rather than the sign of the scroll position, as elastic overscroll pushes
* the position past 0 in either kind of axis (#3791).
*/
function isReversed(element, axisName) {
/**
* The viewport takes its writing mode and direction from <body>, which
* not every browser reflects in the root element's computed style. It
* doesn't adopt the root element's flex layout.
*/
const isRoot = element === document.scrollingElement;
const source = (isRoot && document.body) || element;
let style = styles.get(source);
if (!style)
styles.set(source, (style = getComputedStyle(source)));
const { writingMode, flexDirection } = style;
const isInline = (axisName === "x") !== /^[vs]/.test(writingMode);
return ((isInline
? (style.direction === "rtl") !== (writingMode === "sideways-lr")
: writingMode.endsWith("rl")) !==
/**
* A reversed flex axis flips this. The main axis is the inline axis
* unless flex-direction is a column.
*/
(!isRoot &&
style.display.includes("flex") &&
(isInline !== (flexDirection[0] === "c")
? flexDirection
: style.flexWrap).endsWith("reverse")));
}
function updateAxisInfo(element, axisName, info, time) {
const axis = info[axisName];
const { length, position } = axisKeys[axisName];
const prev = axis.current;
const prevTime = info.time;
const scrollPosition = element[`scroll${position}`];
axis.current = isReversed(element, axisName)
? -scrollPosition || 0 // Avoid -0
: scrollPosition;
axis.containerLength = element[`client${length}`];
axis.targetLength = element[`scroll${length}`];
axis.scrollLength = axis.targetLength - axis.containerLength;
axis.offset.length = 0;
axis.offset[0] = 0;
axis.offset[1] = axis.scrollLength;
axis.progress = clamp(0, 1, progress(0, axis.scrollLength, axis.current));
const elapsed = time - prevTime;
axis.velocity =
elapsed > maxElapsed
? 0
: velocityPerSecond(axis.current - prev, elapsed);
}
/**
* Measures a scroll container. Runs once per container per frame; every
* handler on that container derives its info from the result.
*/
function updateScrollInfo(element, info, time) {
for (const axis in axisKeys) {
updateAxisInfo(element, axis, info, time);
}
info.time = time;
}
export { axisKeys, createScrollInfo, updateScrollInfo };
//# sourceMappingURL=info.mjs.map
@@ -0,0 +1,46 @@
const namedEdges = {
start: 0,
center: 0.5,
end: 1,
};
function resolveEdge(edge, length, inset = 0) {
let delta = 0;
/**
* If we have this edge defined as a preset, replace the definition
* with the numerical value.
*/
if (edge in namedEdges) {
edge = namedEdges[edge];
}
/**
* Handle unit values
*/
if (typeof edge === "string") {
const asNumber = parseFloat(edge);
if (edge.endsWith("px")) {
delta = asNumber;
}
else if (edge.endsWith("%")) {
edge = asNumber / 100;
}
else if (edge.endsWith("vw")) {
delta = (asNumber / 100) * document.documentElement.clientWidth;
}
else if (edge.endsWith("vh")) {
delta = (asNumber / 100) * document.documentElement.clientHeight;
}
else {
edge = asNumber;
}
}
/**
* If the edge is defined as a number, handle as a progress value.
*/
if (typeof edge === "number") {
delta = length * edge;
}
return inset + delta;
}
export { namedEdges, resolveEdge };
//# sourceMappingURL=edge.mjs.map
@@ -0,0 +1,46 @@
import { clamp, progress } from 'motion-utils';
import { resolveOffset } from './offset.mjs';
import { ScrollOffset } from './presets.mjs';
/**
* Resolved offsets map to evenly spaced progress values, so progress is
* derived from the segment index rather than building an interpolator.
*/
function offsetsToProgress(offsets, v) {
const n = offsets.length - 1;
if (n < 1)
return 0;
const reverse = offsets[0] > offsets[n];
const at = (i) => offsets[reverse ? n - i : i];
/**
* Matches interpolate(), which checks for a zero-length first range
* before reversing descending offsets.
*/
if (offsets[0] === offsets[1] && v < at(0))
return reverse ? 1 : 0;
let i = 0;
while (i < n - 1 && v >= at(i + 1))
i++;
const p = (i + progress(at(i), at(i + 1), v)) / n;
return reverse ? 1 - p : p;
}
function resolveOffsets(info, options) {
const { offset: offsetDefinition = ScrollOffset.All, axis = "y" } = options;
const axisInfo = info[axis];
/**
* Reset the length of the resolved offset array rather than creating a new one.
*/
axisInfo.offset.length = 0;
/**
* Populate the offset array by resolving the user's offset definition into
* a list of pixel scroll offsets.
*/
const numOffsets = offsetDefinition.length;
for (let i = 0; i < numOffsets; i++) {
axisInfo.offset[i] = resolveOffset(offsetDefinition[i], axisInfo.containerLength, axisInfo.targetLength, axisInfo.targetOffset);
}
axisInfo.progress = clamp(0, 1, offsetsToProgress(axisInfo.offset, axisInfo.current));
}
export { resolveOffsets };
//# sourceMappingURL=index.mjs.map
@@ -0,0 +1,48 @@
import { isHTMLElement } from 'motion-dom';
function calcInset(element, container) {
const inset = { x: 0, y: 0 };
let current = element;
while (current && current !== container) {
if (isHTMLElement(current)) {
inset.x += current.offsetLeft;
inset.y += current.offsetTop;
current = current.offsetParent;
}
else if (current.tagName === "svg") {
/**
* This isn't an ideal approach to measuring the offset of <svg /> tags.
* It would be preferable, given they behave like HTMLElements in most ways
* to use offsetLeft/Top. But these don't exist on <svg />. Likewise we
* can't use .getBBox() like most SVG elements as these provide the offset
* relative to the SVG itself, which for <svg /> is usually 0x0.
*/
const svgBoundingBox = current.getBoundingClientRect();
current = current.parentElement;
const parentBoundingBox = current.getBoundingClientRect();
inset.x += svgBoundingBox.left - parentBoundingBox.left;
inset.y += svgBoundingBox.top - parentBoundingBox.top;
}
else if (current instanceof SVGGraphicsElement) {
const { x, y } = current.getBBox();
inset.x += x;
inset.y += y;
let svg = null;
let parent = current.parentNode;
while (!svg) {
if (parent.tagName === "svg") {
svg = parent;
}
parent = current.parentNode;
}
current = svg;
}
else {
break;
}
}
return inset;
}
export { calcInset };
//# sourceMappingURL=inset.mjs.map
@@ -0,0 +1,36 @@
import { resolveEdge, namedEdges } from './edge.mjs';
const defaultOffset = [0, 0];
function resolveOffset(offset, containerLength, targetLength, targetInset) {
let offsetDefinition = Array.isArray(offset) ? offset : defaultOffset;
let targetPoint = 0;
let containerPoint = 0;
if (typeof offset === "number") {
/**
* If we're provided offset: [0, 0.5, 1] then each number x should become
* [x, x], so we default to the behaviour of mapping 0 => 0 of both target
* and container etc.
*/
offsetDefinition = [offset, offset];
}
else if (typeof offset === "string") {
offset = offset.trim();
if (offset.includes(" ")) {
offsetDefinition = offset.split(" ");
}
else {
/**
* If we're provided a definition like "100px" then we want to apply
* that only to the top of the target point, leaving the container at 0.
* Whereas a named offset like "end" should be applied to both.
*/
offsetDefinition = [offset, namedEdges[offset] ? offset : `0`];
}
}
targetPoint = resolveEdge(offsetDefinition[0], targetLength, targetInset);
containerPoint = resolveEdge(offsetDefinition[1], containerLength);
return targetPoint - containerPoint;
}
export { resolveOffset };
//# sourceMappingURL=offset.mjs.map
@@ -0,0 +1,9 @@
const ScrollOffset = {
All: [
[0, 0],
[1, 1],
],
};
export { ScrollOffset };
//# sourceMappingURL=presets.mjs.map
@@ -0,0 +1,58 @@
import { warnOnce } from 'motion-utils';
import { axisKeys } from './info.mjs';
import { resolveOffsets } from './offsets/index.mjs';
import { calcInset } from './offsets/inset.mjs';
function getTargetSize(target) {
return "getBBox" in target && target.tagName !== "svg"
? target.getBBox()
: { width: target.clientWidth, height: target.clientHeight };
}
function createOnScrollHandler(container, onScroll, info, options = {}) {
const { target } = options;
/**
* 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 (target &&
target !== container &&
container !== document.documentElement &&
container !== document.scrollingElement &&
container !== document.body) {
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.");
}
}
/**
* Handlers without a target or offset are notified with the container's
* shared info object, so they measure nothing themselves.
*/
const needsOwnInfo = target || options.offset;
return {
measure: (containerInfo) => {
if (!needsOwnInfo)
return;
info.time = containerInfo.time;
for (const key in axisKeys) {
const axis = key;
const { offset } = info[axis];
Object.assign(info[axis], containerInfo[axis]);
info[axis].offset = offset;
}
if (target && target !== container) {
const inset = calcInset(target, container);
const size = getTargetSize(target);
info.x.targetOffset = inset.x;
info.y.targetOffset = inset.y;
info.x.targetLength = size.width;
info.y.targetLength = size.height;
}
resolveOffsets(info, options);
},
notify: (containerInfo) => onScroll(needsOwnInfo ? info : containerInfo),
};
}
export { createOnScrollHandler };
//# sourceMappingURL=on-scroll-handler.mjs.map
@@ -0,0 +1,117 @@
import { resize, frame, cancelFrame, frameData } from 'motion-dom';
import { noop } from 'motion-utils';
import { createScrollInfo, updateScrollInfo } from './info.mjs';
import { createOnScrollHandler } from './on-scroll-handler.mjs';
const scrollListeners = new WeakMap();
const resizeListeners = new WeakMap();
const onScrollHandlers = new WeakMap();
const dimensionCheckProcesses = new WeakMap();
const getEventTarget = (element) => element === document.scrollingElement ? window : element;
function scrollInfo(onScroll, { container = document.scrollingElement, trackContentSize = false, ...options } = {}) {
if (!container)
return noop;
let containerHandlers = onScrollHandlers.get(container);
/**
* Get the onScroll handlers for this container.
* If one isn't found, create a new one.
*/
if (!containerHandlers) {
containerHandlers = new Set();
onScrollHandlers.set(container, containerHandlers);
}
/**
* Create a new onScroll handler for the provided callback.
*/
const info = createScrollInfo();
const containerHandler = createOnScrollHandler(container, onScroll, info, options);
containerHandlers.add(containerHandler);
/**
* Check if there's a scroll event listener for this container.
* If not, create one.
*/
if (!scrollListeners.has(container)) {
const containerInfo = createScrollInfo();
const measureAll = () => {
updateScrollInfo(container, containerInfo, frameData.timestamp);
for (const handler of containerHandlers) {
handler.measure(containerInfo);
}
frame.preUpdate(notifyAll);
};
const notifyAll = () => {
for (const handler of containerHandlers) {
handler.notify(containerInfo);
}
};
const listener = () => frame.read(measureAll);
scrollListeners.set(container, listener);
const target = getEventTarget(container);
window.addEventListener("resize", listener);
if (container !== document.documentElement) {
resizeListeners.set(container, resize(container, listener));
}
target.addEventListener("scroll", listener);
listener();
}
/**
* Enable content size tracking if requested and not already enabled.
*/
if (trackContentSize && !dimensionCheckProcesses.has(container)) {
const listener = scrollListeners.get(container);
// Store initial scroll dimensions (object is reused to avoid allocation)
const size = {
width: container.scrollWidth,
height: container.scrollHeight,
};
// Add frame-based scroll dimension checking to detect content changes
const checkScrollDimensions = () => {
const newWidth = container.scrollWidth;
const newHeight = container.scrollHeight;
if (size.width !== newWidth || size.height !== newHeight) {
listener();
size.width = newWidth;
size.height = newHeight;
}
};
// Schedule with keepAlive=true to run every frame
const dimensionCheckProcess = frame.read(checkScrollDimensions, true);
dimensionCheckProcesses.set(container, dimensionCheckProcess);
}
const listener = scrollListeners.get(container);
frame.read(listener, false, true);
return () => {
cancelFrame(listener);
/**
* Check if we even have any handlers for this container.
*/
const currentHandlers = onScrollHandlers.get(container);
if (!currentHandlers)
return;
currentHandlers.delete(containerHandler);
if (currentHandlers.size)
return;
/**
* If no more handlers, remove the scroll listener too. The handler
* set goes with it, as a measure still queued from this listener
* would otherwise notify handlers added by a later scrollInfo call.
*/
onScrollHandlers.delete(container);
const scrollListener = scrollListeners.get(container);
scrollListeners.delete(container);
if (scrollListener) {
getEventTarget(container).removeEventListener("scroll", scrollListener);
resizeListeners.get(container)?.();
window.removeEventListener("resize", scrollListener);
}
// Clean up scroll dimension checking
const dimensionCheckProcess = dimensionCheckProcesses.get(container);
if (dimensionCheckProcess) {
cancelFrame(dimensionCheckProcess);
dimensionCheckProcesses.delete(container);
}
};
}
export { scrollInfo };
//# sourceMappingURL=track.mjs.map
@@ -0,0 +1,10 @@
import { supportsViewTimeline, supportsScrollTimeline } from 'motion-dom';
function canUseNativeTimeline(target) {
if (typeof window === "undefined")
return false;
return target ? supportsViewTimeline() : supportsScrollTimeline();
}
export { canUseNativeTimeline };
//# sourceMappingURL=can-use-native-timeline.mjs.map
@@ -0,0 +1,25 @@
const timelineCache = new Map();
/**
* Native timelines are only attached to WAAPI animations. Offsets are
* applied to each animation as a range, so a timeline is shared by every
* offset.
*/
function getTimeline({ container, target, axis, }) {
let containerCache = timelineCache.get(container);
if (!containerCache) {
containerCache = new Map();
timelineCache.set(container, containerCache);
}
const targetKey = target ?? "self";
let targetCache = containerCache.get(targetKey);
if (!targetCache) {
targetCache = {};
containerCache.set(targetKey, targetCache);
}
return (targetCache[axis] || (targetCache[axis] = target
? new ViewTimeline({ subject: target, axis })
: new ScrollTimeline({ source: container, axis })));
}
export { getTimeline };
//# sourceMappingURL=get-timeline.mjs.map
@@ -0,0 +1,41 @@
import { resolveOffset } from '../offsets/offset.mjs';
import { ScrollOffset } from '../offsets/presets.mjs';
/**
* Resolved offsets are linear in the target and container lengths, so
* probing them gives [target progress, container progress, pixels].
* vw/vh can resolve to 0px, so they're rejected up front.
*/
const toIntersection = (o) => {
if (/v/u.test(o))
return [];
const px = resolveOffset(o, 0, 0, 0);
return [resolveOffset(o, 0, 1, 0) - px, px - resolveOffset(o, 1, 0, 0), px];
};
const toRange = ([t, c, px]) => !px &&
(c === 0 || c === 1) &&
`${c ? "entry" : "exit"}-crossing ${t * 100}%`;
/**
* Maps an offset to an equivalent ViewTimeline range. Returns undefined when
* there isn't one, which signals the caller to fall back to JS-based scroll
* tracking.
*/
function offsetToViewTimelineRange(offset = ScrollOffset.All) {
if (offset.length !== 2)
return;
const [start, end] = offset.map(toIntersection);
const points = [toRange(start), toRange(end)];
const a = end[0] - start[0];
const b = start[1] - end[1];
if (points[0] && points[1] && (a || b)) {
return {
points: points,
a,
b,
cover: !start[0] && a === 1 && b === 1,
};
}
}
export { offsetToViewTimelineRange };
//# sourceMappingURL=offset-to-range.mjs.map
@@ -0,0 +1,44 @@
import { resolveElements } from 'motion-dom';
const thresholds = {
some: 0,
all: 1,
};
function inView(elementOrSelector, onStart, { root, margin: rootMargin, amount = "some" } = {}) {
const elements = 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();
}
export { inView };
//# sourceMappingURL=index.mjs.map
@@ -0,0 +1,10 @@
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);
}
export { distance, distance2D };
//# sourceMappingURL=distance.mjs.map
+152
View File
@@ -0,0 +1,152 @@
{
"name": "framer-motion",
"version": "14.3.1",
"description": "A simple and powerful JavaScript animation library",
"main": "dist/cjs/index.js",
"module": "dist/es/index.mjs",
"exports": {
".": {
"types": "./dist/index.d.ts",
"require": "./dist/cjs/index.js",
"import": "./dist/es/index.mjs",
"default": "./dist/cjs/index.js"
},
"./debug": {
"types": "./dist/debug.d.ts",
"require": "./dist/cjs/debug.js",
"import": "./dist/es/debug.mjs",
"default": "./dist/cjs/debug.js"
},
"./dom/mini": {
"types": "./dist/dom-mini.d.ts",
"require": "./dist/cjs/dom-mini.js",
"import": "./dist/es/dom-mini.mjs",
"default": "./dist/cjs/dom-mini.js"
},
"./dom": {
"types": "./dist/dom.d.ts",
"require": "./dist/cjs/dom.js",
"import": "./dist/es/dom.mjs",
"default": "./dist/cjs/dom.js"
},
"./client": {
"types": "./dist/client.d.ts",
"require": "./dist/cjs/client.js",
"import": "./dist/es/client.mjs",
"default": "./dist/cjs/client.js"
},
"./m": {
"types": "./dist/m.d.ts",
"require": "./dist/cjs/m.js",
"import": "./dist/es/m.mjs",
"default": "./dist/cjs/m.js"
},
"./mini": {
"types": "./dist/mini.d.ts",
"require": "./dist/cjs/mini.js",
"import": "./dist/es/mini.mjs",
"default": "./dist/cjs/mini.js"
},
"./projection": {
"types": "./dist/projection.d.ts",
"import": "./dist/es/projection.mjs",
"default": "./dist/es/projection.mjs"
},
"./animate-view": {
"types": "./dist/animate-view.d.ts",
"require": "./dist/cjs/animate-view.js",
"import": "./dist/es/animate-view.mjs",
"default": "./dist/cjs/animate-view.js"
},
"./package.json": "./package.json"
},
"types": "dist/index.d.ts",
"author": "Matt Perry",
"license": "MIT",
"repository": "https://github.com/motiondivision/motion/",
"sideEffects": false,
"keywords": [
"react animation",
"react",
"pose",
"react pose",
"animation",
"gestures",
"drag",
"spring",
"popmotion",
"framer",
"waapi"
],
"scripts": {
"build": "yarn clean && tsc --noEmitOnError -p . && rollup -c && node ./scripts/check-bundle.js",
"dev": "yarn watch",
"clean": "rm -rf types dist lib",
"test": "yarn test-server && yarn test-client",
"test-client": "jest --config jest.config.json --max-workers=2",
"test-server": "jest --config jest.config.ssr.json",
"prettier": "prettier ./src/* --write",
"watch": "concurrently -c blue,red -n tsc --noEmitOnError ,rollup --kill-others \"tsc --noEmitOnError --watch -p . --preserveWatchOutput\" \"rollup --config --watch --no-watch.clearScreen\"",
"prepack": "yarn build && yarn measure && node ../../dev/inc/bundlesize.mjs framer-motion",
"postpublish": "git push --tags",
"measure": "rollup -c ./rollup.size.config.mjs"
},
"dependencies": {
"motion-dom": "14.3.1",
"motion-utils": "14.1.1",
"tslib": "^2.4.0"
},
"devDependencies": {
"@radix-ui/react-dialog": "^1.1.15",
"@thednp/dommatrix": "^2.0.11",
"@types/three": "0.137.0",
"concurrently": "^7.3.0",
"jest": "^29.7.0",
"rollup": "4.59.0",
"three": "0.137.0",
"typescript": "5.4.3"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
},
"bundlesize": [
{
"path": "./dist/size-rollup-motion.js",
"maxSize": "42.2 kB"
},
{
"path": "./dist/size-rollup-m.js",
"maxSize": "5 kB"
},
{
"path": "./dist/size-rollup-m-dom-animation.js",
"maxSize": "28.6 kB"
},
{
"path": "./dist/size-rollup-m-dom-max.js",
"maxSize": "42.3 kB"
},
{
"path": "./dist/size-rollup-animate.js",
"maxSize": "22 kB"
},
{
"path": "./dist/size-rollup-scroll.js",
"maxSize": "3.7 kB"
},
{
"path": "./dist/size-rollup-waapi-animate.js",
"maxSize": "3.2 kB"
}
],
"gitHead": "1785a8816da2682ef53bfc1fe8b3f61d86f17924"
}
+18
View File
@@ -0,0 +1,18 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.warning = function () { };
exports.invariant = function () { };
if (process.env.NODE_ENV !== 'production') {
exports.warning = function (check, message) {
if (!check && typeof console !== 'undefined') {
console.warn(message);
}
};
exports.invariant = function (check, message) {
if (!check) {
throw new Error(message);
}
};
}
+55
View File
@@ -0,0 +1,55 @@
{
"name": "hey-listen",
"version": "1.0.8",
"description": "Warning and invariant dev-ex messaging.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"module": "dist/hey-listen.es.js",
"jsnext:main": "dist/hey-listen.es.js",
"scripts": {
"test": "jest",
"build": "rollup -c",
"watch": "rollup -c -w",
"prepublishOnly": "npm run test && npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Popmotion/hey-listen.git"
},
"keywords": [
"warning",
"invariant"
],
"author": "Matt Perry",
"license": "MIT",
"bugs": {
"url": "https://github.com/Popmotion/hey-listen/issues"
},
"homepage": "https://github.com/Popmotion/hey-listen#readme",
"devDependencies": {
"@types/jest": "^22.2.3",
"@types/node": "^10.0.6",
"jest": "^24.7.1",
"rollup": "^1.9.2",
"rollup-plugin-typescript2": "^0.14.0",
"rollup-plugin-uglify": "^3.0.0",
"ts-jest": "^22.4.5",
"typescript": "^2.8.3"
},
"jest": {
"moduleFileExtensions": [
"ts",
"js",
"json",
"node"
],
"transform": {
"\\.(ts)$": "../node_modules/ts-jest/preprocessor.js"
},
"testRegex": "/_tests/.*\\.(ts|js)$",
"rootDir": "src"
},
"prettier": {
"singleQuote": true
}
}
+257
View File
@@ -0,0 +1,257 @@
function flatHooks(configHooks, hooks = {}, parentName) {
for (const key in configHooks) {
const subHook = configHooks[key];
const name = parentName ? `${parentName}:${key}` : key;
if (typeof subHook === "object" && subHook !== null) flatHooks(subHook, hooks, name);
else if (typeof subHook === "function") hooks[name] = subHook;
}
return hooks;
}
function mergeHooks(...hooks) {
const finalHooks = {};
for (const hook of hooks) {
const flatenHook = flatHooks(hook);
for (const key in flatenHook) if (finalHooks[key]) finalHooks[key].push(flatenHook[key]);
else finalHooks[key] = [flatenHook[key]];
}
for (const key in finalHooks) if (finalHooks[key].length > 1) {
const array = finalHooks[key];
finalHooks[key] = (...arguments_) => serial(array, (function_) => function_(...arguments_));
} else finalHooks[key] = finalHooks[key][0];
return finalHooks;
}
function serial(tasks, function_) {
return tasks.reduce((promise, task) => promise.then(() => function_(task)), Promise.resolve());
}
const createTask = /* @__PURE__ */ (() => {
if (console.createTask) return console.createTask;
const defaultTask = { run: (fn) => fn() };
return () => defaultTask;
})();
function callHooks(hooks, args, startIndex, task) {
for (let i = startIndex; i < hooks.length; i += 1) try {
const result = task ? task.run(() => hooks[i](...args)) : hooks[i](...args);
if (result && typeof result.then === "function") return Promise.resolve(result).then(() => callHooks(hooks, args, i + 1, task));
} catch (error) {
return Promise.reject(error);
}
}
function serialTaskCaller(hooks, args, name) {
if (hooks.length > 0) return callHooks(hooks, args, 0, createTask(name));
}
function parallelTaskCaller(hooks, args, name) {
if (hooks.length > 0) {
const task = createTask(name);
return Promise.all(hooks.map((hook) => task.run(() => hook(...args))));
}
}
function serialCaller(hooks, arguments_) {
return hooks.reduce((promise, hookFunction) => promise.then(() => hookFunction(...arguments_ || [])), Promise.resolve());
}
function parallelCaller(hooks, args) {
return Promise.all(hooks.map((hook) => hook(...args || [])));
}
function callEachWith(callbacks, arg0) {
for (const callback of [...callbacks]) callback(arg0);
}
var Hookable = class {
_hooks;
_before;
_after;
_deprecatedHooks;
_deprecatedMessages;
constructor() {
this._hooks = {};
this._before = void 0;
this._after = void 0;
this._deprecatedMessages = void 0;
this._deprecatedHooks = {};
this.hook = this.hook.bind(this);
this.callHook = this.callHook.bind(this);
this.callHookWith = this.callHookWith.bind(this);
}
hook(name, function_, options = {}) {
if (!name || typeof function_ !== "function") return () => {};
const originalName = name;
let dep;
while (this._deprecatedHooks[name]) {
dep = this._deprecatedHooks[name];
name = dep.to;
}
if (dep && !options.allowDeprecated) {
let message = dep.message;
if (!message) message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : "");
if (!this._deprecatedMessages) this._deprecatedMessages = /* @__PURE__ */ new Set();
if (!this._deprecatedMessages.has(message)) {
console.warn(message);
this._deprecatedMessages.add(message);
}
}
if (!function_.name) try {
Object.defineProperty(function_, "name", {
get: () => "_" + name.replace(/\W+/g, "_") + "_hook_cb",
configurable: true
});
} catch {}
this._hooks[name] = this._hooks[name] || [];
this._hooks[name].push(function_);
return () => {
if (function_) {
this.removeHook(name, function_);
function_ = void 0;
}
};
}
hookOnce(name, function_) {
let _unreg;
let _function = (...arguments_) => {
if (typeof _unreg === "function") _unreg();
_unreg = void 0;
_function = void 0;
return function_(...arguments_);
};
_unreg = this.hook(name, _function);
return _unreg;
}
removeHook(name, function_) {
const hooks = this._hooks[name];
if (hooks) {
const index = hooks.indexOf(function_);
if (index !== -1) hooks.splice(index, 1);
if (hooks.length === 0) this._hooks[name] = void 0;
}
}
clearHook(name) {
this._hooks[name] = void 0;
}
deprecateHook(name, deprecated) {
this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated;
const _hooks = this._hooks[name] || [];
this._hooks[name] = void 0;
for (const hook of _hooks) this.hook(name, hook);
}
deprecateHooks(deprecatedHooks) {
for (const name in deprecatedHooks) this.deprecateHook(name, deprecatedHooks[name]);
}
addHooks(configHooks) {
const hooks = flatHooks(configHooks);
const removeFns = Object.keys(hooks).map((key) => this.hook(key, hooks[key]));
return () => {
for (const unreg of removeFns) unreg();
removeFns.length = 0;
};
}
removeHooks(configHooks) {
const hooks = flatHooks(configHooks);
for (const key in hooks) this.removeHook(key, hooks[key]);
}
removeAllHooks() {
this._hooks = {};
}
callHook(name, ...args) {
return this.callHookWith(serialTaskCaller, name, args);
}
callHookParallel(name, ...args) {
return this.callHookWith(parallelTaskCaller, name, args);
}
callHookWith(caller, name, args) {
const event = this._before || this._after ? {
name,
args,
context: {}
} : void 0;
if (this._before) callEachWith(this._before, event);
const result = caller(this._hooks[name] ? [...this._hooks[name]] : [], args, name);
if (result instanceof Promise) return result.finally(() => {
if (this._after && event) callEachWith(this._after, event);
});
if (this._after && event) callEachWith(this._after, event);
return result;
}
beforeEach(function_) {
this._before = this._before || [];
this._before.push(function_);
return () => {
if (this._before !== void 0) {
const index = this._before.indexOf(function_);
if (index !== -1) this._before.splice(index, 1);
}
};
}
afterEach(function_) {
this._after = this._after || [];
this._after.push(function_);
return () => {
if (this._after !== void 0) {
const index = this._after.indexOf(function_);
if (index !== -1) this._after.splice(index, 1);
}
};
}
};
function createHooks() {
return new Hookable();
}
var HookableCore = class {
_hooks;
constructor() {
this._hooks = {};
}
hook(name, fn) {
if (!name || typeof fn !== "function") return () => {};
this._hooks[name] = this._hooks[name] || [];
this._hooks[name].push(fn);
return () => {
if (fn) {
this.removeHook(name, fn);
fn = void 0;
}
};
}
removeHook(name, function_) {
const hooks = this._hooks[name];
if (hooks) {
const index = hooks.indexOf(function_);
if (index !== -1) hooks.splice(index, 1);
if (hooks.length === 0) this._hooks[name] = void 0;
}
}
callHook(name, ...args) {
const hooks = this._hooks[name];
if (!hooks || hooks.length === 0) return;
return callHooks(hooks.slice(), args, 0);
}
};
const isBrowser = typeof window !== "undefined";
function createDebugger(hooks, _options = {}) {
const options = {
inspect: isBrowser,
group: isBrowser,
filter: () => true,
..._options
};
const _filter = options.filter;
const filter = typeof _filter === "string" ? (name) => name.startsWith(_filter) : _filter;
const _tag = options.tag ? `[${options.tag}] ` : "";
const logPrefix = (event) => _tag + event.name + "".padEnd(event._id, "\0");
const _idCtr = {};
const unsubscribeBefore = hooks.beforeEach((event) => {
if (filter !== void 0 && !filter(event.name)) return;
_idCtr[event.name] = _idCtr[event.name] || 0;
event._id = _idCtr[event.name]++;
console.time(logPrefix(event));
});
const unsubscribeAfter = hooks.afterEach((event) => {
if (filter !== void 0 && !filter(event.name)) return;
if (options.group) console.groupCollapsed(event.name);
if (options.inspect) console.timeLog(logPrefix(event), event.args);
else console.timeEnd(logPrefix(event));
if (options.group) console.groupEnd();
_idCtr[event.name]--;
});
return { close: () => {
unsubscribeBefore();
unsubscribeAfter();
} };
}
export { Hookable, HookableCore, createDebugger, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };
+54
View File
@@ -0,0 +1,54 @@
{
"name": "hookable",
"version": "6.1.2",
"description": "Awaitable hook system",
"keywords": [
"hook",
"hookable",
"plugin",
"tapable",
"tappable"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "unjs/hookable"
},
"files": [
"dist"
],
"type": "module",
"sideEffects": false,
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
".": "./dist/index.mjs"
},
"devDependencies": {
"@types/node": "^25.9.6",
"@vitest/coverage-v8": "^5",
"changelogen": "^0.6.2",
"esbuild": "^0.28.2",
"eslint-config-unjs": "^0.6.2",
"expect-type": "^1.4.0",
"hookable-prev": "npm:hookable@^6.1.1",
"mitata": "^1.0.34",
"obuild": "^0.4.40",
"oxfmt": ">=0.67.0",
"oxlint": "^1.82.0",
"typescript": "^6",
"vite": "^8.3.0",
"vitest": "^5"
},
"scripts": {
"bench": "node --expose-gc --allow-natives-syntax test/bench.ts",
"build": "obuild src/index.ts",
"dev": "vitest",
"lint": "oxlint . && oxfmt --check src test",
"lint:fix": "oxlint . --fix && oxfmt src test",
"prepublish": "pnpm build",
"release": "pnpm test && pnpm build && changelogen --release --publish --push",
"test": "pnpm lint && vitest run --coverage",
"test:types": "tsc --noEmit"
}
}
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
{
"name": "lucide-vue-next",
"version": "0.475.0",
"author": "Eric Fennis",
"description": "A Lucide icon library package for Vue 3 applications",
"license": "ISC",
"homepage": "https://lucide.dev",
"bugs": "https://github.com/lucide-icons/lucide/issues",
"repository": {
"type": "git",
"url": "https://github.com/lucide-icons/lucide.git",
"directory": "packages/lucide-vue-next"
},
"keywords": [
"Lucide",
"Vue",
"Feather",
"Icons",
"Icon",
"SVG",
"Feather Icons",
"Fontawesome",
"Font Awesome"
],
"amdName": "lucide-vue-next",
"source": "build/lucide-vue-next.js",
"main": "dist/cjs/lucide-vue-next.js",
"main:umd": "dist/umd/lucide-vue-next.js",
"module": "dist/esm/lucide-vue-next.js",
"unpkg": "dist/umd/lucide-vue-next.min.js",
"typings": "dist/lucide-vue-next.d.ts",
"sideEffects": false,
"files": [
"dist",
"nuxt.js"
],
"devDependencies": {
"@testing-library/jest-dom": "^6.1.6",
"@testing-library/vue": "^8.0.3",
"@vitejs/plugin-vue": "^4.6.2",
"@vue/test-utils": "2.4.5",
"rollup": "^4.22.4",
"rollup-plugin-dts": "^6.1.0",
"vite": "5.1.8",
"vitest": "^1.4.0",
"vue": "^3.4.21",
"@lucide/shared": "1.0.0",
"@lucide/rollup-plugins": "1.0.0",
"@lucide/build-icons": "1.1.0"
},
"peerDependencies": {
"vue": ">=3.0.1"
},
"scripts": {
"build": "pnpm clean && pnpm copy:license && pnpm build:icons && pnpm build:bundles",
"copy:license": "cp ../../LICENSE ./LICENSE",
"clean": "rm -rf dist && rm -rf ./src/icons/*.ts",
"build:icons": "build-icons --output=./src --templateSrc=./scripts/exportTemplate.mjs --renderUniqueKey --withAliases --aliasesFileExtension=.ts --iconFileExtension=.ts --exportFileName=index.ts",
"build:bundles": "rollup -c ./rollup.config.mjs",
"test": "pnpm build:icons && vitest run",
"test:watch": "vitest watch",
"version": "pnpm version --git-tag-version=false"
}
}
@@ -0,0 +1,206 @@
import { MotionGlobalConfig } from 'motion-utils';
import { time } from '../frameloop/sync-time.mjs';
import { JSAnimation } from './JSAnimation.mjs';
import { getFinalKeyframe } from './keyframes/get-final.mjs';
import { KeyframeResolver, flushKeyframeResolvers } from './keyframes/KeyframesResolver.mjs';
import { NativeAnimationExtended } from './NativeAnimationExtended.mjs';
import { canAnimate } from './utils/can-animate.mjs';
import { makeAnimationInstant } from './utils/make-animation-instant.mjs';
import { resolveStartTime } from './utils/resolve-start-time.mjs';
import { WithPromise } from './utils/WithPromise.mjs';
import { supportsBrowserAnimation } from './waapi/supports/waapi.mjs';
import { canGroupTransform, TransformAnimation } from './waapi/transforms/TransformAnimation.mjs';
class AsyncMotionValueAnimation extends WithPromise {
constructor(options) {
super();
/**
* Bound to support return animation.stop pattern
*/
this.stop = () => {
if (this._animation) {
this._animation.stop();
this.stopTimeline?.();
}
this.keyframeResolver?.cancel();
};
this.createdAt = time.now();
const { keyframes, name, motionValue, element } = options;
/**
* animateMotionValue builds a fresh options object per value, so
* it's completed in place as keyframes resolve rather than copied.
*/
const optionsWithDefaults = options;
optionsWithDefaults.autoplay ?? (optionsWithDefaults.autoplay = true);
optionsWithDefaults.delay ?? (optionsWithDefaults.delay = 0);
optionsWithDefaults.type ?? (optionsWithDefaults.type = "keyframes");
optionsWithDefaults.repeat ?? (optionsWithDefaults.repeat = 0);
optionsWithDefaults.repeatDelay ?? (optionsWithDefaults.repeatDelay = 0);
optionsWithDefaults.repeatType ?? (optionsWithDefaults.repeatType = "loop");
const KeyframeResolver$1 = element?.KeyframeResolver || KeyframeResolver;
this.keyframeResolver = new KeyframeResolver$1(keyframes, (resolvedKeyframes, finalKeyframe, forced) => this.onKeyframesResolved(resolvedKeyframes, finalKeyframe, optionsWithDefaults, !forced), name, motionValue, element);
this.keyframeResolver?.scheduleResolve();
}
onKeyframesResolved(keyframes, finalKeyframe, options, sync) {
this.keyframeResolver = undefined;
const { name, type, velocity, delay, isHandoff, onUpdate } = options;
this.resolvedAt = time.now();
/**
* If we can't animate this value with the resolved keyframes
* then we should complete it immediately.
*/
let canAnimateValue = true;
if (!canAnimate(keyframes, name, type, velocity)) {
canAnimateValue = false;
if (MotionGlobalConfig.instantAnimations || !delay) {
onUpdate?.(getFinalKeyframe(keyframes, options, finalKeyframe));
}
keyframes[0] = keyframes[keyframes.length - 1];
makeAnimationInstant(options);
options.repeat = 0;
}
const { onComplete } = options;
options.finalKeyframe = finalKeyframe;
options.keyframes = keyframes;
/**
* JSAnimation and NativeAnimation call onComplete exactly when
* their own `finished` resolves, so this replaces a promise chain
* per value with a callback.
*/
options.onComplete = () => {
onComplete?.();
this.notifyFinished();
};
/**
* Animate via WAAPI if possible. If this is a handoff animation, the optimised animation will be running via
* WAAPI. Therefore, this animation must be JS to ensure it runs "under" the
* optimised animation.
*
* Also skip WAAPI when keyframes aren't animatable, as the resolved
* values may not be valid CSS and would trigger browser warnings.
*/
const useWaapi = canAnimateValue && !isHandoff && supportsBrowserAnimation(options);
/**
* Independent transforms (x, scale etc) on an HTML element are
* composed into one WAAPI transform animation per element.
*/
const isGroupedTransform = !useWaapi && canGroupTransform(options);
/**
* Resolve startTime for the animation. A startTime passed in options
* (an optimised appear handoff syncing to its WAAPI animation) takes
* precedence.
*
* *Ideally*, we would use the createdAt time as t=0 as the following
* frame would then be the first frame of the animation in progress,
* which would feel snappier. If keyframes resolved on a later frame,
* and long after creation, we start from then instead.
*
* If they resolved immediately, a JSAnimation picks its own start
* time and resolves it the same way on its first frame, so every
* animation started in that moment stays in sync. WAAPI has no frame
* of ours to do that on.
*/
if (sync &&
(useWaapi ||
isGroupedTransform ||
this.resolvedAt !== this.createdAt)) {
options.startTime ?? (options.startTime = resolveStartTime(this.createdAt, this.resolvedAt));
}
let animation;
if (useWaapi) {
/**
* The resolver needed the VisualElement, WAAPI needs the DOM
* element. JSAnimation reads neither, so this is safe to
* leave in place if we fall back to it.
*/
options.element = options.motionValue?.owner?.current;
try {
animation = new NativeAnimationExtended(options);
}
catch {
animation = new JSAnimation(options);
}
}
else if (isGroupedTransform) {
animation = new TransformAnimation(options);
}
else {
animation = new JSAnimation(options);
}
if (this.pendingTimeline) {
this.stopTimeline = animation.attachTimeline(this.pendingTimeline);
this.pendingTimeline = undefined;
}
this._animation = animation;
}
get finished() {
return this._animation ? this._animation.finished : super.finished;
}
then(onResolve, _onReject) {
return this.finished.finally(onResolve).then(() => { });
}
/**
* The value now, if the animation runs on the compositor.
*/
liveValue() {
return this._animation?.liveValue?.();
}
get animation() {
if (!this._animation) {
this.keyframeResolver?.resume();
flushKeyframeResolvers();
}
return this._animation;
}
get duration() {
return this.animation.duration;
}
get iterationDuration() {
return this.animation.iterationDuration;
}
get time() {
return this.animation.time;
}
set time(newTime) {
this.animation.time = newTime;
}
get speed() {
return this.animation.speed;
}
get state() {
return this.animation.state;
}
set speed(newSpeed) {
this.animation.speed = newSpeed;
}
get startTime() {
return this.animation.startTime;
}
attachTimeline(timeline) {
if (this._animation) {
this.stopTimeline = this.animation.attachTimeline(timeline);
}
else {
this.pendingTimeline = timeline;
}
return () => this.stop();
}
play() {
this.animation.play();
}
pause() {
this.animation.pause();
}
complete() {
this.animation.complete();
}
cancel() {
if (this._animation) {
this.animation.cancel();
}
this.keyframeResolver?.cancel();
}
}
export { AsyncMotionValueAnimation };
//# sourceMappingURL=AsyncMotionValueAnimation.mjs.map
@@ -0,0 +1,154 @@
import { time } from '../frameloop/sync-time.mjs';
import { calcGeneratorVelocity } from './generators/utils/velocity.mjs';
import { resolveStartTime } from './utils/resolve-start-time.mjs';
import { WithPromise } from './utils/WithPromise.mjs';
import { frame, cancelFrame } from '../frameloop/frame.mjs';
/**
* Every running follower ticks from this one frame callback.
*/
const active = new Set();
const tickAll = ({ timestamp }) => {
active.forEach((animation) => animation.tick(timestamp));
};
/**
* A lean animation for values that follow a target (`followValue`,
* `springValue`): a single numeric generator, retargeted in place as
* the target moves and ticked from a frame callback shared by every
* follower. Only implements the controls `MotionValue.animation`
* exposes. Repeat options are ignored: a follower only ever heads for
* its latest target.
*/
class FollowAnimation extends WithPromise {
constructor(options) {
super();
this.state = "idle";
this.startTime = 0;
this.currentTime = 0;
/**
* Whether the first tick has fired `onPlay`.
*/
this.started = false;
/**
* A target set since the last tick, applied at the next one so any
* number of sets per frame cost one retarget.
*/
this.hasNextTarget = false;
this.nextTarget = 0;
/**
* Bound to the instance so `animation.stop` can be passed around.
*/
this.stop = () => {
if (this.state === "idle")
return;
this.teardown();
this.options.onStop?.();
};
this.options = options;
this.factory = options.type;
this.generator = this.factory(options);
const { driver } = options;
if (driver)
this.driver = driver((timestamp) => this.tick(timestamp));
this.startTime = this.now();
this.state = "running";
if (this.driver) {
this.driver.start();
}
else {
active.size || frame.update(tickAll, true);
active.add(this);
}
}
/**
* Aim at a new target from wherever the animation has reached by the
* next tick. `velocity` fixes the velocity to steer with, otherwise
* the generator's is used.
*/
setTarget(target, velocity) {
this.nextTarget = target;
this.nextVelocity = velocity;
this.hasNextTarget = true;
}
/**
* Steer towards new keyframes from the current position and velocity.
* Springs are updated in place; other generators are recreated.
*/
retarget(keyframes, velocity) {
const { options, generator } = this;
options.keyframes = keyframes;
options.velocity = velocity;
this.startTime = this.now();
this.currentTime = 0;
if (generator.retarget) {
generator.retarget(keyframes, velocity);
}
else {
this.generator = this.factory(options);
}
}
tick(timestamp) {
const { options, hasNextTarget: retargeted } = this;
const { delay = 0, onUpdate, onPlay } = options;
// Retargets happen on a tick, so only the first start can lag
if (!this.started) {
this.startTime = resolveStartTime(this.startTime, timestamp);
}
const elapsed = Math.round(timestamp - this.startTime) - delay;
const t = (this.currentTime = Math.max(0, elapsed));
const state = this.generator.next(t);
// Hold the origin through the delay, even if the generator is already at rest
const value = elapsed < 0 ? options.keyframes[0] : state.value;
if (retargeted) {
/**
* Steer from where the current trajectory has reached this
* frame, then render that position as the new origin.
*/
this.hasNextTarget = false;
const { keyframes } = options;
keyframes[0] = value;
keyframes[1] = this.nextTarget;
this.retarget(keyframes, this.nextVelocity ?? this.getGeneratorVelocity());
}
if (retargeted || !this.started) {
this.started = true;
onPlay?.();
}
/**
* Callbacks may stop this animation or set a new target, so check
* the live state rather than what was read at the top of the tick.
*/
if (this.state !== "running")
return;
onUpdate?.(value);
if (state.done &&
elapsed >= 0 &&
!retargeted &&
!this.hasNextTarget &&
this.state === "running") {
this.notifyFinished();
this.teardown();
this.state = "finished";
options.onComplete?.();
}
}
getGeneratorVelocity() {
return calcGeneratorVelocity(this.generator, this.currentTime, this.options.velocity);
}
now() {
return this.driver ? this.driver.now() : time.now();
}
teardown() {
this.state = "idle";
if (this.driver) {
this.driver.stop();
}
else {
active.delete(this);
active.size || cancelFrame(tickAll);
}
}
}
export { FollowAnimation };
//# sourceMappingURL=FollowAnimation.mjs.map
@@ -0,0 +1,82 @@
class GroupAnimation {
constructor(animations) {
// Bound to accommodate common `return animation.stop` pattern
this.stop = () => this.runAll("stop");
this.animations = animations.filter(Boolean);
}
get finished() {
return Promise.all(this.animations.map((animation) => animation.finished));
}
/**
* TODO: Filter out cancelled or stopped animations before returning
*/
getAll(propName) {
return this.animations[0][propName];
}
setAll(propName, newValue) {
for (let i = 0; i < this.animations.length; i++) {
this.animations[i][propName] = newValue;
}
}
attachTimeline(timeline) {
const subscriptions = this.animations.map((animation) => animation.attachTimeline(timeline));
return () => {
subscriptions.forEach((cancel, i) => {
cancel && cancel();
this.animations[i].stop();
});
};
}
get time() {
return this.getAll("time");
}
set time(time) {
this.setAll("time", time);
}
get speed() {
return this.getAll("speed");
}
set speed(speed) {
this.setAll("speed", speed);
}
get state() {
return this.getAll("state");
}
get startTime() {
return this.getAll("startTime");
}
get duration() {
return getMax(this.animations, "duration");
}
get iterationDuration() {
return getMax(this.animations, "iterationDuration");
}
runAll(methodName) {
this.animations.forEach((controls) => controls[methodName]());
}
play() {
this.runAll("play");
}
pause() {
this.runAll("pause");
}
cancel() {
this.runAll("cancel");
}
complete() {
this.runAll("complete");
}
}
function getMax(animations, propName) {
let max = 0;
for (let i = 0; i < animations.length; i++) {
const value = animations[i][propName];
if (value !== null && value > max) {
max = value;
}
}
return max;
}
export { GroupAnimation };
//# sourceMappingURL=GroupAnimation.mjs.map
@@ -0,0 +1,10 @@
import { GroupAnimation } from './GroupAnimation.mjs';
class GroupAnimationWithThen extends GroupAnimation {
then(onResolve, _onReject) {
return this.finished.finally(onResolve).then(() => { });
}
}
export { GroupAnimationWithThen };
//# sourceMappingURL=GroupAnimationWithThen.mjs.map
@@ -0,0 +1,418 @@
import { invariant, pipe, clamp, millisecondsToSeconds, secondsToMilliseconds } from 'motion-utils';
import { time } from '../frameloop/sync-time.mjs';
import { mix } from '../utils/mix/index.mjs';
import { frameloopDriver } from './drivers/frame.mjs';
import { inertia } from './generators/inertia.mjs';
import { keyframes } from './generators/keyframes.mjs';
import { calcGeneratorDuration } from './generators/utils/calc-duration.mjs';
import { calcGeneratorVelocity } from './generators/utils/velocity.mjs';
import { getFinalKeyframe } from './keyframes/get-final.mjs';
import { replaceTransitionType } from './utils/replace-transition-type.mjs';
import { resolveStartTime } from './utils/resolve-start-time.mjs';
import { notifyAnimationStart } from './utils/notify-inspector.mjs';
import { WithPromise } from './utils/WithPromise.mjs';
const percentToProgress = (percent) => percent / 100;
const round = (value) => Math.round(value * 10000) / 10000;
class JSAnimation extends WithPromise {
constructor(options) {
super();
this.state = "idle";
this.startTime = null;
this.isStopped = false;
/**
* The current time of the animation.
*/
this.currentTime = 0;
/**
* The time at which the animation was paused.
*/
this.holdTime = null;
/**
* Playback speed as a factor. 0 would be stopped, -1 reverse and 2 double speed.
*/
this.playbackSpeed = 1;
/**
* Reusable state object for the delay phase to avoid
* allocating a new object every frame.
*/
this.delayState = {
done: false,
value: undefined,
};
/**
* This method is bound to the instance to fix a pattern where
* animation.stop is returned as a reference from a useEffect.
*/
this.stop = () => {
const { motionValue } = this.options;
if (motionValue && motionValue.updatedAt !== time.now()) {
this.tick(time.now());
}
this.isStopped = true;
if (this.state === "idle")
return;
this.teardown();
this.options.onStop?.();
};
this.options = options;
this.initAnimation();
this.play();
if (options.autoplay === false)
this.pause();
notifyAnimationStart(this, this.options);
}
initAnimation() {
const { options } = this;
replaceTransitionType(options);
const { type = keyframes, repeat = 0, repeatDelay = 0, repeatType, velocity = 0, } = options;
let { keyframes: keyframes$1 } = options;
const generatorFactory = type || keyframes;
if (process.env.NODE_ENV !== "production" &&
generatorFactory !== keyframes) {
invariant(keyframes$1.length <= 2, `Only two keyframes currently supported with spring and inertia animations. Trying to animate ${keyframes$1}`, "spring-two-frames");
}
if (generatorFactory !== keyframes &&
typeof keyframes$1[0] !== "number") {
this.mixKeyframes = pipe(percentToProgress, mix(keyframes$1[0], keyframes$1[1]));
keyframes$1 = [0, 100];
}
const generator = generatorFactory(keyframes$1 === options.keyframes
? options
: { ...options, keyframes: keyframes$1 });
/**
* If we have a mirror repeat type we need to create a second generator that outputs the
* mirrored (not reversed) animation and later ping pong between the two generators.
*/
if (repeatType === "mirror") {
this.mirroredGenerator = generatorFactory({
...options,
keyframes: [...keyframes$1].reverse(),
velocity: -velocity,
});
}
/**
* If duration is undefined and we have repeat options,
* we need to calculate a duration from the generator.
*
* We set it to the generator itself to cache the duration.
* Any timeline resolver will need to have already precalculated
* the duration by this step.
*/
if (generator.calculatedDuration === null) {
generator.calculatedDuration = calcGeneratorDuration(generator);
}
const { calculatedDuration } = generator;
this.calculatedDuration = calculatedDuration;
this.resolvedDuration = calculatedDuration + repeatDelay;
this.totalDuration = this.resolvedDuration * (repeat + 1) - repeatDelay;
this.generator = generator;
}
updateTime(timestamp) {
const animationTime = Math.round(timestamp - this.startTime) * this.playbackSpeed;
// Update currentTime
if (this.holdTime !== null) {
this.currentTime = this.holdTime;
}
else {
// Rounding the time because floating point arithmetic is not always accurate, e.g. 3000.367 - 1000.367 =
// 2000.0000000000002. This is a problem when we are comparing the currentTime with the duration, for
// example.
this.currentTime = animationTime;
}
}
tick(timestamp, sample = false) {
const { generator, totalDuration, calculatedDuration } = this;
if (this.startTime === null)
return generator.next(0);
const { delay = 0, keyframes, type, onUpdate, finalKeyframe, } = this.options;
if (this.startTime === this.pendingStartTime) {
this.startTime = resolveStartTime(this.startTime, timestamp);
}
this.pendingStartTime = undefined;
/**
* requestAnimationFrame timestamps can come through as lower than
* the startTime as set by performance.now(). Here we prevent this,
* though in the future it could be possible to make setting startTime
* a pending operation that gets resolved here.
*/
if (this.speed > 0) {
this.startTime = Math.min(this.startTime, timestamp);
}
else if (this.speed < 0) {
this.startTime = Math.min(timestamp - totalDuration / this.speed, this.startTime);
}
if (sample) {
this.currentTime = timestamp;
}
else {
this.updateTime(timestamp);
}
// Rebase on delay
const timeWithoutDelay = this.currentTime - delay * (this.playbackSpeed >= 0 ? 1 : -1);
const isInDelayPhase = this.playbackSpeed >= 0
? timeWithoutDelay < 0
: timeWithoutDelay > totalDuration;
this.currentTime = Math.max(timeWithoutDelay, 0);
// If this animation has finished, set the current time to the total duration.
if (this.state === "finished" && this.holdTime === null) {
this.currentTime = totalDuration;
}
const state = this.generate(this.currentTime, isInDelayPhase);
let { done } = state;
if (!isInDelayPhase && calculatedDuration !== null) {
done =
this.playbackSpeed >= 0
? this.currentTime >= totalDuration
: this.currentTime <= 0;
}
const isAnimationFinished = this.holdTime === null &&
(this.state === "finished" || (this.state === "running" && done));
// TODO: The exception for inertia could be cleaner here
if (isAnimationFinished && type !== inertia) {
state.value = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
}
if (onUpdate) {
onUpdate(state.value);
}
if (isAnimationFinished) {
this.finish();
}
return state;
}
/**
* This animation's time, including delay, at a timestamp.
*/
at(timestamp) {
return (this.holdTime ??
round((timestamp - this.startTime) * this.playbackSpeed));
}
/**
* The value at a timestamp, without changing this animation's state.
* If resolved, it ends at the last resolved keyframe, which is in the
* same unit as the others, rather than at finalKeyframe.
*/
sampleAt(timestamp, resolved) {
const { options, totalDuration } = this;
const t = this.at(timestamp) - (options.delay || 0);
return t >= totalDuration && options.type !== inertia
? getFinalKeyframe(options.keyframes, options, resolved ? undefined : options.finalKeyframe)
: this.generate(Math.max(t, 0), t < 0).value;
}
/**
* The generator's state `currentTime` ms after the delay, without
* changing the animation's own state.
*/
generate(currentTime, isInDelayPhase) {
const { generator, totalDuration, mixKeyframes, mirroredGenerator, resolvedDuration, } = this;
const { keyframes, repeat, repeatType, repeatDelay } = this.options;
let elapsed = currentTime;
let frameGenerator = generator;
if (repeat) {
/**
* Get the current progress (0-1) of the animation. If t is >
* than duration we'll get values like 2.5 (midway through the
* third iteration)
*/
const progress = Math.min(currentTime, totalDuration) / resolvedDuration;
/**
* Get the current iteration (0 indexed). For instance the floor of
* 2.5 is 2.
*/
let currentIteration = Math.floor(progress);
/**
* Get the current progress of the iteration by taking the remainder
* so 2.5 is 0.5 through iteration 2
*/
let iterationProgress = progress % 1.0;
/**
* If iteration progress is 1 we count that as the end
* of the previous iteration.
*/
if (!iterationProgress && progress >= 1) {
iterationProgress = 1;
}
iterationProgress === 1 && currentIteration--;
currentIteration = Math.min(currentIteration, repeat + 1);
/**
* Reverse progress if we're not running in "normal" direction
*/
const isOddIteration = Boolean(currentIteration % 2);
if (isOddIteration) {
if (repeatType === "reverse") {
iterationProgress = 1 - iterationProgress;
if (repeatDelay) {
iterationProgress -= repeatDelay / resolvedDuration;
}
}
else if (repeatType === "mirror") {
frameGenerator = mirroredGenerator;
}
}
elapsed = clamp(0, 1, iterationProgress) * resolvedDuration;
}
/**
* If we're in negative time, set state as the initial keyframe.
* This prevents delay: x, duration: 0 animations from finishing
* instantly.
*/
let state;
if (isInDelayPhase) {
this.delayState.value = keyframes[0];
state = this.delayState;
}
else {
state = frameGenerator.next(elapsed);
}
if (mixKeyframes && !isInDelayPhase) {
state.value = mixKeyframes(state.value);
}
return state;
}
/**
* Allows the returned animation to be awaited or promise-chained. Currently
* resolves when the animation finishes at all but in a future update could/should
* reject if its cancels.
*/
then(resolve, reject) {
return this.finished.then(resolve, reject);
}
get duration() {
return millisecondsToSeconds(this.calculatedDuration);
}
get iterationDuration() {
const { delay = 0 } = this.options || {};
return this.duration + millisecondsToSeconds(delay);
}
get time() {
return millisecondsToSeconds(this.currentTime);
}
set time(newTime) {
newTime = secondsToMilliseconds(newTime);
this.currentTime = newTime;
if (this.startTime === null ||
this.holdTime !== null ||
this.playbackSpeed === 0) {
this.holdTime = newTime;
}
else if (this.driver) {
this.startTime = this.driver.now() - newTime / this.playbackSpeed;
}
if (this.driver) {
this.driver.start(false);
}
else {
this.startTime = 0;
this.state = "paused";
this.holdTime = newTime;
this.tick(newTime);
}
}
/**
* Returns the generator's velocity at the current time in units/second.
* Uses the analytical derivative when available (springs), avoiding
* the MotionValue's frame-dependent velocity estimation.
*/
getGeneratorVelocity() {
return calcGeneratorVelocity(this.generator, this.currentTime, this.options.velocity);
}
get speed() {
return this.playbackSpeed;
}
set speed(newSpeed) {
const hasChanged = this.playbackSpeed !== newSpeed;
if (hasChanged && this.driver) {
this.updateTime(time.now());
}
this.playbackSpeed = newSpeed;
if (hasChanged && this.driver) {
this.time = millisecondsToSeconds(this.currentTime);
}
}
play() {
if (this.isStopped)
return;
const { driver = frameloopDriver, startTime } = this.options;
if (!this.driver) {
this.driver = driver((timestamp) => this.tick(timestamp));
}
this.options.onPlay?.();
const now = this.driver.now();
if (this.state === "finished") {
this.updateFinished();
this.startTime = now;
}
else if (this.holdTime !== null) {
this.startTime = now - this.holdTime / (this.playbackSpeed || 1);
}
else if (!this.startTime) {
this.startTime = startTime ?? (this.pendingStartTime = now);
}
if (this.state === "finished" && this.speed < 0) {
this.startTime += this.calculatedDuration;
}
this.holdTime = null;
/**
* Set playState to running only after we've used it in
* the previous logic.
*/
this.state = "running";
this.driver.start();
}
pause() {
this.state = "paused";
this.updateTime(time.now());
this.holdTime = this.currentTime;
}
complete() {
if (this.state !== "running") {
this.play();
}
this.state = "finished";
this.holdTime = null;
}
finish() {
this.notifyFinished();
this.teardown();
this.state = "finished";
this.options.onComplete?.();
}
cancel() {
this.holdTime = null;
this.startTime = 0;
this.tick(0);
this.teardown();
this.options.onCancel?.();
}
teardown() {
this.state = "idle";
this.stopDriver();
this.startTime = this.holdTime = null;
}
stopDriver() {
if (!this.driver)
return;
this.driver.stop();
this.driver = undefined;
}
sample(sampleTime) {
this.startTime = 0;
return this.tick(sampleTime, true);
}
attachTimeline(timeline) {
if (this.options.allowFlatten) {
this.options.type = "keyframes";
this.options.ease = "linear";
this.initAnimation();
}
this.driver?.stop();
return timeline.observe(this);
}
}
// Legacy function support
function animateValue(options) {
return new JSAnimation(options);
}
export { JSAnimation, animateValue, round };
//# sourceMappingURL=JSAnimation.mjs.map
@@ -0,0 +1,178 @@
import { invariant, millisecondsToSeconds, secondsToMilliseconds, noop } from 'motion-utils';
import { setStyle } from '../render/dom/style-set.mjs';
import { supportsScrollTimeline } from '../utils/supports/scroll-timeline.mjs';
import { getFinalKeyframe } from './keyframes/get-final.mjs';
import { WithPromise } from './utils/WithPromise.mjs';
import { notifyAnimationStart } from './utils/notify-inspector.mjs';
import { startWaapiAnimation } from './waapi/start-waapi-animation.mjs';
import { applyGeneratorOptions } from './waapi/utils/apply-generator.mjs';
/**
* NativeAnimation implements AnimationPlaybackControls for the browser's Web Animations API.
*/
class NativeAnimation extends WithPromise {
constructor(options) {
super();
this.finishedTime = null;
this.isStopped = false;
/**
* Tracks a manually-set start time that takes precedence over WAAPI's
* dynamic startTime. This is cleared when play() or time setter is called,
* allowing WAAPI to take over timing.
*/
this.manualStartTime = null;
if (!options)
return;
const { element, name, keyframes, pseudoElement, allowFlatten = false, finalKeyframe, onComplete, } = options;
this.isPseudoElement = Boolean(pseudoElement);
this.allowFlatten = allowFlatten;
this.options = options;
invariant(typeof options.type !== "string", `Mini animate() doesn't support "type" as a string.`, "mini-spring");
const transition = applyGeneratorOptions(options);
this.animation = startWaapiAnimation(element, name, keyframes, transition, pseudoElement);
if (transition.autoplay === false) {
this.animation.pause();
}
this.animation.onfinish = () => {
this.finishedTime = this.time;
if (!pseudoElement) {
const keyframe = getFinalKeyframe(keyframes, this.options, finalKeyframe, this.speed);
if (this.updateMotionValue) {
this.updateMotionValue(keyframe);
}
/**
* If we can, we want to commit the final style as set by the user,
* rather than the computed keyframe value supplied by the animation.
* We always do this, even when a motion value is present, to prevent
* a visual flash in Firefox where the WAAPI animation's fill is removed
* during cancel() before the scheduled render can apply the correct value.
*/
setStyle(element, name, keyframe);
this.animation.cancel();
}
onComplete?.();
this.notifyFinished();
};
notifyAnimationStart(this, options, transition);
}
play() {
if (this.isStopped)
return;
this.manualStartTime = null;
this.animation.play();
if (this.state === "finished") {
this.updateFinished();
}
}
pause() {
this.animation.pause();
}
complete() {
this.animation.finish?.();
}
cancel() {
try {
this.animation.cancel();
}
catch (e) { }
}
stop() {
if (this.isStopped)
return;
this.isStopped = true;
const { state } = this;
if (state === "idle" || state === "finished") {
return;
}
if (this.updateMotionValue) {
this.updateMotionValue();
}
else {
this.commitStyles();
}
if (!this.isPseudoElement)
this.cancel();
}
/**
* WAAPI doesn't natively have any interruption capabilities.
*
* In this method, we commit styles back to the DOM before cancelling
* the animation.
*
* This is designed to be overridden by NativeAnimationExtended, which
* will create a renderless JS animation and sample it twice to calculate
* its current value, "previous" value, and therefore allow
* Motion to also correctly calculate velocity for any subsequent animation
* while deferring the commit until the next animation frame.
*/
commitStyles() {
const element = this.options?.element;
if (!this.isPseudoElement && element?.isConnected) {
this.animation.commitStyles?.();
}
}
get duration() {
const duration = this.animation.effect?.getComputedTiming?.().duration || 0;
return millisecondsToSeconds(Number(duration));
}
get iterationDuration() {
const { delay = 0 } = this.options || {};
return this.duration + millisecondsToSeconds(delay);
}
get time() {
return millisecondsToSeconds(Number(this.animation.currentTime) || 0);
}
set time(newTime) {
const wasFinished = this.finishedTime !== null;
this.manualStartTime = null;
this.finishedTime = null;
this.animation.currentTime = secondsToMilliseconds(newTime);
if (wasFinished) {
this.animation.pause();
}
}
/**
* The playback speed of the animation.
* 1 = normal speed, 2 = double speed, 0.5 = half speed.
*/
get speed() {
return this.animation.playbackRate;
}
set speed(newSpeed) {
// Allow backwards playback after finishing
if (newSpeed < 0)
this.finishedTime = null;
this.animation.playbackRate = newSpeed;
}
get state() {
return this.finishedTime !== null
? "finished"
: this.animation.playState;
}
get startTime() {
return this.manualStartTime ?? Number(this.animation.startTime);
}
set startTime(newStartTime) {
this.manualStartTime = this.animation.startTime = newStartTime;
}
/**
* Attaches a timeline to the animation, for instance the `ScrollTimeline`.
*/
attachTimeline({ timeline, onAttach, observe, }) {
if (this.allowFlatten) {
this.animation.effect?.updateTiming({ easing: "linear" });
}
this.animation.onfinish = null;
if (timeline && supportsScrollTimeline()) {
this.animation.timeline = timeline;
onAttach?.(this.animation);
return noop;
}
else {
return observe(this);
}
}
}
export { NativeAnimation };
//# sourceMappingURL=NativeAnimation.mjs.map
@@ -0,0 +1,115 @@
import { clamp } from 'motion-utils';
import { time } from '../frameloop/sync-time.mjs';
import { setStyle } from '../render/dom/style-set.mjs';
import { JSAnimation } from './JSAnimation.mjs';
import { NativeAnimation } from './NativeAnimation.mjs';
import { replaceTransitionType } from './utils/replace-transition-type.mjs';
import { replaceStringEasing } from './waapi/utils/unsupported-easing.mjs';
/**
* 10ms is chosen here as it strikes a balance between smooth
* results (more than one keyframe per frame at 60fps) and
* keyframe quantity.
*/
const sampleDelta = 10; //ms
class NativeAnimationExtended extends NativeAnimation {
constructor(options) {
/**
* The base NativeAnimation function only supports a subset
* of Motion easings, and WAAPI also only supports some
* easing functions via string/cubic-bezier definitions.
*
* This function replaces those unsupported easing functions
* with a JS easing function. This will later get compiled
* to a linear() easing function.
*/
replaceStringEasing(options);
/**
* Ensure we replace the transition type with a generator function
* before passing to WAAPI.
*
* TODO: Does this have a better home? It could be shared with
* JSAnimation.
*/
replaceTransitionType(options);
super(options);
/**
* Only set startTime when the animation should autoplay.
* Setting startTime on a paused WAAPI animation unpauses it
* (per the WAAPI spec), which breaks autoplay: false.
*/
if (options.startTime !== undefined && options.autoplay !== false) {
this.startTime = options.startTime;
}
this.options = options;
}
/**
* The animation's time now, in ms. Wall-clock rather than WAAPI's
* currentTime while running, as under CPU load currentTime may not
* reflect the actual elapsed time, causing incorrect sampling and
* visual jumps.
*/
elapsed() {
return this.state === "running"
? (time.now() - this.startTime) * this.speed
: this.time * 1000;
}
/**
* The value at a time, from a renderless JS animation.
*/
sample(sampleTime) {
if (!this.sampler) {
const { motionValue, onUpdate, onComplete, onPlay, onStop, element, ...options } = this.options;
/**
* Stop it straight away so its driver doesn't run every frame.
* It can still be sampled.
*/
this.sampler = new JSAnimation({ ...options, autoplay: false });
this.sampler.stop();
}
return this.sampler.sample(Math.max(0, sampleTime)).value;
}
/**
* The value now, as WAAPI doesn't set the motion value while it runs.
*/
liveValue() {
const { state } = this;
if (state === "running" || state === "paused") {
return this.sample(this.elapsed());
}
}
/**
* WAAPI doesn't natively have any interruption capabilities.
*
* Rather than read committed styles back out of the DOM, we can
* sample a renderless JS animation twice to calculate its current
* value, "previous" value, and therefore allow Motion to calculate
* velocity for any subsequent animation.
*/
updateMotionValue(value) {
const { motionValue, element, name } = this.options;
if (!motionValue)
return;
if (value !== undefined) {
motionValue.set(value);
return;
}
const { speed } = this;
const sampleTime = this.elapsed();
const delta = this.state === "running" && speed
? clamp(0, sampleDelta, sampleTime)
: 0;
const current = this.sample(sampleTime);
/**
* Write the estimated value to inline style so it persists
* after cancel(), covering the async gap before the next
* animation starts.
*/
if (element && name)
setStyle(element, name, current);
motionValue.setWithVelocity(this.sample(sampleTime - delta * Math.sign(speed)), current, delta / Math.abs(speed || 1));
}
}
export { NativeAnimationExtended, sampleDelta };
//# sourceMappingURL=NativeAnimationExtended.mjs.map
@@ -0,0 +1,22 @@
import { NativeAnimation } from './NativeAnimation.mjs';
import { notifyAnimationStart } from './utils/notify-inspector.mjs';
class NativeAnimationWrapper extends NativeAnimation {
constructor(animation) {
super();
this.animation = animation;
animation.onfinish = () => {
this.finishedTime = this.time;
this.notifyFinished();
};
/**
* The optionless super() returns before NativeAnimation's own
* notify, so wrapped animations (view transition cross-fades)
* report here, after the wrapped WAAPI animation is attached.
*/
notifyAnimationStart(this, {});
}
}
export { NativeAnimationWrapper };
//# sourceMappingURL=NativeAnimationWrapper.mjs.map
@@ -0,0 +1,113 @@
import { invariant, removeItem } from 'motion-utils';
import { positionalKeys } from '../../render/utils/keys-position.mjs';
import { motionValue } from '../../value/index.mjs';
import { animateMotionValue } from '../interfaces/motion-value.mjs';
import { frame } from '../../frameloop/frame.mjs';
/**
* Effects registered via `animate.addEffect()`, most recent first.
*/
const effects = [];
function addEffect(effect) {
invariant(typeof effect.test === "function" && typeof effect.read === "function", "Effects passed to animate.addEffect() need test() and read().", "effect-missing-test");
removeEffect(effect);
effects.unshift(effect);
}
function removeEffect(effect) {
removeItem(effects, effect);
}
function findEffect(subject) {
return effects.find((effect) => effect.test(subject));
}
/**
* Animate each key in `keyframes` on the motion value `getValue` returns
* for it. `element` is what the keyframe resolver uses to read, render
* and measure the subject when the effect defers resolution to it.
*/
function animateValues(getValue, keyframes, transition = {}, element) {
const animations = [];
const { velocity, path } = transition;
const reduceMotion = transition.reduceMotion ?? element?.shouldReduceMotion;
/**
* Only elements have x/y to move along a path. path claims them by
* deleting them, so hand it a copy.
*/
if (path?.animateValues && element) {
keyframes = { ...keyframes };
path.animateValues(getValue, keyframes, transition, 0, animations);
}
for (const key in keyframes) {
if (key === "transition" || key === "transitionEnd")
continue;
const target = keyframes[key];
if (target === undefined)
continue;
const value = getValue(key);
/**
* If the value is already at the defined target, skip the animation.
* We still re-assert the value via frame.update to take precedence
* over any stale transitionEnd callbacks from previous animations.
*/
const current = value.get();
if (current !== undefined &&
!value.isAnimating() &&
!Array.isArray(target) &&
target === current &&
!velocity) {
frame.update(() => value.set(target));
continue;
}
value.start(animateMotionValue(key, value, target, reduceMotion && positionalKeys.has(key)
? { type: false }
: transition, element));
value.animation &&
animations.push(value.animation);
}
const { transitionEnd } = keyframes;
if (transitionEnd) {
const applyTransitionEnd = () => frame.update(() => {
for (const key in transitionEnd) {
getValue(key).set(transitionEnd[key]);
}
});
animations.length
? Promise.all(animations).then(applyTransitionEnd)
: applyTransitionEnd();
}
return animations;
}
/**
* Animate the keys of `subject` via `effect`. Motion values are created on
* first animation and bound to the subject through the effect for the
* rest of its life.
*
* Without `element`, a new value is seeded synchronously from the first
* keyframe or `effect.read()`. With it, the value starts undefined and the
* keyframe resolver reads the subject (batched, with unit conversion by
* measurement) via `element`.
*/
function animateEffectSubject(effect, subject, keyframes, transition, element) {
return animateValues((key) => {
let value = effect.get(subject, key);
if (!value) {
let initial;
if (!element) {
const target = keyframes[key];
initial =
firstKeyframe(target) ??
effect.read(subject, key, target);
invariant(initial !== undefined, `"${key}" can't be read from the animated subject. Provide [from, to] keyframes.`, "effect-unreadable-value");
}
value = motionValue(initial, { owner: element });
effect(subject, { [key]: value });
}
return value;
}, keyframes, transition, element);
}
function firstKeyframe(target) {
const first = Array.isArray(target) ? target[0] : undefined;
return first === null ? undefined : first;
}
export { addEffect, animateEffectSubject, animateValues, findEffect, removeEffect };
//# sourceMappingURL=effects.mjs.map
@@ -0,0 +1,80 @@
import { styleSubjectEffect } from '../../effects/style/index.mjs';
import { svgSubjectEffect } from '../../effects/svg/index.mjs';
import { measureViewportBox } from '../../projection/utils/measure.mjs';
import { isSVGElement } from '../../utils/is-svg-element.mjs';
import { DOMKeyframesResolver } from '../keyframes/DOMKeyframesResolver.mjs';
import { animateValues, animateEffectSubject } from './effects.mjs';
const noProps = {};
const getElementEffect = (element) => (isSVGElement(element)
? svgSubjectEffect
: styleSubjectEffect);
/**
* Presents an effect bound to a DOM element in the shape the DOM keyframe
* resolver and WAAPI expect, which is that of a VisualElement. Holds no
* state: everything delegates to the effect. Goes once the resolver takes
* an effect and subject directly.
*/
class EffectSubject {
constructor(effect, current) {
this.effect = effect;
this.current = current;
this.KeyframeResolver = DOMKeyframesResolver;
}
getValue(key) {
return this.effect.get(this.current, key);
}
readValue(key, target) {
return this.effect.read(this.current, key, target);
}
render() {
this.effect.flush(this.current);
}
measureViewportBox() {
return measureViewportBox(this.current);
}
getProps() {
return noProps;
}
}
/**
* Animate a DOM element's styles. HTML elements are bound through
* styleEffect, SVG through svgEffect, and both are resolved with the DOM
* keyframe resolver so reads are batched, units are converted by
* measurement and eligible values run on WAAPI.
*
* Pass the element's VisualElement (a <motion.*> component or an
* animateLayout() node) to animate its values instead, so the two keep
* sharing values and a renderer.
*/
function animateElement(element, keyframes, transition, visualElement) {
if (visualElement) {
return animateValues((key) => visualElement.getValue(key, null), keyframes, transition, visualElement);
}
const effect = getElementEffect(element);
return animateEffectSubject(effect, element, keyframes, transition, new EffectSubject(effect, element));
}
/**
* Hand the values styleEffect/svgEffect are rendering on `element`,
* whether bound by animate() or directly, to a VisualElement that now
* owns the element (e.g. created by animateLayout()), so a single
* renderer drives them alongside its own values.
*/
function handOffElementState(element, visualElement) {
const state = getElementEffect(element).state(element);
if (!state)
return;
const { transformKeys } = state;
state.release().forEach((value, key) => {
/**
* The style effect derives transform (from the bound transform
* keys) and transformBox itself; a VisualElement builds its own.
*/
const derived = key === "transformBox" ||
(key === "transform" && transformKeys?.length);
derived || visualElement.addValue(key, value);
});
}
export { animateElement, handOffElementState };
//# sourceMappingURL=element.mjs.map
@@ -0,0 +1,12 @@
import { animateMotionValue } from '../interfaces/motion-value.mjs';
import { motionValue } from '../../value/index.mjs';
import { isMotionValue } from '../../value/utils/is-motion-value.mjs';
function animateSingleValue(value, keyframes, options) {
const motionValue$1 = isMotionValue(value) ? value : motionValue(value);
motionValue$1.start(animateMotionValue("", motionValue$1, keyframes, options));
return motionValue$1.animation;
}
export { animateSingleValue };
//# sourceMappingURL=single-value.mjs.map
@@ -0,0 +1,19 @@
import { time } from '../../frameloop/sync-time.mjs';
import { frameData } from '../../frameloop/frame-data.mjs';
import { cancelFrame, frame } from '../../frameloop/frame.mjs';
const frameloopDriver = (update) => {
const passTimestamp = ({ timestamp }) => update(timestamp);
return {
start: (keepAlive = true) => frame.update(passTimestamp, keepAlive),
stop: () => cancelFrame(passTimestamp),
/**
* If we're processing this frame we can use the
* framelocked timestamp to keep things in sync.
*/
now: () => (frameData.isProcessing ? frameData.timestamp : time.now()),
};
};
export { frameloopDriver };
//# sourceMappingURL=frame.mjs.map
@@ -0,0 +1,90 @@
import { spring } from './spring.mjs';
function inertia({ keyframes, velocity = 0.0, power = 0.8, timeConstant = 325, bounceDamping = 10, bounceStiffness = 500, modifyTarget, min, max, restDelta = 0.5, restSpeed, }) {
const origin = keyframes[0];
const state = {
done: false,
value: origin,
};
const isOutOfBounds = (v) => v < min || v > max;
const nearestBoundary = (v) => {
if (min === undefined)
return max;
if (max === undefined)
return min;
return Math.abs(min - v) < Math.abs(max - v) ? min : max;
};
let amplitude = power * velocity;
const ideal = origin + amplitude;
const target = modifyTarget === undefined ? ideal : modifyTarget(ideal);
/**
* If the target has changed we need to re-calculate the amplitude, otherwise
* the animation will start from the wrong position.
*/
if (target !== ideal)
amplitude = target - origin;
const calcDelta = (t) => -amplitude * Math.exp(-t / timeConstant);
const applyFriction = (t) => {
const delta = calcDelta(t);
state.done = Math.abs(delta) <= restDelta;
state.value = state.done ? target : target + delta;
};
/**
* Ideally this would resolve for t in a stateless way, we could
* do that by always precalculating the animation but as we know
* this will be done anyway we can assume that spring will
* be discovered during that.
*/
let timeReachedBoundary;
let spring$1;
const checkCatchBoundary = (t) => {
if (!isOutOfBounds(state.value))
return;
timeReachedBoundary = t;
spring$1 = spring({
keyframes: [state.value, nearestBoundary(state.value)],
/**
* The friction curve is target + calcDelta(t), so its exact
* derivative is -calcDelta(t) / timeConstant in units/ms,
* converted here to the units/second expected by spring.
*/
velocity: (-calcDelta(t) / timeConstant) * 1000,
damping: bounceDamping,
stiffness: bounceStiffness,
restDelta,
restSpeed,
});
};
checkCatchBoundary(0);
return {
calculatedDuration: null,
next: (t) => {
/**
* We need to resolve the friction to figure out if we need a
* spring but we don't want to do this twice per frame. So here
* we flag if we updated for this frame and later if we did
* we can skip doing it again.
*/
let hasUpdatedFrame = false;
if (!spring$1 && timeReachedBoundary === undefined) {
hasUpdatedFrame = true;
applyFriction(t);
checkCatchBoundary(t);
}
/**
* If we have a spring and the provided t is beyond the moment the friction
* animation crossed the min/max boundary, use the spring.
*/
if (timeReachedBoundary !== undefined && t >= timeReachedBoundary) {
return spring$1.next(t - timeReachedBoundary);
}
else {
!hasUpdatedFrame && applyFriction(t);
return state;
}
},
};
}
export { inertia };
//# sourceMappingURL=inertia.mjs.map
@@ -0,0 +1,74 @@
import { easeInOut, isEasingArray, easingDefinitionToFunction, MotionGlobalConfig, clamp } from 'motion-utils';
import { interpolate } from '../../utils/interpolate.mjs';
import { mix } from '../../utils/mix/index.mjs';
import { defaultOffset } from '../keyframes/offsets/default.mjs';
import { convertOffsetToTimes } from '../keyframes/offsets/time.mjs';
function defaultEasing(values, easing) {
return values.map(() => easing || easeInOut).splice(0, values.length - 1);
}
function keyframes({ duration = 300, keyframes: keyframeValues, times, ease = "easeInOut", }) {
/**
* Easing functions can be externally defined as strings. Here we convert them
* into actual functions.
*/
const easingFunctions = isEasingArray(ease)
? ease.map(easingDefinitionToFunction)
: easingDefinitionToFunction(ease) || easeInOut;
/**
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
* to reduce GC during animation.
*/
const state = {
done: false,
value: keyframeValues[0],
};
/**
* Fast setup path for two keyframes one easing.
*/
if (keyframeValues.length === 2 &&
!Array.isArray(easingFunctions) &&
(!times ||
times.length !== 2 ||
(times[0] === 0 && times[1] === 1))) {
const [origin, target] = keyframeValues;
const mixer = origin === target
? undefined
: (MotionGlobalConfig.mix || mix)(origin, target);
return {
calculatedDuration: duration,
next: (t) => {
state.value = mixer
? mixer(easingFunctions(duration > 0 ? clamp(0, 1, t / duration) : 1))
: target;
state.done = t >= duration;
return state;
},
};
}
/**
* Create a times array based on the provided 0-1 offsets
*/
const absoluteTimes = convertOffsetToTimes(
// Only use the provided offsets if they're the correct length
// TODO Maybe we should warn here if there's a length mismatch
times && times.length === keyframeValues.length
? times
: defaultOffset(keyframeValues), duration);
const mapTimeToKeyframe = interpolate(absoluteTimes, keyframeValues, {
ease: Array.isArray(easingFunctions)
? easingFunctions
: defaultEasing(keyframeValues, easingFunctions),
});
return {
calculatedDuration: duration,
next: (t) => {
state.value = mapTimeToKeyframe(t);
state.done = t >= duration;
return state;
},
};
}
export { defaultEasing, keyframes };
//# sourceMappingURL=keyframes.mjs.map
@@ -0,0 +1,399 @@
import { millisecondsToSeconds, secondsToMilliseconds, warning, clamp } from 'motion-utils';
import { generateLinearEasing } from '../waapi/utils/linear.mjs';
import { calcGeneratorDuration, maxGeneratorDuration } from './utils/calc-duration.mjs';
import { createGeneratorEasing } from './utils/create-generator-easing.mjs';
const springDefaults = {
// Default spring physics
stiffness: 100,
damping: 10,
mass: 1.0,
// Default duration/bounce-based options
duration: 800, // in ms
bounce: 0.3,
visualDuration: 0.3, // in seconds
// Rest thresholds
restSpeed: {
granular: 0.01,
default: 2,
},
restDelta: {
granular: 0.005,
default: 0.5,
},
// Limits
minDuration: 0.01, // in seconds
maxDuration: 10.0, // in seconds
minDamping: 0.05,
};
/**
* Maps bounce to a damping ratio as SwiftUI does: 0 is critically damped, and
* it approaches undamped towards 1 and infinitely overdamped towards -1. Both
* ends are limited so the spring still moves and settles.
*/
const bounceToDampingRatio = (bounce) => bounce < 0
? 1 / Math.max(1 + bounce, springDefaults.minDamping)
: Math.max(1 - bounce, springDefaults.minDamping);
/**
* Scales an overdamped spring's undamped frequency so that, at its defining
* time, it has as far left to go as the critically damped spring that's
* criticalFreqTime radians in by then. A negative bounce then changes the
* shape of the curve, not when it arrives. 1 at a damping ratio of 1, so
* there's no jump at a bounce of 0.
*
* Solved by Newton on the remaining distance (times fast - slow), which is
* convex from criticalFreqTime onwards. criticalFreqTime is always below the
* answer, so it converges from below without overshooting.
*/
function overdampedFreqScale(dampingRatio, criticalFreqTime) {
if (!(dampingRatio > 1))
return 1;
const root = Math.sqrt(dampingRatio * dampingRatio - 1);
const slow = dampingRatio - root;
const fast = dampingRatio + root;
const target = 2 * root * Math.exp(-criticalFreqTime) * (1 + criticalFreqTime);
return (approximateRoot((freqTime) => fast * Math.exp(-slow * freqTime) -
slow * Math.exp(-fast * freqTime) -
target, (freqTime) => Math.exp(-fast * freqTime) - Math.exp(-slow * freqTime), criticalFreqTime) / criticalFreqTime);
}
function calcAngularFreq(undampedFreq, dampingRatio) {
return undampedFreq * Math.sqrt(1 - dampingRatio * dampingRatio);
}
const rootIterations = 12;
function approximateRoot(envelope, derivative, initialGuess) {
let result = initialGuess;
for (let i = 1; i < rootIterations; i++) {
result = result - envelope(result) / derivative(result);
}
return result;
}
/**
* This is ported from the Framer implementation of duration-based spring resolution.
*/
const safeMin = 0.001;
/**
* Assumes zero initial velocity and the default mass: time-defined springs
* ignore inherited velocity and only resolve without valid physics. Returns
* NaN physics when the root search doesn't converge.
*/
function findSpring({ duration = springDefaults.duration, bounce = springDefaults.bounce, }) {
let envelope;
let derivative;
warning(duration <= secondsToMilliseconds(springDefaults.maxDuration), "Spring duration must be 10 seconds or less", "spring-duration-limit");
const dampingRatio = bounceToDampingRatio(bounce);
duration = clamp(springDefaults.minDuration, springDefaults.maxDuration, millisecondsToSeconds(duration));
if (dampingRatio < 1) {
/**
* Underdamped spring
*/
envelope = (undampedFreq) => {
const exponentialDecay = undampedFreq * dampingRatio;
const delta = exponentialDecay * duration;
const b = calcAngularFreq(undampedFreq, dampingRatio);
const c = Math.exp(-delta);
return safeMin - (exponentialDecay / b) * c;
};
derivative = (undampedFreq) => {
const exponentialDecay = undampedFreq * dampingRatio;
const delta = exponentialDecay * duration;
const e = dampingRatio *
dampingRatio *
undampedFreq *
undampedFreq *
duration;
const f = Math.exp(-delta);
const g = calcAngularFreq(undampedFreq * undampedFreq, dampingRatio);
const factor = -envelope(undampedFreq) + safeMin > 0 ? -1 : 1;
return (factor * -e * f) / g;
};
}
else {
/**
* Critically-damped spring. Overdamped springs reuse this root,
* scaled below.
*/
envelope = (undampedFreq) => {
const a = Math.exp(-undampedFreq * duration);
const b = undampedFreq * duration + 1;
return -safeMin + a * b;
};
derivative = (undampedFreq) => {
const a = Math.exp(-undampedFreq * duration);
const b = -undampedFreq * (duration * duration);
return a * b;
};
}
const initialGuess = 5 / duration;
const criticalFreq = approximateRoot(envelope, derivative, initialGuess);
const undampedFreq = criticalFreq *
overdampedFreqScale(dampingRatio, criticalFreq * duration);
const stiffness = undampedFreq * undampedFreq;
return {
stiffness,
damping: dampingRatio * 2 * Math.sqrt(stiffness),
duration: secondsToMilliseconds(duration),
};
}
/**
* Spring physics must be finite. stiffness and mass are also divisors so must
* be positive, whereas a damping of 0 is a valid, perpetually oscillating
* spring. Relational rather than Number.isFinite so numeric strings still
* coerce.
*/
const isValidPhysics = (value, canBeZero) => (canBeZero ? value >= 0 : value > 0) && value < Infinity;
/**
* Returns value if it's usable spring physics, otherwise undefined so callers
* fall back to the default. An explicit `undefined`, e.g. from a forwarded
* optional prop, must fall back too. Invalid physics would otherwise resolve
* to NaN spring values that never report done.
*/
function resolvePhysics(value, canBeZero) {
if (isValidPhysics(value, canBeZero))
return value;
if (process.env.NODE_ENV !== "production") {
warning(value === undefined, "Spring stiffness and mass must be positive, damping 0 or greater", "spring-invalid-physics");
}
return undefined;
}
function getSpringOptions(options) {
/**
* Resolve physics before choosing between physics- and duration-based
* resolution, so an invalid stiffness doesn't also silently discard a
* valid duration/bounce.
*/
const validStiffness = resolvePhysics(options.stiffness);
const validDamping = resolvePhysics(options.damping, true);
const validMass = resolvePhysics(options.mass);
const springOptions = {
...options,
stiffness: validStiffness ?? springDefaults.stiffness,
damping: validDamping ?? springDefaults.damping,
mass: validMass ?? springDefaults.mass,
isResolvedFromDuration: false,
// stiffness/damping/mass overrides duration/bounce
isTimeDefined: (validStiffness ?? validDamping ?? validMass) === undefined &&
(options.duration !== undefined || options.bounce !== undefined),
};
if (springOptions.isTimeDefined) {
if (options.visualDuration) {
const dampingRatio = bounceToDampingRatio(options.bounce || 0);
const root = ((2 * Math.PI) / (options.visualDuration * 1.2)) *
overdampedFreqScale(dampingRatio, (2 * Math.PI) / 1.2);
springOptions.stiffness = root * root;
springOptions.damping =
2 * dampingRatio * Math.sqrt(springOptions.stiffness);
}
else {
Object.assign(springOptions, findSpring(springOptions));
springOptions.isResolvedFromDuration = true;
}
/**
* Time options can degenerate: a NaN bounce gives a NaN damping, an
* infinite visualDuration a 0 stiffness, and findSpring NaN for both
* when it doesn't converge. Replace the two together, so the
* relationship duration resolution establishes between them is never
* left half-overwritten.
*/
if (!isValidPhysics(springOptions.stiffness) ||
!isValidPhysics(springOptions.damping, true)) {
springOptions.stiffness = springDefaults.stiffness;
springOptions.damping = springDefaults.damping;
}
}
return springOptions;
}
function spring(optionsOrVisualDuration = springDefaults.visualDuration, bounce = springDefaults.bounce) {
const options = typeof optionsOrVisualDuration !== "object"
? {
visualDuration: optionsOrVisualDuration,
keyframes: [0, 1],
bounce,
}
: optionsOrVisualDuration;
const origin = options.keyframes[0];
const target = options.keyframes[options.keyframes.length - 1];
/**
* This is the Iterator-spec return value. We ensure it's mutable rather than using a generator
* to reduce GC during animation.
*/
const state = { done: false, value: origin };
const { stiffness, damping, mass, duration, isResolvedFromDuration, isTimeDefined, } = getSpringOptions({ ...options });
/**
* Time-defined springs ignore inherited velocity. Velocity from
* interrupted animations causes massive oscillation on small-range
* animations.
*/
const inheritVelocity = (velocity) => isTimeDefined ? 0 : -millisecondsToSeconds(velocity);
const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
const undampedAngularFreq = millisecondsToSeconds(Math.sqrt(stiffness / mass));
const decay = dampingRatio * undampedAngularFreq;
/**
* Everything that changes when the spring is retargeted: written by
* retarget() and update(), read by the resolvers. Grouped on one object,
* like the coefficients (c) below. Writing doubles to object fields
* measured marginally faster than to captured let variables in optimised
* code; neither allocates, so this is a grouping choice, not a GC one.
*/
const s = {
target,
delta: target - origin,
velocity: inheritVelocity(options.velocity || 0) || 0,
restSpeed: 0,
restDelta: 0,
};
/**
* If we're working on a granular scale, use smaller defaults for determining
* when the spring is finished.
*
* These defaults have been selected empirically based on what strikes a good
* ratio between feeling good and finishing as soon as changes are imperceptible.
*/
const setRestThresholds = () => {
const isGranularScale = Math.abs(s.delta) < 5;
s.restSpeed =
options.restSpeed ||
(isGranularScale
? springDefaults.restSpeed.granular
: springDefaults.restSpeed.default);
s.restDelta =
options.restDelta ||
(isGranularScale
? springDefaults.restDelta.granular
: springDefaults.restDelta.default);
};
setRestThresholds();
let resolveSpring;
let resolveVelocity;
/**
* Derives the coefficients that depend on origin, target and initial
* velocity. Called once now and again whenever the spring is retargeted.
*/
let update;
if (dampingRatio < 1) {
const angularFreq = calcAngularFreq(undampedAngularFreq, dampingRatio);
/**
* A is the position coefficient, sinC/cosC the coefficients of the
* analytical derivative (px/ms). The exp/sin/cos terms depend only
* on t, so they're memoized by t independently of the target: a
* spring retargeted every frame samples the same t each frame and
* skips the transcendentals entirely.
*/
const c = { A: 0, sinC: 0, cosC: 0, t: -1, env: 0, sin: 0, cos: 0 };
update = () => {
c.A = (s.velocity + decay * s.delta) / angularFreq;
c.sinC = decay * c.A + s.delta * angularFreq;
c.cosC = decay * s.delta - c.A * angularFreq;
};
const sample = (t) => {
if (t !== c.t) {
c.t = t;
c.env = Math.exp(-decay * t);
c.sin = Math.sin(angularFreq * t);
c.cos = Math.cos(angularFreq * t);
}
};
// Underdamped spring
resolveSpring = (t) => {
sample(t);
return s.target - c.env * (c.A * c.sin + s.delta * c.cos);
};
resolveVelocity = (t) => {
sample(t);
return c.env * (c.sinC * c.sin + c.cosC * c.cos);
};
}
else if (dampingRatio === 1) {
// Critically damped spring
resolveSpring = (t) => s.target -
Math.exp(-undampedAngularFreq * t) *
(s.delta + (s.velocity + undampedAngularFreq * s.delta) * t);
// Analytical derivative of critically damped spring (px/ms)
const c = { C: 0 };
update = () => {
c.C = s.velocity + undampedAngularFreq * s.delta;
};
resolveVelocity = (t) => Math.exp(-undampedAngularFreq * t) *
(undampedAngularFreq * c.C * t - s.velocity);
}
else {
/**
* Overdamped spring: the sum of a slow and a fast decaying
* exponential, so no term can overflow however heavily it's damped.
*/
const dampedAngularFreq = undampedAngularFreq * Math.sqrt(dampingRatio * dampingRatio - 1);
const slow = decay - dampedAngularFreq;
const fast = decay + dampedAngularFreq;
/**
* Physics-defined springs keep a non-physical limit: once
* dampedAngularFreq * t passes 300, both terms decay at the damping
* rate. Heavily overdamped drag springs (dragElastic: 0,
* dragTransition) rely on this to settle almost at once rather than
* creep at stiffness / damping. Negative bounce needs the exact
* curve, so time-defined springs are exempt.
*/
const limit = isTimeDefined ? Infinity : 300 / dampedAngularFreq;
const decayAt = (rate, t) => Math.exp(t > limit ? -rate * limit - decay * (t - limit) : -rate * t);
const c = { S: 0, F: 0 };
update = () => {
const P = (s.velocity + decay * s.delta) / dampedAngularFreq;
c.S = (s.delta + P) / 2;
c.F = (s.delta - P) / 2;
};
resolveSpring = (t) => s.target - c.S * decayAt(slow, t) - c.F * decayAt(fast, t);
// Analytical derivative of overdamped spring (px/ms)
resolveVelocity = (t) => slow * c.S * decayAt(slow, t) + fast * c.F * decayAt(fast, t);
}
update();
const calculatedDuration = isResolvedFromDuration ? duration || null : null;
const generator = {
calculatedDuration,
/**
* Aim the spring at a new target from its current position and
* velocity, reusing the resolved physics and closures.
*/
retarget: (keyframes, newVelocity) => {
s.target = keyframes[keyframes.length - 1];
s.delta = s.target - keyframes[0];
s.velocity = inheritVelocity(newVelocity);
// Default thresholds depend on the scale of the new delta
if (!(options.restSpeed && options.restDelta))
setRestThresholds();
// Invalidate any duration lazily cached by JSAnimation
generator.calculatedDuration = calculatedDuration;
state.done = false;
update();
},
velocity: (t) => secondsToMilliseconds(resolveVelocity(t)),
next: (t) => {
const current = resolveSpring(t);
if (!isResolvedFromDuration) {
const currentVelocity = secondsToMilliseconds(resolveVelocity(t));
state.done =
Math.abs(currentVelocity) <= s.restSpeed &&
Math.abs(s.target - current) <= s.restDelta;
}
else {
state.done = t >= duration;
}
state.value = state.done ? s.target : current;
return state;
},
toString: () => {
const easingDuration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
const easing = generateLinearEasing((progress) => generator.next(easingDuration * progress).value, easingDuration, 30);
return easingDuration + "ms " + easing;
},
toTransition: () => { },
};
return generator;
}
spring.applyToOptions = (options) => {
const generatorOptions = createGeneratorEasing(options, 100, spring);
options.ease = generatorOptions.ease;
options.duration = secondsToMilliseconds(generatorOptions.duration);
options.type = "keyframes";
return options;
};
export { spring };
//# sourceMappingURL=spring.mjs.map
@@ -0,0 +1,19 @@
/**
* Implement a practical max duration for keyframe generation
* to prevent infinite loops
*/
const maxGeneratorDuration = 20000;
function calcGeneratorDuration(generator, timeStep = 50, maxDuration = maxGeneratorDuration, keyframes) {
let duration = 0;
let state = generator.next(duration);
keyframes?.push(state.value);
while (!state.done && duration < maxDuration) {
duration += timeStep;
state = generator.next(duration);
keyframes?.push(state.value);
}
return duration >= maxDuration ? Infinity : duration;
}
export { calcGeneratorDuration, maxGeneratorDuration };
//# sourceMappingURL=calc-duration.mjs.map
@@ -0,0 +1,33 @@
import { millisecondsToSeconds } from 'motion-utils';
import { calcGeneratorDuration, maxGeneratorDuration } from './calc-duration.mjs';
/**
* Create a progress => progress easing function from a generator.
*/
function createGeneratorEasing(options, scale = 100, createGenerator) {
/**
* The easing is generated over 0 - scale, but velocity is in the
* value's own units, so scale it to match. Otherwise an interrupted
* spring hands off the wrong speed, or direction. Keyframes with
* units (colours, filters, mixed units) give NaN, which is falsy,
* so their velocity passes through unscaled.
*/
const { keyframes: k, velocity: v } = options;
const delta = k && k[k.length - 1] - k[0];
const generator = createGenerator({
...options,
velocity: v && delta ? (v * scale) / delta : v,
keyframes: [0, scale],
});
const duration = Math.min(calcGeneratorDuration(generator), maxGeneratorDuration);
return {
type: "keyframes",
ease: (progress) => {
return generator.next(duration * progress).value / scale;
},
duration: millisecondsToSeconds(duration),
};
}
export { createGeneratorEasing };
//# sourceMappingURL=create-generator-easing.mjs.map
@@ -0,0 +1,6 @@
function isGenerator(type) {
return typeof type === "function" && "applyToOptions" in type;
}
export { isGenerator };
//# sourceMappingURL=is-generator.mjs.map
@@ -0,0 +1,22 @@
import { velocityPerSecond } from 'motion-utils';
const velocitySampleDuration = 5; // ms
function getGeneratorVelocity(resolveValue, t, current) {
const prevT = Math.max(t - velocitySampleDuration, 0);
return velocityPerSecond(current - resolveValue(prevT), t - prevT);
}
/**
* A generator's velocity at time t in units/second: analytical where the
* generator provides it (springs), otherwise by finite difference. Before
* the animation has started this is its initial velocity.
*/
function calcGeneratorVelocity(generator, t, initialVelocity = 0) {
if (t <= 0)
return initialVelocity;
return generator.velocity
? generator.velocity(t)
: getGeneratorVelocity((s) => generator.next(s).value, t, generator.next(t).value);
}
export { calcGeneratorVelocity, getGeneratorVelocity };
//# sourceMappingURL=velocity.mjs.map
@@ -0,0 +1,106 @@
import { secondsToMilliseconds, MotionGlobalConfig } from 'motion-utils';
import { AsyncMotionValueAnimation } from '../AsyncMotionValueAnimation.mjs';
import { JSAnimation } from '../JSAnimation.mjs';
import { getValueTransition } from '../utils/get-value-transition.mjs';
import { makeAnimationInstant } from '../utils/make-animation-instant.mjs';
import { getDefaultTransition } from '../utils/default-transitions.mjs';
import { getFinalKeyframe } from '../keyframes/get-final.mjs';
import { isTransitionDefined } from '../utils/is-transition-defined.mjs';
import { frame } from '../../frameloop/frame.mjs';
const animateMotionValue = (name, value, target, transition = {}, element, isHandoff) => (onComplete) => {
const valueTransition = getValueTransition(transition, name) || {};
/**
* Most transition values are currently completely overwritten by value-specific
* transitions. In the future it'd be nicer to blend these transitions. But for now
* delay actually does inherit from the root transition if not value-specific.
*/
const delay = valueTransition.delay || transition.delay || 0;
/**
* Elapsed isn't a public transition option but can be passed through from
* optimized appear effects in milliseconds.
*/
let { elapsed = 0 } = transition;
elapsed = elapsed - secondsToMilliseconds(delay);
const options = {
keyframes: Array.isArray(target) ? target : [null, target],
ease: "easeOut",
velocity: value.getVelocity(),
...valueTransition,
delay: -elapsed,
onUpdate: (v) => {
value.set(v);
valueTransition.onUpdate && valueTransition.onUpdate(v);
},
onComplete: () => {
onComplete();
valueTransition.onComplete && valueTransition.onComplete();
},
name,
motionValue: value,
element: isHandoff ? undefined : element,
hasOnUpdate: !!valueTransition.onUpdate,
};
/**
* If there's no transition defined for this value, we can generate
* unique transition settings for this value.
*/
if (!isTransitionDefined(valueTransition)) {
Object.assign(options, getDefaultTransition(name, options));
}
/**
* Both WAAPI and our internal animation functions use durations
* as defined by milliseconds, while our external API defines them
* as seconds.
*/
options.duration && (options.duration = secondsToMilliseconds(options.duration));
options.repeatDelay && (options.repeatDelay = secondsToMilliseconds(options.repeatDelay));
/**
* Support deprecated way to set initial value. Prefer keyframe syntax.
*/
if (options.from !== undefined) {
options.keyframes[0] = options.from;
}
let shouldSkip = false;
if (options.type === false ||
(options.duration === 0 && !options.repeatDelay)) {
makeAnimationInstant(options);
if (options.delay === 0) {
shouldSkip = true;
}
}
if (MotionGlobalConfig.instantAnimations ||
MotionGlobalConfig.skipAnimations ||
element?.shouldSkipAnimations ||
valueTransition.skipAnimations) {
shouldSkip = true;
makeAnimationInstant(options);
options.delay = 0;
}
/**
* If the transition type or easing has been explicitly set by the user
* then we don't want to allow flattening the animation.
*/
options.allowFlatten = !valueTransition.type && !valueTransition.ease;
/**
* If we can or must skip creating the animation, and apply only
* the final keyframe, do so. We also check once keyframes are resolved but
* this early check prevents the need to create an animation at all.
*/
if (shouldSkip && !isHandoff && value.get() !== undefined) {
const finalKeyframe = getFinalKeyframe(options.keyframes, valueTransition);
if (finalKeyframe !== undefined) {
frame.update(() => {
options.onUpdate(finalKeyframe);
options.onComplete();
});
return;
}
}
return valueTransition.isSync
? new JSAnimation(options)
: new AsyncMotionValueAnimation(options);
};
export { animateMotionValue };
//# sourceMappingURL=motion-value.mjs.map

Some files were not shown because too many files have changed in this diff Show More