Mission Control Dashboard - Initial implementation

This commit is contained in:
Daniel Arroyo
2026-03-27 18:36:05 +00:00
parent 257cea2c7d
commit a8fb4d4555
12516 changed files with 2307128 additions and 2 deletions

View File

@@ -0,0 +1,42 @@
import { px } from '../../value/types/numbers/units.mjs';
function pixelsToPercent(pixels, axis) {
if (axis.max === axis.min)
return 0;
return (pixels / (axis.max - axis.min)) * 100;
}
/**
* We always correct borderRadius as a percentage rather than pixels to reduce paints.
* For example, if you are projecting a box that is 100px wide with a 10px borderRadius
* into a box that is 200px wide with a 20px borderRadius, that is actually a 10%
* borderRadius in both states. If we animate between the two in pixels that will trigger
* a paint each time. If we animate between the two in percentage we'll avoid a paint.
*/
const correctBorderRadius = {
correct: (latest, node) => {
if (!node.target)
return latest;
/**
* If latest is a string, if it's a percentage we can return immediately as it's
* going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.
*/
if (typeof latest === "string") {
if (px.test(latest)) {
latest = parseFloat(latest);
}
else {
return latest;
}
}
/**
* If latest is a number, it's a pixel value. We use the current viewportBox to calculate that
* pixel value as a percentage of each axis
*/
const x = pixelsToPercent(latest, node.target.x);
const y = pixelsToPercent(latest, node.target.y);
return `${x}% ${y}%`;
},
};
export { correctBorderRadius, pixelsToPercent };
//# sourceMappingURL=scale-border-radius.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"scale-border-radius.mjs","sources":["../../../../src/projection/styles/scale-border-radius.ts"],"sourcesContent":["import { px } from \"../../value/types/numbers/units\"\nimport type { Axis } from \"motion-utils\"\nimport type { ScaleCorrectorDefinition } from \"./types\"\n\nexport function pixelsToPercent(pixels: number, axis: Axis): number {\n if (axis.max === axis.min) return 0\n return (pixels / (axis.max - axis.min)) * 100\n}\n\n/**\n * We always correct borderRadius as a percentage rather than pixels to reduce paints.\n * For example, if you are projecting a box that is 100px wide with a 10px borderRadius\n * into a box that is 200px wide with a 20px borderRadius, that is actually a 10%\n * borderRadius in both states. If we animate between the two in pixels that will trigger\n * a paint each time. If we animate between the two in percentage we'll avoid a paint.\n */\nexport const correctBorderRadius: ScaleCorrectorDefinition = {\n correct: (latest, node) => {\n if (!node.target) return latest\n\n /**\n * If latest is a string, if it's a percentage we can return immediately as it's\n * going to be stretched appropriately. Otherwise, if it's a pixel, convert it to a number.\n */\n if (typeof latest === \"string\") {\n if (px.test(latest)) {\n latest = parseFloat(latest)\n } else {\n return latest\n }\n }\n\n /**\n * If latest is a number, it's a pixel value. We use the current viewportBox to calculate that\n * pixel value as a percentage of each axis\n */\n const x = pixelsToPercent(latest, node.target.x)\n const y = pixelsToPercent(latest, node.target.y)\n\n return `${x}% ${y}%`\n },\n}\n"],"names":[],"mappings":";;AAIM,SAAU,eAAe,CAAC,MAAc,EAAE,IAAU,EAAA;AACtD,IAAA,IAAI,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG;AAAE,QAAA,OAAO,CAAC;AACnC,IAAA,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG;AACjD;AAEA;;;;;;AAMG;AACI,MAAM,mBAAmB,GAA6B;AACzD,IAAA,OAAO,EAAE,CAAC,MAAM,EAAE,IAAI,KAAI;QACtB,IAAI,CAAC,IAAI,CAAC,MAAM;AAAE,YAAA,OAAO,MAAM;AAE/B;;;AAGG;AACH,QAAA,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;AAC5B,YAAA,IAAI,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;AACjB,gBAAA,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;YAC/B;iBAAO;AACH,gBAAA,OAAO,MAAM;YACjB;QACJ;AAEA;;;AAGG;AACH,QAAA,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAChD,QAAA,MAAM,CAAC,GAAG,eAAe,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;AAEhD,QAAA,OAAO,CAAA,EAAG,CAAC,CAAA,EAAA,EAAK,CAAC,GAAG;IACxB,CAAC;;;;;"}

