@human-kit/svelte-components 1.0.0-alpha.2 → 1.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/FOCUS_STATE_CONTRACT.md +63 -0
- package/dist/FOCUS_STATE_REVIEW_TEMPLATE.md +70 -0
- package/dist/calendar/README.md +2 -1
- package/dist/calendar/TODO.md +21 -107
- package/dist/calendar/body-cell/README.md +15 -0
- package/dist/calendar/body-cell/calendar-body-cell.svelte +116 -41
- package/dist/calendar/grid/README.md +13 -0
- package/dist/calendar/grid-body/README.md +13 -0
- package/dist/calendar/grid-header/README.md +13 -0
- package/dist/calendar/header-cell/README.md +14 -0
- package/dist/calendar/heading/README.md +13 -0
- package/dist/calendar/root/README.md +24 -0
- package/dist/calendar/root/calendar-root-test.svelte +4 -0
- package/dist/calendar/root/calendar-root-test.svelte.d.ts +1 -0
- package/dist/calendar/root/calendar-root.svelte +3 -0
- package/dist/calendar/root/calendar-root.svelte.d.ts +1 -0
- package/dist/calendar/root/context.d.ts +4 -0
- package/dist/calendar/root/context.js +28 -25
- package/dist/calendar/root/date-utils.d.ts +1 -1
- package/dist/calendar/root/date-utils.js +16 -26
- package/dist/calendar/trigger-next/README.md +14 -0
- package/dist/calendar/trigger-previous/README.md +14 -0
- package/dist/clock/README.md +75 -0
- package/dist/clock/axis/README.md +24 -0
- package/dist/clock/axis/clock-axis.svelte +37 -0
- package/dist/clock/axis/clock-axis.svelte.d.ts +8 -0
- package/dist/clock/hooks/use-wheel-scroll.svelte.d.ts +16 -0
- package/dist/clock/hooks/use-wheel-scroll.svelte.js +336 -0
- package/dist/clock/index.d.ts +10 -0
- package/dist/clock/index.js +10 -0
- package/dist/clock/index.parts.d.ts +4 -0
- package/dist/clock/index.parts.js +4 -0
- package/dist/clock/root/README.md +38 -0
- package/dist/clock/root/clock-root-test.svelte +62 -0
- package/dist/clock/root/clock-root-test.svelte.d.ts +14 -0
- package/dist/clock/root/clock-root.svelte +329 -0
- package/dist/clock/root/clock-root.svelte.d.ts +25 -0
- package/dist/clock/root/context.d.ts +22 -0
- package/dist/clock/root/context.js +15 -0
- package/dist/clock/root/resolve-visible-columns.d.ts +7 -0
- package/dist/clock/root/resolve-visible-columns.js +16 -0
- package/dist/clock/root/time-utils.d.ts +48 -0
- package/dist/clock/root/time-utils.js +314 -0
- package/dist/clock/root/wheel-options.d.ts +17 -0
- package/dist/clock/root/wheel-options.js +63 -0
- package/dist/clock/wheel-column/README.md +25 -0
- package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte +16 -0
- package/dist/clock/wheel-column/clock-wheel-column-bindable-test.svelte.d.ts +3 -0
- package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte +29 -0
- package/dist/clock/wheel-column/clock-wheel-column-custom-snippet-test.svelte.d.ts +6 -0
- package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte +11 -0
- package/dist/clock/wheel-column/clock-wheel-column-default-height-test.svelte.d.ts +3 -0
- package/dist/clock/wheel-column/clock-wheel-column-test.svelte +38 -0
- package/dist/clock/wheel-column/clock-wheel-column-test.svelte.d.ts +12 -0
- package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte +38 -0
- package/dist/clock/wheel-column/clock-wheel-column-tp-test.svelte.d.ts +12 -0
- package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte +29 -0
- package/dist/clock/wheel-column/clock-wheel-column-untagged-snippet-test.svelte.d.ts +6 -0
- package/dist/clock/wheel-column/clock-wheel-column.svelte +499 -0
- package/dist/clock/wheel-column/clock-wheel-column.svelte.d.ts +17 -0
- package/dist/clock/wheel-item/README.md +17 -0
- package/dist/clock/wheel-item/clock-wheel-item.svelte +49 -0
- package/dist/clock/wheel-item/clock-wheel-item.svelte.d.ts +17 -0
- package/dist/combobox/TODO.md +28 -175
- package/dist/combobox/button/combobox-button.svelte +2 -0
- package/dist/combobox/root/combobox.svelte +30 -0
- package/dist/datepicker/README.md +100 -0
- package/dist/datepicker/TODO.md +28 -0
- package/dist/datepicker/calendar/README.md +19 -0
- package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte +60 -0
- package/dist/datepicker/calendar/date-picker-calendar-unsafe-props-test.svelte.d.ts +3 -0
- package/dist/datepicker/calendar/date-picker-calendar.svelte +65 -0
- package/dist/datepicker/calendar/date-picker-calendar.svelte.d.ts +10 -0
- package/dist/datepicker/index.d.ts +18 -0
- package/dist/datepicker/index.js +18 -0
- package/dist/datepicker/index.parts.d.ts +14 -0
- package/dist/datepicker/index.parts.js +14 -0
- package/dist/datepicker/input/README.md +15 -0
- package/dist/datepicker/input/date-picker-input.svelte +108 -0
- package/dist/datepicker/input/date-picker-input.svelte.d.ts +11 -0
- package/dist/datepicker/internal/strict-props.d.ts +2 -0
- package/dist/datepicker/internal/strict-props.js +28 -0
- package/dist/datepicker/popover/README.md +20 -0
- package/dist/datepicker/popover/date-picker-popover-handler-test.svelte +57 -0
- package/dist/datepicker/popover/date-picker-popover-handler-test.svelte.d.ts +3 -0
- package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte +45 -0
- package/dist/datepicker/popover/date-picker-popover-unsafe-props-test.svelte.d.ts +18 -0
- package/dist/datepicker/popover/date-picker-popover.svelte +87 -0
- package/dist/datepicker/popover/date-picker-popover.svelte.d.ts +7 -0
- package/dist/datepicker/root/README.md +38 -0
- package/dist/datepicker/root/context.d.ts +43 -0
- package/dist/datepicker/root/context.js +15 -0
- package/dist/datepicker/root/date-picker-bindable-empty-test.svelte +24 -0
- package/dist/datepicker/root/date-picker-bindable-empty-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-bindable-test.svelte +41 -0
- package/dist/datepicker/root/date-picker-bindable-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-empty-test.svelte +47 -0
- package/dist/datepicker/root/date-picker-empty-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-locale-typing-test.svelte +47 -0
- package/dist/datepicker/root/date-picker-locale-typing-test.svelte.d.ts +3 -0
- package/dist/datepicker/root/date-picker-open-cancel-test.svelte +54 -0
- package/dist/datepicker/root/date-picker-open-cancel-test.svelte.d.ts +8 -0
- package/dist/datepicker/root/date-picker-root.svelte +495 -0
- package/dist/datepicker/root/date-picker-root.svelte.d.ts +24 -0
- package/dist/datepicker/root/date-picker-test.svelte +86 -0
- package/dist/datepicker/root/date-picker-test.svelte.d.ts +13 -0
- package/dist/datepicker/root/date-utils.d.ts +17 -0
- package/dist/datepicker/root/date-utils.js +138 -0
- package/dist/datepicker/root/draft-evaluation.d.ts +13 -0
- package/dist/datepicker/root/draft-evaluation.js +56 -0
- package/dist/datepicker/root/focus-controller.d.ts +3 -0
- package/dist/datepicker/root/focus-controller.js +15 -0
- package/dist/datepicker/root/open-change.d.ts +5 -0
- package/dist/datepicker/root/open-change.js +13 -0
- package/dist/datepicker/root/open-controller.d.ts +7 -0
- package/dist/datepicker/root/open-controller.js +15 -0
- package/dist/datepicker/root/segment-controller.d.ts +8 -0
- package/dist/datepicker/root/segment-controller.js +53 -0
- package/dist/datepicker/root/segment-state.d.ts +18 -0
- package/dist/datepicker/root/segment-state.js +134 -0
- package/dist/datepicker/root/value-commit.d.ts +4 -0
- package/dist/datepicker/root/value-commit.js +8 -0
- package/dist/datepicker/segment/README.md +14 -0
- package/dist/datepicker/segment/date-picker-segment.svelte +319 -0
- package/dist/datepicker/segment/date-picker-segment.svelte.d.ts +9 -0
- package/dist/datepicker/trigger/README.md +14 -0
- package/dist/datepicker/trigger/date-picker-trigger.svelte +110 -0
- package/dist/datepicker/trigger/date-picker-trigger.svelte.d.ts +9 -0
- package/dist/dialog/content/dialog-content.svelte +6 -6
- package/dist/dialog/root/context.d.ts +2 -1
- package/dist/dialog/root/dialog-root.svelte +9 -2
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/listbox/root/listbox.svelte +44 -0
- package/dist/popover/README.md +10 -0
- package/dist/popover/content/popover-content-standalone-test.svelte +28 -0
- package/dist/popover/content/popover-content-standalone-test.svelte.d.ts +6 -0
- package/dist/popover/content/popover-content-test.svelte +2 -1
- package/dist/popover/content/popover-content-test.svelte.d.ts +2 -1
- package/dist/popover/content/popover-content.svelte +91 -18
- package/dist/popover/content/popover-content.svelte.d.ts +5 -1
- package/dist/popover/index.d.ts +1 -1
- package/dist/popover/index.js +1 -3
- package/dist/popover/root/README.md +10 -15
- package/dist/popover/root/context.d.ts +16 -7
- package/dist/popover/root/context.js +0 -2
- package/dist/popover/root/focus-state.d.ts +4 -0
- package/dist/popover/root/focus-state.js +33 -0
- package/dist/popover/root/popover-root.svelte +90 -17
- package/dist/popover/root/popover-root.svelte.d.ts +2 -1
- package/dist/popover/root/popover-test.svelte +2 -1
- package/dist/popover/root/popover-test.svelte.d.ts +2 -1
- package/dist/popover/trigger/popover-trigger-button.svelte +4 -4
- package/dist/popover/trigger/popover-trigger.svelte +1 -1
- package/dist/portal/portal.svelte +3 -1
- package/dist/primitives/click-outside.d.ts +1 -1
- package/dist/primitives/click-outside.js +1 -1
- package/dist/primitives/focus-trap.d.ts +7 -2
- package/dist/primitives/focus-trap.js +50 -17
- package/dist/primitives/index.d.ts +1 -0
- package/dist/primitives/index.js +1 -0
- package/dist/primitives/input-modality.d.ts +7 -0
- package/dist/primitives/input-modality.js +125 -0
- package/dist/test-utils/focus-contract.d.ts +3 -0
- package/dist/test-utils/focus-contract.js +26 -0
- package/dist/timepicker/IMPLEMENTATION_PLAN.md +254 -0
- package/dist/timepicker/README.md +97 -0
- package/dist/timepicker/TODO.md +86 -0
- package/dist/timepicker/clock/README.md +14 -0
- package/dist/timepicker/clock/time-picker-clock-test.svelte +45 -0
- package/dist/timepicker/clock/time-picker-clock-test.svelte.d.ts +11 -0
- package/dist/timepicker/clock/time-picker-clock.svelte +65 -0
- package/dist/timepicker/clock/time-picker-clock.svelte.d.ts +10 -0
- package/dist/timepicker/index.d.ts +14 -0
- package/dist/timepicker/index.js +14 -0
- package/dist/timepicker/index.parts.d.ts +8 -0
- package/dist/timepicker/index.parts.js +8 -0
- package/dist/timepicker/input/README.md +15 -0
- package/dist/timepicker/input/time-picker-input-forwarding-test.svelte +40 -0
- package/dist/timepicker/input/time-picker-input-forwarding-test.svelte.d.ts +3 -0
- package/dist/timepicker/input/time-picker-input.svelte +109 -0
- package/dist/timepicker/input/time-picker-input.svelte.d.ts +11 -0
- package/dist/timepicker/internal/strict-props.d.ts +4 -0
- package/dist/timepicker/internal/strict-props.js +51 -0
- package/dist/timepicker/popover/README.md +20 -0
- package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte +22 -0
- package/dist/timepicker/popover/time-picker-popover-unsafe-props-test.svelte.d.ts +3 -0
- package/dist/timepicker/popover/time-picker-popover.svelte +89 -0
- package/dist/timepicker/popover/time-picker-popover.svelte.d.ts +7 -0
- package/dist/timepicker/root/README.md +42 -0
- package/dist/timepicker/root/context.d.ts +51 -0
- package/dist/timepicker/root/context.js +15 -0
- package/dist/timepicker/root/time-picker-12h-test.svelte +22 -0
- package/dist/timepicker/root/time-picker-12h-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-bindable-test.svelte +25 -0
- package/dist/timepicker/root/time-picker-bindable-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-empty-test.svelte +20 -0
- package/dist/timepicker/root/time-picker-empty-test.svelte.d.ts +3 -0
- package/dist/timepicker/root/time-picker-root.svelte +625 -0
- package/dist/timepicker/root/time-picker-root.svelte.d.ts +28 -0
- package/dist/timepicker/root/time-picker-test.svelte +72 -0
- package/dist/timepicker/root/time-picker-test.svelte.d.ts +15 -0
- package/dist/timepicker/root/time-utils.d.ts +1 -0
- package/dist/timepicker/root/time-utils.js +3 -0
- package/dist/timepicker/segment/README.md +14 -0
- package/dist/timepicker/segment/time-picker-segment.svelte +365 -0
- package/dist/timepicker/segment/time-picker-segment.svelte.d.ts +9 -0
- package/dist/timepicker/trigger/README.md +14 -0
- package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte +35 -0
- package/dist/timepicker/trigger/time-picker-trigger-forwarding-test.svelte.d.ts +3 -0
- package/dist/timepicker/trigger/time-picker-trigger.svelte +122 -0
- package/dist/timepicker/trigger/time-picker-trigger.svelte.d.ts +9 -0
- package/dist/utils/date-only.d.ts +11 -0
- package/dist/utils/date-only.js +53 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +16 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack, type Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import { useLocaleContextOptional } from '../../locale-provider/context';
|
|
5
|
+
import { setClockContext, type ClockContext } from './context';
|
|
6
|
+
import { resolveVisibleColumns, type ClockColumnInfo } from './resolve-visible-columns';
|
|
7
|
+
import { buildWheelOptions } from './wheel-options';
|
|
8
|
+
import ClockWheelColumn from '../wheel-column/clock-wheel-column.svelte';
|
|
9
|
+
import {
|
|
10
|
+
buildTimePartsFromDraft,
|
|
11
|
+
clampToStep,
|
|
12
|
+
createEmptyTimePickerDraft,
|
|
13
|
+
formatTimePickerValue,
|
|
14
|
+
getSegmentLabel,
|
|
15
|
+
isSegmentValueEmpty,
|
|
16
|
+
isTimeOutOfRange,
|
|
17
|
+
isValidTimePickerValue,
|
|
18
|
+
normalizeSegmentNumberInput,
|
|
19
|
+
toDraftFromTimeValue,
|
|
20
|
+
type TimePickerDraft,
|
|
21
|
+
type TimePickerEditableSegmentType,
|
|
22
|
+
type TimePickerGranularity,
|
|
23
|
+
type TimePickerHourCycle,
|
|
24
|
+
type TimePickerTimeValue
|
|
25
|
+
} from './time-utils';
|
|
26
|
+
|
|
27
|
+
type ClockRootProps = Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> & {
|
|
28
|
+
id?: string;
|
|
29
|
+
value?: TimePickerTimeValue | null;
|
|
30
|
+
defaultValue?: TimePickerTimeValue | null;
|
|
31
|
+
onChange?: (value: TimePickerTimeValue | null) => void;
|
|
32
|
+
minValue?: TimePickerTimeValue;
|
|
33
|
+
maxValue?: TimePickerTimeValue;
|
|
34
|
+
hourCycle?: TimePickerHourCycle;
|
|
35
|
+
granularity?: TimePickerGranularity;
|
|
36
|
+
hourStep?: number;
|
|
37
|
+
minuteStep?: number;
|
|
38
|
+
secondStep?: number;
|
|
39
|
+
isDisabled?: boolean;
|
|
40
|
+
column?: Snippet<[ClockColumnInfo]>;
|
|
41
|
+
children?: Snippet;
|
|
42
|
+
class?: string;
|
|
43
|
+
'aria-label'?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const generatedInstanceId = $props.id();
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
id,
|
|
50
|
+
value = $bindable(),
|
|
51
|
+
defaultValue,
|
|
52
|
+
onChange,
|
|
53
|
+
minValue,
|
|
54
|
+
maxValue,
|
|
55
|
+
hourCycle,
|
|
56
|
+
granularity = 'minute',
|
|
57
|
+
hourStep = 1,
|
|
58
|
+
minuteStep = 1,
|
|
59
|
+
secondStep = 1,
|
|
60
|
+
isDisabled = false,
|
|
61
|
+
column: columnSnippet,
|
|
62
|
+
children,
|
|
63
|
+
class: className = '',
|
|
64
|
+
'aria-label': ariaLabel,
|
|
65
|
+
...restProps
|
|
66
|
+
}: ClockRootProps = $props();
|
|
67
|
+
|
|
68
|
+
function hasExplicitPositionClass(value: string): boolean {
|
|
69
|
+
return /(^|\s)(?:[\w-]+:)*(?:static|fixed|absolute|relative|sticky)(?:\s|$)/.test(value);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolvedClassName = $derived.by(() => {
|
|
73
|
+
const trimmed = className.trim();
|
|
74
|
+
if (trimmed.length === 0) return 'relative';
|
|
75
|
+
if (hasExplicitPositionClass(trimmed)) return trimmed;
|
|
76
|
+
return `${trimmed} relative`;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const instanceId = untrack(() => id) ?? generatedInstanceId;
|
|
80
|
+
const localeContext = useLocaleContextOptional();
|
|
81
|
+
const localeStore = localeContext?.locale;
|
|
82
|
+
const localeFromContext = $derived.by(() => {
|
|
83
|
+
if (!localeStore) return undefined;
|
|
84
|
+
return $localeStore;
|
|
85
|
+
});
|
|
86
|
+
const systemLocale = untrack(() => Intl.DateTimeFormat().resolvedOptions().locale);
|
|
87
|
+
const resolvedLocale = $derived(localeFromContext ?? systemLocale);
|
|
88
|
+
const resolvedHourCycle = $derived.by<TimePickerHourCycle>(() => {
|
|
89
|
+
if (hourCycle) return hourCycle;
|
|
90
|
+
const localeCycle = new Intl.DateTimeFormat(resolvedLocale, {
|
|
91
|
+
hour: 'numeric'
|
|
92
|
+
}).resolvedOptions().hourCycle;
|
|
93
|
+
return localeCycle === 'h11' || localeCycle === 'h12' ? 12 : 24;
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const initialValueProp = untrack(() => value);
|
|
97
|
+
const initialDefaultValue = untrack(() =>
|
|
98
|
+
isValidTimePickerValue(defaultValue) ? defaultValue : null
|
|
99
|
+
);
|
|
100
|
+
const initialPropValue =
|
|
101
|
+
initialValueProp === undefined
|
|
102
|
+
? initialDefaultValue
|
|
103
|
+
: isValidTimePickerValue(initialValueProp)
|
|
104
|
+
? initialValueProp
|
|
105
|
+
: null;
|
|
106
|
+
const initialHourCycle = untrack<TimePickerHourCycle>(() => {
|
|
107
|
+
if (hourCycle) return hourCycle;
|
|
108
|
+
const localeCycle = new Intl.DateTimeFormat(resolvedLocale, {
|
|
109
|
+
hour: 'numeric'
|
|
110
|
+
}).resolvedOptions().hourCycle;
|
|
111
|
+
return localeCycle === 'h11' || localeCycle === 'h12' ? 12 : 24;
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let valueInternal = $state<TimePickerTimeValue | null>(initialPropValue);
|
|
115
|
+
let lastPublishedValue = $state<TimePickerTimeValue | null>(initialPropValue);
|
|
116
|
+
let segmentDraft = $state<TimePickerDraft>(
|
|
117
|
+
initialPropValue
|
|
118
|
+
? toDraftFromTimeValue(initialPropValue, initialHourCycle)
|
|
119
|
+
: createEmptyTimePickerDraft()
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
if (initialValueProp === undefined) {
|
|
123
|
+
value = initialPropValue;
|
|
124
|
+
} else if (initialValueProp !== initialPropValue) {
|
|
125
|
+
value = initialPropValue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
$effect(() => {
|
|
129
|
+
const nextValue = value === undefined ? null : isValidTimePickerValue(value) ? value : null;
|
|
130
|
+
if (nextValue === lastPublishedValue) return;
|
|
131
|
+
publishCommittedValue(nextValue, false);
|
|
132
|
+
segmentDraft = nextValue
|
|
133
|
+
? toDraftFromTimeValue(nextValue, resolvedHourCycle)
|
|
134
|
+
: createEmptyTimePickerDraft();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const normalizedMinValue = $derived(isValidTimePickerValue(minValue) ? minValue : undefined);
|
|
138
|
+
const normalizedMaxValue = $derived(isValidTimePickerValue(maxValue) ? maxValue : undefined);
|
|
139
|
+
|
|
140
|
+
function publishCommittedValue(
|
|
141
|
+
nextValue: TimePickerTimeValue | null,
|
|
142
|
+
emitChange: boolean
|
|
143
|
+
): boolean {
|
|
144
|
+
const bindableValue = value === undefined ? valueInternal : value;
|
|
145
|
+
const normalizedBindableValue = isValidTimePickerValue(bindableValue) ? bindableValue : null;
|
|
146
|
+
const didInternalChange = valueInternal !== nextValue;
|
|
147
|
+
const didBindableChange = normalizedBindableValue !== nextValue;
|
|
148
|
+
if (!didInternalChange && !didBindableChange) return false;
|
|
149
|
+
|
|
150
|
+
valueInternal = nextValue;
|
|
151
|
+
lastPublishedValue = nextValue;
|
|
152
|
+
if (didBindableChange) {
|
|
153
|
+
value = nextValue;
|
|
154
|
+
}
|
|
155
|
+
if (emitChange && didInternalChange) {
|
|
156
|
+
onChange?.(nextValue);
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getSegmentValue(type: TimePickerEditableSegmentType): string {
|
|
162
|
+
if (type === 'hour') return segmentDraft.hour;
|
|
163
|
+
if (type === 'minute') return segmentDraft.minute;
|
|
164
|
+
if (type === 'second') return segmentDraft.second;
|
|
165
|
+
return segmentDraft.dayPeriod;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function setSegmentValue(type: TimePickerEditableSegmentType, nextValue: string) {
|
|
169
|
+
if (isDisabled) return;
|
|
170
|
+
if (type === 'dayPeriod') {
|
|
171
|
+
const normalized = nextValue.trim().toUpperCase();
|
|
172
|
+
if (normalized === '' || normalized === 'AM' || normalized === 'PM') {
|
|
173
|
+
segmentDraft.dayPeriod = normalized;
|
|
174
|
+
} else {
|
|
175
|
+
segmentDraft.dayPeriod = '';
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const maxDigits = 2;
|
|
179
|
+
let normalized = normalizeSegmentNumberInput(nextValue, maxDigits);
|
|
180
|
+
if (normalized.length > 0) {
|
|
181
|
+
const numeric = Number(normalized);
|
|
182
|
+
if (type === 'hour') {
|
|
183
|
+
if (resolvedHourCycle === 12) {
|
|
184
|
+
normalized = String(clampToStep(numeric, Math.max(1, hourStep), 1, 12));
|
|
185
|
+
} else {
|
|
186
|
+
normalized = String(clampToStep(numeric, Math.max(1, hourStep), 0, 23));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (type === 'minute') {
|
|
190
|
+
normalized = String(clampToStep(numeric, Math.max(1, minuteStep), 0, 59));
|
|
191
|
+
}
|
|
192
|
+
if (type === 'second') {
|
|
193
|
+
normalized = String(clampToStep(numeric, Math.max(1, secondStep), 0, 59));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (type === 'hour') segmentDraft.hour = normalized;
|
|
198
|
+
if (type === 'minute') segmentDraft.minute = normalized;
|
|
199
|
+
if (type === 'second') segmentDraft.second = normalized;
|
|
200
|
+
|
|
201
|
+
if (resolvedHourCycle === 12 && isSegmentValueEmpty(segmentDraft.dayPeriod)) {
|
|
202
|
+
segmentDraft.dayPeriod = 'AM';
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
commitFromDraft();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function commitFromDraft() {
|
|
210
|
+
const nextParts = buildTimePartsFromDraft(segmentDraft, granularity, resolvedHourCycle);
|
|
211
|
+
if (!nextParts) {
|
|
212
|
+
publishCommittedValue(null, true);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const candidateValue = formatTimePickerValue(nextParts, granularity);
|
|
217
|
+
if (isTimeOutOfRange(candidateValue, normalizedMinValue, normalizedMaxValue, granularity)) {
|
|
218
|
+
publishCommittedValue(null, true);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
publishCommittedValue(candidateValue, true);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function selectWheelValue(type: TimePickerEditableSegmentType, optionValue: string) {
|
|
226
|
+
if (isDisabled) return;
|
|
227
|
+
|
|
228
|
+
if (type === 'dayPeriod') {
|
|
229
|
+
setSegmentValue(type, optionValue.toUpperCase());
|
|
230
|
+
} else {
|
|
231
|
+
setSegmentValue(type, optionValue);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function getSelectedWheelValue(type: TimePickerEditableSegmentType): string | null {
|
|
236
|
+
const selected = getSegmentValue(type);
|
|
237
|
+
return selected.trim().length > 0 ? selected : null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getWheelOptions(type: TimePickerEditableSegmentType) {
|
|
241
|
+
const hasRangeBounds = normalizedMinValue !== undefined || normalizedMaxValue !== undefined;
|
|
242
|
+
|
|
243
|
+
const getCandidateFromPartial = (
|
|
244
|
+
partial: Partial<TimePickerDraft>
|
|
245
|
+
): TimePickerTimeValue | null => {
|
|
246
|
+
const candidateDraft: TimePickerDraft = {
|
|
247
|
+
hour: partial.hour ?? segmentDraft.hour,
|
|
248
|
+
minute: partial.minute ?? segmentDraft.minute,
|
|
249
|
+
second: partial.second ?? segmentDraft.second,
|
|
250
|
+
dayPeriod: partial.dayPeriod ?? segmentDraft.dayPeriod
|
|
251
|
+
};
|
|
252
|
+
const parts = buildTimePartsFromDraft(candidateDraft, granularity, resolvedHourCycle);
|
|
253
|
+
if (!parts) return null;
|
|
254
|
+
return formatTimePickerValue(parts, granularity);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
return buildWheelOptions({
|
|
258
|
+
type,
|
|
259
|
+
granularity,
|
|
260
|
+
hourCycle: resolvedHourCycle,
|
|
261
|
+
hourStep,
|
|
262
|
+
minuteStep,
|
|
263
|
+
secondStep,
|
|
264
|
+
hasRangeBounds,
|
|
265
|
+
getCandidateFromPartial,
|
|
266
|
+
isOutOfRange: (candidate) =>
|
|
267
|
+
isTimeOutOfRange(candidate, normalizedMinValue, normalizedMaxValue, granularity)
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function getSegmentLabelByType(type: TimePickerEditableSegmentType): string {
|
|
272
|
+
return getSegmentLabel(type, resolvedLocale);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const context: ClockContext = {
|
|
276
|
+
get id() {
|
|
277
|
+
return instanceId;
|
|
278
|
+
},
|
|
279
|
+
get locale() {
|
|
280
|
+
return resolvedLocale;
|
|
281
|
+
},
|
|
282
|
+
get isDisabled() {
|
|
283
|
+
return isDisabled;
|
|
284
|
+
},
|
|
285
|
+
get granularity() {
|
|
286
|
+
return granularity;
|
|
287
|
+
},
|
|
288
|
+
get hourCycle() {
|
|
289
|
+
return resolvedHourCycle;
|
|
290
|
+
},
|
|
291
|
+
get open() {
|
|
292
|
+
return true;
|
|
293
|
+
},
|
|
294
|
+
selectWheelValue,
|
|
295
|
+
getSelectedWheelValue,
|
|
296
|
+
getWheelOptions,
|
|
297
|
+
getSegmentLabel: getSegmentLabelByType
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
setClockContext(context);
|
|
301
|
+
|
|
302
|
+
const visibleColumns = $derived.by(() =>
|
|
303
|
+
resolveVisibleColumns(granularity, resolvedHourCycle, getSegmentLabelByType)
|
|
304
|
+
);
|
|
305
|
+
</script>
|
|
306
|
+
|
|
307
|
+
<div
|
|
308
|
+
id={instanceId}
|
|
309
|
+
class={resolvedClassName}
|
|
310
|
+
role="group"
|
|
311
|
+
aria-label={ariaLabel}
|
|
312
|
+
data-clock="true"
|
|
313
|
+
{...restProps}
|
|
314
|
+
>
|
|
315
|
+
{#if columnSnippet}
|
|
316
|
+
{#each visibleColumns as col (col.type)}
|
|
317
|
+
{@render columnSnippet(col)}
|
|
318
|
+
{/each}
|
|
319
|
+
{#if children}
|
|
320
|
+
{@render children()}
|
|
321
|
+
{/if}
|
|
322
|
+
{:else if children}
|
|
323
|
+
{@render children()}
|
|
324
|
+
{:else}
|
|
325
|
+
{#each visibleColumns as col (col.type)}
|
|
326
|
+
<ClockWheelColumn type={col.type} />
|
|
327
|
+
{/each}
|
|
328
|
+
{/if}
|
|
329
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { type ClockColumnInfo } from './resolve-visible-columns';
|
|
4
|
+
import { type TimePickerGranularity, type TimePickerHourCycle, type TimePickerTimeValue } from './time-utils';
|
|
5
|
+
type ClockRootProps = Omit<HTMLAttributes<HTMLDivElement>, 'class' | 'children'> & {
|
|
6
|
+
id?: string;
|
|
7
|
+
value?: TimePickerTimeValue | null;
|
|
8
|
+
defaultValue?: TimePickerTimeValue | null;
|
|
9
|
+
onChange?: (value: TimePickerTimeValue | null) => void;
|
|
10
|
+
minValue?: TimePickerTimeValue;
|
|
11
|
+
maxValue?: TimePickerTimeValue;
|
|
12
|
+
hourCycle?: TimePickerHourCycle;
|
|
13
|
+
granularity?: TimePickerGranularity;
|
|
14
|
+
hourStep?: number;
|
|
15
|
+
minuteStep?: number;
|
|
16
|
+
secondStep?: number;
|
|
17
|
+
isDisabled?: boolean;
|
|
18
|
+
column?: Snippet<[ClockColumnInfo]>;
|
|
19
|
+
children?: Snippet;
|
|
20
|
+
class?: string;
|
|
21
|
+
'aria-label'?: string;
|
|
22
|
+
};
|
|
23
|
+
declare const ClockRoot: import("svelte").Component<ClockRootProps, {}, "value">;
|
|
24
|
+
type ClockRoot = ReturnType<typeof ClockRoot>;
|
|
25
|
+
export default ClockRoot;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { TimePickerEditableSegmentType, TimePickerGranularity, TimePickerHourCycle } from './time-utils';
|
|
2
|
+
export type ClockContext = {
|
|
3
|
+
id: string;
|
|
4
|
+
locale: string;
|
|
5
|
+
isDisabled: boolean;
|
|
6
|
+
granularity: TimePickerGranularity;
|
|
7
|
+
hourCycle: TimePickerHourCycle;
|
|
8
|
+
/** Whether the clock is visible and should align scroll positions. */
|
|
9
|
+
open: boolean;
|
|
10
|
+
selectWheelValue: (type: TimePickerEditableSegmentType, value: string) => void;
|
|
11
|
+
getSelectedWheelValue: (type: TimePickerEditableSegmentType) => string | null;
|
|
12
|
+
getWheelOptions: (type: TimePickerEditableSegmentType) => Array<{
|
|
13
|
+
value: string;
|
|
14
|
+
label: string;
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
}>;
|
|
17
|
+
getSegmentLabel: (type: TimePickerEditableSegmentType) => string;
|
|
18
|
+
};
|
|
19
|
+
export declare function setClockContext(context: ClockContext): void;
|
|
20
|
+
export declare function getClockContext(): ClockContext | undefined;
|
|
21
|
+
export declare function useClockContext(): ClockContext;
|
|
22
|
+
export type { TimePickerEditableSegmentType as ClockEditableSegmentType };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { getContext, setContext } from 'svelte';
|
|
2
|
+
const KEY = Symbol('clock');
|
|
3
|
+
export function setClockContext(context) {
|
|
4
|
+
setContext(KEY, context);
|
|
5
|
+
}
|
|
6
|
+
export function getClockContext() {
|
|
7
|
+
return getContext(KEY);
|
|
8
|
+
}
|
|
9
|
+
export function useClockContext() {
|
|
10
|
+
const context = getClockContext();
|
|
11
|
+
if (!context) {
|
|
12
|
+
throw new Error('Clock components must be used within Clock.Root.');
|
|
13
|
+
}
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ClockEditableSegmentType } from './context';
|
|
2
|
+
import type { TimePickerGranularity, TimePickerHourCycle } from './time-utils';
|
|
3
|
+
export type ClockColumnInfo = {
|
|
4
|
+
type: ClockEditableSegmentType;
|
|
5
|
+
label?: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function resolveVisibleColumns(granularity: TimePickerGranularity, hourCycle: TimePickerHourCycle, getSegmentLabel?: (type: ClockEditableSegmentType) => string): ClockColumnInfo[];
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function resolveVisibleColumns(granularity, hourCycle, getSegmentLabel) {
|
|
2
|
+
const columns = ['hour'];
|
|
3
|
+
if (granularity !== 'hour') {
|
|
4
|
+
columns.push('minute');
|
|
5
|
+
}
|
|
6
|
+
if (granularity === 'second') {
|
|
7
|
+
columns.push('second');
|
|
8
|
+
}
|
|
9
|
+
if (hourCycle === 12) {
|
|
10
|
+
columns.push('dayPeriod');
|
|
11
|
+
}
|
|
12
|
+
return columns.map((type) => ({
|
|
13
|
+
type,
|
|
14
|
+
label: getSegmentLabel?.(type)
|
|
15
|
+
}));
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type TimePickerGranularity = 'hour' | 'minute' | 'second';
|
|
2
|
+
export type TimePickerHourCycle = 12 | 24;
|
|
3
|
+
export type TimePickerTimeValue = string;
|
|
4
|
+
export type TimePickerSegmentType = 'hour' | 'minute' | 'second' | 'dayPeriod' | 'literal';
|
|
5
|
+
export type TimePickerEditableSegmentType = Exclude<TimePickerSegmentType, 'literal'>;
|
|
6
|
+
export type TimePickerSegmentPart = {
|
|
7
|
+
type: TimePickerSegmentType;
|
|
8
|
+
text: string;
|
|
9
|
+
isPlaceholder: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type TimePickerDraft = {
|
|
12
|
+
hour: string;
|
|
13
|
+
minute: string;
|
|
14
|
+
second: string;
|
|
15
|
+
dayPeriod: string;
|
|
16
|
+
};
|
|
17
|
+
export type TimeParts = {
|
|
18
|
+
hour: number;
|
|
19
|
+
minute: number;
|
|
20
|
+
second: number;
|
|
21
|
+
};
|
|
22
|
+
export declare function createEmptyTimePickerDraft(): TimePickerDraft;
|
|
23
|
+
export declare function isValidTimePickerValue(value: unknown): value is TimePickerTimeValue;
|
|
24
|
+
export declare function parseTimePickerValue(value: TimePickerTimeValue): TimeParts | null;
|
|
25
|
+
export declare function formatTimePickerValue(parts: TimeParts, granularity: TimePickerGranularity): TimePickerTimeValue;
|
|
26
|
+
export declare function toDraftFromTimeValue(value: TimePickerTimeValue, hourCycle: TimePickerHourCycle): TimePickerDraft;
|
|
27
|
+
export declare function getRequiredSegments(granularity: TimePickerGranularity, hourCycle: TimePickerHourCycle): TimePickerEditableSegmentType[];
|
|
28
|
+
export declare function normalizeSegmentNumberInput(value: string, maxDigits?: number): string;
|
|
29
|
+
export declare function isSegmentValueEmpty(value: string | undefined): boolean;
|
|
30
|
+
export declare function buildTimePartsFromDraft(draft: TimePickerDraft, granularity: TimePickerGranularity, hourCycle: TimePickerHourCycle): TimeParts | null;
|
|
31
|
+
export declare function compareTimeParts(left: TimeParts, right: TimeParts): number;
|
|
32
|
+
export declare function isTimeOutOfRange(value: TimePickerTimeValue, minValue: TimePickerTimeValue | undefined, maxValue: TimePickerTimeValue | undefined, granularity?: TimePickerGranularity): boolean;
|
|
33
|
+
export declare function clampToStep(value: number, step: number, min: number, max: number): number;
|
|
34
|
+
export declare function adjustSegmentWithStep(currentValue: string, type: Exclude<TimePickerSegmentType, 'literal' | 'dayPeriod'>, delta: number, options: {
|
|
35
|
+
hourCycle: TimePickerHourCycle;
|
|
36
|
+
hourStep: number;
|
|
37
|
+
minuteStep: number;
|
|
38
|
+
secondStep: number;
|
|
39
|
+
}): string;
|
|
40
|
+
export declare function buildTimePickerSegments(params: {
|
|
41
|
+
locale: string;
|
|
42
|
+
hourCycle: TimePickerHourCycle;
|
|
43
|
+
granularity: TimePickerGranularity;
|
|
44
|
+
draft: TimePickerDraft;
|
|
45
|
+
formatter?: Intl.DateTimeFormat;
|
|
46
|
+
}): TimePickerSegmentPart[];
|
|
47
|
+
export declare function getEditableSegmentOrder(segments: TimePickerSegmentPart[]): TimePickerEditableSegmentType[];
|
|
48
|
+
export declare function getSegmentLabel(type: TimePickerEditableSegmentType, locale: string): string;
|