compote-ui 0.50.0 → 0.51.0

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.
@@ -21,12 +21,21 @@
21
21
  invalid,
22
22
  timeZone,
23
23
  granularity,
24
+ hourCycle,
24
25
  onValueChange
25
26
  }: DateFieldProps = $props();
26
27
 
27
28
  const locale = useLocaleContext();
28
29
  const id = $props.id();
29
30
  const showTimeInput = $derived(!!granularity && granularity !== 'day');
31
+ const resolvedHourCycle = $derived(
32
+ hourCycle ??
33
+ (new Intl.DateTimeFormat(locale().locale, { hour: 'numeric' })
34
+ .formatToParts(new Date(2024, 0, 1, 14))
35
+ .some((p) => p.type === 'dayPeriod')
36
+ ? 12
37
+ : 24)
38
+ );
30
39
 
31
40
  const datePicker = useDatePicker(() => ({
32
41
  id,
@@ -60,6 +69,7 @@
60
69
  id,
61
70
  locale: locale().locale,
62
71
  granularity: granularity ?? 'day',
72
+ hourCycle,
63
73
  value: datePicker().value,
64
74
  min,
65
75
  max,
@@ -102,7 +112,7 @@
102
112
  <PhCalendarBlank class="size-4" />
103
113
  </DatePicker.Trigger>
104
114
  </DatePicker.Control>
105
- <DatePickerCalendar {showTimeInput} />
115
+ <DatePickerCalendar {showTimeInput} hourCycle={resolvedHourCycle} />
106
116
  </DatePicker.RootProvider>
107
117
  </DateInput.Control>
108
118
  <DateInput.HiddenInput {name} />
@@ -6,4 +6,5 @@ export interface DateFieldProps extends Omit<DatePickerRootBaseProps, 'value' |
6
6
  label?: string;
7
7
  name?: string;
8
8
  granularity?: 'day' | 'hour' | 'minute' | 'second';
9
+ hourCycle?: 12 | 24;
9
10
  }
@@ -4,13 +4,25 @@
4
4
  import { CalendarDateTime } from '@internationalized/date';
5
5
  import { PhArrowLeft, PhArrowRight } from '../../icons';
6
6
  import type { DateValue } from '@ark-ui/svelte/date-picker';
7
+ import Select from '../select/select.svelte';
7
8
 
8
- let { showTimeInput = false }: { showTimeInput?: boolean } = $props();
9
+ let { showTimeInput = false, hourCycle = 24 }: { showTimeInput?: boolean; hourCycle?: 12 | 24 } =
10
+ $props();
9
11
 
10
- function getTimeValue(v: DateValue | undefined): string {
11
- if (!v || !('hour' in v)) return '';
12
- const dt = v as CalendarDateTime;
13
- return `${String(dt.hour).padStart(2, '0')}:${String(dt.minute).padStart(2, '0')}`;
12
+ function getHour(v: DateValue | undefined): number {
13
+ if (!v || !('hour' in v)) return 0;
14
+ const h = (v as CalendarDateTime).hour;
15
+ return hourCycle === 12 ? h % 12 || 12 : h;
16
+ }
17
+
18
+ function getMinute(v: DateValue | undefined): number {
19
+ if (!v || !('hour' in v)) return 0;
20
+ return (v as CalendarDateTime).minute;
21
+ }
22
+
23
+ function isPM(v: DateValue | undefined): boolean {
24
+ if (!v || !('hour' in v)) return false;
25
+ return (v as CalendarDateTime).hour >= 12;
14
26
  }
15
27
 
16
28
  function setTime(v: DateValue | undefined, hours: number, minutes: number): CalendarDateTime {
@@ -18,6 +30,32 @@
18
30
  const d = v ?? new CalendarDateTime(new Date().getFullYear(), 1, 1, 0, 0);
19
31
  return new CalendarDateTime(d.year, d.month, d.day, hours, minutes);
20
32
  }
33
+
34
+ function updateHour(v: DateValue | undefined, displayHour: number): CalendarDateTime {
35
+ let h = displayHour;
36
+ if (hourCycle === 12) {
37
+ const pm = isPM(v);
38
+ h = pm ? (displayHour % 12) + 12 : displayHour % 12;
39
+ }
40
+ return setTime(v, h, getMinute(v));
41
+ }
42
+
43
+ function togglePeriod(v: DateValue | undefined): CalendarDateTime {
44
+ const cur = v && 'hour' in v ? (v as CalendarDateTime).hour : 0;
45
+ return setTime(v, cur >= 12 ? cur - 12 : cur + 12, getMinute(v));
46
+ }
47
+
48
+ const minuteItems = Array.from({ length: 60 }, (_, m) => ({
49
+ value: m,
50
+ label: String(m).padStart(2, '0')
51
+ }));
52
+
53
+ const hourItems = $derived(
54
+ Array.from({ length: hourCycle === 12 ? 12 : 24 }, (_, i) => {
55
+ const h = hourCycle === 12 ? i + 1 : i;
56
+ return { value: h, label: String(h).padStart(2, '0') };
57
+ })
58
+ );
21
59
  </script>
22
60
 
23
61
  <Portal>
@@ -74,15 +112,43 @@
74
112
  </DatePicker.TableBody>
75
113
  </DatePicker.Table>
76
114
  {#if showTimeInput}
77
- <input
78
- type="time"
79
- value={getTimeValue(datePicker().value[0])}
80
- oninput={(e) => {
81
- const [h, m] = e.currentTarget.value.split(':').map(Number);
82
- datePicker().setValue([setTime(datePicker().value[0], h, m)]);
83
- }}
84
- class="mt-3 h-9 w-full rounded-md border border-border bg-surface-1 px-3 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
85
- />
115
+ {@const cur = datePicker().value[0]}
116
+ <div class="mt-3 flex items-center justify-center gap-1">
117
+ <Select
118
+ items={hourItems}
119
+ value={getHour(cur)}
120
+ size="sm"
121
+ placeholder="HH"
122
+ onValueChange={(d) =>
123
+ datePicker().setValue([updateHour(cur, Number(d.items[0]?.value))])}
124
+ />
125
+ <span class="text-ink-dim">:</span>
126
+ <Select
127
+ items={minuteItems}
128
+ value={getMinute(cur)}
129
+ size="sm"
130
+ placeholder="mm"
131
+ onValueChange={(d) => {
132
+ const m = Number(d.items[0]?.value);
133
+ const h24 =
134
+ hourCycle === 12
135
+ ? isPM(cur)
136
+ ? (getHour(cur) % 12) + 12
137
+ : getHour(cur) % 12
138
+ : getHour(cur);
139
+ datePicker().setValue([setTime(cur, h24, m)]);
140
+ }}
141
+ />
142
+ {#if hourCycle === 12}
143
+ <button
144
+ type="button"
145
+ onclick={() => datePicker().setValue([togglePeriod(cur)])}
146
+ class="h-8 rounded-md border border-border bg-surface-1 px-2 text-sm shadow-sm hover:bg-surface-2 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
147
+ >
148
+ {isPM(cur) ? 'PM' : 'AM'}
149
+ </button>
150
+ {/if}
151
+ </div>
86
152
  {/if}
87
153
  {/snippet}
88
154
  </DatePicker.Context>
@@ -1,5 +1,6 @@
1
1
  type $$ComponentProps = {
2
2
  showTimeInput?: boolean;
3
+ hourCycle?: 12 | 24;
3
4
  };
4
5
  declare const DatePickerCalendar: import("svelte").Component<$$ComponentProps, {}, "">;
5
6
  type DatePickerCalendar = ReturnType<typeof DatePickerCalendar>;
@@ -14,12 +14,21 @@
14
14
  placeholder,
15
15
  name,
16
16
  granularity,
17
+ hourCycle,
17
18
  onValueChange,
18
19
  ...restProps
19
20
  }: DatePickerProps = $props();
20
21
 
21
22
  const locale = useLocaleContext();
22
23
  const showTimeInput = $derived(!!granularity && granularity !== 'day');
24
+ const resolvedHourCycle = $derived(
25
+ hourCycle ??
26
+ (new Intl.DateTimeFormat(locale().locale, { hour: 'numeric' })
27
+ .formatToParts(new Date(2024, 0, 1, 14))
28
+ .some((p) => p.type === 'dayPeriod')
29
+ ? 12
30
+ : 24)
31
+ );
23
32
  </script>
24
33
 
25
34
  <DatePicker.Root
@@ -30,7 +39,7 @@
30
39
  defaultValue={defaultValue ? [defaultValue] : undefined}
31
40
  onValueChange={(details) => {
32
41
  const picked = details.value[0] ?? null;
33
- if (picked && showTimeInput) {
42
+ if (picked && showTimeInput && !('hour' in picked)) {
34
43
  const h = value && 'hour' in value ? (value as CalendarDateTime).hour : 0;
35
44
  const m = value && 'hour' in value ? (value as CalendarDateTime).minute : 0;
36
45
  value = new CalendarDateTime(picked.year, picked.month, picked.day, h, m);
@@ -62,7 +71,7 @@
62
71
  <PhX class="size-3.5" />
63
72
  </DatePicker.ClearTrigger>
64
73
  </DatePicker.Control>
65
- <DatePickerCalendar {showTimeInput} />
74
+ <DatePickerCalendar {showTimeInput} hourCycle={resolvedHourCycle} />
66
75
  {#if name}
67
76
  <input type="hidden" {name} value={value ?? ''} />
68
77
  {/if}
@@ -7,4 +7,5 @@ export interface DatePickerProps extends Omit<DatePickerRootBaseProps, 'value' |
7
7
  placeholder?: string;
8
8
  name?: string;
9
9
  granularity?: 'day' | 'hour' | 'minute' | 'second';
10
+ hourCycle?: 12 | 24;
10
11
  }
@@ -19,6 +19,7 @@
19
19
  required,
20
20
  invalid,
21
21
  timeZone,
22
+ hourCycle,
22
23
  onValueChange
23
24
  }: DateRangeFieldProps = $props();
24
25
 
@@ -48,6 +49,7 @@
48
49
  id,
49
50
  locale: locale().locale,
50
51
  selectionMode: 'range',
52
+ hourCycle,
51
53
  value: datePicker().value,
52
54
  min,
53
55
  max,
@@ -5,4 +5,6 @@ export interface DateRangeFieldProps extends Omit<DatePickerRootBaseProps, 'valu
5
5
  defaultValue?: DateValue[];
6
6
  label?: string;
7
7
  name?: string;
8
+ granularity?: 'day' | 'hour' | 'minute' | 'second';
9
+ hourCycle?: 12 | 24;
8
10
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compote-ui",
3
- "version": "0.50.0",
3
+ "version": "0.51.0",
4
4
  "license": "MIT",
5
5
  "scripts": {
6
6
  "dev": "vite dev --open",