timepicker-ui
4.6.0GitHub
timepicker-ui
Project / Changelog

Changelog

All notable changes to this project are documented here

Current Version

v4.6.0 - Released October 19, 2026

Version 4.6.0 - October 19, 2026

Added

  • ui.inline.embedded - Renders the inline picker as a role="group" with no aria-modal, shadow or minimum height. It never takes or traps focus. Default false
  • Range methods - setRange({ from, to }), getRange() and setActivePart(part). They need RangePlugin, and setRange emits range:validation. The types TimeOfDay, RangeTimeValue and RangeTimeState are exported
  • range.linked - Set false to make From and To independent: no To-before-From limit, no minDuration / maxDuration check, and the To tab is enabled at once. Default true
  • clock.isTimeDisabled - Disables times with a function (hour 0-23), combined with clock.disabledTime. A function that throws counts as enabled, and its error is rethrown
  • refreshDisabledTime() - Re-applies disabled times after the data behind isTimeDisabled changes, without re-rendering
  • on() and once() return a remover - Both return a function that removes the handler

Version 4.5.0 - October 14, 2026

Check before upgrading

These changes can affect an existing setup. Each one is described under Changed.

  • The .tp-ui-ripple / .tp-ui-ripple-effect classes and the ripple keyframes are removed
  • --tp-wheel-highlight-radius, --tp-popover-radius and --tp-btn-radius are no longer defined on :root
  • --tp-focus-ring-halo is no longer read, and --tp-shadow-focus-ring defaults to none
  • The AM/PM value is always AM / PM, whatever labels.am / labels.pm are set to
  • Out-of-range clock.disabledTime.interval edges such as 25:00 are now rejected and reported through error / callbacks.onError
  • setValue() with a string that is not a time now emits error / callbacks.onError instead of being ignored
  • The scroll lock now styles <html>, and <body> only when it scrolls. CSS that relied on the padding-right added to body should use --tp-scrollbar-gap
  • With CSS anchor positioning, the compact-wheel popover gets tp-ui-popover-anchored and no inline top, left or width. Review CSS that positions .tp-ui-popover by hand
  • With ui.inline.showButtons: true, inline OK and Cancel no longer remove the picker from its container
  • behavior.delayHandler defaults to 0 instead of 300, so the picker closes without a pause

Security

  • Labels are HTML-escaped - Label text in labels.*, range.fromLabel / range.toLabel and timezone.label is now HTML-escaped, so it cannot inject markup. The icon options ui.keyboardIcon and ui.clockIcon (ui.iconTemplate / ui.iconTemplateMobile) still accept HTML

Added

  • Optional seconds - Set clock.showSeconds: true to add a seconds field, face and wheel column. Values become HH:MM:SS (HH:MM:SS AM in 12h). Off by default, with no change when off
  • clock.incrementSeconds and autoSwitchToSeconds - clock.incrementSeconds (1, 5, 10, 15, 20 or 30, default 1) sets the seconds step, and clock.autoSwitchToSeconds (default true) moves to the seconds face after a minute is picked
  • Disabled seconds - clock.disabledTime.seconds disables the listed seconds in every minute, and clock.disabledTime.interval edges can include seconds. Edges without seconds cover the whole minute, so existing intervals still work
  • labels.second - Default 'Second'. Names the seconds field, its caption in the keyboard view, its wheel column and its announcement
  • select:second and onSelectSecond - New event and callback, with the SelectSecondEventData type
  • Seconds in values and payloads - With seconds on, getValue() and the open, confirm and update payloads include seconds, the error payload includes inputSecond, and setValue() accepts '14:30:15'. Values without seconds read as :00
  • Range with seconds - range:confirm and range:validation include durationSeconds. duration stays in whole minutes, and range.minDuration / range.maxDuration are compared with the exact length
  • Seconds layout class and tokens - The modal gets the tp-ui-has-seconds class when seconds are shown, and new CSS tokens such as --tp-wheel-column-width-seconds size the seconds layout
  • Clearer option names - Such as ui.initialView: 'keyboard' for ui.mobile: true and ui.viewSwitch for ui.enableSwitchIcon. The old names work until 5.0, and the options page lists every pair
  • setOptions(), select:period, switch:face and clock.type: 'auto' - setOptions(), the select:period and switch:face events with callbacks.onSelectPeriod and callbacks.onSwitchFace, and clock.type: 'auto', which picks 12h or 24h from the browser locale
  • ui.appendTo and ui.inline.container - ui.appendTo (where the modal goes) and ui.inline.container (where the inline picker renders) accept an element as well as a CSS selector
  • Keyboard control of the clock face - Arrow keys, PageUp / PageDown and Home / End change the value, and Enter or Space moves to the next face. See the accessibility page
  • Mobile landscape size tokens - --tp-mobile-landscape-width, --tp-mobile-landscape-height, --tp-mobile-landscape-height-range and --tp-mobile-landscape-height-timezone set the mobile keyboard view size in landscape
  • --tp-focus-ring - Sets the focus ring color, falling back to --tp-primary
  • labels.announceCleared - Sets the text announced after Clear (default 'Time cleared')
  • ui.inline.autoUpdate works - An inline picker updates its input as you pick
  • Callbacks for every event - callbacks.onShow, callbacks.onSwitchView, callbacks.onWheelScrollStart and callbacks.onWheelScrollEnd, so every public event now has a matching callback
  • Material Design 3 press feedback - A ripple plus hover, focus and pressed state layers, tuned with --tp-state-hover-opacity, --tp-state-focus-opacity, --tp-state-pressed-opacity and --tp-ripple-opacity. With prefers-reduced-motion, a press shows the pressed layer without the ripple
  • --tp-dropdown-option-hover-text - Sets the text color of a hovered or focused timezone option
  • --tp-scrollbar-gap - Set on <html> to the removed scrollbar width while a picker locks the page. Use it to pad fixed or sticky elements
  • --tp-popover-edge-margin - Default 4px. Sets the smallest gap between the compact-wheel popover and the viewport's side edges