View File

@@ -0,0 +1,36 @@
import { complex } from '../../value/types/complex/index.mjs';
import { mixNumber } from '../../utils/mix/number.mjs';
const correctBoxShadow = {
correct: (latest, { treeScale, projectionDelta }) => {
const original = latest;
const shadow = complex.parse(latest);
// TODO: Doesn't support multiple shadows
if (shadow.length > 5)
return original;
const template = complex.createTransformer(latest);
const offset = typeof shadow[0] !== "number" ? 1 : 0;
// Calculate the overall context scale
const xScale = projectionDelta.x.scale * treeScale.x;
const yScale = projectionDelta.y.scale * treeScale.y;
shadow[0 + offset] /= xScale;
shadow[1 + offset] /= yScale;
/**
* Ideally we'd correct x and y scales individually, but because blur and
* spread apply to both we have to take a scale average and apply that instead.
* We could potentially improve the outcome of this by incorporating the ratio between
* the two scales.
*/
const averageScale = mixNumber(xScale, yScale, 0.5);
// Blur
if (typeof shadow[2 + offset] === "number")
shadow[2 + offset] /= averageScale;
// Spread
if (typeof shadow[3 + offset] === "number")
shadow[3 + offset] /= averageScale;
return template(shadow);
},
};
export { correctBoxShadow };
//# sourceMappingURL=scale-box-shadow.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"scale-box-shadow.mjs","sources":["../../../../src/projection/styles/scale-box-shadow.ts"],"sourcesContent":["import { complex } from \"../../value/types/complex\"\nimport { mixNumber } from \"../../utils/mix/number\"\nimport type { ScaleCorrectorDefinition } from \"./types\"\n\nexport const correctBoxShadow: ScaleCorrectorDefinition = {\n correct: (latest: string, { treeScale, projectionDelta }) => {\n const original = latest\n const shadow = complex.parse(latest)\n\n // TODO: Doesn't support multiple shadows\n if (shadow.length > 5) return original\n\n const template = complex.createTransformer(latest)\n const offset = typeof shadow[0] !== \"number\" ? 1 : 0\n\n // Calculate the overall context scale\n const xScale = projectionDelta!.x.scale * treeScale!.x\n const yScale = projectionDelta!.y.scale * treeScale!.y\n\n // Scale x/y\n ;(shadow[0 + offset] as number) /= xScale\n ;(shadow[1 + offset] as number) /= yScale\n\n /**\n * Ideally we'd correct x and y scales individually, but because blur and\n * spread apply to both we have to take a scale average and apply that instead.\n * We could potentially improve the outcome of this by incorporating the ratio between\n * the two scales.\n */\n const averageScale = mixNumber(xScale, yScale, 0.5)\n\n // Blur\n if (typeof shadow[2 + offset] === \"number\")\n (shadow[2 + offset] as number) /= averageScale\n\n // Spread\n if (typeof shadow[3 + offset] === \"number\")\n (shadow[3 + offset] as number) /= averageScale\n\n return template(shadow)\n },\n}\n"],"names":[],"mappings":";;;AAIO,MAAM,gBAAgB,GAA6B;IACtD,OAAO,EAAE,CAAC,MAAc,EAAE,EAAE,SAAS,EAAE,eAAe,EAAE,KAAI;QACxD,MAAM,QAAQ,GAAG,MAAM;QACvB,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC;;AAGpC,QAAA,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;AAAE,YAAA,OAAO,QAAQ;QAEtC,MAAM,QAAQ,GAAG,OAAO,CAAC,iBAAiB,CAAC,MAAM,CAAC;AAClD,QAAA,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,GAAG,CAAC,GAAG,CAAC;;QAGpD,MAAM,MAAM,GAAG,eAAgB,CAAC,CAAC,CAAC,KAAK,GAAG,SAAU,CAAC,CAAC;QACtD,MAAM,MAAM,GAAG,eAAgB,CAAC,CAAC,CAAC,KAAK,GAAG,SAAU,CAAC,CAAC;AAGpD,QAAA,MAAM,CAAC,CAAC,GAAG,MAAM,CAAY,IAAI,MAAM;AACvC,QAAA,MAAM,CAAC,CAAC,GAAG,MAAM,CAAY,IAAI,MAAM;AAEzC;;;;;AAKG;QACH,MAAM,YAAY,GAAG,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC;;QAGnD,IAAI,OAAO,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,QAAQ;AACrC,YAAA,MAAM,CAAC,CAAC,GAAG,MAAM,CAAY,IAAI,YAAY;;QAGlD,IAAI,OAAO,MAAM,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,QAAQ;AACrC,YAAA,MAAM,CAAC,CAAC,GAAG,MAAM,CAAY,IAAI,YAAY;AAElD,QAAA,OAAO,QAAQ,CAAC,MAAM,CAAC;IAC3B,CAAC;;;;;"}

