universal-datetime-picker 2.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) 2022-2026 Raghav Bhardwaj
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,452 @@
1
+ [![npm](https://img.shields.io/npm/v/universal-datetime-picker.svg)](https://www.npmjs.com/package/universal-datetime-picker)
2
+ [![npm downloads](https://img.shields.io/npm/dm/universal-datetime-picker.svg)](https://www.npmjs.com/package/universal-datetime-picker)
3
+ [![types](https://img.shields.io/npm/types/universal-datetime-picker.svg)](https://www.npmjs.com/package/universal-datetime-picker)
4
+ [![CI](https://github.com/Bhardwaj-Raghav/universal-datetime-picker/actions/workflows/ci.yml/badge.svg)](https://github.com/Bhardwaj-Raghav/universal-datetime-picker/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/npm/l/universal-datetime-picker.svg)](https://github.com/Bhardwaj-Raghav/universal-datetime-picker/blob/main/LICENSE)
6
+
7
+ # universal-datetime-picker
8
+
9
+ **Date picker, time picker, datetime picker, and date range calendar** — one accessible TypeScript package. The [docs site](https://universal-datetime-picker.vercel.app) home uses **vanilla JS**; **React, Vue, Svelte, Angular, and CDN** each have dedicated demo pages.
10
+
11
+ Native React components share a headless core with a vanilla DOM renderer and Web Components (`<datetime-picker>`, `<datetime-picker-input>`, `<datetime-picker-range>`).
12
+
13
+ - **Live demos:** [universal-datetime-picker.vercel.app](https://universal-datetime-picker.vercel.app) — home page uses the **vanilla** `createDateTimePicker` API; per-framework pages: [`/react/`](https://universal-datetime-picker.vercel.app/react/), [`/vue/`](https://universal-datetime-picker.vercel.app/vue/), [`/svelte/`](https://universal-datetime-picker.vercel.app/svelte/), [`/angular/`](https://universal-datetime-picker.vercel.app/angular/), [`/vanilla/`](https://universal-datetime-picker.vercel.app/vanilla/), [`/web-components/`](https://universal-datetime-picker.vercel.app/web-components/), [`/cdn/`](https://universal-datetime-picker.vercel.app/cdn/), [`/solid/`](https://universal-datetime-picker.vercel.app/solid/), [`/preact/`](https://universal-datetime-picker.vercel.app/preact/), [`/nextjs/`](https://universal-datetime-picker.vercel.app/nextjs/), [`/nuxt/`](https://universal-datetime-picker.vercel.app/nuxt/)
14
+ - **npm:** [universal-datetime-picker](https://www.npmjs.com/package/universal-datetime-picker)
15
+ - **GitHub:** [Bhardwaj-Raghav/universal-datetime-picker](https://github.com/Bhardwaj-Raghav/universal-datetime-picker)
16
+
17
+ ## Features
18
+
19
+ - React (native), Vue / Svelte / Angular (via Web Components), vanilla JS, and CDN
20
+ - Date picker, time picker, and combined datetime picker
21
+ - Date range picker with keyboard-friendly calendar grid
22
+ - Input + popover or fully inline calendar modes
23
+ - TypeScript types, ESM/CJS builds, React 18+ (optional peer)
24
+ - Accessible UI (dialog, focus trap, arrow-key navigation)
25
+ - Themable via CSS variables; dark theme support
26
+ - Locales via dayjs; custom labels; 12-hour AM/PM mode
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ npm install universal-datetime-picker
32
+ ```
33
+
34
+ ```bash
35
+ yarn add universal-datetime-picker
36
+ ```
37
+
38
+ ```bash
39
+ pnpm add universal-datetime-picker
40
+ ```
41
+
42
+ For React apps, peer dependencies are `react` and `react-dom` (≥ 18). They are **optional** if you only use vanilla, Web Components, or CDN.
43
+
44
+ ## Package entry points
45
+
46
+ | Import | Use for |
47
+ |--------|---------|
48
+ | `universal-datetime-picker` | React components (`DateTime`, `DateTimeInput`, `DateTimeRange`) |
49
+ | `universal-datetime-picker/vanilla` | `createDateTimePicker` / `createDateTimeRangePicker` |
50
+ | `universal-datetime-picker/wc` | `defineCustomElements()` + custom element classes |
51
+ | `universal-datetime-picker/vue` | Registers elements for Vue |
52
+ | `universal-datetime-picker/svelte` | Registers elements / Svelte action |
53
+ | `universal-datetime-picker/angular` | `registerDateTimePickerElements()` |
54
+ | `universal-datetime-picker/core` | Headless controllers + date logic |
55
+ | `universal-datetime-picker/style.css` | Shared stylesheet |
56
+
57
+ ## Screenshots
58
+
59
+ | Date only | Time only |
60
+ |-----------|-----------|
61
+ | ![Date picker calendar](examples/date-only.png) | ![Time picker with hours, minutes, seconds](examples/time-only.png) |
62
+
63
+ | Combined date & time | Separate view (tabs) |
64
+ |----------------------|----------------------|
65
+ | ![Datetime picker with calendar and time panel](examples/date-time-combined.png) | ![Datetime picker with Date and Time tabs](examples/date-time-split.png) |
66
+
67
+ | Date range | French locale · week starts Monday |
68
+ |------------|------------------------------------|
69
+ | ![Date range picker with start and end selection](examples/date-range.png) | ![Localized calendar in French starting on Monday](examples/locale-date.png) |
70
+
71
+ | Dark theme | Popover input |
72
+ |------------|---------------|
73
+ | ![Date time picker in dark theme](examples/dark-mode.png) | ![Datetime input that opens a popover picker](examples/input.png) |
74
+
75
+ ## Quick start (React)
76
+
77
+ ```tsx
78
+ import { useState } from "react";
79
+ import DateTime, { DateTimeInput } from "universal-datetime-picker";
80
+ import "universal-datetime-picker/style.css";
81
+
82
+ function App() {
83
+ // Prefer asString={false} for Date / TimeValue (recommended going forward)
84
+ const [value, setValue] = useState<Date | null>(null);
85
+
86
+ return (
87
+ <>
88
+ <DateTimeInput asString={false} value={value} onChange={setValue} />
89
+ <DateTime inline asString={false} value={value} onChange={setValue} />
90
+ </>
91
+ );
92
+ }
93
+ ```
94
+
95
+ ## Vanilla JS
96
+
97
+ ```ts
98
+ import { createDateTimePicker } from "universal-datetime-picker/vanilla";
99
+ import "universal-datetime-picker/style.css";
100
+
101
+ const root = document.getElementById("picker")!;
102
+ const handle = createDateTimePicker(root, {
103
+ inline: true,
104
+ mode: "date",
105
+ asString: false,
106
+ onChange: (value) => console.log(value),
107
+ });
108
+
109
+ // later: handle.update({ open: false }); handle.destroy();
110
+ ```
111
+
112
+ ## CDN / Web Components
113
+
114
+ ```html
115
+ <link
116
+ rel="stylesheet"
117
+ href="https://cdn.jsdelivr.net/npm/universal-datetime-picker/style.css"
118
+ />
119
+ <script src="https://cdn.jsdelivr.net/npm/universal-datetime-picker"></script>
120
+
121
+ <datetime-picker inline mode="date" as-string="false"></datetime-picker>
122
+ <script>
123
+ document
124
+ .querySelector("datetime-picker")
125
+ .addEventListener("change", (e) => console.log(e.detail));
126
+ </script>
127
+ ```
128
+
129
+ Or register from npm:
130
+
131
+ ```ts
132
+ import { defineCustomElements } from "universal-datetime-picker/wc";
133
+ import "universal-datetime-picker/style.css";
134
+ defineCustomElements();
135
+ ```
136
+
137
+ Custom elements: `<datetime-picker>`, `<datetime-picker-input>`, `<datetime-picker-range>`. They dispatch a bubbling `change` `CustomEvent` whose `detail` is the picker value.
138
+
139
+ ## Vue
140
+
141
+ Register once in `main.ts`, then use tags in SFCs. Configure Vue so `datetime-picker*` tags are treated as custom elements.
142
+
143
+ ```ts
144
+ // main.ts
145
+ import { createApp } from "vue";
146
+ import App from "./App.vue";
147
+ import { defineCustomElements } from "universal-datetime-picker/vue";
148
+ import "universal-datetime-picker/style.css";
149
+
150
+ defineCustomElements();
151
+
152
+ createApp(App)
153
+ .config.compilerOptions.isCustomElement = (tag) =>
154
+ tag.startsWith("datetime-picker")
155
+ .mount("#app");
156
+ ```
157
+
158
+ ```vue
159
+ <template>
160
+ <datetime-picker
161
+ inline
162
+ mode="date"
163
+ as-string="false"
164
+ @change="onChange"
165
+ />
166
+ </template>
167
+ ```
168
+
169
+ More examples: [Vue demo](https://universal-datetime-picker.vercel.app/vue/) on the docs site.
170
+
171
+ ## Svelte
172
+
173
+ Register once in root `+layout.ts` (or app entry), not in every component.
174
+
175
+ ```ts
176
+ // src/routes/+layout.ts
177
+ import { defineCustomElements } from "universal-datetime-picker/svelte";
178
+ import "universal-datetime-picker/style.css";
179
+
180
+ defineCustomElements();
181
+ ```
182
+
183
+ ```svelte
184
+ <datetime-picker
185
+ inline
186
+ mode="date"
187
+ as-string="false"
188
+ on:change={(e) => console.log(e.detail)}
189
+ />
190
+ ```
191
+
192
+ More examples: [Svelte demo](https://universal-datetime-picker.vercel.app/svelte/) on the docs site.
193
+
194
+ ## Angular
195
+
196
+ ```ts
197
+ import { registerDateTimePickerElements } from "universal-datetime-picker/angular";
198
+ import "universal-datetime-picker/style.css";
199
+
200
+ registerDateTimePickerElements();
201
+ ```
202
+
203
+ Add `CUSTOM_ELEMENTS_SCHEMA` to your NgModule or standalone component, then use `<datetime-picker>`, `<datetime-picker-input>`, or `<datetime-picker-range>` in templates. Listen with `(change)="onChange($event.detail)"`.
204
+
205
+ ## Components
206
+
207
+ | Export | Description |
208
+ |--------|-------------|
209
+ | `DateTime` | React datetime / date / time picker (overlay or inline) |
210
+ | `DateTime.Input` / `DateTimeInput` | Date time input that opens a popover calendar |
211
+ | `DateTime.Range` / `DateTimeRange` | React date range picker |
212
+
213
+ ## Return values
214
+
215
+ `onChange` shape depends on `mode` and `asString`:
216
+
217
+ | Mode / flags | `asString` | `onChange` receives |
218
+ |--------------|------------|---------------------|
219
+ | `mode="date"` | `false` | `Date` (start of day) |
220
+ | `mode="datetime"` | `false` | `Date` |
221
+ | `mode="time"` | `false` | `TimeValue` object |
222
+ | any mode | `true` / omitted | formatted `string \| null` |
223
+ | range | `false` | `{ start: Date \| null; end: Date \| null }` |
224
+ | range | `true` / omitted | `{ start: string \| null; end: string \| null }` |
225
+
226
+ **Deprecation:** omitting `asString` still returns a formatted string today, but logs a one-time console warning. Set `asString={true}` to keep strings explicitly, or `asString={false}` to opt into `Date` / `TimeValue` now. A future major release will default to objects.
227
+
228
+ ### `TimeValue` (`mode="time"`, `asString={false}`)
229
+
230
+ ```ts
231
+ {
232
+ hour: 2, // 1–12
233
+ hour24: 14, // 0–23
234
+ minute: 30,
235
+ second: 0,
236
+ ampm: "PM",
237
+ formatted: "14:30:00" // or "02:30:00 PM" when use12Hours
238
+ }
239
+ ```
240
+
241
+ ```tsx
242
+ <DateTime
243
+ inline
244
+ mode="time"
245
+ asString={false}
246
+ onChange={(value) => {
247
+ // value is TimeValue | null
248
+ console.log(value?.hour24, value?.formatted);
249
+ }}
250
+ />
251
+ ```
252
+
253
+ ## Props
254
+
255
+ ### Shared (`DateTime` / `DateTimeInput`)
256
+
257
+ | Prop | Type | Default | Description |
258
+ |------|------|---------|-------------|
259
+ | `value` | `Date \| string \| Dayjs \| null` | — | Controlled value |
260
+ | `defaultValue` | same | — | Uncontrolled initial value |
261
+ | `onChange` | `(value: Date \| TimeValue \| string \| null) => void` | — | Fired on OK / Clear |
262
+ | `asString` | `boolean` | omitted → string (deprecated) | `true` = string; `false` = `Date` / `TimeValue` |
263
+ | `showSeconds` | `boolean` | `true` | Show seconds column; included in default format |
264
+ | `format` | `string` | derived from mode | dayjs format (auto from mode / `use12Hours` / `showSeconds` when omitted) |
265
+ | `mode` | `"datetime" \| "date" \| "time"` | `"datetime"` | Picker mode |
266
+ | `layout` | `"combined" \| "tabs"` | `"combined"` | When `mode="datetime"`: show both panels, or Date/Time tabs. Hidden for date-only / time-only |
267
+ | `minDate` / `maxDate` | date-like | — | Inclusive bounds |
268
+ | `disablePastDates` | `boolean` | `false` | Disable days before today |
269
+ | `disableFutureDates` | `boolean` | `false` | Disable days after today |
270
+ | `weekStartsOn` | `0–6` | `0` | First day of week (0 = Sunday) |
271
+ | `use12Hours` | `boolean` | `false` | 12-hour clock with AM/PM (`false` = 24-hour) |
272
+ | `locale` | `string` | `"en"` | dayjs locale (import locale first) |
273
+ | `labels` | `DateTimeLabels` | English defaults | Override chrome strings |
274
+ | `theme` | `"light" \| "dark"` | — | Force theme (useful for portaled popovers) |
275
+ | `inline` | `boolean` | `false` | Render without overlay |
276
+ | `className` | `string` | — | Root class |
277
+
278
+ ### Overlay control
279
+
280
+ | Prop | Type | Description |
281
+ |------|------|-------------|
282
+ | `open` / `defaultOpen` | `boolean` | Controlled / uncontrolled open state |
283
+ | `onOpenChange` | `(open: boolean) => void` | Open state changes |
284
+ | `popover` | `boolean` | Position near `anchorEl` instead of fullscreen |
285
+ | `anchorEl` | `HTMLElement \| null` | Anchor for popover |
286
+
287
+ `DateTimeInput` always uses popover mode. The popover uses `position: fixed`, flips above the input when needed, repositions on scroll/resize, and closes on outside click or Escape. Time-only popovers use a compact width.
288
+
289
+ ### Use any button or input as the trigger
290
+
291
+ Control `open` yourself to open the picker from any element. Add `popover` and pass the trigger element through `anchorEl` to position the picker beside it.
292
+
293
+ ```tsx
294
+ import { useState } from "react";
295
+ import { DateTime } from "universal-datetime-picker";
296
+
297
+ function CustomDateTrigger() {
298
+ const [open, setOpen] = useState(false);
299
+ const [value, setValue] = useState<Date | null>(null);
300
+ const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
301
+
302
+ return (
303
+ <>
304
+ <button
305
+ ref={setAnchorEl}
306
+ type="button"
307
+ onClick={() => setOpen(true)}
308
+ aria-haspopup="dialog"
309
+ aria-expanded={open}
310
+ >
311
+ {value ? value.toLocaleDateString() : "Choose a date"}
312
+ </button>
313
+
314
+ <DateTime
315
+ mode="date"
316
+ open={open}
317
+ onOpenChange={setOpen}
318
+ popover
319
+ anchorEl={anchorEl}
320
+ asString={false}
321
+ value={value}
322
+ onChange={(next) => setValue(next instanceof Date ? next : null)}
323
+ />
324
+ </>
325
+ );
326
+ }
327
+ ```
328
+
329
+ Leave out `popover` and `anchorEl` to open the same picker as a centered modal:
330
+
331
+ ```tsx
332
+ <button type="button" onClick={() => setOpen(true)}>
333
+ Choose a date
334
+ </button>
335
+ <DateTime
336
+ mode="date"
337
+ open={open}
338
+ onOpenChange={setOpen}
339
+ asString={false}
340
+ onChange={setValue}
341
+ />
342
+ ```
343
+
344
+ ### `DateTimeInput` extras
345
+
346
+ `placeholder`, `id`, `name`, `disabled`, `readOnly`, `aria-label`, `aria-labelledby`, `inputClassName`
347
+
348
+ ### `DateTimeRange`
349
+
350
+ Supports `asString` like the single picker. With `asString={false}`, `onChange` receives `{ start: Date | null; end: Date | null }`; with `asString={true}` (or omitted), ends are formatted strings. Also supports keyboard grid navigation, hover range preview, and the same `locale` / `weekStartsOn` / `labels` props.
351
+
352
+ ### Labels
353
+
354
+ ```tsx
355
+ <DateTime
356
+ inline
357
+ labels={{ ok: "Confirm", clear: "Wipe", close: "Dismiss", date: "Jour" }}
358
+ />
359
+ ```
360
+
361
+ ### Layout
362
+
363
+ By default (`layout="combined"`), datetime mode shows the calendar and time controls together — no Date/Time badges. Use `layout="tabs"` for the classic switcher. When `mode` is `"date"` or `"time"`, the badges are never shown.
364
+
365
+ ```tsx
366
+ {/* Default: both panels */}
367
+ <DateTime inline mode="datetime" asString={false} onChange={setValue} />
368
+
369
+ {/* Classic tabs */}
370
+ <DateTime inline mode="datetime" layout="tabs" asString={false} onChange={setValue} />
371
+
372
+ {/* Date only — no badges */}
373
+ <DateTime inline mode="date" asString={false} onChange={setValue} />
374
+ ```
375
+
376
+ ## 12-hour clock & seconds
377
+
378
+ `use12Hours` switches the time UI to AM/PM (`false` keeps 24-hour). When `format` is omitted, it is derived from `mode`, `use12Hours`, and `showSeconds`:
379
+
380
+ ```tsx
381
+ {/* 12-hour with seconds */}
382
+ <DateTimeInput asString use12Hours value={value} onChange={setValue} />
383
+
384
+ {/* 24-hour, no seconds */}
385
+ <DateTime inline mode="time" showSeconds={false} asString={false} onChange={setTime} />
386
+ ```
387
+
388
+ ## Theming
389
+
390
+ Override CSS variables (light defaults shown):
391
+
392
+ ```css
393
+ :root {
394
+ --ctp-primary: #7cb342;
395
+ --ctp-primary-dark: #558b2f;
396
+ --ctp-surface: #ffffff;
397
+ --ctp-fg: #1f2937;
398
+ --ctp-border: #e5e7eb;
399
+ --ctp-focus: #7cb342;
400
+ --ctp-danger: #dc2626;
401
+ --ctp-selected-bg: #558b2f;
402
+ --ctp-selected-fg: #ffffff;
403
+ --ctp-hover-bg: color-mix(in srgb, var(--ctp-primary) 16%, white);
404
+ --ctp-range-bg: color-mix(in srgb, var(--ctp-primary) 28%, white);
405
+ --ctp-z-index: 1000;
406
+ }
407
+ ```
408
+
409
+ ### Dark theme
410
+
411
+ Wrap the picker (or a parent) with `data-ctp-theme="dark"`:
412
+
413
+ ```tsx
414
+ <div data-ctp-theme="dark">
415
+ <DateTime inline mode="time" onChange={setValue} />
416
+ <DateTimeInput mode="time" onChange={setValue} />
417
+ </div>
418
+ ```
419
+
420
+ Inline pickers inherit theme from the wrapper. Portaled popovers/overlays copy `data-ctp-theme` from the input’s ancestors (or accept an explicit `theme="dark"` prop) so time and datetime popovers stay dark too.
421
+
422
+ Focusable controls use `:focus-visible` rings via `--ctp-focus`. Open animation respects `prefers-reduced-motion`.
423
+
424
+ ## Locales
425
+
426
+ Locales are applied per instance (no global dayjs locale mutation). Import the dayjs locale module before use:
427
+
428
+ ```tsx
429
+ import "dayjs/locale/fr";
430
+ import { DateTime } from "universal-datetime-picker";
431
+
432
+ <DateTime locale="fr" weekStartsOn={1} inline onChange={console.log} />
433
+ ```
434
+
435
+ ## Migrating from react-calendar-time
436
+
437
+ The package was renamed to **universal-datetime-picker** in v2.0.0.
438
+
439
+ 1. Update your dependency: `npm install universal-datetime-picker` (remove `react-calendar-time`).
440
+ 2. Change import specifiers from `react-calendar-time` to `universal-datetime-picker` (including subpaths like `/vanilla`, `/wc`, `/style.css`).
441
+ 3. Web Components are now `<datetime-picker>`, `<datetime-picker-input>`, and `<datetime-picker-range>` (previously `<calendar-time>` etc.).
442
+ 4. React **17** is no longer supported; use React **18+** or use vanilla / Web Components / CDN without React peers.
443
+
444
+ Props, CSS class names (`ctp-*`), and React component exports are unchanged.
445
+
446
+ ## Contributing
447
+
448
+ Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, scripts, and PR guidelines.
449
+
450
+ ## License
451
+
452
+ MIT
@@ -0,0 +1,15 @@
1
+ export { defineCustomElements } from '../wc/index.mjs';
2
+ import '../rangeRenderer-AVv6PJbW.mjs';
3
+ import '../rangeController-Bq35aBwe.mjs';
4
+ import 'dayjs';
5
+
6
+ /**
7
+ * Call once (e.g. in AppModule or main.ts) and add CUSTOM_ELEMENTS_SCHEMA
8
+ * so Angular templates can use <datetime-picker>, <datetime-picker-input>,
9
+ * and <datetime-picker-range>.
10
+ */
11
+ declare function registerDateTimePickerElements(): void;
12
+ /** @deprecated Use registerDateTimePickerElements */
13
+ declare function registerCalendarTimeElements(): void;
14
+
15
+ export { registerCalendarTimeElements, registerDateTimePickerElements };
@@ -0,0 +1,15 @@
1
+ export { defineCustomElements } from '../wc/index.js';
2
+ import '../rangeRenderer-a3hFk-kK.js';
3
+ import '../rangeController-Bq35aBwe.js';
4
+ import 'dayjs';
5
+
6
+ /**
7
+ * Call once (e.g. in AppModule or main.ts) and add CUSTOM_ELEMENTS_SCHEMA
8
+ * so Angular templates can use <datetime-picker>, <datetime-picker-input>,
9
+ * and <datetime-picker-range>.
10
+ */
11
+ declare function registerDateTimePickerElements(): void;
12
+ /** @deprecated Use registerDateTimePickerElements */
13
+ declare function registerCalendarTimeElements(): void;
14
+
15
+ export { registerCalendarTimeElements, registerDateTimePickerElements };