Changed

  • AM/PM value is always AM or PM - labels.am / labels.pm only change the shown and announced text, not the input value or getValue().type
  • update() applies every option group - update({ options }) now applies every option group, including range, timezone, wheel and clearBehavior, and inline pickers re-render with new labels
  • Inline OK and Cancel - With ui.inline.showButtons: true, both keep the picker visible. OK writes the value to the input and emits confirm. Cancel restores the input's value and emits cancel. close() still removes it
  • Stricter interval validation - clock.disabledTime.interval rejects out-of-range edges such as 13:00 PM and reports them through error / callbacks.onError. On create() the picker does not open, and update() keeps the old options. Lowercase am / pm are accepted
  • setValue() converts the other clock format - '14:30' becomes 02:30 PM in a 12h picker. A string that is not a time emits error / callbacks.onError. An empty string is still ignored
  • Clock and wheel agree on disabled times - Both modes now give the same result at interval edges
  • Range 'to' face disabled times - Times before 'from' and inside disabled intervals are both disabled, and in 12h mode clock.disabledTime.hours / minutes apply too
  • Theme color contrast - Text and focus contrast meet WCAG AA in pastel, ai, m2, m3-green, crane and crane-straight, and dark accents follow --tp-primary
  • One focus ring - Every control shows one 3px focus ring. The gap ring and halo are removed: --tp-focus-ring-halo is no longer read and --tp-shadow-focus-ring defaults to none
  • box-sizing set by the picker - The picker sets box-sizing: border-box on its own elements, so it no longer depends on a page-wide reset
  • AM/PM and view-switch hover - Hover on unselected AM/PM buttons and the view-switch icon now shows only the state layer
  • Validation message glyph removed - The warning-sign glyph before the validation message is removed. Screen readers read it aloud and it could not be turned off
  • Radius tokens no longer on :root - --tp-wheel-highlight-radius, --tp-popover-radius and --tp-btn-radius are no longer defined on :root, so theme values reach them. Setting them still works, but reading them with var() outside the picker gets no value
  • Old ripple removed - .tp-ui-ripple, .tp-ui-ripple-effect and the ripple keyframes are removed. Move custom CSS to the new state and ripple tokens
  • Compact-wheel popover - With wheel.placement, the popover uses the Popover API and CSS anchor positioning, so a parent's overflow: hidden or z-index no longer clips or covers it. Browsers without anchor positioning use a script fallback
  • Popover tokens and styling hooks - --tp-popover-gap, --tp-popover-min-width and --tp-popover-max-width now take effect. New popover styling hooks: the tp-ui-popover-anchored and tp-ui-popover-constrained classes and the data-popover-request attribute
  • Scroll lock on <html> - With ui.enableScrollbar: false (the default), the lock sets overflow-y: hidden on <html>, and on <body> only when it scrolls. Only the element that lost its scrollbar gets padding-right
  • Legacy 'Spacebar' key value - No longer counts as Space. Only very old browsers, such as old Edge and Internet Explorer, send it
  • Open and close without a pause - The picker fades in over 200 ms and out over 150 ms, both starting on the click
  • Reduced motion and ui.animation: false - With prefers-reduced-motion or ui.animation: false, the picker opens and closes at once, with no wait
  • behavior.delayHandler default - Now defaults to 0 instead of 300. Set it to 300 to keep the old pause before closing
  • Landscape view switch - In mobile landscape, switching between the keyboard and clock views no longer jumps: the picker resizes smoothly while its content cross-fades
  • Hand animation respects ui.animation - ui.animation: false and prefers-reduced-motion now also stop the clock hand from animating when the face switches

