You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/time-field/time-field-provider.svelte.ts

1094 lines
34 KiB

import { untrack } from 'svelte';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { watch } from 'runed';
import { context, type ProviderOpts, type WithRefOpts } from '../../provider';
import { boolToEmptyStrOrUndef, boolToStr } from '$adom';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import {
readableActive,
state,
type Active,
type ActiveProps,
type StateProps
remove backward-compat re-export shims Per architectural feedback: when relocating code to its correct layer, do the migration completely — update every consumer directly and delete the original. No re-export barrels at the old paths. Removed shims: - `src/uix/soma/attrs/` — was re-exporting `createAttrs` / `registerContract` / `assertContract` / `getContract` from `$uix/morfo` and the bool/data-attr formatters from `$libs/dom`. All 66 importing files updated to import from the canonical locations directly. - `src/uix/soma/reactive/` — `reactive.svelte.ts` was a thin re-export of `$libs/reactive`. The 79 consumers (every soma provider + layers + types) now import primitives directly from `$libs/reactive`. The `opts.ts` Props↔Opts bridge moved to `src/uix/soma/provider/opts.ts` (its natural home — provider plumbing, not reactive primitives). - `attachRef` / `RefAttachment` re-export from `src/uix/soma/provider/provider.svelte.ts` and from `src/uix/soma/index.ts`. Both removed. Consumers (`floating.svelte.ts`, `date-field-provider.svelte.ts`) now import directly from `$libs/reactive`. Also: - Moved `ActiveProps<T>` / `StateProps<T>` types to `src/libs/reactive/types.ts` — they're trivial generic helpers over `Active<T>` / `State<T>` and shouldn't have lived in soma. - Stripped soma's root barrel (`src/uix/soma/index.ts`) of every re-export of libs/morfo concerns. It now only re-exports actually-soma surfaces (Provider base, mergeProps, css helpers, id, types, Soma class). Result: - `grep -r '\$soma' src/uix/morfo` → 0 matches - `grep -r '\$uix' src/libs` → 0 matches - `src/uix/soma/attrs/` and `src/uix/soma/reactive/` no longer exist - Every import statement now reflects the real architectural layer Verified: vitest 19/20 files / 214 tests (1 pre-existing air dead- branch fail using `bun:test`), smoke 68/68, morfo-check 66/66.
5 months ago
} from '$libs/reactive';
import type { SomaFocusEvent, SomaKeyboardEvent, OnChangeFn } from '../../types';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { KEYS, getDirectionalKeys } from '../../keyboard';
import { isNumberString } from '../../types/guards';
import { useId } from '$active-uix/id';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { Soma } from '../../core/soma.svelte';
import {
DateFormatter,
Time,
convertTimeValueToDateValue,
convertTimeValueToTime,
isTimeBefore,
isZonedDateTime,
inferTimeGranularity,
initializeTimeSegmentValues,
createTimeSegmentContent,
isEditableTimeSegmentPart,
EDITABLE_TIME_SEGMENT_PARTS,
type HourCycle,
type TimeGranularity,
type TimeValue,
type TimeValidator,
type TimeOnInvalid,
type DateOnInvalid,
type EditableTimeSegmentPart,
type TimeSegmentObj,
type TimeSegmentPart,
type SegmentPart,
type DayPeriod
} from '$libs/days';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import {
areAllTimeSegmentsFilledDOM,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
getAnnouncer,
getFirstSegment,
handleSegmentNavigation,
initTimeSegmentStates,
isAcceptableSegmentKey,
isFirstSegment,
isSegmentNavigationKey,
moveToNextSegment,
moveToPrevSegment,
removeDescriptionElement,
setDescription,
type Announcer
} from '../../datetime';
refactor(soma): route FieldProvider through field/internals barrel + register DRP view sub-parts (audit Round 3 §3 #23 + P4 follow-up) Two related housekeeping items in one commit: **1. FieldProvider via barrel (audit Round 3 §3 #23)** 37 consumer files across soma imported `FieldProvider` from the deep path `'../field/field-provider.svelte'`, mirroring the same pattern that motivated `internals.ts` indirection on the 5 pickers earlier. Adds `src/uix/soma/components/field/internals.ts` re-exporting `FieldProvider` and exposes it from the barrel `exports.ts`. The 37 imports become the canonical `from '../field'`. Consistency only — no behavioral change. **2. DateRangePicker view sub-parts (P4 follow-up)** The MonthView/YearView soma parts created in commit `e93c0c73` stamp internal layout data-attrs (`-header`, `-prev`, `-heading`, `-next`, `-grid`, `-cell`) that weren't declared in the morfo, surfacing as 12 hardcoded-data-attr violations once the contract suite caught up. Declares the 12 sub-parts in the morfo with the canonical kebab prefix (`month-view-*`, `year-view-*`). Each entry minimal (data: [], aria: []) except `*-prev` / `*-next` which stamp `type='button'` and `*-cell` which surfaces the `data-range-{start,end,in-range}` classification states used by the eidos range tinting CSS. Test result: 2393/2399 passing — back to the canonical 6 fails (Words + cookie infra). The 13 transient regressions are closed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { FieldProvider } from '../field';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { TIME_FIELD_LANGS } from './langs';
// ── Contract ─────────────────────────────────────────────────────────────────
morfo: port 65 components + sium.lazy + morfo-check passes 66/66 All 65 soma components now consume their morfo as the cross-layer contract. createAttrs(morfo) and registerContract(morfo) overloads removed — the Morfo form is now mandatory. Each component provider has a 3-line morfo import + wire-up block instead of 20+ lines of inline declarations. Additions: sium/core/lazy.ts — lazy() combinator for recursive schemas (unblocks morfo's eventual lazy part-tree validation; tests included). sium/examples/ — booking, color, login, settings, signup, survey, tree form demos. Route demos at src/routes/test/sium/{booking,color,login,settings, signup,survey,tree}. Tooling: morfo-check.ts — now tolerates sibling parts sharing a physical element (Link variant of Item, etc.). Queries via allPartAttrs cross-scope. morfo-vocabulary-check.ts — 28 WARN (legitimate per-component vocabularies or subsets like switch missing "indeterminate"). All reviewed, none are bugs. Fixes caught by morfo-check: table: data-sorted / data-pinned marked severity:'optional' (only emitted when sorting/pinning active, not always). tree-grid: data-expanded on Row severity:'optional' (branches only). menubar: data-menubar-value declared on Trigger. splitter: data-splitter-panel-id declared on Panel. Verify: npm run check — 0 morfo errors (4 pre-existing in sium/examples, unrelated). npx vitest run src/uix/morfo — 7/7 tests pass. npm run smoke — 65/65 routes pass. npm run morfo:check — 66/66 morfos validate against real DOM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
import { timeFieldMorfo } from '../../../morfo/components/time-field';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
// ── Segment config ──────────────────────────────────────────────────────────
interface SegmentConfig {
min: number | ((provider: TimeFieldProvider) => number);
max: number | ((provider: TimeFieldProvider) => number);
cycle: number;
padZero?: boolean;
}
const SEGMENT_CONFIGS: Record<'hour' | 'minute' | 'second', SegmentConfig> = {
hour: {
min: (p) => (p.resolvedHourCycle === 12 ? 1 : 0),
max: (p) => (p.resolvedHourCycle === 24 ? 23 : 12),
cycle: 1,
padZero: true
},
minute: { min: 0, max: 59, cycle: 1, padZero: true },
second: { min: 0, max: 59, cycle: 1, padZero: true }
};
// ── Root provider ───────────────────────────────────────────────────────────
export interface TimeFieldOpts
extends
ProviderOpts,
StateProps<{
value: TimeValue | undefined;
placeholder: TimeValue;
}>,
ActiveProps<{
validate: TimeValidator<TimeValue> | undefined;
onInvalid: TimeOnInvalid | undefined;
minValue: TimeValue | undefined;
maxValue: TimeValue | undefined;
disabled: boolean;
readonly: boolean;
readonlySegments: EditableTimeSegmentPart[];
granularity: TimeGranularity | undefined;
hideTimeZone: boolean;
required: boolean;
errorMessageId: string | undefined;
locale: string;
dir: 'ltr' | 'rtl';
hourCycle: HourCycle | undefined;
}> {}
export class TimeFieldProvider {
readonly opts: TimeFieldOpts;
readonly runtimePart: SomaRuntimePart;
readonly runtime: SomaRuntime;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static readonly ctx = context<TimeFieldProvider>('TimeField');
static get(): TimeFieldProvider | undefined {
return this.ctx.getOr(undefined) as TimeFieldProvider | undefined;
}
static require(): TimeFieldProvider {
return this.ctx.get();
}
static create(opts: TimeFieldOpts) {
return new TimeFieldProvider(opts);
}
readonly soma = Soma.require();
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
readonly field = FieldProvider.get();
descriptionId = useId('time-field-description');
segmentValues = $state() as TimeSegmentObj;
states = initTimeSegmentStates();
announcer: Announcer = getAnnouncer(null);
fieldNode = $state<HTMLElement | null>(null);
labelNode = $state<HTMLElement | null>(null);
dayPeriodNode = $state<HTMLElement | null>(null);
name = $state('');
readonly formatter = {
getLocale: () => this.effectiveLocale,
dayPeriod: (date: Date, hourCycle?: HourCycle): 'AM' | 'PM' => {
const parts = new DateFormatter(this.effectiveLocale, {
hour: 'numeric',
minute: 'numeric',
hourCycle: hourCycle === 24 ? 'h23' : undefined
}).formatToParts(date);
return parts.find((p) => p.type === 'dayPeriod')?.value === 'PM' ? 'PM' : 'AM';
},
selectedTime: (value: TimeValue): string => {
const dv = convertTimeValueToDateValue(value);
const tz = isZonedDateTime(dv) ? (dv as unknown as { timeZone: string }).timeZone : 'UTC';
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
const options: Intl.DateTimeFormatOptions = { timeStyle: 'long' };
return new DateFormatter(this.effectiveLocale, options).format(dv.toDate(tz));
}
};
private constructor(opts: TimeFieldOpts) {
this.opts = opts;
this.runtime = this.soma.runtime(timeFieldMorfo, {
props: {
disabled: () => this.isDisabled,
readonly: () => this.isReadonly,
required: () => this.isRequired,
invalid: () => this.isInvalid
},
parts: {
label: () => this.labelNode?.id || undefined
}
});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
context: TimeFieldProvider.ctx,
syncAttrs: true
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.segmentValues = initializeTimeSegmentValues();
$effect(() => {
this.announcer = getAnnouncer(this.soma.dom, this.fieldNode);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
});
// Populate description on value change.
$effect(() => {
if (this.opts.value.current) {
const descId = untrack(() => this.descriptionId);
setDescription({
id: descId,
value: this.formatter.selectedTime(this.opts.value.current),
dom: this.soma.dom,
target: this.fieldNode
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
});
}
});
// Cleanup description on unmount.
$effect(() => {
return () => {
const descId = untrack(() => this.descriptionId);
removeDescriptionElement(descId, this.soma.dom, this.fieldNode);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
};
});
// Sync segmentValues ⇆ value.
if (this.opts.value.current) this.syncSegmentValues(this.opts.value.current);
$effect(() => {
this.opts.locale.current;
if (this.opts.value.current) this.syncSegmentValues(this.opts.value.current);
this.clearUpdating();
});
$effect(() => {
if (this.opts.value.current === undefined) {
this.segmentValues = initializeTimeSegmentValues();
}
});
watch(
() => this.validationStatus,
() => {
if (this.validationStatus !== false) {
this.opts.onInvalid.current?.(
this.validationStatus.reason as never,
this.validationStatus.message as never
);
}
}
);
// Dev warning: readonlySegments without a value has no concrete anchor.
{
let lastWarnedKey: string | null = null;
$effect(() => {
const segs = this.opts.readonlySegments.current;
const hasValue = this.opts.value.current !== undefined;
const key = segs.length > 0 && !hasValue ? [...segs].sort().join(',') : null;
if (key && key !== lastWarnedKey) {
lastWarnedKey = key;
this.soma?.logger.warn(
'TimeField',
'`readonlySegments` is set but `value` is undefined — lock has no concrete anchor; falling back to placeholder. Supply an initial `value` so the locked segment has a defined meaning.',
sema/morfo/eidos: typed selector builder + dialog wrapper + emerge color sema architecture - sema-map: 5-channel registry (motion / sound / color / presence / haptic) via SemaChannelSignatures declaration merging; engine stamps data-event-* tokens; flat CSS-style cascade replaces the eventLabel-overrides middle layer. - sema-map: emerge family gains base.color so intent deltas can shift hue / saturation / intensity. Without a base, the resolver was skipping the channel and Dialog open with intent='threat' rendered as neutral blue in the Sema tab visualisation. - types: SEMA_FAMILY_POLICY const drives compile-time + runtime intent requirements per family. Object shape so future per-family policy fields fit alongside. Emerge events MAY now declare intent (canon update — a Dialog confirming threat carries it in its very appearance). - chans: rename vibra→haptic, add HapticChannel V1 (Vibration API); SoundChannel eager-init on first user gesture (autoplay race fix). morfo selector discipline - morfo/selectors.ts (new): semaSelector(morfo, partKebab, matchers?) — type-checked against morfo.parts and morfo.events. Sema cascade rules MUST use it; hand-written strings are an architecture violation that breaks silently when morfo renames a part. - dialog morfo: open carries intent via fromProp; close-cancel / close-dismiss / close-dismiss-outside drop intent (cancellation has no evaluative load); close-save stays hardcoded fulfill (commit fulfils the user's decision regardless of dialog context). eidos dialog migration (4th pilot) - eidos/components/dialog/: full subdirectory wrapper — flat <Dialog> + compound Provider/Trigger/Overlay/Content/Title/Description/Close/Header/ Footer; size + position responsive props; sheet auto-form on narrow viewports; closePosition for the auto-X. - sema/components/dialog.ts: cascade rules use semaSelector(dialogMorfo, 'content', matchers?). Intent block adds character (haptic kind / pattern) but never overrides pitch / gain / contour — those are intent.deltas signature ownership and overriding flattens per-intent perceptual difference. demo controls + dialog page - src/lib/_demo (DemoSwitch / DemoEnum / DemoText / DemoRange) — unified controls reused across all component demos. - web/routes/dialog: live preview always rendered above tabs; per-event Sema tab with independent intent probe; signature visualisation + Play buttons with fallback target chain. documentation - CLAUDE.md: intent.deltas signature ownership rule; eidos drift defense doctrine (types over lint); 2026-05-09 hand-off entry. - morfo/README: Typed selector builder section. - eidos/README: linter section reframed as opt-in safety net for plain CSS recipes; the architectural mechanism is compile-time typing. - sema/README: open channel registry + flat cascade docs + override semantics + intent policy const + selector builder discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
{ context: { readonlySegments: [...segs] } }
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
);
} else if (!key) {
lastWarnedKey = null;
}
});
}
}
// ── Merged flags ────────────────────────────────────────────────────────
readonly isDisabled = $derived.by(
() => this.opts.disabled.current || (this.field?.isDisabled ?? false)
);
readonly isReadonly = $derived.by(
() => this.opts.readonly.current || (this.field?.isReadonly ?? false)
);
readonly isRequired = $derived.by(
() => this.opts.required.current || (this.field?.isRequired ?? false)
);
// ── Derived ─────────────────────────────────────────────────────────────
readonly effectiveLocale = $derived.by(
() => this.opts.locale.current || this.soma?.langs.getLocale() || 'en'
);
readonly inferredGranularity = $derived.by(() =>
inferTimeGranularity(this.opts.granularity.current)
);
readonly resolvedHourCycle: HourCycle = $derived.by(() => {
const explicit = this.opts.hourCycle.current;
if (explicit === 12 || explicit === 24) return explicit;
const fromService = this.soma?.dates?.getHourCycle?.();
if (fromService === 12 || fromService === 24) return fromService;
// Infer from locale using a 12-hour check.
const fmt = new DateFormatter(this.effectiveLocale, { hour: 'numeric', minute: 'numeric' });
return fmt.resolvedOptions().hour12 ? 12 : 24;
});
readonly readonlySegmentsSet = $derived.by(() => new Set(this.opts.readonlySegments.current));
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
readonly validationStatus = $derived.by(() => {
const value = this.opts.value.current;
if (!value) return false as const;
const msg = this.opts.validate.current?.(value as never);
if (msg) return { reason: 'custom', message: msg } as const;
const vTime = convertTimeValueToTime(value);
const minValue = this.opts.minValue.current;
if (minValue && isTimeBefore(vTime, convertTimeValueToTime(minValue))) {
return { reason: 'min' } as const;
}
const maxValue = this.opts.maxValue.current;
if (maxValue && isTimeBefore(convertTimeValueToTime(maxValue), vTime)) {
return { reason: 'max' } as const;
}
return false;
});
readonly isInvalid = $derived.by(
() => this.validationStatus !== false || (this.field?.isInvalid ?? false)
);
readonly timeRef = $derived.by(() =>
this.opts.value.current !== undefined
? convertTimeValueToTime(this.opts.value.current)
: convertTimeValueToTime(this.opts.placeholder.current)
);
readonly allSegmentContent = $derived.by(() =>
createTimeSegmentContent({
segmentValues: this.segmentValues,
timeRef: this.timeRef,
locale: this.effectiveLocale,
granularity: this.inferredGranularity,
hideTimeZone: this.opts.hideTimeZone.current,
hourCycle: this.resolvedHourCycle
})
);
readonly segmentContents = $derived.by(() => this.allSegmentContent.arr);
readonly sharedSegmentAttrs = {
role: 'spinbutton' as const,
contenteditable: 'true' as const,
tabindex: 0,
spellcheck: false,
inputmode: 'numeric' as const,
autocorrect: 'off' as const,
enterkeyhint: 'next' as const,
style: 'caret-color: transparent;',
onbeforeinput: (e: Event) => e.preventDefault()
};
// ── Label / field helpers ───────────────────────────────────────────────
getLabelledBy(segmentId: string): string {
return `${segmentId} ${this.labelNode?.id ?? ''}`;
}
getLabelNode(): HTMLElement | null {
return this.labelNode;
}
getEmptyLabel(): string {
return this.langs(TIME_FIELD_LANGS.EMPTY);
}
getSegmentLabel(part: string): string {
switch (part) {
case 'hour':
return this.langs(TIME_FIELD_LANGS.HOUR);
case 'minute':
return this.langs(TIME_FIELD_LANGS.MINUTE);
case 'second':
return this.langs(TIME_FIELD_LANGS.SECOND);
default:
return '';
}
}
getDayPeriodLabel(): string {
return this.langs(TIME_FIELD_LANGS.DAY_PERIOD);
}
getTimeZoneLabel(): string {
return this.langs(TIME_FIELD_LANGS.TIME_ZONE);
}
langs(idlangref: string): string {
return this.soma?.langs.ts(idlangref) ?? idlangref;
}
getFieldNode(): HTMLElement | null {
return this.fieldNode;
}
setFieldNode(el: HTMLElement | null) {
this.fieldNode = el;
}
setName(name: string) {
this.name = name;
}
// ── Segment updates ─────────────────────────────────────────────────────
updateSegment<P extends EditableTimeSegmentPart>(
part: P,
updater: (prev: TimeSegmentObj[P]) => TimeSegmentObj[P]
): void {
const previousValue = this.opts.value.current?.toString();
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
const prev = { ...this.segmentValues } as TimeSegmentObj;
const prevVal = prev[part];
const nextVal = updater(prevVal as TimeSegmentObj[P]);
const newSegmentValues: TimeSegmentObj = { ...prev, [part]: nextVal };
this.segmentValues = newSegmentValues;
if (areAllTimeSegmentsFilledDOM(newSegmentValues, this.fieldNode)) {
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
// Reconstruct the TimeValue.
let time: TimeValue = this.timeRef;
const usedSegments = (
EDITABLE_TIME_SEGMENT_PARTS as readonly EditableTimeSegmentPart[]
).filter((p) => isEditableTimeSegmentPart(p));
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
for (const p of usedSegments) {
const v = newSegmentValues[p];
if (v === null) continue;
if (p === 'dayPeriod') {
// Handled via hour conversion below.
continue;
}
time = time.set({ [p]: Number.parseInt(v as string) } as never);
}
// Apply dayPeriod semantics in 12-hour mode.
if (this.resolvedHourCycle === 12 && 'dayPeriod' in newSegmentValues) {
const dp = newSegmentValues.dayPeriod;
const h = (time as unknown as { hour: number }).hour;
if (dp === 'PM' && h < 12) time = time.set({ hour: h + 12 } as never);
if (dp === 'AM' && h >= 12) time = time.set({ hour: h - 12 } as never);
}
this.setValue(time);
} else {
this.setValue(undefined);
}
// Fire the `commit-set` sema event on value change — mirrors date-field so
// every incrementable field emits the same commit event (morfo declares it;
// soma fires it). `partRef('input')` is registered by BOTH the visible
// segment group and the hidden form input; the kebab-keyed runtime registry
// keeps the last one (the hidden input, whose ref can be null) → target
// error. Pass the visible field box explicitly so the commit ring (eidos
// `commit-settle`) lands on it.
const nextValue = this.opts.value.current?.toString();
if (previousValue !== nextValue) {
void this.runtime.trigger('commit-set', { fallbackTarget: this.fieldNode ?? undefined });
}
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
setValue(v: TimeValue | undefined): void {
this.opts.value.current = v;
}
syncSegmentValues(value: TimeValue): void {
const next: TimeSegmentObj = {
hour: this.padZero('hour', (value as unknown as { hour: number }).hour),
minute: this.padZero('minute', (value as unknown as { minute: number }).minute),
second: this.padZero('second', (value as unknown as { second: number }).second),
dayPeriod: this.states.dayPeriod.updating
? (this.states.dayPeriod.updating as DayPeriod)
: this.resolvedHourCycle === 12
? this.formatter.dayPeriod(
(() => {
const dv = convertTimeValueToDateValue(value);
const tz = isZonedDateTime(dv)
? (dv as unknown as { timeZone: string }).timeZone
: 'UTC';
return dv.toDate(tz);
})()
)
: 'AM'
};
this.segmentValues = next;
this.clearUpdating();
}
private padZero(part: 'hour' | 'minute' | 'second', n: number): string {
let display = n;
if (part === 'hour' && this.resolvedHourCycle === 12) {
if (display === 0) display = 12;
else if (display > 12) display = display - 12;
}
return display < 10 ? `0${display}` : `${display}`;
}
clearUpdating(): void {
(Object.keys(this.states) as EditableTimeSegmentPart[]).forEach((k) => {
this.states[k].updating = null;
});
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
dir: this.opts.dir.current,
'aria-invalid': this.isInvalid || undefined
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
} as const)
);
}
// ── Input ───────────────────────────────────────────────────────────────────
export interface TimeFieldInputOpts
extends
WithRefOpts,
ActiveProps<{
name: string;
ariaLabel: string | undefined;
ariaLabelledBy: string | undefined;
}> {}
export class TimeFieldInputProvider {
readonly opts: TimeFieldInputOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts: TimeFieldInputOpts) {
return new TimeFieldInputProvider(opts);
}
readonly provider: TimeFieldProvider;
private constructor(opts: TimeFieldInputOpts) {
this.opts = opts;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.provider = TimeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('input', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => this.provider.setFieldNode(v),
// Per-part source for the morfo's `aria-label` ← `ariaLabel` prop, so
// `renderProps()` resolves it instead of the provider re-declaring it.
// (`disabled`/`readonly`/`invalid` come from the component-level sources.)
props: { ariaLabel: () => this.opts.ariaLabel.current }
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.provider.setFieldNode(opts.ref.current);
watch(
() => this.opts.name.current,
(v) => this.provider.setName(v)
);
$effect(() => {
const field = this.provider.field;
if (!field) return;
field.inputId.current = opts.id.current;
});
}
readonly segments = $derived.by(() => this.provider.segmentContents);
readonly ariaDescribedBy = $derived.by(() => {
try {
const doc = this.provider.soma.dom.getDocument(this.opts.ref.current);
const exists = doc.getElementById(this.provider.descriptionId);
return exists ? this.provider.descriptionId : undefined;
} catch {
return undefined;
}
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
});
readonly props = $derived.by(() => {
const labelledBy = this.opts.ariaLabelledBy.current ?? this.provider.labelNode?.id;
return this.runtimePart.assert({
// Morfo-declared attrs (role=group, aria-label, aria-disabled/readonly,
// aria-invalid, aria-labelledby from the Label part), resolved against
// this part's registered sources — no longer re-declared here.
...this.runtimePart.renderProps(),
// Soma-only: aria-labelledby honours an explicit `ariaLabelledBy` prop
// (the morfo only knows the Label part); aria-describedby is the computed
// hidden-description element id (not the morfo's prop); data-invalid/
// data-disabled are morfo-declared WITHOUT a value.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
'aria-labelledby': labelledBy,
'aria-describedby': this.ariaDescribedBy,
'data-invalid': boolToEmptyStrOrUndef(this.provider.isInvalid),
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled)
} as const);
});
}
// ── Label ───────────────────────────────────────────────────────────────────
export interface TimeFieldLabelOpts extends WithRefOpts {}
export class TimeFieldLabelProvider {
readonly opts: TimeFieldLabelOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts: TimeFieldLabelOpts) {
return new TimeFieldLabelProvider(opts);
}
readonly provider: TimeFieldProvider;
private constructor(opts: TimeFieldLabelOpts) {
this.opts = opts;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.provider = TimeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('label', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => {
this.provider.labelNode = v;
},
syncAttrs: true
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
readonly onclick = () => {
const first = getFirstSegment(this.provider.getFieldNode());
this.provider.soma.dom.focus(first);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
};
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
onclick: this.onclick
} as const)
);
}
// ── Hidden input ────────────────────────────────────────────────────────────
export interface TimeFieldHiddenInputOpts extends ProviderOpts {}
export class TimeFieldHiddenInputProvider {
readonly opts: TimeFieldHiddenInputOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts: TimeFieldHiddenInputOpts) {
return new TimeFieldHiddenInputProvider(opts);
}
readonly provider: TimeFieldProvider;
private constructor(opts: TimeFieldHiddenInputOpts) {
this.opts = opts;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.provider = TimeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('input', {
id: opts.id,
ref: opts.ref,
owner: this
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
readonly props = $derived.by(() => {
const value = this.provider.opts.value.current;
// ISO time string (HH:MM:SS[.sss][+TZ]).
const isoValue = value ? value.toString() : '';
return {
type: 'hidden' as const,
name: this.provider.name,
value: isoValue,
required: this.provider.isRequired || undefined,
'aria-hidden': 'true' as const,
tabindex: -1
};
});
}
// ── Base segment (hour / minute / second) ───────────────────────────────────
export abstract class BaseTimeNumericSegmentProvider {
readonly opts: WithRefOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
readonly provider: TimeFieldProvider;
protected readonly part: 'hour' | 'minute' | 'second';
protected readonly config: SegmentConfig;
protected constructor(
opts: WithRefOpts,
part: 'hour' | 'minute' | 'second',
config: SegmentConfig
) {
this.opts = opts;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.part = part;
this.config = config;
this.provider = TimeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('segment', {
id: opts.id,
ref: opts.ref,
owner: this
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
protected get announcer(): Announcer {
return this.provider.announcer;
}
protected getMin(): number {
return typeof this.config.min === 'function' ? this.config.min(this.provider) : this.config.min;
}
protected getMax(): number {
return typeof this.config.max === 'function' ? this.config.max(this.provider) : this.config.max;
}
protected formatValue(n: number): string {
return this.config.padZero && n < 10 ? `0${n}` : `${n}`;
}
protected get directionalKeys() {
return getDirectionalKeys(this.provider.opts.dir.current, 'horizontal');
}
handleArrowUp(): void {
const stateKey = this.part as EditableTimeSegmentPart;
this.provider.states[stateKey].hasLeftFocus = false;
this.provider.updateSegment(this.part, (prev) => {
if (prev === null) {
const min = this.getMin();
this.announcer.announce(min);
return this.formatValue(min);
}
const n = Number.parseInt(prev as string);
const next = n >= this.getMax() ? this.getMin() : n + this.config.cycle;
this.announcer.announce(next);
return this.formatValue(next);
});
}
handleArrowDown(): void {
const stateKey = this.part as EditableTimeSegmentPart;
this.provider.states[stateKey].hasLeftFocus = false;
this.provider.updateSegment(this.part, (prev) => {
if (prev === null) {
const max = this.getMax();
this.announcer.announce(max);
return this.formatValue(max);
}
const n = Number.parseInt(prev as string);
const next = n <= this.getMin() ? this.getMax() : n - this.config.cycle;
this.announcer.announce(next);
return this.formatValue(next);
});
}
handleNumberKey(e: SomaKeyboardEvent): void {
const stateKey = this.part as EditableTimeSegmentPart;
const num = Number.parseInt(e.key);
const max = this.getMax();
const maxStart = Math.floor(max / 10);
this.provider.updateSegment(this.part, (prev) => {
if (this.provider.states[stateKey].hasLeftFocus) {
prev = null;
this.provider.states[stateKey].hasLeftFocus = false;
}
if (prev === null) {
if (num === 0 && !this.config.padZero) {
return null;
}
// First digit — if it would exceed max, treat as complete.
if (num > maxStart) {
this.announcer.announce(num);
moveToNextSegment(
this.provider.soma.dom,
e as unknown as KeyboardEvent,
this.provider.getFieldNode()
);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
return this.formatValue(num);
}
return `${num}`;
}
const prevN = Number.parseInt(prev as string);
const candidate = prevN * 10 + num;
if (candidate <= max) {
this.announcer.announce(candidate);
moveToNextSegment(
this.provider.soma.dom,
e as unknown as KeyboardEvent,
this.provider.getFieldNode()
);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
return this.formatValue(candidate);
}
// Replace instead of append.
this.announcer.announce(num);
if (num > maxStart) {
moveToNextSegment(
this.provider.soma.dom,
e as unknown as KeyboardEvent,
this.provider.getFieldNode()
);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
return `${num}`;
});
}
handleBackspace(e: SomaKeyboardEvent): void {
const stateKey = this.part as EditableTimeSegmentPart;
this.provider.states[stateKey].hasLeftFocus = false;
let moveToPrev = false;
this.provider.updateSegment(this.part, (prev) => {
if (prev === null) {
moveToPrev = true;
this.announcer.announce(null);
return null;
}
const str = (prev as string).toString();
if (str.length === 1) {
this.announcer.announce(null);
return null;
}
const next = Number.parseInt(str.slice(0, -1));
this.announcer.announce(next);
return `${next}`;
});
if (moveToPrev)
moveToPrevSegment(
this.provider.soma.dom,
e as unknown as KeyboardEvent,
this.provider.getFieldNode()
);
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
protected handleKeydown(e: SomaKeyboardEvent): void {
if (e.ctrlKey || e.metaKey || this.provider.isDisabled) return;
if (e.key !== KEYS.TAB) e.preventDefault();
if (!isAcceptableSegmentKey(e.key)) return;
if (this.provider.readonlySegmentsSet.has(this.part)) return;
if (e.key === KEYS.ARROW_UP) {
this.handleArrowUp();
return;
}
if (e.key === KEYS.ARROW_DOWN) {
this.handleArrowDown();
return;
}
if (isNumberString(e.key)) {
this.handleNumberKey(e);
return;
}
if (e.key === KEYS.BACKSPACE) {
this.handleBackspace(e);
return;
}
if (isSegmentNavigationKey(e.key)) {
const { nextKey, prevKey } = this.directionalKeys;
handleSegmentNavigation(
this.provider.soma.dom,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
e as unknown as KeyboardEvent,
this.provider.getFieldNode(),
nextKey,
prevKey
);
}
}
onkeydown = (e: SomaKeyboardEvent) => this.handleKeydown(e);
onfocusout = (_: SomaFocusEvent) => {
const stateKey = this.part as EditableTimeSegmentPart;
this.provider.states[stateKey].hasLeftFocus = true;
if (this.config.padZero) {
this.provider.updateSegment(this.part, (prev) => {
if (prev && (prev as string).length === 1) return `0${prev}`;
return prev;
});
}
};
getSegmentProps() {
const value = this.provider.segmentValues[this.part];
const isEmpty = value === null || value === undefined;
const valueNow = isEmpty ? undefined : Number.parseInt(value as string);
const valueMin = this.getMin();
const valueMax = this.getMax();
let valueText = isEmpty ? this.provider.getEmptyLabel() : `${valueNow}`;
if (this.part === 'hour' && this.provider.resolvedHourCycle === 12) {
const dp = (this.provider.segmentValues as TimeSegmentObj).dayPeriod;
valueText = isEmpty ? this.provider.getEmptyLabel() : `${valueNow} ${dp ?? 'AM'}`;
}
return {
'aria-label': `${this.provider.getSegmentLabel(this.part)}, `,
'aria-valuemin': valueMin,
'aria-valuemax': valueMax,
'aria-valuenow': valueNow,
'aria-valuetext': valueText
};
}
readonly props = $derived.by(() => {
const p = this.getSegmentProps();
const isReadonly = this.provider.isReadonly || this.provider.readonlySegmentsSet.has(this.part);
return this.runtimePart.assert({
// Morfo-declared attrs (role=spinbutton, aria-disabled, aria-invalid),
// resolved against the component-level sources — no longer re-declared.
...this.runtimePart.renderProps(),
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
...this.provider.sharedSegmentAttrs,
// Soma-only: data-* are morfo-declared WITHOUT a value; the value-aria
// (`...p`), per-segment aria-readonly and aria-labelledby are computed
// here and override the morfo's raw declaration.
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
'data-segment': this.part,
'data-placeholder': boolToEmptyStrOrUndef(this.provider.segmentValues[this.part] === null),
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
'data-invalid': boolToEmptyStrOrUndef(this.provider.isInvalid),
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled),
'data-readonly': boolToEmptyStrOrUndef(isReadonly),
'aria-readonly': boolToStr(isReadonly),
...p,
'aria-labelledby': this.provider.getLabelledBy(this.opts.id.current),
onkeydown: this.onkeydown,
onfocusout: this.onfocusout
} as const);
});
}
class TimeFieldHourSegmentProvider extends BaseTimeNumericSegmentProvider {
constructor(opts: WithRefOpts) {
super(opts, 'hour', SEGMENT_CONFIGS.hour);
}
}
class TimeFieldMinuteSegmentProvider extends BaseTimeNumericSegmentProvider {
constructor(opts: WithRefOpts) {
super(opts, 'minute', SEGMENT_CONFIGS.minute);
}
}
class TimeFieldSecondSegmentProvider extends BaseTimeNumericSegmentProvider {
constructor(opts: WithRefOpts) {
super(opts, 'second', SEGMENT_CONFIGS.second);
}
}
// ── DayPeriod segment ───────────────────────────────────────────────────────
export class TimeFieldDayPeriodSegmentProvider {
readonly opts: WithRefOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts: WithRefOpts) {
return new TimeFieldDayPeriodSegmentProvider(opts);
}
readonly provider: TimeFieldProvider;
private constructor(opts: WithRefOpts) {
this.opts = opts;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.provider = TimeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('segment', {
id: opts.id,
ref: opts.ref,
owner: this,
onRefChange: (v) => {
this.provider.dayPeriodNode = v;
}
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
onkeydown = (e: SomaKeyboardEvent) => {
if (e.ctrlKey || e.metaKey || this.provider.isDisabled) return;
if (this.provider.readonlySegmentsSet.has('dayPeriod')) return;
if (e.key !== KEYS.TAB) e.preventDefault();
const flip = () => {
this.provider.updateSegment('dayPeriod', (prev) => {
const next: DayPeriod = prev === 'PM' ? 'AM' : 'PM';
this.provider.announcer.announce(next);
return next;
});
};
if (
e.key === KEYS.ARROW_UP ||
e.key === KEYS.ARROW_DOWN ||
e.key === KEYS.BACKSPACE ||
e.key === KEYS.SPACE
) {
flip();
return;
}
if (e.key === 'a' || e.key === 'A') {
this.provider.updateSegment('dayPeriod', () => {
this.provider.announcer.announce('AM');
return 'AM';
});
return;
}
if (e.key === 'p' || e.key === 'P') {
this.provider.updateSegment('dayPeriod', () => {
this.provider.announcer.announce('PM');
return 'PM';
});
return;
}
if (isSegmentNavigationKey(e.key)) {
const { nextKey, prevKey } = getDirectionalKeys(this.provider.opts.dir.current, 'horizontal');
handleSegmentNavigation(
this.provider.soma.dom,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
e as unknown as KeyboardEvent,
this.provider.getFieldNode(),
nextKey,
prevKey
);
}
};
readonly props = $derived.by(() => {
const value = (this.provider.segmentValues as TimeSegmentObj).dayPeriod;
const isReadonly =
this.provider.isReadonly || this.provider.readonlySegmentsSet.has('dayPeriod');
return this.runtimePart.assert({
...this.runtimePart.props,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
...this.provider.sharedSegmentAttrs,
'data-segment': 'dayPeriod' as const,
'data-invalid': boolToEmptyStrOrUndef(this.provider.isInvalid),
'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled),
'data-readonly': boolToEmptyStrOrUndef(isReadonly),
'aria-label': this.provider.getDayPeriodLabel(),
'aria-valuemin': 0,
'aria-valuemax': 12,
'aria-valuenow': value === 'AM' ? 0 : 12,
'aria-valuetext': value ?? 'AM',
'aria-labelledby': this.provider.getLabelledBy(this.opts.id.current),
onkeydown: this.onkeydown
} as const);
});
}
// ── Literal segment ─────────────────────────────────────────────────────────
export class TimeFieldLiteralSegmentProvider {
readonly opts: WithRefOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts: WithRefOpts) {
return new TimeFieldLiteralSegmentProvider(opts);
}
private constructor(opts: WithRefOpts) {
this.opts = opts;
const provider = TimeFieldProvider.require();
this.runtimePart = provider.runtime.part('segment', {
id: opts.id,
ref: opts.ref,
owner: this
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
'aria-hidden': 'true' as const,
'data-segment': 'literal' as const
} as const)
);
}
// ── TimeZone segment (read-only) ────────────────────────────────────────────
export class TimeFieldTimeZoneSegmentProvider {
readonly opts: WithRefOpts;
readonly runtimePart: SomaRuntimePart;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
static create(opts: WithRefOpts) {
return new TimeFieldTimeZoneSegmentProvider(opts);
}
readonly provider: TimeFieldProvider;
private constructor(opts: WithRefOpts) {
this.opts = opts;
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
this.provider = TimeFieldProvider.require();
this.runtimePart = this.provider.runtime.part('segment', {
id: opts.id,
ref: opts.ref,
owner: this
});
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,
soma: tier 3 dates + tier 4 time (partial), dias/segments, docs Tier 3 (complete): DateField, DatePicker, DateRangeField, DateRangePicker Tier 4 (partial): TimeField, TimePicker (TimeRangeField pending) Core: $lib/util/dias/segments.ts — pure domain primitives for segmented date/time inputs (constants, guards, initializers, reconciliation, content generation, Intl opts) soma/external/dates re-exports everything (no façades inside soma) soma/datetime/ slimmed to UI-only helpers (announcer, DOM segment nav, SegmentState, KEYS predicates, description writers) Conventions established (A23–A29): A23 — never re-implement date helpers; extend dias A24 — readonlySegments without value anchor logs warning A25 — range components split readonly per-endpoint A26 — contenteditable segments use onbeforeinput to block IME/paste A27 — picker composition pattern with shared writableActive refs A28 — time placeholders render as hh/mm/ss via dias/segments A29 — demo pages are interactive testbeds, not static galleries Demo pages: Material Design 3 styled, every public prop wired to a live control, Field integration + state readout. Day-period uses real radio group in 12h mode. Auto-anchor on range pickers keeps the end month visible. 12h sliders map 0↔12 / 13-23↔1-11 correctly. Docs: COMPONENT_GUIDE.md + SOMA_ARCHITECTURE.md updated with A23–A29, checklist items 28–31, picker composition section, dias boundary, directory structure note for soma/datetime/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 months ago
'aria-label': this.provider.getTimeZoneLabel(),
'data-segment': 'timeZoneName' as const,
'data-invalid': boolToEmptyStrOrUndef(this.provider.isInvalid),
'data-readonly': '' as const,
'aria-readonly': 'true' as const,
tabindex: -1
} as const)
);
}
// ── Dispatcher ──────────────────────────────────────────────────────────────
export function createTimeSegmentProvider(part: SegmentPart, opts: WithRefOpts) {
switch (part) {
case 'hour':
return new TimeFieldHourSegmentProvider(opts);
case 'minute':
return new TimeFieldMinuteSegmentProvider(opts);
case 'second':
return new TimeFieldSecondSegmentProvider(opts);
case 'dayPeriod':
return TimeFieldDayPeriodSegmentProvider.create(opts);
case 'timeZoneName':
return TimeFieldTimeZoneSegmentProvider.create(opts);
case 'literal':
default:
return TimeFieldLiteralSegmentProvider.create(opts);
}
}

Powered by TurnKey Linux.