Relocate the 10 pure helpers (monthBoundaryFrom/To, buildMonthSequence, fillDensityGaps, clipBucketsToRange, aggregateToYears, selectionBoundaryFrom/To, tickIndicesFor, formatTickLabel) and their unit tests out of document/timeline.ts into a shared module so lib/timeline/ can consume them without importing lib/document/. The /api/documents/density glue (buildDensityUrl, fetchDensity, DensityState, DensityFilters) stays in document/timeline.ts. Re-point the three density components and the density-filter spec at the shared module. Refs #779 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
41 lines
1.2 KiB
Svelte
41 lines
1.2 KiB
Svelte
<script lang="ts">
|
|
import { formatTickLabel, tickIndicesFor } from '$lib/shared/utils/monthBuckets';
|
|
import { getLocale } from '$lib/paraglide/runtime';
|
|
import type { components } from '$lib/generated/api';
|
|
|
|
type MonthBucket = components['schemas']['MonthBucket'];
|
|
|
|
let {
|
|
filled
|
|
}: {
|
|
filled: MonthBucket[];
|
|
} = $props();
|
|
|
|
const tickIndices = $derived(tickIndicesFor(filled));
|
|
|
|
// When all visible buckets share a year, the X-axis omits the year so a
|
|
// 12-month zoom reads as "Jan Feb Mär…" without repetition.
|
|
const omitTickYear = $derived.by(() => {
|
|
if (filled.length === 0 || filled[0].month.length === 4) return false;
|
|
const firstYear = filled[0].month.slice(0, 4);
|
|
return filled.every((b) => b.month.slice(0, 4) === firstYear);
|
|
});
|
|
</script>
|
|
|
|
<div
|
|
class="relative mt-1 h-4 font-sans text-xs leading-none text-ink-3"
|
|
aria-hidden="true"
|
|
data-testid="timeline-x-axis"
|
|
>
|
|
{#each tickIndices as idx (filled[idx]?.month)}
|
|
{@const tickLeftPct = ((idx + 0.5) / filled.length) * 100}
|
|
<span
|
|
class="absolute -translate-x-1/2 whitespace-nowrap"
|
|
data-testid="timeline-x-tick"
|
|
style="left: {tickLeftPct}%;"
|
|
>
|
|
{formatTickLabel(filled[idx].month, getLocale(), omitTickYear)}
|
|
</span>
|
|
{/each}
|
|
</div>
|