timepicker-ui 4.2.1 → 4.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,87 +1,64 @@
1
1
  # timepicker-ui
2
2
 
3
- Modern time picker library built with TypeScript. Works with any framework or vanilla JavaScript.
3
+ Highly customizable time picker UI library for JavaScript and modern frameworks (React, Vue, Angular), with clock and wheel modes, zero dependencies, and full SSR support.
4
4
 
5
5
  [![npm version](https://badge.fury.io/js/timepicker-ui.svg)](https://badge.fury.io/js/timepicker-ui)
6
6
  [![downloads](https://img.shields.io/npm/dm/timepicker-ui)](https://npmcharts.com/compare/timepicker-ui?minimal=true)
7
7
  [![license](https://img.shields.io/badge/license-MIT-green.svg)](https://img.shields.io/npm/l/timepicker-ui)
8
8
  [![Coverage Status](https://coveralls.io/repos/github/pglejzer/timepicker-ui/badge.svg?branch=main)](https://coveralls.io/github/pglejzer/timepicker-ui?branch=main)
9
9
  [![Tests](https://github.com/pglejzer/timepicker-ui/actions/workflows/tests.yml/badge.svg)](https://github.com/pglejzer/timepicker-ui/actions/workflows/tests.yml)
10
- [![Socket Badge](https://badge.socket.dev/npm/package/timepicker-ui/4.2.0)](https://badge.socket.dev/npm/package/timepicker-ui/4.2.0)
10
+ [![Socket Badge](https://badge.socket.dev/npm/package/timepicker-ui/4.3.0)](https://badge.socket.dev/npm/package/timepicker-ui/4.3.0)
11
11
 
12
- [Live Demo](https://timepicker-ui.vercel.app/) • [Documentation](https://timepicker-ui.vercel.app/docs) • [React Wrapper](https://github.com/pglejzer/timepicker-ui-react) • [Changelog](./CHANGELOG.md)
12
+ [Live Demo](https://timepicker-ui.vercel.app/) • [Documentation](https://timepicker-ui.vercel.app/docs) • [Changelog](./CHANGELOG.md) • [React Wrapper](https://github.com/pglejzer/timepicker-ui-react)
13
13
 
14
- **Upgrading from v3?** Check the [upgrade guide](#upgrade-guide-v3--v4) below.
15
- **Upgrading from v2?** Check the [v2 → v3 upgrade guide](#upgrade-guide-v2--v3).
14
+ ## Why timepicker-ui?
16
15
 
17
- ## Features
18
-
19
- - 10 built-in themes (Material, Crane, Dark, Glassmorphic, Cyberpunk, and more)
20
- - Mobile-first design with touch support
21
- - **Wheel (scroll-spinner) mode** - alternative to the analog clock face
22
- - **Compact-wheel mode** - headerless wheel picker with optional popover placement
23
- - Framework agnostic - works with React, Vue, Angular, Svelte, or vanilla JS
24
- - Full TypeScript support
25
- - Inline mode for always-visible timepicker
26
- - Clear button to reset time selection
27
- - ARIA-compliant and keyboard accessible
28
- - SSR compatible
29
- - Lightweight with tree-shaking support
16
+ - **Zero dependencies** - no runtime deps, smaller bundle, no supply-chain risk
17
+ - **Multiple UI modes** - analog clock, scroll wheel, compact popover - not just one layout
18
+ - **Plugin architecture** - range, timezone, wheel - import only what you need
19
+ - **SSR safe** - works in Next.js, Nuxt, Remix, Astro out of the box
20
+ - **Any framework** - React, Vue, Angular, Svelte, or plain JS - same API everywhere
21
+ - **Accessible** - ARIA, keyboard nav, focus trap, screen reader support
30
22
 
31
- ## Plugins
23
+ ## Use Cases
32
24
 
33
- Optional features available as separate imports to reduce core bundle size:
25
+ - Booking forms, scheduling, reservations
26
+ - Admin panels and dashboards
27
+ - Mobile-first apps with touch/scroll UX
28
+ - Time range selection (via Range plugin)
29
+ - SSR applications (Next.js, Nuxt, Remix, Astro)
34
30
 
35
- ```javascript
36
- import { TimepickerUI, PluginRegistry } from "timepicker-ui";
37
- import { RangePlugin } from "timepicker-ui/plugins/range";
38
- import { TimezonePlugin } from "timepicker-ui/plugins/timezone";
31
+ ## Compared to Other Libraries
39
32
 
40
- // Register plugins before creating instances
41
- PluginRegistry.register(RangePlugin);
42
- PluginRegistry.register(TimezonePlugin);
43
- ```
33
+ | | timepicker-ui | Typical UI lib |
34
+ | ----------------- | -------------------------------- | -------------------------- |
35
+ | Dependencies | **0** | 1-10+ |
36
+ | UI modes | Clock + Wheel + Compact | Clock only |
37
+ | Plugin system | Yes (range, timezone, wheel) | No |
38
+ | Framework lock-in | None - works everywhere | Often React-only |
39
+ | SSR safe | Yes, out of the box | Often requires workarounds |
40
+ | Tree-shakeable | Yes - plugins excluded if unused | Varies |
44
41
 
45
- Register plugins once at app startup, then use features via options.
46
-
47
- ```javascript
48
- import { TimepickerUI, PluginRegistry } from "timepicker-ui";
49
- import { RangePlugin } from "timepicker-ui/plugins/range";
50
-
51
- PluginRegistry.register(RangePlugin);
52
-
53
- const picker = new TimepickerUI(input, {
54
- range: { enabled: true },
55
- });
56
- ```
57
-
58
- ## Contributions
42
+ ## Features
59
43
 
60
- Contributions welcome! Feel free to [open an issue or PR](https://github.com/pglejzer/timepicker-ui/issues).
44
+ - [10 built-in themes](https://timepicker-ui.vercel.app/docs/features/themes) (Material, Crane, Dark, Glassmorphic, Cyberpunk, and more)
45
+ - [Analog clock](https://timepicker-ui.vercel.app/examples/basic/getting-started), [wheel](https://timepicker-ui.vercel.app/examples/plugins/wheel), and [compact-wheel](https://timepicker-ui.vercel.app/examples/plugins/wheel) picker modes
46
+ - [Inline mode](https://timepicker-ui.vercel.app/docs/features/inline-mode), [clear button](https://timepicker-ui.vercel.app/docs/features/clear-button), [disabled time ranges](https://timepicker-ui.vercel.app/docs/features/disabled-time)
47
+ - [Mobile-first](https://timepicker-ui.vercel.app/docs/features/mobile) with touch & keyboard support
48
+ - Framework agnostic - React, Vue, Angular, Svelte, vanilla JS
49
+ - Full [TypeScript support](https://timepicker-ui.vercel.app/docs/api/typescript), SSR compatible, true tree-shaking
61
50
 
62
51
  ## Installation
63
52
 
53
+ Full guide: [Installation docs](https://timepicker-ui.vercel.app/docs/installation)
54
+
64
55
  ```bash
65
56
  npm install timepicker-ui
66
57
  ```
67
58
 
68
- ## Important: Global CSS Required
69
-
70
- Your app needs this global CSS rule for correct styling:
71
-
72
- ```css
73
- *,
74
- *::before,
75
- *::after {
76
- box-sizing: border-box;
77
- }
78
- ```
79
-
80
- Most projects include this by default.
81
-
82
59
  ## Quick Start
83
60
 
84
- ### Basic Usage
61
+ Full guide: [Quick Start docs](https://timepicker-ui.vercel.app/docs/quick-start)
85
62
 
86
63
  ```html
87
64
  <input id="timepicker" type="text" />
@@ -92,808 +69,154 @@ import { TimepickerUI } from "timepicker-ui";
92
69
  import "timepicker-ui/main.css";
93
70
 
94
71
  const input = document.querySelector("#timepicker");
95
- const picker = new TimepickerUI(input);
96
- picker.create();
97
- ```
98
-
99
- ### With Options
100
-
101
- ```javascript
102
72
  const picker = new TimepickerUI(input, {
103
- ui: {
104
- theme: "dark",
105
- animation: true,
106
- backdrop: true,
107
- },
108
- clock: {
109
- type: "24h",
110
- },
73
+ clock: { type: "24h" },
74
+ ui: { theme: "dark" },
111
75
  callbacks: {
112
- onConfirm: (data) => {
113
- console.log("Selected time:", data);
114
- },
76
+ onConfirm: (data) => console.log("Selected:", data),
115
77
  },
116
78
  });
117
79
  picker.create();
118
80
  ```
119
81
 
120
- ### React Example
121
-
122
- ```tsx
123
- import { useEffect, useRef } from "react";
124
- import { TimepickerUI } from "timepicker-ui";
125
- import "timepicker-ui/main.css";
82
+ > **Note** - Requires [global](https://timepicker-ui.vercel.app/docs/installation) `box-sizing: border-box` (included by default in most frameworks) .
126
83
 
127
- function TimePickerComponent() {
128
- const inputRef = useRef<HTMLInputElement>(null);
84
+ ## API
129
85
 
130
- useEffect(() => {
131
- if (inputRef.current) {
132
- const picker = new TimepickerUI(inputRef.current, {
133
- callbacks: {
134
- onConfirm: (data) => {
135
- console.log("Time selected:", data);
136
- },
137
- });
138
- picker.create();
139
-
140
- return () => picker.destroy();
141
- }
142
- }, []);
143
-
144
- return <input ref={inputRef} type="text" />;
145
- }
146
- ```
147
-
148
- ## Configuration
149
-
150
- Full documentation available at [timepicker-ui.vercel.app/docs](https://timepicker-ui.vercel.app/docs)
151
-
152
- ### Options Structure (v4.0.0 Breaking Change)
153
-
154
- **Options are now organized into 5 logical groups:**
155
-
156
- ```typescript
157
- const picker = new TimepickerUI(input, {
158
- clock: ClockOptions, // Clock behavior (type, increments, disabled time)
159
- ui: UIOptions, // Appearance (theme, animation, mobile, inline)
160
- labels: LabelsOptions, // Text labels (AM/PM, buttons, headers)
161
- behavior: BehaviorOptions, // Behavior (focus, delays, ID)
162
- callbacks: CallbacksOptions, // Event handlers
163
- clearBehavior: ClearBehaviorOptions, // Clear button behavior
164
- });
165
- ```
166
-
167
- ### Clock Options
168
-
169
- | Property | Type | Default | Description |
170
- | --------------------- | -------------- | ----------- | ------------------------------- |
171
- | `type` | `12h` / `24h` | `12h` | Clock format |
172
- | `incrementHours` | number | `1` | Hour increment step |
173
- | `incrementMinutes` | number | `1` | Minute increment step |
174
- | `smoothHourSnap` | boolean | `true` | Smooth hour dragging with snap |
175
- | `autoSwitchToMinutes` | boolean | `true` | Auto-switch after hour selected |
176
- | `disabledTime` | object | `undefined` | Disable specific hours/minutes |
177
- | `currentTime` | boolean/object | `undefined` | Set current time to input |
178
-
179
- ### UI Options
180
-
181
- | Property | Type | Default | Description |
182
- | --------------------- | ----------------------------------- | ----------- | ------------------------------------------------------------------ |
183
- | `theme` | string | `basic` | Theme (11 themes available) |
184
- | `animation` | boolean | `true` | Enable animations |
185
- | `backdrop` | boolean | `true` | Show backdrop overlay |
186
- | `mobile` | boolean | `false` | Force mobile version |
187
- | `enableSwitchIcon` | boolean | `false` | Show desktop/mobile switch icon |
188
- | `editable` | boolean | `false` | Allow manual input editing |
189
- | `enableScrollbar` | boolean | `false` | Enable scroll when picker open |
190
- | `cssClass` | string | `undefined` | Additional CSS class |
191
- | `appendModalSelector` | string | `""` | Custom container selector |
192
- | `iconTemplate` | string | SVG | Desktop switch icon template |
193
- | `iconTemplateMobile` | string | SVG | Mobile switch icon template |
194
- | `inline` | object | `undefined` | Inline mode configuration |
195
- | `clearButton` | boolean | `false` | Show clear button |
196
- | `mode` | `clock` / `wheel` / `compact-wheel` | `clock` | Picker mode - analog clock, scroll-spinner, or headerless wheel |
197
- | `wheel` | object | `undefined` | Wheel/compact-wheel config (placement, hideFooter, commitOnScroll) |
198
-
199
- ### Labels Options
200
-
201
- | Property | Type | Default | Description |
202
- | -------------- | ------ | ------------- | ------------------- |
203
- | `am` | string | `AM` | AM label text |
204
- | `pm` | string | `PM` | PM label text |
205
- | `ok` | string | `OK` | OK button text |
206
- | `cancel` | string | `Cancel` | Cancel button text |
207
- | `time` | string | `Select time` | Desktop time label |
208
- | `mobileTime` | string | `Enter Time` | Mobile time label |
209
- | `mobileHour` | string | `Hour` | Mobile hour label |
210
- | `mobileMinute` | string | `Minute` | Mobile minute label |
211
- | `clear` | string | `Clear` | Clear button text |
212
-
213
- ### Behavior Options
214
-
215
- | Property | Type | Default | Description |
216
- | ---------------------- | ------- | -------------- | ----------------------- |
217
- | `focusInputAfterClose` | boolean | `false` | Focus input after close |
218
- | `focusTrap` | boolean | `true` | Trap focus in modal |
219
- | `delayHandler` | number | `300` | Click delay (ms) |
220
- | `id` | string | auto-generated | Custom instance ID |
221
-
222
- ### Clear Behavior Options
223
-
224
- | Property | Type | Default | Description |
225
- | ------------ | ------- | ------- | --------------------------------------- |
226
- | `clearInput` | boolean | `true` | Whether clearing also empties the input |
227
-
228
- ### Callbacks Options
229
-
230
- | Property | Type | Description |
231
- | ---------------- | -------- | ------------------------ |
232
- | `onConfirm` | function | Time confirmed |
233
- | `onCancel` | function | Cancelled |
234
- | `onOpen` | function | Picker opened |
235
- | `onUpdate` | function | Time updated (real-time) |
236
- | `onSelectHour` | function | Hour selected |
237
- | `onSelectMinute` | function | Minute selected |
238
- | `onSelectAM` | function | AM selected |
239
- | `onSelectPM` | function | PM selected |
240
- | `onError` | function | Error occurred |
241
- | `onClear` | function | Time cleared |
242
-
243
- ### Migration from v3.x to v4.0.0
244
-
245
- **All options must be moved into groups:**
246
-
247
- ```diff
248
- // v3.x (DEPRECATED)
249
- -const picker = new TimepickerUI(input, {
250
- - clockType: '24h',
251
- - theme: 'dark',
252
- - animation: true,
253
- - incrementMinutes: 5,
254
- - amLabel: 'AM',
255
- - onConfirm: (data) => {}
256
- -});
257
-
258
- // v4.0.0 (NEW)
259
- +const picker = new TimepickerUI(input, {
260
- + clock: {
261
- + type: '24h',
262
- + incrementMinutes: 5
263
- + },
264
- + ui: {
265
- + theme: 'dark',
266
- + animation: true
267
- + },
268
- + labels: {
269
- + am: 'AM'
270
- + },
271
- + callbacks: {
272
- + onConfirm: (data) => {}
273
- + }
274
- +});
275
- ```
276
-
277
- **Full migration table:**
278
-
279
- | v3.x (flat) | v4.0.0 (grouped) |
280
- | --------------------------- | ------------------------------- |
281
- | `clockType` | `clock.type` |
282
- | `incrementHours` | `clock.incrementHours` |
283
- | `incrementMinutes` | `clock.incrementMinutes` |
284
- | `manualMinuteSwitch` | `clock.manualMinuteSwitch` |
285
- | `disabledTime` | `clock.disabledTime` |
286
- | `currentTime` | `clock.currentTime` |
287
- | `theme` | `ui.theme` |
288
- | `animation` | `ui.animation` |
289
- | `backdrop` | `ui.backdrop` |
290
- | `mobile` | `ui.mobile` |
291
- | `enableSwitchIcon` | `ui.enableSwitchIcon` |
292
- | `editable` | `ui.editable` |
293
- | `enableScrollbar` | `ui.enableScrollbar` |
294
- | `cssClass` | `ui.cssClass` |
295
- | `appendModalSelector` | `ui.appendModalSelector` |
296
- | `iconTemplate` | `ui.iconTemplate` |
297
- | `iconTemplateMobile` | `ui.iconTemplateMobile` |
298
- | `inline` | `ui.inline` |
299
- | `amLabel` | `labels.am` |
300
- | `pmLabel` | `labels.pm` |
301
- | `okLabel` | `labels.ok` |
302
- | `cancelLabel` | `labels.cancel` |
303
- | `timeLabel` | `labels.time` |
304
- | `mobileTimeLabel` | `labels.mobileTime` |
305
- | `hourMobileLabel` | `labels.mobileHour` |
306
- | `minuteMobileLabel` | `labels.mobileMinute` |
307
- | `focusInputAfterCloseModal` | `behavior.focusInputAfterClose` |
308
- | `focusTrap` | `behavior.focusTrap` |
309
- | `delayHandler` | `behavior.delayHandler` |
310
- | `id` | `behavior.id` |
311
- | `onConfirm` | `callbacks.onConfirm` |
312
- | `onCancel` | `callbacks.onCancel` |
313
- | `onOpen` | `callbacks.onOpen` |
314
- | `onUpdate` | `callbacks.onUpdate` |
315
- | `onSelectHour` | `callbacks.onSelectHour` |
316
- | `onSelectMinute` | `callbacks.onSelectMinute` |
317
- | `onSelectAM` | `callbacks.onSelectAM` |
318
- | `onSelectPM` | `callbacks.onSelectPM` |
319
- | `onError` | `callbacks.onError` |
320
-
321
- ### Themes
322
-
323
- Available themes: `basic`, `crane`, `crane-straight`, `m3-green`, `m2`, `dark`, `glassmorphic`, `pastel`, `ai`, `cyberpunk`
86
+ Full reference: [Methods](https://timepicker-ui.vercel.app/docs/api/methods) · [Events](https://timepicker-ui.vercel.app/docs/api/events) · [TypeScript](https://timepicker-ui.vercel.app/docs/api/typescript)
324
87
 
325
88
  ```javascript
326
- import "timepicker-ui/main.css"; // Required base styles
327
- import "timepicker-ui/theme-dark.css"; // Specific theme
328
-
329
- const picker = new TimepickerUI(input, {
330
- ui: {
331
- theme: "dark",
332
- },
333
- });
334
- ```
335
-
336
- ### Disabled Time
337
-
338
- ```javascript
339
- const picker = new TimepickerUI(input, {
340
- clock: {
341
- disabledTime: {
342
- hours: [1, 3, 5, 8],
343
- minutes: [15, 30, 45],
344
- interval: "10:00 AM - 2:00 PM",
345
- },
346
- },
347
- });
348
- ```
349
-
350
- ### Inline Mode
351
-
352
- ```javascript
353
- const picker = new TimepickerUI(input, {
354
- ui: {
355
- inline: {
356
- enabled: true,
357
- containerId: "timepicker-container",
358
- showButtons: false,
359
- autoUpdate: true,
360
- },
361
- },
362
- });
363
- ```
364
-
365
- ### Wheel Mode
366
-
367
- Wheel mode replaces the analog clock face with a touch-friendly scroll-spinner. The header (hour/minute inputs, AM/PM toggle) and footer (OK/Cancel buttons) remain unchanged - only the clock body is replaced.
368
-
369
- ```javascript
370
- const picker = new TimepickerUI(input, {
371
- ui: { mode: "wheel" },
372
- });
373
-
374
- picker.create();
375
- ```
376
-
377
- Wheel mode works with all existing features:
378
-
379
- - **12h / 24h**: Respects `clock.type` - AM/PM column appears only in 12h mode
380
- - **Themes**: Inherits the active theme via CSS variables
381
- - **Disabled time**: Disabled hours/minutes are dimmed and skipped during scroll snap
382
- - **Hide disabled options**: Set `wheel.hideDisabled: true` to completely remove disabled values from the list
383
- - **setValue / getValue**: `picker.setValue('09:30 AM')` scrolls the wheel to the correct position
384
- - **Keyboard navigation**: Arrow Up/Down scrolls one item, Tab moves between columns
385
- - **Events**: All standard events work - `select:hour`, `select:minute`, `update`, `confirm`, `cancel`, `clear`, `select:am`, `select:pm`, `error`
386
- - **Wheel-specific events**: `wheel:scroll:start` (column starts scrolling), `wheel:scroll:end` (column snaps to value with `previousValue`)
387
- - **Auto-commit**: Set `wheel.commitOnScroll: true` to auto-confirm on scroll end without pressing OK
388
- - **Persist on outside click**: Set `wheel.ignoreOutsideClick: true` to keep the picker open when clicking outside
389
-
390
- ### Compact-Wheel Mode
391
-
392
- Compact-wheel mode is a headerless variant of wheel mode - it shows only the scroll wheels without the hour/minute input header. Ideal for minimal UIs or popover-style pickers.
393
-
394
- ```javascript
395
- const picker = new TimepickerUI(input, {
396
- ui: { mode: "compact-wheel" },
397
- });
398
-
399
- picker.create();
400
- ```
401
-
402
- Combine with `wheel.placement` to open as a popover anchored to the input:
403
-
404
- ```javascript
405
- const picker = new TimepickerUI(input, {
406
- ui: { mode: "compact-wheel" },
407
- wheel: {
408
- placement: "auto", // 'auto', 'top', or 'bottom'
409
- },
410
- });
411
- ```
412
-
413
- **Limitations:**
414
-
415
- - Range plugin (`range.enabled`) is not supported in wheel or compact-wheel mode
416
- - `ui.mobile` is ignored - wheel layout is always the same regardless of viewport
417
-
418
- ## API Methods
419
-
420
- ### Instance Methods
421
-
422
- ```javascript
423
- const picker = new TimepickerUI(input, options);
424
-
425
- picker.create(); // Initialize
426
- picker.open(); // Open programmatically
427
- picker.close(); // Close
428
- picker.destroy(); // Clean up
429
- picker.getValue(); // Get current time
430
- picker.setValue("14:30"); // Set time
431
- picker.update({ options }); // Update configuration
432
- ```
433
-
434
- ### Static Methods
435
-
436
- ```javascript
437
- TimepickerUI.getById("my-id"); // Get instance by ID
438
- TimepickerUI.getAllInstances(); // Get all instances
439
- TimepickerUI.destroyAll(); // Destroy all instances
440
- ```
441
-
442
- ## Events
443
-
444
- Listen to timepicker events using the **EventEmitter API** (v4+) or callback options:
445
-
446
- ### EventEmitter API (Recommended)
447
-
448
- ```javascript
449
- const picker = new TimepickerUI(input);
450
- picker.create();
451
-
452
- picker.on("confirm", (data) => {
453
- console.log("Time confirmed:", data);
454
- });
455
-
456
- picker.on("cancel", (data) => {
457
- console.log("Cancelled:", data);
458
- });
459
-
460
- picker.on("open", () => {
461
- console.log("Picker opened");
462
- });
463
-
464
- picker.on("update", (data) => {
465
- console.log("Time updated:", data);
466
- });
467
-
468
- picker.on("select:hour", (data) => {
469
- console.log("Hour selected:", data.hour);
470
- });
471
-
472
- picker.on("select:minute", (data) => {
473
- console.log("Minute selected:", data.minutes);
474
- });
475
-
476
- picker.on("select:am", (data) => {
477
- console.log("AM selected");
478
- });
479
-
480
- picker.on("select:pm", (data) => {
481
- console.log("PM selected");
482
- });
483
-
484
- picker.on("error", (data) => {
485
- console.log("Error:", data.error);
486
- });
487
-
488
- picker.on("clear", (data) => {
489
- console.log("Cleared, previous value:", data.previousValue);
490
- });
491
-
492
- // Wheel-specific events (wheel mode only)
493
- picker.on("wheel:scroll:start", (data) => {
494
- console.log("Scroll started on:", data.column);
495
- });
496
-
497
- picker.on("wheel:scroll:end", (data) => {
498
- console.log("Column:", data.column, "snapped to:", data.value);
499
- console.log("Previous value:", data.previousValue);
500
- });
501
-
502
- picker.once("confirm", (data) => {
503
- console.log("This runs only once");
504
- });
89
+ picker.create(); // Initialize
90
+ picker.open(); // Open programmatically
91
+ picker.close(); // Close
92
+ picker.destroy(); // Clean up
93
+ picker.getValue(); // Get current time string
94
+ picker.setValue("14:30"); // Set time
95
+ picker.update({ ... }); // Update options at runtime
505
96
 
97
+ // Events
98
+ picker.on("confirm", (data) => {});
99
+ picker.on("cancel", (data) => {});
100
+ picker.on("open", () => {});
101
+ picker.on("update", (data) => {});
102
+ picker.on("clear", (data) => {});
103
+ picker.on("error", (data) => {});
104
+ picker.once("confirm", handler);
506
105
  picker.off("confirm", handler);
507
- ```
508
-
509
- ### Legacy DOM Events (v3.x Only)
510
-
511
- ⚠️ **Only Available in v3.x - Completely Removed in v4.0.0**
512
106
 
513
- DOM events (e.g., `timepicker:confirm`) were removed in v4.0.0. Use the **EventEmitter API** shown above or **callback options** instead.
514
-
515
- **v3.x code (no longer works in v4):**
516
-
517
- ```javascript
518
- input.addEventListener("timepicker:confirm", (e) => {
519
- console.log("Time:", e.detail);
520
- });
107
+ // Static
108
+ TimepickerUI.getById("my-id");
109
+ TimepickerUI.getAllInstances();
110
+ TimepickerUI.destroyAll();
521
111
  ```
522
112
 
523
- **v4.0.0 alternatives:**
113
+ ## Options Overview
524
114
 
525
- ```javascript
526
- // Option 1: EventEmitter API
527
- picker.on("confirm", (data) => {
528
- console.log("Time:", data);
529
- });
115
+ Options are grouped into logical namespaces. Full reference: [Options docs](https://timepicker-ui.vercel.app/docs/api/options) · [Configuration guide](https://timepicker-ui.vercel.app/docs/configuration)
530
116
 
531
- // Option 2: Callback options
532
- const picker = new TimepickerUI(input, {
533
- callbacks: {
534
- onConfirm: (data) => {
535
- console.log("Time:", data);
536
- },
537
- },
538
- });
539
- ```
540
-
541
- Removed events: `timepicker:open`, `timepicker:cancel`, `timepicker:confirm`, `timepicker:update`, `timepicker:select-hour`, `timepicker:select-minute`, `timepicker:select-am`, `timepicker:select-pm`, `timepicker:error`
542
-
543
- ## Upgrade Guide: v3 → v4
544
-
545
- ### Breaking Changes
546
-
547
- **1. CSS Class Names Renamed**
548
-
549
- All CSS classes have been shortened from `timepicker-ui-*` to `tp-ui-*`:
550
-
551
- ```css
552
- /* v3 */
553
- .timepicker-ui-wrapper {
554
- }
555
- .timepicker-ui-modal {
556
- }
557
- .timepicker-ui-clock-face {
558
- }
559
- .timepicker-ui-hour {
560
- }
561
- .timepicker-ui-minutes {
562
- }
563
- .timepicker-ui-am {
564
- }
565
- .timepicker-ui-pm {
566
- }
567
-
568
- /* v4 */
569
- .tp-ui-wrapper {
570
- }
571
- .tp-ui-modal {
572
- }
573
- .tp-ui-clock-face {
574
- }
575
- .tp-ui-hour {
576
- }
577
- .tp-ui-minutes {
578
- }
579
- .tp-ui-am {
580
- }
581
- .tp-ui-pm {
582
- }
583
- ```
584
-
585
- **Impact:** If you have custom CSS targeting timepicker elements, update all class selectors.
586
-
587
- **2. Grouped Options Structure**
588
-
589
- All options are now organized into logical groups for better maintainability:
590
-
591
- ```javascript
592
- // v3
593
- const picker = new TimepickerUI(input, {
594
- clockType: "12h",
595
- theme: "dark",
596
- enableSwitchIcon: true,
597
- mobile: true,
598
- animation: true,
599
- backdrop: true,
600
- focusTrap: true,
601
- editable: true,
602
- onConfirm: (data) => console.log(data),
603
- });
604
-
605
- // v4 - Grouped options
606
- const picker = new TimepickerUI(input, {
117
+ ```typescript
118
+ new TimepickerUI(input, {
607
119
  clock: {
608
- type: "12h",
120
+ type: "12h" | "24h", // default: "12h"
609
121
  incrementHours: 1,
610
122
  incrementMinutes: 1,
611
- currentTime: { time: new Date(), updateInput: true },
612
- disabledTime: { hours: [1, 2, 3] },
613
- manualMinuteSwitch: false,
123
+ disabledTime: { hours: [], minutes: [], interval: "" },
124
+ currentTime: boolean | object,
614
125
  },
615
126
  ui: {
616
- theme: "dark",
617
- enableSwitchIcon: true,
618
- mobile: true,
127
+ theme: "basic" | "dark" | "m3-green" | "crane" | ..., // 10 themes
128
+ mode: "clock" | "wheel" | "compact-wheel", // default: "clock"
619
129
  animation: true,
620
130
  backdrop: true,
621
- editable: true,
622
- cssClass: "my-custom-class",
623
- inline: { enabled: false },
624
- },
625
- labels: {
626
- time: "Select Time",
627
- am: "AM",
628
- pm: "PM",
629
- ok: "OK",
630
- cancel: "Cancel",
631
- },
632
- behavior: {
633
- focusTrap: true,
634
- focusInputAfterClose: false,
635
- delayHandler: 300,
131
+ mobile: false,
132
+ editable: false,
133
+ inline: { enabled: false, containerId: "", showButtons: true, autoUpdate: false },
134
+ clearButton: false,
135
+ cssClass: "",
636
136
  },
637
- callbacks: {
638
- onConfirm: (data) => console.log(data),
639
- onCancel: (data) => console.log(data),
640
- onOpen: (data) => console.log(data),
641
- onUpdate: (data) => console.log(data),
642
- onSelectHour: (data) => console.log(data),
643
- onSelectMinute: (data) => console.log(data),
644
- onSelectAM: (data) => console.log(data),
645
- onSelectPM: (data) => console.log(data),
646
- onError: (data) => console.log(data),
647
- },
648
- });
649
- ```
650
-
651
- **2. Legacy DOM Events Removed**
652
-
653
- DOM events have been completely removed. Use EventEmitter API or callback options:
654
-
655
- ```javascript
656
- // v3 - Deprecated (removed in v4)
657
- input.addEventListener("timepicker:confirm", (e) => {
658
- console.log(e.detail);
659
- });
660
-
661
- // v4 - EventEmitter API (recommended)
662
- picker.on("confirm", (data) => {
663
- console.log(data);
664
- });
665
-
666
- // v4 - Or use callback options
667
- const picker = new TimepickerUI(input, {
668
- callbacks: {
669
- onConfirm: (data) => console.log(data),
670
- },
671
- });
672
- ```
673
-
674
- **3. setTheme() Method Removed**
675
-
676
- Programmatic theme setting via `setTheme()` has been removed. Use CSS classes instead:
677
-
678
- ```javascript
679
- // v3 - Removed in v4
680
- picker.setTheme({
681
- primaryColor: "#ff0000",
682
- backgroundColor: "#000000",
683
- });
684
-
685
- // v4 - Use CSS classes with CSS variables
686
- const picker = new TimepickerUI(input, {
687
- ui: { cssClass: "my-custom-theme" },
137
+ labels: { am, pm, ok, cancel, time, mobileTime, mobileHour, mobileMinute, clear },
138
+ behavior: { focusTrap: true, focusInputAfterClose: false, delayHandler: 300, id: "" },
139
+ callbacks: { onConfirm, onCancel, onOpen, onUpdate, onSelectHour, onSelectMinute, onSelectAM, onSelectPM, onError, onClear },
140
+ wheel: { placement: "auto" | "top" | "bottom", hideFooter: false, commitOnScroll: false, hideDisabled: false, ignoreOutsideClick: false },
688
141
  });
689
142
  ```
690
143
 
691
- ```css
692
- /* Define custom theme in CSS */
693
- .tp-ui-wrapper.my-custom-theme {
694
- --tp-primary: #ff0000;
695
- --tp-bg: #000000;
696
- --tp-surface: #f0f0f0;
697
- }
698
- ```
699
-
700
- **4. SSR-Safe Architecture**
701
-
702
- All modules are now SSR-safe and can be imported in Node.js environments without crashing.
144
+ ## Themes
703
145
 
704
- ### Migration Steps
146
+ Browse all themes: [Theme docs](https://timepicker-ui.vercel.app/docs/features/themes) · [Live examples](https://timepicker-ui.vercel.app/examples/themes/basic) · [Custom styling](https://timepicker-ui.vercel.app/docs/advanced/styling)
705
147
 
706
- 1. **Update option structure** - Move options into `clock`, `ui`, `labels`, `behavior`, `callbacks` groups
707
- 2. **Replace DOM events** - Switch to `picker.on()` EventEmitter API or callback options
708
- 3. **Remove setTheme() calls** - Use CSS classes with CSS variable overrides
709
- 4. **Test SSR compatibility** - If using Next.js/Nuxt/Remix, verify hydration works correctly
710
-
711
- ### New in v4
712
-
713
- - **Composition-based architecture** - No inheritance, pure composition with managers
714
- - **EventEmitter API** - Type-safe event handling with `on()`, `off()`, `once()`
715
- - **Callback bridge** - Callbacks automatically connected to EventEmitter
716
- - **SSR compatibility** - All modules can be imported in Node.js
717
- - **Better TypeScript types** - Fully typed event payloads and options
718
- - **Smaller bundle** - Removed unused code, optimized build (63.3 KB ESM)
719
- - **Focus improvements** - Auto-focus on modal open, auto-focus on minute switch
720
- - **Clear button** - Reset time selection with a dedicated clear button (v4.2.0)- **Compact-wheel mode** - Headerless wheel picker with optional popover placement (v4.2.0)
721
- - **Hide disabled options** - Remove disabled values from the list instead of dimming (v4.2.0)
722
- - **Auto-commit on scroll** - Auto-confirm time at scroll end in wheel modes (v4.2.0)
723
-
724
- ### Bundle Size Comparison
725
-
726
- - v3: 80 KB ESM
727
- - v4: 63.3 KB ESM (-20.9%)
728
-
729
- ---
730
-
731
- ## Upgrade Guide: v2 → v3
732
-
733
- ### Breaking Changes
734
-
735
- **1. CSS must be imported manually**
148
+ Available: `basic`, `crane`, `crane-straight`, `m3-green`, `m2`, `dark`, `glassmorphic`, `pastel`, `ai`, `cyberpunk`
736
149
 
737
150
  ```javascript
738
- // v3 - Required
739
- import "timepicker-ui/main.css";
740
- ```
741
-
742
- **2. Event names changed**
151
+ import "timepicker-ui/main.css"; // always required
152
+ import "timepicker-ui/theme-dark.css"; // theme-specific stylesheet
743
153
 
744
- ```javascript
745
- // v2
746
- input.addEventListener("show", ...);
747
- input.addEventListener("accept", ...);
748
-
749
- // v3
750
- input.addEventListener("timepicker:open", ...);
751
- input.addEventListener("timepicker:confirm", ...);
154
+ new TimepickerUI(input, { ui: { theme: "dark" } });
752
155
  ```
753
156
 
754
- **3. Use callbacks instead of events**
755
-
756
- ```javascript
757
- // v3 - Recommended
758
- const picker = new TimepickerUI(input, {
759
- onConfirm: (data) => console.log(data),
760
- onCancel: (data) => console.log("Cancelled"),
761
- });
762
- ```
157
+ ## Plugins
763
158
 
764
- **4. destroy() behavior**
159
+ Docs: [Plugins overview](https://timepicker-ui.vercel.app/docs/features/plugins) · Examples: [Range](https://timepicker-ui.vercel.app/examples/plugins/range) · [Timezone](https://timepicker-ui.vercel.app/examples/plugins/timezone) · [Wheel](https://timepicker-ui.vercel.app/examples/plugins/wheel)
765
160
 
766
161
  ```javascript
767
- // v2 - Removed input from DOM
768
- picker.destroy();
769
-
770
- // v3 - Only destroys picker, keeps input
771
- picker.destroy();
772
- ```
773
-
774
- ### New in v3
775
-
776
- - Inline mode
777
- - Instance management (`getById`, `destroyAll`)
778
- - Direct callbacks
779
- - 5 new themes
780
- - `getValue()` and `setValue()` methods
781
- - Better TypeScript support
782
- - **EventEmitter API** for modern event handling (`on`, `off`, `once`)
783
-
784
- ### Migration to EventEmitter (v3.x)
785
-
786
- Starting in v3.x, we recommend using the new EventEmitter API instead of DOM events:
162
+ import { TimepickerUI, PluginRegistry } from "timepicker-ui";
163
+ import { RangePlugin } from "timepicker-ui/plugins/range";
164
+ import { TimezonePlugin } from "timepicker-ui/plugins/timezone";
165
+ import { WheelPlugin } from "timepicker-ui/plugins/wheel";
787
166
 
788
- ```javascript
789
- // Old way (deprecated, will be removed in v4)
790
- input.addEventListener("timepicker:confirm", (e) => {
791
- console.log(e.detail);
792
- });
167
+ // Register once at app startup
168
+ PluginRegistry.register(RangePlugin);
169
+ PluginRegistry.register(TimezonePlugin);
170
+ PluginRegistry.register(WheelPlugin);
793
171
 
794
- // New way (recommended)
795
- picker.on("confirm", (data) => {
796
- console.log(data);
797
- });
172
+ new TimepickerUI(input, { range: { enabled: true } });
798
173
  ```
799
174
 
800
- Benefits:
175
+ ## Upgrading
801
176
 
802
- - Cleaner API without prefixes
803
- - Better TypeScript support
804
- - No DOM pollution
805
- - Memory-efficient (automatic cleanup on destroy)
806
- - Supports `once()` for one-time listeners
177
+ - **v3 → v4** - grouped options, EventEmitter API, CSS classes renamed. See [Migration Guide](https://timepicker-ui.vercel.app/docs/migration-guide) · [Breaking Changes](./readme/BREAKING_CHANGES_V4.md) · [CHANGELOG](./CHANGELOG.md)
178
+ - **v2 v3** - CSS must be imported manually, event names changed. See [Legacy Migration](https://timepicker-ui.vercel.app/docs/legacy-migration) · [CHANGELOG](./CHANGELOG.md)
807
179
 
808
180
  ## Framework Integration
809
181
 
810
- ### React
182
+ Full examples: [React](https://timepicker-ui.vercel.app/react) · [Vue / Angular / Svelte](https://timepicker-ui.vercel.app/docs/quick-start)
183
+
184
+ **React (quick example)**
811
185
 
812
- ```jsx
186
+ ```tsx
813
187
  import { useEffect, useRef } from "react";
814
188
  import { TimepickerUI } from "timepicker-ui";
815
189
  import "timepicker-ui/main.css";
816
190
 
817
- function App() {
818
- const inputRef = useRef(null);
819
-
191
+ function TimePicker() {
192
+ const ref = useRef<HTMLInputElement>(null);
820
193
  useEffect(() => {
821
- if (!inputRef.current) return;
822
- const picker = new TimepickerUI(inputRef.current);
194
+ if (!ref.current) return;
195
+ const picker = new TimepickerUI(ref.current);
823
196
  picker.create();
824
197
  return () => picker.destroy();
825
198
  }, []);
826
-
827
- return <input ref={inputRef} placeholder="Select time" />;
199
+ return <input ref={ref} />;
828
200
  }
829
201
  ```
830
202
 
831
- ### Vue 3
832
-
833
- ```vue
834
- <template>
835
- <input ref="pickerInput" placeholder="Select time" />
836
- </template>
837
-
838
- <script setup>
839
- import { onMounted, ref } from "vue";
840
- import { TimepickerUI } from "timepicker-ui";
841
- import "timepicker-ui/main.css";
842
-
843
- const pickerInput = ref(null);
844
-
845
- onMounted(() => {
846
- if (!pickerInput.value) return;
847
- const picker = new TimepickerUI(pickerInput.value);
848
- picker.create();
849
- });
850
- </script>
851
- ```
852
-
853
- ### Angular
854
-
855
- ```typescript
856
- import { Component, ElementRef, ViewChild, AfterViewInit } from "@angular/core";
857
- import { TimepickerUI } from "timepicker-ui";
858
-
859
- @Component({
860
- selector: "app-root",
861
- template: `<input #timepickerInput placeholder="Select time" />`,
862
- })
863
- export class App implements AfterViewInit {
864
- @ViewChild("timepickerInput") inputRef!: ElementRef<HTMLInputElement>;
865
-
866
- ngAfterViewInit() {
867
- const picker = new TimepickerUI(this.inputRef.nativeElement);
868
- picker.create();
869
- }
870
- }
871
- ```
203
+ > There is also a dedicated [React wrapper package](https://github.com/pglejzer/timepicker-ui-react).
872
204
 
873
- Add to `angular.json` styles:
205
+ ## Performance
874
206
 
875
- ```json
876
- "styles": ["src/styles.css", "timepicker-ui/main.css"]
877
- ```
207
+ - **Tree-shakeable** - unused plugins are fully excluded from the bundle
208
+ - **[Lightweight core](https://timepicker-ui.vercel.app/bundle-stats)** - with tree-shaking support
209
+ - **Plugins loaded on demand** - range, timezone, wheel add size only when imported
210
+ - **No runtime dependencies** - nothing extra to download or audit
878
211
 
879
212
  ## Development
880
213
 
881
- Development tooling is in the [`app/`](./app) directory. See [`app/README.md`](./app/README.md) for details.
882
-
883
- ## License
884
-
885
- MIT © [Piotr Glejzer](https://github.com/pglejzer)
214
+ See [`app/README.md`](./app/README.md) for local development setup.
886
215
 
887
216
  ## Contributing
888
217
 
889
- Contributions welcome! Check the [issues page](https://github.com/pglejzer/timepicker-ui/issues).
218
+ Contributions welcome! [Open an issue or PR](https://github.com/pglejzer/timepicker-ui/issues).
890
219
 
891
- ## Browser Support
892
-
893
- Chrome 60+, Firefox 55+, Safari 12+, Edge 79+, iOS Safari 12+, Chrome Android 60+
894
-
895
- ## Support
220
+ ## License
896
221
 
897
- - [Documentation](https://pglejzer.github.io/timepicker-ui-docs/)
898
- - [Report Bug](https://github.com/pglejzer/timepicker-ui/issues)
899
- - [Discussions](https://github.com/pglejzer/timepicker-ui/discussions)
222
+ MIT © [Piotr Glejzer](https://github.com/pglejzer)