react-morpheus 0.1.5 → 0.1.7
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 +550 -106
- 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,6 @@ var sourceContentFade = {
|
|
|
173
182
|
duration: 0.12,
|
|
174
183
|
ease: "easeOut"
|
|
175
184
|
};
|
|
176
|
-
var sourceReturnDelay = 0.06;
|
|
177
|
-
var sourceGrowthRatio = 2;
|
|
178
185
|
var instantTransition = { duration: 0 };
|
|
179
186
|
var createSourceContentMotion = (spring) => ({
|
|
180
187
|
opacity: sourceContentFade,
|
|
@@ -191,6 +198,12 @@ var createTargetContentMotion = (spring) => ({
|
|
|
191
198
|
top: spring
|
|
192
199
|
});
|
|
193
200
|
var safeScale = (from, to) => to === 0 ? 1 : from / to;
|
|
201
|
+
function getAxisScale(fromSize, toSize) {
|
|
202
|
+
return {
|
|
203
|
+
x: safeScale(toSize.width, fromSize.width),
|
|
204
|
+
y: safeScale(toSize.height, fromSize.height)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
194
207
|
function getAnchoredPanelPosition(anchor, collapsedSize, targetSize) {
|
|
195
208
|
const alignLeft = 0;
|
|
196
209
|
const alignCenterX = (collapsedSize.width - targetSize.width) / 2;
|
|
@@ -230,7 +243,6 @@ function getLayerInteractivityProps(interactive) {
|
|
|
230
243
|
}
|
|
231
244
|
function getMorphMotionState({
|
|
232
245
|
expanded,
|
|
233
|
-
direction,
|
|
234
246
|
anchor,
|
|
235
247
|
spring,
|
|
236
248
|
animationEnabled,
|
|
@@ -241,10 +253,9 @@ function getMorphMotionState({
|
|
|
241
253
|
const expandedLayerSize = expandedSurface.size;
|
|
242
254
|
const animatedSize = expanded ? expandedLayerSize : collapsedLayerSize;
|
|
243
255
|
const visualStyle = expanded ? expandedSurface.visualStyle : collapsed.visualStyle;
|
|
244
|
-
const panelAnchor = anchor ?? defaultAnchorByDirection[direction];
|
|
245
256
|
const sourcePanelPosition = { left: 0, top: 0 };
|
|
246
257
|
const targetPanelPosition = getAnchoredPanelPosition(
|
|
247
|
-
|
|
258
|
+
anchor,
|
|
248
259
|
collapsedLayerSize,
|
|
249
260
|
expandedLayerSize
|
|
250
261
|
);
|
|
@@ -254,25 +265,19 @@ function getMorphMotionState({
|
|
|
254
265
|
anchorPosition: expanded ? targetPanelPosition : sourcePanelPosition,
|
|
255
266
|
animatedSize,
|
|
256
267
|
collapsedLayerSize,
|
|
257
|
-
collapsedToExpandedScale:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
268
|
+
collapsedToExpandedScale: getAxisScale(
|
|
269
|
+
expandedLayerSize,
|
|
270
|
+
collapsedLayerSize
|
|
271
|
+
),
|
|
261
272
|
expandedLayerSize,
|
|
262
|
-
sourceGrowthScale:
|
|
263
|
-
|
|
264
|
-
y: sourceGrowthRatio
|
|
265
|
-
},
|
|
266
|
-
sourceOpacityTransition: {
|
|
267
|
-
...sourceContentFade,
|
|
268
|
-
delay: expanded ? 0 : sourceReturnDelay
|
|
269
|
-
},
|
|
273
|
+
sourceGrowthScale: getAxisScale(collapsedLayerSize, expandedLayerSize),
|
|
274
|
+
sourceOpacityTransition: { ...sourceContentFade },
|
|
270
275
|
sourcePosition: expanded ? invertPosition(targetPanelPosition) : sourcePanelPosition,
|
|
271
276
|
sourceTransition: createSourceContentMotion(activeSpring),
|
|
272
277
|
targetOpacityTransition: { ...contentFade, delay: expanded ? 0.03 : 0 },
|
|
273
278
|
targetPosition: expanded ? sourcePanelPosition : targetPanelPosition,
|
|
274
279
|
targetTransition: createTargetContentMotion(activeSpring),
|
|
275
|
-
transformOrigin: anchorTransformOrigins[
|
|
280
|
+
transformOrigin: anchorTransformOrigins[anchor],
|
|
276
281
|
visualStyle
|
|
277
282
|
};
|
|
278
283
|
}
|
|
@@ -324,22 +329,129 @@ function MorphOverlay({
|
|
|
324
329
|
) : null });
|
|
325
330
|
}
|
|
326
331
|
|
|
332
|
+
// src/placement.ts
|
|
333
|
+
var clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), Math.max(minimum, maximum));
|
|
334
|
+
function getBoundedPanelSize(naturalSize, viewport, padding) {
|
|
335
|
+
const safePadding = Math.max(0, padding);
|
|
336
|
+
return {
|
|
337
|
+
width: Math.min(
|
|
338
|
+
naturalSize.width,
|
|
339
|
+
Math.max(1, viewport.width - safePadding * 2)
|
|
340
|
+
),
|
|
341
|
+
height: Math.min(
|
|
342
|
+
naturalSize.height,
|
|
343
|
+
Math.max(1, viewport.height - safePadding * 2)
|
|
344
|
+
)
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
function getAnchoredTargetRect(anchor, source, targetSize, viewport, padding) {
|
|
348
|
+
const sourceCenterX = source.left + source.width / 2;
|
|
349
|
+
const sourceCenterY = source.top + source.height / 2;
|
|
350
|
+
let left = source.left;
|
|
351
|
+
let top = source.top;
|
|
352
|
+
switch (anchor) {
|
|
353
|
+
case "left-top" /* LeftTop */:
|
|
354
|
+
break;
|
|
355
|
+
case "left-middle" /* LeftMiddle */:
|
|
356
|
+
top = sourceCenterY - targetSize.height / 2;
|
|
357
|
+
break;
|
|
358
|
+
case "left-bottom" /* LeftBottom */:
|
|
359
|
+
top = source.top + source.height - targetSize.height;
|
|
360
|
+
break;
|
|
361
|
+
case "top-middle" /* TopMiddle */:
|
|
362
|
+
left = sourceCenterX - targetSize.width / 2;
|
|
363
|
+
break;
|
|
364
|
+
case "middle-middle" /* MiddleMiddle */:
|
|
365
|
+
left = sourceCenterX - targetSize.width / 2;
|
|
366
|
+
top = sourceCenterY - targetSize.height / 2;
|
|
367
|
+
break;
|
|
368
|
+
case "right-top" /* RightTop */:
|
|
369
|
+
left = source.left + source.width - targetSize.width;
|
|
370
|
+
break;
|
|
371
|
+
case "right-middle" /* RightMiddle */:
|
|
372
|
+
left = source.left + source.width - targetSize.width;
|
|
373
|
+
top = sourceCenterY - targetSize.height / 2;
|
|
374
|
+
break;
|
|
375
|
+
case "right-bottom" /* RightBottom */:
|
|
376
|
+
left = source.left + source.width - targetSize.width;
|
|
377
|
+
top = source.top + source.height - targetSize.height;
|
|
378
|
+
break;
|
|
379
|
+
case "bottom-middle" /* BottomMiddle */:
|
|
380
|
+
left = sourceCenterX - targetSize.width / 2;
|
|
381
|
+
top = source.top + source.height - targetSize.height;
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
const safePadding = Math.max(0, padding);
|
|
385
|
+
const minimumLeft = viewport.left + safePadding;
|
|
386
|
+
const minimumTop = viewport.top + safePadding;
|
|
387
|
+
const maximumLeft = viewport.left + viewport.width - safePadding - targetSize.width;
|
|
388
|
+
const maximumTop = viewport.top + viewport.height - safePadding - targetSize.height;
|
|
389
|
+
return {
|
|
390
|
+
left: clamp(left, minimumLeft, maximumLeft),
|
|
391
|
+
top: clamp(top, minimumTop, maximumTop),
|
|
392
|
+
...targetSize
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function getPortalLayerPositions(expanded, source, target) {
|
|
396
|
+
const sourceFromTarget = {
|
|
397
|
+
left: source.left - target.left,
|
|
398
|
+
top: source.top - target.top
|
|
399
|
+
};
|
|
400
|
+
const targetFromSource = {
|
|
401
|
+
left: target.left - source.left,
|
|
402
|
+
top: target.top - source.top
|
|
403
|
+
};
|
|
404
|
+
const origin = { left: 0, top: 0 };
|
|
405
|
+
return expanded ? {
|
|
406
|
+
sourcePosition: sourceFromTarget,
|
|
407
|
+
targetPosition: origin
|
|
408
|
+
} : {
|
|
409
|
+
sourcePosition: origin,
|
|
410
|
+
targetPosition: targetFromSource
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
327
414
|
// src/component.tsx
|
|
328
415
|
import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
329
416
|
var liveSurfaceContentClassName = "rmorph:absolute";
|
|
417
|
+
var MorphMeasurementContext = createContext(false);
|
|
418
|
+
function mergeClassNames(...classNames) {
|
|
419
|
+
return classNames.filter(Boolean).join(" ");
|
|
420
|
+
}
|
|
421
|
+
function getViewportBounds() {
|
|
422
|
+
const viewport = window.visualViewport;
|
|
423
|
+
if (viewport) {
|
|
424
|
+
return {
|
|
425
|
+
left: viewport.offsetLeft,
|
|
426
|
+
top: viewport.offsetTop,
|
|
427
|
+
width: viewport.width,
|
|
428
|
+
height: viewport.height
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
return {
|
|
432
|
+
left: 0,
|
|
433
|
+
top: 0,
|
|
434
|
+
width: document.documentElement.clientWidth,
|
|
435
|
+
height: document.documentElement.clientHeight
|
|
436
|
+
};
|
|
437
|
+
}
|
|
330
438
|
function MorphMeasurementNodes({
|
|
331
439
|
collapsedRef,
|
|
332
440
|
expandedRef,
|
|
333
441
|
collapsedContent,
|
|
334
442
|
expandedContent
|
|
335
443
|
}) {
|
|
336
|
-
|
|
444
|
+
const measurementStyle = {
|
|
445
|
+
overflow: "clip"
|
|
446
|
+
};
|
|
447
|
+
return /* @__PURE__ */ jsxs(MorphMeasurementContext.Provider, { value: true, children: [
|
|
337
448
|
/* @__PURE__ */ jsx2(
|
|
338
449
|
"div",
|
|
339
450
|
{
|
|
340
451
|
ref: collapsedRef,
|
|
341
|
-
|
|
452
|
+
inert: true,
|
|
342
453
|
className: "rmorph:invisible rmorph:absolute rmorph:inset-x-0 rmorph:top-0 rmorph:pointer-events-none",
|
|
454
|
+
style: measurementStyle,
|
|
343
455
|
children: collapsedContent
|
|
344
456
|
}
|
|
345
457
|
),
|
|
@@ -347,8 +459,9 @@ function MorphMeasurementNodes({
|
|
|
347
459
|
"div",
|
|
348
460
|
{
|
|
349
461
|
ref: expandedRef,
|
|
350
|
-
|
|
462
|
+
inert: true,
|
|
351
463
|
className: "rmorph:invisible rmorph:absolute rmorph:inset-x-0 rmorph:top-0 rmorph:pointer-events-none",
|
|
464
|
+
style: measurementStyle,
|
|
352
465
|
children: expandedContent
|
|
353
466
|
}
|
|
354
467
|
)
|
|
@@ -364,12 +477,13 @@ function MorphShell({
|
|
|
364
477
|
spring,
|
|
365
478
|
onMorphComplete,
|
|
366
479
|
onMorphStart,
|
|
367
|
-
children
|
|
480
|
+
children,
|
|
481
|
+
className
|
|
368
482
|
}) {
|
|
369
483
|
return /* @__PURE__ */ jsx2(
|
|
370
484
|
motion2.div,
|
|
371
485
|
{
|
|
372
|
-
className: "rmorph:absolute rmorph:shadow-xs",
|
|
486
|
+
className: mergeClassNames("rmorph:absolute rmorph:shadow-xs", className),
|
|
373
487
|
initial: false,
|
|
374
488
|
animate: {
|
|
375
489
|
width: animatedSize.width,
|
|
@@ -389,6 +503,51 @@ function MorphShell({
|
|
|
389
503
|
}
|
|
390
504
|
);
|
|
391
505
|
}
|
|
506
|
+
function PortalMorphShell({
|
|
507
|
+
expanded,
|
|
508
|
+
lifecyclePhase,
|
|
509
|
+
geometry,
|
|
510
|
+
collapsedVisualStyle,
|
|
511
|
+
expandedVisualStyle,
|
|
512
|
+
overflow,
|
|
513
|
+
zIndex,
|
|
514
|
+
spring,
|
|
515
|
+
onMorphComplete,
|
|
516
|
+
onMorphStart,
|
|
517
|
+
children,
|
|
518
|
+
className
|
|
519
|
+
}) {
|
|
520
|
+
const rect = expanded ? geometry.target : geometry.source;
|
|
521
|
+
const visualStyle = expanded ? expandedVisualStyle : collapsedVisualStyle;
|
|
522
|
+
return /* @__PURE__ */ jsx2(
|
|
523
|
+
motion2.div,
|
|
524
|
+
{
|
|
525
|
+
"data-morpheus-portal-shell": "",
|
|
526
|
+
"data-morpheus-phase": lifecyclePhase,
|
|
527
|
+
inert: lifecyclePhase === "handoff" || void 0,
|
|
528
|
+
className: mergeClassNames("rmorph:fixed rmorph:shadow-xs", className),
|
|
529
|
+
initial: false,
|
|
530
|
+
animate: {
|
|
531
|
+
left: rect.left,
|
|
532
|
+
top: rect.top,
|
|
533
|
+
width: rect.width,
|
|
534
|
+
height: rect.height,
|
|
535
|
+
backgroundColor: visualStyle.backgroundColor,
|
|
536
|
+
borderRadius: visualStyle.borderRadius
|
|
537
|
+
},
|
|
538
|
+
onAnimationComplete: onMorphComplete,
|
|
539
|
+
...onMorphStart ? { onAnimationStart: onMorphStart } : {},
|
|
540
|
+
style: {
|
|
541
|
+
overflow,
|
|
542
|
+
overscrollBehavior: "contain",
|
|
543
|
+
pointerEvents: lifecyclePhase === "handoff" ? "none" : void 0,
|
|
544
|
+
zIndex
|
|
545
|
+
},
|
|
546
|
+
transition: spring,
|
|
547
|
+
children
|
|
548
|
+
}
|
|
549
|
+
);
|
|
550
|
+
}
|
|
392
551
|
function MorphContentLayers({
|
|
393
552
|
expanded,
|
|
394
553
|
settledState,
|
|
@@ -461,8 +620,26 @@ function MorphContentLayers({
|
|
|
461
620
|
)
|
|
462
621
|
] });
|
|
463
622
|
}
|
|
464
|
-
function
|
|
465
|
-
|
|
623
|
+
function MeasurementTreePlaceholder({
|
|
624
|
+
className,
|
|
625
|
+
collapsedContent
|
|
626
|
+
}) {
|
|
627
|
+
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 }) }) });
|
|
628
|
+
}
|
|
629
|
+
function Morpheus(props) {
|
|
630
|
+
const insideMeasurementTree = useContext(MorphMeasurementContext);
|
|
631
|
+
if (insideMeasurementTree) {
|
|
632
|
+
return /* @__PURE__ */ jsx2(
|
|
633
|
+
MeasurementTreePlaceholder,
|
|
634
|
+
{
|
|
635
|
+
className: props.className ?? "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
636
|
+
collapsedContent: props.collapsedContent
|
|
637
|
+
}
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return /* @__PURE__ */ jsx2(MorpheusRoot, { ...props });
|
|
641
|
+
}
|
|
642
|
+
function MorpheusRoot({
|
|
466
643
|
anchor,
|
|
467
644
|
expanded,
|
|
468
645
|
onClose,
|
|
@@ -471,6 +648,9 @@ function Morpheus({
|
|
|
471
648
|
collapsedContent,
|
|
472
649
|
expandedContent,
|
|
473
650
|
className = "rmorph:relative rmorph:inline-block rmorph:align-top",
|
|
651
|
+
shellClassName,
|
|
652
|
+
portalContainer,
|
|
653
|
+
viewportPadding = 16,
|
|
474
654
|
overlayColor = "#05070a",
|
|
475
655
|
overlayOpacity = 0.54,
|
|
476
656
|
overlayBlur = 0,
|
|
@@ -478,13 +658,51 @@ function Morpheus({
|
|
|
478
658
|
spring = defaultPanelSpring
|
|
479
659
|
}) {
|
|
480
660
|
const measurements = useMorphMeasurements();
|
|
661
|
+
const anchorRef = useRef2(null);
|
|
662
|
+
const [defaultPortalContainer, setDefaultPortalContainer] = useState2(null);
|
|
481
663
|
const [animationEnabled, setAnimationEnabled] = useState2(false);
|
|
482
664
|
const [shellOverflowVisible, setShellOverflowVisible] = useState2(false);
|
|
483
|
-
const [
|
|
665
|
+
const [inlineSettledState, setInlineSettledState] = useState2(expanded ? "expanded" : "collapsed");
|
|
666
|
+
const [portalPhase, setPortalPhase] = useState2(
|
|
484
667
|
expanded ? "expanded" : "collapsed"
|
|
485
668
|
);
|
|
669
|
+
const [portalVisualExpanded, setPortalVisualExpanded] = useState2(expanded);
|
|
670
|
+
const [portalGeometry, setPortalGeometry] = useState2(
|
|
671
|
+
null
|
|
672
|
+
);
|
|
486
673
|
const hasOpenedRef = useRef2(expanded);
|
|
487
674
|
const beforeCloseCalledRef = useRef2(false);
|
|
675
|
+
const beforeCloseRef = useRef2(beforeClose);
|
|
676
|
+
const afterCloseRef = useRef2(afterClose);
|
|
677
|
+
const previousExpandedRef = useRef2(expanded);
|
|
678
|
+
const portalStartFrameRef = useRef2(null);
|
|
679
|
+
const handoffFirstFrameRef = useRef2(null);
|
|
680
|
+
const handoffSecondFrameRef = useRef2(null);
|
|
681
|
+
const portalPhaseRef = useRef2(portalPhase);
|
|
682
|
+
const expandedRef = useRef2(expanded);
|
|
683
|
+
portalPhaseRef.current = portalPhase;
|
|
684
|
+
expandedRef.current = expanded;
|
|
685
|
+
beforeCloseRef.current = beforeClose;
|
|
686
|
+
afterCloseRef.current = afterClose;
|
|
687
|
+
const cancelPortalFrames = useCallback(() => {
|
|
688
|
+
if (portalStartFrameRef.current !== null) {
|
|
689
|
+
cancelAnimationFrame(portalStartFrameRef.current);
|
|
690
|
+
portalStartFrameRef.current = null;
|
|
691
|
+
}
|
|
692
|
+
if (handoffFirstFrameRef.current !== null) {
|
|
693
|
+
cancelAnimationFrame(handoffFirstFrameRef.current);
|
|
694
|
+
handoffFirstFrameRef.current = null;
|
|
695
|
+
}
|
|
696
|
+
if (handoffSecondFrameRef.current !== null) {
|
|
697
|
+
cancelAnimationFrame(handoffSecondFrameRef.current);
|
|
698
|
+
handoffSecondFrameRef.current = null;
|
|
699
|
+
}
|
|
700
|
+
}, []);
|
|
701
|
+
useEffect(() => {
|
|
702
|
+
if (portalContainer === void 0) {
|
|
703
|
+
setDefaultPortalContainer(document.body);
|
|
704
|
+
}
|
|
705
|
+
}, [portalContainer]);
|
|
488
706
|
useEffect(() => {
|
|
489
707
|
if (!measurements.ready) {
|
|
490
708
|
return;
|
|
@@ -492,113 +710,339 @@ function Morpheus({
|
|
|
492
710
|
const frame = requestAnimationFrame(() => setAnimationEnabled(true));
|
|
493
711
|
return () => cancelAnimationFrame(frame);
|
|
494
712
|
}, [measurements.ready]);
|
|
495
|
-
const
|
|
713
|
+
const resolvedPortalContainer = portalContainer === void 0 ? defaultPortalContainer : portalContainer;
|
|
714
|
+
const portalEnabled = resolvedPortalContainer !== null;
|
|
715
|
+
const panelAnchor = anchor;
|
|
716
|
+
const updatePortalGeometry = useCallback(() => {
|
|
717
|
+
const sourceElement = anchorRef.current;
|
|
718
|
+
if (!sourceElement || !measurements.ready) {
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
const sourceBounds = sourceElement.getBoundingClientRect();
|
|
722
|
+
const source = {
|
|
723
|
+
left: sourceBounds.left,
|
|
724
|
+
top: sourceBounds.top,
|
|
725
|
+
width: sourceBounds.width,
|
|
726
|
+
height: sourceBounds.height
|
|
727
|
+
};
|
|
728
|
+
const viewport = getViewportBounds();
|
|
729
|
+
const targetSize = getBoundedPanelSize(
|
|
730
|
+
measurements.expanded.size,
|
|
731
|
+
viewport,
|
|
732
|
+
viewportPadding
|
|
733
|
+
);
|
|
734
|
+
const target = getAnchoredTargetRect(
|
|
735
|
+
panelAnchor,
|
|
736
|
+
source,
|
|
737
|
+
targetSize,
|
|
738
|
+
viewport,
|
|
739
|
+
viewportPadding
|
|
740
|
+
);
|
|
741
|
+
setPortalGeometry((current) => {
|
|
742
|
+
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) {
|
|
743
|
+
return current;
|
|
744
|
+
}
|
|
745
|
+
return { source, target };
|
|
746
|
+
});
|
|
747
|
+
}, [
|
|
748
|
+
measurements.expanded.size,
|
|
749
|
+
measurements.ready,
|
|
750
|
+
panelAnchor,
|
|
751
|
+
viewportPadding
|
|
752
|
+
]);
|
|
753
|
+
useLayoutEffect2(() => {
|
|
754
|
+
if (!portalEnabled || !measurements.ready) {
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
const expandedChanged = previousExpandedRef.current !== expanded;
|
|
758
|
+
previousExpandedRef.current = expanded;
|
|
759
|
+
updatePortalGeometry();
|
|
760
|
+
if (!expandedChanged) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
cancelPortalFrames();
|
|
764
|
+
if (expanded) {
|
|
765
|
+
setPortalPhase("opening");
|
|
766
|
+
setPortalVisualExpanded(false);
|
|
767
|
+
portalStartFrameRef.current = requestAnimationFrame(() => {
|
|
768
|
+
portalStartFrameRef.current = null;
|
|
769
|
+
if (!expandedRef.current || portalPhaseRef.current !== "opening") {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
setPortalVisualExpanded(true);
|
|
773
|
+
});
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (portalPhaseRef.current !== "collapsed" && portalPhaseRef.current !== "closing") {
|
|
777
|
+
setPortalPhase("closing");
|
|
778
|
+
setPortalVisualExpanded(false);
|
|
779
|
+
if (hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
780
|
+
beforeCloseCalledRef.current = true;
|
|
781
|
+
beforeCloseRef.current?.();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
}, [
|
|
785
|
+
cancelPortalFrames,
|
|
496
786
|
expanded,
|
|
497
|
-
|
|
787
|
+
measurements.ready,
|
|
788
|
+
portalEnabled,
|
|
789
|
+
updatePortalGeometry
|
|
790
|
+
]);
|
|
791
|
+
useLayoutEffect2(() => {
|
|
792
|
+
if (portalPhase !== "handoff" || expanded) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
handoffFirstFrameRef.current = requestAnimationFrame(() => {
|
|
796
|
+
handoffFirstFrameRef.current = null;
|
|
797
|
+
if (expandedRef.current || portalPhaseRef.current !== "handoff") {
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
handoffSecondFrameRef.current = requestAnimationFrame(() => {
|
|
801
|
+
handoffSecondFrameRef.current = null;
|
|
802
|
+
if (expandedRef.current || portalPhaseRef.current !== "handoff") {
|
|
803
|
+
return;
|
|
804
|
+
}
|
|
805
|
+
setPortalPhase("collapsed");
|
|
806
|
+
setPortalGeometry(null);
|
|
807
|
+
if (hasOpenedRef.current) {
|
|
808
|
+
hasOpenedRef.current = false;
|
|
809
|
+
afterCloseRef.current?.();
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
return cancelPortalFrames;
|
|
814
|
+
}, [cancelPortalFrames, expanded, portalPhase]);
|
|
815
|
+
useEffect(() => {
|
|
816
|
+
if (!portalEnabled || portalPhase === "collapsed" || !measurements.ready) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
let frame = null;
|
|
820
|
+
const scheduleUpdate = () => {
|
|
821
|
+
if (frame !== null) {
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
frame = requestAnimationFrame(() => {
|
|
825
|
+
frame = null;
|
|
826
|
+
updatePortalGeometry();
|
|
827
|
+
});
|
|
828
|
+
};
|
|
829
|
+
const observer = new ResizeObserver(scheduleUpdate);
|
|
830
|
+
if (anchorRef.current) {
|
|
831
|
+
observer.observe(anchorRef.current);
|
|
832
|
+
}
|
|
833
|
+
window.addEventListener("scroll", scheduleUpdate, true);
|
|
834
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
835
|
+
window.visualViewport?.addEventListener("resize", scheduleUpdate);
|
|
836
|
+
window.visualViewport?.addEventListener("scroll", scheduleUpdate);
|
|
837
|
+
return () => {
|
|
838
|
+
if (frame !== null) {
|
|
839
|
+
cancelAnimationFrame(frame);
|
|
840
|
+
}
|
|
841
|
+
observer.disconnect();
|
|
842
|
+
window.removeEventListener("scroll", scheduleUpdate, true);
|
|
843
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
844
|
+
window.visualViewport?.removeEventListener("resize", scheduleUpdate);
|
|
845
|
+
window.visualViewport?.removeEventListener("scroll", scheduleUpdate);
|
|
846
|
+
};
|
|
847
|
+
}, [measurements.ready, portalEnabled, portalPhase, updatePortalGeometry]);
|
|
848
|
+
useEffect(() => cancelPortalFrames, [cancelPortalFrames]);
|
|
849
|
+
const motionState = getMorphMotionState({
|
|
850
|
+
expanded: portalEnabled ? portalVisualExpanded : expanded,
|
|
498
851
|
anchor,
|
|
499
852
|
spring,
|
|
500
853
|
animationEnabled,
|
|
501
854
|
collapsed: measurements.collapsed,
|
|
502
855
|
expandedSurface: measurements.expanded
|
|
503
856
|
});
|
|
504
|
-
const overflowVisible = expanded && shellOverflowVisible;
|
|
505
857
|
const shouldRenderStaticSource = !measurements.ready && !expanded;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
858
|
+
const portalPresent = portalPhase !== "collapsed";
|
|
859
|
+
const portalIsRendered = portalEnabled && portalPresent && portalGeometry !== null && resolvedPortalContainer !== null;
|
|
860
|
+
const inlineSourceHidden = portalIsRendered && portalPhase !== "handoff";
|
|
861
|
+
const portalSettledState = portalPhase === "expanded" || portalPhase === "closing" ? "expanded" : "collapsed";
|
|
862
|
+
const portalTargetIsBounded = portalGeometry !== null && (portalGeometry.target.width < measurements.expanded.size.width || portalGeometry.target.height < measurements.expanded.size.height);
|
|
863
|
+
const contentLayers = (surfaceExpanded, settledState, portalMode = false) => {
|
|
864
|
+
const layerPositions = portalMode && portalGeometry ? getPortalLayerPositions(
|
|
865
|
+
surfaceExpanded,
|
|
866
|
+
portalGeometry.source,
|
|
867
|
+
portalGeometry.target
|
|
868
|
+
) : {
|
|
869
|
+
sourcePosition: motionState.sourcePosition,
|
|
870
|
+
targetPosition: motionState.targetPosition
|
|
871
|
+
};
|
|
872
|
+
const sourceGrowthScale = portalMode && portalGeometry ? getAxisScale(
|
|
873
|
+
motionState.collapsedLayerSize,
|
|
874
|
+
portalGeometry.target
|
|
875
|
+
) : motionState.sourceGrowthScale;
|
|
876
|
+
const collapsedToExpandedScale = portalMode && portalGeometry ? getAxisScale(
|
|
877
|
+
motionState.expandedLayerSize,
|
|
878
|
+
portalGeometry.source
|
|
879
|
+
) : motionState.collapsedToExpandedScale;
|
|
880
|
+
return /* @__PURE__ */ jsx2(
|
|
881
|
+
MorphContentLayers,
|
|
509
882
|
{
|
|
510
|
-
expanded,
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
883
|
+
expanded: surfaceExpanded,
|
|
884
|
+
settledState,
|
|
885
|
+
collapsedContent,
|
|
886
|
+
expandedContent,
|
|
887
|
+
collapsedLayerSize: motionState.collapsedLayerSize,
|
|
888
|
+
expandedLayerSize: motionState.expandedLayerSize,
|
|
889
|
+
sourcePosition: layerPositions.sourcePosition,
|
|
890
|
+
targetPosition: layerPositions.targetPosition,
|
|
891
|
+
sourceGrowthScale,
|
|
892
|
+
collapsedToExpandedScale,
|
|
893
|
+
transformOrigin: motionState.transformOrigin,
|
|
894
|
+
sourceTransition: motionState.sourceTransition,
|
|
895
|
+
sourceOpacityTransition: motionState.sourceOpacityTransition,
|
|
896
|
+
targetTransition: motionState.targetTransition,
|
|
897
|
+
targetOpacityTransition: motionState.targetOpacityTransition
|
|
516
898
|
}
|
|
517
|
-
)
|
|
518
|
-
|
|
899
|
+
);
|
|
900
|
+
};
|
|
901
|
+
const measurementNodes = /* @__PURE__ */ jsx2(
|
|
902
|
+
MorphMeasurementNodes,
|
|
903
|
+
{
|
|
904
|
+
collapsedRef: measurements.collapsedRef,
|
|
905
|
+
expandedRef: measurements.expandedRef,
|
|
906
|
+
collapsedContent,
|
|
907
|
+
expandedContent
|
|
908
|
+
}
|
|
909
|
+
);
|
|
910
|
+
if (!portalEnabled && portalContainer === null) {
|
|
911
|
+
const overflowVisible = expanded && shellOverflowVisible;
|
|
912
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
519
913
|
/* @__PURE__ */ jsx2(
|
|
520
|
-
|
|
914
|
+
MorphOverlay,
|
|
521
915
|
{
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
916
|
+
expanded,
|
|
917
|
+
onClose,
|
|
918
|
+
color: overlayColor,
|
|
919
|
+
opacity: overlayOpacity,
|
|
920
|
+
blur: overlayBlur,
|
|
921
|
+
zIndex
|
|
526
922
|
}
|
|
527
923
|
),
|
|
528
|
-
/* @__PURE__ */
|
|
924
|
+
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
925
|
+
measurementNodes,
|
|
926
|
+
/* @__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: [
|
|
927
|
+
/* @__PURE__ */ jsx2(
|
|
928
|
+
"div",
|
|
929
|
+
{
|
|
930
|
+
"aria-hidden": "true",
|
|
931
|
+
style: {
|
|
932
|
+
visibility: "hidden",
|
|
933
|
+
width: motionState.collapsedLayerSize.width,
|
|
934
|
+
height: motionState.collapsedLayerSize.height
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
),
|
|
938
|
+
/* @__PURE__ */ jsx2(
|
|
939
|
+
MorphShell,
|
|
940
|
+
{
|
|
941
|
+
expanded,
|
|
942
|
+
position: motionState.anchorPosition,
|
|
943
|
+
animatedSize: motionState.animatedSize,
|
|
944
|
+
visualStyle: motionState.visualStyle,
|
|
945
|
+
overflowVisible,
|
|
946
|
+
zIndex,
|
|
947
|
+
spring: motionState.activeSpring,
|
|
948
|
+
className: shellClassName,
|
|
949
|
+
onMorphComplete: () => {
|
|
950
|
+
if (expanded) {
|
|
951
|
+
hasOpenedRef.current = true;
|
|
952
|
+
beforeCloseCalledRef.current = false;
|
|
953
|
+
setInlineSettledState("expanded");
|
|
954
|
+
setShellOverflowVisible(true);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
setInlineSettledState("collapsed");
|
|
958
|
+
if (hasOpenedRef.current) {
|
|
959
|
+
hasOpenedRef.current = false;
|
|
960
|
+
afterClose?.();
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
onMorphStart: () => {
|
|
964
|
+
if (!expanded && hasOpenedRef.current && !beforeCloseCalledRef.current) {
|
|
965
|
+
beforeCloseCalledRef.current = true;
|
|
966
|
+
beforeClose?.();
|
|
967
|
+
}
|
|
968
|
+
setShellOverflowVisible(false);
|
|
969
|
+
setInlineSettledState(expanded ? "collapsed" : "expanded");
|
|
970
|
+
},
|
|
971
|
+
children: contentLayers(expanded, inlineSettledState)
|
|
972
|
+
}
|
|
973
|
+
)
|
|
974
|
+
] }) })
|
|
975
|
+
] })
|
|
976
|
+
] });
|
|
977
|
+
}
|
|
978
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
979
|
+
/* @__PURE__ */ jsxs("div", { className, "aria-live": "polite", children: [
|
|
980
|
+
measurementNodes,
|
|
981
|
+
/* @__PURE__ */ jsx2("div", { className: "rmorph:relative rmorph:mx-auto rmorph:block rmorph:w-fit rmorph:align-top", children: /* @__PURE__ */ jsx2(
|
|
982
|
+
"div",
|
|
983
|
+
{
|
|
984
|
+
ref: anchorRef,
|
|
985
|
+
"data-morpheus-inline-source": "",
|
|
986
|
+
...getLayerInteractivityProps(!inlineSourceHidden),
|
|
987
|
+
className: mergeClassNames(
|
|
988
|
+
"rmorph:inline-block rmorph:align-top rmorph:shadow-xs",
|
|
989
|
+
shellClassName
|
|
990
|
+
),
|
|
991
|
+
style: {
|
|
992
|
+
visibility: inlineSourceHidden ? "hidden" : void 0,
|
|
993
|
+
pointerEvents: inlineSourceHidden ? "none" : void 0
|
|
994
|
+
},
|
|
995
|
+
children: collapsedContent
|
|
996
|
+
}
|
|
997
|
+
) })
|
|
998
|
+
] }),
|
|
999
|
+
portalIsRendered && portalGeometry && resolvedPortalContainer ? createPortal(
|
|
1000
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
529
1001
|
/* @__PURE__ */ jsx2(
|
|
530
|
-
|
|
1002
|
+
MorphOverlay,
|
|
531
1003
|
{
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
1004
|
+
expanded,
|
|
1005
|
+
onClose,
|
|
1006
|
+
color: overlayColor,
|
|
1007
|
+
opacity: overlayOpacity,
|
|
1008
|
+
blur: overlayBlur,
|
|
1009
|
+
zIndex
|
|
538
1010
|
}
|
|
539
1011
|
),
|
|
540
1012
|
/* @__PURE__ */ jsx2(
|
|
541
|
-
|
|
1013
|
+
PortalMorphShell,
|
|
542
1014
|
{
|
|
543
|
-
expanded,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
1015
|
+
expanded: portalVisualExpanded,
|
|
1016
|
+
lifecyclePhase: portalPhase,
|
|
1017
|
+
geometry: portalGeometry,
|
|
1018
|
+
collapsedVisualStyle: measurements.collapsed.visualStyle,
|
|
1019
|
+
expandedVisualStyle: measurements.expanded.visualStyle,
|
|
1020
|
+
overflow: portalPhase === "expanded" ? portalTargetIsBounded ? "auto" : "visible" : "hidden",
|
|
548
1021
|
zIndex,
|
|
549
1022
|
spring: motionState.activeSpring,
|
|
1023
|
+
className: shellClassName,
|
|
550
1024
|
onMorphComplete: () => {
|
|
551
|
-
if (
|
|
1025
|
+
if (expandedRef.current && portalPhaseRef.current === "opening" && portalVisualExpanded) {
|
|
552
1026
|
hasOpenedRef.current = true;
|
|
553
1027
|
beforeCloseCalledRef.current = false;
|
|
554
|
-
|
|
555
|
-
setShellOverflowVisible(true);
|
|
1028
|
+
setPortalPhase("expanded");
|
|
556
1029
|
return;
|
|
557
1030
|
}
|
|
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?.();
|
|
1031
|
+
if (!expandedRef.current && portalPhaseRef.current === "closing" && !portalVisualExpanded) {
|
|
1032
|
+
setPortalPhase("handoff");
|
|
568
1033
|
}
|
|
569
|
-
setShellOverflowVisible(false);
|
|
570
|
-
setSettledState(expanded ? "collapsed" : "expanded");
|
|
571
1034
|
},
|
|
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
|
-
)
|
|
1035
|
+
children: contentLayers(portalVisualExpanded, portalSettledState, true)
|
|
592
1036
|
}
|
|
593
1037
|
)
|
|
594
|
-
] })
|
|
595
|
-
|
|
1038
|
+
] }),
|
|
1039
|
+
resolvedPortalContainer
|
|
1040
|
+
) : null
|
|
596
1041
|
] });
|
|
597
1042
|
}
|
|
598
1043
|
export {
|
|
599
1044
|
MorphAnchor,
|
|
600
1045
|
Morpheus,
|
|
601
1046
|
Morpheus as default,
|
|
602
|
-
defaultAnchorByDirection,
|
|
603
1047
|
morphSpringPresets
|
|
604
1048
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-morpheus",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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",
|