@dryui/ui 0.1.5 → 0.1.7
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/backdrop/backdrop.svelte +1 -1
- package/dist/carousel/carousel-viewport.svelte +1 -1
- package/dist/date-field/date-field-root.svelte +0 -1
- package/dist/date-field/date-field-segment.svelte +1 -1
- package/dist/date-picker/datepicker-calendar.svelte +0 -1
- package/dist/date-range-picker/date-range-picker-calendar.svelte +44 -26
- package/dist/date-time-input/date-time-input.svelte +12 -33
- package/dist/notification-center/notification-center-group.svelte +2 -2
- package/dist/notification-center/notification-center-item.svelte +2 -1
- package/dist/notification-center/notification-center-panel.svelte +21 -2
- package/dist/notification-center/notification-center-root.svelte +1 -1
- package/dist/notification-center/notification-center-trigger.svelte +3 -5
- package/dist/select/select-root.svelte +0 -1
- package/dist/select/select-trigger.svelte +2 -0
- package/dist/time-input/index.d.ts +8 -1
- package/dist/time-input/time-input.svelte +118 -74
- package/package.json +1181 -440
- package/skills/dryui/SKILL.md +111 -221
- package/skills/dryui/rules/composition.md +280 -291
|
@@ -142,7 +142,7 @@
|
|
|
142
142
|
color var(--dry-duration-fast) var(--dry-ease-default);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
[data-df-segment]:focus
|
|
145
|
+
[data-df-segment]:focus {
|
|
146
146
|
outline: 2px solid var(--dry-color-stroke-focus);
|
|
147
147
|
outline-offset: -1px;
|
|
148
148
|
background: var(--dry-color-fill-brand-weak);
|
|
@@ -176,7 +176,13 @@
|
|
|
176
176
|
{@const inRange = isInSelectedRange(day) || isInPreviewRange(day)}
|
|
177
177
|
{@const rangeStart = isRangeStart(day)}
|
|
178
178
|
{@const rangeEnd = isRangeEnd(day)}
|
|
179
|
-
<div
|
|
179
|
+
<div
|
|
180
|
+
role="gridcell"
|
|
181
|
+
data-calendar-cell
|
|
182
|
+
data-in-range={inRange ? '' : undefined}
|
|
183
|
+
data-range-start={rangeStart ? '' : undefined}
|
|
184
|
+
data-range-end={rangeEnd ? '' : undefined}
|
|
185
|
+
>
|
|
180
186
|
<button
|
|
181
187
|
type="button"
|
|
182
188
|
tabindex={focused ? 0 : -1}
|
|
@@ -279,7 +285,6 @@
|
|
|
279
285
|
[data-drp-calendar] [data-calendar-row] {
|
|
280
286
|
display: grid;
|
|
281
287
|
grid-template-columns: repeat(7, minmax(0, 1fr));
|
|
282
|
-
gap: 1px;
|
|
283
288
|
}
|
|
284
289
|
|
|
285
290
|
[data-drp-calendar] [data-calendar-columnheader] {
|
|
@@ -297,6 +302,29 @@
|
|
|
297
302
|
place-items: center;
|
|
298
303
|
}
|
|
299
304
|
|
|
305
|
+
/* ── Cell-level range band (seamless, fills entire grid track) ── */
|
|
306
|
+
|
|
307
|
+
[data-drp-calendar] [data-calendar-cell][data-in-range],
|
|
308
|
+
[data-drp-calendar] [data-calendar-cell][data-range-start],
|
|
309
|
+
[data-drp-calendar] [data-calendar-cell][data-range-end] {
|
|
310
|
+
background: var(
|
|
311
|
+
--dry-range-calendar-range-bg,
|
|
312
|
+
color-mix(in srgb, var(--dry-color-fill-brand) 15%, transparent)
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
[data-drp-calendar] [data-calendar-cell][data-range-start] {
|
|
317
|
+
border-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md)) 0 0
|
|
318
|
+
var(--dry-range-calendar-day-radius, var(--dry-radius-md));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
[data-drp-calendar] [data-calendar-cell][data-range-end] {
|
|
322
|
+
border-radius: 0 var(--dry-range-calendar-day-radius, var(--dry-radius-md))
|
|
323
|
+
var(--dry-range-calendar-day-radius, var(--dry-radius-md)) 0;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/* ── Day button base ── */
|
|
327
|
+
|
|
300
328
|
[data-drp-calendar] [data-calendar-day-button] {
|
|
301
329
|
display: inline-grid;
|
|
302
330
|
place-items: center;
|
|
@@ -330,39 +358,29 @@
|
|
|
330
358
|
font-weight: 600;
|
|
331
359
|
}
|
|
332
360
|
|
|
333
|
-
[data-drp-calendar] [data-calendar-day-button][data-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
color: var(--dry-range-calendar-selected-color, var(--dry-color-on-brand));
|
|
337
|
-
font-weight: 600;
|
|
361
|
+
[data-drp-calendar] [data-calendar-day-button][data-outside-month] {
|
|
362
|
+
color: var(--dry-range-calendar-outside-color, var(--dry-color-text-weak));
|
|
363
|
+
opacity: 0.6;
|
|
338
364
|
}
|
|
339
365
|
|
|
340
|
-
[data-drp-calendar] [data-calendar-day-button][data-
|
|
341
|
-
|
|
342
|
-
|
|
366
|
+
[data-drp-calendar] [data-calendar-day-button][data-disabled] {
|
|
367
|
+
cursor: not-allowed;
|
|
368
|
+
opacity: 0.4;
|
|
369
|
+
pointer-events: none;
|
|
343
370
|
}
|
|
344
371
|
|
|
345
|
-
|
|
346
|
-
background: var(
|
|
347
|
-
--dry-range-calendar-range-bg,
|
|
348
|
-
color-mix(in srgb, var(--dry-color-fill-brand) 15%, transparent)
|
|
349
|
-
);
|
|
350
|
-
border-radius: 0;
|
|
351
|
-
}
|
|
372
|
+
/* ── Range endpoints (last to win specificity over in-range/outside) ── */
|
|
352
373
|
|
|
353
374
|
[data-drp-calendar] [data-calendar-day-button][data-range-start],
|
|
354
375
|
[data-drp-calendar] [data-calendar-day-button][data-range-end] {
|
|
376
|
+
background: var(--dry-range-calendar-selected-bg, var(--dry-color-fill-brand));
|
|
377
|
+
color: var(--dry-range-calendar-selected-color, var(--dry-color-on-brand));
|
|
378
|
+
font-weight: 600;
|
|
355
379
|
border-radius: var(--dry-range-calendar-day-radius, var(--dry-radius-md));
|
|
356
380
|
}
|
|
357
381
|
|
|
358
|
-
[data-drp-calendar] [data-calendar-day-button][data-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
[data-drp-calendar] [data-calendar-day-button][data-disabled] {
|
|
364
|
-
cursor: not-allowed;
|
|
365
|
-
opacity: 0.4;
|
|
366
|
-
pointer-events: none;
|
|
382
|
+
[data-drp-calendar] [data-calendar-day-button][data-range-start]:hover:not([data-disabled]),
|
|
383
|
+
[data-drp-calendar] [data-calendar-day-button][data-range-end]:hover:not([data-disabled]) {
|
|
384
|
+
background: var(--dry-range-calendar-selected-hover-bg, var(--dry-color-fill-brand-hover));
|
|
367
385
|
}
|
|
368
386
|
</style>
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
-
import {
|
|
3
|
+
import { DatePicker } from '../date-picker/index.js';
|
|
4
4
|
import { TimeInput } from '../time-input/index.js';
|
|
5
5
|
|
|
6
|
-
type DateSegmentType = 'month' | 'day' | 'year';
|
|
7
|
-
|
|
8
6
|
interface Props extends HTMLAttributes<HTMLDivElement> {
|
|
9
7
|
value?: Date;
|
|
10
8
|
min?: Date;
|
|
@@ -27,28 +25,12 @@
|
|
|
27
25
|
...rest
|
|
28
26
|
}: Props = $props();
|
|
29
27
|
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
year: 'numeric',
|
|
33
|
-
month: '2-digit',
|
|
34
|
-
day: '2-digit'
|
|
35
|
-
});
|
|
36
|
-
const parts = formatter.formatToParts(new Date(2000, 0, 1));
|
|
37
|
-
const order: DateSegmentType[] = [];
|
|
38
|
-
for (const part of parts) {
|
|
39
|
-
if (part.type === 'month') order.push('month');
|
|
40
|
-
else if (part.type === 'day') order.push('day');
|
|
41
|
-
else if (part.type === 'year') order.push('year');
|
|
42
|
-
}
|
|
43
|
-
return order.length === 3 ? order : ['month', 'day', 'year'];
|
|
28
|
+
function toTimeString(d: Date): string {
|
|
29
|
+
return `${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`;
|
|
44
30
|
}
|
|
45
31
|
|
|
46
|
-
const segmentOrder = $derived(getSegmentOrder(locale));
|
|
47
|
-
|
|
48
32
|
let dateValue = $state<Date | null>(value);
|
|
49
|
-
let timeValue = $state(
|
|
50
|
-
String(value.getHours()).padStart(2, '0') + ':' + String(value.getMinutes()).padStart(2, '0')
|
|
51
|
-
);
|
|
33
|
+
let timeValue = $state(toTimeString(value));
|
|
52
34
|
|
|
53
35
|
let updating = false;
|
|
54
36
|
|
|
@@ -57,8 +39,7 @@
|
|
|
57
39
|
const v = value;
|
|
58
40
|
if (v) {
|
|
59
41
|
dateValue = new Date(v.getFullYear(), v.getMonth(), v.getDate());
|
|
60
|
-
timeValue =
|
|
61
|
-
String(v.getHours()).padStart(2, '0') + ':' + String(v.getMinutes()).padStart(2, '0');
|
|
42
|
+
timeValue = toTimeString(v);
|
|
62
43
|
}
|
|
63
44
|
});
|
|
64
45
|
|
|
@@ -93,18 +74,16 @@
|
|
|
93
74
|
|
|
94
75
|
<div data-date-time-input class={className} {...rest}>
|
|
95
76
|
<div data-part="date-section">
|
|
96
|
-
<
|
|
97
|
-
{
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{/each}
|
|
103
|
-
</DateField.Root>
|
|
77
|
+
<DatePicker.Root bind:value={dateValue} {locale} {disabled} min={min ?? null} max={max ?? null}>
|
|
78
|
+
<DatePicker.Trigger {size} />
|
|
79
|
+
<DatePicker.Content>
|
|
80
|
+
<DatePicker.Calendar />
|
|
81
|
+
</DatePicker.Content>
|
|
82
|
+
</DatePicker.Root>
|
|
104
83
|
</div>
|
|
105
84
|
|
|
106
85
|
<div data-part="time-section">
|
|
107
|
-
<TimeInput bind:value={timeValue} {disabled} />
|
|
86
|
+
<TimeInput bind:value={timeValue} {disabled} {size} />
|
|
108
87
|
</div>
|
|
109
88
|
|
|
110
89
|
{#if name}
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
padding: var(--dry-space-2, 0.5rem) var(--dry-nc-item-padding, 0.75rem);
|
|
23
23
|
font-size: var(--dry-type-ui-caption-size, var(--dry-text-xs-size, 0.75rem));
|
|
24
24
|
font-weight: 600;
|
|
25
|
-
color: var(--dry-color-text-weak
|
|
25
|
+
color: var(--dry-color-text-weak);
|
|
26
26
|
text-transform: uppercase;
|
|
27
27
|
letter-spacing: 0.05em;
|
|
28
|
-
background: var(--dry-color-fill
|
|
28
|
+
background: var(--dry-color-fill);
|
|
29
29
|
}
|
|
30
30
|
</style>
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
<style>
|
|
44
44
|
[data-notification-center-item] {
|
|
45
45
|
padding: var(--dry-nc-item-padding, 0.75rem);
|
|
46
|
+
color: var(--dry-color-text-strong);
|
|
46
47
|
border-bottom: 1px solid var(--dry-color-stroke-weak, #e2e8f0);
|
|
47
48
|
transition: background var(--dry-duration-fast, 100ms) ease;
|
|
48
49
|
}
|
|
@@ -52,7 +53,7 @@
|
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
[data-notification-center-item][data-state='unread'] {
|
|
55
|
-
background: var(--dry-nc-unread-bg
|
|
56
|
+
background: var(--dry-nc-unread-bg);
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
[data-notification-center-item][data-variant='warning'] {
|
|
@@ -53,6 +53,19 @@
|
|
|
53
53
|
} catch {
|
|
54
54
|
// Already shown
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
// Nudge panel into viewport if anchor positioning overflows
|
|
58
|
+
requestAnimationFrame(() => {
|
|
59
|
+
if (!panelEl) return;
|
|
60
|
+
panelEl.style.translate = '';
|
|
61
|
+
const rect = panelEl.getBoundingClientRect();
|
|
62
|
+
const pad = 8;
|
|
63
|
+
if (rect.left < pad) {
|
|
64
|
+
panelEl.style.translate = `${pad - rect.left}px 0`;
|
|
65
|
+
} else if (rect.right > window.innerWidth - pad) {
|
|
66
|
+
panelEl.style.translate = `${window.innerWidth - pad - rect.right}px 0`;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
56
69
|
} else {
|
|
57
70
|
try {
|
|
58
71
|
if (panelEl.matches(':popover-open')) {
|
|
@@ -61,6 +74,7 @@
|
|
|
61
74
|
} catch {
|
|
62
75
|
// Already hidden
|
|
63
76
|
}
|
|
77
|
+
if (panelEl) panelEl.style.translate = '';
|
|
64
78
|
}
|
|
65
79
|
});
|
|
66
80
|
</script>
|
|
@@ -94,11 +108,16 @@
|
|
|
94
108
|
inset: unset;
|
|
95
109
|
margin: 0;
|
|
96
110
|
|
|
111
|
+
&:not(:popover-open) {
|
|
112
|
+
display: none;
|
|
113
|
+
}
|
|
114
|
+
|
|
97
115
|
display: grid;
|
|
98
|
-
grid-template-columns: var(--dry-nc-panel-width, 24rem);
|
|
116
|
+
grid-template-columns: min(var(--dry-nc-panel-width, 24rem), calc(100dvw - var(--dry-space-4, 1rem) * 2));
|
|
99
117
|
max-height: 28rem;
|
|
100
118
|
overflow-y: auto;
|
|
101
|
-
background: var(--dry-color-bg-
|
|
119
|
+
background: var(--dry-color-bg-overlay, #f1f3f5);
|
|
120
|
+
color: var(--dry-color-text-strong);
|
|
102
121
|
border: 1px solid var(--dry-color-stroke-weak, #e2e8f0);
|
|
103
122
|
border-radius: var(--dry-radius-lg, 0.5rem);
|
|
104
123
|
box-shadow: var(--dry-shadow-lg, 0 10px 15px -3px rgba(0, 0, 0, 0.1));
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
[data-notification-center-root] {
|
|
67
67
|
--dry-nc-panel-width: 24rem;
|
|
68
68
|
--dry-nc-item-padding: var(--dry-space-3, 0.75rem);
|
|
69
|
-
--dry-nc-unread-bg: var(--dry-color-fill-brand
|
|
69
|
+
--dry-nc-unread-bg: color-mix(in srgb, var(--dry-color-fill-brand) 12%, transparent);
|
|
70
70
|
|
|
71
71
|
position: relative;
|
|
72
72
|
display: inline-grid;
|
|
@@ -19,20 +19,17 @@
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
function handleClick(e: MouseEvent & { currentTarget: HTMLButtonElement }) {
|
|
23
|
-
ctx.toggle();
|
|
24
|
-
if (onclick) (onclick as (e: MouseEvent & { currentTarget: HTMLButtonElement }) => void)(e);
|
|
25
|
-
}
|
|
26
22
|
</script>
|
|
27
23
|
|
|
28
24
|
<button
|
|
29
25
|
bind:this={el}
|
|
30
26
|
id={ctx.triggerId}
|
|
27
|
+
popovertarget={ctx.panelId}
|
|
31
28
|
aria-expanded={ctx.open}
|
|
32
29
|
aria-haspopup="dialog"
|
|
33
30
|
data-notification-center-trigger
|
|
34
31
|
class={className}
|
|
35
|
-
onclick
|
|
32
|
+
{onclick}
|
|
36
33
|
{...rest}
|
|
37
34
|
>
|
|
38
35
|
{@render children({ unreadCount: ctx.unreadCount })}
|
|
@@ -53,6 +50,7 @@
|
|
|
53
50
|
color: var(--dry-color-text-strong);
|
|
54
51
|
box-shadow: var(--dry-shadow-sm);
|
|
55
52
|
font: inherit;
|
|
53
|
+
cursor: pointer;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
56
|
[data-notification-center-trigger]:focus-visible {
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
grid-template-columns: 1fr 1.5rem;
|
|
100
100
|
align-items: center;
|
|
101
101
|
gap: var(--dry-space-2);
|
|
102
|
+
min-height: var(--dry-space-12);
|
|
102
103
|
padding: var(--dry-select-padding-y) var(--dry-select-padding-x);
|
|
103
104
|
font-size: var(--dry-select-font-size);
|
|
104
105
|
line-height: var(--dry-type-small-leading);
|
|
@@ -171,6 +172,7 @@
|
|
|
171
172
|
--dry-select-padding-x: var(--dry-space-2);
|
|
172
173
|
--dry-select-padding-y: var(--dry-space-1);
|
|
173
174
|
--dry-select-font-size: var(--dry-type-tiny-size);
|
|
175
|
+
min-height: var(--dry-space-8);
|
|
174
176
|
line-height: var(--dry-type-tiny-leading);
|
|
175
177
|
}
|
|
176
178
|
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface TimeInputProps {
|
|
2
|
+
value?: string;
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
step?: number;
|
|
5
|
+
size?: 'sm' | 'md' | 'lg';
|
|
6
|
+
name?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
}
|
|
2
9
|
export { default as TimeInput } from './time-input.svelte';
|
|
@@ -1,97 +1,141 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
2
3
|
import { getFormControlCtx } from '@dryui/primitives';
|
|
3
|
-
import
|
|
4
|
+
import { Select } from '../select/index.js';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
value?: string;
|
|
8
|
+
disabled?: boolean;
|
|
9
|
+
step?: number;
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
name?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
}
|
|
4
14
|
|
|
5
15
|
let {
|
|
6
16
|
value = $bindable(''),
|
|
7
17
|
disabled = false,
|
|
8
18
|
step,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
size = 'md',
|
|
20
|
+
name,
|
|
21
|
+
class: className
|
|
22
|
+
}: Props = $props();
|
|
12
23
|
|
|
13
24
|
const ctx = getFormControlCtx();
|
|
14
25
|
const isDisabled = $derived(disabled || ctx?.disabled || false);
|
|
15
|
-
</script>
|
|
16
26
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
bind:value
|
|
21
|
-
{step}
|
|
22
|
-
id={ctx?.id}
|
|
23
|
-
disabled={isDisabled}
|
|
24
|
-
required={ctx?.required || undefined}
|
|
25
|
-
aria-describedby={ctx?.describedBy}
|
|
26
|
-
aria-invalid={ctx?.hasError || undefined}
|
|
27
|
-
aria-errormessage={ctx?.errorMessageId}
|
|
28
|
-
data-time-input
|
|
29
|
-
data-disabled={isDisabled || undefined}
|
|
30
|
-
class={className}
|
|
31
|
-
{...rest}
|
|
32
|
-
/>
|
|
33
|
-
</span>
|
|
27
|
+
let hourStr = $state('');
|
|
28
|
+
let minuteStr = $state('');
|
|
29
|
+
let updating = false;
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
// Parse incoming value into hour/minute (only reacts to value changes)
|
|
32
|
+
$effect(() => {
|
|
33
|
+
if (updating) return;
|
|
34
|
+
const parts = value.split(':');
|
|
35
|
+
const h = parts[0] ?? '';
|
|
36
|
+
const m = parts[1] ?? '';
|
|
37
|
+
untrack(() => {
|
|
38
|
+
if (h !== hourStr) hourStr = h;
|
|
39
|
+
if (m !== minuteStr) minuteStr = m;
|
|
40
|
+
});
|
|
41
|
+
});
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
color: var(--dry-time-input-color);
|
|
55
|
-
font-family: var(--dry-font-sans);
|
|
56
|
-
font-size: var(--dry-time-input-font-size);
|
|
57
|
-
line-height: var(--dry-type-small-leading);
|
|
58
|
-
box-sizing: border-box;
|
|
59
|
-
appearance: none;
|
|
60
|
-
transition:
|
|
61
|
-
border-color var(--dry-duration-fast) var(--dry-ease-default),
|
|
62
|
-
box-shadow var(--dry-duration-fast) var(--dry-ease-default);
|
|
63
|
-
|
|
64
|
-
&::-webkit-calendar-picker-indicator {
|
|
65
|
-
opacity: 0.7;
|
|
66
|
-
cursor: pointer;
|
|
67
|
-
filter: none;
|
|
43
|
+
// Reconstruct value when selects change (only reacts to hourStr/minuteStr)
|
|
44
|
+
$effect(() => {
|
|
45
|
+
const h = hourStr;
|
|
46
|
+
const m = minuteStr;
|
|
47
|
+
if (h && m) {
|
|
48
|
+
const newVal = `${h}:${m}`;
|
|
49
|
+
untrack(() => {
|
|
50
|
+
if (newVal !== value) {
|
|
51
|
+
updating = true;
|
|
52
|
+
value = newVal;
|
|
53
|
+
updating = false;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
68
56
|
}
|
|
57
|
+
});
|
|
69
58
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
59
|
+
// Generate hour options 00-23
|
|
60
|
+
const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0'));
|
|
73
61
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
62
|
+
// Generate minute options based on step
|
|
63
|
+
const minutes = $derived.by(() => {
|
|
64
|
+
const stepMinutes = step && step >= 60 ? Math.floor(step / 60) : 1;
|
|
65
|
+
const count = Math.floor(60 / stepMinutes);
|
|
66
|
+
return Array.from({ length: count }, (_, i) =>
|
|
67
|
+
String(i * stepMinutes).padStart(2, '0')
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
</script>
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
72
|
+
<div
|
|
73
|
+
role="group"
|
|
74
|
+
aria-label="Time"
|
|
75
|
+
data-time-input-wrapper
|
|
76
|
+
data-disabled={isDisabled || undefined}
|
|
77
|
+
id={ctx?.id}
|
|
78
|
+
aria-describedby={ctx?.describedBy}
|
|
79
|
+
aria-invalid={ctx?.hasError || undefined}
|
|
80
|
+
aria-errormessage={ctx?.errorMessageId}
|
|
81
|
+
class={className}
|
|
82
|
+
>
|
|
83
|
+
<Select.Root bind:value={hourStr} disabled={isDisabled}>
|
|
84
|
+
<Select.Trigger {size} aria-label="Hour">
|
|
85
|
+
<span data-time-display data-placeholder={!hourStr ? '' : undefined}>
|
|
86
|
+
{hourStr || 'HH'}
|
|
87
|
+
</span>
|
|
88
|
+
</Select.Trigger>
|
|
89
|
+
<Select.Content>
|
|
90
|
+
{#each hours as h (h)}
|
|
91
|
+
<Select.Item value={h}>{h}</Select.Item>
|
|
92
|
+
{/each}
|
|
93
|
+
</Select.Content>
|
|
94
|
+
</Select.Root>
|
|
95
|
+
|
|
96
|
+
<span data-time-separator>:</span>
|
|
97
|
+
|
|
98
|
+
<Select.Root bind:value={minuteStr} disabled={isDisabled}>
|
|
99
|
+
<Select.Trigger {size} aria-label="Minute">
|
|
100
|
+
<span data-time-display data-placeholder={!minuteStr ? '' : undefined}>
|
|
101
|
+
{minuteStr || 'MM'}
|
|
102
|
+
</span>
|
|
103
|
+
</Select.Trigger>
|
|
104
|
+
<Select.Content>
|
|
105
|
+
{#each minutes as m (m)}
|
|
106
|
+
<Select.Item value={m}>{m}</Select.Item>
|
|
107
|
+
{/each}
|
|
108
|
+
</Select.Content>
|
|
109
|
+
</Select.Root>
|
|
110
|
+
|
|
111
|
+
{#if name}
|
|
112
|
+
<input type="hidden" {name} {value} disabled={isDisabled || undefined} />
|
|
113
|
+
{/if}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<style>
|
|
117
|
+
[data-time-input-wrapper] {
|
|
118
|
+
display: inline-grid;
|
|
119
|
+
grid-auto-flow: column;
|
|
120
|
+
grid-auto-columns: max-content;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: var(--dry-space-1);
|
|
84
123
|
}
|
|
85
124
|
|
|
86
|
-
[data-time-
|
|
87
|
-
|
|
88
|
-
[data-time-input]::-webkit-datetime-edit-ampm-field {
|
|
89
|
-
padding: 0;
|
|
125
|
+
[data-time-display][data-placeholder] {
|
|
126
|
+
color: var(--dry-color-text-weak);
|
|
90
127
|
}
|
|
91
128
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
129
|
+
[data-time-separator] {
|
|
130
|
+
font-family: var(--dry-font-sans);
|
|
131
|
+
font-size: var(--dry-type-small-size);
|
|
132
|
+
color: var(--dry-color-text-strong);
|
|
133
|
+
font-weight: 600;
|
|
134
|
+
line-height: 1;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
[data-time-input-wrapper][data-disabled] {
|
|
138
|
+
opacity: 0.55;
|
|
139
|
+
pointer-events: none;
|
|
96
140
|
}
|
|
97
141
|
</style>
|