renovate[bot] 3d6a6f77a8
chore(deps): update dependency eslint-plugin-svelte to v3 (#16532)
* chore(deps): update dependency eslint-plugin-svelte to v3

* chore: linting

* chore: rebase

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2025-03-03 14:24:26 +00:00

126 lines
3.6 KiB
Svelte

<script lang="ts">
import { focusOutside } from '$lib/actions/focus-outside';
import { shortcuts } from '$lib/actions/shortcut';
import Icon from '$lib/components/elements/icon.svelte';
import { generateId } from '$lib/utils/generate-id';
import { t } from 'svelte-i18n';
interface Props {
count?: number;
rating: number;
readOnly?: boolean;
onRating: (rating: number) => void | undefined;
}
let { count = 5, rating, readOnly = false, onRating }: Props = $props();
let ratingSelection = $state(rating);
let hoverRating = $state(0);
let focusRating = $state(0);
let timeoutId: ReturnType<typeof setTimeout> | undefined;
$effect(() => {
ratingSelection = rating;
});
const starIcon =
'M10.788 3.21c.448-1.077 1.976-1.077 2.424 0l2.082 5.007 5.404.433c1.164.093 1.636 1.545.749 2.305l-4.117 3.527 1.257 5.273c.271 1.136-.964 2.033-1.96 1.425L12 18.354 7.373 21.18c-.996.608-2.231-.29-1.96-1.425l1.257-5.273-4.117-3.527c-.887-.76-.415-2.212.749-2.305l5.404-.433 2.082-5.006z';
const id = generateId();
const handleSelect = (newRating: number) => {
if (readOnly) {
return;
}
if (newRating === rating) {
return;
}
onRating(newRating);
};
const setHoverRating = (value: number) => {
if (readOnly) {
return;
}
hoverRating = value;
};
const reset = () => {
setHoverRating(0);
focusRating = 0;
};
const handleSelectDebounced = (value: number) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
handleSelect(value);
}, 300);
};
</script>
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<fieldset
class="text-immich-primary dark:text-immich-dark-primary w-fit cursor-default"
onmouseleave={() => setHoverRating(0)}
use:focusOutside={{ onFocusOut: reset }}
use:shortcuts={[
{ shortcut: { key: 'ArrowLeft' }, preventDefault: false, onShortcut: (event) => event.stopPropagation() },
{ shortcut: { key: 'ArrowRight' }, preventDefault: false, onShortcut: (event) => event.stopPropagation() },
]}
>
<legend class="sr-only">{$t('rating')}</legend>
<div class="flex flex-row" data-testid="star-container">
{#each { length: count } as _, index (index)}
{@const value = index + 1}
{@const filled = hoverRating >= value || (hoverRating === 0 && ratingSelection >= value)}
{@const starId = `${id}-${value}`}
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
<label
for={starId}
class:cursor-pointer={!readOnly}
class:ring-2={focusRating === value}
onmouseover={() => setHoverRating(value)}
tabindex={-1}
data-testid="star"
>
<span class="sr-only">{$t('rating_count', { values: { count: value } })}</span>
<Icon
path={starIcon}
size="1.5em"
strokeWidth={1}
color={filled ? 'currentcolor' : 'transparent'}
strokeColor={filled ? 'currentcolor' : '#c1cce8'}
ariaHidden
/>
</label>
<input
type="radio"
name="stars"
{value}
id={starId}
bind:group={ratingSelection}
disabled={readOnly}
onfocus={() => {
focusRating = value;
}}
onchange={() => handleSelectDebounced(value)}
class="sr-only"
/>
{/each}
</div>
</fieldset>
{#if ratingSelection > 0 && !readOnly}
<button
type="button"
onclick={() => {
ratingSelection = 0;
handleSelect(ratingSelection);
}}
class="cursor-pointer text-xs text-immich-primary dark:text-immich-dark-primary"
>
{$t('rating_clear')}
</button>
{/if}