View File

@@ -0,0 +1,31 @@
import { isCSSVariableName } from '../../animation/utils/is-css-variable.mjs';
import { correctBorderRadius } from './scale-border-radius.mjs';
import { correctBoxShadow } from './scale-box-shadow.mjs';
const scaleCorrectors = {
borderRadius: {
...correctBorderRadius,
applyTo: [
"borderTopLeftRadius",
"borderTopRightRadius",
"borderBottomLeftRadius",
"borderBottomRightRadius",
],
},
borderTopLeftRadius: correctBorderRadius,
borderTopRightRadius: correctBorderRadius,
borderBottomLeftRadius: correctBorderRadius,
borderBottomRightRadius: correctBorderRadius,
boxShadow: correctBoxShadow,
};
function addScaleCorrector(correctors) {
for (const key in correctors) {
scaleCorrectors[key] = correctors[key];
if (isCSSVariableName(key)) {
scaleCorrectors[key].isCSSVariable = true;
}
}
}
export { addScaleCorrector, scaleCorrectors };
//# sourceMappingURL=scale-correction.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"scale-correction.mjs","sources":["../../../../src/projection/styles/scale-correction.ts"],"sourcesContent":["import { isCSSVariableName } from \"../../animation/utils/is-css-variable\"\nimport { correctBorderRadius } from \"./scale-border-radius\"\nimport { correctBoxShadow } from \"./scale-box-shadow\"\nimport type { ScaleCorrectorMap } from \"./types\"\n\nexport const scaleCorrectors: ScaleCorrectorMap = {\n borderRadius: {\n ...correctBorderRadius,\n applyTo: [\n \"borderTopLeftRadius\",\n \"borderTopRightRadius\",\n \"borderBottomLeftRadius\",\n \"borderBottomRightRadius\",\n ],\n },\n borderTopLeftRadius: correctBorderRadius,\n borderTopRightRadius: correctBorderRadius,\n borderBottomLeftRadius: correctBorderRadius,\n borderBottomRightRadius: correctBorderRadius,\n boxShadow: correctBoxShadow,\n}\n\nexport function addScaleCorrector(correctors: ScaleCorrectorMap) {\n for (const key in correctors) {\n scaleCorrectors[key] = correctors[key]\n if (isCSSVariableName(key)) {\n scaleCorrectors[key].isCSSVariable = true\n }\n }\n}\n"],"names":[],"mappings":";;;;AAKO,MAAM,eAAe,GAAsB;AAC9C,IAAA,YAAY,EAAE;AACV,QAAA,GAAG,mBAAmB;AACtB,QAAA,OAAO,EAAE;YACL,qBAAqB;YACrB,sBAAsB;YACtB,wBAAwB;YACxB,yBAAyB;AAC5B,SAAA;AACJ,KAAA;AACD,IAAA,mBAAmB,EAAE,mBAAmB;AACxC,IAAA,oBAAoB,EAAE,mBAAmB;AACzC,IAAA,sBAAsB,EAAE,mBAAmB;AAC3C,IAAA,uBAAuB,EAAE,mBAAmB;AAC5C,IAAA,SAAS,EAAE,gBAAgB;;AAGzB,SAAU,iBAAiB,CAAC,UAA6B,EAAA;AAC3D,IAAA,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE;QAC1B,eAAe,CAAC,GAAG,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC;AACtC,QAAA,IAAI,iBAAiB,CAAC,GAAG,CAAC,EAAE;AACxB,YAAA,eAAe,CAAC,GAAG,CAAC,CAAC,aAAa,GAAG,IAAI;QAC7C;IACJ;AACJ;;;;"}

