|
|
<script lang="ts">
|
|
|
import { DateRangePickerProvider } from '$soma/components/date-range-picker';
|
|
|
import { CalendarDate } from '$libs/days';
|
|
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
|
|
|
|
// Year-grid range view (kind='year'). 3×4 decade grid + decade nav.
|
|
|
//
|
|
|
// Selection state machine (mirrors the day-grid range selection):
|
|
|
// - Empty (neither start nor end set): click sets startValue (and
|
|
|
// clears endValue).
|
|
|
// - Pending (start set, no end): click sets endValue. If the clicked
|
|
|
// year is before startValue, swap them so start <= end.
|
|
|
// - Complete (both set): click resets to a fresh start + clears end.
|
|
|
//
|
|
|
// Year endpoints normalise to Jan 1 for start, Dec 31 for end.
|
|
|
interface YearRangeViewProps extends HTMLAttributes<HTMLDivElement> {}
|
|
|
|
|
|
const { ...rest }: YearRangeViewProps = $props();
|
|
|
|
|
|
const provider = DateRangePickerProvider.require();
|
|
|
|
|
|
const startVal = $derived(provider.opts.startValue.current);
|
|
|
const endVal = $derived(provider.opts.endValue.current);
|
|
|
const placeholder = $derived(provider.opts.placeholder.current);
|
|
|
|
|
|
// Decade window: anchor on startValue or placeholder, snap to decade.
|
|
|
const anchorYear = $derived(startVal?.year ?? placeholder.year);
|
|
|
const decadeStart = $derived(anchorYear - (anchorYear % 10));
|
|
|
const years = $derived(Array.from({ length: 12 }, (_, i) => decadeStart + i));
|
|
|
|
|
|
function selectYear(year: number) {
|
|
|
const s = startVal;
|
|
|
const e = endVal;
|
|
|
const isComplete = s !== undefined && e !== undefined;
|
|
|
|
|
|
if (!s || isComplete) {
|
|
|
// Fresh start.
|
|
|
provider.opts.startValue.current = new CalendarDate(year, 1, 1);
|
|
|
provider.opts.endValue.current = undefined;
|
|
|
provider.opts.placeholder.current = new CalendarDate(year, 1, 1);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// Pending end: set the second endpoint, swap if reversed.
|
|
|
const startYear = s.year;
|
|
|
if (year < startYear) {
|
|
|
provider.opts.endValue.current = new CalendarDate(startYear, 12, 31);
|
|
|
provider.opts.startValue.current = new CalendarDate(year, 1, 1);
|
|
|
} else {
|
|
|
provider.opts.endValue.current = new CalendarDate(year, 12, 31);
|
|
|
}
|
|
|
provider.handleRangeSelect();
|
|
|
}
|
|
|
|
|
|
function stepDecade(direction: -1 | 1) {
|
|
|
const p = placeholder;
|
|
|
provider.opts.placeholder.current = new CalendarDate(p.year + direction * 10, p.month, p.day);
|
|
|
}
|
|
|
|
|
|
function classFor(year: number): { start: boolean; end: boolean; inRange: boolean } {
|
|
|
const sy = startVal?.year;
|
|
|
const ey = endVal?.year;
|
|
|
if (sy === undefined) return { start: false, end: false, inRange: false };
|
|
|
const start = year === sy;
|
|
|
const end = ey !== undefined && year === ey;
|
|
|
const inRange =
|
|
|
ey !== undefined && year > Math.min(sy, ey) && year < Math.max(sy, ey);
|
|
|
return { start, end, inRange };
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<div data-date-range-picker-year-view role="grid" aria-label="Select year range" {...rest}>
|
|
|
<div data-date-range-picker-year-view-header>
|
|
|
<button
|
|
|
type="button"
|
|
|
data-date-range-picker-year-view-prev
|
|
|
aria-label="Previous decade"
|
|
|
onclick={() => stepDecade(-1)}
|
|
|
>
|
|
|
‹
|
|
|
</button>
|
|
|
<span data-date-range-picker-year-view-heading>
|
|
|
{decadeStart} – {decadeStart + 11}
|
|
|
</span>
|
|
|
<button
|
|
|
type="button"
|
|
|
data-date-range-picker-year-view-next
|
|
|
aria-label="Next decade"
|
|
|
onclick={() => stepDecade(1)}
|
|
|
>
|
|
|
›
|
|
|
</button>
|
|
|
</div>
|
|
|
<div data-date-range-picker-year-view-grid>
|
|
|
{#each years as year (year)}
|
|
|
{@const c = classFor(year)}
|
|
|
<button
|
|
|
type="button"
|
|
|
data-date-range-picker-year-view-cell
|
|
|
data-range-start={c.start ? '' : undefined}
|
|
|
data-range-end={c.end ? '' : undefined}
|
|
|
data-in-range={c.inRange ? '' : undefined}
|
|
|
aria-pressed={c.start || c.end}
|
|
|
onclick={() => selectYear(year)}
|
|
|
>
|
|
|
{year}
|
|
|
</button>
|
|
|
{/each}
|
|
|
</div>
|
|
|
</div>
|