@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,314 @@
|
|
|
1
|
+
const timeValuePattern = /^(?:[01]\d|2[0-3]):[0-5]\d(?::[0-5]\d)?$/;
|
|
2
|
+
export function createEmptyTimePickerDraft() {
|
|
3
|
+
return {
|
|
4
|
+
hour: '',
|
|
5
|
+
minute: '',
|
|
6
|
+
second: '',
|
|
7
|
+
dayPeriod: ''
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function isValidTimePickerValue(value) {
|
|
11
|
+
return typeof value === 'string' && timeValuePattern.test(value);
|
|
12
|
+
}
|
|
13
|
+
export function parseTimePickerValue(value) {
|
|
14
|
+
if (!isValidTimePickerValue(value))
|
|
15
|
+
return null;
|
|
16
|
+
const [hourPart, minutePart, secondPart] = value.split(':');
|
|
17
|
+
return {
|
|
18
|
+
hour: Number(hourPart),
|
|
19
|
+
minute: Number(minutePart),
|
|
20
|
+
second: Number(secondPart ?? '0')
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function pad2(value) {
|
|
24
|
+
return value.toString().padStart(2, '0');
|
|
25
|
+
}
|
|
26
|
+
function formatDraftTwoDigits(value) {
|
|
27
|
+
const numeric = Number(value);
|
|
28
|
+
if (!Number.isFinite(numeric))
|
|
29
|
+
return value;
|
|
30
|
+
return pad2(Math.trunc(numeric));
|
|
31
|
+
}
|
|
32
|
+
function getDayPeriodPlaceholder(locale) {
|
|
33
|
+
try {
|
|
34
|
+
const formatter = new Intl.DateTimeFormat(locale, {
|
|
35
|
+
hour: 'numeric',
|
|
36
|
+
hourCycle: 'h12',
|
|
37
|
+
timeZone: 'UTC'
|
|
38
|
+
});
|
|
39
|
+
const dayPeriod = formatter
|
|
40
|
+
.formatToParts(new Date(Date.UTC(2024, 0, 1, 9, 0, 0)))
|
|
41
|
+
.find((part) => part.type === 'dayPeriod')
|
|
42
|
+
?.value?.trim();
|
|
43
|
+
return dayPeriod && dayPeriod.length > 0 ? dayPeriod : 'AM';
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return 'AM';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function formatTimePickerValue(parts, granularity) {
|
|
50
|
+
const hour = pad2(parts.hour);
|
|
51
|
+
if (granularity === 'hour') {
|
|
52
|
+
return `${hour}:00`;
|
|
53
|
+
}
|
|
54
|
+
const minute = pad2(parts.minute);
|
|
55
|
+
if (granularity === 'minute') {
|
|
56
|
+
return `${hour}:${minute}`;
|
|
57
|
+
}
|
|
58
|
+
return `${hour}:${minute}:${pad2(parts.second)}`;
|
|
59
|
+
}
|
|
60
|
+
export function toDraftFromTimeValue(value, hourCycle) {
|
|
61
|
+
const parsed = parseTimePickerValue(value);
|
|
62
|
+
if (!parsed)
|
|
63
|
+
return createEmptyTimePickerDraft();
|
|
64
|
+
if (hourCycle === 24) {
|
|
65
|
+
return {
|
|
66
|
+
hour: String(parsed.hour),
|
|
67
|
+
minute: String(parsed.minute),
|
|
68
|
+
second: String(parsed.second),
|
|
69
|
+
dayPeriod: ''
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
const isPm = parsed.hour >= 12;
|
|
73
|
+
const hour12 = parsed.hour % 12 || 12;
|
|
74
|
+
return {
|
|
75
|
+
hour: String(hour12),
|
|
76
|
+
minute: String(parsed.minute),
|
|
77
|
+
second: String(parsed.second),
|
|
78
|
+
dayPeriod: isPm ? 'PM' : 'AM'
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export function getRequiredSegments(granularity, hourCycle) {
|
|
82
|
+
const required = ['hour'];
|
|
83
|
+
if (granularity !== 'hour') {
|
|
84
|
+
required.push('minute');
|
|
85
|
+
}
|
|
86
|
+
if (granularity === 'second') {
|
|
87
|
+
required.push('second');
|
|
88
|
+
}
|
|
89
|
+
if (hourCycle === 12) {
|
|
90
|
+
required.push('dayPeriod');
|
|
91
|
+
}
|
|
92
|
+
return required;
|
|
93
|
+
}
|
|
94
|
+
export function normalizeSegmentNumberInput(value, maxDigits = 2) {
|
|
95
|
+
const digits = value.replace(/\D+/g, '').slice(0, maxDigits);
|
|
96
|
+
if (!digits)
|
|
97
|
+
return '';
|
|
98
|
+
const numeric = Number(digits);
|
|
99
|
+
if (!Number.isFinite(numeric))
|
|
100
|
+
return '';
|
|
101
|
+
return String(numeric);
|
|
102
|
+
}
|
|
103
|
+
export function isSegmentValueEmpty(value) {
|
|
104
|
+
return !value || value.trim().length === 0;
|
|
105
|
+
}
|
|
106
|
+
export function buildTimePartsFromDraft(draft, granularity, hourCycle) {
|
|
107
|
+
const hourRaw = draft.hour.trim();
|
|
108
|
+
const minuteRaw = draft.minute.trim();
|
|
109
|
+
const secondRaw = draft.second.trim();
|
|
110
|
+
const dayPeriodRaw = draft.dayPeriod.trim().toUpperCase();
|
|
111
|
+
if (!hourRaw)
|
|
112
|
+
return null;
|
|
113
|
+
if (granularity !== 'hour' && !minuteRaw)
|
|
114
|
+
return null;
|
|
115
|
+
if (granularity === 'second' && !secondRaw)
|
|
116
|
+
return null;
|
|
117
|
+
if (hourCycle === 12 && !dayPeriodRaw)
|
|
118
|
+
return null;
|
|
119
|
+
const hourNumeric = Number(hourRaw);
|
|
120
|
+
if (!Number.isInteger(hourNumeric))
|
|
121
|
+
return null;
|
|
122
|
+
let hour24 = hourNumeric;
|
|
123
|
+
if (hourCycle === 12) {
|
|
124
|
+
if (hourNumeric < 1 || hourNumeric > 12)
|
|
125
|
+
return null;
|
|
126
|
+
if (dayPeriodRaw !== 'AM' && dayPeriodRaw !== 'PM')
|
|
127
|
+
return null;
|
|
128
|
+
hour24 = hourNumeric % 12;
|
|
129
|
+
if (dayPeriodRaw === 'PM') {
|
|
130
|
+
hour24 += 12;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else if (hourNumeric < 0 || hourNumeric > 23) {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
const minute = granularity === 'hour' ? 0 : Number(minuteRaw);
|
|
137
|
+
if (!Number.isInteger(minute) || minute < 0 || minute > 59)
|
|
138
|
+
return null;
|
|
139
|
+
const second = granularity === 'second' ? Number(secondRaw) : 0;
|
|
140
|
+
if (!Number.isInteger(second) || second < 0 || second > 59)
|
|
141
|
+
return null;
|
|
142
|
+
return {
|
|
143
|
+
hour: hour24,
|
|
144
|
+
minute,
|
|
145
|
+
second
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export function compareTimeParts(left, right) {
|
|
149
|
+
const leftTotal = left.hour * 3600 + left.minute * 60 + left.second;
|
|
150
|
+
const rightTotal = right.hour * 3600 + right.minute * 60 + right.second;
|
|
151
|
+
if (leftTotal < rightTotal)
|
|
152
|
+
return -1;
|
|
153
|
+
if (leftTotal > rightTotal)
|
|
154
|
+
return 1;
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
function truncateToGranularity(parts, granularity) {
|
|
158
|
+
if (granularity === 'hour')
|
|
159
|
+
return { hour: parts.hour, minute: 0, second: 0 };
|
|
160
|
+
if (granularity === 'minute')
|
|
161
|
+
return { hour: parts.hour, minute: parts.minute, second: 0 };
|
|
162
|
+
return parts;
|
|
163
|
+
}
|
|
164
|
+
export function isTimeOutOfRange(value, minValue, maxValue, granularity = 'second') {
|
|
165
|
+
const currentRaw = parseTimePickerValue(value);
|
|
166
|
+
if (!currentRaw)
|
|
167
|
+
return true;
|
|
168
|
+
const current = truncateToGranularity(currentRaw, granularity);
|
|
169
|
+
if (minValue) {
|
|
170
|
+
const minRaw = parseTimePickerValue(minValue);
|
|
171
|
+
if (minRaw) {
|
|
172
|
+
const min = truncateToGranularity(minRaw, granularity);
|
|
173
|
+
if (compareTimeParts(current, min) < 0)
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (maxValue) {
|
|
178
|
+
const maxRaw = parseTimePickerValue(maxValue);
|
|
179
|
+
if (maxRaw) {
|
|
180
|
+
const max = truncateToGranularity(maxRaw, granularity);
|
|
181
|
+
if (compareTimeParts(current, max) > 0)
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
function clamp(value, min, max) {
|
|
188
|
+
return Math.min(max, Math.max(min, value));
|
|
189
|
+
}
|
|
190
|
+
export function clampToStep(value, step, min, max) {
|
|
191
|
+
if (!Number.isFinite(value))
|
|
192
|
+
return min;
|
|
193
|
+
const safeStep = Number.isFinite(step) && step > 0 ? step : 1;
|
|
194
|
+
const clamped = clamp(value, min, max);
|
|
195
|
+
const stepped = Math.round(clamped / safeStep) * safeStep;
|
|
196
|
+
return clamp(stepped, min, max);
|
|
197
|
+
}
|
|
198
|
+
function wrapCircular(value, min, max) {
|
|
199
|
+
const range = max - min + 1;
|
|
200
|
+
if (range <= 0)
|
|
201
|
+
return min;
|
|
202
|
+
let normalized = (value - min) % range;
|
|
203
|
+
if (normalized < 0)
|
|
204
|
+
normalized += range;
|
|
205
|
+
return normalized + min;
|
|
206
|
+
}
|
|
207
|
+
export function adjustSegmentWithStep(currentValue, type, delta, options) {
|
|
208
|
+
let min = 0;
|
|
209
|
+
let max = 59;
|
|
210
|
+
let step = 1;
|
|
211
|
+
if (type === 'hour') {
|
|
212
|
+
if (options.hourCycle === 12) {
|
|
213
|
+
min = 1;
|
|
214
|
+
max = 12;
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
min = 0;
|
|
218
|
+
max = 23;
|
|
219
|
+
}
|
|
220
|
+
step = Math.max(1, options.hourStep);
|
|
221
|
+
}
|
|
222
|
+
if (type === 'minute') {
|
|
223
|
+
min = 0;
|
|
224
|
+
max = 59;
|
|
225
|
+
step = Math.max(1, options.minuteStep);
|
|
226
|
+
}
|
|
227
|
+
if (type === 'second') {
|
|
228
|
+
min = 0;
|
|
229
|
+
max = 59;
|
|
230
|
+
step = Math.max(1, options.secondStep);
|
|
231
|
+
}
|
|
232
|
+
const current = Number(currentValue || String(min));
|
|
233
|
+
const base = Number.isFinite(current) ? current : min;
|
|
234
|
+
const next = wrapCircular(base + step * delta, min, max);
|
|
235
|
+
return String(next);
|
|
236
|
+
}
|
|
237
|
+
export function buildTimePickerSegments(params) {
|
|
238
|
+
const { locale, hourCycle, granularity, draft, formatter } = params;
|
|
239
|
+
const formatOptions = {
|
|
240
|
+
hour: 'numeric',
|
|
241
|
+
minute: granularity !== 'hour' ? '2-digit' : undefined,
|
|
242
|
+
second: granularity === 'second' ? '2-digit' : undefined,
|
|
243
|
+
hourCycle: hourCycle === 12 ? 'h12' : 'h23',
|
|
244
|
+
timeZone: 'UTC'
|
|
245
|
+
};
|
|
246
|
+
const sampleDate = new Date(Date.UTC(2024, 0, 1, 14, 30, 45));
|
|
247
|
+
const resolvedFormatter = formatter ?? new Intl.DateTimeFormat(locale, formatOptions);
|
|
248
|
+
const parts = resolvedFormatter.formatToParts(sampleDate);
|
|
249
|
+
return parts
|
|
250
|
+
.map((part) => {
|
|
251
|
+
if (part.type === 'literal') {
|
|
252
|
+
return {
|
|
253
|
+
type: 'literal',
|
|
254
|
+
text: part.value,
|
|
255
|
+
isPlaceholder: false
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
if (part.type === 'hour') {
|
|
259
|
+
const isPlaceholder = isSegmentValueEmpty(draft.hour);
|
|
260
|
+
const hourText = hourCycle === 24 && draft.hour.trim() === '0' ? '00' : draft.hour;
|
|
261
|
+
return {
|
|
262
|
+
type: 'hour',
|
|
263
|
+
text: isPlaceholder ? (hourCycle === 12 ? 'hh' : 'HH') : hourText,
|
|
264
|
+
isPlaceholder
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (part.type === 'minute' && granularity !== 'hour') {
|
|
268
|
+
const isPlaceholder = isSegmentValueEmpty(draft.minute);
|
|
269
|
+
return {
|
|
270
|
+
type: 'minute',
|
|
271
|
+
text: isPlaceholder ? 'mm' : formatDraftTwoDigits(draft.minute),
|
|
272
|
+
isPlaceholder
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (part.type === 'second' && granularity === 'second') {
|
|
276
|
+
const isPlaceholder = isSegmentValueEmpty(draft.second);
|
|
277
|
+
return {
|
|
278
|
+
type: 'second',
|
|
279
|
+
text: isPlaceholder ? 'ss' : formatDraftTwoDigits(draft.second),
|
|
280
|
+
isPlaceholder
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
if (part.type === 'dayPeriod' && hourCycle === 12) {
|
|
284
|
+
const isPlaceholder = isSegmentValueEmpty(draft.dayPeriod);
|
|
285
|
+
return {
|
|
286
|
+
type: 'dayPeriod',
|
|
287
|
+
text: isPlaceholder ? getDayPeriodPlaceholder(locale) : draft.dayPeriod,
|
|
288
|
+
isPlaceholder
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
})
|
|
293
|
+
.filter((part) => part !== null);
|
|
294
|
+
}
|
|
295
|
+
export function getEditableSegmentOrder(segments) {
|
|
296
|
+
return segments
|
|
297
|
+
.filter((segment) => segment.type !== 'literal')
|
|
298
|
+
.map((segment) => segment.type);
|
|
299
|
+
}
|
|
300
|
+
export function getSegmentLabel(type, locale) {
|
|
301
|
+
try {
|
|
302
|
+
const displayNames = new Intl.DisplayNames([locale], { type: 'dateTimeField' });
|
|
303
|
+
return displayNames.of(type) ?? type;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
if (type === 'hour')
|
|
307
|
+
return 'Hour';
|
|
308
|
+
if (type === 'minute')
|
|
309
|
+
return 'Minute';
|
|
310
|
+
if (type === 'second')
|
|
311
|
+
return 'Second';
|
|
312
|
+
return 'Day period';
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TimePickerDraft, TimePickerEditableSegmentType, TimePickerGranularity, TimePickerHourCycle, TimePickerTimeValue } from './time-utils';
|
|
2
|
+
export type WheelOption = {
|
|
3
|
+
value: string;
|
|
4
|
+
label: string;
|
|
5
|
+
disabled: boolean;
|
|
6
|
+
};
|
|
7
|
+
export declare function buildWheelOptions(params: {
|
|
8
|
+
type: TimePickerEditableSegmentType;
|
|
9
|
+
granularity: TimePickerGranularity;
|
|
10
|
+
hourCycle: TimePickerHourCycle;
|
|
11
|
+
hourStep: number;
|
|
12
|
+
minuteStep: number;
|
|
13
|
+
secondStep: number;
|
|
14
|
+
hasRangeBounds: boolean;
|
|
15
|
+
getCandidateFromPartial: (partial: Partial<TimePickerDraft>) => TimePickerTimeValue | null;
|
|
16
|
+
isOutOfRange: (value: TimePickerTimeValue) => boolean;
|
|
17
|
+
}): WheelOption[];
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function buildWheelOptions(params) {
|
|
2
|
+
const { type, hourCycle, hourStep, minuteStep, secondStep, hasRangeBounds, getCandidateFromPartial, isOutOfRange } = params;
|
|
3
|
+
const options = [];
|
|
4
|
+
if (type === 'dayPeriod') {
|
|
5
|
+
for (const option of ['AM', 'PM']) {
|
|
6
|
+
const disabled = hasRangeBounds
|
|
7
|
+
? (() => {
|
|
8
|
+
const candidate = getCandidateFromPartial({ dayPeriod: option });
|
|
9
|
+
return candidate ? isOutOfRange(candidate) : false;
|
|
10
|
+
})()
|
|
11
|
+
: false;
|
|
12
|
+
options.push({
|
|
13
|
+
value: option,
|
|
14
|
+
label: option,
|
|
15
|
+
disabled
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return options;
|
|
19
|
+
}
|
|
20
|
+
let min = 0;
|
|
21
|
+
let max = 59;
|
|
22
|
+
let step = 1;
|
|
23
|
+
if (type === 'hour') {
|
|
24
|
+
if (hourCycle === 12) {
|
|
25
|
+
min = 1;
|
|
26
|
+
max = 12;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
min = 0;
|
|
30
|
+
max = 23;
|
|
31
|
+
}
|
|
32
|
+
step = Math.max(1, hourStep);
|
|
33
|
+
}
|
|
34
|
+
else if (type === 'minute') {
|
|
35
|
+
min = 0;
|
|
36
|
+
max = 59;
|
|
37
|
+
step = Math.max(1, minuteStep);
|
|
38
|
+
}
|
|
39
|
+
else if (type === 'second') {
|
|
40
|
+
min = 0;
|
|
41
|
+
max = 59;
|
|
42
|
+
step = Math.max(1, secondStep);
|
|
43
|
+
}
|
|
44
|
+
for (let current = min; current <= max; current += step) {
|
|
45
|
+
const valueString = String(current);
|
|
46
|
+
const disabled = hasRangeBounds
|
|
47
|
+
? (() => {
|
|
48
|
+
const candidate = getCandidateFromPartial(type === 'hour'
|
|
49
|
+
? { hour: valueString }
|
|
50
|
+
: type === 'minute'
|
|
51
|
+
? { minute: valueString }
|
|
52
|
+
: { second: valueString });
|
|
53
|
+
return candidate ? isOutOfRange(candidate) : false;
|
|
54
|
+
})()
|
|
55
|
+
: false;
|
|
56
|
+
options.push({
|
|
57
|
+
value: valueString,
|
|
58
|
+
label: String(current).padStart(2, '0'),
|
|
59
|
+
disabled
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return options;
|
|
63
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Clock WheelColumn
|
|
2
|
+
|
|
3
|
+
## API reference
|
|
4
|
+
|
|
5
|
+
### Clock.WheelColumn
|
|
6
|
+
|
|
7
|
+
Name: `Clock.WheelColumn`
|
|
8
|
+
Description: Scrollable spinbutton column for a single editable segment (`hour`, `minute`, `second`, `dayPeriod`).
|
|
9
|
+
|
|
10
|
+
| Prop | Type | Default | Description |
|
|
11
|
+
| -------------- | ---------------------------------------------------------------- | --------------- | ----------------------------------------------------------- |
|
|
12
|
+
| `type` | `'hour' \| 'minute' \| 'second' \| 'dayPeriod'` | `required` | Segment represented by this wheel column. |
|
|
13
|
+
| `children` | `Snippet<[{ value: string; label: string; disabled: boolean }]>` | `undefined` | Optional custom item renderer for each wheel option. |
|
|
14
|
+
| `class` | `string` | `'h-55'` | CSS class names for the spinbutton column. |
|
|
15
|
+
| `aria-label` | `string` | `segment label` | Overrides the computed accessible column label. |
|
|
16
|
+
| `...restProps` | `HTMLAttributes<HTMLDivElement>` | `-` | Additional attributes forwarded to the column root element. |
|
|
17
|
+
|
|
18
|
+
### Context utilities
|
|
19
|
+
|
|
20
|
+
Name: `useClockContext`
|
|
21
|
+
Description: Reads shared state and operations from `Clock.Root`.
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
| ----------------- | -------------------- | ------- | ------------------------------------------------ |
|
|
25
|
+
| `useClockContext` | `() => ClockContext` | `-` | Returns context and throws outside `Clock.Root`. |
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Clock from '../index.parts';
|
|
3
|
+
|
|
4
|
+
let value = $state<string | null>('14:30');
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<Clock.Root bind:value hourCycle={24} class="flex gap-2">
|
|
8
|
+
{#snippet column(col)}
|
|
9
|
+
<Clock.WheelColumn type={col.type} class="h-44 w-16" />
|
|
10
|
+
{/snippet}
|
|
11
|
+
</Clock.Root>
|
|
12
|
+
|
|
13
|
+
<p data-testid="bind-value">{value}</p>
|
|
14
|
+
<button type="button" data-testid="set-value-16-45" onclick={() => (value = '16:45')}
|
|
15
|
+
>Set value</button
|
|
16
|
+
>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Clock from '../index.parts';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
let { defaultValue = '14:30' }: Props = $props();
|
|
9
|
+
|
|
10
|
+
let selectedValue = $state<string | null>('');
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Clock.Root
|
|
14
|
+
{defaultValue}
|
|
15
|
+
onChange={(nextValue) => {
|
|
16
|
+
selectedValue = nextValue;
|
|
17
|
+
}}
|
|
18
|
+
class="flex gap-2"
|
|
19
|
+
>
|
|
20
|
+
{#snippet column(col)}
|
|
21
|
+
<Clock.WheelColumn type={col.type} class="h-44 w-16">
|
|
22
|
+
{#snippet children(option)}
|
|
23
|
+
<Clock.WheelItem type={col.type} {option} class="custom-wheel-item" />
|
|
24
|
+
{/snippet}
|
|
25
|
+
</Clock.WheelColumn>
|
|
26
|
+
{/snippet}
|
|
27
|
+
</Clock.Root>
|
|
28
|
+
|
|
29
|
+
<p data-testid="clock-value">{selectedValue}</p>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
defaultValue?: string;
|
|
3
|
+
};
|
|
4
|
+
declare const ClockWheelColumnCustomSnippetTest: import("svelte").Component<Props, {}, "">;
|
|
5
|
+
type ClockWheelColumnCustomSnippetTest = ReturnType<typeof ClockWheelColumnCustomSnippetTest>;
|
|
6
|
+
export default ClockWheelColumnCustomSnippetTest;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Clock from '../index.parts';
|
|
3
|
+
|
|
4
|
+
let value = $state<string | null>('14:30');
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<Clock.Root bind:value hourCycle={24} class="flex gap-2">
|
|
8
|
+
{#snippet column(col)}
|
|
9
|
+
<Clock.WheelColumn type={col.type} class="w-16" />
|
|
10
|
+
{/snippet}
|
|
11
|
+
</Clock.Root>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import * as Clock from '../index.parts';
|
|
4
|
+
import type { ClockColumnInfo } from '../root/resolve-visible-columns';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
defaultValue?: string;
|
|
8
|
+
defaultOpen?: boolean;
|
|
9
|
+
hourCycle?: 12 | 24;
|
|
10
|
+
granularity?: 'hour' | 'minute' | 'second';
|
|
11
|
+
minValue?: string;
|
|
12
|
+
maxValue?: string;
|
|
13
|
+
useSnippet?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
defaultValue = '14:30',
|
|
18
|
+
hourCycle = 24,
|
|
19
|
+
granularity = 'minute',
|
|
20
|
+
minValue,
|
|
21
|
+
maxValue,
|
|
22
|
+
useSnippet = false
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
let value = $state<string | null>(untrack(() => defaultValue));
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
{#if useSnippet}
|
|
29
|
+
<Clock.Root bind:value {hourCycle} {granularity} {minValue} {maxValue} class="flex gap-2">
|
|
30
|
+
{#snippet column(col: ClockColumnInfo)}
|
|
31
|
+
<div data-testid="panel-column" data-type={col.type}>{col.label}</div>
|
|
32
|
+
{/snippet}
|
|
33
|
+
</Clock.Root>
|
|
34
|
+
{:else}
|
|
35
|
+
<Clock.Root bind:value {hourCycle} {granularity} {minValue} {maxValue} class="flex gap-2" />
|
|
36
|
+
{/if}
|
|
37
|
+
|
|
38
|
+
<p data-testid="clock-value">{value}</p>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
defaultValue?: string;
|
|
3
|
+
defaultOpen?: boolean;
|
|
4
|
+
hourCycle?: 12 | 24;
|
|
5
|
+
granularity?: 'hour' | 'minute' | 'second';
|
|
6
|
+
minValue?: string;
|
|
7
|
+
maxValue?: string;
|
|
8
|
+
useSnippet?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const ClockWheelColumnTest: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type ClockWheelColumnTest = ReturnType<typeof ClockWheelColumnTest>;
|
|
12
|
+
export default ClockWheelColumnTest;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import * as Clock from '../index.parts';
|
|
4
|
+
import type { ClockColumnInfo } from '../root/resolve-visible-columns';
|
|
5
|
+
|
|
6
|
+
type Props = {
|
|
7
|
+
defaultValue?: string;
|
|
8
|
+
defaultOpen?: boolean;
|
|
9
|
+
hourCycle?: 12 | 24;
|
|
10
|
+
granularity?: 'hour' | 'minute' | 'second';
|
|
11
|
+
minValue?: string;
|
|
12
|
+
maxValue?: string;
|
|
13
|
+
useSnippet?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
defaultValue = '14:30',
|
|
18
|
+
hourCycle = 24,
|
|
19
|
+
granularity = 'minute',
|
|
20
|
+
minValue,
|
|
21
|
+
maxValue,
|
|
22
|
+
useSnippet = false
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
let value = $state<string | null>(untrack(() => defaultValue));
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
{#if useSnippet}
|
|
29
|
+
<Clock.Root bind:value {hourCycle} {granularity} {minValue} {maxValue} class="flex gap-2">
|
|
30
|
+
{#snippet column(col: ClockColumnInfo)}
|
|
31
|
+
<div data-testid="panel-column" data-type={col.type}>{col.label}</div>
|
|
32
|
+
{/snippet}
|
|
33
|
+
</Clock.Root>
|
|
34
|
+
{:else}
|
|
35
|
+
<Clock.Root bind:value {hourCycle} {granularity} {minValue} {maxValue} class="flex gap-2" />
|
|
36
|
+
{/if}
|
|
37
|
+
|
|
38
|
+
<p data-testid="clock-value">{value}</p>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
defaultValue?: string;
|
|
3
|
+
defaultOpen?: boolean;
|
|
4
|
+
hourCycle?: 12 | 24;
|
|
5
|
+
granularity?: 'hour' | 'minute' | 'second';
|
|
6
|
+
minValue?: string;
|
|
7
|
+
maxValue?: string;
|
|
8
|
+
useSnippet?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const ClockWheelColumnTpTest: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type ClockWheelColumnTpTest = ReturnType<typeof ClockWheelColumnTpTest>;
|
|
12
|
+
export default ClockWheelColumnTpTest;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import * as Clock from '../index.parts';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
defaultValue?: string;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
let { defaultValue = '14:30' }: Props = $props();
|
|
9
|
+
|
|
10
|
+
let selectedValue = $state<string | null>('');
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<Clock.Root
|
|
14
|
+
{defaultValue}
|
|
15
|
+
onChange={(nextValue) => {
|
|
16
|
+
selectedValue = nextValue;
|
|
17
|
+
}}
|
|
18
|
+
class="flex gap-2"
|
|
19
|
+
>
|
|
20
|
+
{#snippet column(col)}
|
|
21
|
+
<Clock.WheelColumn type={col.type} class="h-44 w-16">
|
|
22
|
+
{#snippet children(option)}
|
|
23
|
+
<div data-testid="untagged-wheel-option">{option.label}</div>
|
|
24
|
+
{/snippet}
|
|
25
|
+
</Clock.WheelColumn>
|
|
26
|
+
{/snippet}
|
|
27
|
+
</Clock.Root>
|
|
28
|
+
|
|
29
|
+
<p data-testid="clock-value">{selectedValue}</p>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type Props = {
|
|
2
|
+
defaultValue?: string;
|
|
3
|
+
};
|
|
4
|
+
declare const ClockWheelColumnUntaggedSnippetTest: import("svelte").Component<Props, {}, "">;
|
|
5
|
+
type ClockWheelColumnUntaggedSnippetTest = ReturnType<typeof ClockWheelColumnUntaggedSnippetTest>;
|
|
6
|
+
export default ClockWheelColumnUntaggedSnippetTest;
|