@bug-on/m3-expressive 1.3.4 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +31 -7
- package/dist/assets/material-symbols-cdn.css +17 -23
- package/dist/buttons.js +129 -35
- package/dist/buttons.js.map +1 -1
- package/dist/buttons.mjs +129 -35
- package/dist/buttons.mjs.map +1 -1
- package/dist/{core-COvZHQ_k.d.ts → core-Cckt8qEm.d.ts} +10 -4
- package/dist/{core-CRkixy1y.d.mts → core-DDfG4pym.d.mts} +10 -4
- package/dist/core.d.mts +2 -2
- package/dist/core.d.ts +2 -2
- package/dist/core.js +6 -5
- package/dist/core.js.map +1 -1
- package/dist/core.mjs +6 -5
- package/dist/core.mjs.map +1 -1
- package/dist/fading-blur-mask--uZbBKLj.d.mts +77 -0
- package/dist/fading-blur-mask--uZbBKLj.d.ts +77 -0
- package/dist/feedback.js +1 -1
- package/dist/feedback.js.map +1 -1
- package/dist/feedback.mjs +1 -1
- package/dist/feedback.mjs.map +1 -1
- package/dist/forms.d.mts +2 -2
- package/dist/forms.d.ts +2 -2
- package/dist/forms.js +17 -7
- package/dist/forms.js.map +1 -1
- package/dist/forms.mjs +17 -7
- package/dist/forms.mjs.map +1 -1
- package/dist/index.d.mts +9 -8
- package/dist/index.d.ts +9 -8
- package/dist/index.js +988 -309
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +984 -310
- package/dist/index.mjs.map +1 -1
- package/dist/layout.d.mts +5 -3
- package/dist/layout.d.ts +5 -3
- package/dist/layout.js +61 -6
- package/dist/layout.js.map +1 -1
- package/dist/layout.mjs +61 -7
- package/dist/layout.mjs.map +1 -1
- package/dist/material-symbols-cdn.css +17 -23
- package/dist/{md3-expressive-shapes-CPcfl_Hf.d.mts → md3-expressive-shapes-wk_98LJh.d.mts} +20 -1
- package/dist/{md3-expressive-shapes-CPcfl_Hf.d.ts → md3-expressive-shapes-wk_98LJh.d.ts} +20 -1
- package/dist/navigation.d.mts +26 -84
- package/dist/navigation.d.ts +26 -84
- package/dist/navigation.js +95 -29
- package/dist/navigation.js.map +1 -1
- package/dist/navigation.mjs +95 -29
- package/dist/navigation.mjs.map +1 -1
- package/dist/overlays.d.mts +4 -2
- package/dist/overlays.d.ts +4 -2
- package/dist/overlays.js +85 -44
- package/dist/overlays.js.map +1 -1
- package/dist/overlays.mjs +85 -44
- package/dist/overlays.mjs.map +1 -1
- package/dist/pickers.d.mts +12 -0
- package/dist/pickers.d.ts +12 -0
- package/dist/pickers.js +186 -120
- package/dist/pickers.js.map +1 -1
- package/dist/pickers.mjs +186 -120
- package/dist/pickers.mjs.map +1 -1
- package/dist/shapes.d.mts +141 -15
- package/dist/shapes.d.ts +141 -15
- package/dist/shapes.js +618 -126
- package/dist/shapes.js.map +1 -1
- package/dist/shapes.mjs +614 -127
- package/dist/shapes.mjs.map +1 -1
- package/dist/{side-sheet-modal-64FGhDxL.d.mts → side-sheet-modal-BycxrabB.d.mts} +70 -0
- package/dist/{side-sheet-modal-Bd5Qqvp9.d.ts → side-sheet-modal-Cw4vemKx.d.ts} +70 -0
- package/dist/{text-field-4OlT9o8s.d.mts → text-field-B1fLh5Sh.d.mts} +22 -3
- package/dist/{text-field-DARNdj14.d.ts → text-field-C0VQLp8Y.d.ts} +22 -3
- package/dist/{typography-339RV6v7.d.mts → typography-CKTjvlZ4.d.mts} +25 -0
- package/dist/{typography-339RV6v7.d.ts → typography-CKTjvlZ4.d.ts} +25 -0
- package/dist/typography.css +1 -1
- package/llms-full.txt +24 -13
- package/llms.txt +11 -6
- package/package.json +3 -3
package/dist/shapes.js
CHANGED
|
@@ -38,6 +38,13 @@ function transformFeature(feature, fn) {
|
|
|
38
38
|
}
|
|
39
39
|
return edgeFeature(cubics);
|
|
40
40
|
}
|
|
41
|
+
function reverseFeature(feature) {
|
|
42
|
+
const cubics = feature.cubics.slice().reverse().map((c) => c.reverse());
|
|
43
|
+
if (feature.type === "corner") {
|
|
44
|
+
return cornerFeature(cubics, feature.convex);
|
|
45
|
+
}
|
|
46
|
+
return edgeFeature(cubics);
|
|
47
|
+
}
|
|
41
48
|
|
|
42
49
|
// src/shapes/math/corner-rounding.ts
|
|
43
50
|
function cornerRounding(radius = 0, smoothing = 0) {
|
|
@@ -82,6 +89,10 @@ function convex(prevX, prevY, currX, currY, nextX, nextY) {
|
|
|
82
89
|
const v2y = nextY - currY;
|
|
83
90
|
return v1x * v2y - v1y * v2x > 0;
|
|
84
91
|
}
|
|
92
|
+
function formatCoordinate(v) {
|
|
93
|
+
const rounded = Math.round(v * 1e4) / 1e4;
|
|
94
|
+
return (rounded === 0 ? 0 : rounded).toString();
|
|
95
|
+
}
|
|
85
96
|
|
|
86
97
|
// src/shapes/math/point.ts
|
|
87
98
|
function point(x, y) {
|
|
@@ -181,9 +192,16 @@ var Cubic = class _Cubic {
|
|
|
181
192
|
*/
|
|
182
193
|
pointOnCurve(t) {
|
|
183
194
|
const u = 1 - t;
|
|
195
|
+
const uu = u * u;
|
|
196
|
+
const tt = t * t;
|
|
197
|
+
const uuu = uu * u;
|
|
198
|
+
const ttt = tt * t;
|
|
199
|
+
const c0 = 3 * t * uu;
|
|
200
|
+
const c1 = 3 * tt * u;
|
|
201
|
+
const p = this.points;
|
|
184
202
|
return point(
|
|
185
|
-
|
|
186
|
-
|
|
203
|
+
p[0] * uuu + p[2] * c0 + p[4] * c1 + p[6] * ttt,
|
|
204
|
+
p[1] * uuu + p[3] * c0 + p[5] * c1 + p[7] * ttt
|
|
187
205
|
);
|
|
188
206
|
}
|
|
189
207
|
/**
|
|
@@ -406,13 +424,17 @@ var MutableCubic = class extends Cubic {
|
|
|
406
424
|
* @param progress - Interpolation factor [0, 1]
|
|
407
425
|
*/
|
|
408
426
|
interpolate(c1, c2, progress) {
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
427
|
+
const p1 = c1.points;
|
|
428
|
+
const p2 = c2.points;
|
|
429
|
+
const p = this.points;
|
|
430
|
+
p[0] = p1[0] + (p2[0] - p1[0]) * progress;
|
|
431
|
+
p[1] = p1[1] + (p2[1] - p1[1]) * progress;
|
|
432
|
+
p[2] = p1[2] + (p2[2] - p1[2]) * progress;
|
|
433
|
+
p[3] = p1[3] + (p2[3] - p1[3]) * progress;
|
|
434
|
+
p[4] = p1[4] + (p2[4] - p1[4]) * progress;
|
|
435
|
+
p[5] = p1[5] + (p2[5] - p1[5]) * progress;
|
|
436
|
+
p[6] = p1[6] + (p2[6] - p1[6]) * progress;
|
|
437
|
+
p[7] = p1[7] + (p2[7] - p1[7]) * progress;
|
|
416
438
|
}
|
|
417
439
|
/**
|
|
418
440
|
* Mutably applies a PointTransformer to all points.
|
|
@@ -824,6 +846,36 @@ var RoundedPolygon = class _RoundedPolygon {
|
|
|
824
846
|
this.centerY + d
|
|
825
847
|
];
|
|
826
848
|
}
|
|
849
|
+
/**
|
|
850
|
+
* Calculates the approximate signed area of the polygon using anchor points.
|
|
851
|
+
* Returns > 0 for Clockwise (in screen coords, y down), < 0 for Counter-Clockwise.
|
|
852
|
+
*/
|
|
853
|
+
calculateSignedArea() {
|
|
854
|
+
let area = 0;
|
|
855
|
+
const n = this.cubics.length;
|
|
856
|
+
for (let i = 0; i < n; i++) {
|
|
857
|
+
const c = this.cubics[i];
|
|
858
|
+
area += c.anchor0X * c.anchor1Y - c.anchor1X * c.anchor0Y;
|
|
859
|
+
}
|
|
860
|
+
return area / 2;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Returns true if the polygon outline runs in Clockwise order (screen coords).
|
|
864
|
+
*/
|
|
865
|
+
isClockwise() {
|
|
866
|
+
return this.calculateSignedArea() > 0;
|
|
867
|
+
}
|
|
868
|
+
/**
|
|
869
|
+
* Returns a reversed copy of the polygon with reversed traversal order.
|
|
870
|
+
* Flips the winding direction (CW ↔ CCW).
|
|
871
|
+
*/
|
|
872
|
+
reversed() {
|
|
873
|
+
const reversedFeatures = this.features.slice().reverse().map((f) => reverseFeature(f));
|
|
874
|
+
return new _RoundedPolygon(
|
|
875
|
+
reversedFeatures,
|
|
876
|
+
point(this.centerX, this.centerY)
|
|
877
|
+
);
|
|
878
|
+
}
|
|
827
879
|
};
|
|
828
880
|
function buildCubicsList(features) {
|
|
829
881
|
const result = [];
|
|
@@ -892,10 +944,21 @@ function validateContiguity(cubics) {
|
|
|
892
944
|
|
|
893
945
|
// src/shapes/catalog/md3-expressive-shapes.ts
|
|
894
946
|
function createShape(pts) {
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
947
|
+
let rawArea = 0;
|
|
948
|
+
for (const p of pts) {
|
|
949
|
+
rawArea += p[0] * p[7] - p[6] * p[1];
|
|
950
|
+
}
|
|
951
|
+
const normalizedPts = rawArea < 0 ? pts.slice().reverse().map((p) => [p[6], p[7], p[4], p[5], p[2], p[3], p[0], p[1]]) : pts;
|
|
952
|
+
const cubics = normalizedPts.map((p) => new Cubic(p));
|
|
953
|
+
const n = cubics.length;
|
|
954
|
+
const features = cubics.map((c, i) => {
|
|
955
|
+
const prev = cubics[(i - 1 + n) % n].pointOnCurve(0.5);
|
|
956
|
+
const curr = c.pointOnCurve(0.5);
|
|
957
|
+
const next = cubics[(i + 1) % n].pointOnCurve(0.5);
|
|
958
|
+
const isConvex = convex(prev.x, prev.y, curr.x, curr.y, next.x, next.y);
|
|
959
|
+
return cornerFeature([c], isConvex);
|
|
960
|
+
});
|
|
961
|
+
return RoundedPolygon.fromFeatures(features);
|
|
899
962
|
}
|
|
900
963
|
var archShape = createShape([
|
|
901
964
|
[1, 0.83461, 1, 0.8547, 1, 0.86477, 0.99898, 0.87322],
|
|
@@ -1993,6 +2056,94 @@ function resolveShape(ref) {
|
|
|
1993
2056
|
return shape;
|
|
1994
2057
|
}
|
|
1995
2058
|
|
|
2059
|
+
// src/shapes/catalog/shape-pairings.ts
|
|
2060
|
+
var MD3_SHAPE_PAIRINGS = {
|
|
2061
|
+
// ─── Organic & Floral ──────────────────────────────────────────
|
|
2062
|
+
circle: "flower",
|
|
2063
|
+
flower: "sunny",
|
|
2064
|
+
sunny: "verySunny",
|
|
2065
|
+
verySunny: "flower",
|
|
2066
|
+
bun: "clamshell",
|
|
2067
|
+
clamshell: "bun",
|
|
2068
|
+
puffy: "circle",
|
|
2069
|
+
// ─── Starburst & Explosive ─────────────────────────────────────
|
|
2070
|
+
burst: "softBurst",
|
|
2071
|
+
softBurst: "boom",
|
|
2072
|
+
boom: "softBoom",
|
|
2073
|
+
softBoom: "burst",
|
|
2074
|
+
cookie12Sided: "softBurst",
|
|
2075
|
+
// ─── Clover, Gem & Diamond ─────────────────────────────────────
|
|
2076
|
+
diamond: "puffyDiamond",
|
|
2077
|
+
puffyDiamond: "diamond",
|
|
2078
|
+
clover4Leaf: "clover8Leaf",
|
|
2079
|
+
clover8Leaf: "clover4Leaf",
|
|
2080
|
+
gem: "diamond",
|
|
2081
|
+
heart: "clover4Leaf",
|
|
2082
|
+
// ─── Cookies & Geometric Multi-sided ───────────────────────────
|
|
2083
|
+
cookie4Sided: "cookie6Sided",
|
|
2084
|
+
cookie6Sided: "cookie9Sided",
|
|
2085
|
+
cookie7Sided: "cookie12Sided",
|
|
2086
|
+
cookie9Sided: "cookie7Sided",
|
|
2087
|
+
square: "cookie4Sided",
|
|
2088
|
+
// ─── Rounded & Architectural ───────────────────────────────────
|
|
2089
|
+
pill: "oval",
|
|
2090
|
+
oval: "arch",
|
|
2091
|
+
arch: "semiCircle",
|
|
2092
|
+
semiCircle: "pill",
|
|
2093
|
+
slanted: "arch",
|
|
2094
|
+
// ─── Polygonal, Directional & Pixel ────────────────────────────
|
|
2095
|
+
triangle: "arrow",
|
|
2096
|
+
arrow: "pentagon",
|
|
2097
|
+
pentagon: "triangle",
|
|
2098
|
+
fan: "ghostish",
|
|
2099
|
+
ghostish: "fan",
|
|
2100
|
+
pixelTriangle: "pixelCircle",
|
|
2101
|
+
pixelCircle: "circle"
|
|
2102
|
+
};
|
|
2103
|
+
var MD3_SHAPE_FAMILIES = {
|
|
2104
|
+
organic: [
|
|
2105
|
+
"circle",
|
|
2106
|
+
"flower",
|
|
2107
|
+
"sunny",
|
|
2108
|
+
"verySunny",
|
|
2109
|
+
"bun",
|
|
2110
|
+
"clamshell",
|
|
2111
|
+
"puffy"
|
|
2112
|
+
],
|
|
2113
|
+
starburst: ["burst", "softBurst", "boom", "softBoom", "cookie12Sided"],
|
|
2114
|
+
cloverAndGems: [
|
|
2115
|
+
"diamond",
|
|
2116
|
+
"puffyDiamond",
|
|
2117
|
+
"clover4Leaf",
|
|
2118
|
+
"clover8Leaf",
|
|
2119
|
+
"gem",
|
|
2120
|
+
"heart"
|
|
2121
|
+
],
|
|
2122
|
+
cookiesAndPolygons: [
|
|
2123
|
+
"cookie4Sided",
|
|
2124
|
+
"cookie6Sided",
|
|
2125
|
+
"cookie7Sided",
|
|
2126
|
+
"cookie9Sided",
|
|
2127
|
+
"square"
|
|
2128
|
+
],
|
|
2129
|
+
roundedContainers: ["pill", "oval", "arch", "semiCircle", "slanted"],
|
|
2130
|
+
directionalAndPixel: [
|
|
2131
|
+
"triangle",
|
|
2132
|
+
"arrow",
|
|
2133
|
+
"pentagon",
|
|
2134
|
+
"fan",
|
|
2135
|
+
"ghostish",
|
|
2136
|
+
"pixelTriangle",
|
|
2137
|
+
"pixelCircle"
|
|
2138
|
+
]
|
|
2139
|
+
};
|
|
2140
|
+
function getRecommendedMorphShape(shape) {
|
|
2141
|
+
if (typeof shape === "string" && shape in MD3_SHAPE_PAIRINGS) {
|
|
2142
|
+
return MD3_SHAPE_PAIRINGS[shape];
|
|
2143
|
+
}
|
|
2144
|
+
return "circle";
|
|
2145
|
+
}
|
|
2146
|
+
|
|
1996
2147
|
// src/shapes/catalog/shape-tokens.ts
|
|
1997
2148
|
var MD3CornerRadius = {
|
|
1998
2149
|
/** 0dp — Sharp corners */
|
|
@@ -2158,6 +2309,56 @@ function pillStarVertices(width, height, numVerticesPerRadius, innerRadiusRatio,
|
|
|
2158
2309
|
return result;
|
|
2159
2310
|
}
|
|
2160
2311
|
|
|
2312
|
+
// src/shapes/morph/feature-mapping.ts
|
|
2313
|
+
function angleDiff(a1, a2) {
|
|
2314
|
+
let diff = Math.abs(a1 - a2);
|
|
2315
|
+
if (diff > Math.PI) diff = 2 * Math.PI - diff;
|
|
2316
|
+
return diff;
|
|
2317
|
+
}
|
|
2318
|
+
var TWO_PI_SQ = 4 * Math.PI * Math.PI;
|
|
2319
|
+
function featureMapper(features1, features2) {
|
|
2320
|
+
const convex1 = features1.filter((f) => f.convex);
|
|
2321
|
+
const convex2 = features2.filter((f) => f.convex);
|
|
2322
|
+
const set1 = convex1.length > 0 ? convex1 : features1;
|
|
2323
|
+
const set2 = convex2.length > 0 ? convex2 : features2;
|
|
2324
|
+
if (set1.length === 0 || set2.length === 0) {
|
|
2325
|
+
return { map: (v) => v, mapBack: (v) => v };
|
|
2326
|
+
}
|
|
2327
|
+
let bestShift = 0;
|
|
2328
|
+
let minCost = Number.MAX_VALUE;
|
|
2329
|
+
for (const f1 of set1) {
|
|
2330
|
+
for (const f2 of set2) {
|
|
2331
|
+
const shift = positiveModulo(f2.progress - f1.progress, 1);
|
|
2332
|
+
let cost = 0;
|
|
2333
|
+
for (const c1 of set1) {
|
|
2334
|
+
const expected = positiveModulo(c1.progress + shift, 1);
|
|
2335
|
+
let dMin = Number.MAX_VALUE;
|
|
2336
|
+
let matchedC2 = set2[0];
|
|
2337
|
+
for (const c2 of set2) {
|
|
2338
|
+
const diff = Math.abs(expected - c2.progress);
|
|
2339
|
+
const d = diff > 0.5 ? 1 - diff : diff;
|
|
2340
|
+
if (d < dMin) {
|
|
2341
|
+
dMin = d;
|
|
2342
|
+
matchedC2 = c2;
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
const progErrorSq = dMin * dMin * TWO_PI_SQ;
|
|
2346
|
+
const angDiff = c1.angle !== void 0 && matchedC2.angle !== void 0 ? angleDiff(c1.angle, matchedC2.angle) : 0;
|
|
2347
|
+
cost += progErrorSq + angDiff * angDiff;
|
|
2348
|
+
if (cost >= minCost) break;
|
|
2349
|
+
}
|
|
2350
|
+
if (cost < minCost) {
|
|
2351
|
+
minCost = cost;
|
|
2352
|
+
bestShift = shift;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
return {
|
|
2357
|
+
map: (v) => positiveModulo(v + bestShift, 1),
|
|
2358
|
+
mapBack: (v) => positiveModulo(v - bestShift, 1)
|
|
2359
|
+
};
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2161
2362
|
// src/shapes/morph/float-mapping.ts
|
|
2162
2363
|
function mapSegments(value, segments) {
|
|
2163
2364
|
for (const seg of segments) {
|
|
@@ -2202,32 +2403,6 @@ function createDoubleMapper(fromValues, toValues) {
|
|
|
2202
2403
|
};
|
|
2203
2404
|
}
|
|
2204
2405
|
|
|
2205
|
-
// src/shapes/morph/feature-mapping.ts
|
|
2206
|
-
function featureMapper(features1, features2) {
|
|
2207
|
-
const convex1 = features1.filter((f) => f.convex);
|
|
2208
|
-
const convex2 = features2.filter((f) => f.convex);
|
|
2209
|
-
if (convex1.length === 0 || convex2.length === 0) {
|
|
2210
|
-
return createDoubleMapper([], []);
|
|
2211
|
-
}
|
|
2212
|
-
const fromValues = [];
|
|
2213
|
-
const toValues = [];
|
|
2214
|
-
for (const f1 of convex1) {
|
|
2215
|
-
let bestDist = Number.MAX_VALUE;
|
|
2216
|
-
let bestProgress = 0;
|
|
2217
|
-
for (const f2 of convex2) {
|
|
2218
|
-
const diff = Math.abs(f1.progress - f2.progress);
|
|
2219
|
-
const dist = Math.min(diff, 1 - diff);
|
|
2220
|
-
if (dist < bestDist) {
|
|
2221
|
-
bestDist = dist;
|
|
2222
|
-
bestProgress = f2.progress;
|
|
2223
|
-
}
|
|
2224
|
-
}
|
|
2225
|
-
fromValues.push(f1.progress);
|
|
2226
|
-
toValues.push(bestProgress);
|
|
2227
|
-
}
|
|
2228
|
-
return createDoubleMapper(fromValues, toValues);
|
|
2229
|
-
}
|
|
2230
|
-
|
|
2231
2406
|
// src/shapes/morph/polygon-measure.ts
|
|
2232
2407
|
var MeasuredPolygon = class _MeasuredPolygon {
|
|
2233
2408
|
constructor(measuredCubics, features) {
|
|
@@ -2261,9 +2436,8 @@ var MeasuredPolygon = class _MeasuredPolygon {
|
|
|
2261
2436
|
const t = segLen > 0 ? (cutPoint - cutCubic.startOutlineProgress) / segLen : 0;
|
|
2262
2437
|
const [left, right] = cutCubic.cubic.split(t);
|
|
2263
2438
|
const shift = (p) => {
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
return s;
|
|
2439
|
+
const s = p - cutPoint;
|
|
2440
|
+
return s < 0 ? s + 1 : s;
|
|
2267
2441
|
};
|
|
2268
2442
|
if (!right.zeroLength()) {
|
|
2269
2443
|
result.push({
|
|
@@ -2272,9 +2446,19 @@ var MeasuredPolygon = class _MeasuredPolygon {
|
|
|
2272
2446
|
endOutlineProgress: shift(cutCubic.endOutlineProgress)
|
|
2273
2447
|
});
|
|
2274
2448
|
}
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
const mc = this.measuredCubics[
|
|
2449
|
+
const totalCubics = this.measuredCubics.length;
|
|
2450
|
+
for (let i = cutCubicIdx + 1; i < totalCubics; i++) {
|
|
2451
|
+
const mc = this.measuredCubics[i];
|
|
2452
|
+
if (!mc.cubic.zeroLength()) {
|
|
2453
|
+
result.push({
|
|
2454
|
+
cubic: mc.cubic,
|
|
2455
|
+
startOutlineProgress: shift(mc.startOutlineProgress),
|
|
2456
|
+
endOutlineProgress: shift(mc.endOutlineProgress)
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
for (let i = 0; i < cutCubicIdx; i++) {
|
|
2461
|
+
const mc = this.measuredCubics[i];
|
|
2278
2462
|
if (!mc.cubic.zeroLength()) {
|
|
2279
2463
|
result.push({
|
|
2280
2464
|
cubic: mc.cubic,
|
|
@@ -2356,7 +2540,16 @@ var MeasuredPolygon = class _MeasuredPolygon {
|
|
|
2356
2540
|
const mc = measured[Math.min(cubicIdx + middleCubicIdx, measured.length - 1)];
|
|
2357
2541
|
if (mc) {
|
|
2358
2542
|
const midProgress = (mc.startOutlineProgress + mc.endOutlineProgress) / 2;
|
|
2359
|
-
|
|
2543
|
+
const pt = mc.cubic.pointOnCurve(0.5);
|
|
2544
|
+
const angle = Math.atan2(
|
|
2545
|
+
pt.y - polygon.centerY,
|
|
2546
|
+
pt.x - polygon.centerX
|
|
2547
|
+
);
|
|
2548
|
+
featureList.push({
|
|
2549
|
+
progress: midProgress,
|
|
2550
|
+
convex: feature.convex,
|
|
2551
|
+
angle
|
|
2552
|
+
});
|
|
2360
2553
|
}
|
|
2361
2554
|
}
|
|
2362
2555
|
cubicIdx += feature.cubics.length;
|
|
@@ -2366,18 +2559,30 @@ var MeasuredPolygon = class _MeasuredPolygon {
|
|
|
2366
2559
|
};
|
|
2367
2560
|
function approximateLength(cubic) {
|
|
2368
2561
|
let len = 0;
|
|
2369
|
-
|
|
2562
|
+
const p = cubic.points;
|
|
2563
|
+
let prevX = p[0];
|
|
2564
|
+
let prevY = p[1];
|
|
2370
2565
|
const steps = 4;
|
|
2371
2566
|
for (let i = 1; i <= steps; i++) {
|
|
2372
|
-
const
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2567
|
+
const t = i / steps;
|
|
2568
|
+
const u = 1 - t;
|
|
2569
|
+
const uu = u * u;
|
|
2570
|
+
const tt = t * t;
|
|
2571
|
+
const uuu = uu * u;
|
|
2572
|
+
const ttt = tt * t;
|
|
2573
|
+
const c0 = 3 * t * uu;
|
|
2574
|
+
const c1 = 3 * tt * u;
|
|
2575
|
+
const px = p[0] * uuu + p[2] * c0 + p[4] * c1 + p[6] * ttt;
|
|
2576
|
+
const py = p[1] * uuu + p[3] * c0 + p[5] * c1 + p[7] * ttt;
|
|
2577
|
+
len += distance(px - prevX, py - prevY);
|
|
2578
|
+
prevX = px;
|
|
2579
|
+
prevY = py;
|
|
2376
2580
|
}
|
|
2377
2581
|
return len;
|
|
2378
2582
|
}
|
|
2379
2583
|
|
|
2380
2584
|
// src/shapes/morph/morph.ts
|
|
2585
|
+
var morphMatchCache = /* @__PURE__ */ new WeakMap();
|
|
2381
2586
|
var Morph = class _Morph {
|
|
2382
2587
|
/**
|
|
2383
2588
|
* Creates a Morph between two shapes.
|
|
@@ -2393,23 +2598,79 @@ var Morph = class _Morph {
|
|
|
2393
2598
|
* Returns the interpolated shape at the given progress as a list of Cubics.
|
|
2394
2599
|
*
|
|
2395
2600
|
* Note: This allocates a new list. For performance-critical animation loops,
|
|
2396
|
-
* use {@link forEachCubic} instead.
|
|
2601
|
+
* use {@link toSvgPath} or {@link forEachCubic} instead.
|
|
2397
2602
|
*
|
|
2398
2603
|
* @param progress - Value in [0, 1]. 0 = start shape, 1 = end shape.
|
|
2399
2604
|
* Values outside [0, 1] produce exaggerated shapes (useful for bounce/overshoot).
|
|
2400
2605
|
* @returns List of interpolated Cubic curves forming the morphed shape
|
|
2401
2606
|
*/
|
|
2402
2607
|
asCubics(progress) {
|
|
2403
|
-
const
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2608
|
+
const match = this.morphMatch;
|
|
2609
|
+
const len = match.length;
|
|
2610
|
+
const result = new Array(len);
|
|
2611
|
+
for (let i = 0; i < len; i++) {
|
|
2612
|
+
const [c1, c2] = match[i];
|
|
2613
|
+
const p1 = c1.points;
|
|
2614
|
+
const p2 = c2.points;
|
|
2615
|
+
result[i] = new Cubic([
|
|
2616
|
+
p1[0] + (p2[0] - p1[0]) * progress,
|
|
2617
|
+
p1[1] + (p2[1] - p1[1]) * progress,
|
|
2618
|
+
p1[2] + (p2[2] - p1[2]) * progress,
|
|
2619
|
+
p1[3] + (p2[3] - p1[3]) * progress,
|
|
2620
|
+
p1[4] + (p2[4] - p1[4]) * progress,
|
|
2621
|
+
p1[5] + (p2[5] - p1[5]) * progress,
|
|
2622
|
+
p1[6] + (p2[6] - p1[6]) * progress,
|
|
2623
|
+
p1[7] + (p2[7] - p1[7]) * progress
|
|
2624
|
+
]);
|
|
2410
2625
|
}
|
|
2411
2626
|
return result;
|
|
2412
2627
|
}
|
|
2628
|
+
/**
|
|
2629
|
+
* Returns an SVG path data string for the morphed shape at the given progress.
|
|
2630
|
+
* Directly evaluates coordinates without allocating intermediate Cubic objects.
|
|
2631
|
+
*
|
|
2632
|
+
* @param progress - Value in [0, 1]
|
|
2633
|
+
* @param width - Target pixel width (default: 1)
|
|
2634
|
+
* @param height - Target pixel height (default: 1)
|
|
2635
|
+
*/
|
|
2636
|
+
toSvgPath(progress, width = 1, height = 1) {
|
|
2637
|
+
const match = this.morphMatch;
|
|
2638
|
+
const len = match.length;
|
|
2639
|
+
if (len === 0) return "";
|
|
2640
|
+
const paddingX = width * 0.015;
|
|
2641
|
+
const paddingY = height * 0.015;
|
|
2642
|
+
const sx = width - 2 * paddingX;
|
|
2643
|
+
const sy = height - 2 * paddingY;
|
|
2644
|
+
const [firstC1, firstC2] = match[0];
|
|
2645
|
+
const f1 = firstC1.points;
|
|
2646
|
+
const f2 = firstC2.points;
|
|
2647
|
+
const a0x = f1[0] + (f2[0] - f1[0]) * progress;
|
|
2648
|
+
const a0y = f1[1] + (f2[1] - f1[1]) * progress;
|
|
2649
|
+
let d = `M ${formatCoordinate(paddingX + a0x * sx)} ${formatCoordinate(paddingY + sy * (1 - a0y))}`;
|
|
2650
|
+
for (let i = 0; i < len; i++) {
|
|
2651
|
+
const [c1, c2] = match[i];
|
|
2652
|
+
const p1 = c1.points;
|
|
2653
|
+
const p2 = c2.points;
|
|
2654
|
+
const c0x = p1[2] + (p2[2] - p1[2]) * progress;
|
|
2655
|
+
const c0y = p1[3] + (p2[3] - p1[3]) * progress;
|
|
2656
|
+
const c1x = p1[4] + (p2[4] - p1[4]) * progress;
|
|
2657
|
+
const c1y = p1[5] + (p2[5] - p1[5]) * progress;
|
|
2658
|
+
const a1x = p1[6] + (p2[6] - p1[6]) * progress;
|
|
2659
|
+
const a1y = p1[7] + (p2[7] - p1[7]) * progress;
|
|
2660
|
+
d += ` C ${formatCoordinate(paddingX + c0x * sx)} ${formatCoordinate(paddingY + sy * (1 - c0y))},${formatCoordinate(paddingX + c1x * sx)} ${formatCoordinate(paddingY + sy * (1 - c1y))},${formatCoordinate(paddingX + a1x * sx)} ${formatCoordinate(paddingY + sy * (1 - a1y))}`;
|
|
2661
|
+
}
|
|
2662
|
+
return `${d} Z`;
|
|
2663
|
+
}
|
|
2664
|
+
/**
|
|
2665
|
+
* Returns a CSS clip-path value for the morphed shape at the given progress.
|
|
2666
|
+
*
|
|
2667
|
+
* @param progress - Value in [0, 1]
|
|
2668
|
+
* @param width - Element width in pixels
|
|
2669
|
+
* @param height - Element height in pixels
|
|
2670
|
+
*/
|
|
2671
|
+
toClipPath(progress, width, height) {
|
|
2672
|
+
return `path('${this.toSvgPath(progress, width, height)}')`;
|
|
2673
|
+
}
|
|
2413
2674
|
/**
|
|
2414
2675
|
* Iterates over the morphed cubics without allocating new Cubic instances.
|
|
2415
2676
|
* Reuses a single MutableCubic for each callback invocation.
|
|
@@ -2483,8 +2744,15 @@ var Morph = class _Morph {
|
|
|
2483
2744
|
* @internal
|
|
2484
2745
|
*/
|
|
2485
2746
|
static match(p1, p2) {
|
|
2486
|
-
|
|
2487
|
-
|
|
2747
|
+
let cachedMap = morphMatchCache.get(p1);
|
|
2748
|
+
if (cachedMap) {
|
|
2749
|
+
const cached = cachedMap.get(p2);
|
|
2750
|
+
if (cached) return cached;
|
|
2751
|
+
}
|
|
2752
|
+
const poly1 = p1.isClockwise() ? p1 : p1.reversed();
|
|
2753
|
+
const poly2 = p2.isClockwise() ? p2 : p2.reversed();
|
|
2754
|
+
const measured1 = MeasuredPolygon.measurePolygon(poly1);
|
|
2755
|
+
const measured2 = MeasuredPolygon.measurePolygon(poly2);
|
|
2488
2756
|
const doubleMapper = featureMapper(measured1.features, measured2.features);
|
|
2489
2757
|
const polygon2CutPoint = doubleMapper.map(0);
|
|
2490
2758
|
const bs1 = measured1;
|
|
@@ -2495,11 +2763,12 @@ var Morph = class _Morph {
|
|
|
2495
2763
|
let b2 = bs2.getOrNull(i2++);
|
|
2496
2764
|
while (b1 !== null && b2 !== null) {
|
|
2497
2765
|
const b1a = i1 >= bs1.size ? 1 : b1.endOutlineProgress;
|
|
2498
|
-
const
|
|
2499
|
-
const b2a = doubleMapper.mapBack(b2aRaw);
|
|
2766
|
+
const b2a = i2 >= bs2.size ? 1 : b2.endOutlineProgress;
|
|
2500
2767
|
const minb = Math.min(b1a, b2a);
|
|
2501
|
-
let seg1 = b1
|
|
2502
|
-
let
|
|
2768
|
+
let seg1 = b1;
|
|
2769
|
+
let newb1 = bs1.getOrNull(i1);
|
|
2770
|
+
let seg2 = b2;
|
|
2771
|
+
let newb2 = bs2.getOrNull(i2);
|
|
2503
2772
|
if (b1a > minb + ANGLE_EPSILON) {
|
|
2504
2773
|
const [cut, rest] = bs1.cutAtProgress(b1, minb);
|
|
2505
2774
|
seg1 = cut;
|
|
@@ -2508,11 +2777,7 @@ var Morph = class _Morph {
|
|
|
2508
2777
|
i1++;
|
|
2509
2778
|
}
|
|
2510
2779
|
if (b2a > minb + ANGLE_EPSILON) {
|
|
2511
|
-
const
|
|
2512
|
-
doubleMapper.map(minb) - polygon2CutPoint,
|
|
2513
|
-
1
|
|
2514
|
-
);
|
|
2515
|
-
const [cut, rest] = bs2.cutAtProgress(b2, targetProgress);
|
|
2780
|
+
const [cut, rest] = bs2.cutAtProgress(b2, minb);
|
|
2516
2781
|
seg2 = cut;
|
|
2517
2782
|
newb2 = rest;
|
|
2518
2783
|
} else {
|
|
@@ -2522,44 +2787,83 @@ var Morph = class _Morph {
|
|
|
2522
2787
|
b1 = newb1;
|
|
2523
2788
|
b2 = newb2;
|
|
2524
2789
|
}
|
|
2790
|
+
if (!cachedMap) {
|
|
2791
|
+
cachedMap = /* @__PURE__ */ new WeakMap();
|
|
2792
|
+
morphMatchCache.set(p1, cachedMap);
|
|
2793
|
+
}
|
|
2794
|
+
cachedMap.set(p2, result);
|
|
2525
2795
|
return result;
|
|
2526
2796
|
}
|
|
2527
2797
|
};
|
|
2528
2798
|
|
|
2529
2799
|
// src/shapes/render/shape-rendering.ts
|
|
2530
2800
|
function toSvgPath(cubics, width = 1, height = 1) {
|
|
2531
|
-
|
|
2801
|
+
const len = cubics.length;
|
|
2802
|
+
if (len === 0) return "";
|
|
2532
2803
|
const paddingX = width * 0.015;
|
|
2533
2804
|
const paddingY = height * 0.015;
|
|
2534
2805
|
const sx = width - 2 * paddingX;
|
|
2535
2806
|
const sy = height - 2 * paddingY;
|
|
2536
|
-
const
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
parts.push(
|
|
2542
|
-
`C ${fmt(paddingX + c.control0X * sx)} ${fmt(flipY(c.control0Y))},${fmt(paddingX + c.control1X * sx)} ${fmt(flipY(c.control1Y))},${fmt(paddingX + c.anchor1X * sx)} ${fmt(flipY(c.anchor1Y))}`
|
|
2543
|
-
);
|
|
2807
|
+
const c0 = cubics[0];
|
|
2808
|
+
let d = `M ${formatCoordinate(paddingX + c0.anchor0X * sx)} ${formatCoordinate(paddingY + sy * (1 - c0.anchor0Y))}`;
|
|
2809
|
+
for (let i = 0; i < len; i++) {
|
|
2810
|
+
const c = cubics[i];
|
|
2811
|
+
d += ` C ${formatCoordinate(paddingX + c.control0X * sx)} ${formatCoordinate(paddingY + sy * (1 - c.control0Y))},${formatCoordinate(paddingX + c.control1X * sx)} ${formatCoordinate(paddingY + sy * (1 - c.control1Y))},${formatCoordinate(paddingX + c.anchor1X * sx)} ${formatCoordinate(paddingY + sy * (1 - c.anchor1Y))}`;
|
|
2544
2812
|
}
|
|
2545
|
-
|
|
2546
|
-
return parts.join(" ");
|
|
2813
|
+
return `${d} Z`;
|
|
2547
2814
|
}
|
|
2548
2815
|
function toClipPath(polygon, width, height) {
|
|
2549
2816
|
const pathData = toSvgPath(polygon.cubics, width, height);
|
|
2550
2817
|
return `path('${pathData}')`;
|
|
2551
2818
|
}
|
|
2552
2819
|
function interpolatePath(morph, progress, width, height) {
|
|
2553
|
-
|
|
2554
|
-
return toClipPath({ cubics }, width, height);
|
|
2555
|
-
}
|
|
2556
|
-
function fmt(v) {
|
|
2557
|
-
return Number(v.toFixed(4)).toString();
|
|
2820
|
+
return morph.toClipPath(progress, width, height);
|
|
2558
2821
|
}
|
|
2822
|
+
|
|
2823
|
+
// src/ui/shape-media/animation-utils.ts
|
|
2559
2824
|
function prefersReducedMotion() {
|
|
2560
|
-
if (typeof window === "undefined")
|
|
2825
|
+
if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
|
|
2826
|
+
return false;
|
|
2827
|
+
}
|
|
2561
2828
|
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
2562
2829
|
}
|
|
2830
|
+
function easeCubicInOut(t) {
|
|
2831
|
+
return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
2832
|
+
}
|
|
2833
|
+
function cubicBezier(x1, y1, x2, y2) {
|
|
2834
|
+
return (t) => {
|
|
2835
|
+
if (t === 0 || t === 1) return t;
|
|
2836
|
+
let tParam = t;
|
|
2837
|
+
for (let i = 0; i < 8; i++) {
|
|
2838
|
+
const x = 3 * (1 - tParam) ** 2 * tParam * x1 + 3 * (1 - tParam) * tParam ** 2 * x2 + tParam ** 3;
|
|
2839
|
+
const dx = 3 * (1 - tParam) ** 2 * x1 + 6 * (1 - tParam) * tParam * (x2 - x1) + 3 * tParam ** 2 * (1 - x2);
|
|
2840
|
+
if (Math.abs(dx) < 1e-6) break;
|
|
2841
|
+
tParam -= (x - t) / dx;
|
|
2842
|
+
}
|
|
2843
|
+
return 3 * (1 - tParam) ** 2 * tParam * y1 + 3 * (1 - tParam) * tParam ** 2 * y2 + tParam ** 3;
|
|
2844
|
+
};
|
|
2845
|
+
}
|
|
2846
|
+
function getEasingFunction(easing) {
|
|
2847
|
+
if (Array.isArray(easing)) {
|
|
2848
|
+
if (easing.length === 4) {
|
|
2849
|
+
const [x1, y1, x2, y2] = easing;
|
|
2850
|
+
return cubicBezier(x1, y1, x2, y2);
|
|
2851
|
+
}
|
|
2852
|
+
return easeCubicInOut;
|
|
2853
|
+
}
|
|
2854
|
+
switch (easing) {
|
|
2855
|
+
case "linear":
|
|
2856
|
+
return (t) => t;
|
|
2857
|
+
case "ease-in":
|
|
2858
|
+
return (t) => t * t * t;
|
|
2859
|
+
case "ease-out":
|
|
2860
|
+
return (t) => 1 - (1 - t) ** 3;
|
|
2861
|
+
default:
|
|
2862
|
+
return easeCubicInOut;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
// src/ui/shape-media/useShapeMorph.ts
|
|
2563
2867
|
function useShapeMorph({
|
|
2564
2868
|
shape,
|
|
2565
2869
|
morphTo,
|
|
@@ -2574,10 +2878,16 @@ function useShapeMorph({
|
|
|
2574
2878
|
const duration = (_a = morphOptions == null ? void 0 : morphOptions.duration) != null ? _a : 0.3;
|
|
2575
2879
|
const easing = (_b = morphOptions == null ? void 0 : morphOptions.easing) != null ? _b : "ease-in-out";
|
|
2576
2880
|
const easingFunction = react.useMemo(() => getEasingFunction(easing), [easing]);
|
|
2881
|
+
const resolvedTarget = react.useMemo(() => {
|
|
2882
|
+
if (morphTo === "auto") {
|
|
2883
|
+
return getRecommendedMorphShape(shape);
|
|
2884
|
+
}
|
|
2885
|
+
return morphTo;
|
|
2886
|
+
}, [morphTo, shape]);
|
|
2577
2887
|
const startShape = react.useMemo(() => resolveShape(shape), [shape]);
|
|
2578
2888
|
const endShape = react.useMemo(
|
|
2579
|
-
() =>
|
|
2580
|
-
[
|
|
2889
|
+
() => resolvedTarget ? resolveShape(resolvedTarget) : null,
|
|
2890
|
+
[resolvedTarget]
|
|
2581
2891
|
);
|
|
2582
2892
|
const morph = react.useMemo(
|
|
2583
2893
|
() => endShape ? new Morph(startShape, endShape) : null,
|
|
@@ -2592,7 +2902,45 @@ function useShapeMorph({
|
|
|
2592
2902
|
const startProgressRef = react.useRef(0);
|
|
2593
2903
|
const targetProgressRef = react.useRef(0);
|
|
2594
2904
|
const currentProgressRef = react.useRef(0);
|
|
2905
|
+
const prevShapeRef = react.useRef(shape);
|
|
2906
|
+
react.useEffect(() => {
|
|
2907
|
+
if (prevShapeRef.current === shape) return;
|
|
2908
|
+
const oldShape = prevShapeRef.current;
|
|
2909
|
+
prevShapeRef.current = shape;
|
|
2910
|
+
if (disabled || prefersReducedMotion()) {
|
|
2911
|
+
setClipPath(toClipPath(resolveShape(shape), width, height));
|
|
2912
|
+
return;
|
|
2913
|
+
}
|
|
2914
|
+
try {
|
|
2915
|
+
const transitionMorph = new Morph(
|
|
2916
|
+
resolveShape(oldShape),
|
|
2917
|
+
resolveShape(shape)
|
|
2918
|
+
);
|
|
2919
|
+
const start = performance.now();
|
|
2920
|
+
const durationMs = duration * 1e3;
|
|
2921
|
+
const animateTransition = (timestamp) => {
|
|
2922
|
+
const elapsed = timestamp - start;
|
|
2923
|
+
const rawT = Math.min(elapsed / durationMs, 1);
|
|
2924
|
+
const ease = easingFunction(rawT);
|
|
2925
|
+
setClipPath(interpolatePath(transitionMorph, ease, width, height));
|
|
2926
|
+
if (rawT < 1) {
|
|
2927
|
+
animRef.current = requestAnimationFrame(animateTransition);
|
|
2928
|
+
} else {
|
|
2929
|
+
animRef.current = null;
|
|
2930
|
+
}
|
|
2931
|
+
};
|
|
2932
|
+
if (animRef.current !== null) cancelAnimationFrame(animRef.current);
|
|
2933
|
+
animRef.current = requestAnimationFrame(animateTransition);
|
|
2934
|
+
} catch (e) {
|
|
2935
|
+
setClipPath(toClipPath(resolveShape(shape), width, height));
|
|
2936
|
+
}
|
|
2937
|
+
}, [shape, disabled, duration, easingFunction, width, height]);
|
|
2938
|
+
const prevDimsRef = react.useRef({ width, height, startShape, morph });
|
|
2595
2939
|
react.useEffect(() => {
|
|
2940
|
+
if (prevDimsRef.current.width === width && prevDimsRef.current.height === height && prevDimsRef.current.startShape === startShape && prevDimsRef.current.morph === morph) {
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
prevDimsRef.current = { width, height, startShape, morph };
|
|
2596
2944
|
if (morph) {
|
|
2597
2945
|
setClipPath(
|
|
2598
2946
|
interpolatePath(morph, currentProgressRef.current, width, height)
|
|
@@ -2686,41 +3034,6 @@ function useShapeMorph({
|
|
|
2686
3034
|
}, [morphTo, disabled, morphOn, activate, deactivate, isActive]);
|
|
2687
3035
|
return { clipPath, isActive, activate, deactivate, setProgress, handlers };
|
|
2688
3036
|
}
|
|
2689
|
-
function easeCubicInOut(t) {
|
|
2690
|
-
return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
2691
|
-
}
|
|
2692
|
-
function cubicBezier(x1, y1, x2, y2) {
|
|
2693
|
-
return (t) => {
|
|
2694
|
-
if (t === 0 || t === 1) return t;
|
|
2695
|
-
let tParam = t;
|
|
2696
|
-
for (let i = 0; i < 8; i++) {
|
|
2697
|
-
const x = 3 * (1 - tParam) ** 2 * tParam * x1 + 3 * (1 - tParam) * tParam ** 2 * x2 + tParam ** 3;
|
|
2698
|
-
const dx = 3 * (1 - tParam) ** 2 * x1 + 6 * (1 - tParam) * tParam * (x2 - x1) + 3 * tParam ** 2 * (1 - x2);
|
|
2699
|
-
if (Math.abs(dx) < 1e-6) break;
|
|
2700
|
-
tParam -= (x - t) / dx;
|
|
2701
|
-
}
|
|
2702
|
-
return 3 * (1 - tParam) ** 2 * tParam * y1 + 3 * (1 - tParam) * tParam ** 2 * y2 + tParam ** 3;
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
function getEasingFunction(easing) {
|
|
2706
|
-
if (Array.isArray(easing)) {
|
|
2707
|
-
if (easing.length === 4) {
|
|
2708
|
-
const [x1, y1, x2, y2] = easing;
|
|
2709
|
-
return cubicBezier(x1, y1, x2, y2);
|
|
2710
|
-
}
|
|
2711
|
-
return easeCubicInOut;
|
|
2712
|
-
}
|
|
2713
|
-
switch (easing) {
|
|
2714
|
-
case "linear":
|
|
2715
|
-
return (t) => t;
|
|
2716
|
-
case "ease-in":
|
|
2717
|
-
return (t) => t * t * t;
|
|
2718
|
-
case "ease-out":
|
|
2719
|
-
return (t) => 1 - (1 - t) ** 3;
|
|
2720
|
-
default:
|
|
2721
|
-
return easeCubicInOut;
|
|
2722
|
-
}
|
|
2723
|
-
}
|
|
2724
3037
|
function ShapeMedia({
|
|
2725
3038
|
shape,
|
|
2726
3039
|
morphTo,
|
|
@@ -2848,10 +3161,14 @@ function ShapeSvg({
|
|
|
2848
3161
|
style,
|
|
2849
3162
|
"aria-label": ariaLabel
|
|
2850
3163
|
}) {
|
|
3164
|
+
const resolvedTarget = react.useMemo(() => {
|
|
3165
|
+
if (morphTo === "auto") return getRecommendedMorphShape(shape);
|
|
3166
|
+
return morphTo;
|
|
3167
|
+
}, [morphTo, shape]);
|
|
2851
3168
|
const startPolygon = react.useMemo(() => resolveShape(shape), [shape]);
|
|
2852
3169
|
const endPolygon = react.useMemo(
|
|
2853
|
-
() =>
|
|
2854
|
-
[
|
|
3170
|
+
() => resolvedTarget ? resolveShape(resolvedTarget) : null,
|
|
3171
|
+
[resolvedTarget]
|
|
2855
3172
|
);
|
|
2856
3173
|
const morph = react.useMemo(
|
|
2857
3174
|
() => endPolygon ? new Morph(startPolygon, endPolygon) : null,
|
|
@@ -2885,6 +3202,176 @@ function ShapeSvg({
|
|
|
2885
3202
|
}
|
|
2886
3203
|
);
|
|
2887
3204
|
}
|
|
3205
|
+
function useShapeSequenceMorph({
|
|
3206
|
+
shapes,
|
|
3207
|
+
duration = 0.6,
|
|
3208
|
+
interval = 2,
|
|
3209
|
+
autoplay = false,
|
|
3210
|
+
loop = true,
|
|
3211
|
+
morphOptions,
|
|
3212
|
+
width,
|
|
3213
|
+
height,
|
|
3214
|
+
disabled = false
|
|
3215
|
+
}) {
|
|
3216
|
+
var _a;
|
|
3217
|
+
const resolvedDuration = (_a = morphOptions == null ? void 0 : morphOptions.duration) != null ? _a : duration;
|
|
3218
|
+
const [currentIndex, setCurrentIndex] = react.useState(0);
|
|
3219
|
+
const [nextIndex, setNextIndex] = react.useState(() => shapes.length > 1 ? 1 : 0);
|
|
3220
|
+
const [progress, setProgress] = react.useState(0);
|
|
3221
|
+
const [isPlaying, setIsPlaying] = react.useState(autoplay);
|
|
3222
|
+
const animRef = react.useRef(null);
|
|
3223
|
+
const timerRef = react.useRef(null);
|
|
3224
|
+
const startTimeRef = react.useRef(0);
|
|
3225
|
+
const isTransitioningRef = react.useRef(false);
|
|
3226
|
+
const numShapes = shapes.length;
|
|
3227
|
+
const currentShape = react.useMemo(() => {
|
|
3228
|
+
if (numShapes === 0) return resolveShape("circle");
|
|
3229
|
+
const safeIdx = (currentIndex % numShapes + numShapes) % numShapes;
|
|
3230
|
+
return resolveShape(shapes[safeIdx]);
|
|
3231
|
+
}, [shapes, currentIndex, numShapes]);
|
|
3232
|
+
const targetShape = react.useMemo(() => {
|
|
3233
|
+
if (numShapes === 0) return resolveShape("circle");
|
|
3234
|
+
const safeIdx = (nextIndex % numShapes + numShapes) % numShapes;
|
|
3235
|
+
return resolveShape(shapes[safeIdx]);
|
|
3236
|
+
}, [shapes, nextIndex, numShapes]);
|
|
3237
|
+
const morph = react.useMemo(() => {
|
|
3238
|
+
if (currentShape === targetShape) return null;
|
|
3239
|
+
return new Morph(currentShape, targetShape);
|
|
3240
|
+
}, [currentShape, targetShape]);
|
|
3241
|
+
const [clipPath, setClipPath] = react.useState(
|
|
3242
|
+
() => toClipPath(currentShape, width, height)
|
|
3243
|
+
);
|
|
3244
|
+
const prevDimsRef = react.useRef({ width, height, currentShape, morph, progress });
|
|
3245
|
+
react.useEffect(() => {
|
|
3246
|
+
if (prevDimsRef.current.width === width && prevDimsRef.current.height === height && prevDimsRef.current.currentShape === currentShape && prevDimsRef.current.morph === morph && prevDimsRef.current.progress === progress) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
prevDimsRef.current = { width, height, currentShape, morph, progress };
|
|
3250
|
+
if (morph && progress > 0) {
|
|
3251
|
+
setClipPath(interpolatePath(morph, progress, width, height));
|
|
3252
|
+
} else {
|
|
3253
|
+
setClipPath(toClipPath(currentShape, width, height));
|
|
3254
|
+
}
|
|
3255
|
+
}, [width, height, currentShape, morph, progress]);
|
|
3256
|
+
const transitionTo = react.useCallback(
|
|
3257
|
+
(toIndex) => {
|
|
3258
|
+
if (disabled || numShapes <= 1 || isTransitioningRef.current) return;
|
|
3259
|
+
const targetSafe = (toIndex % numShapes + numShapes) % numShapes;
|
|
3260
|
+
if (targetSafe === currentIndex) return;
|
|
3261
|
+
setNextIndex(targetSafe);
|
|
3262
|
+
if (prefersReducedMotion()) {
|
|
3263
|
+
setCurrentIndex(targetSafe);
|
|
3264
|
+
setNextIndex((targetSafe + 1) % numShapes);
|
|
3265
|
+
setProgress(0);
|
|
3266
|
+
setClipPath(
|
|
3267
|
+
toClipPath(resolveShape(shapes[targetSafe]), width, height)
|
|
3268
|
+
);
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
3271
|
+
isTransitioningRef.current = true;
|
|
3272
|
+
startTimeRef.current = performance.now();
|
|
3273
|
+
const fromPoly = resolveShape(shapes[currentIndex]);
|
|
3274
|
+
const toPoly = resolveShape(shapes[targetSafe]);
|
|
3275
|
+
const activeMorph = new Morph(fromPoly, toPoly);
|
|
3276
|
+
const animate = (timestamp) => {
|
|
3277
|
+
const elapsed = timestamp - startTimeRef.current;
|
|
3278
|
+
const durationMs = resolvedDuration * 1e3;
|
|
3279
|
+
const rawT = Math.min(elapsed / durationMs, 1);
|
|
3280
|
+
const ease = easeCubicInOut(rawT);
|
|
3281
|
+
setProgress(rawT);
|
|
3282
|
+
setClipPath(interpolatePath(activeMorph, ease, width, height));
|
|
3283
|
+
if (rawT < 1) {
|
|
3284
|
+
animRef.current = requestAnimationFrame(animate);
|
|
3285
|
+
} else {
|
|
3286
|
+
animRef.current = null;
|
|
3287
|
+
isTransitioningRef.current = false;
|
|
3288
|
+
setCurrentIndex(targetSafe);
|
|
3289
|
+
setNextIndex((targetSafe + 1) % numShapes);
|
|
3290
|
+
setProgress(0);
|
|
3291
|
+
}
|
|
3292
|
+
};
|
|
3293
|
+
if (animRef.current !== null) cancelAnimationFrame(animRef.current);
|
|
3294
|
+
animRef.current = requestAnimationFrame(animate);
|
|
3295
|
+
},
|
|
3296
|
+
[
|
|
3297
|
+
disabled,
|
|
3298
|
+
numShapes,
|
|
3299
|
+
currentIndex,
|
|
3300
|
+
shapes,
|
|
3301
|
+
resolvedDuration,
|
|
3302
|
+
width,
|
|
3303
|
+
height
|
|
3304
|
+
]
|
|
3305
|
+
);
|
|
3306
|
+
const next = react.useCallback(() => {
|
|
3307
|
+
if (numShapes <= 1) return;
|
|
3308
|
+
if (!loop && currentIndex >= numShapes - 1) return;
|
|
3309
|
+
transitionTo((currentIndex + 1) % numShapes);
|
|
3310
|
+
}, [numShapes, loop, currentIndex, transitionTo]);
|
|
3311
|
+
const prev = react.useCallback(() => {
|
|
3312
|
+
if (numShapes <= 1) return;
|
|
3313
|
+
if (!loop && currentIndex <= 0) return;
|
|
3314
|
+
transitionTo((currentIndex - 1 + numShapes) % numShapes);
|
|
3315
|
+
}, [numShapes, loop, currentIndex, transitionTo]);
|
|
3316
|
+
const goTo = react.useCallback(
|
|
3317
|
+
(index) => {
|
|
3318
|
+
transitionTo(index);
|
|
3319
|
+
},
|
|
3320
|
+
[transitionTo]
|
|
3321
|
+
);
|
|
3322
|
+
const play = react.useCallback(() => {
|
|
3323
|
+
setIsPlaying(true);
|
|
3324
|
+
}, []);
|
|
3325
|
+
const pause = react.useCallback(() => {
|
|
3326
|
+
setIsPlaying(false);
|
|
3327
|
+
if (timerRef.current !== null) {
|
|
3328
|
+
clearTimeout(timerRef.current);
|
|
3329
|
+
timerRef.current = null;
|
|
3330
|
+
}
|
|
3331
|
+
}, []);
|
|
3332
|
+
react.useEffect(() => {
|
|
3333
|
+
if (!isPlaying || disabled || numShapes <= 1) return;
|
|
3334
|
+
timerRef.current = setTimeout(() => {
|
|
3335
|
+
if (!loop && currentIndex >= numShapes - 1) {
|
|
3336
|
+
setIsPlaying(false);
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
next();
|
|
3340
|
+
}, interval * 1e3);
|
|
3341
|
+
return () => {
|
|
3342
|
+
if (timerRef.current !== null) {
|
|
3343
|
+
clearTimeout(timerRef.current);
|
|
3344
|
+
timerRef.current = null;
|
|
3345
|
+
}
|
|
3346
|
+
};
|
|
3347
|
+
}, [isPlaying, disabled, numShapes, loop, currentIndex, interval, next]);
|
|
3348
|
+
react.useEffect(() => {
|
|
3349
|
+
return () => {
|
|
3350
|
+
if (animRef.current !== null) cancelAnimationFrame(animRef.current);
|
|
3351
|
+
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
3352
|
+
};
|
|
3353
|
+
}, []);
|
|
3354
|
+
react.useEffect(() => {
|
|
3355
|
+
if (currentIndex >= shapes.length) {
|
|
3356
|
+
setCurrentIndex(0);
|
|
3357
|
+
setNextIndex(shapes.length > 1 ? 1 : 0);
|
|
3358
|
+
}
|
|
3359
|
+
}, [shapes.length, currentIndex]);
|
|
3360
|
+
const safeCurrentIndex = numShapes > 0 ? (currentIndex % numShapes + numShapes) % numShapes : 0;
|
|
3361
|
+
const safeNextIndex = numShapes > 0 ? (nextIndex % numShapes + numShapes) % numShapes : 0;
|
|
3362
|
+
return {
|
|
3363
|
+
clipPath,
|
|
3364
|
+
currentIndex: safeCurrentIndex,
|
|
3365
|
+
nextIndex: safeNextIndex,
|
|
3366
|
+
progress,
|
|
3367
|
+
isPlaying,
|
|
3368
|
+
next,
|
|
3369
|
+
prev,
|
|
3370
|
+
goTo,
|
|
3371
|
+
play,
|
|
3372
|
+
pause
|
|
3373
|
+
};
|
|
3374
|
+
}
|
|
2888
3375
|
|
|
2889
3376
|
exports.ANGLE_EPSILON = ANGLE_EPSILON;
|
|
2890
3377
|
exports.Cubic = Cubic;
|
|
@@ -2892,6 +3379,8 @@ exports.DISTANCE_EPSILON = DISTANCE_EPSILON;
|
|
|
2892
3379
|
exports.FLOAT_PI = FLOAT_PI;
|
|
2893
3380
|
exports.MD3CornerRadius = MD3CornerRadius;
|
|
2894
3381
|
exports.MD3Shapes = MD3Shapes;
|
|
3382
|
+
exports.MD3_SHAPE_FAMILIES = MD3_SHAPE_FAMILIES;
|
|
3383
|
+
exports.MD3_SHAPE_PAIRINGS = MD3_SHAPE_PAIRINGS;
|
|
2895
3384
|
exports.MeasuredPolygon = MeasuredPolygon;
|
|
2896
3385
|
exports.Morph = Morph;
|
|
2897
3386
|
exports.MutableCubic = MutableCubic;
|
|
@@ -2915,9 +3404,11 @@ exports.dividePoint = dividePoint;
|
|
|
2915
3404
|
exports.dotProduct = dotProduct;
|
|
2916
3405
|
exports.edgeFeature = edgeFeature;
|
|
2917
3406
|
exports.featureMapper = featureMapper;
|
|
3407
|
+
exports.formatCoordinate = formatCoordinate;
|
|
2918
3408
|
exports.getDirection = getDirection;
|
|
2919
3409
|
exports.getDistance = getDistance;
|
|
2920
3410
|
exports.getDistanceSquared = getDistanceSquared;
|
|
3411
|
+
exports.getRecommendedMorphShape = getRecommendedMorphShape;
|
|
2921
3412
|
exports.interpolate = interpolate;
|
|
2922
3413
|
exports.interpolatePath = interpolatePath;
|
|
2923
3414
|
exports.lerpPoint = lerpPoint;
|
|
@@ -2937,5 +3428,6 @@ exports.toSvgPath = toSvgPath;
|
|
|
2937
3428
|
exports.transformFeature = transformFeature;
|
|
2938
3429
|
exports.transformPoint = transformPoint;
|
|
2939
3430
|
exports.useShapeMorph = useShapeMorph;
|
|
3431
|
+
exports.useShapeSequenceMorph = useShapeSequenceMorph;
|
|
2940
3432
|
//# sourceMappingURL=shapes.js.map
|
|
2941
3433
|
//# sourceMappingURL=shapes.js.map
|