This fixes an issue where the example component-examples/dropdown did not render the leave transition
177 lines
4.7 KiB
Svelte
177 lines
4.7 KiB
Svelte
<script lang="ts">
|
|
import { createEventDispatcher, onMount, setContext } from "svelte";
|
|
import { writable, Writable } from "svelte/store";
|
|
import { match } from "$lib/utils/match";
|
|
import { State } from "$lib/internal/open-closed";
|
|
import { Reason, transition } from "$lib/utils/transition";
|
|
|
|
import {
|
|
hasChildren,
|
|
NestingContextValues,
|
|
NESTING_CONTEXT_NAME,
|
|
TreeStates,
|
|
useNesting,
|
|
useParentNesting,
|
|
useTransitionContext,
|
|
} from "./TransitionRoot.svelte";
|
|
import { useId } from "$lib/hooks/use-id";
|
|
|
|
export let unmount = true;
|
|
export let enter = "";
|
|
export let enterFrom = "";
|
|
export let enterTo = "";
|
|
export let entered = "";
|
|
export let leave = "";
|
|
export let leaveFrom = "";
|
|
export let leaveTo = "";
|
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
let container: HTMLElement | null = null;
|
|
let state = TreeStates.Visible;
|
|
|
|
let transitionContext = useTransitionContext();
|
|
let nestingContext = useParentNesting();
|
|
|
|
let initial = true;
|
|
|
|
let id = useId();
|
|
|
|
let isTransitioning = false;
|
|
|
|
let nesting: Writable<NestingContextValues> = writable();
|
|
nesting.set(
|
|
useNesting(() => {
|
|
// When all children have been unmounted we can only hide ourselves if and only if we are not
|
|
// transitioning ourselves. Otherwise we would unmount before the transitions are finished.
|
|
if (!isTransitioning) {
|
|
state = TreeStates.Hidden;
|
|
$nestingContext.unregister(id);
|
|
dispatch("afterLeave");
|
|
}
|
|
})
|
|
);
|
|
|
|
onMount(() => $nestingContext.register(id));
|
|
|
|
$: {
|
|
(() => {
|
|
// If we are in another mode than the Hidden mode then ignore
|
|
/* if (strategy.value !== RenderStrategy.Hidden) return */
|
|
if (!id) return;
|
|
|
|
// Make sure that we are visible
|
|
if ($transitionContext.show && state !== TreeStates.Visible) {
|
|
state = TreeStates.Visible;
|
|
return;
|
|
}
|
|
|
|
match(state, {
|
|
[TreeStates.Hidden]: () => $nestingContext.unregister(id),
|
|
[TreeStates.Visible]: () => $nestingContext.register(id),
|
|
});
|
|
})();
|
|
}
|
|
|
|
function splitClasses(classes: string = "") {
|
|
return classes
|
|
.split(" ")
|
|
.filter((className) => className.trim().length > 1);
|
|
}
|
|
|
|
let enterClasses = splitClasses(enter);
|
|
let enterFromClasses = splitClasses(enterFrom);
|
|
let enterToClasses = splitClasses(enterTo);
|
|
|
|
let enteredClasses = splitClasses(entered);
|
|
|
|
let leaveClasses = splitClasses(leave);
|
|
let leaveFromClasses = splitClasses(leaveFrom);
|
|
let leaveToClasses = splitClasses(leaveTo);
|
|
|
|
let mounted = false;
|
|
onMount(() => (mounted = true));
|
|
|
|
function executeTransition(show: boolean, appear: boolean) {
|
|
// Skipping initial transition
|
|
let skip = initial && !appear;
|
|
|
|
let node = container;
|
|
if (!node || !(node instanceof HTMLElement)) return;
|
|
if (skip) return;
|
|
|
|
isTransitioning = true;
|
|
|
|
if (show) dispatch("beforeEnter");
|
|
if (!show) dispatch("beforeLeave");
|
|
|
|
return show
|
|
? transition(
|
|
node,
|
|
enterClasses,
|
|
enterFromClasses,
|
|
enterToClasses,
|
|
enteredClasses,
|
|
(reason) => {
|
|
isTransitioning = false;
|
|
if (reason === Reason.Finished) dispatch("afterEnter");
|
|
}
|
|
)
|
|
: transition(
|
|
node,
|
|
leaveClasses,
|
|
leaveFromClasses,
|
|
leaveToClasses,
|
|
enteredClasses,
|
|
(reason) => {
|
|
isTransitioning = false;
|
|
|
|
if (reason !== Reason.Finished) return;
|
|
|
|
// When we don't have children anymore we can safely unregister from the parent and hide
|
|
// ourselves.
|
|
if (!hasChildren($nesting)) {
|
|
state = TreeStates.Hidden;
|
|
$nestingContext.unregister(id);
|
|
dispatch("afterLeave");
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
let _cleanup: (() => void) | null | undefined = null;
|
|
$: {
|
|
if (mounted) {
|
|
if (_cleanup) {
|
|
_cleanup();
|
|
}
|
|
_cleanup = executeTransition(
|
|
$transitionContext.show,
|
|
$transitionContext.appear
|
|
);
|
|
initial = false;
|
|
}
|
|
}
|
|
|
|
setContext(NESTING_CONTEXT_NAME, nesting);
|
|
let openClosedState: Writable<State> = writable(State.Closed);
|
|
setContext("OpenClosed", openClosedState);
|
|
|
|
$: openClosedState.set(
|
|
match(state, {
|
|
[TreeStates.Visible]: State.Open,
|
|
[TreeStates.Hidden]: State.Closed,
|
|
})
|
|
);
|
|
|
|
// This is not in the base headless UI library, but is needed to prevent re-renders during the transition
|
|
// from blowing away the transition classes
|
|
$: classes = isTransitioning ? container?.className : $$props.class;
|
|
</script>
|
|
|
|
<div bind:this={container} {...$$restProps} class={classes}>
|
|
{#if state === TreeStates.Visible}
|
|
<slot />
|
|
{/if}
|
|
</div>
|