Deprecated

  • Old names of renamed options, select:am / select:pm, update() - For options such as ui.mobile or labels.hourLabel, use the new names listed on the options page. For select:am / select:pm use select:period, and for update() use setOptions()
  • OptionTypes - Use TimepickerOptions
  • clock.currentTime.locales - Has no effect. The library formats the value
  • getValue().degreesHours / degreesMinutes - Internal clock-hand angles. Use hour / minutes
  • labels.toggleLabel - Has no effect now that the view switch is one button. Use labels.switchToKeyboard / labels.switchToClock
  • labels.format24Label, labels.timezoneSelectorLabel - Never had an effect. The timezone selector label comes from labels.timezone
  • focusInputAfterCloseModal - Legacy option type. Use behavior.returnFocus
  • hide event, HideEventData - Never emitted
  • range:get-disabled-time, range:update-disabled events - Never emitted
  • ErrorEventData.rejectedHour / rejectedMinute - Always undefined

Each item still works unless its entry says otherwise. All of them are removed in 5.0.

Fixed

  • Disabled hour click - Clicking a disabled hour no longer switches the clock to the minutes view
  • Stacked event handlers - Reopening a picker, range pickers included, no longer stacks event handlers
  • onClear fired twice - callbacks.onClear fires once per clear
  • close(true) - Commits the current selection to the input, like the OK button
  • Double confirm or cancel - Once OK, Cancel or Escape starts a close, further input is ignored: a double-click or second Escape no longer fires confirm / cancel twice
  • getValue() and destroy() while closing - While the picker is closing, getValue() returns the input value and destroy() cancels the pending close
  • Opening while the picker fades out - Enter or a click on the input while the picker fades out is ignored, instead of firing show / callbacks.onShow without opening the picker
  • Inline wheel Cancel with commitOnScroll - Inline wheel pickers with wheel.commitOnScroll: Cancel right after a scroll no longer fires confirm / callbacks.onConfirm after cancel
  • Callbacks set through update() - Callbacks passed later through update({ options: { callbacks } }) take effect, replaced callbacks stop firing, and inline pickers no longer fire onOpen again after update()
  • update() rewrites the input format - Changing clock.type or clock.showSeconds through update() rewrites the input in the new format, range values (from - to) included, so getValue() reads it correctly
  • update() on an open picker - update() on an open modal or popover re-renders it with the new options right away. An unconfirmed selection resets to the input value
  • show before open - When the user opens the picker, show / callbacks.onShow now fire before open / callbacks.onOpen. picker.open() still emits only open
  • Invalid value on open - Opening the picker on an invalid value shows and announces the error (labels.invalidTimeFormat) inside the dialog, and the input gets aria-invalid and aria-describedby
  • Invalid disabledTime reported - An invalid clock.disabledTime, including an interval without '-', is reported through error / callbacks.onError instead of silently breaking the picker
  • Clear button on first open - The Clear button is enabled on the first open after page load
  • data-open with quotes - Opener elements with a quote or backslash in their data-open value no longer break the picker
  • data-open inside a wrapper element - Passing a wrapper element instead of the input to new TimepickerUI() makes the wrapper the picker root, so data-open buttons inside it open the picker
  • setValue() with 12h values and an open clock - setValue() accepts zero-padded 12h hours such as '03:45 PM' and 12h values longer than 8 characters. While the clock is open, it updates the whole face, not only the hand
  • Degrees while open - getValue().degreesHours / degreesMinutes and the open payload degrees now follow clicks and drags on the clock
  • currentTime in 12h locales - clock.currentTime: true keeps the AM/PM part
  • clock.smoothHourSnap - On release, the hand animates to the hour instead of jumping. With reduced motion it moves without animation
  • update after closing mid-drag - Closing the picker during a clock drag, for example with Escape while the mouse button is held, no longer emits update events afterwards
  • Arrow keys on the time fields - Arrow keys on the minute field step by clock.incrementMinutes. Arrow keys and typed values on the time fields skip disabled times, and OK stays disabled while the shown time is disabled
  • clock.incrementHours - Now works with the default clock.smoothHourSnap: true, in wheel and compact-wheel modes, and with the hour field's arrow keys, PageUp / PageDown and Home / End
  • Clock hand froze on held arrow keys - Holding an arrow key no longer freezes the clock hand
  • Slow key presses with several pickers - Key presses no longer slow down pages with several open or inline pickers
  • View-switch button - The view-switch control is one button with one Tab stop, a label that says what it does and stays localized, a focus ring and a 40px target
  • ui.iconTemplateMobile placement - Goes inside the view-switch button, like ui.iconTemplate, instead of replacing it on the first mobile render
  • Clock drag announcements - Drag announcements use labels.announceHour / labels.announceMinute
  • ARIA state in sync - The hour and minute fields' aria-valuenow / aria-valuetext and the AM/PM aria-pressed now stay in sync with the shown time
  • Range tab placeholder - Empty range tabs no longer read the --:-- placeholder aloud, also after Clear
  • Clock face and numbers for screen readers - After switching views, the decorative clock numbers stay hidden from screen readers, and on mobile the clock face is no longer hidden
  • Time field names after switching views - The hour and minute fields' accessible names follow the view: labels.mobileHour / mobileMinute in the keyboard view, labels.hourLabel / minuteLabel in the clock view
  • Focus rings - Focus rings show only during keyboard use, and now appear on the hour and minute fields in every theme, the mobile Cancel and Clear buttons and the timezone dropdown
  • prefers-contrast: more - High-contrast styles now apply with prefers-contrast: more. They used prefers-contrast: high, which browsers never match
  • Forced colors - In Windows High Contrast, the selection stays visible in system colors, and disabled clock numbers use GrayText
  • --tp-font-family - Now sets the picker font as documented, and the time fields and buttons inherit it
  • Wheel separator - The ':' separator is visible in the basic and m2 themes
  • Wheel disabled items - Keyboard navigation skips disabled items, disabled options have aria-disabled set to true, and zero-padded clock.disabledTime.minutes strings such as '05' work
  • Wheel header fields - The header hour and minute fields move the columns and stay in sync when disabled times move a column
  • Wheel times between steps - A time between a column's steps, such as '10:07' with clock.incrementMinutes: 5, now lands on the nearest item instead of a wrong one
  • Theme radius values - In crane-straight the wheel selection highlight and the popover are square again, and the m2 popover uses its 4px radius
  • Compact-wheel AM/PM column - Shows labels.am / labels.pm and opens on the input's period, and interval-disabled minutes follow the selected hour and period
  • Compact-wheel getValue() - getValue() while the picker is open returns the wheel selection
  • Compact-wheel Clear - Clear no longer flips AM/PM, or jumps back to the input value with clearBehavior.clearInput: false
  • Compact-wheel popover size - The popover is as wide as its columns, so 12h with seconds fits, and it scrolls inside a short viewport
  • Compact-wheel popover near a viewport edge - With wheel.placement: 'top' or 'bottom', the popover switches sides when fewer than three wheel rows fit, instead of covering the input
  • Compact-wheel popover in right-to-left containers - With dir='rtl' set only on a container, the popover lines up with the input's right edge, with or without CSS anchor positioning
  • Compact-wheel popover tokens in rem, em or calc() - Without CSS anchor positioning, the --tp-popover-* gap, margin and width tokens now work in rem, em or calc(), not only px
  • wheel.placement default - Types and docs: wheel.placement has no default (the JSDoc said 'auto'). Without it, compact-wheel opens as a modal, as before
  • Range by keyboard - A range can be completed with the keyboard alone, and focus moves to the hour field after the From time is committed
  • Range duration limits - OK stays disabled while the range is outside range.minDuration / range.maxDuration, also when picked with the pointer
  • Timezone default fallback - A timezone.default missing from the list falls back to the browser's zone if listed, otherwise the first option, instead of an empty label
  • Timezone Clear - Clear resets the selected zone, and the trigger then shows timezone.label instead of the English 'Timezone...' or 'undefined...'
  • Timezone check mark - Screen readers no longer read 'check mark' as part of the selected zone name
  • Timezone field in right-to-left pages - The closed field is mirrored in right-to-left pages, with the arrow on the left
  • Crane and pastel contrast - Hovered and focused timezone options in crane / crane-straight and the pastel footer buttons are now readable
  • Page shift on open - Opening a modal picker no longer shifts the page sideways or overwrites its padding-right, and pages that scroll on <html> are locked too
  • 12h header overflow - The 12h header no longer overflows by 2px and fits phones 330px wide or narrower
  • Inline picker layout - Inline pickers follow the theme's corner radius and no longer leave empty space: 500px in compact-wheel and mobile, or a 48px footer band with ui.inline.showButtons: false
  • Mobile landscape size - The keyboard view size follows the orientation after rotating, and uses the taller timezone height when range and timezone are both on
  • Editable after leaving mobile - After update() sets ui.mobile: false, the desktop time fields are no longer left editable
  • Drag out to the backdrop - A press that starts inside the picker and ends over the backdrop, such as dragging the clock hand, no longer closes it
  • Landscape on large phones - The landscape layout now applies to any landscape viewport up to 480px tall. Current large phones, such as the iPhone 16 Pro Max, got the portrait card cut off
  • Highlight follows the hand - When the clock switches faces, the highlighted number follows the hand instead of turning white before the hand arrives

