@kitlangton/solid-motion 0.1.0
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/LICENSE +48 -0
- package/README.md +54 -0
- package/dist/animation/animate/index.d.ts +23 -0
- package/dist/animation/animate/resolve-subjects.d.ts +3 -0
- package/dist/animation/animate/sequence.d.ts +3 -0
- package/dist/animation/animate/subject.d.ts +7 -0
- package/dist/animation/sequence/create.d.ts +6 -0
- package/dist/animation/sequence/types.d.ts +51 -0
- package/dist/animation/sequence/utils/calc-repeat-duration.d.ts +1 -0
- package/dist/animation/sequence/utils/calc-time.d.ts +2 -0
- package/dist/animation/sequence/utils/edit.d.ts +5 -0
- package/dist/animation/sequence/utils/normalize-times.d.ts +1 -0
- package/dist/animation/sequence/utils/sort.d.ts +2 -0
- package/dist/animation/utils/create-visual-element.d.ts +2 -0
- package/dist/animation/utils/is-dom-keyframes.d.ts +2 -0
- package/dist/components/AnimatePresence.d.ts +27 -0
- package/dist/components/AnimatePresenceContext.d.ts +23 -0
- package/dist/components/AnimatePresenceTypes.d.ts +57 -0
- package/dist/components/MotionConfig.d.ts +16 -0
- package/dist/context/MotionContext/create.d.ts +3 -0
- package/dist/context/MotionContext/utils.d.ts +3 -0
- package/dist/context/MotionContext.d.ts +7 -0
- package/dist/context/PresenceContext.d.ts +13 -0
- package/dist/events/add-pointer-event.d.ts +2 -0
- package/dist/events/event-info.d.ts +4 -0
- package/dist/gestures/drag/VisualElementDragControls.d.ts +86 -0
- package/dist/gestures/drag/index.d.ts +11 -0
- package/dist/gestures/drag/utils/constraints.d.ts +77 -0
- package/dist/gestures/focus.d.ts +8 -0
- package/dist/gestures/hover.d.ts +5 -0
- package/dist/gestures/pan/PanSession.d.ts +102 -0
- package/dist/gestures/pan/index.d.ts +15 -0
- package/dist/gestures/press.d.ts +5 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +2466 -0
- package/dist/index.js.map +1 -0
- package/dist/motion/features/animation/exit.d.ts +7 -0
- package/dist/motion/features/animation/index.d.ts +9 -0
- package/dist/motion/features/animations.d.ts +10 -0
- package/dist/motion/features/definitions.d.ts +3 -0
- package/dist/motion/features/feature-bundle.d.ts +34 -0
- package/dist/motion/features/gestures.d.ts +26 -0
- package/dist/motion/features/load-features.d.ts +1 -0
- package/dist/motion/index.d.ts +22 -0
- package/dist/motion/types.d.ts +27 -0
- package/dist/motion/utils/drag-projection.d.ts +3 -0
- package/dist/motion/utils/use-visual-state.d.ts +14 -0
- package/dist/motion/utils/valid-prop.d.ts +1 -0
- package/dist/render/dom/create-visual-element.d.ts +2 -0
- package/dist/render/dom/filter-props.d.ts +2 -0
- package/dist/render/dom/is-svg-component.d.ts +1 -0
- package/dist/render/dom/style.d.ts +1 -0
- package/dist/render/html/use-props.d.ts +7 -0
- package/dist/render/html/utils/create-render-state.d.ts +2 -0
- package/dist/render/svg/lowercase-elements.d.ts +1 -0
- package/dist/render/svg/use-props.d.ts +3 -0
- package/dist/render/svg/utils/create-render-state.d.ts +2 -0
- package/dist/render/types.d.ts +3 -0
- package/dist/server.js +2407 -0
- package/dist/server.js.map +1 -0
- package/dist/source/animation/animate/index.js +39 -0
- package/dist/source/animation/animate/resolve-subjects.js +19 -0
- package/dist/source/animation/animate/sequence.js +11 -0
- package/dist/source/animation/animate/subject.js +37 -0
- package/dist/source/animation/sequence/create.js +171 -0
- package/dist/source/animation/sequence/types.js +1 -0
- package/dist/source/animation/sequence/utils/calc-repeat-duration.js +3 -0
- package/dist/source/animation/sequence/utils/calc-time.js +17 -0
- package/dist/source/animation/sequence/utils/edit.js +21 -0
- package/dist/source/animation/sequence/utils/normalize-times.js +5 -0
- package/dist/source/animation/sequence/utils/sort.js +12 -0
- package/dist/source/animation/utils/create-visual-element.js +37 -0
- package/dist/source/animation/utils/is-dom-keyframes.js +3 -0
- package/dist/source/components/AnimatePresence.jsx +444 -0
- package/dist/source/components/AnimatePresenceContext.jsx +20 -0
- package/dist/source/components/AnimatePresenceTypes.js +1 -0
- package/dist/source/components/MotionConfig.jsx +13 -0
- package/dist/source/context/MotionContext/create.js +19 -0
- package/dist/source/context/MotionContext/utils.js +11 -0
- package/dist/source/context/MotionContext.js +2 -0
- package/dist/source/context/PresenceContext.js +2 -0
- package/dist/source/events/add-pointer-event.js +5 -0
- package/dist/source/events/event-info.js +16 -0
- package/dist/source/gestures/drag/VisualElementDragControls.js +531 -0
- package/dist/source/gestures/drag/index.js +47 -0
- package/dist/source/gestures/drag/utils/constraints.js +160 -0
- package/dist/source/gestures/focus.js +37 -0
- package/dist/source/gestures/hover.js +25 -0
- package/dist/source/gestures/pan/PanSession.js +273 -0
- package/dist/source/gestures/pan/index.js +46 -0
- package/dist/source/gestures/press.js +28 -0
- package/dist/source/index.js +11 -0
- package/dist/source/motion/features/animation/exit.js +33 -0
- package/dist/source/motion/features/animation/index.js +27 -0
- package/dist/source/motion/features/animations.js +10 -0
- package/dist/source/motion/features/definitions.js +29 -0
- package/dist/source/motion/features/feature-bundle.js +6 -0
- package/dist/source/motion/features/gestures.js +20 -0
- package/dist/source/motion/features/load-features.js +12 -0
- package/dist/source/motion/index.jsx +176 -0
- package/dist/source/motion/types.js +1 -0
- package/dist/source/motion/utils/drag-projection.js +16 -0
- package/dist/source/motion/utils/use-visual-state.js +53 -0
- package/dist/source/motion/utils/valid-prop.js +40 -0
- package/dist/source/render/dom/create-visual-element.js +10 -0
- package/dist/source/render/dom/filter-props.js +15 -0
- package/dist/source/render/dom/is-svg-component.js +10 -0
- package/dist/source/render/dom/style.js +12 -0
- package/dist/source/render/html/use-props.js +39 -0
- package/dist/source/render/html/utils/create-render-state.js +6 -0
- package/dist/source/render/svg/lowercase-elements.js +27 -0
- package/dist/source/render/svg/use-props.js +28 -0
- package/dist/source/render/svg/utils/create-render-state.js +5 -0
- package/dist/source/render/types.js +1 -0
- package/dist/source/utils/distance.js +7 -0
- package/dist/source/utils/get-context-window.js +4 -0
- package/dist/source/utils/is-ref-object.js +5 -0
- package/dist/source/utils/safe-react-types.js +1 -0
- package/dist/source/utils/use-reduced-motion.js +15 -0
- package/dist/source/value/use-motion-value-event.js +5 -0
- package/dist/source/value/use-motion-value.js +16 -0
- package/dist/source/value/use-spring.js +59 -0
- package/dist/source/value/use-transform.js +25 -0
- package/dist/source/value/use-velocity.js +17 -0
- package/dist/utils/distance.d.ts +3 -0
- package/dist/utils/get-context-window.d.ts +2 -0
- package/dist/utils/is-ref-object.d.ts +2 -0
- package/dist/utils/safe-react-types.d.ts +3 -0
- package/dist/utils/use-reduced-motion.d.ts +6 -0
- package/dist/value/use-motion-value-event.d.ts +3 -0
- package/dist/value/use-motion-value.d.ts +5 -0
- package/dist/value/use-spring.d.ts +5 -0
- package/dist/value/use-transform.d.ts +9 -0
- package/dist/value/use-velocity.d.ts +3 -0
- package/package.json +61 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { batch, children, createEffect, createMemo, createSignal, For, Index, mergeProps, on, onMount, splitProps, untrack, useContext } from "solid-js";
|
|
2
|
+
import { PresenceContext } from "../context/PresenceContext";
|
|
3
|
+
let presenceCounter = 0;
|
|
4
|
+
let autoKeyIndex = 0;
|
|
5
|
+
const autoKeyMap = new WeakMap();
|
|
6
|
+
/**
|
|
7
|
+
* AnimatePresence for Solid.js
|
|
8
|
+
*
|
|
9
|
+
* Enables exit animations by keeping elements in the DOM
|
|
10
|
+
* until their exit animations complete.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```solid
|
|
14
|
+
* <AnimatePresence>
|
|
15
|
+
* {show() && (
|
|
16
|
+
* <motion.div
|
|
17
|
+
* exit={{ opacity: 0 }}
|
|
18
|
+
* />
|
|
19
|
+
* )}
|
|
20
|
+
* </AnimatePresence>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function AnimatePresence(props) {
|
|
24
|
+
const merged = mergeProps({
|
|
25
|
+
initial: true,
|
|
26
|
+
mode: "sync",
|
|
27
|
+
presenceAffectsLayout: true,
|
|
28
|
+
}, props);
|
|
29
|
+
const [local, others] = splitProps(merged, [
|
|
30
|
+
"children",
|
|
31
|
+
"when",
|
|
32
|
+
"presenceKey",
|
|
33
|
+
"initial",
|
|
34
|
+
"onExitComplete",
|
|
35
|
+
"mode",
|
|
36
|
+
"presenceAffectsLayout",
|
|
37
|
+
"custom",
|
|
38
|
+
"anchorX",
|
|
39
|
+
"root",
|
|
40
|
+
]);
|
|
41
|
+
// Track if this is the initial render
|
|
42
|
+
let isInitialRender = true;
|
|
43
|
+
let didWarn = false;
|
|
44
|
+
const warnNonFactory = () => {
|
|
45
|
+
if (didWarn)
|
|
46
|
+
return;
|
|
47
|
+
didWarn = true;
|
|
48
|
+
console.warn("[AnimatePresence] Solid requires children to be a function for exit animations to work reliably. " +
|
|
49
|
+
"Use <AnimatePresence when={condition}>{() => <motion.div .../>}</AnimatePresence>.");
|
|
50
|
+
};
|
|
51
|
+
if (local.when !== undefined) {
|
|
52
|
+
const currentChild = createMemo(() => {
|
|
53
|
+
if (!local.when)
|
|
54
|
+
return undefined;
|
|
55
|
+
const childrenValue = local.children;
|
|
56
|
+
const getChild = () => {
|
|
57
|
+
let value = typeof childrenValue === "function" ? childrenValue() : childrenValue;
|
|
58
|
+
if (typeof value === "function" && value.length === 0) {
|
|
59
|
+
value = value();
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
};
|
|
63
|
+
if (typeof childrenValue !== "function") {
|
|
64
|
+
warnNonFactory();
|
|
65
|
+
}
|
|
66
|
+
return { key: local.presenceKey ?? "index-0", getChild };
|
|
67
|
+
});
|
|
68
|
+
const initialChild = currentChild();
|
|
69
|
+
const [rendered, setRendered] = createSignal(initialChild ? [initialChild] : []);
|
|
70
|
+
createEffect(on(currentChild, (next) => {
|
|
71
|
+
const previous = untrack(rendered);
|
|
72
|
+
const exiting = previous.filter((child) => child.key !== next?.key);
|
|
73
|
+
// Reuse the mounted record on reentry, so its exit can reverse in place.
|
|
74
|
+
const present = next && (previous.find((child) => child.key === next.key) ?? next);
|
|
75
|
+
setRendered(local.mode === "wait" && exiting.length
|
|
76
|
+
? exiting
|
|
77
|
+
: present ? [present, ...exiting] : exiting);
|
|
78
|
+
}, { defer: true }));
|
|
79
|
+
onMount(() => { isInitialRender = false; });
|
|
80
|
+
return (<For each={rendered()}>
|
|
81
|
+
{(child) => {
|
|
82
|
+
const isPresent = () => currentChild()?.key === child.key;
|
|
83
|
+
const onExitComplete = () => {
|
|
84
|
+
const next = untrack(currentChild);
|
|
85
|
+
const previous = untrack(rendered);
|
|
86
|
+
if (next?.key === child.key || !previous.includes(child))
|
|
87
|
+
return;
|
|
88
|
+
const remaining = previous.filter((item) => item !== child);
|
|
89
|
+
const hasExits = remaining.some((item) => item.key !== next?.key);
|
|
90
|
+
setRendered(!hasExits && next
|
|
91
|
+
? [remaining.find((item) => item.key === next.key) ?? next]
|
|
92
|
+
: remaining);
|
|
93
|
+
if (!hasExits)
|
|
94
|
+
local.onExitComplete?.();
|
|
95
|
+
};
|
|
96
|
+
return (<PresenceChild key={child.key} isPresent={isPresent} onExitComplete={isPresent() ? undefined : onExitComplete} initial={isInitialRender && !local.initial ? false : undefined} custom={local.custom} presenceAffectsLayout={local.presenceAffectsLayout} getChild={child.getChild} childKey={child.key} mode={local.mode} anchorX={local.anchorX} root={local.root}/>);
|
|
97
|
+
}}
|
|
98
|
+
</For>);
|
|
99
|
+
}
|
|
100
|
+
const resolveChildrenList = () => {
|
|
101
|
+
const childrenValue = local.children;
|
|
102
|
+
if (local.when !== undefined) {
|
|
103
|
+
if (!local.when)
|
|
104
|
+
return [];
|
|
105
|
+
if (typeof childrenValue !== "function") {
|
|
106
|
+
warnNonFactory();
|
|
107
|
+
}
|
|
108
|
+
const getChild = typeof childrenValue === "function" ? childrenValue : () => childrenValue;
|
|
109
|
+
const key = local.presenceKey ?? "index-0";
|
|
110
|
+
return [{ key, getChild }];
|
|
111
|
+
}
|
|
112
|
+
const child = typeof childrenValue === "function" ? childrenValue() : childrenValue;
|
|
113
|
+
if (!child || child === true)
|
|
114
|
+
return [];
|
|
115
|
+
const list = Array.isArray(child) ? child : [child];
|
|
116
|
+
return list
|
|
117
|
+
.filter((item) => item !== false && item != null)
|
|
118
|
+
.map((item, index) => ({
|
|
119
|
+
key: getChildKey(item, index),
|
|
120
|
+
getChild: () => item,
|
|
121
|
+
}));
|
|
122
|
+
};
|
|
123
|
+
// Track present children (filtered to elements only)
|
|
124
|
+
const presentChildren = createMemo(() => resolveChildrenList());
|
|
125
|
+
// Track which keys are currently present
|
|
126
|
+
const presentKeys = createMemo(() => {
|
|
127
|
+
return presentChildren().map((child) => child.key);
|
|
128
|
+
});
|
|
129
|
+
// Track all children that should be rendered (including exiting ones)
|
|
130
|
+
const [allChildren, setAllChildren] = createSignal([], { equals: false });
|
|
131
|
+
// Track exit completion status per key
|
|
132
|
+
const [exitStatus, setExitStatus] = createSignal(new Map(), { equals: false });
|
|
133
|
+
// Handle child changes
|
|
134
|
+
createEffect(() => {
|
|
135
|
+
const current = presentChildren();
|
|
136
|
+
const currentKeys = presentKeys();
|
|
137
|
+
const prev = untrack(allChildren);
|
|
138
|
+
const prevExitStatus = untrack(exitStatus);
|
|
139
|
+
let nextExitStatus = prevExitStatus;
|
|
140
|
+
let exitStatusChanged = false;
|
|
141
|
+
// Identify exiting children
|
|
142
|
+
const exitingChildren = prev.filter((child) => !currentKeys.includes(child.key));
|
|
143
|
+
// Clear exit tracking for children that re-entered
|
|
144
|
+
prevExitStatus.forEach((_, key) => {
|
|
145
|
+
if (currentKeys.includes(key)) {
|
|
146
|
+
if (!exitStatusChanged) {
|
|
147
|
+
nextExitStatus = new Map(prevExitStatus);
|
|
148
|
+
exitStatusChanged = true;
|
|
149
|
+
}
|
|
150
|
+
nextExitStatus.delete(key);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
// Mark exiting children
|
|
154
|
+
exitingChildren.forEach((child) => {
|
|
155
|
+
if (!prevExitStatus.has(child.key)) {
|
|
156
|
+
if (!exitStatusChanged) {
|
|
157
|
+
nextExitStatus = new Map(prevExitStatus);
|
|
158
|
+
exitStatusChanged = true;
|
|
159
|
+
}
|
|
160
|
+
nextExitStatus.set(child.key, false);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
if (exitStatusChanged) {
|
|
164
|
+
setExitStatus(nextExitStatus);
|
|
165
|
+
}
|
|
166
|
+
// Build new allChildren array
|
|
167
|
+
const nextChildren = [];
|
|
168
|
+
// Add all current children
|
|
169
|
+
for (let i = 0; i < current.length; i++) {
|
|
170
|
+
const child = current[i];
|
|
171
|
+
nextChildren.push({
|
|
172
|
+
key: child.key,
|
|
173
|
+
getChild: child.getChild,
|
|
174
|
+
isPresent: true,
|
|
175
|
+
index: i,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// In "wait" mode, only show exiting children until they're all done
|
|
179
|
+
if (local.mode === "wait" && exitingChildren.length > 0) {
|
|
180
|
+
nextChildren.length = 0;
|
|
181
|
+
exitingChildren.forEach((child) => {
|
|
182
|
+
nextChildren.push({
|
|
183
|
+
...child,
|
|
184
|
+
isPresent: false,
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
// In "sync" and "popLayout" modes, include exiting children alongside present ones
|
|
190
|
+
exitingChildren.forEach((child) => {
|
|
191
|
+
nextChildren.push({
|
|
192
|
+
...child,
|
|
193
|
+
isPresent: false,
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const isSame = prev.length === nextChildren.length &&
|
|
198
|
+
prev.every((child, index) => {
|
|
199
|
+
const next = nextChildren[index];
|
|
200
|
+
return child.key === next.key && child.getChild === next.getChild && child.isPresent === next.isPresent;
|
|
201
|
+
});
|
|
202
|
+
if (!isSame) {
|
|
203
|
+
setAllChildren(nextChildren);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
// Check if all exits are complete
|
|
207
|
+
const allExitsComplete = createMemo(() => {
|
|
208
|
+
const status = exitStatus();
|
|
209
|
+
if (!status.size)
|
|
210
|
+
return false;
|
|
211
|
+
for (const isComplete of status.values()) {
|
|
212
|
+
if (!isComplete)
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
});
|
|
217
|
+
// Handle completion
|
|
218
|
+
createEffect(() => {
|
|
219
|
+
if (allExitsComplete()) {
|
|
220
|
+
// All exits done, clean up and notify
|
|
221
|
+
batch(() => {
|
|
222
|
+
setExitStatus(new Map());
|
|
223
|
+
// Filter to only present children
|
|
224
|
+
const current = presentChildren();
|
|
225
|
+
const keys = presentKeys();
|
|
226
|
+
setAllChildren(current.map((child, i) => ({
|
|
227
|
+
key: child.key,
|
|
228
|
+
getChild: child.getChild,
|
|
229
|
+
isPresent: true,
|
|
230
|
+
index: i,
|
|
231
|
+
})));
|
|
232
|
+
});
|
|
233
|
+
local.onExitComplete?.();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
// Mark initial render complete after first mount
|
|
237
|
+
onMount(() => {
|
|
238
|
+
isInitialRender = false;
|
|
239
|
+
});
|
|
240
|
+
return (<Index each={allChildren()}>
|
|
241
|
+
{(childState) => {
|
|
242
|
+
const state = childState;
|
|
243
|
+
const isPresent = () => state().isPresent;
|
|
244
|
+
const onExitComplete = () => {
|
|
245
|
+
const status = untrack(exitStatus);
|
|
246
|
+
const key = state().key;
|
|
247
|
+
if (!status.has(key))
|
|
248
|
+
return;
|
|
249
|
+
batch(() => {
|
|
250
|
+
setExitStatus((map) => {
|
|
251
|
+
const newMap = new Map(map);
|
|
252
|
+
if (!newMap.has(key))
|
|
253
|
+
return newMap;
|
|
254
|
+
newMap.set(key, true);
|
|
255
|
+
return newMap;
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
return (<PresenceChild key={state().key} isPresent={isPresent} onExitComplete={isPresent() ? undefined : onExitComplete} initial={isInitialRender && !local.initial ? false : undefined} custom={local.custom} presenceAffectsLayout={local.presenceAffectsLayout} getChild={state().getChild} childKey={state().key} mode={local.mode} anchorX={local.anchorX} root={local.root}/>);
|
|
260
|
+
}}
|
|
261
|
+
</Index>);
|
|
262
|
+
}
|
|
263
|
+
function PopChild(props) {
|
|
264
|
+
let container;
|
|
265
|
+
let wasPresent = props.isPresent;
|
|
266
|
+
const [style, setStyle] = createSignal({ display: "contents" });
|
|
267
|
+
createEffect(() => {
|
|
268
|
+
const now = props.isPresent;
|
|
269
|
+
if (wasPresent && !now) {
|
|
270
|
+
const target = container?.firstElementChild ?? container;
|
|
271
|
+
if (target) {
|
|
272
|
+
const rect = target.getBoundingClientRect();
|
|
273
|
+
const parent = target.offsetParent;
|
|
274
|
+
const parentRect = parent ? parent.getBoundingClientRect() : { left: 0, top: 0, width: 0 };
|
|
275
|
+
const top = rect.top - parentRect.top;
|
|
276
|
+
const left = rect.left - parentRect.left;
|
|
277
|
+
const right = parent ? parentRect.width - rect.width - left : 0;
|
|
278
|
+
const x = props.anchorX === "right"
|
|
279
|
+
? { right: `${right}px`, left: "auto" }
|
|
280
|
+
: { left: `${left}px`, right: "auto" };
|
|
281
|
+
setStyle({
|
|
282
|
+
position: "absolute",
|
|
283
|
+
top: `${top}px`,
|
|
284
|
+
width: `${rect.width}px`,
|
|
285
|
+
height: `${rect.height}px`,
|
|
286
|
+
...x,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
else if (now) {
|
|
291
|
+
setStyle({ display: "contents" });
|
|
292
|
+
}
|
|
293
|
+
wasPresent = now;
|
|
294
|
+
});
|
|
295
|
+
return (<div ref={(el) => (container = el)} style={style()}>
|
|
296
|
+
{props.children}
|
|
297
|
+
</div>);
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Individual presence child wrapper
|
|
301
|
+
*/
|
|
302
|
+
function PresenceChild(props) {
|
|
303
|
+
const presenceId = String(presenceCounter++);
|
|
304
|
+
const [local, others] = splitProps(props, [
|
|
305
|
+
"isPresent",
|
|
306
|
+
"getChild",
|
|
307
|
+
"childKey",
|
|
308
|
+
"onExitComplete",
|
|
309
|
+
"initial",
|
|
310
|
+
"custom",
|
|
311
|
+
"presenceAffectsLayout",
|
|
312
|
+
"mode",
|
|
313
|
+
"anchorX",
|
|
314
|
+
"root",
|
|
315
|
+
]);
|
|
316
|
+
// Track registered exit handlers from child motion components
|
|
317
|
+
const [registeredChildren, setRegisteredChildren] = createSignal(new Map());
|
|
318
|
+
// Create presence context for this child
|
|
319
|
+
const register = (childId) => {
|
|
320
|
+
const key = String(childId);
|
|
321
|
+
batch(() => {
|
|
322
|
+
setRegisteredChildren((map) => {
|
|
323
|
+
const newMap = new Map(map);
|
|
324
|
+
newMap.set(key, false);
|
|
325
|
+
return newMap;
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
return () => {
|
|
329
|
+
batch(() => {
|
|
330
|
+
setRegisteredChildren((map) => {
|
|
331
|
+
const newMap = new Map(map);
|
|
332
|
+
newMap.delete(key);
|
|
333
|
+
return newMap;
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
};
|
|
337
|
+
};
|
|
338
|
+
const onExitComplete = (childId) => {
|
|
339
|
+
if (!local.onExitComplete)
|
|
340
|
+
return;
|
|
341
|
+
const key = String(childId);
|
|
342
|
+
batch(() => {
|
|
343
|
+
setRegisteredChildren((map) => {
|
|
344
|
+
const newMap = new Map(map);
|
|
345
|
+
newMap.set(key, true);
|
|
346
|
+
return newMap;
|
|
347
|
+
});
|
|
348
|
+
});
|
|
349
|
+
const allComplete = Array.from(registeredChildren().values()).every(Boolean);
|
|
350
|
+
if (allComplete) {
|
|
351
|
+
local.onExitComplete?.();
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
createEffect(on(local.isPresent, (present) => {
|
|
355
|
+
const current = untrack(registeredChildren);
|
|
356
|
+
if (current.size) {
|
|
357
|
+
setRegisteredChildren((map) => {
|
|
358
|
+
const newMap = new Map(map);
|
|
359
|
+
newMap.forEach((_, key) => newMap.set(key, false));
|
|
360
|
+
return newMap;
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
if (!present && current.size === 0) {
|
|
364
|
+
local.onExitComplete?.();
|
|
365
|
+
}
|
|
366
|
+
}, { defer: true }));
|
|
367
|
+
const resolvedInitial = () => {
|
|
368
|
+
if (local.initial === false)
|
|
369
|
+
return false;
|
|
370
|
+
if (typeof local.initial === "string" || Array.isArray(local.initial))
|
|
371
|
+
return local.initial;
|
|
372
|
+
return undefined;
|
|
373
|
+
};
|
|
374
|
+
const presenceContext = createMemo(() => ({
|
|
375
|
+
id: () => presenceId,
|
|
376
|
+
isPresent: local.isPresent,
|
|
377
|
+
initial: () => resolvedInitial(),
|
|
378
|
+
custom: () => local.custom,
|
|
379
|
+
onExitComplete,
|
|
380
|
+
register,
|
|
381
|
+
}));
|
|
382
|
+
return (<PresenceContext.Provider value={presenceContext()}>
|
|
383
|
+
<PresenceChildContent getChild={local.getChild} childKey={local.childKey} isPresent={local.isPresent} mode={local.mode} anchorX={local.anchorX} root={local.root}/>
|
|
384
|
+
</PresenceContext.Provider>);
|
|
385
|
+
}
|
|
386
|
+
function PresenceChildContent(props) {
|
|
387
|
+
const resolved = children(() => {
|
|
388
|
+
props.childKey;
|
|
389
|
+
return untrack(() => props.getChild());
|
|
390
|
+
});
|
|
391
|
+
if (props.mode === "popLayout") {
|
|
392
|
+
return (<PopChild isPresent={props.isPresent()} anchorX={props.anchorX} root={props.root}>
|
|
393
|
+
{resolved()}
|
|
394
|
+
</PopChild>);
|
|
395
|
+
}
|
|
396
|
+
return resolved();
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get a unique key for a child element
|
|
400
|
+
*/
|
|
401
|
+
function getChildKey(child, index) {
|
|
402
|
+
if (!child)
|
|
403
|
+
return "";
|
|
404
|
+
const directKey = child.key ?? child.id ?? child?.props?.key;
|
|
405
|
+
if (directKey != null && directKey !== "")
|
|
406
|
+
return directKey;
|
|
407
|
+
if (typeof child === "string" || typeof child === "number")
|
|
408
|
+
return child;
|
|
409
|
+
if (typeof child === "object" || typeof child === "function") {
|
|
410
|
+
const cached = autoKeyMap.get(child);
|
|
411
|
+
if (cached)
|
|
412
|
+
return cached;
|
|
413
|
+
}
|
|
414
|
+
const nextKey = `index-${index}`;
|
|
415
|
+
if (typeof child === "object" || typeof child === "function") {
|
|
416
|
+
autoKeyMap.set(child, nextKey);
|
|
417
|
+
}
|
|
418
|
+
return nextKey;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Hook to access presence state and signal safe removal
|
|
422
|
+
*/
|
|
423
|
+
export function usePresence() {
|
|
424
|
+
const context = useContext(PresenceContext);
|
|
425
|
+
if (!context) {
|
|
426
|
+
return [true, null];
|
|
427
|
+
}
|
|
428
|
+
const isPresent = context.isPresent();
|
|
429
|
+
const safeToRemove = () => {
|
|
430
|
+
if (context.onExitComplete) {
|
|
431
|
+
context.onExitComplete(context.id());
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
return [!isPresent ? false : true, !isPresent ? safeToRemove : null];
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Simple hook to check if component is present
|
|
438
|
+
*/
|
|
439
|
+
export function useIsPresent() {
|
|
440
|
+
const context = useContext(PresenceContext);
|
|
441
|
+
if (!context)
|
|
442
|
+
return true;
|
|
443
|
+
return context.isPresent();
|
|
444
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createContext, useContext } from "solid-js";
|
|
2
|
+
export const AnimatePresenceContext = createContext(null);
|
|
3
|
+
export function AnimatePresenceProvider(props) {
|
|
4
|
+
return (<AnimatePresenceContext.Provider value={props.value}>
|
|
5
|
+
{props.children}
|
|
6
|
+
</AnimatePresenceContext.Provider>);
|
|
7
|
+
}
|
|
8
|
+
export function createPresenceState(config) {
|
|
9
|
+
return {
|
|
10
|
+
id: config.id,
|
|
11
|
+
isPresent: config.isPresent,
|
|
12
|
+
initial: config.initial,
|
|
13
|
+
custom: config.custom || (() => undefined),
|
|
14
|
+
onExitComplete: config.onExitComplete,
|
|
15
|
+
register: config.register || (() => () => { }),
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function usePresenceInternal() {
|
|
19
|
+
return useContext(AnimatePresenceContext);
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createContext, splitProps, useContext } from "solid-js";
|
|
2
|
+
const defaultContext = {
|
|
3
|
+
transformPagePoint: ((p) => p),
|
|
4
|
+
isStatic: false,
|
|
5
|
+
reducedMotion: "never",
|
|
6
|
+
};
|
|
7
|
+
export const MotionConfigContext = createContext(defaultContext);
|
|
8
|
+
export function MotionConfig(props) {
|
|
9
|
+
const parent = useContext(MotionConfigContext);
|
|
10
|
+
const [local, contextProps] = splitProps(props, ["children"]);
|
|
11
|
+
const value = { ...parent, ...contextProps };
|
|
12
|
+
return <MotionConfigContext.Provider value={value}>{local.children}</MotionConfigContext.Provider>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createEffect, createSignal } from "solid-js";
|
|
2
|
+
import { getCurrentTreeVariants } from "./utils";
|
|
3
|
+
export function createMotionContext(props, parentContext) {
|
|
4
|
+
const [initial, setInitial] = createSignal();
|
|
5
|
+
const [animate, setAnimate] = createSignal();
|
|
6
|
+
createEffect(() => {
|
|
7
|
+
const variants = getCurrentTreeVariants(props, parentContext);
|
|
8
|
+
setInitial(variants.initial);
|
|
9
|
+
setAnimate(variants.animate);
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
get initial() {
|
|
13
|
+
return initial();
|
|
14
|
+
},
|
|
15
|
+
get animate() {
|
|
16
|
+
return animate();
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isControllingVariants, isVariantLabel } from "motion-dom";
|
|
2
|
+
export function getCurrentTreeVariants(props, context) {
|
|
3
|
+
if (isControllingVariants(props)) {
|
|
4
|
+
const { initial, animate } = props;
|
|
5
|
+
return {
|
|
6
|
+
initial: initial === false || isVariantLabel(initial) ? initial : undefined,
|
|
7
|
+
animate: isVariantLabel(animate) ? animate : undefined,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
return props.inherit !== false ? context : {};
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isPrimaryPointer } from "motion-dom";
|
|
2
|
+
export function extractEventInfo(event) {
|
|
3
|
+
return {
|
|
4
|
+
point: {
|
|
5
|
+
x: event.pageX,
|
|
6
|
+
y: event.pageY,
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export const addPointerInfo = (handler) => {
|
|
11
|
+
return (event) => {
|
|
12
|
+
const pointer = event;
|
|
13
|
+
if (isPrimaryPointer(pointer))
|
|
14
|
+
handler(pointer, extractEventInfo(pointer));
|
|
15
|
+
};
|
|
16
|
+
};
|