Skip to content

Commit

Permalink
push fixes for animate presence and useCycle
Browse files Browse the repository at this point in the history
  • Loading branch information
JonathonRP committed Dec 23, 2024
1 parent ae3605a commit 3106dfc
Show file tree
Hide file tree
Showing 24 changed files with 1,145 additions and 325 deletions.
796 changes: 796 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,41 +33,41 @@
"release": "bun --bun run build && bun --bun changeset publish"
},
"dependencies": {
"bits-ui": "^0.21.16",
"bits-ui": "^0.22.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-svelte": "^0.453.0",
"lucide-svelte": "^0.468.0",
"mode-watcher": "^0.5.0",
"nanoid": "^5.0.9",
"tailwind-merge": "^2.5.5",
"tailwind-variants": "^0.2.1"
"tailwind-variants": "^0.3.0"
},
"devDependencies": {
"@biomejs/biome": "^1.9.4",
"@changesets/cli": "^2.27.11",
"@emotion/is-prop-valid": "^1.3.1",
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/kit": "^2.12.1",
"@sveltejs/kit": "^2.13.0",
"@sveltejs/package": "^2.3.7",
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
"@tailwindcss/typography": "^0.5.15",
"@tailwindcss/vite": "^4.0.0-beta.8",
"@tsconfig/svelte": "^5.0.4",
"@types/node": "^20.17.10",
"@vitest/ui": "latest",
"@vitest/ui": "^2.1.8",
"csstype": "^3.1.3",
"publint": "^0.2.12",
"runed": "^0.18.0",
"sv": "^0.5.11",
"runed": "^0.20.0",
"sv": "^0.6.8",
"svelte-check": "^4.1.1",
"tailwindcss": "^4.0.0-beta.8",
"typescript": "^5.7.2",
"vite": "latest",
"vitest": "latest"
"vite": "^6.0.4",
"vitest": "^2.1.8"
},
"peerDependencies": {
"svelte": "^5.14.4"
"svelte": "^5.15.0"
},
"engines": {
"bun": ">=1.0.0",
Expand Down
4 changes: 1 addition & 3 deletions src/lib/components/motion/AnimateLayout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
function toggleSwitch() {
active = !active;
}
// active={1 * !active} do we still need this??
</script>

<Box>
Expand All @@ -24,7 +22,7 @@
<button class="switch" data-active={active} onclick={toggleSwitch}>
<motion.div
active={1 * !active}
layout
ut
class="handle"
transition={spring}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,24 @@ Copyright (c) 2018 Framer B.V. -->

<script lang="ts" generics="T extends {key:any}">
import type { ConditionalGeneric, AnimatePresenceProps } from "./index.js";
import { getContext } from "svelte";
import PresenceChild from "./PresenceChild/PresenceChild.svelte";
import { useContext } from "../../context/utils/context.svelte.js";
import { LayoutGroupContext } from "../../context/LayoutGroupContext.js";
import { fromStore } from "svelte/store";
import { invariant } from "$lib/motion-start/utils/errors.js";
type $$Props = AnimatePresenceProps<ConditionalGeneric<T>>;
export let list: $$Props["list"] = undefined,
mode = "sync" as const,
custom: $$Props["custom"] = undefined,
initial: $$Props["initial"] = true,
onExitComplete: $$Props["onExitComplete"] = undefined,
exitBeforeEnter: $$Props["exitBeforeEnter"] = undefined,
presenceAffectsLayout = true,
show: $$Props["show"] = undefined,
isCustom = false;
show: $$Props["show"] = undefined;
invariant(!exitBeforeEnter, "Replace exitBeforeEnter with mode='wait'");
let _list = list !== undefined ? list : show ? [{ key: 1 }] : [];
$: _list = list !== undefined ? list : show ? [{ key: 1 }] : [];
Expand All @@ -29,7 +31,6 @@ Copyright (c) 2018 Framer B.V. -->
_list = [..._list];
};
function getChildKey(child: { key: number }) {
return child.key || "";
}
Expand Down Expand Up @@ -161,18 +162,27 @@ Copyright (c) 2018 Framer B.V. -->
} else {
isInitialRender = false;
}
if (
process.env.NODE_ENV !== "production" &&
mode === "wait" &&
renderedChildren.length > 1
) {
console.warn(
`You're attempting to animate multiple children within AnimatePresence, but its mode is set to "wait". This will lead to odd visual behaviour.`,
);
}
</script>

