Anki/ts/components/Collapsible.svelte
Matthias Metelka d110c4916c
Introduce setting to collapse field by default (#1990)
* Introduce setting to collapse field by default

* Fix schema order

* Change wording from adjective to imperative

sounds a bit less clunky

* Update rslib/src/notetype/schema11.rs (dae)

* Keep settings in single column

* Add back Toggle Visual Editor string

* Add RichTextBadge component and show it conditionally

* Reverse input order depending on default setting

* Make PlainTextInput border-radius responsive to toggle states

* Prevent first Collapsible transition differently

* Focus inputs after Collapsible transition

The double tick calls are just a temporary solution until I find the exact moment an input is focusable again.

* Use requestAnimationFrame to await focusable state

Note: Svelte tick doesn't seem to work in this scenario.
2022-08-31 23:34:39 +10:00

104 lines
2.8 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 { promiseWithResolver } from "../lib/promise";
export let id: string | undefined = undefined;
let className: string = "";
export { className as class };
export let collapsed = false;
let isCollapsed = false;
let hidden = collapsed;
const [outerPromise, outerResolve] = promiseWithResolver<HTMLElement>();
const [innerPromise, innerResolve] = promiseWithResolver<HTMLElement>();
let style: string;
function setStyle(height: number, duration: number) {
style = `--collapse-height: -${height}px; --duration: ${duration}ms`;
}
/* The following two functions use synchronous DOM-manipulation,
because Editor field inputs would lose focus when using tick() */
function getRequiredHeight(el: HTMLElement): number {
el.style.setProperty("position", "absolute");
el.style.setProperty("visibility", "hidden");
el.removeAttribute("hidden");
const height = el.clientHeight;
el.setAttribute("hidden", "");
el.style.removeProperty("position");
el.style.removeProperty("visibility");
return height;
}
async function transition(collapse: boolean) {
const outer = await outerPromise;
const inner = await innerPromise;
outer.style.setProperty("overflow", "hidden");
isCollapsed = true;
const height = collapse ? inner.clientHeight : getRequiredHeight(inner);
const duration = Math.sqrt(height * 80);
setStyle(height, duration);
if (!collapse) {
inner.removeAttribute("hidden");
isCollapsed = false;
}
inner.addEventListener(
"transitionend",
() => {
inner.toggleAttribute("hidden", collapse);
outer.style.removeProperty("overflow");
hidden = collapse;
},
{ once: true },
);
}
/* prevent transition on mount for performance reasons */
let firstTransition = true;
$: {
transition(collapsed);
firstTransition = false;
}
</script>
<div {id} class="collapsible-container {className}" use:outerResolve>
<div
class="collapsible-inner"
class:collapsed={isCollapsed}
class:no-transition={firstTransition}
use:innerResolve
{style}
>
<slot {hidden} />
</div>
</div>
<style lang="scss">
.collapsible-container {
position: relative;
}
.collapsible-inner {
transition: margin-top var(--duration) ease-in;
&.collapsed {
margin-top: var(--collapse-height);
}
&.no-transition {
transition: none;
}
}
</style>