timepicker-ui 4.2.2 → 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.2)](https://badge.socket.dev/npm/package/timepicker-ui/4.2.2)
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,819 +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
- wheel: WheelOptions, // Wheel/compact-wheel mode configuration
165
- });
166
- ```
167
-
168
- ### Clock Options
169
-
170
- | Property | Type | Default | Description |
171
- | --------------------- | -------------- | ----------- | ------------------------------- |
172
- | `type` | `12h` / `24h` | `12h` | Clock format |
173
- | `incrementHours` | number | `1` | Hour increment step |
174
- | `incrementMinutes` | number | `1` | Minute increment step |
175
- | `smoothHourSnap` | boolean | `true` | Smooth hour dragging with snap |
176
- | `autoSwitchToMinutes` | boolean | `true` | Auto-switch after hour selected |
177
- | `disabledTime` | object | `undefined` | Disable specific hours/minutes |
178
- | `currentTime` | boolean/object | `undefined` | Set current time to input |
179
-
180
- ### UI Options
181
-
182
- | Property | Type | Default | Description |
183
- | --------------------- | ----------------------------------- | ----------- | ------------------------------------------------------------------ |
184
- | `theme` | string | `basic` | Theme (11 themes available) |
185
- | `animation` | boolean | `true` | Enable animations |
186
- | `backdrop` | boolean | `true` | Show backdrop overlay |
187
- | `mobile` | boolean | `false` | Force mobile version |
188
- | `enableSwitchIcon` | boolean | `false` | Show desktop/mobile switch icon |
189
- | `editable` | boolean | `false` | Allow manual input editing |
190
- | `enableScrollbar` | boolean | `false` | Enable scroll when picker open |
191
- | `cssClass` | string | `undefined` | Additional CSS class |
192
- | `appendModalSelector` | string | `""` | Custom container selector |
193
- | `iconTemplate` | string | SVG | Desktop switch icon template |
194
- | `iconTemplateMobile` | string | SVG | Mobile switch icon template |
195
- | `inline` | object | `undefined` | Inline mode configuration |
196
- | `clearButton` | boolean | `false` | Show clear button |
197
- | `mode` | `clock` / `wheel` / `compact-wheel` | `clock` | Picker mode - analog clock, scroll-spinner, or headerless wheel |
198
- | `wheel` | `WheelOptions` | `undefined` | Wheel/compact-wheel config (placement, hideFooter, commitOnScroll) |
199
-
200
- ### Labels Options
201
-
202
- | Property | Type | Default | Description |
203
- | -------------- | ------ | ------------- | ------------------- |
204
- | `am` | string | `AM` | AM label text |
205
- | `pm` | string | `PM` | PM label text |
206
- | `ok` | string | `OK` | OK button text |
207
- | `cancel` | string | `Cancel` | Cancel button text |
208
- | `time` | string | `Select time` | Desktop time label |
209
- | `mobileTime` | string | `Enter Time` | Mobile time label |
210
- | `mobileHour` | string | `Hour` | Mobile hour label |
211
- | `mobileMinute` | string | `Minute` | Mobile minute label |
212
- | `clear` | string | `Clear` | Clear button text |
213
-
214
- ### Behavior Options
215
-
216
- | Property | Type | Default | Description |
217
- | ---------------------- | ------- | -------------- | ----------------------- |
218
- | `focusInputAfterClose` | boolean | `false` | Focus input after close |
219
- | `focusTrap` | boolean | `true` | Trap focus in modal |
220
- | `delayHandler` | number | `300` | Click delay (ms) |
221
- | `id` | string | auto-generated | Custom instance ID |
222
-
223
- ### Clear Behavior Options
224
-
225
- | Property | Type | Default | Description |
226
- | ------------ | ------- | ------- | --------------------------------------- |
227
- | `clearInput` | boolean | `true` | Whether clearing also empties the input |
228
-
229
- ### Wheel Options
230
-
231
- | Property | Type | Default | Description |
232
- | -------------------- | ------------------------- | ------- | -------------------------------------------------------------------- |
233
- | `placement` | `auto` / `top` / `bottom` | `auto` | Popover placement in compact-wheel mode |
234
- | `hideFooter` | boolean | `false` | Hide OK/Cancel/Clear buttons — useful with `commitOnScroll` |
235
- | `commitOnScroll` | boolean | `false` | Auto-confirm time at end of scroll without pressing OK |
236
- | `hideDisabled` | boolean | `false` | Remove disabled values from list instead of dimming them |
237
- | `ignoreOutsideClick` | boolean | `false` | Prevent picker from closing when clicking outside (wheel modes only) |
238
-
239
- ### Callbacks Options
240
-
241
- | Property | Type | Description |
242
- | ---------------- | -------- | ------------------------ |
243
- | `onConfirm` | function | Time confirmed |
244
- | `onCancel` | function | Cancelled |
245
- | `onOpen` | function | Picker opened |
246
- | `onUpdate` | function | Time updated (real-time) |
247
- | `onSelectHour` | function | Hour selected |
248
- | `onSelectMinute` | function | Minute selected |
249
- | `onSelectAM` | function | AM selected |
250
- | `onSelectPM` | function | PM selected |
251
- | `onError` | function | Error occurred |
252
- | `onClear` | function | Time cleared |
253
-
254
- ### Migration from v3.x to v4.0.0
255
-
256
- **All options must be moved into groups:**
257
-
258
- ```diff
259
- // v3.x (DEPRECATED)
260
- -const picker = new TimepickerUI(input, {
261
- - clockType: '24h',
262
- - theme: 'dark',
263
- - animation: true,
264
- - incrementMinutes: 5,
265
- - amLabel: 'AM',
266
- - onConfirm: (data) => {}
267
- -});
268
-
269
- // v4.0.0 (NEW)
270
- +const picker = new TimepickerUI(input, {
271
- + clock: {
272
- + type: '24h',
273
- + incrementMinutes: 5
274
- + },
275
- + ui: {
276
- + theme: 'dark',
277
- + animation: true
278
- + },
279
- + labels: {
280
- + am: 'AM'
281
- + },
282
- + callbacks: {
283
- + onConfirm: (data) => {}
284
- + }
285
- +});
286
- ```
287
-
288
- **Full migration table:**
289
-
290
- | v3.x (flat) | v4.0.0 (grouped) |
291
- | --------------------------- | ------------------------------- |
292
- | `clockType` | `clock.type` |
293
- | `incrementHours` | `clock.incrementHours` |
294
- | `incrementMinutes` | `clock.incrementMinutes` |
295
- | `manualMinuteSwitch` | `clock.manualMinuteSwitch` |
296
- | `disabledTime` | `clock.disabledTime` |
297
- | `currentTime` | `clock.currentTime` |
298
- | `theme` | `ui.theme` |
299
- | `animation` | `ui.animation` |
300
- | `backdrop` | `ui.backdrop` |
301
- | `mobile` | `ui.mobile` |
302
- | `enableSwitchIcon` | `ui.enableSwitchIcon` |
303
- | `editable` | `ui.editable` |
304
- | `enableScrollbar` | `ui.enableScrollbar` |
305
- | `cssClass` | `ui.cssClass` |
306
- | `appendModalSelector` | `ui.appendModalSelector` |
307
- | `iconTemplate` | `ui.iconTemplate` |
308
- | `iconTemplateMobile` | `ui.iconTemplateMobile` |
309
- | `inline` | `ui.inline` |
310
- | `amLabel` | `labels.am` |
311
- | `pmLabel` | `labels.pm` |
312
- | `okLabel` | `labels.ok` |
313
- | `cancelLabel` | `labels.cancel` |
314
- | `timeLabel` | `labels.time` |
315
- | `mobileTimeLabel` | `labels.mobileTime` |
316
- | `hourMobileLabel` | `labels.mobileHour` |
317
- | `minuteMobileLabel` | `labels.mobileMinute` |
318
- | `focusInputAfterCloseModal` | `behavior.focusInputAfterClose` |
319
- | `focusTrap` | `behavior.focusTrap` |
320
- | `delayHandler` | `behavior.delayHandler` |
321
- | `id` | `behavior.id` |
322
- | `onConfirm` | `callbacks.onConfirm` |
323
- | `onCancel` | `callbacks.onCancel` |
324
- | `onOpen` | `callbacks.onOpen` |
325
- | `onUpdate` | `callbacks.onUpdate` |
326
- | `onSelectHour` | `callbacks.onSelectHour` |
327
- | `onSelectMinute` | `callbacks.onSelectMinute` |
328
- | `onSelectAM` | `callbacks.onSelectAM` |
329
- | `onSelectPM` | `callbacks.onSelectPM` |
330
- | `onError` | `callbacks.onError` |
331
-
332
- ### Themes
333
-
334
- 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)
335
87
 
336
88
  ```javascript
