anki/ts/components/Collapsible.svelte
Matthias Metelka 35431c5944
Collapsible patch (#2052)
* Animate Collapsible in both directions

* Fix field margin issues

* Fix code style issues

* Make duration prop optional

* Implement reduced motion mode for Collapsible

* Refactor Collapsible and add comments

* Fix LabelContainer badges disappearing when field is still hovered

* Remove reducedMotion store and use body class instead

* Export optional animated boolean

* Do not export duration

* Add 5px top padding to Fields.svelte

to make it look like it used to.

* Revert "Add 5px top padding to Fields.svelte"

This reverts commit f30026149a89f3d3b289c5030cd1ca34f728b036.

* Add top margin of 5px to Fields.svelte
2022-09-14 15:26:07 +10:00

92 lines
2.4 KiB
Svelte

<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { tick } from "svelte";
import { cubicIn, cubicOut } from "svelte/easing";
import { tweened } from "svelte/motion";
export let collapse = false;
export let animated = !document.body.classList.contains("reduced-motion");
let collapsed = false;
let contentHeight = 0;
function dynamicDuration(height: number): number {
return 100 + Math.pow(height, 1 / 4) * 25;
}
$: duration = dynamicDuration(contentHeight);
const size = tweened<number>(undefined);
async function transition(collapse: boolean): Promise<void> {
if (collapse) {
contentHeight = collapsibleElement.clientHeight;
size.set(0, {
duration: duration,
easing: cubicOut,
});
} else {
/* Tell content to show and await response */
collapsed = false;
await tick();
/* Measure content height to tween to */
contentHeight = collapsibleElement.clientHeight;
size.set(1, {
duration: duration,
easing: cubicIn,
});
}
}
$: if (collapsibleElement) {
if (animated) {
transition(collapse);
} else {
collapsed = collapse;
}
}
let collapsibleElement: HTMLElement;
$: collapsed = $size === 0;
$: expanded = $size === 1;
$: height = $size * contentHeight;
$: transitioning = $size > 0 && !(collapsed || expanded);
$: measuring = !(collapsed || transitioning || expanded);
</script>
<div
bind:this={collapsibleElement}
class="collapsible"
class:animated
class:expanded
class:measuring
class:transitioning
style:--height="{height}px"
>
<slot {collapsed} />
</div>
{#if measuring}
<!-- Maintain document flow while collapsible height is measured -->
<div class="collapsible-placeholder" />
{/if}
<style lang="scss">
.collapsible.animated {
&.measuring {
position: absolute;
opacity: 0;
}
&.transitioning {
overflow: hidden;
height: var(--height);
&.expanded {
overflow: visible;
}
}
}
</style>