{#each childrenToRender as child (getChildKey(child))}
<PresenceChild
mode="sync"
{mode}
isPresent={child.present}
initial={initial ? undefined : false}
custom={child.onExit ? custom : undefined}
{presenceAffectsLayout}
onExitComplete={child.onExit}
{isCustom}
>
<slot item={child.item} />
</PresenceChild>
{/each}
{/each}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Copyright (c) 2018 Framer B.V. -->
import PopChild from "../PopChild/PopChild.svelte";
import { useContext } from "$lib/motion-start/context/utils/context.svelte.js";
import { useId } from "$lib/motion-start/utils/useId.js";
import { fromStore, toStore } from "svelte/store";
import { fromStore } from "svelte/store";
import { tick } from "svelte";
interface Props extends PresenceChildProps {}
Expand All @@ -30,53 +31,51 @@ Copyright (c) 2018 Framer B.V. -->
}: Props = $props();
const presenceChildren = newChildrenMap();
const id = $derived(useId());
const id = useId();
const refresh = $derived(presenceAffectsLayout ? undefined : isPresent);
const memoContext = $derived((flag?: boolean) => {
return {
id,
initial,
isPresent,
custom,
onExitComplete: (childId: string | number) => {
presenceChildren.set(childId, true);
let allComplete = true;
presenceChildren.forEach((isComplete) => {
if (!isComplete) allComplete = false;
});
const presenceProps = (freshness?: boolean) => ({
id,
initial,
isPresent,
custom,
onExitComplete: (childId: string | number) => {
presenceChildren.set(childId, true);
let allComplete = true;
presenceChildren.forEach((isComplete) => {
if (!isComplete) allComplete = false;
});
allComplete && onExitComplete?.();
},
register: (childId: string | number) => {
presenceChildren.set(childId, false);
return () => presenceChildren.delete(childId);
},
};
allComplete && onExitComplete?.();
},
register: (childId: string | number) => {
presenceChildren.set(childId, false);
return () => presenceChildren.delete(childId);
},
});
let context = fromStore(useContext(PresenceContext));
let context = fromStore(useContext(PresenceContext)).current;
$effect(() => {
if (presenceAffectsLayout) {
context.current = memoContext();
context = presenceProps();
}
});
$effect(() => {
context = memoContext(refresh);
});
const keyset = (flag?: boolean) => {
$effect(() => (context = presenceProps(refresh)));
const keyset = (presence: boolean) =>
presenceChildren.forEach((_, key) => presenceChildren.set(key, false));
};
$effect(() => {
keyset(isPresent);
!isPresent && !presenceChildren.size && onExitComplete?.();
tick().then(() => {
!isPresent && !presenceChildren.size && onExitComplete?.();
});
});
PresenceContext.Provider = context.current;
PresenceContext.Provider = context;
</script>

{#if mode === "popLayout"}
Expand Down
13 changes: 10 additions & 3 deletions src/lib/motion-start/context/utils/context.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getContext, hasContext, onMount, setContext, untrack } from 'svelte';
import type { createSubscriber } from 'svelte/reactivity';
import { writable, type Writable } from 'svelte/store';

type UnwrapWritable<T> = T extends Writable<infer I> ? I : T;
Expand All @@ -21,22 +22,28 @@ class CallableContext<T> extends Function {
}

class Context<T extends Writable<UnwrapWritable<T>>> extends CallableContext<T> {
// #update: () => void;
// #subscribe: ReturnType<typeof createSubscriber>;
#state: T;

public constructor(private initial: T) {
super(() => {
// this.#subscribe();
this.#state = getContext<T>(this);
// $effect(() => {
// this.#state = getContext<T>(this);
// });

return this.#state || initial;
});
this.#state = this.initial;
// this.#subscribe = createSubscriber((update) => {
// this.#update = update;

// return this.#state?.subscribe(update);
// });
}

set Provider(value: UnwrapWritable<T>) {
setContext(this, writable(value));
// this.#update?.();
}
}

Expand Down
75 changes: 38 additions & 37 deletions src/lib/motion-start/motion/Motion.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Copyright (c) 2018 Framer B.V. -->
MotionComponentConfig<Instance, RenderState>,
"Component" | "createVisualElement" | "useVisualState" | "useRender"
>
>("motion");
>("motionContexts");
/**
* If we need to measure the element we load this functionality in a
Expand All @@ -124,45 +124,46 @@ Copyright (c) 2018 Framer B.V. -->
const { isStatic } = $derived(configAndProps);
const context = useCreateMotionContext<Instance>(props);
const context = $derived(useCreateMotionContext<Instance>(motionProps));
const visualState = $derived(useVisualState(motionProps, isStatic));
$effect.pre(() => {
if (!isStatic && isBrowser) {
useStrictMode(configAndProps, preloadedFeatures);
const layoutProjection = getProjectionFunctionality(configAndProps);
/**
* If we need to measure the element we load this functionality in a
* separate class component in order to gain access to getSnapshotBeforeUpdate.
*/
MeasureLayout = layoutProjection.MeasureLayout;
/**
* Create a VisualElement for this component. A VisualElement provides a common
* interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
* providing a way of rendering to these APIs outside of the React render loop
* for more performant animations and interactions
*/
configAndProps;
context.visualElement = untrack(() =>
useVisualElement<Instance, RenderState>(
as,
visualState,
configAndProps,
createVisualElement,
layoutProjection.ProjectionNode,
),
);
// MotionContext.Provider
untrack(() => (MotionContext.Provider = context));
}
});
if (!isStatic && isBrowser) {
useStrictMode(configAndProps, preloadedFeatures);
$effect(() => () => {
// Since useMotionRef is not called on destroy, the visual element is unmounted here
context?.visualElement?.unmount();
const layoutProjection = $derived(
getProjectionFunctionality(configAndProps),
);
/**
* If we need to measure the element we load this functionality in a
* separate class component in order to gain access to getSnapshotBeforeUpdate.
*/
MeasureLayout = layoutProjection.MeasureLayout;
/**
* Create a VisualElement for this component. A VisualElement provides a common
* interface to renderer-specific APIs (ie DOM/Three.js etc) as well as
* providing a way of rendering to these APIs outside of the React render loop
* for more performant animations and interactions
*/
context.visualElement = useVisualElement<Instance, RenderState>(
as,
visualState,
() => configAndProps,
createVisualElement,
layoutProjection.ProjectionNode,
);
}
$effect(() => {
// MotionContext.Provider
MotionContext.Provider = context;
return () => {
console.log("dismount");
// Since useMotionRef is not called on destroy, the visual element is unmounted here
context.visualElement?.unmount();
};
});
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,14 @@ export class ExitAnimationFeature extends Feature<unknown> {
private id: number = id++;

update() {
// note this never runs
if (!this.node.presenceContext) return;

const { isPresent, onExitComplete } = this.node.presenceContext;
const { isPresent, onExitComplete, id } = this.node.presenceContext;
const { isPresent: prevIsPresent } = this.node.prevPresenceContext || {};

$inspect(isPresent, prevIsPresent, id);

if (!this.node.animationState || isPresent === prevIsPresent) {
return;
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/motion-start/motion/features/drag.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Copyright (c) 2018 Framer B.V.
import { DragGesture } from '../../gestures/drag/index.svelte';
import { PanGesture } from '../../gestures/pan/index.svelte';
import { HTMLProjectionNode } from '../../projection/node/HTMLProjectionNode.svelte';
import { MeasureLayout } from './layout/MeasureLayout.svelte';
import MeasureLayout from './layout/MeasureLayout.svelte';
import type { FeaturePackages } from './types';

export const drag: FeaturePackages = {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/motion-start/motion/features/layout.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright (c) 2018 Framer B.V.
*/

import { HTMLProjectionNode } from '../../projection/node/HTMLProjectionNode.svelte';
import { MeasureLayout } from './layout/MeasureLayout.svelte';
import MeasureLayout from './layout/MeasureLayout.svelte';
import type { FeaturePackages } from './types';

export const layout: FeaturePackages = {
Expand Down
Loading

0 comments on commit 3106dfc

Please sign in to comment.