337
- import "timepicker-ui/main.css"; // Required base styles
338
- import "timepicker-ui/theme-dark.css"; // Specific theme
339
-
340
- const picker = new TimepickerUI(input, {
341
- ui: {
342
- theme: "dark",
343
- },
344
- });
345
- ```
346
-
347
- ### Disabled Time
348
-
349
- ```javascript
350
- const picker = new TimepickerUI(input, {
351
- clock: {
352
- disabledTime: {
353
- hours: [1, 3, 5, 8],
354
- minutes: [15, 30, 45],
355
- interval: "10:00 AM - 2:00 PM",
356
- },
357
- },
358
- });
359
- ```
360
-
361
- ### Inline Mode
362
-
363
- ```javascript
364
- const picker = new TimepickerUI(input, {
365
- ui: {
366
- inline: {
367
- enabled: true,
368
- containerId: "timepicker-container",
369
- showButtons: false,
370
- autoUpdate: true,
371
- },
372
- },
373
- });
374
- ```
375
-
376
- ### Wheel Mode
377
-
378
- 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.
379
-
380
- ```javascript
381
- const picker = new TimepickerUI(input, {
382
- ui: { mode: "wheel" },
383
- });
384
-
385
- picker.create();
386
- ```
387
-
388
- Wheel mode works with all existing features:
389
-
390
- - **12h / 24h**: Respects `clock.type` - AM/PM column appears only in 12h mode
391
- - **Themes**: Inherits the active theme via CSS variables
392
- - **Disabled time**: Disabled hours/minutes are dimmed and skipped during scroll snap
393
- - **Hide disabled options**: Set `wheel.hideDisabled: true` to completely remove disabled values from the list
394
- - **setValue / getValue**: `picker.setValue('09:30 AM')` scrolls the wheel to the correct position
395
- - **Keyboard navigation**: Arrow Up/Down scrolls one item, Tab moves between columns
396
- - **Events**: All standard events work - `select:hour`, `select:minute`, `update`, `confirm`, `cancel`, `clear`, `select:am`, `select:pm`, `error`
397
- - **Wheel-specific events**: `wheel:scroll:start` (column starts scrolling), `wheel:scroll:end` (column snaps to value with `previousValue`)
398
- - **Auto-commit**: Set `wheel.commitOnScroll: true` to auto-confirm on scroll end without pressing OK
399
- - **Persist on outside click**: Set `wheel.ignoreOutsideClick: true` to keep the picker open when clicking outside
400
-
401
- ### Compact-Wheel Mode
402
-
403
- 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.
404
-
405
- ```javascript
406
- const picker = new TimepickerUI(input, {
407
- ui: { mode: "compact-wheel" },
408
- });
409
-
410
- picker.create();
411
- ```
412
-
413
- Combine with `wheel.placement` to open as a popover anchored to the input:
414
-
415
- ```javascript
416
- const picker = new TimepickerUI(input, {
417
- ui: { mode: "compact-wheel" },
418
- wheel: {
419
- placement: "auto", // 'auto', 'top', or 'bottom'
420
- },
421
- });
422
- ```
423
-
424
- **Limitations:**
425
-
426
- - Range plugin (`range.enabled`) is not supported in wheel or compact-wheel mode
427
- - `ui.mobile` is ignored - wheel layout is always the same regardless of viewport
428
-
429
- ## API Methods
430
-
431
- ### Instance Methods
432
-
433
- ```javascript
434
- const picker = new TimepickerUI(input, options);
435
-
436
- picker.create(); // Initialize
437
- picker.open(); // Open programmatically
438
- picker.close(); // Close
439
- picker.destroy(); // Clean up
440
- picker.getValue(); // Get current time
441
- picker.setValue("14:30"); // Set time
442
- picker.update({ options }); // Update configuration
443
- ```
444
-
445
- ### Static Methods
446
-
447
- ```javascript
448
- TimepickerUI.getById("my-id"); // Get instance by ID
449
- TimepickerUI.getAllInstances(); // Get all instances
450
- TimepickerUI.destroyAll(); // Destroy all instances
451
- ```
452
-
453
- ## Events
454
-
455
- Listen to timepicker events using the **EventEmitter API** (v4+) or callback options:
456
-
457
- ### EventEmitter API (Recommended)
458
-
459
- ```javascript
460
- const picker = new TimepickerUI(input);
461
- picker.create();
462
-
463
- picker.on("confirm", (data) => {
464
- console.log("Time confirmed:", data);
465
- });
466
-
467
- picker.on("cancel", (data) => {
468
- console.log("Cancelled:", data);
469
- });
470
-
471
- picker.on("open", () => {
472
- console.log("Picker opened");
473
- });
474
-
475
- picker.on("update", (data) => {
476
- console.log("Time updated:", data);
477
- });
478
-
479
- picker.on("select:hour", (data) => {
480
- console.log("Hour selected:", data.hour);
481
- });
482
-
483
- picker.on("select:minute", (data) => {
484
- console.log("Minute selected:", data.minutes);
485
- });
486
-
487
- picker.on("select:am", (data) => {
488
- console.log("AM selected");
489
- });
490
-
491
- picker.on("select:pm", (data) => {
492
- console.log("PM selected");
493
- });
494
-
495
- picker.on("error", (data) => {
496
- console.log("Error:", data.error);
497
- });
498
-
499
- picker.on("clear", (data) => {
500
- console.log("Cleared, previous value:", data.previousValue);
501
- });
502
-
503
- // Wheel-specific events (wheel mode only)
504
- picker.on("wheel:scroll:start", (data) => {
505
- console.log("Scroll started on:", data.column);
506
- });
507
-
508
- picker.on("wheel:scroll:end", (data) => {
509
- console.log("Column:", data.column, "snapped to:", data.value);
510
- console.log("Previous value:", data.previousValue);
511
- });
512
-
513
- picker.once("confirm", (data) => {
514
- console.log("This runs only once");
515
- });
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
516
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);
517
105
  picker.off("confirm", handler);
