@lekoala/date-picker 0.1.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.
Files changed (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +236 -0
  3. package/custom-elements.json +366 -0
  4. package/dist/date-picker.css +344 -0
  5. package/dist/date-picker.js +2693 -0
  6. package/dist/date-picker.min.css +1 -0
  7. package/dist/date-picker.min.js +17 -0
  8. package/dist/date-picker.standalone.min.js +361 -0
  9. package/dist/types/calendar-model.d.ts +34 -0
  10. package/dist/types/calendar-model.d.ts.map +1 -0
  11. package/dist/types/date-calendar.d.ts +228 -0
  12. package/dist/types/date-calendar.d.ts.map +1 -0
  13. package/dist/types/date-field.d.ts +94 -0
  14. package/dist/types/date-field.d.ts.map +1 -0
  15. package/dist/types/date-picker.d.ts +258 -0
  16. package/dist/types/date-picker.d.ts.map +1 -0
  17. package/dist/types/date-range.d.ts +89 -0
  18. package/dist/types/date-range.d.ts.map +1 -0
  19. package/dist/types/date.d.ts +61 -0
  20. package/dist/types/date.d.ts.map +1 -0
  21. package/dist/types/define.d.ts +2 -0
  22. package/dist/types/define.d.ts.map +1 -0
  23. package/dist/types/index.d.ts +9 -0
  24. package/dist/types/index.d.ts.map +1 -0
  25. package/dist/types/intl.d.ts +33 -0
  26. package/dist/types/intl.d.ts.map +1 -0
  27. package/dist/types/locales/ar.d.ts +22 -0
  28. package/dist/types/locales/ar.d.ts.map +1 -0
  29. package/dist/types/locales/de.d.ts +22 -0
  30. package/dist/types/locales/de.d.ts.map +1 -0
  31. package/dist/types/locales/en.d.ts +22 -0
  32. package/dist/types/locales/en.d.ts.map +1 -0
  33. package/dist/types/locales/es.d.ts +22 -0
  34. package/dist/types/locales/es.d.ts.map +1 -0
  35. package/dist/types/locales/fr.d.ts +22 -0
  36. package/dist/types/locales/fr.d.ts.map +1 -0
  37. package/dist/types/locales/hi.d.ts +22 -0
  38. package/dist/types/locales/hi.d.ts.map +1 -0
  39. package/dist/types/locales/id.d.ts +22 -0
  40. package/dist/types/locales/id.d.ts.map +1 -0
  41. package/dist/types/locales/it.d.ts +22 -0
  42. package/dist/types/locales/it.d.ts.map +1 -0
  43. package/dist/types/locales/ja.d.ts +22 -0
  44. package/dist/types/locales/ja.d.ts.map +1 -0
  45. package/dist/types/locales/ko.d.ts +22 -0
  46. package/dist/types/locales/ko.d.ts.map +1 -0
  47. package/dist/types/locales/nl.d.ts +22 -0
  48. package/dist/types/locales/nl.d.ts.map +1 -0
  49. package/dist/types/locales/pl.d.ts +22 -0
  50. package/dist/types/locales/pl.d.ts.map +1 -0
  51. package/dist/types/locales/pt-BR.d.ts +22 -0
  52. package/dist/types/locales/pt-BR.d.ts.map +1 -0
  53. package/dist/types/locales/pt-PT.d.ts +22 -0
  54. package/dist/types/locales/pt-PT.d.ts.map +1 -0
  55. package/dist/types/locales/ru.d.ts +22 -0
  56. package/dist/types/locales/ru.d.ts.map +1 -0
  57. package/dist/types/locales/tr.d.ts +22 -0
  58. package/dist/types/locales/tr.d.ts.map +1 -0
  59. package/dist/types/locales/zh-CN.d.ts +22 -0
  60. package/dist/types/locales/zh-CN.d.ts.map +1 -0
  61. package/dist/types/messages.d.ts +43 -0
  62. package/dist/types/messages.d.ts.map +1 -0
  63. package/dist/types/source.d.ts +28 -0
  64. package/dist/types/source.d.ts.map +1 -0
  65. package/docs/ACCESSIBILITY.md +128 -0
  66. package/docs/API.md +289 -0
  67. package/docs/ARCHITECTURE.md +183 -0
  68. package/docs/AT-CHECKLIST.md +63 -0
  69. package/docs/CALENDAR-INTEGRATION.md +70 -0
  70. package/docs/DECISIONS.md +40 -0
  71. package/docs/MIGRATION-FLATPICKR.md +92 -0
  72. package/docs/ROADMAP.md +105 -0
  73. package/docs/TESTING.md +73 -0
  74. package/docs/USE_CASES.md +128 -0
  75. package/package.json +174 -0
  76. package/src/calendar-model.js +80 -0
  77. package/src/date-calendar.js +800 -0
  78. package/src/date-field.js +203 -0
  79. package/src/date-picker.css +344 -0
  80. package/src/date-picker.js +1140 -0
  81. package/src/date-range.js +172 -0
  82. package/src/date.js +203 -0
  83. package/src/define.js +9 -0
  84. package/src/index.js +15 -0
  85. package/src/intl.js +155 -0
  86. package/src/locales/ar.js +20 -0
  87. package/src/locales/de.js +20 -0
  88. package/src/locales/en.js +20 -0
  89. package/src/locales/es.js +20 -0
  90. package/src/locales/fr.js +20 -0
  91. package/src/locales/hi.js +20 -0
  92. package/src/locales/id.js +20 -0
  93. package/src/locales/it.js +20 -0
  94. package/src/locales/ja.js +20 -0
  95. package/src/locales/ko.js +20 -0
  96. package/src/locales/nl.js +20 -0
  97. package/src/locales/pl.js +20 -0
  98. package/src/locales/pt-BR.js +20 -0
  99. package/src/locales/pt-PT.js +20 -0
  100. package/src/locales/ru.js +20 -0
  101. package/src/locales/tr.js +20 -0
  102. package/src/locales/zh-CN.js +20 -0
  103. package/src/messages.js +31 -0
  104. package/src/source.js +50 -0
@@ -0,0 +1,203 @@
1
+ import { isDate } from "./date.js";
2
+ import { createDateAdapter } from "./intl.js";
3
+
4
+ /**
5
+ * Owns a single editable form field inside a picker: the visible input, its
6
+ * hidden canonical ISO field, the Intl adapter and the text commit pipeline
7
+ * (parsing, availability gate, stale-response protection).
8
+ *
9
+ * It deliberately knows nothing about calendars, popups or ranges. Remote
10
+ * availability is injected as an async `confirm(date)` callback and the
11
+ * committed canonical value is applied by the coordinator, so all canonical
12
+ * reflection and events stay at the picker level.
13
+ */
14
+ export class DateFieldController {
15
+ /**
16
+ * @param {HTMLInputElement} input
17
+ * @param {{messages?: Record<string, string>, locale?: string}} [options]
18
+ */
19
+ constructor(input, options = {}) {
20
+ this.input = input;
21
+ this.messages = options.messages || {};
22
+ this.adapter = createDateAdapter(options.locale || "");
23
+ /** @type {HTMLInputElement | null} */
24
+ this.hidden = null;
25
+ /** Current canonical value (YYYY-MM-DD or ""). */
26
+ this.canonical = "";
27
+ this._dirty = false;
28
+ this._commitId = 0;
29
+ this._originalName = input.getAttribute("name") || "";
30
+ /** @type {MutationObserver | null} */
31
+ this._attributeObserver = null;
32
+ /** @type {(() => void) | null} */
33
+ this.onAttributesChanged = null;
34
+ /**
35
+ * Async availability gate for a parsed date. Returning `{ ok: false }`
36
+ * marks the target unavailable. Swappable at runtime.
37
+ * @type {((date: string) => Promise<{ok: boolean, message?: string}>) | null}
38
+ */
39
+ this.confirm = null;
40
+ }
41
+
42
+ /** @param {string} locale */
43
+ setLocale(locale) {
44
+ this.adapter = createDateAdapter(locale || "");
45
+ }
46
+
47
+ /** @param {Record<string, string>} messages */
48
+ setMessages(messages) {
49
+ this.messages = messages || {};
50
+ }
51
+
52
+ /** @param {string} value */
53
+ format(value) {
54
+ return this.adapter.format(value);
55
+ }
56
+
57
+ /** @param {string} text */
58
+ parse(text) {
59
+ return this.adapter.parse(text);
60
+ }
61
+
62
+ get placeholder() {
63
+ return this.adapter.placeholder;
64
+ }
65
+
66
+ get isDisabled() {
67
+ return this.input.disabled;
68
+ }
69
+
70
+ get isReadOnly() {
71
+ return this.input.readOnly;
72
+ }
73
+
74
+ /** Create the hidden canonical field for an authored `name` and observe the
75
+ * input for dynamic name/form/disabled changes. */
76
+ setupFormValue() {
77
+ const input = this.input;
78
+ this._originalName = input.getAttribute("name") || "";
79
+ this._attributeObserver = new MutationObserver(() => this.onAttributesChanged?.());
80
+ this._attributeObserver.observe(input, {
81
+ attributes: true,
82
+ attributeFilter: ["disabled", "readonly", "name", "form"],
83
+ });
84
+ this.syncInputState();
85
+ }
86
+
87
+ /** Sync the hidden field with the current dynamic attributes. */
88
+ syncInputState() {
89
+ const input = this.input;
90
+ const name = input.getAttribute("name") || "";
91
+ if (name) {
92
+ if (!this.hidden) {
93
+ const hidden = document.createElement("input");
94
+ hidden.type = "hidden";
95
+ hidden.value = this.canonical;
96
+ input.insertAdjacentElement("afterend", hidden);
97
+ this.hidden = hidden;
98
+ }
99
+ if (this.hidden) this.hidden.name = name;
100
+ input.removeAttribute("name");
101
+ }
102
+ if (this.hidden) {
103
+ if (input.hasAttribute("form")) this.hidden.setAttribute("form", input.getAttribute("form") || "");
104
+ else this.hidden.removeAttribute("form");
105
+ this.hidden.disabled = input.disabled;
106
+ }
107
+ }
108
+
109
+ /** Invalidate any pending (possibly in-flight) commit. */
110
+ dirty() {
111
+ this._commitId++;
112
+ }
113
+
114
+ /** Whether the user edited the visible text since the last application. */
115
+ get isDirty() {
116
+ return this._dirty;
117
+ }
118
+
119
+ /** Live text input: the raw text no longer matches the canonical value, so
120
+ * the hidden ISO must not submit it. */
121
+ handleInput() {
122
+ const input = this.input;
123
+ this._dirty = true;
124
+ this.dirty();
125
+ if (this.hidden) this.hidden.value = "";
126
+ const text = input.value.trim();
127
+ if (!text) {
128
+ input.setCustomValidity("");
129
+ return;
130
+ }
131
+ const canonical = this.canonical ? this.adapter.format(this.canonical) : "";
132
+ input.setCustomValidity(text === canonical ? "" : this.messages.invalidDate);
133
+ }
134
+
135
+ /**
136
+ * Commit the visible text. Returns the parsed canonical date on success, the
137
+ * empty string on a clear, or a falsy marker for invalid/stale commits. The
138
+ * caller applies the canonical value so reflection and events stay central.
139
+ * @returns {Promise<{status: "ok" | "clear" | "invalid" | "stale", value?: string}>}
140
+ */
141
+ async commit() {
142
+ const input = this.input;
143
+ const commitId = ++this._commitId;
144
+ const text = input.value.trim();
145
+ if (!text) {
146
+ if (commitId !== this._commitId) return { status: "stale" };
147
+ input.setCustomValidity("");
148
+ return { status: "clear", value: "" };
149
+ }
150
+ const parsed = this.adapter.parse(text);
151
+ if (!parsed) {
152
+ if (commitId !== this._commitId) return { status: "stale" };
153
+ if (this.hidden) this.hidden.value = "";
154
+ input.setCustomValidity(this.messages.invalidDate);
155
+ return { status: "invalid" };
156
+ }
157
+ if (this.confirm) {
158
+ const result = await this.confirm(parsed);
159
+ if (commitId !== this._commitId) return { status: "stale" };
160
+ if (!result?.ok) {
161
+ if (this.hidden) this.hidden.value = "";
162
+ input.setCustomValidity(result?.message || this.messages.unavailableDate);
163
+ return { status: "invalid" };
164
+ }
165
+ }
166
+ return { status: "ok", value: parsed };
167
+ }
168
+
169
+ /**
170
+ * @param {string} value
171
+ * @param {{format?: boolean}} [options]
172
+ */
173
+ setCanonical(value, options = {}) {
174
+ const next = value || "";
175
+ if (next && !isDate(next)) throw new TypeError(`Invalid date-picker value: ${next}`);
176
+ // A programmatic/coordinator application supersedes any in-flight text
177
+ // commit, so an older pending result can never overwrite a newer value.
178
+ this.dirty();
179
+ this.canonical = next;
180
+ this._dirty = false;
181
+ if (this.hidden) this.hidden.value = next;
182
+ if (options.format !== false) this.input.value = next ? this.adapter.format(next) : "";
183
+ this.input.setCustomValidity("");
184
+ }
185
+
186
+ /** Restore value from `input.defaultValue` (native form reset). */
187
+ restoreDefault() {
188
+ const text = String(this.input.defaultValue ?? "").trim();
189
+ const parsed = isDate(text) ? text : this.adapter.parse(text);
190
+ return parsed && isDate(parsed) ? parsed : "";
191
+ }
192
+
193
+ /** Remove the hidden field and attribute observer, restoring the original
194
+ * authored `name` on the visible input. */
195
+ teardown() {
196
+ this._attributeObserver?.disconnect();
197
+ this._attributeObserver = null;
198
+ const hiddenName = this.hidden?.getAttribute("name") || this._originalName;
199
+ if (hiddenName) this.input.setAttribute("name", hiddenName);
200
+ this.hidden?.remove();
201
+ this.hidden = null;
202
+ }
203
+ }
@@ -0,0 +1,344 @@
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;
9
+ --dp-radius: 0.5rem;
10
+ --dp-day-size: 2.75rem;
11
+ --dp-gap: 0.25rem;
12
+ color: var(--dp-fg);
13
+ font: inherit;
14
+ }
15
+
16
+ .dp-visually-hidden {
17
+ position: absolute !important;
18
+ inline-size: 1px !important;
19
+ block-size: 1px !important;
20
+ padding: 0 !important;
21
+ margin: -1px !important;
22
+ overflow: hidden !important;
23
+ clip: rect(0 0 0 0) !important;
24
+ white-space: nowrap !important;
25
+ border: 0 !important;
26
+ }
27
+
28
+ date-calendar {
29
+ display: block;
30
+ inline-size: max-content;
31
+ max-inline-size: 100%;
32
+ }
33
+
34
+ .dp-calendar-shell {
35
+ inline-size: max-content;
36
+ max-inline-size: 100%;
37
+ }
38
+
39
+ .dp-calendar-header {
40
+ display: grid;
41
+ grid-template-columns: minmax(0, 1fr) auto auto auto;
42
+ align-items: center;
43
+ gap: var(--dp-gap);
44
+ margin-block-end: 0.4rem;
45
+ }
46
+
47
+ .dp-calendar-header .dp-prev {
48
+ margin-inline-start: calc(var(--dp-gap) * 2.5);
49
+ }
50
+
51
+ .dp-calendar-header :where(button, select, input) {
52
+ min-block-size: 2.25rem;
53
+ border: 1px solid var(--dp-border);
54
+ border-radius: calc(var(--dp-radius) * 0.75);
55
+ background: var(--dp-bg);
56
+ color: var(--dp-fg);
57
+ font: inherit;
58
+ }
59
+
60
+ .dp-calendar-header select,
61
+ .dp-calendar-header input {
62
+ min-inline-size: 0;
63
+ padding-inline: 0.45rem;
64
+ }
65
+
66
+ .dp-month-select {
67
+ inline-size: 100%;
68
+ }
69
+
70
+ .dp-year-input {
71
+ inline-size: 5.5rem;
72
+ }
73
+
74
+ .dp-nav {
75
+ display: grid;
76
+ place-items: center;
77
+ inline-size: 2.25rem;
78
+ padding: 0;
79
+ font-size: 1.35rem;
80
+ line-height: 1;
81
+ cursor: pointer;
82
+ }
83
+
84
+ .dp-nav svg {
85
+ display: block;
86
+ inline-size: 1rem;
87
+ block-size: 1rem;
88
+ pointer-events: none;
89
+ }
90
+
91
+ .dp-calendar-header :where(button, select, input):disabled,
92
+ .dp-picker-button:disabled {
93
+ cursor: not-allowed;
94
+ opacity: 0.45;
95
+ }
96
+
97
+ .dp-nav:hover:not(:disabled) {
98
+ background: var(--dp-hover);
99
+ }
100
+
101
+ .dp-day:hover:not([data-disabled="true"]):not([data-selected="true"]):not([data-range-start="true"]):not(
102
+ [data-range-end="true"]
103
+ ) {
104
+ background: var(--dp-hover);
105
+ }
106
+
107
+ .dp-calendar-heading {
108
+ margin: 0 0 0.35rem;
109
+ font-size: 0.875rem;
110
+ font-weight: 600;
111
+ text-align: center;
112
+ }
113
+
114
+ .dp-grid {
115
+ border-collapse: separate;
116
+ /* --dp-gap scopes to the header grid; day-cell spacing stays fixed. */
117
+ border-spacing: 0 0.25rem;
118
+ table-layout: fixed;
119
+ }
120
+
121
+ .dp-grid :where(th, td) {
122
+ box-sizing: border-box;
123
+ inline-size: var(--dp-day-size);
124
+ block-size: var(--dp-day-size);
125
+ padding: 0;
126
+ text-align: center;
127
+ }
128
+
129
+ .dp-grid thead th {
130
+ block-size: 1.6rem;
131
+ color: var(--dp-muted);
132
+ font-size: 0.72rem;
133
+ font-weight: 600;
134
+ }
135
+
136
+ .dp-day {
137
+ position: relative;
138
+ border: 1px solid transparent;
139
+ border-radius: calc(var(--dp-radius) * 0.75);
140
+ cursor: pointer;
141
+ outline: none;
142
+ vertical-align: middle;
143
+ font-size: 0.875rem;
144
+ }
145
+
146
+ .dp-day:focus-visible {
147
+ outline: 2px solid var(--dp-accent);
148
+ outline-offset: 1px;
149
+ z-index: 1;
150
+ }
151
+
152
+ .dp-day[data-selected="true"] {
153
+ background: var(--dp-accent);
154
+ color: var(--dp-accent-fg);
155
+ }
156
+
157
+ .dp-day[data-today="true"]:not([data-selected="true"]) {
158
+ border-color: var(--dp-accent);
159
+ }
160
+
161
+ .dp-day[data-outside-month="true"] {
162
+ color: var(--dp-muted);
163
+ }
164
+
165
+ .dp-day[data-disabled="true"] {
166
+ cursor: not-allowed;
167
+ opacity: 0.42;
168
+ }
169
+
170
+ .dp-day[data-range-start="true"],
171
+ .dp-day[data-range-end="true"] {
172
+ background: var(--dp-accent);
173
+ color: var(--dp-accent-fg);
174
+ }
175
+
176
+ .dp-day[data-in-range="true"] {
177
+ border-radius: 0;
178
+ background: color-mix(in srgb, var(--dp-accent) 18%, var(--dp-bg));
179
+ }
180
+
181
+ /* Join the band without gaps, including the corners beside each endpoint. */
182
+ .dp-day[data-range-start="true"]:has(+ [data-in-range="true"], + [data-range-end="true"]) {
183
+ border-start-end-radius: 0;
184
+ border-end-end-radius: 0;
185
+ }
186
+
187
+ .dp-day:is([data-in-range="true"], [data-range-start="true"]) + .dp-day[data-range-end="true"] {
188
+ border-start-start-radius: 0;
189
+ border-end-start-radius: 0;
190
+ }
191
+
192
+ .dp-day[data-in-range="true"]:first-of-type {
193
+ border-start-start-radius: var(--dp-radius);
194
+ border-end-start-radius: var(--dp-radius);
195
+ }
196
+
197
+ .dp-day[data-in-range="true"]:last-of-type {
198
+ border-start-end-radius: var(--dp-radius);
199
+ border-end-end-radius: var(--dp-radius);
200
+ }
201
+
202
+ .dp-day-number {
203
+ display: block;
204
+ font-variant-numeric: tabular-nums;
205
+ }
206
+
207
+ .dp-day-extra {
208
+ position: absolute;
209
+ inset-inline: 2px;
210
+ inset-block-end: 1px;
211
+ display: flex;
212
+ justify-content: center;
213
+ gap: 2px;
214
+ min-block-size: 0.35rem;
215
+ font-size: 0.55rem;
216
+ line-height: 1;
217
+ pointer-events: none;
218
+ }
219
+
220
+ .dp-week-number,
221
+ .dp-week-heading {
222
+ inline-size: 1.6rem !important;
223
+ color: var(--dp-muted);
224
+ font-size: 0.65rem;
225
+ font-weight: 500;
226
+ }
227
+
228
+ date-picker {
229
+ position: relative;
230
+ display: inline-grid;
231
+ grid-template-columns: minmax(0, 1fr) auto;
232
+ align-items: stretch;
233
+ max-inline-size: 100%;
234
+ }
235
+
236
+ date-picker > input:not([type="hidden"]) {
237
+ grid-column: 1;
238
+ min-inline-size: 0;
239
+ }
240
+
241
+ date-picker[range] > input:not([type="hidden"]) {
242
+ grid-column: 1;
243
+ }
244
+
245
+ date-picker[range] > input[data-range-start] {
246
+ grid-row: 1;
247
+ }
248
+
249
+ date-picker[range] > input[data-range-end] {
250
+ grid-row: 2;
251
+ }
252
+
253
+ .dp-picker-button {
254
+ grid-column: 2;
255
+ min-inline-size: 2.5rem;
256
+ border: 1px solid var(--dp-border);
257
+ border-inline-start: 0;
258
+ border-start-start-radius: 0;
259
+ border-end-start-radius: 0;
260
+ border-start-end-radius: var(--dp-radius);
261
+ border-end-end-radius: var(--dp-radius);
262
+ background: var(--dp-bg);
263
+ color: var(--dp-fg);
264
+ font: inherit;
265
+ cursor: pointer;
266
+ }
267
+
268
+ date-picker[range] > .dp-picker-button {
269
+ grid-row: 1 / span 2;
270
+ }
271
+
272
+ .dp-picker-panel[popover] {
273
+ position: fixed;
274
+ inset: auto;
275
+ margin: 0;
276
+ padding: 0.65rem;
277
+ border: 1px solid var(--dp-border);
278
+ border-radius: var(--dp-radius);
279
+ background: var(--dp-bg);
280
+ color: var(--dp-fg);
281
+ box-shadow: 0 0.5rem 1.5rem rgb(0 0 0 / 0.16);
282
+ max-block-size: var(--available-height, 90svh);
283
+ max-inline-size: calc(100vi - 1rem);
284
+ overflow: auto;
285
+ }
286
+
287
+ .dp-picker-panel:not(:popover-open) {
288
+ display: none;
289
+ }
290
+
291
+ @media (forced-colors: active) {
292
+ .dp-day[data-selected="true"] {
293
+ border: 2px solid Highlight;
294
+ background-color: Canvas;
295
+ color: CanvasText;
296
+ font-weight: 700;
297
+ }
298
+
299
+ .dp-day[data-range-start="true"],
300
+ .dp-day[data-range-end="true"] {
301
+ border: 2px solid Highlight;
302
+ background-color: Canvas;
303
+ color: CanvasText;
304
+ font-weight: 700;
305
+ }
306
+
307
+ .dp-day[data-in-range="true"] {
308
+ color: CanvasText;
309
+ }
310
+
311
+ .dp-day[data-in-range="true"]::after {
312
+ content: "";
313
+ position: absolute;
314
+ inset-inline: 3px;
315
+ inset-block-end: 4px;
316
+ block-size: 1px;
317
+ background-color: Highlight;
318
+ }
319
+
320
+ .dp-day[data-selected="true"]::before {
321
+ content: "";
322
+ position: absolute;
323
+ inset: 3px;
324
+ border: 1px solid CanvasText;
325
+ border-radius: calc(var(--dp-radius) * 0.5);
326
+ pointer-events: none;
327
+ }
328
+
329
+ .dp-day[data-today="true"] {
330
+ border-color: Highlight;
331
+ }
332
+
333
+ .dp-day-extra {
334
+ color: CanvasText;
335
+ forced-color-adjust: none;
336
+ }
337
+
338
+ .dp-day[data-disabled="true"],
339
+ .dp-calendar-header :where(button, select, input):disabled,
340
+ .dp-picker-button:disabled {
341
+ opacity: 1;
342
+ color: GrayText;
343
+ }
344
+ }