TirayaFrontend/src/lib/components/ui/Swiper.svelte

125 lines
2.9 KiB
Svelte

<script lang="ts">
import swipeable from "$lib/util/actions/swipeable";
import { clamp } from "$lib/util/functions";
import { type Coords, Direction } from "$lib/util/types";
import { createEventDispatcher } from "svelte";
import IconButton from "./IconButton.svelte";
import { mdiChevronLeft, mdiChevronRight } from "@mdi/js";
export let hasPrev = true;
export let hasNext = true;
/** Bind to get the current state */
export let swiping = false;
export let arrows = false;
export let cls = "";
const dispatch = createEventDispatcher();
let width: number;
let pos: number = 0;
$: pos = -width + offset;
let offset = 0;
function onSwipeStart() {
swiping = true;
}
function onSwipeMove(event: CustomEvent<Coords & { dx: number; dy: number }>) {
offset = clamp(offset + event.detail.dx, hasNext ? -width : 0, hasPrev ? width : 0);
}
function onSwipeEnd(event: CustomEvent<{ direction: Direction | null }>) {
reset();
const dir = event.detail.direction;
if (
(dir === Direction.Forward && hasNext) ||
(dir === Direction.Backward && hasPrev)
) {
dispatch("swiped", dir);
}
}
function reset() {
swiping = false;
offset = 0;
}
</script>
<div
class="carousel {cls}"
bind:clientWidth={width}
use:swipeable={{ thresholdProvider: () => width / 3 }}
on:swipeStart={onSwipeStart}
on:swipeMove={onSwipeMove}
on:swipeEnd={onSwipeEnd}
on:swipeFailed={reset}
>
<div
class="flex absolute h-full overflow-hidden"
class:pointer-events-none={offset !== 0}
style={swiping ? `transform: translateX(${pos}px)` : undefined}
>
{#if swiping}
<div style={`width: ${width}px; min-width: ${width}px;`}>
<slot name="prev" />
</div>
{/if}
<div style={`width: ${width}px; min-width: ${width}px;`}>
<slot name="curr" />
</div>
{#if swiping}
<div style={`width: ${width}px; min-width: ${width}px;`}>
<slot name="next" />
</div>
{/if}
</div>
{#if arrows}
<div class="arrow left-0">
<div>
<IconButton
path={mdiChevronLeft}
size="xs"
color="default"
on:click={() => dispatch("swiped", Direction.Backward)}
/>
</div>
</div>
<div class="arrow right-0">
<div>
<IconButton
path={mdiChevronRight}
size="xs"
color="default"
on:click={() => dispatch("swiped", Direction.Forward)}
/>
</div>
</div>
{/if}
</div>
<style lang="postcss">
.carousel {
grid-area: trackname;
position: relative;
}
.arrow {
@apply absolute top-0 h-full w-6;
@apply flex items-center;
@apply pointer-events-none;
}
.arrow > div {
@apply h-6 w-6 pointer-events-auto;
}
.arrow > div > :global(button) {
@apply hidden;
}
.arrow > div:hover > :global(button) {
@apply flex;
}
</style>