518
- ```
519
-
520
- ### Legacy DOM Events (v3.x Only)
521
-
522
- ⚠️ **Only Available in v3.x - Completely Removed in v4.0.0**
523
106
 
524
- DOM events (e.g., `timepicker:confirm`) were removed in v4.0.0. Use the **EventEmitter API** shown above or **callback options** instead.
525
-
526
- **v3.x code (no longer works in v4):**
527
-
528
- ```javascript
529
- input.addEventListener("timepicker:confirm", (e) => {
530
- console.log("Time:", e.detail);
531
- });
107
+ // Static
108
+ TimepickerUI.getById("my-id");
109
+ TimepickerUI.getAllInstances();
110
+ TimepickerUI.destroyAll();
532
111
  ```
533
112
 
534
- **v4.0.0 alternatives:**
113
+ ## Options Overview
535
114
 
536
- ```javascript
537
- // Option 1: EventEmitter API
538
- picker.on("confirm", (data) => {
539
- console.log("Time:", data);
540
- });
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)
541
116
 
542
- // Option 2: Callback options
543
- const picker = new TimepickerUI(input, {
544
- callbacks: {
545
- onConfirm: (data) => {
546
- console.log("Time:", data);
547
- },
548
- },
549
- });
550
- ```
551
-
552
- Removed events: `timepicker:open`, `timepicker:cancel`, `timepicker:confirm`, `timepicker:update`, `timepicker:select-hour`, `timepicker:select-minute`, `timepicker:select-am`, `timepicker:select-pm`, `timepicker:error`
553
-
554
- ## Upgrade Guide: v3 → v4
555
-
556
- ### Breaking Changes
557
-
558
- **1. CSS Class Names Renamed**
559
-
560
- All CSS classes have been shortened from `timepicker-ui-*` to `tp-ui-*`:
561
-
562
- ```css
563
- /* v3 */
564
- .timepicker-ui-wrapper {
565
- }
566
- .timepicker-ui-modal {
567
- }
568
- .timepicker-ui-clock-face {
569
- }
570
- .timepicker-ui-hour {
571
- }
572
- .timepicker-ui-minutes {
573
- }
574
- .timepicker-ui-am {
575
- }
576
- .timepicker-ui-pm {
577
- }
578
-
579
- /* v4 */
580
- .tp-ui-wrapper {
581
- }
582
- .tp-ui-modal {
583
- }
584
- .tp-ui-clock-face {
585
- }
586
- .tp-ui-hour {
587
- }
588
- .tp-ui-minutes {
589
- }
590
- .tp-ui-am {
591
- }
592
- .tp-ui-pm {
593
- }
594
- ```
595
-
596
- **Impact:** If you have custom CSS targeting timepicker elements, update all class selectors.
597
-
598
- **2. Grouped Options Structure**
599
-
600
- All options are now organized into logical groups for better maintainability:
601
-
602
- ```javascript
603
- // v3
604
- const picker = new TimepickerUI(input, {
605
- clockType: "12h",
606
- theme: "dark",
607
- enableSwitchIcon: true,
608
- mobile: true,
609
- animation: true,
610
- backdrop: true,
611
- focusTrap: true,
612
- editable: true,
613
- onConfirm: (data) => console.log(data),
614
- });
615
-
616
- // v4 - Grouped options
617
- const picker = new TimepickerUI(input, {
117
+ ```typescript
118
+ new TimepickerUI(input, {
618
119
  clock: {
619
- type: "12h",
120
+ type: "12h" | "24h", // default: "12h"
620
121
  incrementHours: 1,
621
122
  incrementMinutes: 1,
622
- currentTime: { time: new Date(), updateInput: true },
623
- disabledTime: { hours: [1, 2, 3] },
624
- manualMinuteSwitch: false,
123
+ disabledTime: { hours: [], minutes: [], interval: "" },
124
+ currentTime: boolean | object,
625
125
  },
626
126
  ui: {
627
- theme: "dark",
628
- enableSwitchIcon: true,
629
- mobile: true,
127
+ theme: "basic" | "dark" | "m3-green" | "crane" | ..., // 10 themes
128
+ mode: "clock" | "wheel" | "compact-wheel", // default: "clock"
630
129
  animation: true,
631
130
  backdrop: true,
632
- editable: true,
633
- cssClass: "my-custom-class",
634
- inline: { enabled: false },
635
- },
636
- labels: {
637
- time: "Select Time",
638
- am: "AM",
639
- pm: "PM",
640
- ok: "OK",
641
- cancel: "Cancel",
642
- },
643
- behavior: {
644
- focusTrap: true,
645
- focusInputAfterClose: false,
646
- delayHandler: 300,
131
+ mobile: false,
132
+ editable: false,
133
+ inline: { enabled: false, containerId: "", showButtons: true, autoUpdate: false },
134
+ clearButton: false,
135
+ cssClass: "",
647
136
  },
648
- callbacks: {
649
- onConfirm: (data) => console.log(data),
650
- onCancel: (data) => console.log(data),
651
- onOpen: (data) => console.log(data),
652
- onUpdate: (data) => console.log(data),
653
- onSelectHour: (data) => console.log(data),
654
- onSelectMinute: (data) => console.log(data),
655
- onSelectAM: (data) => console.log(data),
656
- onSelectPM: (data) => console.log(data),
657
- onError: (data) => console.log(data),
658
- },
659
- });
660
- ```
661
-
662
- **2. Legacy DOM Events Removed**
663
-
664
- DOM events have been completely removed. Use EventEmitter API or callback options:
665
-
666
- ```javascript
667
- // v3 - Deprecated (removed in v4)
668
- input.addEventListener("timepicker:confirm", (e) => {
669
- console.log(e.detail);
670
- });
671
-
672
- // v4 - EventEmitter API (recommended)
673
- picker.on("confirm", (data) => {
674
- console.log(data);
675
- });
676
-
677
- // v4 - Or use callback options
678
- const picker = new TimepickerUI(input, {
679
- callbacks: {
680
- onConfirm: (data) => console.log(data),
681
- },
682
- });
683
- ```
684
-
685
- **3. setTheme() Method Removed**
686
-
687
- Programmatic theme setting via `setTheme()` has been removed. Use CSS classes instead:
688
-
689
- ```javascript
690
- // v3 - Removed in v4
691
- picker.setTheme({
692
- primaryColor: "#ff0000",
693
- backgroundColor: "#000000",
694
- });
695
-
696
- // v4 - Use CSS classes with CSS variables
697
- const picker = new TimepickerUI(input, {
698
- 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 },
699
141
  });
700
142
  ```
701
143
 
702
- ```css
703
- /* Define custom theme in CSS */
704
- .tp-ui-wrapper.my-custom-theme {
705
- --tp-primary: #ff0000;
706
- --tp-bg: #000000;
707
- --tp-surface: #f0f0f0;
708
- }
709
- ```
710
-
711
- **4. SSR-Safe Architecture**
712
-
713
- All modules are now SSR-safe and can be imported in Node.js environments without crashing.
144
+ ## Themes
714
145
 
715
- ### 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)
716
147
 
717
- 1. **Update option structure** - Move options into `clock`, `ui`, `labels`, `behavior`, `callbacks` groups
718
- 2. **Replace DOM events** - Switch to `picker.on()` EventEmitter API or callback options
719
- 3. **Remove setTheme() calls** - Use CSS classes with CSS variable overrides
720
- 4. **Test SSR compatibility** - If using Next.js/Nuxt/Remix, verify hydration works correctly
721
-
722
- ### New in v4
723
-
724
- - **Composition-based architecture** - No inheritance, pure composition with managers
725
- - **EventEmitter API** - Type-safe event handling with `on()`, `off()`, `once()`
726
- - **Callback bridge** - Callbacks automatically connected to EventEmitter
727
- - **SSR compatibility** - All modules can be imported in Node.js
728
- - **Better TypeScript types** - Fully typed event payloads and options
729
- - **Smaller bundle** - Removed unused code, optimized build (63.3 KB ESM)
730
- - **Focus improvements** - Auto-focus on modal open, auto-focus on minute switch
731
- - **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)
732
- - **Hide disabled options** - Remove disabled values from the list instead of dimming (v4.2.0)
733
- - **Auto-commit on scroll** - Auto-confirm time at scroll end in wheel modes (v4.2.0)
734
-
735
- ### Bundle Size Comparison
736
-
737
- - v3: 80 KB ESM
738
- - v4: 63.3 KB ESM (-20.9%)
739
-
740
- ---
741
-
742
- ## Upgrade Guide: v2 → v3
743
-
744
- ### Breaking Changes
745
-
746
- **1. CSS must be imported manually**
148
+ Available: `basic`, `crane`, `crane-straight`, `m3-green`, `m2`, `dark`, `glassmorphic`, `pastel`, `ai`, `cyberpunk`
747
149
 
748
150
  ```javascript
749
- // v3 - Required
750
- import "timepicker-ui/main.css";
751
- ```
752
-
753
- **2. Event names changed**
151
+ import "timepicker-ui/main.css"; // always required
152
+ import "timepicker-ui/theme-dark.css"; // theme-specific stylesheet
754
153
 
755
- ```javascript
756
- // v2
757
- input.addEventListener("show", ...);
758
- input.addEventListener("accept", ...);
759
-
760
- // v3
761
- input.addEventListener("timepicker:open", ...);
762
- input.addEventListener("timepicker:confirm", ...);
154
+ new TimepickerUI(input, { ui: { theme: "dark" } });
763
155
  ```
764
156
 
765
- **3. Use callbacks instead of events**
766
-
767
- ```javascript
768
- // v3 - Recommended
769
- const picker = new TimepickerUI(input, {
770
- onConfirm: (data) => console.log(data),
771
- onCancel: (data) => console.log("Cancelled"),
772
- });
773
- ```
157
+ ## Plugins
774
158
 
775
- **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)
776
160
 
