Files
pocketbase/ui/src/components/base/Accordion.svelte
T
2022-10-30 10:28:14 +02:00

124 lines
3.1 KiB
Svelte

<script>
import { onMount, createEventDispatcher } from "svelte";
import { slide } from "svelte/transition";
const dispatch = createEventDispatcher();
let accordionElem;
let expandTimeoutId;
let classes = "";
export { classes as class }; // export reserved keyword
export let draggable = false;
export let active = false;
export let interactive = true;
export let single = false; // ensures that only one accordion is expanded in its given parent container
let isDragOver = false;
$: if (active) {
clearTimeout(expandTimeoutId);
expandTimeoutId = setTimeout(() => {
if (accordionElem?.scrollIntoView) {
accordionElem.scrollIntoView({
behavior: "smooth",
block: "nearest",
});
}
}, 250);
}
export function expand() {
collapseSiblings();
active = true;
dispatch("expand");
}
export function collapse() {
active = false;
clearTimeout(expandTimeoutId);
dispatch("collapse");
}
export function toggle() {
dispatch("toggle");
if (active) {
collapse();
} else {
expand();
}
}
export function collapseSiblings() {
if (single && accordionElem.closest(".accordions")) {
const handlers = accordionElem
.closest(".accordions")
.querySelectorAll(".accordion.active .accordion-header.interactive");
for (const handler of handlers) {
handler.click(); // @todo consider using store or other more reliable approach
}
}
}
function keyToggle(e) {
if (!interactive) {
return;
}
if (e.code === "Enter" || e.code === "Space") {
e.preventDefault();
toggle();
}
}
onMount(() => {
return () => clearTimeout(expandTimeoutId);
});
</script>
<div
bind:this={accordionElem}
tabindex={interactive ? 0 : -1}
class="accordion {isDragOver ? 'drag-over' : ''} {classes}"
class:active
on:keydown|self={keyToggle}
>
<header
class="accordion-header"
{draggable}
class:interactive
on:click|preventDefault={() => interactive && toggle()}
on:drop|preventDefault={(e) => {
if (draggable) {
isDragOver = false;
collapseSiblings();
dispatch("drop", e);
}
}}
on:dragstart={(e) => draggable && dispatch("dragstart", e)}
on:dragenter={(e) => {
if (draggable) {
isDragOver = true;
dispatch("dragenter", e);
}
}}
on:dragleave={(e) => {
if (draggable) {
isDragOver = false;
dispatch("dragleave", e);
}
}}
on:dragover|preventDefault
>
<slot name="header" {active} />
</header>
{#if active}
<div class="accordion-content" transition:slide|local={{ duration: 150 }}>
<slot />
</div>
{/if}
</div>