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 f30026149a.

* Add top margin of 5px to Fields.svelte
This commit is contained in:
Matthias Metelka 2022-09-14 07:26:07 +02:00 committed by GitHub
parent 9d9d4a97c7
commit 35431c5944
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 90 additions and 59 deletions

View file

@ -3,53 +3,89 @@ Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
--> -->
<script lang="ts"> <script lang="ts">
import { cubicOut } from "svelte/easing"; import { tick } from "svelte";
import { cubicIn, cubicOut } from "svelte/easing";
import { tweened } from "svelte/motion"; import { tweened } from "svelte/motion";
import { removeStyleProperties } from "../lib/styling";
export let duration = 300;
export let collapse = false; export let collapse = false;
export let animated = !document.body.classList.contains("reduced-motion");
let collapsed = false; let collapsed = false;
let contentHeight = 0;
const size = tweened<number>(undefined, { function dynamicDuration(height: number): number {
duration, return 100 + Math.pow(height, 1 / 4) * 25;
easing: cubicOut,
});
function doCollapse(collapse: boolean): void {
if (collapse) {
size.set(0);
} else {
collapsed = false;
size.set(1, { duration: 0 });
}
} }
$: duration = dynamicDuration(contentHeight);
$: doCollapse(collapse); const size = tweened<number>(undefined);
let collapsibleElement: HTMLElement; async function transition(collapse: boolean): Promise<void> {
let clientHeight: number; if (collapse) {
contentHeight = collapsibleElement.clientHeight;
function updateHeight(percentage: number): void { size.set(0, {
collapsibleElement.style.overflow = "hidden"; duration: duration,
easing: cubicOut,
if (percentage === 1) { });
removeStyleProperties(collapsibleElement, "height", "overflow");
} else if (percentage === 0) {
collapsed = true;
removeStyleProperties(collapsibleElement, "height", "overflow");
} else { } else {
collapsibleElement.style.height = `${percentage * clientHeight}px`; /* 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 (collapsibleElement) {
updateHeight($size); 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> </script>
<div bind:this={collapsibleElement} class="collapsible" bind:clientHeight> <div
bind:this={collapsibleElement}
class="collapsible"
class:animated
class:expanded
class:measuring
class:transitioning
style:--height="{height}px"
>
<slot {collapsed} /> <slot {collapsed} />
</div> </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>

View file

@ -85,34 +85,28 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
onDestroy(() => api?.destroy()); onDestroy(() => api?.destroy());
</script> </script>
<slot name="field-label" /> <div class="field-container" on:mouseenter on:mouseleave>
<slot name="field-label" />
<Collapsible collapse={collapsed} let:collapsed={hidden}> <Collapsible collapse={collapsed} let:collapsed={hidden}>
<div <div use:elementResolve class="editor-field" on:focusin on:focusout {hidden}>
use:elementResolve <EditingArea
class="editor-field" {content}
on:focusin fontFamily={field.fontFamily}
on:focusout fontSize={field.fontSize}
on:mouseenter api={editingArea}
on:mouseleave >
{hidden} {#if flipInputs}
> <slot name="plain-text-input" />
<EditingArea <slot name="rich-text-input" />
{content} {:else}
fontFamily={field.fontFamily} <slot name="rich-text-input" />
fontSize={field.fontSize} <slot name="plain-text-input" />
api={editingArea} {/if}
> </EditingArea>
{#if flipInputs} </div>
<slot name="plain-text-input" /> </Collapsible>
<slot name="rich-text-input" /> </div>
{:else}
<slot name="rich-text-input" />
<slot name="plain-text-input" />
{/if}
</EditingArea>
</div>
</Collapsible>
<style lang="scss"> <style lang="scss">
.editor-field { .editor-field {

View file

@ -12,6 +12,7 @@ Contains the fields. This contains the scrollable area.
<style lang="scss"> <style lang="scss">
.fields { .fields {
margin-top: 5px;
display: grid; display: grid;
grid-auto-rows: min-content; grid-auto-rows: min-content;
grid-gap: 6px; grid-gap: 6px;