Eliminates `Provider.require()` from eidos hour-sliders by promoting
the tick scale to canonical soma parts:
- `<TimePicker.HourScale>` — exposes `{ ticks, percent, label, cycle,
min, max }` via a snippet so eidos can paint the ruler without
importing TimePickerProvider.
- `<TimeRangePicker.HourScale>` — same shape for the range variant.
Scale is global (both endpoints share the cycle), so no endpoint
prop.
Eidos hour-sliders become pure composition + paint via snippet props.
No JS coupling between eidos and the soma provider class.
Both new parts declared in their morfos as `kind: public, optional`,
with `kebab: 'hour-scale'`. Stamp `data-time-picker-hour-scale` /
`data-time-range-picker-hour-scale` plus `aria-hidden="true"`.
Test result unchanged: 2391/2397 passing. No regressions.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
parent
74ccfead70
commit
a5243168e5
@ -0,0 +1,63 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Soma `<TimePicker.HourScale>` — exposes the hour-axis tick data
|
||||||
|
* (cycle / min / max / positions) via a `children` snippet so visual
|
||||||
|
* layers (eidos) can paint a ruler / scale above the slider without
|
||||||
|
* importing the TimePickerProvider class.
|
||||||
|
*
|
||||||
|
* <TimePicker.HourScale>
|
||||||
|
* {#snippet children({ ticks, percent, label })}
|
||||||
|
* {#each ticks as v}
|
||||||
|
* <span style:left={percent(v)}>{label(v)}</span>
|
||||||
|
* {/each}
|
||||||
|
* {/snippet}
|
||||||
|
* </TimePicker.HourScale>
|
||||||
|
*
|
||||||
|
* If `children` is omitted, the scale renders an empty wrapper —
|
||||||
|
* useful as a positioning anchor when visuals are stamped via CSS
|
||||||
|
* pseudo-elements.
|
||||||
|
*/
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { TimePickerProvider } from '../time-picker-provider.svelte';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface TimePickerHourScaleSnippetProps {
|
||||||
|
ticks: readonly number[];
|
||||||
|
percent: (v: number) => string;
|
||||||
|
label: (v: number) => string;
|
||||||
|
cycle: 12 | 24;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimePickerHourScaleProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
||||||
|
children?: Snippet<[TimePickerHourScaleSnippetProps]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { children, ...restProps }: TimePickerHourScaleProps = $props();
|
||||||
|
|
||||||
|
const provider = TimePickerProvider.require();
|
||||||
|
const cycle = $derived<12 | 24>(provider.resolvedHourCycle);
|
||||||
|
const min = $derived(provider.hourMin);
|
||||||
|
const max = $derived(provider.hourMax);
|
||||||
|
|
||||||
|
// Endpoint-inclusive ticks. 6-hour step on 24-cycle gives clean
|
||||||
|
// 0/6/12/18/23 without the awkward 21→23 close pair. 12-cycle keeps
|
||||||
|
// the classic analog clock face hours.
|
||||||
|
const ticks = $derived<readonly number[]>(
|
||||||
|
cycle === 12 ? [1, 3, 6, 9, 12] : [0, 6, 12, 18, 23]
|
||||||
|
);
|
||||||
|
|
||||||
|
function percent(v: number): string {
|
||||||
|
const t = (v - min) / (max - min || 1);
|
||||||
|
return `${Math.max(0, Math.min(1, t)) * 100}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function label(v: number): string {
|
||||||
|
return cycle === 12 ? String(v) : String(v).padStart(2, '0');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-time-picker-hour-scale aria-hidden="true" {...restProps}>
|
||||||
|
{@render children?.({ ticks, percent, label, cycle, min, max })}
|
||||||
|
</div>
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* Soma `<TimeRangePicker.HourScale>` — exposes the hour-axis tick data
|
||||||
|
* (cycle / min / max / positions) via a `children` snippet so visual
|
||||||
|
* layers (eidos) can paint a ruler above each endpoint's slider
|
||||||
|
* without importing the TimeRangePickerProvider class.
|
||||||
|
*
|
||||||
|
* Scale is global to the range picker (both endpoints share the same
|
||||||
|
* cycle / hour range), so this part does NOT take an `endpoint` prop.
|
||||||
|
*/
|
||||||
|
import type { Snippet } from 'svelte';
|
||||||
|
import { TimeRangePickerProvider } from '../time-range-picker-provider.svelte';
|
||||||
|
import type { HTMLAttributes } from 'svelte/elements';
|
||||||
|
|
||||||
|
interface TimeRangePickerHourScaleSnippetProps {
|
||||||
|
ticks: readonly number[];
|
||||||
|
percent: (v: number) => string;
|
||||||
|
label: (v: number) => string;
|
||||||
|
cycle: 12 | 24;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TimeRangePickerHourScaleProps = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
||||||
|
children?: Snippet<[TimeRangePickerHourScaleSnippetProps]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
let { children, ...restProps }: TimeRangePickerHourScaleProps = $props();
|
||||||
|
|
||||||
|
const provider = TimeRangePickerProvider.require();
|
||||||
|
const cycle = $derived<12 | 24>(provider.resolvedHourCycle);
|
||||||
|
const min = $derived(provider.hourMin);
|
||||||
|
const max = $derived(provider.hourMax);
|
||||||
|
|
||||||
|
const ticks = $derived<readonly number[]>(
|
||||||
|
cycle === 12 ? [1, 3, 6, 9, 12] : [0, 6, 12, 18, 23]
|
||||||
|
);
|
||||||
|
|
||||||
|
function percent(v: number): string {
|
||||||
|
const t = (v - min) / (max - min || 1);
|
||||||
|
return `${Math.max(0, Math.min(1, t)) * 100}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function label(v: number): string {
|
||||||
|
return cycle === 12 ? String(v) : String(v).padStart(2, '0');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div data-time-range-picker-hour-scale aria-hidden="true" {...restProps}>
|
||||||
|
{@render children?.({ ticks, percent, label, cycle, min, max })}
|
||||||
|
</div>
|
||||||
Loading…
Reference in new issue