temporal-react-datepicker 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 temporal-react-datepicker contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,440 @@
1
+ # temporal-react-datepicker
2
+
3
+ A high-performance, accessible React date picker built on the [JavaScript Temporal API](https://tc39.es/proposal-temporal/docs/). Uses `Temporal.PlainDate` for all date logic — immutable, correct calendar arithmetic, no legacy `Date` object.
4
+
5
+ ## Requirements
6
+
7
+ - React 19+
8
+ - `@js-temporal/polyfill` 0.5.1+
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install temporal-react-datepicker @js-temporal/polyfill
14
+ # or
15
+ pnpm add temporal-react-datepicker @js-temporal/polyfill
16
+ ```
17
+
18
+ ## Basic usage
19
+
20
+ ```tsx
21
+ import { useState } from 'react'
22
+ import { Temporal } from '@js-temporal/polyfill'
23
+ import { TemporalDatePicker } from 'temporal-react-datepicker'
24
+
25
+ function App() {
26
+ const [date, setDate] = useState<Temporal.PlainDate | undefined>(undefined)
27
+
28
+ return <TemporalDatePicker value={date} onChange={setDate} />
29
+ }
30
+ ```
31
+
32
+ ## Date range selection
33
+
34
+ ```tsx
35
+ import type { DateRange } from 'temporal-react-datepicker'
36
+
37
+ const [range, setRange] = useState<DateRange | undefined>(undefined)
38
+
39
+ <TemporalDatePicker
40
+ mode="range"
41
+ clearable
42
+ value={range}
43
+ onChange={setRange}
44
+ />
45
+ ```
46
+
47
+ `DateRange` is `{ start: Temporal.PlainDate; end: Temporal.PlainDate | null }`. After the first click `end` is `null`; after the second click both dates are set.
48
+
49
+ ## Props
50
+
51
+ The component uses a discriminated union — the available props depend on `mode` and `clearable`.
52
+
53
+ ### Shared props
54
+
55
+ | Prop | Type | Default | Description |
56
+ |---|---|---|---|
57
+ | `mode` | `'single' \| 'range'` | `'single'` | Selection mode. |
58
+ | `clearable` | `boolean` | `false` | Show a `×` button to reset the selection when a value is set. |
59
+ | `isDateDisabled` | `(date: Temporal.PlainDate) => boolean` | `undefined` | Return `true` to disable (and block) a specific date. |
60
+ | `showWeekNumbers` | `boolean` | `false` | Show ISO week numbers as an extra column on the left. |
61
+ | `className` | `string` | `''` | Additional CSS class on the root element. |
62
+ | `locale` | `string` | `navigator.language` | BCP 47 locale tag for month/weekday labels. |
63
+ | `labels` | `Partial<Labels>` | English strings | Override any UI string (aria-labels, panel titles, range context). |
64
+ | `renderDayContent` | `(date, state: DayState) => ReactNode` | `undefined` | Custom renderer for each day cell. Falls back to the day number. |
65
+
66
+ ### `mode="single"` props
67
+
68
+ | Prop | Type | Description |
69
+ |---|---|---|
70
+ | `value` | `Temporal.PlainDate \| undefined` | Currently selected date. |
71
+ | `onChange` | `(date: Temporal.PlainDate) => void` | Called on selection. |
72
+
73
+ When `clearable={true}`, `onChange` is widened to `(date: Temporal.PlainDate \| undefined) => void`.
74
+
75
+ ### `mode="range"` props
76
+
77
+ | Prop | Type | Description |
78
+ |---|---|---|
79
+ | `value` | `DateRange \| undefined` | Currently selected range. |
80
+ | `onChange` | `(range: DateRange) => void` | Called on each click. Fires with `end: null` after the first click. |
81
+
82
+ When `clearable={true}`, `onChange` is `(range: DateRange \| undefined) => void`.
83
+
84
+ ## DayState
85
+
86
+ The `renderDayContent` callback receives a `DayState` object as its second argument:
87
+
88
+ ```ts
89
+ interface DayState {
90
+ selected: boolean // true in single mode when this date === value
91
+ inRange: boolean // true for days between range.start and range.end
92
+ isRangeStart: boolean // true when this date === range.start
93
+ isRangeEnd: boolean // true when this date === range.end
94
+ }
95
+ ```
96
+
97
+ ```tsx
98
+ // Bold weight for selected day
99
+ <TemporalDatePicker
100
+ value={date}
101
+ onChange={setDate}
102
+ renderDayContent={(d, { selected }) => (
103
+ <span style={{ fontWeight: selected ? 800 : 400 }}>{d.day}</span>
104
+ )}
105
+ />
106
+ ```
107
+
108
+ ```tsx
109
+ // Dot indicator on Fridays (payday)
110
+ <TemporalDatePicker
111
+ value={date}
112
+ onChange={setDate}
113
+ renderDayContent={(d, { selected }) => (
114
+ <span style={{ position: 'relative', display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
115
+ {d.day}
116
+ {d.dayOfWeek === 5 && (
117
+ <span style={{
118
+ width: 3, height: 3, borderRadius: '50%',
119
+ background: selected ? 'currentColor' : '#38bdf8'
120
+ }} />
121
+ )}
122
+ </span>
123
+ )}
124
+ />
125
+ ```
126
+
127
+ ```tsx
128
+ // Custom badge for range mode (shows "S" / "E" on start and end)
129
+ <TemporalDatePicker
130
+ mode="range"
131
+ value={range}
132
+ onChange={setRange}
133
+ renderDayContent={(d, { isRangeStart, isRangeEnd, inRange }) => (
134
+ <span style={{ position: 'relative' }}>
135
+ {d.day}
136
+ {isRangeStart && (
137
+ <span style={{ position: 'absolute', top: -4, right: -4, fontSize: 8, fontWeight: 700 }}>S</span>
138
+ )}
139
+ {isRangeEnd && (
140
+ <span style={{ position: 'absolute', top: -4, right: -4, fontSize: 8, fontWeight: 700 }}>E</span>
141
+ )}
142
+ </span>
143
+ )}
144
+ />
145
+ ```
146
+
147
+ ```tsx
148
+ // Highlight days from an external data source (e.g. events)
149
+ const EVENTS = new Set(['2026-03-10', '2026-03-18', '2026-03-25'])
150
+
151
+ <TemporalDatePicker
152
+ value={date}
153
+ onChange={setDate}
154
+ renderDayContent={(d, { selected }) => (
155
+ <span style={{ position: 'relative', display: 'inline-flex', flexDirection: 'column', alignItems: 'center', gap: 2 }}>
156
+ {d.day}
157
+ {EVENTS.has(d.toString()) && (
158
+ <span style={{
159
+ width: 4, height: 4, borderRadius: '50%',
160
+ background: selected ? 'white' : '#f472b6'
161
+ }} />
162
+ )}
163
+ </span>
164
+ )}
165
+ />
166
+ ```
167
+
168
+ ## Disabled dates
169
+
170
+ Return `true` from `isDateDisabled` to block a date. Disabled days are not clickable, not keyboard-focusable, and in range mode they block the range from crossing them.
171
+
172
+ ```tsx
173
+ // Weekends only
174
+ <TemporalDatePicker
175
+ value={date}
176
+ onChange={setDate}
177
+ isDateDisabled={d => d.dayOfWeek >= 6}
178
+ />
179
+ ```
180
+
181
+ ```tsx
182
+ // Past dates (relative to today)
183
+ const today = Temporal.Now.plainDateISO()
184
+ <TemporalDatePicker
185
+ value={date}
186
+ onChange={setDate}
187
+ isDateDisabled={d => Temporal.PlainDate.compare(d, today) < 0}
188
+ />
189
+ ```
190
+
191
+ ```tsx
192
+ // Future dates — only allow selecting up to today
193
+ <TemporalDatePicker
194
+ value={date}
195
+ onChange={setDate}
196
+ isDateDisabled={d => Temporal.PlainDate.compare(d, today) > 0}
197
+ />
198
+ ```
199
+
200
+ ```tsx
201
+ // Specific blocked dates (e.g. bank holidays)
202
+ const HOLIDAYS = new Set(['2026-01-01', '2026-12-25', '2026-12-26'])
203
+ <TemporalDatePicker
204
+ value={date}
205
+ onChange={setDate}
206
+ isDateDisabled={d => HOLIDAYS.has(d.toString())}
207
+ />
208
+ ```
209
+
210
+ ```tsx
211
+ // Combined: no weekends AND no past dates
212
+ <TemporalDatePicker
213
+ value={date}
214
+ onChange={setDate}
215
+ isDateDisabled={d =>
216
+ d.dayOfWeek >= 6 ||
217
+ Temporal.PlainDate.compare(d, Temporal.Now.plainDateISO()) < 0
218
+ }
219
+ />
220
+ ```
221
+
222
+ ```tsx
223
+ // Range mode with disabled dates — crossing a disabled date is blocked
224
+ <TemporalDatePicker
225
+ mode="range"
226
+ value={range}
227
+ onChange={setRange}
228
+ isDateDisabled={d => d.dayOfWeek >= 6}
229
+ />
230
+ ```
231
+
232
+ ## Navigation
233
+
234
+ Click the **month name** or **year** in the header to jump directly to a month or year selector panel. Use `Escape` to return to the calendar without making a selection.
235
+
236
+ ## Locale
237
+
238
+ The `locale` prop accepts any [BCP 47 language tag](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#locales_argument). It controls month names, weekday labels, and day `aria-label` strings. Defaults to `navigator.language`.
239
+
240
+ ```tsx
241
+ // Spanish (Spain)
242
+ <TemporalDatePicker value={date} onChange={setDate} locale="es-ES" />
243
+
244
+ // English (US)
245
+ <TemporalDatePicker value={date} onChange={setDate} locale="en-US" />
246
+
247
+ // French
248
+ <TemporalDatePicker value={date} onChange={setDate} locale="fr-FR" />
249
+
250
+ // Japanese
251
+ <TemporalDatePicker value={date} onChange={setDate} locale="ja-JP" />
252
+
253
+ // Arabic (right-to-left script — layout RTL must be handled by the consumer)
254
+ <TemporalDatePicker value={date} onChange={setDate} locale="ar-SA" />
255
+
256
+ // Portuguese (Brazil)
257
+ <TemporalDatePicker value={date} onChange={setDate} locale="pt-BR" />
258
+ ```
259
+
260
+ To follow the browser locale automatically (default behaviour):
261
+
262
+ ```tsx
263
+ <TemporalDatePicker value={date} onChange={setDate} />
264
+ // equivalent to:
265
+ <TemporalDatePicker value={date} onChange={setDate} locale={navigator.language} />
266
+ ```
267
+
268
+ To let the user switch locale at runtime, just pass a state variable:
269
+
270
+ ```tsx
271
+ const [locale, setLocale] = useState('en-US')
272
+
273
+ <select onChange={e => setLocale(e.target.value)}>
274
+ <option value="en-US">English</option>
275
+ <option value="es-ES">Español</option>
276
+ <option value="fr-FR">Français</option>
277
+ <option value="de-DE">Deutsch</option>
278
+ </select>
279
+
280
+ <TemporalDatePicker value={date} onChange={setDate} locale={locale} />
281
+ ```
282
+
283
+ ## Labels (i18n for UI strings)
284
+
285
+ The `locale` prop controls date formatting (month names, weekday headers, day `aria-label`s) via the browser's `Intl` API. UI strings such as button labels, panel titles, and range context suffixes are controlled separately via the `labels` prop.
286
+
287
+ The default language is **English**. Pass `Partial<Labels>` to override any string:
288
+
289
+ ```tsx
290
+ import type { Labels } from 'temporal-react-datepicker'
291
+
292
+ const spanishLabels: Partial<Labels> = {
293
+ prevMonth: 'Mes anterior',
294
+ nextMonth: 'Mes siguiente',
295
+ clearSelection: 'Limpiar selección',
296
+ selectMonth: name => `Seleccionar mes, actualmente ${name}`,
297
+ selectYear: year => `Seleccionar año, actualmente ${year}`,
298
+ weekNumberHeader: 'Semana',
299
+ weekNumber: n => `Semana ${n}`,
300
+ rangeStart: ', inicio de rango',
301
+ rangeEnd: ', fin de rango',
302
+ inRange: ', dentro del rango',
303
+ monthPanelAnnouncement: 'Selector de mes',
304
+ yearPanelAnnouncement: 'Selector de año',
305
+ prevYear: 'Año anterior',
306
+ nextYear: 'Año siguiente',
307
+ monthPanelTitle: 'Seleccionar mes',
308
+ prevYearWindow: 'Ventana anterior',
309
+ nextYearWindow: 'Ventana siguiente',
310
+ yearPanelTitle: 'Seleccionar año',
311
+ }
312
+
313
+ <TemporalDatePicker value={date} onChange={setDate} locale="es-ES" labels={spanishLabels} />
314
+ ```
315
+
316
+ You only need to supply the strings you want to override — the rest fall back to English defaults. For example, to only translate the range context suffixes for French:
317
+
318
+ ```tsx
319
+ <TemporalDatePicker
320
+ mode="range"
321
+ value={range}
322
+ onChange={setRange}
323
+ locale="fr-FR"
324
+ labels={{
325
+ rangeStart: ', début de la plage',
326
+ rangeEnd: ', fin de la plage',
327
+ inRange: ', dans la plage',
328
+ }}
329
+ />
330
+ ```
331
+
332
+ ### `Labels` interface
333
+
334
+ ```ts
335
+ interface Labels {
336
+ // Calendar header buttons
337
+ prevMonth: string
338
+ nextMonth: string
339
+ clearSelection: string
340
+ selectMonth: (monthName: string) => string // e.g. "Select month, currently March"
341
+ selectYear: (year: number) => string // e.g. "Select year, currently 2026"
342
+ // Week number column
343
+ weekNumberHeader: string // column header aria-label
344
+ weekNumber: (weekNum: number) => string // row aria-label, e.g. "Week 12"
345
+ // Range context (appended to day aria-label)
346
+ rangeStart: string // e.g. ", range start"
347
+ rangeEnd: string // e.g. ", range end"
348
+ inRange: string // e.g. ", in range"
349
+ // aria-live announcements on panel switch
350
+ monthPanelAnnouncement: string
351
+ yearPanelAnnouncement: string
352
+ // Month panel (jump nav)
353
+ prevYear: string
354
+ nextYear: string
355
+ monthPanelTitle: string
356
+ // Year panel (jump nav)
357
+ prevYearWindow: string
358
+ nextYearWindow: string
359
+ yearPanelTitle: string
360
+ }
361
+ ```
362
+
363
+ ## Theming
364
+
365
+ Override CSS custom properties on `.temporal-datepicker` or any ancestor:
366
+
367
+ ```css
368
+ /* Emerald theme */
369
+ .my-app .temporal-datepicker {
370
+ --tdp-accent: #10b981;
371
+ --tdp-accent-fg: #ffffff;
372
+ --tdp-today-text: #10b981;
373
+ --tdp-today-ring: #10b981;
374
+ }
375
+ ```
376
+
377
+ ### CSS tokens
378
+
379
+ | Token | Default (dark) | Description |
380
+ |---|---|---|
381
+ | `--tdp-accent` | `#38bdf8` | Accent color (selected day, focus ring, range edges). |
382
+ | `--tdp-accent-fg` | `#0f172a` | Foreground on accent. |
383
+ | `--tdp-bg` | `#1e293b` | Calendar background. |
384
+ | `--tdp-border` | `#334155` | Border color. |
385
+ | `--tdp-text` | `#e2e8f0` | Primary text. |
386
+ | `--tdp-text-muted` | `#64748b` | Muted text (weekday headers, week numbers). |
387
+ | `--tdp-day-hover-bg` | `rgba(255,255,255,0.06)` | Day hover background. |
388
+ | `--tdp-today-text` | `#38bdf8` | Today text color. |
389
+ | `--tdp-today-ring` | `#38bdf8` | Today indicator dot. |
390
+ | `--tdp-range-bg` | `rgba(56,189,248,0.15)` | Background for days inside a range. |
391
+ | `--tdp-range-edge-bg` | `var(--tdp-accent)` | Background for range start/end. |
392
+ | `--tdp-range-edge-text` | `var(--tdp-accent-fg)` | Foreground for range start/end. |
393
+ | `--tdp-disabled-text` | `#334155` | Disabled day text. |
394
+ | `--tdp-weeknum-text` | `var(--tdp-text-muted)` | Week number column text. |
395
+ | `--tdp-radius` | `12px` | Container border radius. |
396
+ | `--tdp-day-radius` | `8px` | Day button border radius. |
397
+ | `--tdp-day-size` | `36px` | Day button size. |
398
+ | `--tdp-shadow` | (dark shadow) | Container box shadow. |
399
+
400
+ The default theme is dark. Light mode is applied automatically via `@media (prefers-color-scheme: light)`.
401
+
402
+ ## Keyboard navigation
403
+
404
+ | Key | Action |
405
+ |---|---|
406
+ | `←` / `→` | Previous / next day |
407
+ | `↑` / `↓` | Same day, previous / next week |
408
+ | `Page Up` / `Page Down` | Previous / next month |
409
+ | `Home` | First day of the current week |
410
+ | `End` | Last day of the current week |
411
+ | `Enter` / `Space` | Select the focused date |
412
+ | `Tab` | Exit the calendar grid |
413
+
414
+ In month/year panels: `← → ↑ ↓` navigate between items, `Enter`/`Space` confirms, `Escape` returns to calendar.
415
+
416
+
417
+ ## Accessibility
418
+
419
+ - `role="grid"` on the calendar with `role="columnheader"` / `role="gridcell"` per cell.
420
+ - Full `aria-label` on every day button (e.g. "lunes, 17 de marzo de 2026").
421
+ - Range days include context: "inicio de rango", "fin de rango", "dentro del rango".
422
+ - Today marked with `aria-current="date"`.
423
+ - Selected dates use `aria-selected="true"`.
424
+ - Disabled days use the native `disabled` attribute.
425
+ - Panel switches announced via `aria-live="polite"`.
426
+ - All interactive elements expose `:focus-visible` styles.
427
+
428
+ ## Development
429
+
430
+ ```bash
431
+ pnpm install # install dependencies
432
+ pnpm dev # start demo app at http://localhost:5173
433
+ pnpm build # build library to /dist
434
+ pnpm type-check # TypeScript compiler check
435
+ pnpm lint # ESLint
436
+ ```
437
+
438
+ ## License
439
+
440
+ MIT
package/dist/index.css ADDED
@@ -0,0 +1,2 @@
1
+ .temporal-datepicker{--tdp-accent:#38bdf8;--tdp-accent-fg:#0f172a;--tdp-bg:#1e293b;--tdp-border:#334155;--tdp-text:#e2e8f0;--tdp-text-muted:#64748b;--tdp-day-hover-bg:#ffffff0f;--tdp-today-text:#38bdf8;--tdp-today-ring:#38bdf8;--tdp-selected-bg:var(--tdp-accent);--tdp-selected-text:var(--tdp-accent-fg);--tdp-range-bg:#38bdf826;--tdp-range-edge-bg:var(--tdp-accent);--tdp-range-edge-text:var(--tdp-accent-fg);--tdp-disabled-text:#334155;--tdp-disabled-bg:transparent;--tdp-weeknum-text:var(--tdp-text-muted);--tdp-panel-item-hover-bg:var(--tdp-day-hover-bg);--tdp-panel-item-selected-bg:var(--tdp-accent);--tdp-panel-item-selected-text:var(--tdp-accent-fg);--tdp-radius:12px;--tdp-day-radius:8px;--tdp-width:100%;--tdp-day-size:36px;--tdp-shadow:0 8px 32px #0006, 0 2px 8px #0000004d}@media (prefers-color-scheme:light){.temporal-datepicker{--tdp-bg:#fff;--tdp-border:#e2e8f0;--tdp-text:#0f172a;--tdp-text-muted:#94a3b8;--tdp-day-hover-bg:#f1f5f9;--tdp-accent:#0ea5e9;--tdp-accent-fg:#fff;--tdp-today-text:#0ea5e9;--tdp-today-ring:#0ea5e9;--tdp-shadow:0 4px 24px #00000014, 0 1px 4px #0000000f;--tdp-range-bg:#0ea5e91f;--tdp-disabled-text:#cbd5e1}}.temporal-datepicker{width:var(--tdp-width);background:var(--tdp-bg);border:1px solid var(--tdp-border);border-radius:var(--tdp-radius);color:var(--tdp-text);box-sizing:border-box;box-shadow:var(--tdp-shadow);padding:16px;font-family:inherit;font-size:14px;line-height:1;transition:border-color .15s}.tdp-header{justify-content:space-between;align-items:center;gap:8px;margin-bottom:14px;display:flex}.tdp-month-label{color:var(--tdp-text);text-align:center;letter-spacing:.015em;text-transform:capitalize;flex:1;font-size:13px;font-weight:600}.tdp-nav{border:1px solid var(--tdp-border);border-radius:calc(var(--tdp-day-radius) - 2px);width:28px;height:28px;color:var(--tdp-text-muted);cursor:pointer;background:0 0;flex-shrink:0;justify-content:center;align-items:center;padding:0;font-size:16px;line-height:1;transition:background .12s,color .12s,border-color .12s;display:inline-flex}.tdp-nav:hover{background:var(--tdp-day-hover-bg);color:var(--tdp-text);border-color:var(--tdp-text-muted)}.tdp-nav:focus-visible{outline:2px solid var(--tdp-accent);outline-offset:2px;border-color:#0000}.tdp-grid{border-collapse:collapse;table-layout:fixed;width:100%}.tdp-weekday{text-align:center;height:28px;color:var(--tdp-text-muted);text-transform:uppercase;letter-spacing:.07em;padding:0 0 4px;font-size:10px;font-weight:600}.tdp-cell{text-align:center;padding:2px}.tdp-day{width:var(--tdp-day-size);height:var(--tdp-day-size);border-radius:var(--tdp-day-radius);color:var(--tdp-text);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:0;font-family:inherit;font-size:13px;transition:background .1s,color .1s,transform .1s;display:inline-flex;position:relative}.tdp-day:hover:not(:disabled){background:var(--tdp-day-hover-bg);transform:scale(1.08)}.tdp-day:focus-visible{outline:2px solid var(--tdp-accent);outline-offset:1px}.tdp-day--today{color:var(--tdp-today-text);font-weight:700}.tdp-day--today:after{content:"";background:var(--tdp-today-ring);border-radius:50%;width:3px;height:3px;position:absolute;bottom:4px;left:50%;transform:translate(-50%)}.tdp-day--selected{background:var(--tdp-selected-bg);color:var(--tdp-selected-text);font-weight:600}.tdp-day--selected:hover{background:var(--tdp-selected-bg);opacity:.88;transform:scale(1.08)}.tdp-day--selected.tdp-day--today:after{background:var(--tdp-selected-text);opacity:.5}.tdp-day--range-start,.tdp-day--range-end{background:var(--tdp-range-edge-bg);color:var(--tdp-range-edge-text);font-weight:600}.tdp-day--range-start:hover:not(:disabled),.tdp-day--range-end:hover:not(:disabled){background:var(--tdp-range-edge-bg);opacity:.88;transform:scale(1.08)}.tdp-day--in-range{background:var(--tdp-range-bg);color:var(--tdp-text);border-radius:0}.tdp-day--in-range:hover:not(:disabled){background:var(--tdp-range-bg);transform:none}.tdp-day:disabled,.tdp-day--disabled{color:var(--tdp-disabled-text);background:var(--tdp-disabled-bg);cursor:not-allowed;opacity:.4;transform:none}.tdp-weeknum-header,.tdp-weeknum{color:var(--tdp-weeknum-text);text-align:right;-webkit-user-select:none;user-select:none;pointer-events:none;width:22px;padding-right:6px;font-size:10px;font-weight:500}.tdp-month-btn,.tdp-year-btn{color:var(--tdp-text);cursor:pointer;text-transform:capitalize;letter-spacing:.015em;background:0 0;border:none;border-radius:4px;padding:2px 4px;font-family:inherit;font-size:13px;font-weight:600;line-height:1;transition:background .12s,color .12s}.tdp-month-btn:hover,.tdp-year-btn:hover{background:var(--tdp-day-hover-bg);color:var(--tdp-text)}.tdp-month-btn:focus-visible,.tdp-year-btn:focus-visible{outline:2px solid var(--tdp-accent);outline-offset:2px}.tdp-sr-only{clip:rect(0, 0, 0, 0);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.tdp-panel-grid{flex-direction:column;gap:2px;margin-top:8px;display:flex}.tdp-panel-row{grid-template-columns:repeat(3,1fr);gap:2px;display:grid}.tdp-panel-item{border-radius:var(--tdp-day-radius);width:100%;color:var(--tdp-text);cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;padding:9px 4px;font-family:inherit;font-size:13px;transition:background .1s,color .1s;display:inline-flex}.tdp-panel-item:hover{background:var(--tdp-panel-item-hover-bg)}.tdp-panel-item:focus-visible{outline:2px solid var(--tdp-accent);outline-offset:2px}.tdp-panel-item--selected{background:var(--tdp-panel-item-selected-bg);color:var(--tdp-panel-item-selected-text);font-weight:600}.tdp-panel-item--selected:hover{background:var(--tdp-panel-item-selected-bg);opacity:.88}
2
+ /*$vite$:1*/
@@ -0,0 +1 @@
1
+ export {}
package/dist/index.js ADDED
@@ -0,0 +1,533 @@
1
+ import { useEffect as e, useRef as t, useState as n } from "react";
2
+ import { Temporal as r } from "@js-temporal/polyfill";
3
+ import { Fragment as i, jsx as a, jsxs as o } from "react/jsx-runtime";
4
+ //#region src/lib/panels/MonthPanel.tsx
5
+ function s(e) {
6
+ return Array.from({ length: 12 }, (t, n) => new Date(2024, n, 1).toLocaleString(e, { month: "short" }));
7
+ }
8
+ var c = ({ viewDate: r, locale: i, labels: c, onSelect: l, onClose: u }) => {
9
+ let [d, f] = n(r.year), [p, m] = n(r.month - 1), h = t(Array(12).fill(null)), g = s(i);
10
+ e(() => {
11
+ h.current[p]?.focus();
12
+ }, []);
13
+ function _(e) {
14
+ m(e), h.current[e]?.focus();
15
+ }
16
+ function v(e, t) {
17
+ switch (e.key) {
18
+ case "ArrowLeft":
19
+ e.preventDefault(), _((t - 1 + 12) % 12);
20
+ break;
21
+ case "ArrowRight":
22
+ e.preventDefault(), _((t + 1) % 12);
23
+ break;
24
+ case "ArrowUp":
25
+ e.preventDefault(), _((t - 3 + 12) % 12);
26
+ break;
27
+ case "ArrowDown":
28
+ e.preventDefault(), _((t + 3) % 12);
29
+ break;
30
+ case "Enter":
31
+ case " ":
32
+ e.preventDefault(), l(d, t + 1);
33
+ break;
34
+ case "Escape":
35
+ e.preventDefault(), u();
36
+ break;
37
+ }
38
+ }
39
+ return /* @__PURE__ */ o("div", {
40
+ className: "tdp-panel",
41
+ children: [/* @__PURE__ */ o("div", {
42
+ className: "tdp-header",
43
+ children: [
44
+ /* @__PURE__ */ a("button", {
45
+ type: "button",
46
+ className: "tdp-nav",
47
+ onClick: () => f((e) => e - 1),
48
+ "aria-label": c.prevYear,
49
+ children: "‹"
50
+ }),
51
+ /* @__PURE__ */ a("span", {
52
+ className: "tdp-month-label",
53
+ children: d
54
+ }),
55
+ /* @__PURE__ */ a("button", {
56
+ type: "button",
57
+ className: "tdp-nav",
58
+ onClick: () => f((e) => e + 1),
59
+ "aria-label": c.nextYear,
60
+ children: "›"
61
+ })
62
+ ]
63
+ }), /* @__PURE__ */ a("div", {
64
+ role: "grid",
65
+ className: "tdp-panel-grid",
66
+ "aria-label": c.monthPanelTitle,
67
+ children: [
68
+ 0,
69
+ 1,
70
+ 2,
71
+ 3
72
+ ].map((e) => /* @__PURE__ */ a("div", {
73
+ role: "row",
74
+ className: "tdp-panel-row",
75
+ children: [
76
+ 0,
77
+ 1,
78
+ 2
79
+ ].map((t) => {
80
+ let n = e * 3 + t, i = n + 1, o = d === r.year && i === r.month;
81
+ return /* @__PURE__ */ a("button", {
82
+ ref: (e) => {
83
+ h.current[n] = e;
84
+ },
85
+ role: "gridcell",
86
+ type: "button",
87
+ className: `tdp-panel-item${o ? " tdp-panel-item--selected" : ""}`,
88
+ tabIndex: p === n ? 0 : -1,
89
+ "aria-selected": o,
90
+ onClick: () => l(d, i),
91
+ onKeyDown: (e) => v(e, n),
92
+ onFocus: () => m(n),
93
+ children: g[n]
94
+ }, n);
95
+ })
96
+ }, e))
97
+ })]
98
+ });
99
+ }, l = ({ viewDate: r, labels: i, onSelect: s, onClose: c }) => {
100
+ let [l, u] = n(r.year - 5), [d, f] = n(Math.max(0, Math.min(11, r.year - (r.year - 5)))), p = t(Array(12).fill(null));
101
+ e(() => {
102
+ p.current[d]?.focus();
103
+ }, []);
104
+ function m(e) {
105
+ f(e), p.current[e]?.focus();
106
+ }
107
+ function h(e, t) {
108
+ switch (e.key) {
109
+ case "ArrowLeft":
110
+ e.preventDefault(), m((t - 1 + 12) % 12);
111
+ break;
112
+ case "ArrowRight":
113
+ e.preventDefault(), m((t + 1) % 12);
114
+ break;
115
+ case "ArrowUp":
116
+ e.preventDefault(), m((t - 3 + 12) % 12);
117
+ break;
118
+ case "ArrowDown":
119
+ e.preventDefault(), m((t + 3) % 12);
120
+ break;
121
+ case "Enter":
122
+ case " ":
123
+ e.preventDefault(), s(l + t);
124
+ break;
125
+ case "Escape":
126
+ e.preventDefault(), c();
127
+ break;
128
+ }
129
+ }
130
+ return /* @__PURE__ */ o("div", {
131
+ className: "tdp-panel",
132
+ children: [/* @__PURE__ */ o("div", {
133
+ className: "tdp-header",
134
+ children: [
135
+ /* @__PURE__ */ a("button", {
136
+ type: "button",
137
+ className: "tdp-nav",
138
+ onClick: () => u((e) => e - 12),
139
+ "aria-label": i.prevYearWindow,
140
+ children: "‹"
141
+ }),
142
+ /* @__PURE__ */ o("span", {
143
+ className: "tdp-month-label",
144
+ children: [
145
+ l,
146
+ " – ",
147
+ l + 11
148
+ ]
149
+ }),
150
+ /* @__PURE__ */ a("button", {
151
+ type: "button",
152
+ className: "tdp-nav",
153
+ onClick: () => u((e) => e + 12),
154
+ "aria-label": i.nextYearWindow,
155
+ children: "›"
156
+ })
157
+ ]
158
+ }), /* @__PURE__ */ a("div", {
159
+ role: "grid",
160
+ className: "tdp-panel-grid",
161
+ "aria-label": i.yearPanelTitle,
162
+ children: [
163
+ 0,
164
+ 1,
165
+ 2,
166
+ 3
167
+ ].map((e) => /* @__PURE__ */ a("div", {
168
+ role: "row",
169
+ className: "tdp-panel-row",
170
+ children: [
171
+ 0,
172
+ 1,
173
+ 2
174
+ ].map((t) => {
175
+ let n = e * 3 + t, i = l + n, o = i === r.year;
176
+ return /* @__PURE__ */ a("button", {
177
+ ref: (e) => {
178
+ p.current[n] = e;
179
+ },
180
+ role: "gridcell",
181
+ type: "button",
182
+ className: `tdp-panel-item${o ? " tdp-panel-item--selected" : ""}`,
183
+ tabIndex: d === n ? 0 : -1,
184
+ "aria-selected": o,
185
+ onClick: () => s(i),
186
+ onKeyDown: (e) => h(e, n),
187
+ onFocus: () => f(n),
188
+ children: i
189
+ }, n);
190
+ })
191
+ }, e))
192
+ })]
193
+ });
194
+ }, u = {
195
+ CALENDAR: "calendar",
196
+ MONTH: "month",
197
+ YEAR: "year"
198
+ };
199
+ function d() {
200
+ let [e, t] = n(u.CALENDAR);
201
+ return {
202
+ view: e,
203
+ setView: t
204
+ };
205
+ }
206
+ //#endregion
207
+ //#region src/lib/hooks/useDateRange.ts
208
+ function f(e, t, n) {
209
+ let [i, a] = r.PlainDate.compare(e, t) < 0 ? [e, t] : [t, e], o = i.add({ days: 1 });
210
+ for (; r.PlainDate.compare(o, a) < 0;) {
211
+ if (n(o)) return !0;
212
+ o = o.add({ days: 1 });
213
+ }
214
+ return !1;
215
+ }
216
+ function p(e, t, n) {
217
+ let i = r.PlainDate.compare(t, e) > 0, a = e;
218
+ for (let o = 1;; o++) {
219
+ let s = e.add({ days: i ? o : -o });
220
+ if (n(s)) return a;
221
+ if (r.PlainDate.compare(s, t) === 0) return t;
222
+ a = s;
223
+ }
224
+ }
225
+ function m(e) {
226
+ let [t, i] = n(null);
227
+ function a(t, n, i) {
228
+ if (e?.(t)) return;
229
+ if (!n || n.end !== null) {
230
+ i({
231
+ start: t,
232
+ end: null
233
+ });
234
+ return;
235
+ }
236
+ let [a, o] = r.PlainDate.compare(t, n.start) >= 0 ? [n.start, t] : [t, n.start];
237
+ e && f(a, o, e) || i({
238
+ start: a,
239
+ end: o
240
+ });
241
+ }
242
+ function o(n) {
243
+ return t ? e ? p(n, t, e) : t : null;
244
+ }
245
+ function s(e, t) {
246
+ if (!t) return {
247
+ isRangeStart: !1,
248
+ isRangeEnd: !1,
249
+ inRange: !1,
250
+ isPreview: !1
251
+ };
252
+ if (t.end !== null) return {
253
+ isRangeStart: r.PlainDate.compare(e, t.start) === 0,
254
+ isRangeEnd: r.PlainDate.compare(e, t.end) === 0,
255
+ inRange: r.PlainDate.compare(e, t.start) > 0 && r.PlainDate.compare(e, t.end) < 0,
256
+ isPreview: !1
257
+ };
258
+ let n = o(t.start);
259
+ if (!n) return {
260
+ isRangeStart: r.PlainDate.compare(e, t.start) === 0,
261
+ isRangeEnd: !1,
262
+ inRange: !1,
263
+ isPreview: !0
264
+ };
265
+ let [i, a] = r.PlainDate.compare(n, t.start) >= 0 ? [t.start, n] : [n, t.start];
266
+ return {
267
+ isRangeStart: r.PlainDate.compare(e, i) === 0,
268
+ isRangeEnd: r.PlainDate.compare(e, a) === 0,
269
+ inRange: r.PlainDate.compare(e, i) >= 0 && r.PlainDate.compare(e, a) <= 0,
270
+ isPreview: !0
271
+ };
272
+ }
273
+ return {
274
+ setHoverDate: i,
275
+ handleRangeClick: a,
276
+ getDayRangeState: s
277
+ };
278
+ }
279
+ //#endregion
280
+ //#region src/lib/temporal-date-picker.tsx
281
+ var h = {
282
+ prevMonth: "Previous month",
283
+ nextMonth: "Next month",
284
+ clearSelection: "Clear selection",
285
+ selectMonth: (e) => `Select month, currently ${e}`,
286
+ selectYear: (e) => `Select year, currently ${e}`,
287
+ weekNumberHeader: "Week",
288
+ weekNumber: (e) => `Week ${e}`,
289
+ rangeStart: ", range start",
290
+ rangeEnd: ", range end",
291
+ inRange: ", in range",
292
+ monthPanelAnnouncement: "Month selector",
293
+ yearPanelAnnouncement: "Year selector",
294
+ prevYear: "Previous year",
295
+ nextYear: "Next year",
296
+ monthPanelTitle: "Select month",
297
+ prevYearWindow: "Previous window",
298
+ nextYearWindow: "Next window",
299
+ yearPanelTitle: "Select year"
300
+ };
301
+ function g(e) {
302
+ let t = [];
303
+ for (let n = 1; n <= 7; n++) {
304
+ let r = new Date(2024, 0, n);
305
+ t.push({
306
+ short: r.toLocaleString(e, { weekday: "narrow" }),
307
+ long: r.toLocaleString(e, { weekday: "long" })
308
+ });
309
+ }
310
+ return t;
311
+ }
312
+ function _(e) {
313
+ let t = e.with({ day: 1 }), n = e.daysInMonth, r = t.dayOfWeek - 1, i = [...Array.from({ length: r }, () => null), ...Array.from({ length: n }, (e, n) => t.add({ days: n }))], a = [];
314
+ for (let e = 0; e < i.length; e += 7) {
315
+ let t = i.slice(e, e + 7);
316
+ for (; t.length < 7;) t.push(null);
317
+ a.push(t);
318
+ }
319
+ return a;
320
+ }
321
+ var v = (e) => {
322
+ let { className: s = "", locale: u, labels: f, renderDayContent: p, showWeekNumbers: v, isDateDisabled: y } = e, b = {
323
+ ...h,
324
+ ...f
325
+ }, x = u ?? (typeof navigator < "u" ? navigator.language : "en"), [S, C] = n(() => e.mode === "range" ? e.value?.start ?? r.Now.plainDateISO() : e.value ?? r.Now.plainDateISO()), [w, T] = n(null), E = t(null), { view: D, setView: O } = d(), { setHoverDate: k, handleRangeClick: A, getDayRangeState: j } = m(y), M = r.Now.plainDateISO(), N = (e) => r.PlainDate.compare(M, e) === 0, P = e.value !== void 0;
326
+ function F() {
327
+ e.clearable && e.onChange(void 0);
328
+ }
329
+ function I(t) {
330
+ e.mode === "range" ? A(t, e.value, e.onChange) : (e.onChange(t), T(t));
331
+ }
332
+ let L = (e) => {
333
+ T(e), (e.year !== S.year || e.month !== S.month) && C(e);
334
+ }, R = (t) => {
335
+ let n = e.mode === "range" ? void 0 : e.value, r = w ?? n ?? M;
336
+ switch (t.key) {
337
+ case "ArrowLeft":
338
+ t.preventDefault(), L(r.subtract({ days: 1 }));
339
+ break;
340
+ case "ArrowRight":
341
+ t.preventDefault(), L(r.add({ days: 1 }));
342
+ break;
343
+ case "ArrowUp":
344
+ t.preventDefault(), L(r.subtract({ weeks: 1 }));
345
+ break;
346
+ case "ArrowDown":
347
+ t.preventDefault(), L(r.add({ weeks: 1 }));
348
+ break;
349
+ case "Home":
350
+ t.preventDefault(), L(r.subtract({ days: r.dayOfWeek - 1 }));
351
+ break;
352
+ case "End":
353
+ t.preventDefault(), L(r.add({ days: 7 - r.dayOfWeek }));
354
+ break;
355
+ case "PageUp":
356
+ t.preventDefault(), L(r.subtract({ months: 1 }));
357
+ break;
358
+ case "PageDown":
359
+ t.preventDefault(), L(r.add({ months: 1 }));
360
+ break;
361
+ case "Enter":
362
+ case " ":
363
+ t.preventDefault(), w && I(w);
364
+ break;
365
+ default: break;
366
+ }
367
+ }, z = () => {
368
+ C((e) => e.subtract({ months: 1 })), T(null);
369
+ }, B = () => {
370
+ C((e) => e.add({ months: 1 })), T(null);
371
+ }, V = S.toLocaleString(x, {
372
+ month: "long",
373
+ year: "numeric"
374
+ }), H = S.toLocaleString(x, { month: "long" }), U = g(x), W = _(S), G = e.mode === "range" ? e.value?.start : void 0, K = w ?? (e.mode === "range" ? G : e.value) ?? M, q = D === "month" ? b.monthPanelAnnouncement : D === "year" ? b.yearPanelAnnouncement : V;
375
+ function J(t) {
376
+ if (e.mode === "range") {
377
+ let n = j(t, e.value);
378
+ return {
379
+ selected: !1,
380
+ inRange: n.inRange,
381
+ isRangeStart: n.isRangeStart,
382
+ isRangeEnd: n.isRangeEnd
383
+ };
384
+ }
385
+ return {
386
+ selected: e.value !== void 0 && r.PlainDate.compare(e.value, t) === 0,
387
+ inRange: !1,
388
+ isRangeStart: !1,
389
+ isRangeEnd: !1
390
+ };
391
+ }
392
+ function Y(e, t) {
393
+ let n = ["tdp-day"];
394
+ return t.selected && n.push("tdp-day--selected"), N(e) && n.push("tdp-day--today"), t.isRangeStart && n.push("tdp-day--range-start"), t.isRangeEnd && n.push("tdp-day--range-end"), t.inRange && !t.isRangeStart && !t.isRangeEnd && n.push("tdp-day--in-range"), n.join(" ");
395
+ }
396
+ return /* @__PURE__ */ o("div", {
397
+ className: `temporal-datepicker ${s}`.trim(),
398
+ children: [
399
+ /* @__PURE__ */ a("div", {
400
+ "aria-live": "polite",
401
+ "aria-atomic": "true",
402
+ className: "tdp-sr-only",
403
+ children: q
404
+ }),
405
+ D === "calendar" && /* @__PURE__ */ o(i, { children: [/* @__PURE__ */ o("div", {
406
+ className: "tdp-header",
407
+ children: [
408
+ /* @__PURE__ */ a("button", {
409
+ type: "button",
410
+ className: "tdp-nav",
411
+ onClick: z,
412
+ "aria-label": b.prevMonth,
413
+ children: "‹"
414
+ }),
415
+ /* @__PURE__ */ o("div", {
416
+ className: "tdp-month-label",
417
+ children: [/* @__PURE__ */ a("button", {
418
+ type: "button",
419
+ className: "tdp-month-btn",
420
+ onClick: () => O("month"),
421
+ "aria-label": b.selectMonth(H),
422
+ children: H
423
+ }), /* @__PURE__ */ a("button", {
424
+ type: "button",
425
+ className: "tdp-year-btn",
426
+ onClick: () => O("year"),
427
+ "aria-label": b.selectYear(S.year),
428
+ children: S.year
429
+ })]
430
+ }),
431
+ e.clearable && P && /* @__PURE__ */ a("button", {
432
+ type: "button",
433
+ className: "tdp-nav tdp-clear",
434
+ onClick: F,
435
+ "aria-label": b.clearSelection,
436
+ children: "×"
437
+ }),
438
+ /* @__PURE__ */ a("button", {
439
+ type: "button",
440
+ className: "tdp-nav",
441
+ onClick: B,
442
+ "aria-label": b.nextMonth,
443
+ children: "›"
444
+ })
445
+ ]
446
+ }), /* @__PURE__ */ o("table", {
447
+ ref: E,
448
+ role: "grid",
449
+ "aria-label": V,
450
+ className: "tdp-grid",
451
+ onKeyDown: R,
452
+ children: [/* @__PURE__ */ a("thead", { children: /* @__PURE__ */ o("tr", {
453
+ role: "row",
454
+ children: [v && /* @__PURE__ */ a("th", {
455
+ role: "columnheader",
456
+ scope: "col",
457
+ className: "tdp-weekday tdp-weeknum-header",
458
+ "aria-label": b.weekNumberHeader
459
+ }), U.map(({ short: e, long: t }) => /* @__PURE__ */ a("th", {
460
+ role: "columnheader",
461
+ scope: "col",
462
+ className: "tdp-weekday",
463
+ abbr: t,
464
+ children: e
465
+ }, t))]
466
+ }) }), /* @__PURE__ */ a("tbody", { children: W.map((t, n) => {
467
+ let i = t.find((e) => e !== null)?.weekOfYear;
468
+ return /* @__PURE__ */ o("tr", {
469
+ role: "row",
470
+ children: [v && /* @__PURE__ */ a("th", {
471
+ scope: "row",
472
+ className: "tdp-weeknum",
473
+ "aria-label": i == null ? b.weekNumberHeader : b.weekNumber(i),
474
+ children: i ?? ""
475
+ }), t.map((t, n) => {
476
+ if (!t) return /* @__PURE__ */ a("td", {
477
+ role: "gridcell",
478
+ className: "tdp-cell"
479
+ }, `empty-${n}`);
480
+ let i = y?.(t) ?? !1, o = J(t), s = N(t), c = r.PlainDate.compare(K, t) === 0, l = Y(t, o), u = t.toLocaleString(x, {
481
+ weekday: "long",
482
+ year: "numeric",
483
+ month: "long",
484
+ day: "numeric"
485
+ });
486
+ return o.isRangeStart && !o.isRangeEnd && (u += b.rangeStart), o.isRangeEnd && !o.isRangeStart && (u += b.rangeEnd), o.inRange && !o.isRangeStart && !o.isRangeEnd && (u += b.inRange), /* @__PURE__ */ a("td", {
487
+ role: "gridcell",
488
+ "aria-selected": o.selected || o.isRangeStart || o.isRangeEnd,
489
+ className: "tdp-cell",
490
+ children: /* @__PURE__ */ a("button", {
491
+ type: "button",
492
+ className: l,
493
+ onClick: () => I(t),
494
+ onFocus: () => T(t),
495
+ onMouseEnter: () => e.mode === "range" && k(t),
496
+ onMouseLeave: () => e.mode === "range" && k(null),
497
+ tabIndex: c ? 0 : -1,
498
+ disabled: i,
499
+ "aria-label": u,
500
+ "aria-current": s ? "date" : void 0,
501
+ "aria-pressed": e.mode === "range" ? void 0 : o.selected,
502
+ children: p ? p(t, o) : t.day
503
+ })
504
+ }, t.toString());
505
+ })]
506
+ }, n);
507
+ }) })]
508
+ })] }),
509
+ D === "month" && /* @__PURE__ */ a(c, {
510
+ viewDate: S,
511
+ locale: x,
512
+ labels: b,
513
+ onSelect: (e, t) => {
514
+ C(S.with({
515
+ year: e,
516
+ month: t
517
+ })), O("calendar");
518
+ },
519
+ onClose: () => O("calendar")
520
+ }),
521
+ D === "year" && /* @__PURE__ */ a(l, {
522
+ viewDate: S,
523
+ labels: b,
524
+ onSelect: (e) => {
525
+ C(S.with({ year: e })), O("calendar");
526
+ },
527
+ onClose: () => O("calendar")
528
+ })
529
+ ]
530
+ });
531
+ };
532
+ //#endregion
533
+ export { v as TemporalDatePicker };
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require(`react`),require(`@js-temporal/polyfill`),require(`react/jsx-runtime`)):typeof define==`function`&&define.amd?define([`exports`,`react`,`@js-temporal/polyfill`,`react/jsx-runtime`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.TemporalDatePicker={},e.React,e.Temporal,e.ReactJsxRuntime))})(this,function(e,t,n,r){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});function i(e){return Array.from({length:12},(t,n)=>new Date(2024,n,1).toLocaleString(e,{month:`short`}))}var a=({viewDate:e,locale:n,labels:a,onSelect:o,onClose:s})=>{let[c,l]=(0,t.useState)(e.year),[u,d]=(0,t.useState)(e.month-1),f=(0,t.useRef)(Array(12).fill(null)),p=i(n);(0,t.useEffect)(()=>{f.current[u]?.focus()},[]);function m(e){d(e),f.current[e]?.focus()}function h(e,t){switch(e.key){case`ArrowLeft`:e.preventDefault(),m((t-1+12)%12);break;case`ArrowRight`:e.preventDefault(),m((t+1)%12);break;case`ArrowUp`:e.preventDefault(),m((t-3+12)%12);break;case`ArrowDown`:e.preventDefault(),m((t+3)%12);break;case`Enter`:case` `:e.preventDefault(),o(c,t+1);break;case`Escape`:e.preventDefault(),s();break}}return(0,r.jsxs)(`div`,{className:`tdp-panel`,children:[(0,r.jsxs)(`div`,{className:`tdp-header`,children:[(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav`,onClick:()=>l(e=>e-1),"aria-label":a.prevYear,children:`‹`}),(0,r.jsx)(`span`,{className:`tdp-month-label`,children:c}),(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav`,onClick:()=>l(e=>e+1),"aria-label":a.nextYear,children:`›`})]}),(0,r.jsx)(`div`,{role:`grid`,className:`tdp-panel-grid`,"aria-label":a.monthPanelTitle,children:[0,1,2,3].map(t=>(0,r.jsx)(`div`,{role:`row`,className:`tdp-panel-row`,children:[0,1,2].map(n=>{let i=t*3+n,a=i+1,s=c===e.year&&a===e.month;return(0,r.jsx)(`button`,{ref:e=>{f.current[i]=e},role:`gridcell`,type:`button`,className:`tdp-panel-item${s?` tdp-panel-item--selected`:``}`,tabIndex:u===i?0:-1,"aria-selected":s,onClick:()=>o(c,a),onKeyDown:e=>h(e,i),onFocus:()=>d(i),children:p[i]},i)})},t))})]})},o=({viewDate:e,labels:n,onSelect:i,onClose:a})=>{let[o,s]=(0,t.useState)(e.year-5),[c,l]=(0,t.useState)(Math.max(0,Math.min(11,e.year-(e.year-5)))),u=(0,t.useRef)(Array(12).fill(null));(0,t.useEffect)(()=>{u.current[c]?.focus()},[]);function d(e){l(e),u.current[e]?.focus()}function f(e,t){switch(e.key){case`ArrowLeft`:e.preventDefault(),d((t-1+12)%12);break;case`ArrowRight`:e.preventDefault(),d((t+1)%12);break;case`ArrowUp`:e.preventDefault(),d((t-3+12)%12);break;case`ArrowDown`:e.preventDefault(),d((t+3)%12);break;case`Enter`:case` `:e.preventDefault(),i(o+t);break;case`Escape`:e.preventDefault(),a();break}}return(0,r.jsxs)(`div`,{className:`tdp-panel`,children:[(0,r.jsxs)(`div`,{className:`tdp-header`,children:[(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav`,onClick:()=>s(e=>e-12),"aria-label":n.prevYearWindow,children:`‹`}),(0,r.jsxs)(`span`,{className:`tdp-month-label`,children:[o,` – `,o+11]}),(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav`,onClick:()=>s(e=>e+12),"aria-label":n.nextYearWindow,children:`›`})]}),(0,r.jsx)(`div`,{role:`grid`,className:`tdp-panel-grid`,"aria-label":n.yearPanelTitle,children:[0,1,2,3].map(t=>(0,r.jsx)(`div`,{role:`row`,className:`tdp-panel-row`,children:[0,1,2].map(n=>{let a=t*3+n,s=o+a,d=s===e.year;return(0,r.jsx)(`button`,{ref:e=>{u.current[a]=e},role:`gridcell`,type:`button`,className:`tdp-panel-item${d?` tdp-panel-item--selected`:``}`,tabIndex:c===a?0:-1,"aria-selected":d,onClick:()=>i(s),onKeyDown:e=>f(e,a),onFocus:()=>l(a),children:s},a)})},t))})]})},s={CALENDAR:`calendar`,MONTH:`month`,YEAR:`year`};function c(){let[e,n]=(0,t.useState)(s.CALENDAR);return{view:e,setView:n}}function l(e,t,r){let[i,a]=n.Temporal.PlainDate.compare(e,t)<0?[e,t]:[t,e],o=i.add({days:1});for(;n.Temporal.PlainDate.compare(o,a)<0;){if(r(o))return!0;o=o.add({days:1})}return!1}function u(e,t,r){let i=n.Temporal.PlainDate.compare(t,e)>0,a=e;for(let o=1;;o++){let s=e.add({days:i?o:-o});if(r(s))return a;if(n.Temporal.PlainDate.compare(s,t)===0)return t;a=s}}function d(e){let[r,i]=(0,t.useState)(null);function a(t,r,i){if(e?.(t))return;if(!r||r.end!==null){i({start:t,end:null});return}let[a,o]=n.Temporal.PlainDate.compare(t,r.start)>=0?[r.start,t]:[t,r.start];e&&l(a,o,e)||i({start:a,end:o})}function o(t){return r?e?u(t,r,e):r:null}function s(e,t){if(!t)return{isRangeStart:!1,isRangeEnd:!1,inRange:!1,isPreview:!1};if(t.end!==null)return{isRangeStart:n.Temporal.PlainDate.compare(e,t.start)===0,isRangeEnd:n.Temporal.PlainDate.compare(e,t.end)===0,inRange:n.Temporal.PlainDate.compare(e,t.start)>0&&n.Temporal.PlainDate.compare(e,t.end)<0,isPreview:!1};let r=o(t.start);if(!r)return{isRangeStart:n.Temporal.PlainDate.compare(e,t.start)===0,isRangeEnd:!1,inRange:!1,isPreview:!0};let[i,a]=n.Temporal.PlainDate.compare(r,t.start)>=0?[t.start,r]:[r,t.start];return{isRangeStart:n.Temporal.PlainDate.compare(e,i)===0,isRangeEnd:n.Temporal.PlainDate.compare(e,a)===0,inRange:n.Temporal.PlainDate.compare(e,i)>=0&&n.Temporal.PlainDate.compare(e,a)<=0,isPreview:!0}}return{setHoverDate:i,handleRangeClick:a,getDayRangeState:s}}var f={prevMonth:`Previous month`,nextMonth:`Next month`,clearSelection:`Clear selection`,selectMonth:e=>`Select month, currently ${e}`,selectYear:e=>`Select year, currently ${e}`,weekNumberHeader:`Week`,weekNumber:e=>`Week ${e}`,rangeStart:`, range start`,rangeEnd:`, range end`,inRange:`, in range`,monthPanelAnnouncement:`Month selector`,yearPanelAnnouncement:`Year selector`,prevYear:`Previous year`,nextYear:`Next year`,monthPanelTitle:`Select month`,prevYearWindow:`Previous window`,nextYearWindow:`Next window`,yearPanelTitle:`Select year`};function p(e){let t=[];for(let n=1;n<=7;n++){let r=new Date(2024,0,n);t.push({short:r.toLocaleString(e,{weekday:`narrow`}),long:r.toLocaleString(e,{weekday:`long`})})}return t}function m(e){let t=e.with({day:1}),n=e.daysInMonth,r=t.dayOfWeek-1,i=[...Array.from({length:r},()=>null),...Array.from({length:n},(e,n)=>t.add({days:n}))],a=[];for(let e=0;e<i.length;e+=7){let t=i.slice(e,e+7);for(;t.length<7;)t.push(null);a.push(t)}return a}e.TemporalDatePicker=e=>{let{className:i=``,locale:s,labels:l,renderDayContent:u,showWeekNumbers:h,isDateDisabled:g}=e,_={...f,...l},v=s??(typeof navigator<`u`?navigator.language:`en`),[y,b]=(0,t.useState)(()=>e.mode===`range`?e.value?.start??n.Temporal.Now.plainDateISO():e.value??n.Temporal.Now.plainDateISO()),[x,S]=(0,t.useState)(null),C=(0,t.useRef)(null),{view:w,setView:T}=c(),{setHoverDate:E,handleRangeClick:D,getDayRangeState:O}=d(g),k=n.Temporal.Now.plainDateISO(),A=e=>n.Temporal.PlainDate.compare(k,e)===0,j=e.value!==void 0;function M(){e.clearable&&e.onChange(void 0)}function N(t){e.mode===`range`?D(t,e.value,e.onChange):(e.onChange(t),S(t))}let P=e=>{S(e),(e.year!==y.year||e.month!==y.month)&&b(e)},F=t=>{let n=e.mode===`range`?void 0:e.value,r=x??n??k;switch(t.key){case`ArrowLeft`:t.preventDefault(),P(r.subtract({days:1}));break;case`ArrowRight`:t.preventDefault(),P(r.add({days:1}));break;case`ArrowUp`:t.preventDefault(),P(r.subtract({weeks:1}));break;case`ArrowDown`:t.preventDefault(),P(r.add({weeks:1}));break;case`Home`:t.preventDefault(),P(r.subtract({days:r.dayOfWeek-1}));break;case`End`:t.preventDefault(),P(r.add({days:7-r.dayOfWeek}));break;case`PageUp`:t.preventDefault(),P(r.subtract({months:1}));break;case`PageDown`:t.preventDefault(),P(r.add({months:1}));break;case`Enter`:case` `:t.preventDefault(),x&&N(x);break;default:break}},I=()=>{b(e=>e.subtract({months:1})),S(null)},L=()=>{b(e=>e.add({months:1})),S(null)},R=y.toLocaleString(v,{month:`long`,year:`numeric`}),z=y.toLocaleString(v,{month:`long`}),B=p(v),V=m(y),H=e.mode===`range`?e.value?.start:void 0,U=x??(e.mode===`range`?H:e.value)??k,W=w===`month`?_.monthPanelAnnouncement:w===`year`?_.yearPanelAnnouncement:R;function G(t){if(e.mode===`range`){let n=O(t,e.value);return{selected:!1,inRange:n.inRange,isRangeStart:n.isRangeStart,isRangeEnd:n.isRangeEnd}}return{selected:e.value!==void 0&&n.Temporal.PlainDate.compare(e.value,t)===0,inRange:!1,isRangeStart:!1,isRangeEnd:!1}}function K(e,t){let n=[`tdp-day`];return t.selected&&n.push(`tdp-day--selected`),A(e)&&n.push(`tdp-day--today`),t.isRangeStart&&n.push(`tdp-day--range-start`),t.isRangeEnd&&n.push(`tdp-day--range-end`),t.inRange&&!t.isRangeStart&&!t.isRangeEnd&&n.push(`tdp-day--in-range`),n.join(` `)}return(0,r.jsxs)(`div`,{className:`temporal-datepicker ${i}`.trim(),children:[(0,r.jsx)(`div`,{"aria-live":`polite`,"aria-atomic":`true`,className:`tdp-sr-only`,children:W}),w===`calendar`&&(0,r.jsxs)(r.Fragment,{children:[(0,r.jsxs)(`div`,{className:`tdp-header`,children:[(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav`,onClick:I,"aria-label":_.prevMonth,children:`‹`}),(0,r.jsxs)(`div`,{className:`tdp-month-label`,children:[(0,r.jsx)(`button`,{type:`button`,className:`tdp-month-btn`,onClick:()=>T(`month`),"aria-label":_.selectMonth(z),children:z}),(0,r.jsx)(`button`,{type:`button`,className:`tdp-year-btn`,onClick:()=>T(`year`),"aria-label":_.selectYear(y.year),children:y.year})]}),e.clearable&&j&&(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav tdp-clear`,onClick:M,"aria-label":_.clearSelection,children:`×`}),(0,r.jsx)(`button`,{type:`button`,className:`tdp-nav`,onClick:L,"aria-label":_.nextMonth,children:`›`})]}),(0,r.jsxs)(`table`,{ref:C,role:`grid`,"aria-label":R,className:`tdp-grid`,onKeyDown:F,children:[(0,r.jsx)(`thead`,{children:(0,r.jsxs)(`tr`,{role:`row`,children:[h&&(0,r.jsx)(`th`,{role:`columnheader`,scope:`col`,className:`tdp-weekday tdp-weeknum-header`,"aria-label":_.weekNumberHeader}),B.map(({short:e,long:t})=>(0,r.jsx)(`th`,{role:`columnheader`,scope:`col`,className:`tdp-weekday`,abbr:t,children:e},t))]})}),(0,r.jsx)(`tbody`,{children:V.map((t,i)=>{let a=t.find(e=>e!==null)?.weekOfYear;return(0,r.jsxs)(`tr`,{role:`row`,children:[h&&(0,r.jsx)(`th`,{scope:`row`,className:`tdp-weeknum`,"aria-label":a==null?_.weekNumberHeader:_.weekNumber(a),children:a??``}),t.map((t,i)=>{if(!t)return(0,r.jsx)(`td`,{role:`gridcell`,className:`tdp-cell`},`empty-${i}`);let a=g?.(t)??!1,o=G(t),s=A(t),c=n.Temporal.PlainDate.compare(U,t)===0,l=K(t,o),d=t.toLocaleString(v,{weekday:`long`,year:`numeric`,month:`long`,day:`numeric`});return o.isRangeStart&&!o.isRangeEnd&&(d+=_.rangeStart),o.isRangeEnd&&!o.isRangeStart&&(d+=_.rangeEnd),o.inRange&&!o.isRangeStart&&!o.isRangeEnd&&(d+=_.inRange),(0,r.jsx)(`td`,{role:`gridcell`,"aria-selected":o.selected||o.isRangeStart||o.isRangeEnd,className:`tdp-cell`,children:(0,r.jsx)(`button`,{type:`button`,className:l,onClick:()=>N(t),onFocus:()=>S(t),onMouseEnter:()=>e.mode===`range`&&E(t),onMouseLeave:()=>e.mode===`range`&&E(null),tabIndex:c?0:-1,disabled:a,"aria-label":d,"aria-current":s?`date`:void 0,"aria-pressed":e.mode===`range`?void 0:o.selected,children:u?u(t,o):t.day})},t.toString())})]},i)})})]})]}),w===`month`&&(0,r.jsx)(a,{viewDate:y,locale:v,labels:_,onSelect:(e,t)=>{b(y.with({year:e,month:t})),T(`calendar`)},onClose:()=>T(`calendar`)}),w===`year`&&(0,r.jsx)(o,{viewDate:y,labels:_,onSelect:e=>{b(y.with({year:e})),T(`calendar`)},onClose:()=>T(`calendar`)})]})}});
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "temporal-react-datepicker",
3
+ "version": "1.0.0",
4
+ "description": "High-performance React date picker built on the Temporal API. Immutable dates, range selection, accessible.",
5
+ "keywords": [
6
+ "react",
7
+ "datepicker",
8
+ "date-picker",
9
+ "temporal",
10
+ "temporal-api",
11
+ "date-range",
12
+ "accessible",
13
+ "a11y"
14
+ ],
15
+ "license": "MIT",
16
+ "homepage": "https://github.com/PabloViniegra/temporal-react-datepicker#readme",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/PabloViniegra/temporal-react-datepicker.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/PabloViniegra/temporal-react-datepicker/issues"
23
+ },
24
+ "type": "module",
25
+ "main": "./dist/index.umd.cjs",
26
+ "module": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.umd.cjs",
32
+ "types": "./dist/index.d.ts"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "peerDependencies": {
39
+ "@js-temporal/polyfill": ">=0.5.1",
40
+ "react": ">=19.0.0 <20.0.0",
41
+ "react-dom": ">=19.0.0 <20.0.0"
42
+ },
43
+ "devDependencies": {
44
+ "@babel/core": "^7.29.0",
45
+ "@eslint/js": "^9.39.4",
46
+ "@js-temporal/polyfill": "^0.5.1",
47
+ "@rolldown/plugin-babel": "^0.2.0",
48
+ "@types/babel__core": "^7.20.5",
49
+ "@types/node": "^24.12.0",
50
+ "@types/react": "^19.2.14",
51
+ "@types/react-dom": "^19.2.3",
52
+ "@vitejs/plugin-react": "^6.0.0",
53
+ "babel-plugin-react-compiler": "^1.0.0",
54
+ "eslint": "^9.39.4",
55
+ "eslint-plugin-react-hooks": "^7.0.1",
56
+ "eslint-plugin-react-refresh": "^0.5.2",
57
+ "globals": "^17.4.0",
58
+ "react": "^19.2.4",
59
+ "react-dom": "^19.2.4",
60
+ "typescript": "~5.9.3",
61
+ "typescript-eslint": "^8.56.1",
62
+ "vite": "^8.0.0",
63
+ "vite-plugin-dts": "^4.5.4"
64
+ },
65
+ "scripts": {
66
+ "dev": "vite",
67
+ "build": "vite build",
68
+ "preview": "vite preview",
69
+ "lint": "eslint .",
70
+ "type-check": "tsc --noEmit"
71
+ }
72
+ }