react-morpheus 0.1.5 → 0.1.8
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/README.md +16 -9
- package/dist/index.d.ts +12 -8
- package/dist/index.js +553 -102
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -52,7 +52,6 @@ function Example() {
|
|
|
52
52
|
|
|
53
53
|
return (
|
|
54
54
|
<Morpheus
|
|
55
|
-
direction="bottom"
|
|
56
55
|
anchor={MorphAnchor.TopMiddle}
|
|
57
56
|
expanded={expanded}
|
|
58
57
|
onClose={() => setExpanded(false)}
|
|
@@ -60,6 +59,7 @@ function Example() {
|
|
|
60
59
|
overlayOpacity={0.18}
|
|
61
60
|
overlayBlur={3}
|
|
62
61
|
overlayZIndex={1200}
|
|
62
|
+
viewportPadding={16}
|
|
63
63
|
spring={morphSpringPresets.smooth}
|
|
64
64
|
collapsedContent={source}
|
|
65
65
|
expandedContent={
|
|
@@ -85,8 +85,7 @@ should run around the closing animation.
|
|
|
85
85
|
|
|
86
86
|
| Prop | Type | Required | Notes |
|
|
87
87
|
| --- | --- | --- | --- |
|
|
88
|
-
| `
|
|
89
|
-
| `anchor` | `MorphAnchor` | Optional | The edge or point that stays visually anchored. |
|
|
88
|
+
| `anchor` | `MorphAnchor` | Required | The edge or point that stays visually anchored. |
|
|
90
89
|
| `expanded` | `boolean` | Required | Controlled open state owned by your app. |
|
|
91
90
|
| `onClose` | `() => void` | Optional | Called when the overlay requests closing. Use it to update your controlled open state. |
|
|
92
91
|
| `beforeClose` | `() => void` | Optional | Called when the closing animation starts, after `expanded` has changed to `false`. Useful for close-intent side effects. |
|
|
@@ -94,6 +93,9 @@ should run around the closing animation.
|
|
|
94
93
|
| `collapsedContent` | `ReactNode` | Required | The source surface Morpheus measures, renders, and uses as the opener. |
|
|
95
94
|
| `expandedContent` | `ReactNode` | Required | The destination surface Morpheus measures and animates into. |
|
|
96
95
|
| `className` | `string` | Optional | Classes applied to the root wrapper. Useful for block layout and responsive width constraints. |
|
|
96
|
+
| `shellClassName` | `string` | Optional | Classes applied directly to the animated shell, including when it is portalled. |
|
|
97
|
+
| `portalContainer` | `HTMLElement \| null` | Optional | Portal root for the expanded surface. Defaults to `document.body` after mount; pass `null` for legacy inline rendering. |
|
|
98
|
+
| `viewportPadding` | `number` | Optional | Minimum viewport gutter for the expanded shell. Defaults to `16`. |
|
|
97
99
|
| `overlayColor` | `string` | Optional | Backdrop color shown while expanded. |
|
|
98
100
|
| `overlayOpacity` | `number` | Optional | Backdrop opacity from `0` to `1`. |
|
|
99
101
|
| `overlayBlur` | `number` | Optional | Backdrop blur in pixels. |
|
|
@@ -116,14 +118,19 @@ upward; middle anchors feel better for centered inspectors or command surfaces.
|
|
|
116
118
|
|
|
117
119
|
### Parent Container Overflow
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
121
|
+
Expanded surfaces and their overlays portal to `document.body` by default, so
|
|
122
|
+
they can escape clipped, transformed, and independently stacked ancestors. The
|
|
123
|
+
collapsed source remains in its original layout position. Pass another
|
|
124
|
+
document-level `portalContainer` when needed, or `null` to opt into the legacy
|
|
125
|
+
inline behavior.
|
|
126
|
+
|
|
127
|
+
Targets are clamped to the viewport using `viewportPadding`. When their natural
|
|
128
|
+
size is larger than the available viewport, the shell stays bounded and becomes
|
|
129
|
+
scrollable after the opening animation settles.
|
|
123
130
|
|
|
124
131
|
### Stacking
|
|
125
132
|
|
|
126
|
-
`overlayZIndex` controls the expanded
|
|
133
|
+
`overlayZIndex` controls the portalled expanded surface. The overlay renders one
|
|
127
134
|
layer below it so apps can move the whole morphing stack above their own chrome.
|
|
128
135
|
Without this prop, Morpheus uses surface `1000` and overlay `999`.
|
|
129
136
|
|
|
@@ -131,7 +138,7 @@ Without this prop, Morpheus uses surface `1000` and overlay `999`.
|
|
|
131
138
|
|
|
132
139
|
`example.html` is a small local playground for trying Morpheus in a browser
|
|
133
140
|
without setting up an app shell. Use it to check the default interaction,
|
|
134
|
-
|
|
141
|
+
anchors and overlay behavior while changing the component.
|
|
135
142
|
|
|
136
143
|
### Scripts
|
|
137
144
|
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import * as react from 'react';
|
|
|
2
2
|
import { ReactNode, RefObject } from 'react';
|
|
3
3
|
import { Transition } from 'framer-motion';
|
|
4
4
|
|
|
5
|
-
type MorphDirection = "top" | "right" | "bottom" | "left";
|
|
6
5
|
declare enum MorphAnchor {
|
|
7
6
|
LeftTop = "left-top",
|
|
8
7
|
LeftMiddle = "left-middle",
|
|
@@ -18,6 +17,10 @@ type PanelSize = {
|
|
|
18
17
|
width: number;
|
|
19
18
|
height: number;
|
|
20
19
|
};
|
|
20
|
+
type PanelRect = PanelSize & {
|
|
21
|
+
left: number;
|
|
22
|
+
top: number;
|
|
23
|
+
};
|
|
21
24
|
type ContentOffset = {
|
|
22
25
|
x: number;
|
|
23
26
|
y: number;
|
|
@@ -49,8 +52,7 @@ type MorphMeasurements = {
|
|
|
49
52
|
};
|
|
50
53
|
type MorphMotionInput = {
|
|
51
54
|
expanded: boolean;
|
|
52
|
-
|
|
53
|
-
anchor?: MorphAnchor | undefined;
|
|
55
|
+
anchor: MorphAnchor;
|
|
54
56
|
spring: Transition;
|
|
55
57
|
animationEnabled: boolean;
|
|
56
58
|
collapsed: MorphMeasuredSurface;
|
|
@@ -74,8 +76,7 @@ type MorphMotionState = {
|
|
|
74
76
|
visualStyle: PanelVisualStyle;
|
|
75
77
|
};
|
|
76
78
|
type MorphProps = {
|
|
77
|
-
|
|
78
|
-
anchor?: MorphAnchor | undefined;
|
|
79
|
+
anchor: MorphAnchor;
|
|
79
80
|
expanded: boolean;
|
|
80
81
|
onClose?: (() => void) | undefined;
|
|
81
82
|
beforeClose?: (() => void) | undefined;
|
|
@@ -83,6 +84,9 @@ type MorphProps = {
|
|
|
83
84
|
collapsedContent: ReactNode;
|
|
84
85
|
expandedContent: ReactNode;
|
|
85
86
|
className?: string;
|
|
87
|
+
shellClassName?: string;
|
|
88
|
+
portalContainer?: HTMLElement | null;
|
|
89
|
+
viewportPadding?: number;
|
|
86
90
|
overlayColor?: string;
|
|
87
91
|
overlayOpacity?: number;
|
|
88
92
|
overlayBlur?: number;
|
|
@@ -114,6 +118,7 @@ type MorphShellProps = {
|
|
|
114
118
|
onMorphComplete: () => void;
|
|
115
119
|
onMorphStart: () => void;
|
|
116
120
|
children: ReactNode;
|
|
121
|
+
className?: string | undefined;
|
|
117
122
|
};
|
|
118
123
|
type MorphContentLayersProps = {
|
|
119
124
|
expanded: boolean;
|
|
@@ -136,9 +141,8 @@ type MorphLayerInteractivityProps = {
|
|
|
136
141
|
inert?: boolean;
|
|
137
142
|
};
|
|
138
143
|
|
|
139
|
-
declare function Morpheus(
|
|
144
|
+
declare function Morpheus(props: MorphProps): react.JSX.Element;
|
|
140
145
|
|
|
141
|
-
declare const defaultAnchorByDirection: Record<MorphDirection, MorphAnchor>;
|
|
142
146
|
declare const morphSpringPresets: {
|
|
143
147
|
balanced: {
|
|
144
148
|
type: "spring";
|
|
@@ -172,4 +176,4 @@ declare const morphSpringPresets: {
|
|
|
172
176
|
};
|
|
173
177
|
};
|
|
174
178
|
|
|
175
|
-
export { type ContentOffset, MorphAnchor, type MorphContentLayersProps, type
|
|
179
|
+
export { type ContentOffset, MorphAnchor, type MorphContentLayersProps, type MorphLayerInteractivityProps, type MorphMeasuredSurface, type MorphMeasurementNodesProps, type MorphMeasurements, type MorphMotionInput, type MorphMotionState, type MorphOverlayProps, type MorphProps, type MorphShellProps, type MorphSpringPreset, type MorphSurfaceSnapshot, Morpheus, type PanelPosition, type PanelRect, type PanelSize, type PanelVisualStyle, Morpheus as default, morphSpringPresets };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
// src/component.tsx
|
|
2
2
|
import { motion as motion2 } from "framer-motion";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useLayoutEffect as useLayoutEffect2,
|
|
9
|
+
useRef as useRef2,
|
|
10
|
+
useState as useState2
|
|
11
|
+
} from "react";
|
|
12
|
+
import { createPortal } from "react-dom";
|
|
4
13
|
|
|
5
14
|
// src/measurement.ts
|
|
6
15
|
import { useLayoutEffect, useRef, useState } from "react";
|
|
@@ -40,8 +49,14 @@ function getSurfaceVisualStyle(element) {
|
|
|
40
49
|
}
|
|
41
50
|
function measureSurface(element) {
|
|
42
51
|
const measuredElement = getMeasuredElement(element);
|
|
43
|
-
const width =
|
|
44
|
-
|
|
52
|
+
const width = Math.max(
|
|
53
|
+
measuredElement.offsetWidth,
|
|
54
|
+
measuredElement.scrollWidth
|
|
55
|
+
);
|
|
56
|
+
const height = Math.max(
|
|
57
|
+
measuredElement.offsetHeight,
|
|
58
|
+
measuredElement.scrollHeight
|
|
59
|
+
);
|
|
45
60
|
if (width === 0 || height === 0) {
|
|
46
61
|
return null;
|
|
47
62
|
}
|
|
@@ -115,12 +130,6 @@ var MorphAnchor = /* @__PURE__ */ ((MorphAnchor2) => {
|
|
|
115
130
|
})(MorphAnchor || {});
|
|
116
131
|
|
|
117
132
|
// src/motion.ts
|
|
118
|
-
var defaultAnchorByDirection = {
|
|
119
|
-
top: "bottom-middle" /* BottomMiddle */,
|
|
120
|
-
right: "left-middle" /* LeftMiddle */,
|
|
121
|
-
bottom: "top-middle" /* TopMiddle */,
|
|
122
|
-
left: "right-middle" /* RightMiddle */
|
|
123
|
-
};
|
|
124
133
|
var anchorTransformOrigins = {
|
|
125
134
|
["left-top" /* LeftTop */]: "left top",
|
|
126
135
|
["left-middle" /* LeftMiddle */]: "left center",
|
|
@@ -173,8 +182,7 @@ var sourceContentFade = {
|
|
|
173
182
|
duration: 0.12,
|
|
174
183
|
ease: "easeOut"
|
|
175
184
|
};
|
|
176
|
-
var sourceReturnDelay = 0.
|
|
177
|
-
var sourceGrowthRatio = 2;
|
|
185
|
+
var sourceReturnDelay = 0.1;
|
|
178
186
|
var instantTransition = { duration: 0 };
|
|
179
187
|
var createSourceContentMotion = (spring) => ({
|
|
180
188
|
opacity: sourceContentFade,
|
|
@@ -191,6 +199,12 @@ var createTargetContentMotion = (spring) => ({
|
|
|
191
199
|
top: spring
|
|
192
200
|
});
|
|
193
201
|
var safeScale = (from, to) => to === 0 ? 1 : from / to;
|
|
202
|
+
function getAxisScale(fromSize, toSize) {
|
|
203
|
+
return {
|
|
204
|
+
x: safeScale(toSize.width, fromSize.width),
|
|
205
|
+
y: safeScale(toSize.height, fromSize.height)
|
|
206
|
+
};
|
|
207
|
+
}
|
|
194
208
|
function getAnchoredPanelPosition(anchor, collapsedSize, targetSize) {
|
|
195
209
|
const alignLeft = 0;
|
|
196
210
|
const alignCenterX = (collapsedSize.width - targetSize.width) / 2;
|
|
@@ -230,7 +244,6 @@ function getLayerInteractivityProps(interactive) {
|
|
|
230
244
|
}
|
|
231
245
|
function getMorphMotionState({
|
|
232
246
|
expanded,
|
|
233
|
-
direction,
|
|
234
247
|
anchor,
|
|
235
248
|
spring,
|
|
236
249
|
animationEnabled,
|
|
@@ -241,10 +254,9 @@ function getMorphMotionState({
|
|
|
241
254
|
const expandedLayerSize = expandedSurface.size;
|
|
242
255
|
const animatedSize = expanded ? expandedLayerSize : collapsedLayerSize;
|
|
243
256
|
const visualStyle = expanded ? expandedSurface.visualStyle : collapsed.visualStyle;
|
|
244
|
-
const panelAnchor = anchor ?? defaultAnchorByDirection[direction];
|
|
245
257
|
const sourcePanelPosition = { left: 0, top: 0 };
|
|
246
258
|
const targetPanelPosition = getAnchoredPanelPosition(
|
|
247
|
-
|
|
259
|
+
anchor,
|
|
248
260
|
collapsedLayerSize,
|
|
249
261
|
expandedLayerSize
|
|
250
262
|
);
|
|
@@ -254,17 +266,17 @@ function getMorphMotionState({
|
|
|
254
266
|
anchorPosition: expanded ? targetPanelPosition : sourcePanelPosition,
|
|
255
267
|
animatedSize,
|
|
256
268
|
collapsedLayerSize,
|
|
257
|
-
collapsedToExpandedScale:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
269
|
+
collapsedToExpandedScale: getAxisScale(
|
|
270
|
+
expandedLayerSize,
|
|
271
|
+
collapsedLayerSize
|
|
272
|
+
),
|
|
261
273
|
expandedLayerSize,
|
|
262
|
-
sourceGrowthScale:
|
|
263
|
-
x: sourceGrowthRatio,
|
|
264
|
-
y: sourceGrowthRatio
|
|
265
|
-
},
|
|
274
|
+
sourceGrowthScale: getAxisScale(collapsedLayerSize, expandedLayerSize),
|
|
266
275
|
sourceOpacityTransition: {
|
|
267
276
|
...sourceContentFade,
|
|
277
|
+
// On close, the source layer is still being non-uniformly scaled from
|
|
278
|
+
// the target geometry. Keep it transparent until the spring has brought
|
|
279
|
+
// it close enough to its natural proportions to avoid a stretched flash.
|
|
268
280
|
delay: expanded ? 0 : sourceReturnDelay
|
|
269
281
|
},
|
|
270
282
|
sourcePosition: expanded ? invertPosition(targetPanelPosition) : sourcePanelPosition,
|
|
@@ -272,7 +284,7 @@ function getMorphMotionState({
|
|
|
272
284
|
targetOpacityTransition: { ...contentFade, delay: expanded ? 0.03 : 0 },
|
|
273
285
|
targetPosition: expanded ? sourcePanelPosition : targetPanelPosition,
|
|
274
286
|
targetTransition: createTargetContentMotion(activeSpring),
|
|
275
|
-
transformOrigin: anchorTransformOrigins[
|
|
287
|
+
transformOrigin: anchorTransformOrigins[anchor],
|
|
276
288
|
visualStyle
|
|
277
289
|
};
|
|
278
290
|
}
|
|
@@ -324,22 +336,129 @@ function MorphOverlay({
|
|
|
324
336
|
) : null });
|
|
325
337
|
}
|
|
326
338
|
|
|
339
|
+
// src/placement.ts
|
|
340
|
+
var clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
|
|
341
|
+
function getBoundedPanelSize(naturalSize, viewport, padding) {
|
|
342
|
+
const safePadding = Math.max(0, padding);
|
|
343
|
+
return {
|
|
344
|
+
width: Math.min(
|
|
345
|
+
naturalSize.width,
|
|
346
|
+
Math.max(1, viewport.width - safePadding * 2)
|
|
347
|
+
),
|
|
348
|
+
height: Math.min(
|
|
349
|
+
naturalSize.height,
|
|
350
|
+
Math.max(1, viewport.height - safePadding * 2)
|
|
351
|
+
)
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
function getAnchoredTargetRect(anchor, source, targetSize, viewport, padding) {
|
|
355
|
+
const sourceCenterX = source.left + source.width / 2;
|
|
356
|
+
const sourceCenterY = source.top + source.height / 2;
|
|
357
|
+
let left = source.left;
|
|
358
|
+
let top = source.top;
|
|
359
|
+
switch (anchor) {
|
|
360
|
+
case "left-top" /* LeftTop */:
|
|
361
|
+
break;
|
|
362
|
+
case "left-middle" /* LeftMiddle */:
|
|
363
|
+
top = sourceCenterY - targetSize.height / 2;
|
|
364
|
+
break;
|
|
365
|
+
case "left-bottom" /* LeftBottom */:
|
|
366
|
+
top = source.top + source.height - targetSize.height;
|
|
367
|
+
break;
|
|
368
|
+
case "top-middle" /* TopMiddle */:
|
|
369
|
+
left = sourceCenterX - targetSize.width / 2;
|
|
370
|
+
break;
|
|
371
|
+
case "middle-middle" /* MiddleMiddle */:
|
|
372
|
+
left = sourceCenterX - targetSize.width / 2;
|
|
373
|
+
top = sourceCenterY - targetSize.height / 2;
|
|
374
|
+
break;
|
|
375
|
+
case "right-top" /* RightTop */:
|
|
376
|
+
left = source.left + source.width - targetSize.width;
|
|
377
|
+
break;
|
|
378
|
+
case "right-middle" /* RightMiddle */:
|
|
379
|
+
left = source.left + source.width - targetSize.width;
|
|
380
|
+
top = sourceCenterY - targetSize.height / 2;
|
|
381
|
+
break;
|
|
382
|
+
case "right-bottom" /* RightBottom */:
|
|
383
|
+
left = source.left + source.width - targetSize.width;
|
|
384
|
+
top = source.top + source.height - targetSize.height;
|
|
385
|
+
break;
|
|
386
|
+
case "bottom-middle" /* BottomMiddle */:
|
|
387
|
+
left = sourceCenterX - targetSize.width / 2;
|
|
388
|
+
top = source.top + source.height - targetSize.height;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
const safePadding = Math.max(0, padding);
|
|
392
|
+
const minimumLeft = viewport.left + safePadding;
|
|
393
|
+
const minimumTop = viewport.top + safePadding;
|
|
394
|
+
const maximumLeft = viewport.left + viewport.width - safePadding - targetSize.width;
|
|
395
|
+
const maximumTop = viewport.top + viewport.height - safePadding - targetSize.height;
|
|
396
|
+
return {
|
|
397
|
+
left: clamp(left, minimumLeft, maximumLeft),
|
|
398
|
+
top: clamp(top, minimumTop, maximumTop),
|
|
399
|
+
...targetSize
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
function getPortalLayerPositions(expanded, source, target) {
|
|
403
|
+
const sourceFromTarget = {
|
|
404
|
+
left: source.left - target.left,
|
|
405
|
+
top: source.top - target.top
|
|
406
|
+
};
|
|
407
|
+
const targetFromSource = {
|
|
408
|
+
left: target.left - source.left,
|
|
409
|
+
top: target.top - source.top
|
|
410
|
+
};
|
|
411
|
+
const origin = { left: 0, top: 0 };
|
|
412
|
+
return expanded ? {
|
|
413
|
+
sourcePosition: sourceFromTarget,
|
|
414
|
+
targetPosition: origin
|
|
415
|
+
} : {
|
|
416
|
+
sourcePosition: origin,
|
|
417
|
+
targetPosition: targetFromSource
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
327
421
|
// src/component.tsx
|
|
328
422
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
329
423
|
var liveSurfaceContentClassName = "rmorph:absolute";
|
|
424
|
+
var MorphMeasurementContext = createContext(false);
|
|
425
|
+
function mergeClassNames(...classNames) {
|
|
426
|
+
return classNames.filter(Boolean).join(" ");
|
|
427
|
+
}
|
|
428
|
+
function getViewportBounds() {
|
|
429
|
+
const viewport = window.visualViewport;
|
|
430
|
+
if (viewport) {
|
|
431
|
+
return {
|
|
432
|
+
left: viewport.offsetLeft,
|
|
433
|
+
top: viewport.offsetTop,
|
|
434
|
+
width: viewport.width,
|
|
435
|
+
height: viewport.height
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return {
|
|
439
|
+
left: 0,
|
|
440
|
+
top: 0,
|
|
441
|
+
width: document.documentElement.clientWidth,
|
|
442
|
+
height: document.documentElement.clientHeight
|
|
443
|
+
};
|
|
444
|
+
}
|
|
330
445
|
function MorphMeasurementNodes({
|
|
331
446
|
collapsedRef,
|
|
332
447
|
expandedRef,
|
|
333
448
|
collapsedContent,
|
|
334
449
|
expandedContent
|
|
335
450
|
}) {
|
|
336
|
-
|
|
451
|
+
const measurementStyle = {
|
|
452
|
+
overflow: "clip"
|
|
453
|
+
};
|
|
454
|
+
return /* @__PURE__ */ jsxs(MorphMeasurementContext.Provider, { value: true, children: [
|
|
337
455
|
/* @__PURE__ */ jsx2(
|
|
338
456
|
"div",
|
|
339
457
|
{
|
|
340
458
|
ref: collapsedRef,
|
|
341
|
-
|
|
459
|
+
inert: true,
|
|
342
460
|
className: "rmorph:invisible rmorph:absolute rmorph:inset-x-0 rmorph:top-0 rmorph:pointer-events-none",
|
|
461
|
+
style: measurementStyle,
|
|
343
462
|
children: collapsedContent
|
|
344
463
|
}
|
|
345
464
|
),
|
|
@@ -347,8 +466,9 @@ function MorphMeasurementNodes({
|
|
|
347
466
|
"div",
|
|
348
467
|
{
|
|
349
468
|
ref: expandedRef,
|
|
350
|
-
|
|
469
|
+
inert: true,
|
|
351
470
|
className: "rmorph:invisible rmorph:absolute rmorph:inset-x-0 rmorph:top-0 rmorph:pointer-events-none",
|
|
471
|
+
style: measurementStyle,
|
|
352
472
|
children: expandedContent
|
|
353
473
|
}
|
|
354
474
|
)
|
|
@@ -364,12 +484,13 @@ function MorphShell({
|
|
|
364
484
|
spring,
|
|
365
485
|
onMorphComplete,
|
|
366
486
|
onMorphStart,
|
|
367
|
-
children
|
|
487
|
+
children,
|
|
488
|
+
className
|
|
368
489
|
}) {
|
|
369
490
|
return /* @__PURE__ */ jsx2(
|
|
370
491
|
motion2.div,
|
|
371
492
|
{
|
|
372
|
-
className: "rmorph:absolute rmorph:shadow-xs",
|
|
493
|
+
className: mergeClassNames("rmorph:absolute rmorph:shadow-xs", className),
|
|
373
494
|
initial: false,
|
|
374
495
|
animate: {
|
|
375
496
|
width: animatedSize.width,
|
|
@@ -389,6 +510,51 @@ function MorphShell({
|
|
|
389
510
|
}
|
|
390
511
|
);
|
|
391
512
|
}
|
|
513
|
+
function PortalMorphShell({
|
|
514
|
+
expanded,
|
|
515
|
+
lifecyclePhase,
|
|
516
|
+
geometry,
|
|
517
|
+
collapsedVisualStyle,
|
|
518
|
+
expandedVisualStyle,
|
|
519
|
+
overflow,
|
|
520
|
+
zIndex,
|
|
521
|
+
spring,
|
|
522
|
+
onMorphComplete,
|
|
523
|
+
onMorphStart,
|
|
524
|
+
children,
|
|
525
|
+
className
|
|
526
|
+
}) {
|
|
527
|
+
const rect = expanded ? geometry.target : geometry.source;
|
|
528
|
+
const visualStyle = expanded ? expandedVisualStyle : collapsedVisualStyle;
|
|
529
|
+
return /* @__PURE__ */ jsx2(
|
|
530
|
+
motion2.div,
|
|
531
|
+
{
|
|
532
|
+
"data-morpheus-portal-shell": "",
|
|
533
|
+
"data-morpheus-phase": lifecyclePhase,
|
|
534
|
+
inert: lifecyclePhase === "handoff" || void 0,
|
|
535
|
+
className: mergeClassNames("rmorph:fixed rmorph:shadow-xs", className),
|
|
536
|
+
initial: false,
|
|
537
|
+
animate: {
|
|
538
|
+
left: rect.left,
|
|
539
|
+
top: rect.top,
|
|
540
|
+
width: rect.width,
|
|
541
|
+
height: rect.height,
|
|
542
|
+
backgroundColor: visualStyle.backgroundColor,
|
|
543
|
+
borderRadius: visualStyle.borderRadius
|
|
544
|
+
},
|
|
545
|
+
onAnimationComplete: onMorphComplete,
|
|
546
|
+
...onMorphStart ? { onAnimationStart: onMorphStart } : {},
|
|
547
|
+
style: {
|
|
548
|
+
overflow,
|
|
549
|
+
overscrollBehavior: "contain",
|
|
550
|
+
pointerEvents: lifecyclePhase === "handoff" ? "none" : void 0,
|
|
551
|
+
zIndex
|
|
552
|
+
},
|
|
553
|
+
transition: spring,
|
|
554
|
+
children
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
}
|
|
392
558
|
function MorphContentLayers({
|
|
393
559
|
expanded,
|
|
394
560
|
settledState,
|
|
@@ -461,8 +627,26 @@ function MorphContentLayers({
|
|
|
461
627
|
)
|
|
462
628
|
] });
|
|
463
629
|
}
|
|
464
|
-
function
|
|
465
|
-
|
|
630
|
+
function MeasurementTreePlaceholder({
|
|
631
|
+
className,
|
|
632
|
+
collapsedContent
|
|
633
|
+
}) {
|
|
634
|
+
return /* @__PURE__ */ jsx2("div", { className, children: /* @__PURE__ */ jsx2("div", { className: "rmorph:relative rmorph:mx-auto rmorph:block rmorph:w-fit rmorph:align-top", children: /* @__PURE__ */ jsx2("div", { className: "rmorph:inline-block rmorph:align-top", children: collapsedContent }) }) });
|
|
635
|
+
}
|
|
636
|
+
function Morpheus(props) {
|
|
637
|
+
const insideMeasurementTree = useContext(MorphMeasurementContext);
|
|
638
|
+
if (insideMeasurementTree) {
|
|
639
|
+
return /* @__PURE__ */ jsx2(
|
|
640
|
+
MeasurementTreePlaceholder,
|
|
641
|
+
{
|
|
642
|
+
className: props.className ?? "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
643
|
+
collapsedContent: props.collapsedContent
|
|
644
|
+
}
|
|
645
|
+
);
|
|
646
|
+
}
|
|
647
|
+
return /* @__PURE__ */ jsx2(MorpheusRoot, { ...props });
|
|
648
|
+
}
|
|
649
|
+
function MorpheusRoot({
|
|
466
650
|
anchor,
|
|
467
651
|
expanded,
|
|
468
652
|
onClose,
|
|
@@ -471,6 +655,9 @@ function Morpheus({
|
|
|
471
655
|
collapsedContent,
|
|
472
656
|
expandedContent,
|
|
473
657
|
className = "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
658
|
+
shellClassName,
|
|
659
|
+
portalContainer,
|
|
660
|
+
viewportPadding = 16,
|
|
474
661
|
overlayColor = "#05070a",
|
|
475
662
|
overlayOpacity = 0.54,
|
|
476
663
|
overlayBlur = 0,
|
|
@@ -478,13 +665,51 @@ function Morpheus({
|
|
|
478
665
|
spring = defaultPanelSpring
|
|
479
666
|
}) {
|
|
480
667
|
const measurements = useMorphMeasurements();
|
|
668
|
+
const anchorRef = useRef2(null);
|
|
669
|
+
const [defaultPortalContainer, setDefaultPortalContainer] = useState2(null);
|
|
481
670
|
const [animationEnabled, setAnimationEnabled] = useState2(false);
|
|
482
671
|
const [shellOverflowVisible, setShellOverflowVisible] = useState2(false);
|
|
483
|
-
const [
|
|
672
|
+
const [inlineSettledState, setInlineSettledState] = useState2(expanded ? "expanded" : "collapsed");
|
|
673
|
+
const [portalPhase, setPortalPhase] = useState2(
|
|
484
674
|
expanded ? "expanded" : "collapsed"
|
|
485
675
|
);
|
|
676
|
+
const [portalVisualExpanded, setPortalVisualExpanded] = useState2(expanded);
|
|
677
|
+
const [portalGeometry, setPortalGeometry] = useState2(
|
|
678
|
+
null
|
|
679
|
+
);
|
|
486
680
|
const hasOpenedRef = useRef2(expanded);
|
|
487
681
|
const beforeCloseCalledRef = useRef2(false);
|
|
682
|
+
const beforeCloseRef = useRef2(beforeClose);
|
|
683
|
+
const afterCloseRef = useRef2(afterClose);
|
|
684
|
+
const previousExpandedRef = useRef2(expanded);
|
|
685
|
+
const portalStartFrameRef = useRef2(null);
|
|
686
|
+
const handoffFirstFrameRef = useRef2(null);
|
|
687
|
+
const handoffSecondFrameRef = useRef2(null);
|
|
688
|
+
const portalPhaseRef = useRef2(portalPhase);
|
|
689
|
+
const expandedRef = useRef2(expanded);
|
|
690
|
+
portalPhaseRef.current = portalPhase;
|
|
691
|
+
expandedRef.current = expanded;
|
|
692
|
+
beforeCloseRef.current = beforeClose;
|
|
693
|
+
afterCloseRef.current = afterClose;
|
|
694
|
+
const cancelPortalFrames = useCallback(() => {
|
|
695
|
+
if (portalStartFrameRef.current !== null) {
|
|
696
|
+
cancelAnimationFrame(portalStartFrameRef.current);
|
|
697
|
+
portalStartFrameRef.current = null;
|
|
698
|
+
}
|
|
699
|
+
if (handoffFirstFrameRef.current !== null) {
|
|
700
|
+
cancelAnimationFrame(handoffFirstFrameRef.current);
|
|
701
|
+
handoffFirstFrameRef.current = null;
|
|
702
|
+
}
|
|
703
|
+
if (handoffSecondFrameRef.current !== null) {
|
|
704
|
+
cancelAnimationFrame(handoffSecondFrameRef.current);
|
|
705
|
+
handoffSecondFrameRef.current = null;
|
|
706
|
+
}
|
|
707
|
+
}, []);
|
|
708
|
+
useEffect(() => {
|
|
709
|
+
if (portalContainer === void 0) {
|
|
710
|
+
setDefaultPortalContainer(document.body);
|
|
711
|
+
}
|
|
712
|
+
}, [portalContainer]);
|
|
488
713
|
useEffect(() => {
|
|
489
714
|
if (!measurements.ready) {
|
|
490
715
|
return;
|
|
@@ -492,113 +717,339 @@ function Morpheus({
|
|
|
492
717
|
const frame = requestAnimationFrame(() => setAnimationEnabled(true));
|
|
493
718
|
return () => cancelAnimationFrame(frame);
|
|
494
719
|
}, [measurements.ready]);
|
|
495
|
-
const
|
|
720
|
+
const resolvedPortalContainer = portalContainer === void 0 ? defaultPortalContainer : portalContainer;
|
|
721
|
+
const portalEnabled = resolvedPortalContainer !== null;
|
|
722
|
+
const panelAnchor = anchor;
|
|
723
|
+
const updatePortalGeometry = useCallback(() => {
|
|
724
|
+
const sourceElement = anchorRef.current;
|
|
725
|
+
if (!sourceElement || !measurements.ready) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const sourceBounds = sourceElement.getBoundingClientRect();
|
|
729
|
+
const source = {
|
|
730
|
+
left: sourceBounds.left,
|
|
731
|
+
top: sourceBounds.top,
|
|
732
|
+
width: sourceBounds.width,
|
|
733
|
+
height: sourceBounds.height
|
|
734
|
+
};
|
|
735
|
+
const viewport = getViewportBounds();
|
|
736
|
+
const targetSize = getBoundedPanelSize(
|
|
737
|
+
measurements.expanded.size,
|
|
738
|
+
viewport,
|
|
739
|
+
viewportPadding
|
|
740
|
+
);
|
|
741
|
+
const target = getAnchoredTargetRect(
|
|
742
|
+
panelAnchor,
|
|
743
|
+
source,
|
|
744
|
+
targetSize,
|
|
745
|
+
viewport,
|
|
746
|
+
viewportPadding
|
|
747
|
+
);
|
|
748
|
+
setPortalGeometry((current) => {
|
|
749
|
+
if (current?.source.left === source.left && current.source.top === source.top && current.source.width === source.width && current.source.height === source.height && current.target.left === target.left && current.target.top === target.top && current.target.width === target.width && current.target.height === target.height) {
|
|
750
|
+
return current;
|
|
751
|
+
}
|
|
752
|
+
return { source, target };
|
|
753
|
+
});
|
|
754
|
+
}, [
|
|
755
|
+
measurements.expanded.size,
|
|
756
|
+
measurements.ready,
|
|
757
|
+
panelAnchor,
|
|
758
|
+
viewportPadding
|
|
759
|
+
]);
|
|
760
|
+
useLayoutEffect2(() => {
|
|
761
|
+
if (!portalEnabled || !measurements.ready) {
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
const expandedChanged = previousExpandedRef.current !== expanded;
|
|
765
|
+
previousExpandedRef.current = expanded;
|
|
766
|
+
updatePortalGeometry();
|
|
767
|
+
if (!expandedChanged) {
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
cancelPortalFrames();
|
|
771
|
+
if (expanded) {
|
|
772
|
+
setPortalPhase("opening");
|
|
773
|
+
setPortalVisualExpanded(false);
|
|
774
|
+
portalStartFrameRef.current = requestAnimationFrame(() => {
|
|
775
|
+
portalStartFrameRef.current = null;
|
|
776
|
+
if (!expandedRef.current || portalPhaseRef.current !== "opening") {
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
setPortalVisualExpanded(true);
|
|
780
|
+
});
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (portalPhaseRef.current !== "collapsed" && portalPhaseRef.current !== "closing") {
|
|
784
|
+
setPortalPhase("closing");
|
|
785
|
+
setPortalVisualExpanded(false);
|
|
786
|
+
if (hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
787
|
+
beforeCloseCalledRef.current = true;
|
|
788
|
+
beforeCloseRef.current?.();
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}, [
|
|
792
|
+
cancelPortalFrames,
|
|
496
793
|
expanded,
|
|
497
|
-
|
|
794
|
+
measurements.ready,
|
|
795
|
+
portalEnabled,
|
|
796
|
+
updatePortalGeometry
|
|
797
|
+
]);
|
|
798
|
+
useLayoutEffect2(() => {
|
|
799
|
+
if (portalPhase !== "handoff" || expanded) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
handoffFirstFrameRef.current = requestAnimationFrame(() => {
|
|
803
|
+
handoffFirstFrameRef.current = null;
|
|
804
|
+
if (expandedRef.current || portalPhaseRef.current !== "handoff") {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
handoffSecondFrameRef.current = requestAnimationFrame(() => {
|
|
808
|
+
handoffSecondFrameRef.current = null;
|
|
809
|
+
if (expandedRef.current || portalPhaseRef.current !== "handoff") {
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
setPortalPhase("collapsed");
|
|
813
|
+
setPortalGeometry(null);
|
|
814
|
+
if (hasOpenedRef.current) {
|
|
815
|
+
hasOpenedRef.current = false;
|
|
816
|
+
afterCloseRef.current?.();
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
});
|
|
820
|
+
return cancelPortalFrames;
|
|
821
|
+
}, [cancelPortalFrames, expanded, portalPhase]);
|
|
822
|
+
useEffect(() => {
|
|
823
|
+
if (!portalEnabled || portalPhase === "collapsed" || !measurements.ready) {
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
let frame = null;
|
|
827
|
+
const scheduleUpdate = () => {
|
|
828
|
+
if (frame !== null) {
|
|
829
|
+
return;
|
|
830
|
+
}
|
|
831
|
+
frame = requestAnimationFrame(() => {
|
|
832
|
+
frame = null;
|
|
833
|
+
updatePortalGeometry();
|
|
834
|
+
});
|
|
835
|
+
};
|
|
836
|
+
const observer = new ResizeObserver(scheduleUpdate);
|
|
837
|
+
if (anchorRef.current) {
|
|
838
|
+
observer.observe(anchorRef.current);
|
|
839
|
+
}
|
|
840
|
+
window.addEventListener("scroll", scheduleUpdate, true);
|
|
841
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
842
|
+
window.visualViewport?.addEventListener("resize", scheduleUpdate);
|
|
843
|
+
window.visualViewport?.addEventListener("scroll", scheduleUpdate);
|
|
844
|
+
return () => {
|
|
845
|
+
if (frame !== null) {
|
|
846
|
+
cancelAnimationFrame(frame);
|
|
847
|
+
}
|
|
848
|
+
observer.disconnect();
|
|
849
|
+
window.removeEventListener("scroll", scheduleUpdate, true);
|
|
850
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
851
|
+
window.visualViewport?.removeEventListener("resize", scheduleUpdate);
|
|
852
|
+
window.visualViewport?.removeEventListener("scroll", scheduleUpdate);
|
|
853
|
+
};
|
|
854
|
+
}, [measurements.ready, portalEnabled, portalPhase, updatePortalGeometry]);
|
|
855
|
+
useEffect(() => cancelPortalFrames, [cancelPortalFrames]);
|
|
856
|
+
const motionState = getMorphMotionState({
|
|
857
|
+
expanded: portalEnabled ? portalVisualExpanded : expanded,
|
|
498
858
|
anchor,
|
|
499
859
|
spring,
|
|
500
860
|
animationEnabled,
|
|
501
861
|
collapsed: measurements.collapsed,
|
|
502
862
|
expandedSurface: measurements.expanded
|
|
503
863
|
});
|
|
504
|
-
const overflowVisible = expanded && shellOverflowVisible;
|
|
505
864
|
const shouldRenderStaticSource = !measurements.ready && !expanded;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
865
|
+
const portalPresent = portalPhase !== "collapsed";
|
|
866
|
+
const portalIsRendered = portalEnabled && portalPresent && portalGeometry !== null && resolvedPortalContainer !== null;
|
|
867
|
+
const inlineSourceHidden = portalIsRendered && portalPhase !== "handoff";
|
|
868
|
+
const portalSettledState = portalPhase === "expanded" || portalPhase === "closing" ? "expanded" : "collapsed";
|
|
869
|
+
const portalTargetIsBounded = portalGeometry !== null && (portalGeometry.target.width < measurements.expanded.size.width || portalGeometry.target.height < measurements.expanded.size.height);
|
|
870
|
+
const contentLayers = (surfaceExpanded, settledState, portalMode = false) => {
|
|
871
|
+
const layerPositions = portalMode && portalGeometry ? getPortalLayerPositions(
|
|
872
|
+
surfaceExpanded,
|
|
873
|
+
portalGeometry.source,
|
|
874
|
+
portalGeometry.target
|
|
875
|
+
) : {
|
|
876
|
+
sourcePosition: motionState.sourcePosition,
|
|
877
|
+
targetPosition: motionState.targetPosition
|
|
878
|
+
};
|
|
879
|
+
const sourceGrowthScale = portalMode && portalGeometry ? getAxisScale(
|
|
880
|
+
motionState.collapsedLayerSize,
|
|
881
|
+
portalGeometry.target
|
|
882
|
+
) : motionState.sourceGrowthScale;
|
|
883
|
+
const collapsedToExpandedScale = portalMode && portalGeometry ? getAxisScale(
|
|
884
|
+
motionState.expandedLayerSize,
|
|
885
|
+
portalGeometry.source
|
|
886
|
+
) : motionState.collapsedToExpandedScale;
|
|
887
|
+
return /* @__PURE__ */ jsx2(
|
|
888
|
+
MorphContentLayers,
|
|
509
889
|
{
|
|
510
|
-
expanded,
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
890
|
+
expanded: surfaceExpanded,
|
|
891
|
+
settledState,
|
|
892
|
+
collapsedContent,
|
|
893
|
+
expandedContent,
|
|
894
|
+
collapsedLayerSize: motionState.collapsedLayerSize,
|
|
895
|
+
expandedLayerSize: motionState.expandedLayerSize,
|
|
896
|
+
sourcePosition: layerPositions.sourcePosition,
|
|
897
|
+
targetPosition: layerPositions.targetPosition,
|
|
898
|
+
sourceGrowthScale,
|
|
899
|
+
collapsedToExpandedScale,
|
|
900
|
+
transformOrigin: motionState.transformOrigin,
|
|
901
|
+
sourceTransition: motionState.sourceTransition,
|
|
902
|
+
sourceOpacityTransition: motionState.sourceOpacityTransition,
|
|
903
|
+
targetTransition: motionState.targetTransition,
|
|
904
|
+
targetOpacityTransition: motionState.targetOpacityTransition
|
|
516
905
|
}
|
|
517
|
-
)
|
|
518
|
-
|
|
906
|
+
);
|
|
907
|
+
};
|
|
908
|
+
const measurementNodes = /* @__PURE__ */ jsx2(
|
|
909
|
+
MorphMeasurementNodes,
|
|
910
|
+
{
|
|
911
|
+
collapsedRef: measurements.collapsedRef,
|
|
912
|
+
expandedRef: measurements.expandedRef,
|
|
913
|
+
collapsedContent,
|
|
914
|
+
expandedContent
|
|
915
|
+
}
|
|
916
|
+
);
|
|
917
|
+
if (!portalEnabled && portalContainer === null) {
|
|
918
|
+
const overflowVisible = expanded && shellOverflowVisible;
|
|
919
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
519
920
|
/* @__PURE__ */ jsx2(
|
|
520
|
-
|
|
921
|
+
MorphOverlay,
|
|
521
922
|
{
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
923
|
+
expanded,
|
|
924
|
+
onClose,
|
|
925
|
+
color: overlayColor,
|
|
926
|
+
opacity: overlayOpacity,
|
|
927
|
+
blur: overlayBlur,
|
|
928
|
+
zIndex
|
|
526
929
|
}
|
|
527
930
|
),
|
|
528
|
-
/* @__PURE__ */
|
|
931
|
+
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
932
|
+
measurementNodes,
|
|
933
|
+
/* @__PURE__ */ jsx2("div", { className: "rmorph:relative rmorph:mx-auto rmorph:block rmorph:w-fit rmorph:align-top", children: shouldRenderStaticSource ? /* @__PURE__ */ jsx2("div", { className: "rmorph:inline-block rmorph:align-top", children: collapsedContent }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
934
|
+
/* @__PURE__ */ jsx2(
|
|
935
|
+
"div",
|
|
936
|
+
{
|
|
937
|
+
"aria-hidden": "true",
|
|
938
|
+
style: {
|
|
939
|
+
visibility: "hidden",
|
|
940
|
+
width: motionState.collapsedLayerSize.width,
|
|
941
|
+
height: motionState.collapsedLayerSize.height
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
),
|
|
945
|
+
/* @__PURE__ */ jsx2(
|
|
946
|
+
MorphShell,
|
|
947
|
+
{
|
|
948
|
+
expanded,
|
|
949
|
+
position: motionState.anchorPosition,
|
|
950
|
+
animatedSize: motionState.animatedSize,
|
|
951
|
+
visualStyle: motionState.visualStyle,
|
|
952
|
+
overflowVisible,
|
|
953
|
+
zIndex,
|
|
954
|
+
spring: motionState.activeSpring,
|
|
955
|
+
className: shellClassName,
|
|
956
|
+
onMorphComplete: () => {
|
|
957
|
+
if (expanded) {
|
|
958
|
+
hasOpenedRef.current = true;
|
|
959
|
+
beforeCloseCalledRef.current = false;
|
|
960
|
+
setInlineSettledState("expanded");
|
|
961
|
+
setShellOverflowVisible(true);
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
setInlineSettledState("collapsed");
|
|
965
|
+
if (hasOpenedRef.current) {
|
|
966
|
+
hasOpenedRef.current = false;
|
|
967
|
+
afterClose?.();
|
|
968
|
+
}
|
|
969
|
+
},
|
|
970
|
+
onMorphStart: () => {
|
|
971
|
+
if (!expanded && hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
972
|
+
beforeCloseCalledRef.current = true;
|
|
973
|
+
beforeClose?.();
|
|
974
|
+
}
|
|
975
|
+
setShellOverflowVisible(false);
|
|
976
|
+
setInlineSettledState(expanded ? "collapsed" : "expanded");
|
|
977
|
+
},
|
|
978
|
+
children: contentLayers(expanded, inlineSettledState)
|
|
979
|
+
}
|
|
980
|
+
)
|
|
981
|
+
] }) })
|
|
982
|
+
] })
|
|
983
|
+
] });
|
|
984
|
+
}
|
|
985
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
986
|
+
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
987
|
+
measurementNodes,
|
|
988
|
+
/* @__PURE__ */ jsx2("div", { className: "rmorph:relative rmorph:mx-auto rmorph:block rmorph:w-fit rmorph:align-top", children: /* @__PURE__ */ jsx2(
|
|
989
|
+
"div",
|
|
990
|
+
{
|
|
991
|
+
ref: anchorRef,
|
|
992
|
+
"data-morpheus-inline-source": "",
|
|
993
|
+
...getLayerInteractivityProps(!inlineSourceHidden),
|
|
994
|
+
className: mergeClassNames(
|
|
995
|
+
"rmorph:inline-block rmorph:align-top rmorph:shadow-xs",
|
|
996
|
+
shellClassName
|
|
997
|
+
),
|
|
998
|
+
style: {
|
|
999
|
+
visibility: inlineSourceHidden ? "hidden" : void 0,
|
|
1000
|
+
pointerEvents: inlineSourceHidden ? "none" : void 0
|
|
1001
|
+
},
|
|
1002
|
+
children: collapsedContent
|
|
1003
|
+
}
|
|
1004
|
+
) })
|
|
1005
|
+
] }),
|
|
1006
|
+
portalIsRendered && portalGeometry && resolvedPortalContainer ? createPortal(
|
|
1007
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
529
1008
|
/* @__PURE__ */ jsx2(
|
|
530
|
-
|
|
1009
|
+
MorphOverlay,
|
|
531
1010
|
{
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1011
|
+
expanded,
|
|
1012
|
+
onClose,
|
|
1013
|
+
color: overlayColor,
|
|
1014
|
+
opacity: overlayOpacity,
|
|
1015
|
+
blur: overlayBlur,
|
|
1016
|
+
zIndex
|
|
538
1017
|
}
|
|
539
1018
|
),
|
|
540
1019
|
/* @__PURE__ */ jsx2(
|
|
541
|
-
|
|
1020
|
+
PortalMorphShell,
|
|
542
1021
|
{
|
|
543
|
-
expanded,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1022
|
+
expanded: portalVisualExpanded,
|
|
1023
|
+
lifecyclePhase: portalPhase,
|
|
1024
|
+
geometry: portalGeometry,
|
|
1025
|
+
collapsedVisualStyle: measurements.collapsed.visualStyle,
|
|
1026
|
+
expandedVisualStyle: measurements.expanded.visualStyle,
|
|
1027
|
+
overflow: portalPhase === "expanded" ? portalTargetIsBounded ? "auto" : "visible" : "hidden",
|
|
548
1028
|
zIndex,
|
|
549
1029
|
spring: motionState.activeSpring,
|
|
1030
|
+
className: shellClassName,
|
|
550
1031
|
onMorphComplete: () => {
|
|
551
|
-
if (
|
|
1032
|
+
if (expandedRef.current && portalPhaseRef.current === "opening" && portalVisualExpanded) {
|
|
552
1033
|
hasOpenedRef.current = true;
|
|
553
1034
|
beforeCloseCalledRef.current = false;
|
|
554
|
-
|
|
555
|
-
setShellOverflowVisible(true);
|
|
1035
|
+
setPortalPhase("expanded");
|
|
556
1036
|
return;
|
|
557
1037
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
hasOpenedRef.current = false;
|
|
561
|
-
afterClose?.();
|
|
562
|
-
}
|
|
563
|
-
},
|
|
564
|
-
onMorphStart: () => {
|
|
565
|
-
if (!expanded && hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
566
|
-
beforeCloseCalledRef.current = true;
|
|
567
|
-
beforeClose?.();
|
|
1038
|
+
if (!expandedRef.current && portalPhaseRef.current === "closing" && !portalVisualExpanded) {
|
|
1039
|
+
setPortalPhase("handoff");
|
|
568
1040
|
}
|
|
569
|
-
setShellOverflowVisible(false);
|
|
570
|
-
setSettledState(expanded ? "collapsed" : "expanded");
|
|
571
1041
|
},
|
|
572
|
-
children:
|
|
573
|
-
MorphContentLayers,
|
|
574
|
-
{
|
|
575
|
-
expanded,
|
|
576
|
-
settledState,
|
|
577
|
-
collapsedContent,
|
|
578
|
-
expandedContent,
|
|
579
|
-
collapsedLayerSize: motionState.collapsedLayerSize,
|
|
580
|
-
expandedLayerSize: motionState.expandedLayerSize,
|
|
581
|
-
sourcePosition: motionState.sourcePosition,
|
|
582
|
-
targetPosition: motionState.targetPosition,
|
|
583
|
-
sourceGrowthScale: motionState.sourceGrowthScale,
|
|
584
|
-
collapsedToExpandedScale: motionState.collapsedToExpandedScale,
|
|
585
|
-
transformOrigin: motionState.transformOrigin,
|
|
586
|
-
sourceTransition: motionState.sourceTransition,
|
|
587
|
-
sourceOpacityTransition: motionState.sourceOpacityTransition,
|
|
588
|
-
targetTransition: motionState.targetTransition,
|
|
589
|
-
targetOpacityTransition: motionState.targetOpacityTransition
|
|
590
|
-
}
|
|
591
|
-
)
|
|
1042
|
+
children: contentLayers(portalVisualExpanded, portalSettledState, true)
|
|
592
1043
|
}
|
|
593
1044
|
)
|
|
594
|
-
] })
|
|
595
|
-
|
|
1045
|
+
] }),
|
|
1046
|
+
resolvedPortalContainer
|
|
1047
|
+
) : null
|
|
596
1048
|
] });
|
|
597
1049
|
}
|
|
598
1050
|
export {
|
|
599
1051
|
MorphAnchor,
|
|
600
1052
|
Morpheus,
|
|
601
1053
|
Morpheus as default,
|
|
602
|
-
defaultAnchorByDirection,
|
|
603
1054
|
morphSpringPresets
|
|
604
1055
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-morpheus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "A React component for morphing one surface into other.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"build:js": "tsup src/index.ts --format esm --dts --external react --external react-dom --external framer-motion --clean",
|
|
30
30
|
"build:css": "tailwindcss -i src/style.css -o dist/style.css --minify",
|
|
31
31
|
"check": "tsc --noEmit",
|
|
32
|
+
"test": "bun test",
|
|
32
33
|
"publish:npm": "bun run scripts/publish.ts",
|
|
33
34
|
"release:minor": "bun run scripts/release.ts minor",
|
|
34
35
|
"release:major": "bun run scripts/release.ts major",
|
|
@@ -40,7 +41,9 @@
|
|
|
40
41
|
"react-dom": "^19.0.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
44
|
+
"@happy-dom/global-registrator": "^20.11.1",
|
|
43
45
|
"@tailwindcss/cli": "^4.3.3",
|
|
46
|
+
"@testing-library/react": "^16.3.2",
|
|
44
47
|
"@types/bun": "^1.3.14",
|
|
45
48
|
"@types/react": "^19.2.17",
|
|
46
49
|
"@types/react-dom": "^19.2.3",
|