timepicker-ui 3.1.3 → 4.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) pglejzer
3
+ Copyright (c) 2025 pglejzer (Piotr Glejzer)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -8,11 +8,12 @@ Modern time picker library built with TypeScript. Works with any framework or va
8
8
 
9
9
  [Live Demo](https://timepicker-ui.vercel.app/) • [Documentation](https://timepicker-ui.vercel.app/docs) • [Changelog](./CHANGELOG.md)
10
10
 
11
- **Upgrading from v2?** Check the [upgrade guide](#upgrade-guide-v2--v3) below.
11
+ **Upgrading from v3?** Check the [upgrade guide](#upgrade-guide-v3--v4) below.
12
+ **Upgrading from v2?** Check the [v2 → v3 upgrade guide](#upgrade-guide-v2--v3).
12
13
 
13
14
  ## Features
14
15
 
15
- - 9 built-in themes (Material, Crane, Dark, Glassmorphic, Cyberpunk, and more)
16
+ - 10 built-in themes (Material, Crane, Dark, Glassmorphic, Cyberpunk, and more)
16
17
  - Mobile-first design with touch support
17
18
  - Framework agnostic - works with React, Vue, Angular, Svelte, or vanilla JS
18
19
  - Full TypeScript support
@@ -25,10 +26,9 @@ Modern time picker library built with TypeScript. Works with any framework or va
25
26
 
26
27
  This project is actively maintained. Some areas planned for improvement:
27
28
 
28
- - No unit/integration tests yet
29
- - Some files need refactoring
30
- - A few `any` types remain in the codebase
31
- - No performance monitoring
29
+ - Unit/integration test coverage could be expanded
30
+ - Performance monitoring tooling
31
+ - Further TypeScript strictness improvements
32
32
 
33
33
  Contributions welcome! Feel free to [open an issue or PR](https://github.com/pglejzer/timepicker-ui/issues).
34
34
 
@@ -73,12 +73,18 @@ picker.create();
73
73
 
74
74
  ```javascript
75
75
  const picker = new TimepickerUI(input, {
76
- theme: "dark",
77
- clockType: "24h",
78
- animation: true,
79
- backdrop: true,
80
- onConfirm: (data) => {
81
- console.log("Selected time:", data);
76
+ ui: {
77
+ theme: "dark",
78
+ animation: true,
79
+ backdrop: true,
80
+ },
81
+ clock: {
82
+ type: "24h",
83
+ },
84
+ callbacks: {
85
+ onConfirm: (data) => {
86
+ console.log("Selected time:", data);
87
+ },
82
88
  },
83
89
  });
84
90
  picker.create();
@@ -97,8 +103,9 @@ function TimePickerComponent() {
97
103
  useEffect(() => {
98
104
  if (inputRef.current) {
99
105
  const picker = new TimepickerUI(inputRef.current, {
100
- onConfirm: (data) => {
101
- console.log("Time selected:", data);
106
+ callbacks: {
107
+ onConfirm: (data) => {
108
+ console.log("Time selected:", data);
102
109
  },
103
110
  });
104
111
  picker.create();
@@ -115,42 +122,174 @@ function TimePickerComponent() {
115
122
 
116
123
  Full documentation available at [timepicker-ui.vercel.app/docs](https://timepicker-ui.vercel.app/docs)
117
124
 
118
- ### Key Options
119
-
120
- | Option | Type | Default | Description |
121
- | -------------------------------- | ------------- | ----------- | --------------------------------- |
122
- | `clockType` | `12h` / `24h` | `12h` | Clock format |
123
- | `theme` | string | `basic` | UI theme (9 themes available) |
124
- | `animation` | boolean | `true` | Enable animations |
125
- | `backdrop` | boolean | `true` | Show backdrop overlay |
126
- | `editable` | boolean | `false` | Allow manual input editing |
127
- | `mobile` | boolean | `false` | Force mobile version |
128
- | `disabledTime` | object | `undefined` | Disable specific hours/minutes |
129
- | `incrementHours` | number | `1` | Hour increment step |
130
- | `incrementMinutes` | number | `1` | Minute increment step |
131
- | `switchToMinutesAfterSelectHour` | boolean | `true` | Auto-switch to minutes after hour |
132
- | `okLabel` | string | `OK` | OK button text |
133
- | `cancelLabel` | string | `CANCEL` | Cancel button text |
134
- | `onConfirm` | function | `undefined` | Callback when time is confirmed |
135
- | `onCancel` | function | `undefined` | Callback when cancelled |
136
- | `onOpen` | function | `undefined` | Callback when picker opens |
137
- | `onUpdate` | function | `undefined` | Callback when time is updated |
138
- | `onSelectHour` | function | `undefined` | Callback when hour is selected |
139
- | `onSelectMinute` | function | `undefined` | Callback when minute is selected |
140
- | `onSelectAM` | function | `undefined` | Callback when AM is selected |
141
- | `onSelectPM` | function | `undefined` | Callback when PM is selected |
142
- | `onError` | function | `undefined` | Callback when error occurs |
125
+ ### Options Structure (v4.0.0 Breaking Change)
126
+
127
+ **Options are now organized into 5 logical groups:**
128
+
129
+ ```typescript
130
+ const picker = new TimepickerUI(input, {
131
+ clock: ClockOptions, // Clock behavior (type, increments, disabled time)
132
+ ui: UIOptions, // Appearance (theme, animation, mobile, inline)
133
+ labels: LabelsOptions, // Text labels (AM/PM, buttons, headers)
134
+ behavior: BehaviorOptions, // Behavior (focus, delays, ID)
135
+ callbacks: CallbacksOptions, // Event handlers
136
+ });
137
+ ```
138
+
139
+ ### Clock Options
140
+
141
+ | Property | Type | Default | Description |
142
+ | --------------------- | -------------- | ----------- | ------------------------------- |
143
+ | `type` | `12h` / `24h` | `12h` | Clock format |
144
+ | `incrementHours` | number | `1` | Hour increment step |
145
+ | `incrementMinutes` | number | `1` | Minute increment step |
146
+ | `autoSwitchToMinutes` | boolean | `false` | Auto-switch after hour selected |
147
+ | `disabledTime` | object | `undefined` | Disable specific hours/minutes |
148
+ | `currentTime` | boolean/object | `undefined` | Set current time to input |
149
+
150
+ ### UI Options
151
+
152
+ | Property | Type | Default | Description |
153
+ | --------------------- | ------- | ----------- | ------------------------------- |
154
+ | `theme` | string | `basic` | Theme (11 themes available) |
155
+ | `animation` | boolean | `true` | Enable animations |
156
+ | `backdrop` | boolean | `true` | Show backdrop overlay |
157
+ | `mobile` | boolean | `false` | Force mobile version |
158
+ | `enableSwitchIcon` | boolean | `false` | Show desktop/mobile switch icon |
159
+ | `editable` | boolean | `false` | Allow manual input editing |
160
+ | `enableScrollbar` | boolean | `false` | Enable scroll when picker open |
161
+ | `cssClass` | string | `undefined` | Additional CSS class |
162
+ | `appendModalSelector` | string | `""` | Custom container selector |
163
+ | `iconTemplate` | string | SVG | Desktop switch icon template |
164
+ | `iconTemplateMobile` | string | SVG | Mobile switch icon template |
165
+ | `inline` | object | `undefined` | Inline mode configuration |
166
+
167
+ ### Labels Options
168
+
169
+ | Property | Type | Default | Description |
170
+ | -------------- | ------ | ------------- | ------------------- |
171
+ | `am` | string | `AM` | AM label text |
172
+ | `pm` | string | `PM` | PM label text |
173
+ | `ok` | string | `OK` | OK button text |
174
+ | `cancel` | string | `Cancel` | Cancel button text |
175
+ | `time` | string | `Select time` | Desktop time label |
176
+ | `mobileTime` | string | `Enter Time` | Mobile time label |
177
+ | `mobileHour` | string | `Hour` | Mobile hour label |
178
+ | `mobileMinute` | string | `Minute` | Mobile minute label |
179
+
180
+ ### Behavior Options
181
+
182
+ | Property | Type | Default | Description |
183
+ | ---------------------- | ------- | -------------- | ----------------------- |
184
+ | `focusInputAfterClose` | boolean | `false` | Focus input after close |
185
+ | `focusTrap` | boolean | `true` | Trap focus in modal |
186
+ | `delayHandler` | number | `300` | Click delay (ms) |
187
+ | `id` | string | auto-generated | Custom instance ID |
188
+
189
+ ### Callbacks Options
190
+
191
+ | Property | Type | Description |
192
+ | ---------------- | -------- | ------------------------ |
193
+ | `onConfirm` | function | Time confirmed |
194
+ | `onCancel` | function | Cancelled |
195
+ | `onOpen` | function | Picker opened |
196
+ | `onUpdate` | function | Time updated (real-time) |
197
+ | `onSelectHour` | function | Hour selected |
198
+ | `onSelectMinute` | function | Minute selected |
199
+ | `onSelectAM` | function | AM selected |
200
+ | `onSelectPM` | function | PM selected |
201
+ | `onError` | function | Error occurred |
202
+
203
+ ### Migration from v3.x to v4.0.0
204
+
205
+ **All options must be moved into groups:**
206
+
207
+ ```diff
208
+ // v3.x (DEPRECATED)
209
+ -const picker = new TimepickerUI(input, {
210
+ - clockType: '24h',
211
+ - theme: 'dark',
212
+ - animation: true,
213
+ - incrementMinutes: 5,
214
+ - amLabel: 'AM',
215
+ - onConfirm: (data) => {}
216
+ -});
217
+
218
+ // v4.0.0 (NEW)
219
+ +const picker = new TimepickerUI(input, {
220
+ + clock: {
221
+ + type: '24h',
222
+ + incrementMinutes: 5
223
+ + },
224
+ + ui: {
225
+ + theme: 'dark',
226
+ + animation: true
227
+ + },
228
+ + labels: {
229
+ + am: 'AM'
230
+ + },
231
+ + callbacks: {
232
+ + onConfirm: (data) => {}
233
+ + }
234
+ +});
235
+ ```
236
+
237
+ **Full migration table:**
238
+
239
+ | v3.x (flat) | v4.0.0 (grouped) |
240
+ | --------------------------- | ------------------------------- |
241
+ | `clockType` | `clock.type` |
242
+ | `incrementHours` | `clock.incrementHours` |
243
+ | `incrementMinutes` | `clock.incrementMinutes` |
244
+ | `autoSwitchToMinutes` | `clock.autoSwitchToMinutes` |
245
+ | `disabledTime` | `clock.disabledTime` |
246
+ | `currentTime` | `clock.currentTime` |
247
+ | `theme` | `ui.theme` |
248
+ | `animation` | `ui.animation` |
249
+ | `backdrop` | `ui.backdrop` |
250
+ | `mobile` | `ui.mobile` |
251
+ | `enableSwitchIcon` | `ui.enableSwitchIcon` |
252
+ | `editable` | `ui.editable` |
253
+ | `enableScrollbar` | `ui.enableScrollbar` |
254
+ | `cssClass` | `ui.cssClass` |
255
+ | `appendModalSelector` | `ui.appendModalSelector` |
256
+ | `iconTemplate` | `ui.iconTemplate` |
257
+ | `iconTemplateMobile` | `ui.iconTemplateMobile` |
258
+ | `inline` | `ui.inline` |
259
+ | `amLabel` | `labels.am` |
260
+ | `pmLabel` | `labels.pm` |
261
+ | `okLabel` | `labels.ok` |
262
+ | `cancelLabel` | `labels.cancel` |
263
+ | `timeLabel` | `labels.time` |
264
+ | `mobileTimeLabel` | `labels.mobileTime` |
265
+ | `hourMobileLabel` | `labels.mobileHour` |
266
+ | `minuteMobileLabel` | `labels.mobileMinute` |
267
+ | `focusInputAfterCloseModal` | `behavior.focusInputAfterClose` |
268
+ | `focusTrap` | `behavior.focusTrap` |
269
+ | `delayHandler` | `behavior.delayHandler` |
270
+ | `id` | `behavior.id` |
271
+ | `onConfirm` | `callbacks.onConfirm` |
272
+ | `onCancel` | `callbacks.onCancel` |
273
+ | `onOpen` | `callbacks.onOpen` |
274
+ | `onUpdate` | `callbacks.onUpdate` |
275
+ | `onSelectHour` | `callbacks.onSelectHour` |
276
+ | `onSelectMinute` | `callbacks.onSelectMinute` |
277
+ | `onSelectAM` | `callbacks.onSelectAM` |
278
+ | `onSelectPM` | `callbacks.onSelectPM` |
279
+ | `onError` | `callbacks.onError` |
143
280
 
144
281
  ### Themes
145
282
 
146
- Available themes: `basic`, `crane-straight`, `crane-radius`, `m3`, `dark`, `glassmorphic`, `pastel`, `ai`, `cyberpunk`
283
+ Available themes: `basic`, `crane`, `crane-straight`, `m3-green`, `m2`, `dark`, `glassmorphic`, `pastel`, `ai`, `cyberpunk`
147
284
 
148
285
  ```javascript
149
286
  import "timepicker-ui/main.css"; // Required base styles
150
287
  import "timepicker-ui/theme-dark.css"; // Specific theme
151
288
 
152
289
  const picker = new TimepickerUI(input, {
153
- theme: "dark",
290
+ ui: {
291
+ theme: "dark",
292
+ },
154
293
  });
155
294
  ```
156
295
 
@@ -158,10 +297,12 @@ const picker = new TimepickerUI(input, {
158
297
 
159
298
  ```javascript
160
299
  const picker = new TimepickerUI(input, {
161
- disabledTime: {
162
- hours: [1, 3, 5, 8],
163
- minutes: [15, 30, 45],
164
- interval: "10:00 AM - 2:00 PM",
300
+ clock: {
301
+ disabledTime: {
302
+ hours: [1, 3, 5, 8],
303
+ minutes: [15, 30, 45],
304
+ interval: "10:00 AM - 2:00 PM",
305
+ },
165
306
  },
166
307
  });
167
308
  ```
@@ -170,11 +311,13 @@ const picker = new TimepickerUI(input, {
170
311
 
171
312
  ```javascript
172
313
  const picker = new TimepickerUI(input, {
173
- inline: {
174
- enabled: true,
175
- containerId: "timepicker-container",
176
- showButtons: false,
177
- autoUpdate: true,
314
+ ui: {
315
+ inline: {
316
+ enabled: true,
317
+ containerId: "timepicker-container",
318
+ showButtons: false,
319
+ autoUpdate: true,
320
+ },
178
321
  },
179
322
  });
180
323
  ```
@@ -205,7 +348,7 @@ TimepickerUI.destroyAll(); // Destroy all instances
205
348
 
206
349
  ## Events
207
350
 
208
- Listen to timepicker events using the new **EventEmitter API** (recommended) or the legacy DOM events (deprecated, will be removed in v4):
351
+ Listen to timepicker events using the **EventEmitter API** (v4+) or callback options:
209
352
 
210
353
  ### EventEmitter API (Recommended)
211
354
 
@@ -256,21 +399,224 @@ picker.once("confirm", (data) => {
256
399
  picker.off("confirm", handler);
257
400
  ```
258
401
 
259
- ### Legacy DOM Events (Deprecated)
402
+ ### Legacy DOM Events (v3.x Only)
403
+
404
+ ⚠️ **Only Available in v3.x - Completely Removed in v4.0.0**
405
+
406
+ DOM events (e.g., `timepicker:confirm`) were removed in v4.0.0. Use the **EventEmitter API** shown above or **callback options** instead.
260
407
 
261
- **Note:** DOM events (e.g., `timepicker:confirm`) are deprecated and will be removed in v4. Please migrate to the new EventEmitter API shown above.
408
+ **v3.x code (no longer works in v4):**
262
409
 
263
410
  ```javascript
264
411
  input.addEventListener("timepicker:confirm", (e) => {
265
412
  console.log("Time:", e.detail);
266
413
  });
414
+ ```
415
+
416
+ **v4.0.0 alternatives:**
417
+
418
+ ```javascript
419
+ // Option 1: EventEmitter API
420
+ picker.on("confirm", (data) => {
421
+ console.log("Time:", data);
422
+ });
423
+
424
+ // Option 2: Callback options
425
+ const picker = new TimepickerUI(input, {
426
+ callbacks: {
427
+ onConfirm: (data) => {
428
+ console.log("Time:", data);
429
+ },
430
+ },
431
+ });
432
+ ```
433
+
434
+ Removed events: `timepicker:open`, `timepicker:cancel`, `timepicker:confirm`, `timepicker:update`, `timepicker:select-hour`, `timepicker:select-minute`, `timepicker:select-am`, `timepicker:select-pm`, `timepicker:error`
435
+
436
+ ## Upgrade Guide: v3 → v4
437
+
438
+ ### Breaking Changes
439
+
440
+ **1. CSS Class Names Renamed**
441
+
442
+ All CSS classes have been shortened from `timepicker-ui-*` to `tp-ui-*`:
443
+
444
+ ```css
445
+ /* v3 */
446
+ .timepicker-ui-wrapper {
447
+ }
448
+ .timepicker-ui-modal {
449
+ }
450
+ .timepicker-ui-clock-face {
451
+ }
452
+ .timepicker-ui-hour {
453
+ }
454
+ .timepicker-ui-minutes {
455
+ }
456
+ .timepicker-ui-am {
457
+ }
458
+ .timepicker-ui-pm {
459
+ }
460
+
461
+ /* v4 */
462
+ .tp-ui-wrapper {
463
+ }
464
+ .tp-ui-modal {
465
+ }
466
+ .tp-ui-clock-face {
467
+ }
468
+ .tp-ui-hour {
469
+ }
470
+ .tp-ui-minutes {
471
+ }
472
+ .tp-ui-am {
473
+ }
474
+ .tp-ui-pm {
475
+ }
476
+ ```
477
+
478
+ **Impact:** If you have custom CSS targeting timepicker elements, update all class selectors.
479
+
480
+ **2. Grouped Options Structure**
481
+
482
+ All options are now organized into logical groups for better maintainability:
483
+
484
+ ```javascript
485
+ // v3
486
+ const picker = new TimepickerUI(input, {
487
+ clockType: "12h",
488
+ theme: "dark",
489
+ enableSwitchIcon: true,
490
+ mobile: true,
491
+ animation: true,
492
+ backdrop: true,
493
+ focusTrap: true,
494
+ editable: true,
495
+ onConfirm: (data) => console.log(data),
496
+ });
497
+
498
+ // v4 - Grouped options
499
+ const picker = new TimepickerUI(input, {
500
+ clock: {
501
+ type: "12h",
502
+ incrementHours: 1,
503
+ incrementMinutes: 1,
504
+ currentTime: { time: new Date(), updateInput: true },
505
+ disabledTime: { hours: [1, 2, 3] },
506
+ autoSwitchToMinutes: false,
507
+ },
508
+ ui: {
509
+ theme: "dark",
510
+ enableSwitchIcon: true,
511
+ mobile: true,
512
+ animation: true,
513
+ backdrop: true,
514
+ editable: true,
515
+ cssClass: "my-custom-class",
516
+ inline: { enabled: false },
517
+ },
518
+ labels: {
519
+ time: "Select Time",
520
+ am: "AM",
521
+ pm: "PM",
522
+ ok: "OK",
523
+ cancel: "Cancel",
524
+ },
525
+ behavior: {
526
+ focusTrap: true,
527
+ focusInputAfterClose: false,
528
+ delayHandler: 300,
529
+ },
530
+ callbacks: {
531
+ onConfirm: (data) => console.log(data),
532
+ onCancel: (data) => console.log(data),
533
+ onOpen: (data) => console.log(data),
534
+ onUpdate: (data) => console.log(data),
535
+ onSelectHour: (data) => console.log(data),
536
+ onSelectMinute: (data) => console.log(data),
537
+ onSelectAM: (data) => console.log(data),
538
+ onSelectPM: (data) => console.log(data),
539
+ onError: (data) => console.log(data),
540
+ },
541
+ });
542
+ ```
543
+
544
+ **2. Legacy DOM Events Removed**
545
+
546
+ DOM events have been completely removed. Use EventEmitter API or callback options:
547
+
548
+ ```javascript
549
+ // v3 - Deprecated (removed in v4)
550
+ input.addEventListener("timepicker:confirm", (e) => {
551
+ console.log(e.detail);
552
+ });
267
553
 
268
- input.addEventListener("timepicker:cancel", (e) => {
269
- console.log("Cancelled");
554
+ // v4 - EventEmitter API (recommended)
555
+ picker.on("confirm", (data) => {
556
+ console.log(data);
557
+ });
558
+
559
+ // v4 - Or use callback options
560
+ const picker = new TimepickerUI(input, {
561
+ callbacks: {
562
+ onConfirm: (data) => console.log(data),
563
+ },
270
564
  });
271
565
  ```
272
566
 
273
- Available legacy events: `timepicker:open`, `timepicker:cancel`, `timepicker:confirm`, `timepicker:update`, `timepicker:select-hour`, `timepicker:select-minute`, `timepicker:select-am`, `timepicker:select-pm`, `timepicker:error`
567
+ **3. setTheme() Method Removed**
568
+
569
+ Programmatic theme setting via `setTheme()` has been removed. Use CSS classes instead:
570
+
571
+ ```javascript
572
+ // v3 - Removed in v4
573
+ picker.setTheme({
574
+ primaryColor: "#ff0000",
575
+ backgroundColor: "#000000",
576
+ });
577
+
578
+ // v4 - Use CSS classes with CSS variables
579
+ const picker = new TimepickerUI(input, {
580
+ ui: { cssClass: "my-custom-theme" },
581
+ });
582
+ ```
583
+
584
+ ```css
585
+ /* Define custom theme in CSS */
586
+ .tp-ui-wrapper.my-custom-theme {
587
+ --tp-primary: #ff0000;
588
+ --tp-bg: #000000;
589
+ --tp-surface: #f0f0f0;
590
+ }
591
+ ```
592
+
593
+ **4. SSR-Safe Architecture**
594
+
595
+ All modules are now SSR-safe and can be imported in Node.js environments without crashing.
596
+
597
+ ### Migration Steps
598
+
599
+ 1. **Update option structure** - Move options into `clock`, `ui`, `labels`, `behavior`, `callbacks` groups
600
+ 2. **Replace DOM events** - Switch to `picker.on()` EventEmitter API or callback options
601
+ 3. **Remove setTheme() calls** - Use CSS classes with CSS variable overrides
602
+ 4. **Test SSR compatibility** - If using Next.js/Nuxt/Remix, verify hydration works correctly
603
+
604
+ ### New in v4
605
+
606
+ - **Composition-based architecture** - No inheritance, pure composition with managers
607
+ - **EventEmitter API** - Type-safe event handling with `on()`, `off()`, `once()`
608
+ - **Callback bridge** - Callbacks automatically connected to EventEmitter
609
+ - **SSR compatibility** - All modules can be imported in Node.js
610
+ - **Better TypeScript types** - Fully typed event payloads and options
611
+ - **Smaller bundle** - Removed unused code, optimized build (63.3 KB ESM)
612
+ - **Focus improvements** - Auto-focus on modal open, auto-focus on minute switch
613
+
614
+ ### Bundle Size Comparison
615
+
616
+ - v3: 80 KB ESM
617
+ - v4: 63.3 KB ESM (-20.9%)
618
+
619
+ ---
274
620
 
275
621
  ## Upgrade Guide: v2 → v3
276
622