Upcoming in 5.0

5.0 will change how plugins are built and registered, and will rename the part of each CSS class after the tp-ui- prefix to follow Material Design component naming. It also removes the items deprecated above and other unused API, and ships with a migration guide. See the roadmap for details.

In 5.0, behavior.returnFocus defaults to true: on close, focus goes back to the input, or to the data-open element that opened the picker. Set behavior.returnFocus: true now to return focus to the input.

Version 4.4.0 - June 10, 2026

Added

  • Blueprint and Blueprint Dark themes - Two new built-in themes - 'blueprint' (light precision-instrument look: cobalt-blue accent, white surfaces, hairline borders) and 'blueprint-dark' (cobalt accent on near-black neutrals). Enable via ui.theme: 'blueprint' / 'blueprint-dark' and import the matching timepicker-ui/theme-blueprint.css / theme-blueprint-dark.css stylesheet. Brings the bundled theme count from 10 to 12, each still tree-shakeable as a separate CSS file
  • 16 localizable accessibility labels - New optional labels.* options for fully localizable screen-reader and ARIA text: hourLabel, minuteLabel, clockLabel, periodLabel, timeLabel, format24Label, rangeSelectionLabel, switchToKeyboardLabel, switchToClockLabel, toggleLabel, timezoneSelectorLabel, announceHour, announceMinute, announceAmSelected, announcePmSelected, and invalidTimeFormat. All ship with English defaults - previously these strings were hardcoded
  • Home / End / Page keyboard navigation - Home / End / PageUp / PageDown key support on the hour and minute spinbuttons (clock mode) and on the wheel columns, in addition to the existing Arrow keys, for faster keyboard time selection