777
161
  ```javascript
778
- // v2 - Removed input from DOM
779
- picker.destroy();
780
-
781
- // v3 - Only destroys picker, keeps input
782
- picker.destroy();
783
- ```
784
-
785
- ### New in v3
786
-
787
- - Inline mode
788
- - Instance management (`getById`, `destroyAll`)
789
- - Direct callbacks
790
- - 5 new themes
791
- - `getValue()` and `setValue()` methods
792
- - Better TypeScript support
793
- - **EventEmitter API** for modern event handling (`on`, `off`, `once`)
794
-
795
- ### Migration to EventEmitter (v3.x)
796
-
797
- 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";
798
166
 
799
- ```javascript
800
- // Old way (deprecated, will be removed in v4)
801
- input.addEventListener("timepicker:confirm", (e) => {
802
- console.log(e.detail);
803
- });
167
+ // Register once at app startup
168
+ PluginRegistry.register(RangePlugin);
169
+ PluginRegistry.register(TimezonePlugin);
170
+ PluginRegistry.register(WheelPlugin);
804
171
 
805
- // New way (recommended)
806
- picker.on("confirm", (data) => {
807
- console.log(data);
808
- });
172
+ new TimepickerUI(input, { range: { enabled: true } });
809
173
  ```
810
174
 
