@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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Small, native-first date picker and inline calendar primitives.
4
4
 
5
- > v0.2: 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.
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
- 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.
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.2
184
+ - Bun 1.4.3
181
185
  - TypeScript 7.0.2 (`checkJs` + declaration emit)
182
- - Biome 2.5.11
183
- - Playwright 1.62.1
184
- - `@lekoala/floating ^0.1.1`
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 in v0.2:
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 for v0.2:
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;
@@ -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-nav-gap"
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-nav-gap"
414
+ "name": "--dp-row-gap"
415
+ },
416
+ {
417
+ "name": "--dp-picker-button-size"
388
418
  },
389
419
  {
390
420
  "name": "--dp-year-inline-size"
@@ -1,17 +1,20 @@
1
1
  :where(date-calendar, date-picker) {
2
- --dp-bg: Canvas;
3
- --dp-fg: CanvasText;
4
- --dp-muted: color-mix(in srgb, CanvasText 58%, Canvas);
5
- --dp-border: color-mix(in srgb, CanvasText 22%, Canvas);
6
- --dp-hover: color-mix(in srgb, CanvasText 7%, Canvas);
7
- --dp-accent: Highlight;
8
- --dp-accent-fg: HighlightText;
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.25rem;
13
+ --dp-gap: 0.375rem;
12
14
  --dp-header-control-size: 2.25rem;
13
- --dp-header-block-gap: 0.4rem;
14
- --dp-nav-gap: calc(var(--dp-gap) * 2.5);
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 native caret: truncate with an ellipsis instead. */
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; day-cell spacing stays fixed. */
155
- border-spacing: 0 0.25rem;
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.6rem;
187
+ block-size: 1.5rem;
169
188
  color: var(--dp-muted);
170
189
  font-size: 0.72rem;
171
- font-weight: 600;
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
- .dp-day:focus-visible {
185
- outline: 2px solid var(--dp-accent);
186
- outline-offset: 2px;
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
- .dp-day[data-today="true"]:not([data-selected="true"]) {
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) auto;
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) auto 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) auto minmax(0, 1fr) 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: 3;
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: 4;
397
+ grid-column: 3;
318
398
  grid-row: 1;
319
399
  min-inline-size: 0;
320
- inline-size: 100%;
321
400
  }
322
401
 
323
- /* From/to read as one joined control: square the inner corners, but only
324
- when both companions are present so a lone time keeps its own radius. */
325
- date-picker:has(> [data-time-start]):has(> [data-time-end]) > input[data-time-start] {
326
- border-start-end-radius: 0;
327
- border-end-end-radius: 0;
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-column: 2;
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: 1 / span 2;
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.65rem;
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(0 0 0 / 0.16);
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
- .dp-day[data-selected="true"] {
389
- border: 2px solid Highlight;
390
- background-color: Canvas;
391
- color: CanvasText;
392
- font-weight: 700;
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
- .dp-day[data-range-start="true"],
396
- .dp-day[data-range-end="true"] {
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
- .dp-day[data-selected="true"]::before {
417
- content: "";
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
  }