@lekoala/date-picker 0.2.0 → 0.4.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.
- package/README.md +12 -8
- package/custom-elements.json +32 -2
- package/dist/date-picker.css +163 -115
- package/dist/date-picker.js +656 -171
- package/dist/date-picker.min.css +1 -1
- package/dist/date-picker.min.js +13 -13
- package/dist/date-picker.standalone.min.js +177 -129
- package/dist/types/date-calendar.d.ts +34 -3
- package/dist/types/date-calendar.d.ts.map +1 -1
- package/dist/types/date-field.d.ts +15 -5
- package/dist/types/date-field.d.ts.map +1 -1
- package/dist/types/date-picker.d.ts +153 -14
- package/dist/types/date-picker.d.ts.map +1 -1
- package/dist/types/date-range.d.ts +102 -20
- package/dist/types/date-range.d.ts.map +1 -1
- package/dist/types/date.d.ts +10 -0
- package/dist/types/date.d.ts.map +1 -1
- package/dist/types/intl.d.ts.map +1 -1
- package/docs/ACCESSIBILITY.md +22 -10
- package/docs/API.md +72 -3
- package/docs/ARCHITECTURE.md +16 -2
- package/docs/AT-CHECKLIST.md +1 -1
- package/docs/DECISIONS.md +19 -4
- package/docs/MIGRATION-FLATPICKR.md +2 -2
- package/docs/TESTING.md +1 -1
- package/docs/USE_CASES.md +3 -0
- package/package.json +7 -7
- package/src/date-calendar.js +148 -48
- package/src/date-field.js +20 -8
- package/src/date-picker.css +163 -115
- package/src/date-picker.js +549 -110
- package/src/date-range.js +184 -30
- package/src/date.js +50 -1
- package/src/intl.js +13 -7
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Small, native-first date picker and inline calendar primitives.
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> The interaction contract, event names, public styling tokens and the component API are frozen — see [docs/DECISIONS.md](docs/DECISIONS.md). Internal DOM and class names are `.dp-*` implementation details and stay private.
|
|
6
6
|
|
|
7
7
|
This project fills the gap between a normal editable date field and a full scheduling calendar. A plain date input remains the right answer for simple CMS/editing forms. This component becomes useful when a date has context: availability, disabled days, annotations, remote state, start/end relationships, or a mini calendar that navigates another view.
|
|
8
8
|
|
|
@@ -162,7 +162,11 @@ The calendar follows the WAI-ARIA date-picker/grid interaction model:
|
|
|
162
162
|
|
|
163
163
|
The picker popup uses native Popover for the top layer and `@lekoala/floating` for geometry. It has `role="dialog"`, but it is intentionally **not** marked `aria-modal` because the current implementation does not inert the rest of the document.
|
|
164
164
|
|
|
165
|
-
|
|
165
|
+
The calendar trigger is a real button painted inside the field's own box (like the native `input[type=time]` indicator): the field reserves its place with `padding-inline-end`, keeps its own border and background, and its focus ring wraps the trigger too. The trigger draws no chrome of its own and shows a small inner ring when focused.
|
|
166
|
+
|
|
167
|
+
Fields, header controls and days share an inner outline that survives forced colors. Customize its color with `--dp-focus` (default `var(--dp-accent)`) and thickness with `--dp-focus-width` (default `2px`) on `date-picker` and `date-calendar`. The trigger keeps a `-5px` inset around its icon; selected days and range endpoints use `--dp-accent-fg` for contrast. Fields and header controls now show this solid outline instead of the previous soft halo.
|
|
168
|
+
|
|
169
|
+
When the popup opens on the field's focus, grid navigation is reachable via `ArrowDown` (or the trigger button) so the focus-restore/Escape flow stays predictable. Closing returns focus to the control that opened the popover: the field when it opened by focus, the trigger when it was activated. In range mode focus follows the active bound instead, because a first pick moves the workflow to the other bound.
|
|
166
170
|
|
|
167
171
|
See [docs/ACCESSIBILITY.md](docs/ACCESSIBILITY.md).
|
|
168
172
|
|
|
@@ -177,11 +181,11 @@ If a previous `bun run dev` is still holding port `4859`, clear it with `bun run
|
|
|
177
181
|
|
|
178
182
|
Tooling is aligned with the current LeKoala component repos:
|
|
179
183
|
|
|
180
|
-
- Bun 1.4.
|
|
184
|
+
- Bun 1.4.3
|
|
181
185
|
- TypeScript 7.0.2 (`checkJs` + declaration emit)
|
|
182
|
-
- Biome 2.5.
|
|
183
|
-
- Playwright 1.
|
|
184
|
-
- `@lekoala/floating ^0.
|
|
186
|
+
- Biome 2.5.15
|
|
187
|
+
- Playwright 1.64.0
|
|
188
|
+
- `@lekoala/floating ^0.2.0`
|
|
185
189
|
|
|
186
190
|
Useful commands:
|
|
187
191
|
|
|
@@ -212,7 +216,7 @@ The default ESM exports point at `src/`, matching the native-first development s
|
|
|
212
216
|
|
|
213
217
|
## Scope
|
|
214
218
|
|
|
215
|
-
Included
|
|
219
|
+
Included:
|
|
216
220
|
|
|
217
221
|
- inline single-date selection;
|
|
218
222
|
- mini-calendar/navigation-only use;
|
|
@@ -234,7 +238,7 @@ Included in v0.2:
|
|
|
234
238
|
- ISO week numbers;
|
|
235
239
|
- keyboard and focus contracts.
|
|
236
240
|
|
|
237
|
-
Deliberately out of scope
|
|
241
|
+
Deliberately out of scope:
|
|
238
242
|
|
|
239
243
|
- custom time-picker UI and combined datetime values (only native `input[type=time]` companions are supported);
|
|
240
244
|
- timezones;
|
package/custom-elements.json
CHANGED
|
@@ -139,6 +139,11 @@
|
|
|
139
139
|
"kind": "method",
|
|
140
140
|
"name": "focusGrid"
|
|
141
141
|
},
|
|
142
|
+
{
|
|
143
|
+
"kind": "method",
|
|
144
|
+
"name": "activateDate",
|
|
145
|
+
"description": "Run one explicit activation: availability check, then the cancelable `dateactivate` seam, then selection. Click, keyboard and any other input device share this single path, so an application that cancels `dateactivate` cannot be bypassed by a new interaction."
|
|
146
|
+
},
|
|
142
147
|
{
|
|
143
148
|
"kind": "method",
|
|
144
149
|
"name": "render"
|
|
@@ -151,6 +156,9 @@
|
|
|
151
156
|
{
|
|
152
157
|
"name": "datechange"
|
|
153
158
|
},
|
|
159
|
+
{
|
|
160
|
+
"name": "datefocus"
|
|
161
|
+
},
|
|
154
162
|
{
|
|
155
163
|
"name": "dateinvalid"
|
|
156
164
|
},
|
|
@@ -189,6 +197,12 @@
|
|
|
189
197
|
{
|
|
190
198
|
"name": "--dp-accent-fg"
|
|
191
199
|
},
|
|
200
|
+
{
|
|
201
|
+
"name": "--dp-focus"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"name": "--dp-focus-width"
|
|
205
|
+
},
|
|
192
206
|
{
|
|
193
207
|
"name": "--dp-radius"
|
|
194
208
|
},
|
|
@@ -205,7 +219,10 @@
|
|
|
205
219
|
"name": "--dp-header-block-gap"
|
|
206
220
|
},
|
|
207
221
|
{
|
|
208
|
-
"name": "--dp-
|
|
222
|
+
"name": "--dp-row-gap"
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"name": "--dp-picker-button-size"
|
|
209
226
|
},
|
|
210
227
|
{
|
|
211
228
|
"name": "--dp-year-inline-size"
|
|
@@ -316,6 +333,10 @@
|
|
|
316
333
|
"kind": "getter",
|
|
317
334
|
"name": "isDateDisabled"
|
|
318
335
|
},
|
|
336
|
+
{
|
|
337
|
+
"kind": "getter",
|
|
338
|
+
"name": "coordinateSpace"
|
|
339
|
+
},
|
|
319
340
|
{
|
|
320
341
|
"kind": "method",
|
|
321
342
|
"name": "validate"
|
|
@@ -368,6 +389,12 @@
|
|
|
368
389
|
{
|
|
369
390
|
"name": "--dp-accent-fg"
|
|
370
391
|
},
|
|
392
|
+
{
|
|
393
|
+
"name": "--dp-focus"
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
"name": "--dp-focus-width"
|
|
397
|
+
},
|
|
371
398
|
{
|
|
372
399
|
"name": "--dp-radius"
|
|
373
400
|
},
|
|
@@ -384,7 +411,10 @@
|
|
|
384
411
|
"name": "--dp-header-block-gap"
|
|
385
412
|
},
|
|
386
413
|
{
|
|
387
|
-
"name": "--dp-
|
|
414
|
+
"name": "--dp-row-gap"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
"name": "--dp-picker-button-size"
|
|
388
418
|
},
|
|
389
419
|
{
|
|
390
420
|
"name": "--dp-year-inline-size"
|
package/dist/date-picker.css
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
:where(date-calendar, date-picker) {
|
|
2
|
-
--dp-bg:
|
|
3
|
-
--dp-fg:
|
|
4
|
-
--dp-muted: color-mix(in srgb,
|
|
5
|
-
--dp-border: color-mix(in srgb,
|
|
6
|
-
--dp-hover: color-mix(in srgb,
|
|
7
|
-
--dp-accent:
|
|
8
|
-
--dp-accent-fg:
|
|
2
|
+
--dp-bg: #ffffff;
|
|
3
|
+
--dp-fg: #1f2328;
|
|
4
|
+
--dp-muted: color-mix(in srgb, var(--dp-fg) 58%, var(--dp-bg));
|
|
5
|
+
--dp-border: color-mix(in srgb, var(--dp-fg) 16%, var(--dp-bg));
|
|
6
|
+
--dp-hover: color-mix(in srgb, var(--dp-fg) 5%, var(--dp-bg));
|
|
7
|
+
--dp-accent: #2563eb;
|
|
8
|
+
--dp-accent-fg: #fff;
|
|
9
|
+
--dp-focus: var(--dp-accent);
|
|
10
|
+
--dp-focus-width: 2px;
|
|
9
11
|
--dp-radius: 0.5rem;
|
|
10
12
|
--dp-day-size: 2.75rem;
|
|
11
|
-
--dp-gap: 0.
|
|
13
|
+
--dp-gap: 0.375rem;
|
|
12
14
|
--dp-header-control-size: 2.25rem;
|
|
13
|
-
--dp-header-block-gap: 0.
|
|
14
|
-
--dp-
|
|
15
|
+
--dp-header-block-gap: 0.75rem;
|
|
16
|
+
--dp-row-gap: 0.375rem;
|
|
17
|
+
--dp-picker-button-size: 2.5rem;
|
|
15
18
|
--dp-year-inline-size: 5.5rem;
|
|
16
19
|
--dp-day-radius: calc(var(--dp-radius) * 0.75);
|
|
17
20
|
color: var(--dp-fg);
|
|
@@ -48,6 +51,9 @@ date-calendar {
|
|
|
48
51
|
--dp-header-control-size, the row grows to the tallest control and the
|
|
49
52
|
others stretch to match, so month/year/nav stay equal. */
|
|
50
53
|
align-items: stretch;
|
|
54
|
+
/* Uniform horizontal rhythm: the four header controls are self-evident from
|
|
55
|
+
their kind and position, so no group gets an extra semantic gap. The air
|
|
56
|
+
belongs to the vertical zones (header -> weekdays -> dates). */
|
|
51
57
|
gap: var(--dp-gap);
|
|
52
58
|
margin-block-end: var(--dp-header-block-gap);
|
|
53
59
|
}
|
|
@@ -59,10 +65,6 @@ date-calendar[data-year-first] .dp-calendar-header {
|
|
|
59
65
|
grid-template-columns: auto minmax(0, 1fr) auto auto;
|
|
60
66
|
}
|
|
61
67
|
|
|
62
|
-
.dp-calendar-header .dp-prev {
|
|
63
|
-
margin-inline-start: var(--dp-nav-gap);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
68
|
.dp-calendar-header :where(button, select, input) {
|
|
67
69
|
min-block-size: var(--dp-header-control-size);
|
|
68
70
|
box-sizing: border-box;
|
|
@@ -78,26 +80,42 @@ date-calendar[data-year-first] .dp-calendar-header {
|
|
|
78
80
|
line-height: 1.2;
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
/* The select menulist carries UA-internal leading that author padding only
|
|
82
|
-
adds to (and it ignores line-height), so it gets zero author padding.
|
|
83
|
-
Equal outer heights come from the header grid's stretch alignment: the row
|
|
84
|
-
sizes to the tallest control and the others stretch to match. */
|
|
85
|
-
.dp-calendar-header select {
|
|
86
|
-
padding-block: 0;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
.dp-calendar-header select,
|
|
90
83
|
.dp-calendar-header input {
|
|
91
84
|
min-inline-size: 0;
|
|
92
85
|
padding-inline: 0.45rem;
|
|
93
86
|
}
|
|
94
87
|
|
|
88
|
+
/* Author-drawn caret so the month select matches the nav controls without a
|
|
89
|
+
hardcoded SVG color (currentColor keeps theming). Equal outer heights come
|
|
90
|
+
from the header grid's stretch alignment: the row sizes to the tallest
|
|
91
|
+
control and the others stretch to match. */
|
|
95
92
|
.dp-month-select {
|
|
96
93
|
inline-size: 100%;
|
|
94
|
+
min-inline-size: 0;
|
|
97
95
|
/* Long month names in a narrow track (compact consumers, long locales)
|
|
98
|
-
would paint under the
|
|
96
|
+
would paint under the caret: truncate with an ellipsis instead. */
|
|
99
97
|
overflow: hidden;
|
|
100
98
|
text-overflow: ellipsis;
|
|
99
|
+
appearance: none;
|
|
100
|
+
padding-inline: 0.45rem 2rem;
|
|
101
|
+
background-image:
|
|
102
|
+
linear-gradient(45deg, transparent 50%, currentColor 50%),
|
|
103
|
+
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
|
104
|
+
background-position:
|
|
105
|
+
calc(100% - 0.9rem) 50%,
|
|
106
|
+
calc(100% - 0.65rem) 50%;
|
|
107
|
+
background-size: 0.3rem 0.3rem;
|
|
108
|
+
background-repeat: no-repeat;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* Inherited RTL mirrors the header layout via the grid. The logical
|
|
112
|
+
`padding-inline` already flips, so only the physically-positioned caret has
|
|
113
|
+
to follow it to the other side. */
|
|
114
|
+
[dir="rtl"] .dp-month-select,
|
|
115
|
+
html[dir="rtl"] .dp-month-select {
|
|
116
|
+
background-position:
|
|
117
|
+
0.9rem 50%,
|
|
118
|
+
0.65rem 50%;
|
|
101
119
|
}
|
|
102
120
|
|
|
103
121
|
.dp-year-input {
|
|
@@ -121,14 +139,6 @@ date-calendar[data-year-first] .dp-calendar-header {
|
|
|
121
139
|
pointer-events: none;
|
|
122
140
|
}
|
|
123
141
|
|
|
124
|
-
/* Inherited RTL mirrors the header layout via the grid; mirror the nav glyphs
|
|
125
|
-
so previous/next keep their visual sense. Actions stay chronological: no
|
|
126
|
-
button swap, no keyboard remapping. */
|
|
127
|
-
[dir="rtl"] .dp-nav svg,
|
|
128
|
-
html[dir="rtl"] .dp-nav svg {
|
|
129
|
-
transform: scaleX(-1);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
142
|
.dp-calendar-header :where(button, select, input):disabled,
|
|
133
143
|
.dp-picker-button:disabled {
|
|
134
144
|
cursor: not-allowed;
|
|
@@ -139,6 +149,15 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
139
149
|
background: var(--dp-hover);
|
|
140
150
|
}
|
|
141
151
|
|
|
152
|
+
/* Inner outlines survive forced colors and keep focus inside each control.
|
|
153
|
+
The picker trigger uses a tighter inset around its icon below. */
|
|
154
|
+
.dp-calendar-header :where(button, select, input):focus-visible,
|
|
155
|
+
date-picker > input:not([type="hidden"]):focus-visible,
|
|
156
|
+
.dp-day:focus-visible {
|
|
157
|
+
outline: var(--dp-focus-width) solid var(--dp-focus);
|
|
158
|
+
outline-offset: calc(var(--dp-focus-width) * -1);
|
|
159
|
+
}
|
|
160
|
+
|
|
142
161
|
.dp-day:hover:not([data-disabled="true"]):not([data-selected="true"]):not([data-range-start="true"]):not(
|
|
143
162
|
[data-range-end="true"]
|
|
144
163
|
) {
|
|
@@ -151,8 +170,8 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
151
170
|
|
|
152
171
|
.dp-grid {
|
|
153
172
|
border-collapse: separate;
|
|
154
|
-
/* --dp-gap scopes to the header grid;
|
|
155
|
-
border-spacing: 0
|
|
173
|
+
/* --dp-gap scopes to the header grid; vertical week rhythm is --dp-row-gap. */
|
|
174
|
+
border-spacing: 0 var(--dp-row-gap);
|
|
156
175
|
table-layout: fixed;
|
|
157
176
|
}
|
|
158
177
|
|
|
@@ -165,10 +184,10 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
165
184
|
}
|
|
166
185
|
|
|
167
186
|
.dp-grid thead th {
|
|
168
|
-
block-size: 1.
|
|
187
|
+
block-size: 1.5rem;
|
|
169
188
|
color: var(--dp-muted);
|
|
170
189
|
font-size: 0.72rem;
|
|
171
|
-
font-weight:
|
|
190
|
+
font-weight: 500;
|
|
172
191
|
}
|
|
173
192
|
|
|
174
193
|
.dp-day {
|
|
@@ -181,14 +200,9 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
181
200
|
font-size: 0.875rem;
|
|
182
201
|
}
|
|
183
202
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
outline-
|
|
187
|
-
/* Separation gap between the cell fill and the ring: invisible on plain
|
|
188
|
-
days, but keeps the ring readable on accent-filled days (selected, range
|
|
189
|
-
endpoints) where an accent-on-accent outline would disappear. */
|
|
190
|
-
box-shadow: 0 0 0 2px var(--dp-bg);
|
|
191
|
-
z-index: 1;
|
|
203
|
+
/* On an accent-filled cell the ring inverts so it stays readable. */
|
|
204
|
+
.dp-day:is([data-selected="true"], [data-range-start="true"], [data-range-end="true"]):focus-visible {
|
|
205
|
+
outline-color: var(--dp-accent-fg);
|
|
192
206
|
}
|
|
193
207
|
|
|
194
208
|
/* Base tint for days outside the displayed month. It stays before the
|
|
@@ -203,7 +217,10 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
203
217
|
color: var(--dp-accent-fg);
|
|
204
218
|
}
|
|
205
219
|
|
|
206
|
-
|
|
220
|
+
/* Today never competes with a stronger state: a selected day or a range
|
|
221
|
+
endpoint keeps its own treatment, in normal rendering and in
|
|
222
|
+
forced-colors alike. */
|
|
223
|
+
.dp-day[data-today="true"]:not([data-selected="true"], [data-range-start="true"], [data-range-end="true"]) {
|
|
207
224
|
border-color: var(--dp-accent);
|
|
208
225
|
}
|
|
209
226
|
|
|
@@ -235,13 +252,35 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
235
252
|
}
|
|
236
253
|
|
|
237
254
|
.dp-day[data-in-range="true"]:first-of-type {
|
|
238
|
-
border-start-start-radius: var(--dp-radius);
|
|
239
|
-
border-end-start-radius: var(--dp-radius);
|
|
255
|
+
border-start-start-radius: var(--dp-day-radius);
|
|
256
|
+
border-end-start-radius: var(--dp-day-radius);
|
|
240
257
|
}
|
|
241
258
|
|
|
242
259
|
.dp-day[data-in-range="true"]:last-of-type {
|
|
243
|
-
border-start-end-radius: var(--dp-radius);
|
|
244
|
-
border-end-end-radius: var(--dp-radius);
|
|
260
|
+
border-start-end-radius: var(--dp-day-radius);
|
|
261
|
+
border-end-end-radius: var(--dp-day-radius);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Projected band: the range the next commit would produce. It reads as one
|
|
265
|
+
step short of the committed band, so a hovered, keyboard or dragged preview
|
|
266
|
+
is never mistaken for the selection itself. */
|
|
267
|
+
[data-range-preview] .dp-day:is([data-range-start="true"], [data-range-end="true"]) {
|
|
268
|
+
background: color-mix(in srgb, var(--dp-accent) 58%, var(--dp-bg));
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
[data-range-preview] .dp-day[data-in-range="true"] {
|
|
272
|
+
background: color-mix(in srgb, var(--dp-accent) 11%, var(--dp-bg));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/* Endpoint handles are draggable only while the committed range is complete
|
|
276
|
+
and its two ends sit on different days. */
|
|
277
|
+
[data-range-drag] .dp-day:is([data-range-start="true"], [data-range-end="true"]) {
|
|
278
|
+
cursor: grab;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
[data-range-drag="active"],
|
|
282
|
+
[data-range-drag="active"] .dp-day {
|
|
283
|
+
cursor: grabbing;
|
|
245
284
|
}
|
|
246
285
|
|
|
247
286
|
.dp-day-number {
|
|
@@ -284,97 +323,117 @@ html[dir="rtl"] .dp-nav svg {
|
|
|
284
323
|
date-picker {
|
|
285
324
|
position: relative;
|
|
286
325
|
display: inline-grid;
|
|
287
|
-
grid-template-columns: minmax(0, 1fr)
|
|
326
|
+
grid-template-columns: minmax(0, 1fr);
|
|
288
327
|
align-items: stretch;
|
|
289
328
|
max-inline-size: 100%;
|
|
290
329
|
}
|
|
291
330
|
|
|
331
|
+
/* Every authored control is a grid item. */
|
|
292
332
|
date-picker > input:not([type="hidden"]) {
|
|
333
|
+
min-inline-size: 0;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* Date fields reserve room for the trigger, which is painted inside the
|
|
337
|
+
field's own box: the field is the real outer box, so its focus ring wraps
|
|
338
|
+
the affordance too. `grid-row` must stay explicit, otherwise the trigger
|
|
339
|
+
(placed with a definite row) claims the row and pushes the field down. */
|
|
340
|
+
date-picker > input:not([type="hidden"]):not([type="time"]) {
|
|
293
341
|
grid-column: 1;
|
|
342
|
+
grid-row: 1;
|
|
343
|
+
box-sizing: border-box;
|
|
344
|
+
inline-size: 100%;
|
|
345
|
+
padding-inline-end: calc(var(--dp-picker-button-size) + 0.5rem);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/* The trigger is a real button overlaid on the field's inline-end, like the
|
|
349
|
+
native `input[type=time]` indicator: no chrome of its own, transparent, so
|
|
350
|
+
the authored field background and border stay visible underneath. */
|
|
351
|
+
.dp-picker-button {
|
|
352
|
+
display: grid;
|
|
353
|
+
place-items: center;
|
|
354
|
+
grid-column: 1;
|
|
355
|
+
grid-row: 1;
|
|
356
|
+
justify-self: end;
|
|
357
|
+
align-self: stretch;
|
|
358
|
+
inline-size: var(--dp-picker-button-size);
|
|
294
359
|
min-inline-size: 0;
|
|
360
|
+
border: 0;
|
|
361
|
+
background: transparent;
|
|
362
|
+
color: var(--dp-fg);
|
|
363
|
+
font: inherit;
|
|
364
|
+
cursor: pointer;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/* Like the native time indicator: the ring wraps the icon, not the field (the
|
|
368
|
+
field already shows its own ring through :focus-visible). Same radius as
|
|
369
|
+
everywhere else: no bespoke shape. */
|
|
370
|
+
.dp-picker-button:focus-visible {
|
|
371
|
+
outline: var(--dp-focus-width) solid var(--dp-focus);
|
|
372
|
+
outline-offset: -5px;
|
|
373
|
+
border-radius: var(--dp-radius);
|
|
295
374
|
}
|
|
296
375
|
|
|
297
376
|
/* Optional native time companions share the date row; authored inputs stay
|
|
298
377
|
consumer-styled, the picker only reserves their grid cells. Time tracks are
|
|
299
|
-
flexible (never `auto`) so a wide UA time control cannot squeeze the date.
|
|
378
|
+
flexible (never `auto`) so a wide UA time control cannot squeeze the date.
|
|
379
|
+
Each companion keeps its own box and its own gap: nothing is joined, so
|
|
380
|
+
focus rings never merge and no neighbour swallows another's focus. */
|
|
300
381
|
date-picker:has(> [data-time-start], > [data-time-end]) {
|
|
301
|
-
grid-template-columns: minmax(0, 1fr)
|
|
382
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
383
|
+
column-gap: var(--dp-gap);
|
|
302
384
|
}
|
|
303
385
|
|
|
304
386
|
date-picker:has(> [data-time-start]):has(> [data-time-end]) {
|
|
305
|
-
grid-template-columns: minmax(0, 1fr)
|
|
387
|
+
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
|
306
388
|
}
|
|
307
389
|
|
|
308
390
|
date-picker > input[data-time-start] {
|
|
309
|
-
grid-column:
|
|
391
|
+
grid-column: 2;
|
|
310
392
|
grid-row: 1;
|
|
311
393
|
min-inline-size: 0;
|
|
312
|
-
inline-size: 100%;
|
|
313
|
-
margin-inline-start: var(--dp-gap);
|
|
314
394
|
}
|
|
315
395
|
|
|
316
396
|
date-picker > input[data-time-end] {
|
|
317
|
-
grid-column:
|
|
397
|
+
grid-column: 3;
|
|
318
398
|
grid-row: 1;
|
|
319
399
|
min-inline-size: 0;
|
|
320
|
-
inline-size: 100%;
|
|
321
400
|
}
|
|
322
401
|
|
|
323
|
-
/*
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
date-picker:has(> [data-time-start]):has(> [data-time-end]) > input[data-time-end] {
|
|
331
|
-
border-start-start-radius: 0;
|
|
332
|
-
border-end-start-radius: 0;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
date-picker[range] > input:not([type="hidden"]) {
|
|
336
|
-
grid-column: 1;
|
|
402
|
+
/* Range: two stacked fields, each owning its trigger, sharing one popup and
|
|
403
|
+
one calendar. No trigger spans both bounds, so each bound keeps a local
|
|
404
|
+
affordance and a local focus target. */
|
|
405
|
+
date-picker[range] {
|
|
406
|
+
row-gap: var(--dp-gap);
|
|
337
407
|
}
|
|
338
408
|
|
|
339
409
|
date-picker[range] > input[data-range-start] {
|
|
340
410
|
grid-row: 1;
|
|
341
|
-
border-end-start-radius: 0;
|
|
342
411
|
}
|
|
343
412
|
|
|
344
413
|
date-picker[range] > input[data-range-end] {
|
|
345
414
|
grid-row: 2;
|
|
346
|
-
border-start-start-radius: 0;
|
|
347
415
|
}
|
|
348
416
|
|
|
349
|
-
.dp-picker-button {
|
|
350
|
-
grid-
|
|
351
|
-
min-inline-size: 2.5rem;
|
|
352
|
-
border: 1px solid var(--dp-border);
|
|
353
|
-
border-inline-start: 0;
|
|
354
|
-
border-start-start-radius: 0;
|
|
355
|
-
border-end-start-radius: 0;
|
|
356
|
-
border-start-end-radius: var(--dp-radius);
|
|
357
|
-
border-end-end-radius: var(--dp-radius);
|
|
358
|
-
background: var(--dp-bg);
|
|
359
|
-
color: var(--dp-fg);
|
|
360
|
-
font: inherit;
|
|
361
|
-
cursor: pointer;
|
|
417
|
+
date-picker[range] > .dp-picker-button[data-endpoint="start"] {
|
|
418
|
+
grid-row: 1;
|
|
362
419
|
}
|
|
363
420
|
|
|
364
|
-
date-picker[range] > .dp-picker-button {
|
|
365
|
-
grid-row:
|
|
421
|
+
date-picker[range] > .dp-picker-button[data-endpoint="end"] {
|
|
422
|
+
grid-row: 2;
|
|
366
423
|
}
|
|
367
424
|
|
|
368
425
|
.dp-picker-panel[popover] {
|
|
426
|
+
/* Safe baseline. show() switches to absolute only when the consumer
|
|
427
|
+
* explicitly selects document coordinate space. */
|
|
369
428
|
position: fixed;
|
|
370
429
|
inset: auto;
|
|
371
430
|
margin: 0;
|
|
372
|
-
padding: 0.
|
|
431
|
+
padding: 0.875rem 1rem;
|
|
373
432
|
border: 1px solid var(--dp-border);
|
|
374
433
|
border-radius: var(--dp-radius);
|
|
375
434
|
background: var(--dp-bg);
|
|
376
435
|
color: var(--dp-fg);
|
|
377
|
-
box-shadow: 0 0.5rem 1.5rem rgb(
|
|
436
|
+
box-shadow: 0 0.5rem 1.5rem rgb(15 23 42 / 0.12);
|
|
378
437
|
max-block-size: var(--available-height, 90svh);
|
|
379
438
|
max-inline-size: calc(100vi - 1rem);
|
|
380
439
|
overflow: auto;
|
|
@@ -385,19 +444,22 @@ date-picker[range] > .dp-picker-button {
|
|
|
385
444
|
}
|
|
386
445
|
|
|
387
446
|
@media (forced-colors: active) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
447
|
+
/* The author-drawn caret is a gradient and disappears here: hand the
|
|
448
|
+
control back to the browser instead of trying to rebuild it. */
|
|
449
|
+
.dp-month-select,
|
|
450
|
+
[dir="rtl"] .dp-month-select {
|
|
451
|
+
appearance: auto;
|
|
452
|
+
background-image: none;
|
|
453
|
+
padding-inline: 0.45rem;
|
|
393
454
|
}
|
|
394
455
|
|
|
395
|
-
|
|
396
|
-
|
|
456
|
+
/* Fills vanish here, so selection and endpoints fall back to a 2px
|
|
457
|
+
Highlight border. The in-range underline is the only band marker left:
|
|
458
|
+
the range fill is lost and the browser does not know the semantics. */
|
|
459
|
+
.dp-day:is([data-selected="true"], [data-range-start="true"], [data-range-end="true"]) {
|
|
397
460
|
border: 2px solid Highlight;
|
|
398
461
|
background-color: Canvas;
|
|
399
462
|
color: CanvasText;
|
|
400
|
-
font-weight: 700;
|
|
401
463
|
}
|
|
402
464
|
|
|
403
465
|
.dp-day[data-in-range="true"] {
|
|
@@ -413,15 +475,8 @@ date-picker[range] > .dp-picker-button {
|
|
|
413
475
|
background-color: Highlight;
|
|
414
476
|
}
|
|
415
477
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
position: absolute;
|
|
419
|
-
inset: 3px;
|
|
420
|
-
border: 1px solid CanvasText;
|
|
421
|
-
border-radius: calc(var(--dp-radius) * 0.5);
|
|
422
|
-
pointer-events: none;
|
|
423
|
-
}
|
|
424
|
-
|
|
478
|
+
/* Pin the today marker: the deterministic accent is a non-system color
|
|
479
|
+
and would otherwise remap unpredictably here. */
|
|
425
480
|
.dp-day[data-today="true"] {
|
|
426
481
|
border-color: Highlight;
|
|
427
482
|
}
|
|
@@ -430,11 +485,4 @@ date-picker[range] > .dp-picker-button {
|
|
|
430
485
|
color: CanvasText;
|
|
431
486
|
forced-color-adjust: none;
|
|
432
487
|
}
|
|
433
|
-
|
|
434
|
-
.dp-day[data-disabled="true"],
|
|
435
|
-
.dp-calendar-header :where(button, select, input):disabled,
|
|
436
|
-
.dp-picker-button:disabled {
|
|
437
|
-
opacity: 1;
|
|
438
|
-
color: GrayText;
|
|
439
|
-
}
|
|
440
488
|
}
|