View File

@@ -0,0 +1,50 @@
function buildProjectionTransform(delta, treeScale, latestTransform) {
let transform = "";
/**
* The translations we use to calculate are always relative to the viewport coordinate space.
* But when we apply scales, we also scale the coordinate space of an element and its children.
* For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need
* to move an element 100 pixels, we actually need to move it 200 in within that scaled space.
*/
const xTranslate = delta.x.translate / treeScale.x;
const yTranslate = delta.y.translate / treeScale.y;
const zTranslate = latestTransform?.z || 0;
if (xTranslate || yTranslate || zTranslate) {
transform = `translate3d(${xTranslate}px, ${yTranslate}px, ${zTranslate}px) `;
}
/**
* Apply scale correction for the tree transform.
* This will apply scale to the screen-orientated axes.
*/
if (treeScale.x !== 1 || treeScale.y !== 1) {
transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `;
}
if (latestTransform) {
const { transformPerspective, rotate, rotateX, rotateY, skewX, skewY } = latestTransform;
if (transformPerspective)
transform = `perspective(${transformPerspective}px) ${transform}`;
if (rotate)
transform += `rotate(${rotate}deg) `;
if (rotateX)
transform += `rotateX(${rotateX}deg) `;
if (rotateY)
transform += `rotateY(${rotateY}deg) `;
if (skewX)
transform += `skewX(${skewX}deg) `;
if (skewY)
transform += `skewY(${skewY}deg) `;
}
/**
* Apply scale to match the size of the element to the size we want it.
* This will apply scale to the element-orientated axes.
*/
const elementScaleX = delta.x.scale * treeScale.x;
const elementScaleY = delta.y.scale * treeScale.y;
if (elementScaleX !== 1 || elementScaleY !== 1) {
transform += `scale(${elementScaleX}, ${elementScaleY})`;
}
return transform || "none";
}
export { buildProjectionTransform };
//# sourceMappingURL=transform.mjs.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"transform.mjs","sources":["../../../../src/projection/styles/transform.ts"],"sourcesContent":["import type { Delta, Point } from \"motion-utils\"\nimport type { ResolvedValues } from \"../../node/types\"\n\nexport function buildProjectionTransform(\n delta: Delta,\n treeScale: Point,\n latestTransform?: ResolvedValues\n): string {\n let transform = \"\"\n\n /**\n * The translations we use to calculate are always relative to the viewport coordinate space.\n * But when we apply scales, we also scale the coordinate space of an element and its children.\n * For instance if we have a treeScale (the culmination of all parent scales) of 0.5 and we need\n * to move an element 100 pixels, we actually need to move it 200 in within that scaled space.\n */\n const xTranslate = delta.x.translate / treeScale.x\n const yTranslate = delta.y.translate / treeScale.y\n const zTranslate = latestTransform?.z || 0\n if (xTranslate || yTranslate || zTranslate) {\n transform = `translate3d(${xTranslate}px, ${yTranslate}px, ${zTranslate}px) `\n }\n\n /**\n * Apply scale correction for the tree transform.\n * This will apply scale to the screen-orientated axes.\n */\n if (treeScale.x !== 1 || treeScale.y !== 1) {\n transform += `scale(${1 / treeScale.x}, ${1 / treeScale.y}) `\n }\n\n if (latestTransform) {\n const { transformPerspective, rotate, rotateX, rotateY, skewX, skewY } =\n latestTransform\n if (transformPerspective)\n transform = `perspective(${transformPerspective}px) ${transform}`\n if (rotate) transform += `rotate(${rotate}deg) `\n if (rotateX) transform += `rotateX(${rotateX}deg) `\n if (rotateY) transform += `rotateY(${rotateY}deg) `\n if (skewX) transform += `skewX(${skewX}deg) `\n if (skewY) transform += `skewY(${skewY}deg) `\n }\n\n /**\n * Apply scale to match the size of the element to the size we want it.\n * This will apply scale to the element-orientated axes.\n */\n const elementScaleX = delta.x.scale * treeScale.x\n const elementScaleY = delta.y.scale * treeScale.y\n if (elementScaleX !== 1 || elementScaleY !== 1) {\n transform += `scale(${elementScaleX}, ${elementScaleY})`\n }\n\n return transform || \"none\"\n}\n"],"names":[],"mappings":"SAGgB,wBAAwB,CACpC,KAAY,EACZ,SAAgB,EAChB,eAAgC,EAAA;IAEhC,IAAI,SAAS,GAAG,EAAE;AAElB;;;;;AAKG;IACH,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;IAClD,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC;AAClD,IAAA,MAAM,UAAU,GAAG,eAAe,EAAE,CAAC,IAAI,CAAC;AAC1C,IAAA,IAAI,UAAU,IAAI,UAAU,IAAI,UAAU,EAAE;QACxC,SAAS,GAAG,eAAe,UAAU,CAAA,IAAA,EAAO,UAAU,CAAA,IAAA,EAAO,UAAU,MAAM;IACjF;AAEA;;;AAGG;AACH,IAAA,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,IAAI,SAAS,CAAC,CAAC,KAAK,CAAC,EAAE;AACxC,QAAA,SAAS,IAAI,CAAA,MAAA,EAAS,CAAC,GAAG,SAAS,CAAC,CAAC,CAAA,EAAA,EAAK,CAAC,GAAG,SAAS,CAAC,CAAC,IAAI;IACjE;IAEA,IAAI,eAAe,EAAE;AACjB,QAAA,MAAM,EAAE,oBAAoB,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAClE,eAAe;AACnB,QAAA,IAAI,oBAAoB;AACpB,YAAA,SAAS,GAAG,CAAA,YAAA,EAAe,oBAAoB,CAAA,IAAA,EAAO,SAAS,EAAE;AACrE,QAAA,IAAI,MAAM;AAAE,YAAA,SAAS,IAAI,CAAA,OAAA,EAAU,MAAM,CAAA,KAAA,CAAO;AAChD,QAAA,IAAI,OAAO;AAAE,YAAA,SAAS,IAAI,CAAA,QAAA,EAAW,OAAO,CAAA,KAAA,CAAO;AACnD,QAAA,IAAI,OAAO;AAAE,YAAA,SAAS,IAAI,CAAA,QAAA,EAAW,OAAO,CAAA,KAAA,CAAO;AACnD,QAAA,IAAI,KAAK;AAAE,YAAA,SAAS,IAAI,CAAA,MAAA,EAAS,KAAK,CAAA,KAAA,CAAO;AAC7C,QAAA,IAAI,KAAK;AAAE,YAAA,SAAS,IAAI,CAAA,MAAA,EAAS,KAAK,CAAA,KAAA,CAAO;IACjD;AAEA;;;AAGG;IACH,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACjD,MAAM,aAAa,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IACjD,IAAI,aAAa,KAAK,CAAC,IAAI,aAAa,KAAK,CAAC,EAAE;AAC5C,QAAA,SAAS,IAAI,CAAA,MAAA,EAAS,aAAa,CAAA,EAAA,EAAK,aAAa,GAAG;IAC5D;IAEA,OAAO,SAAS,IAAI,MAAM;AAC9B;;;;"}