125 lines
2.9 KiB
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>
|