generated from tpl/obsidian-sample-plugin
109 lines
2.4 KiB
Svelte
109 lines
2.4 KiB
Svelte
<script lang="ts">
|
|
import { Star, StarHalf } from "lucide-svelte";
|
|
|
|
interface Props {
|
|
value?: number;
|
|
name?: string;
|
|
}
|
|
|
|
const maxV = 5;
|
|
|
|
let { value = $bindable(), name }: Props = $props();
|
|
|
|
let ctrl: HTMLElement | null = $state(null);
|
|
let w = $state(0);
|
|
let h = $state(0);
|
|
|
|
$effect(() => {
|
|
if (ctrl) {
|
|
ctrl.style.width = `${w}px`;
|
|
ctrl.style.height = `${h}px`;
|
|
}
|
|
});
|
|
|
|
let hovering = $state(false);
|
|
let valueHover = $state(0);
|
|
|
|
let displayVal = $derived(hovering ? valueHover : (value ?? 0));
|
|
let items = $derived.by(() => {
|
|
const full = Number.isInteger(displayVal);
|
|
return Array.from({ length: maxV }, (_, i) => i + 1).map((index) => ({
|
|
index,
|
|
state:
|
|
index <= Math.ceil(displayVal)
|
|
? full || index != Math.ceil(displayVal)
|
|
? "active"
|
|
: "partial"
|
|
: "inactive",
|
|
}));
|
|
});
|
|
|
|
function calcSliderPos(e: MouseEvent) {
|
|
return (e.offsetX / (e.target as HTMLElement).clientWidth) * maxV;
|
|
}
|
|
|
|
function onclick() {
|
|
value = valueHover;
|
|
}
|
|
|
|
function onmouseout() {
|
|
hovering = false;
|
|
valueHover = 0;
|
|
}
|
|
|
|
function onmousemove(e: MouseEvent) {
|
|
hovering = true;
|
|
valueHover = Math.ceil(calcSliderPos(e) * 2) / 2;
|
|
}
|
|
</script>
|
|
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
|
|
<div class="rating-input" {onclick} {onmouseout}>
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div class="ctrl" {onmousemove} bind:this={ctrl}></div>
|
|
<div class="cont m-1" bind:clientWidth={w} bind:clientHeight={h}>
|
|
{#each items as item (item.index)}
|
|
<div class="rating-item">
|
|
{#if item.state === "inactive"}
|
|
<Star fill="var(--interactive-normal)" />
|
|
{:else if item.state === "active"}
|
|
<Star fill="var(--interactive-accent)" />
|
|
{:else if item.state === "partial"}
|
|
<Star fill="var(--interactive-normal)" />
|
|
<StarHalf fill="var(--interactive-accent)" />
|
|
{/if}
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
|
|
<style lang="scss">
|
|
.rating-input {
|
|
cursor: pointer;
|
|
|
|
.ctrl {
|
|
position: absolute;
|
|
z-index: 2;
|
|
}
|
|
|
|
.cont {
|
|
display: flex;
|
|
}
|
|
|
|
:global(svg) {
|
|
position: absolute;
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
|
|
.rating-item {
|
|
position: relative;
|
|
width: var(--size-4-16);
|
|
height: var(--size-4-16);
|
|
}
|
|
}
|
|
</style>
|