811
- Benefits:
175
+ ## Upgrading
812
176
 
813
- - Cleaner API without prefixes
814
- - Better TypeScript support
815
- - No DOM pollution
816
- - Memory-efficient (automatic cleanup on destroy)
817
- - 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)
818
179
 
819
180
  ## Framework Integration
820
181
 
821
- ### 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)**
822
185
 
823
- ```jsx
186
+ ```tsx
824
187
  import { useEffect, useRef } from "react";
825
188
  import { TimepickerUI } from "timepicker-ui";
826
189
  import "timepicker-ui/main.css";
827
190
 
828
- function App() {
829
- const inputRef = useRef(null);
830
-
191
+ function TimePicker() {
192
+ const ref = useRef<HTMLInputElement>(null);
831
193
  useEffect(() => {
832
- if (!inputRef.current) return;
833
- const picker = new TimepickerUI(inputRef.current);
194
+ if (!ref.current) return;
195
+ const picker = new TimepickerUI(ref.current);
834
196
  picker.create();
835
197
  return () => picker.destroy();
836
198
  }, []);
837
-
838
- return <input ref={inputRef} placeholder="Select time" />;
199
+ return <input ref={ref} />;
839
200
  }
840
201
  ```
841
202
 
842
- ### Vue 3
843
-
844
- ```vue
845
- <template>
846
- <input ref="pickerInput" placeholder="Select time" />
847
- </template>
848
-
849
- <script setup>
850
- import { onMounted, ref } from "vue";
851
- import { TimepickerUI } from "timepicker-ui";
852
- import "timepicker-ui/main.css";
853
-
854
- const pickerInput = ref(null);
855
-
856
- onMounted(() => {
857
- if (!pickerInput.value) return;
858
- const picker = new TimepickerUI(pickerInput.value);
859
- picker.create();
860
- });
861
- </script>
862
- ```
863
-
864
- ### Angular
865
-
866
- ```typescript
867
- import { Component, ElementRef, ViewChild, AfterViewInit } from "@angular/core";
868
- import { TimepickerUI } from "timepicker-ui";
869
-
870
- @Component({
871
- selector: "app-root",
872
- template: `<input #timepickerInput placeholder="Select time" />`,
873
- })
874
- export class App implements AfterViewInit {
875
- @ViewChild("timepickerInput") inputRef!: ElementRef<HTMLInputElement>;
876
-
877
- ngAfterViewInit() {
878
- const picker = new TimepickerUI(this.inputRef.nativeElement);
879
- picker.create();
880
- }
881
- }
882
- ```
203
+ > There is also a dedicated [React wrapper package](https://github.com/pglejzer/timepicker-ui-react).
883
204
 
884
- Add to `angular.json` styles:
205
+ ## Performance
885
206
 
886
- ```json
887
- "styles": ["src/styles.css", "timepicker-ui/main.css"]
888
- ```
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
889
211
 
890
212
  ## Development
891
213
 
892
- Development tooling is in the [`app/`](./app) directory. See [`app/README.md`](./app/README.md) for details.
893
-
894
- ## License
895
-
896
- MIT © [Piotr Glejzer](https://github.com/pglejzer)
214
+ See [`app/README.md`](./app/README.md) for local development setup.
897
215
 
898
216
  ## Contributing
899
217
 
900
- 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).
901
219
 
902
- ## Browser Support
903
-
904
- Chrome 60+, Firefox 55+, Safari 12+, Edge 79+, iOS Safari 12+, Chrome Android 60+
905
-
906
- ## Support
220
+ ## License
907
221
 
908
- - [Documentation](https://pglejzer.github.io/timepicker-ui-docs/)
909
- - [Report Bug](https://github.com/pglejzer/timepicker-ui/issues)
910
- - [Discussions](https://github.com/pglejzer/timepicker-ui/discussions)
222
+ MIT © [Piotr Glejzer](https://github.com/pglejzer)