Changed

  • Localizable assistive-technology text - Screen-reader announcements and ARIA labels across the clock, wheel, range, timezone, and modal now read from the labels group instead of hardcoded English, so all assistive-technology text can be localized
  • Spinbutton aria-valuetext - Spinbutton inputs now expose aria-valuetext and keep it in sync on every change for more accurate screen-reader output
  • Smarter focus trap - Focus trap now skips elements marked aria-disabled / aria-hidden, so keyboard tabbing only lands on genuinely actionable controls

Fixed

  • Focus-ring visibility - Improved focus-ring visibility with a new high-contrast focus halo, so the focused control is clearly distinguishable on every theme
  • Firefox number-input spinner buttons - Native browser spinner buttons no longer appear on the clock-mode time inputs in Firefox - number inputs now render cleanly and consistently across browsers (-moz-appearance: textfield + -webkit-appearance reset). Thanks to @in-omnibus (https://github.com/in-omnibus) for reporting and proposing the fix (https://github.com/pglejzer/timepicker-ui/issues/166)
  • Scroll lock release - Fix the page scroll lock not releasing after rapid open/close cycles (or with multiple pickers on a page), which could leave the whole page unscrollable; body scroll locking is now reference-counted across instances
  • theme-crane-straight.css export - Added the missing theme-crane-straight.css entry to the package exports map, so the documented import 'timepicker-ui/theme-crane-straight.css' subpath now resolves instead of throwing ERR_PACKAGE_PATH_NOT_EXPORTED

Version 4.3.0 - March 26, 2026

Added

  • Plugin templateProvider hook - Plugins can register their own modal templates without core static imports, enabling true tree-shaking
  • Plugin clearHandler hook - Plugins can register custom clear logic executed when the clear button is pressed
  • New exported types - TemplateProvider and ClearHandler types available from the public API

Changed

  • Tree-shakeable plugin system - Unused plugins (wheel, range, timezone) are completely excluded from the final bundle when not imported. Core no longer statically imports any plugin implementation modules
  • Wheel template registration - Wheel template is no longer statically bundled with core - it is registered by the WheelPlugin at import time
  • Plugin clear logic decoupled - Range and timezone clear logic moved from core ClearButtonManager to plugin-provided clear handlers

Version 4.2.2 - March 24, 2026

Fixed

  • WheelOptions not exported - WheelOptions interface was defined but missing from the public API export. TypeScript consumers can now import it directly to type wheel/compact-wheel configuration objects

Version 4.2.1 - March 18, 2026

Fixed

  • Plugin type declarations - Plugin .d.ts files (range, timezone, wheel) used 'never' for factory parameters, causing PluginRegistry.register() to reject plugins in strict TypeScript projects

Version 4.2.0 - March 18, 2026

Added

  • Wheel mode - Scroll-spinner interface replacing the analog clock face. Enable via ui.mode: 'wheel'. Supports 12h/24h, all themes, disabled time, and keyboard navigation
  • Compact-wheel mode - Headerless wheel picker without the hour/minute inputs header. Enable via ui.mode: 'compact-wheel'. Combine with wheel.placement for popover positioning
  • wheel.placement option - Popover placement ('auto', 'top', 'bottom') for compact-wheel mode. Opens as a popover anchored to the input instead of a centered modal
  • wheel.hideDisabled option - Completely remove disabled hours/minutes from the wheel list instead of dimming them. Only applies to wheel and compact-wheel modes
  • wheel.commitOnScroll option - Auto-commit time at the end of wheel scrolling without pressing OK. Only applies to wheel and compact-wheel modes
  • Clear button - Reset time selection with a dedicated clear button. Enabled by default via ui.clearButton option
  • clearBehavior.clearInput option - Control whether clearing also empties the input field value (default: true)
  • labels.clear option - Customize the clear button text (default: "Clear")
  • onClear callback and clear event - New callback and EventEmitter event with previousValue payload
  • New exported types - ClearEventData, ClearBehaviorOptions, WheelScrollStartEventData, WheelScrollEndEventData
  • wheel.ignoreOutsideClick option - Prevent the picker from closing when clicking outside its area. Only applies to wheel and compact-wheel modes

Fixed

  • Rapid-click race condition - Rapidly clicking the input before the modal fully opened could cause the picker to remain invisible in the DOM or permanently block the input element
  • Range plugin landscape layout - From/To header tabs were mispositioned in landscape orientation. Fixed with proper absolute positioning within the grid layout
  • Timezone plugin landscape layout - Timezone picker was broken in landscape orientation. Implemented CSS Grid layout that properly positions header, timezone selector, and clock face side by side
  • AM/PM border color in landscape - Hardcoded black border replaced with theme-aware var(--tp-border) variable
  • Compact-wheel popover viewport detection - Popover did not flip from bottom to top early enough near the viewport edge. Added safety threshold, switched to accurate visible viewport measurement, and improved fallback to prefer the side with more space
  • PluginFactory type safety - PluginFactory type now correctly accepts CoreState and EventEmitter parameters instead of using any/never workarounds. Removed unsafe type assertions from the plugin registry

Version 4.1.7 - March 8, 2026

Fixed

  • Range plugin 12h AM/PM sync - AM/PM state desynchronization when switching between From/To tabs. Clock now correctly syncs internal AM/PM state with saved range values

Version 4.1.6 - February 14, 2026

Fixed

  • Range plugin minute disabling - Minutes were incorrectly disabled for all hours in TO picker instead of only for the FROM hour. Now minutes are blocked only when TO hour equals FROM hour (both 12h and 24h modes)
  • setValue() method - Fixed setValue() not working correctly when called programmatically after initialization

Version 4.1.5 - February 10, 2026

Fixed

  • DisabledTime update() method - update() now properly calls getDisableTime() to refresh disabled time state after options change

Version 4.1.4 - February 9, 2026

Fixed

  • iOS Mobile hover state fix - v4.1.3 didn't fix the hover bug on all mobile devices, especially iOS Safari. Enhanced CSS with additional touch-action properties and improved media query specificity to completely prevent hover state persistence on all mobile browsers
  • Range mode validation - OK button now correctly disabled when end time ≤ start time. AM/PM changes automatically trigger validation to prevent invalid range selection

Version 4.1.3 - February 9, 2026

Fixed

  • Mobile hover state persistence - Fixed hover state remaining on hour/minute inputs and AM/PM buttons after touch events on mobile devices
  • Range mode AM/PM state - Fixed AM/PM remaining selected when switching from filled 'from' to empty 'to' in range mode
  • Range mode input updates - Input value now updates only on confirm instead of during selection in range mode
  • Mobile view class assignment - Added missing mobile class to wrapper-type-time, AM/PM buttons and input-ripple-wrapper when toggling mobile view
  • Input readonly removal on view switch - Fixed readonly attribute not being removed when switching from desktop to mobile view with enableSwitchIcon
  • Dynamic input validation on view switch - Mobile input validation now works when switching views - blur handlers check readonly attribute dynamically instead of static options

Added

  • Range plugin TypeScript types - Exported RangeOptions, RangeConfirmEventData, RangeSwitchEventData, RangeValidationEventData for better type safety

Version 4.1.2 - February 8, 2026

Fixed

  • Range button text visibility - Fixed text visibility in range buttons for crane, crane-straight, dark and cyberpunk themes
  • Documentation examples - Corrected timezone option property name from defaultTimezone to default in all examples

Added

  • Auto-switch to minutes - Hour and minute input elements automatically update active state when hour is selected on desktop

Version 4.1.1 - February 1, 2026

Fixed

  • Mobile input validation - Fixed issue where invalid time values (e.g., 169:70) could be entered via keyboard in mobile mode. Inputs now auto-clamp to valid ranges

Internal

  • Test coverage - Expanded test suite to 1100+ tests with 90%+ code coverage

Version 4.1.0 - January 31, 2026

Added

  • Plugin architecture - Range and Timezone as optional plugins - import only what you need for smaller bundles
  • Smooth hour snapping - New smoothHourSnap option (default: true) enables fluid hour dragging with animation
  • Range mode time blocking - When from time is selected, to picker automatically disables earlier times with visual feedback
  • Timezone dropdown theming - CSS variables --tp-dropdown-option-bg and --tp-dropdown-option-text for consistent styling

Changed

  • Modular bundle structure - Core bundle no longer includes Range and Timezone code. Reduces default bundle size
  • Smooth hour dragging is now default - smoothHourSnap defaults to true. Set to false for legacy discrete jump behavior

Version 4.0.3 - January 23, 2026

Fixed

  • Desktop/mobile view switching - Fixed enableSwitchIcon toggle not updating label text (Select time <-> Enter time), icon (keyboard <-> schedule), and Hour/Minute label visibility

Version 4.0.2 - November 23, 2025

Fixed

  • Firefox Compatibility - Fixed 'TouchEvent is not defined' error in DragHandlers - changed from instanceof TouchEvent to 'touches' in event check

Added

  • Exported Grouped Option Types - Added exports for TimepickerOptions, ClockOptions, UIOptions, LabelsOptions, BehaviorOptions, CallbacksOptions to support type-safe option composition
  • onUpdate Event Emissions - Added missing onUpdate event emissions in EventManager and ClockManager for real-time value synchronization

Firefox Fix Details

index.ts
// Before (causing error on Firefox)
if (event instanceof TouchEvent) { // ERROR: TouchEvent not defined
const touch = event.touches[0];
}
// After (cross-browser compatible)
if ('touches' in event) { // OK: Works everywhere
const touch = event.touches[0];
}

Version 4.0.1 - November 21, 2025

Fixed

  • TypeScript Callback Types - All callback functions now have proper generic types instead of unknown
  • Type-safe Event Payloads - onConfirm, onOpen, onUpdate, and other callbacks now correctly infer event data types
  • Improved IntelliSense - Full autocomplete for event data properties without requiring manual type annotations

Before (4.0.0)

index.ts
// Callbacks had unknown types
callbacks: {
onConfirm: (data) => {
// data: unknown - no autocomplete
console.log(data.hour); // TypeScript error
}
}

After (4.0.1)

index.ts
// Callbacks are now properly typed
callbacks: {
onConfirm: (data) => {
// data: ConfirmEventData - full autocomplete
console.log(data.hour); // string | undefined
console.log(data.minutes); // string | undefined
console.log(data.type); // string | undefined
}
}

Version 4.0.0 - November 21, 2025

BREAKING CHANGES - Migration Required

All CSS class names and CSS variable names have been renamed:

index.diff
// CSS Class Names (ALL classes changed)
- .timepicker-ui-*
+ .tp-ui-*
Examples:
- .timepicker-ui-wrapper -> .tp-ui-wrapper
- .timepicker-ui-modal -> .tp-ui-modal
- .timepicker-ui-clock-face -> .tp-ui-clock-face
- .timepicker-ui-hour -> .tp-ui-hour
- .timepicker-ui-minutes -> .tp-ui-minutes
- .timepicker-ui-am -> .tp-ui-am
- .timepicker-ui-pm -> .tp-ui-pm
- .timepicker-ui-ok-btn -> .tp-ui-ok-btn
- .timepicker-ui-cancel-btn -> .tp-ui-cancel-btn
// CSS Variables (ALL variables changed)
- --timepicker-*
+ --tp-*
Examples:
- --timepicker-bg -> --tp-bg
- --timepicker-primary -> --tp-primary
- --timepicker-text -> --tp-text
- --timepicker-surface -> --tp-surface
- --timepicker-border -> --tp-border
- --timepicker-shadow -> --tp-shadow
- --timepicker-border-radius -> --tp-border-radius
- --timepicker-font-family -> --tp-font-family
// Options Structure (GROUPED)
// v3.x (flat - DEPRECATED):
-{
- clockType: '24h',
- theme: 'dark',
- animation: true,
- amLabel: 'AM',
- onConfirm: (data) => {}
-}
// v4.0.0 (grouped - NEW):
+{
+ clock: {
+ type: '24h'
+ },
+ ui: {
+ theme: 'dark',
+ animation: true
+ },
+ labels: {
+ am: 'AM'
+ },
+ callbacks: {
+ onConfirm: (data) => {}
+ }
+}
// Migration Steps:
1. Find & replace all CSS class names in your stylesheets
2. Find & replace all CSS variable names in your custom themes
3. Reorganize options into groups: clock, ui, labels, behavior, callbacks
4. Update clockType -> clock.type, theme -> ui.theme, onConfirm -> callbacks.onConfirm
5. Test all customizations and themes thoroughly

Important: The NPM package name remains timepicker-ui. Only CSS classes and variables changed.

Changed (Breaking)

  • CSS Class Renaming - All .timepicker-ui-* classes renamed to .tp-ui-*
  • CSS Variable Renaming - All --timepicker-* variables renamed to --tp-*
  • Options Structure Reorganization - Options now grouped into: clock, ui, labels, behavior, callbacks
  • Legacy DOM Events Removed - timepicker:* custom events completely removed - use EventEmitter API or callbacks
  • setTheme() Method Removed - Programmatic theming removed - use CSS classes with CSS variables instead

Added

  • EventEmitter API with Callback Bridge - All 9 callback options automatically bridge to EventEmitter with on(), off(), once() methods
  • SSR-Safe Architecture - All modules can be imported in Node.js (Next.js, Nuxt, Remix, Astro compatible)
  • Composition-Based Architecture - Pure composition with CoreState, Managers, Lifecycle - no inheritance, fully testable
  • Auto-Focus Improvements - Modal auto-focuses on open, minutes auto-focus with autoSwitchToMinutes
  • TypeScript Enhancements - No any types, fully typed event payloads, grouped options types, stricter type safety

Changed

  • Architecture Refactored - Composition-only pattern - removed all extends, pure dependency injection
  • Bundle Size Reduced - 80 KB -> 63.3 KB ESM (-20.9%)
  • Theme Count - 11 -> 10 themes (removed theme-custom from production)
  • Event System Unified - Single source of truth - callbacks bridge to EventEmitter automatically
  • Options API Restructured - 40+ flat options -> 5 logical groups

Removed

  • Legacy DOM Events - timepicker:open, timepicker:confirm, timepicker:cancel, etc. - use picker.on() instead
  • setTheme() Method - Programmatic theme setting removed - use CSS classes instead
  • theme-custom - Removed from production bundle - users create custom themes via CSS variables

Fixed

  • Focus Trap Auto-Focus - Modal wrapper auto-focuses on open (no Tab press needed)
  • Auto-Switch Focus - Minute input receives focus when autoSwitchToMinutes enabled
  • SSR Compatibility - No DOM globals during initialization - safe for server rendering
  • currentTime Parsing - Simplified time parsing for better reliability
  • Dynamic Updates - Fixed React useEffect patterns to prevent infinite loops

Benefits

  • Shorter class names - Reduced HTML size and improved readability
  • Smaller bundle - -20.9% bundle size reduction (80 KB -> 63.3 KB)
  • Better DX - Cleaner API with grouped options and EventEmitter
  • SSR Compatible - Works with Next.js, Nuxt, Remix, Astro.
  • Type Safety - No any types, fully typed events and options

Version 3.2.0 - November 14, 2025

Added

  • Material Design 3 Ripple Effect - Interactive ripple animations on buttons and inputs
  • M3 Color System - Complete color token implementation with CSS variables
  • M2 Legacy Theme - Material Design 2 theme (theme: 'm2')
  • Mobile Clock Face Toggle - Expand/collapse clock face with smooth animations
  • Desktop to Mobile Switching - Dynamic view switching via enableSwitchIcon
  • Local SVG Icon Assets - Built-in keyboard.svg and schedule.svg icons
  • New ClockSystem Architecture - Three-layer Engine -> Controller -> Renderer with RAF batching

Changed

  • Theme Naming (Breaking) - Renamed 'm3' -> 'm3-green'
  • Option Naming (Breaking) - Renamed 'switchToMinutesAfterSelectHour' -> 'autoSwitchToMinutes'
  • ConfigManager Refactoring - Split into 4 modules (87% code reduction)
  • Code Quality - KISS/DRY optimization - longest method 225->18 lines (-92%)

Fixed

  • Clock face state bugs - Fixed mobile <-> desktop switching issues
  • Animation performance - Removed will-change causing resize lag
  • Angle Jump Bug - Fixed Math.atan2() parameter order
  • Disabled minutes styling - Visual state now reflects configuration
  • 24h positioning - Fixed inner/outer circle dimensions
  • 24h click overlap - Reduced threshold and element size
  • ClockFace disappears early - Destroy after modal animation completes
  • Landscape layout - Fixed expansion direction and class system
  • Desktop init bug - Removed incorrect .mobile class
  • autoSwitchToMinutes regression - Fixed feature after ClockSystem refactor

Performance

  • 60fps transitions - Multi-phase RAF scheduling
  • ClockSystem gains - Rendering 15ms->3ms, Drag 8ms->2ms, -25% code size

Migration Guide

Breaking Changes:

index.diff
// Theme rename
- theme: 'm3'
+ theme: 'm3-green'
// Option rename
- switchToMinutesAfterSelectHour: true
+ autoSwitchToMinutes: true

Previous Versions

Version 3.1.2

Nov 7, 2025

  • Fixed CSS variable issues with var(--timepicker-text)
  • Resolved input color styling problems

Version 3.1.1

Nov 7, 2025

  • Virtual DOM caching - 25% performance improvement
  • RAF batching for smoother animations
  • Input sanitization for XSS protection

Version 3.1.0

Nov 7, 2025

  • EventEmitter API - on(), once(), off()
  • Deprecated DOM events - migrate to EventEmitter

Full Changelog

View the complete changelog with all versions on GitHub
Esc