UNPKG

timescape

Version:

A flexible, headless date and time input library for JavaScript. Provides tools for building fully customizable date and time input fields, with support for libraries like React, Preact, Vue, Svelte and Solid.

1,010 lines (1,003 loc) 39.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); // src/integrations/react.ts var react_exports = {}; __export(react_exports, { $NOW: () => $NOW, useTimescape: () => useTimescape, useTimescapeRange: () => useTimescapeRange }); module.exports = __toCommonJS(react_exports); var import_react = require("react"); // src/date.ts var isSameSeconds = (ts1, ts2) => Math.floor(ts1 / 1e3) === Math.floor(ts2 / 1e3); var isLeapYear = (year) => year % 4 === 0 && year % 100 !== 0 || year % 400 === 0; var daysInMonth = (date) => { const month = date.getMonth(); const year = date.getFullYear(); return new Date(year, month + 1, 0).getDate(); }; var add = (date, type, amount) => { const newDate = new Date(date); switch (type) { case "days": newDate.setDate(newDate.getDate() + amount); break; case "months": { const dayOfMonth = newDate.getDate(); newDate.setMonth(newDate.getMonth() + amount); if (newDate.getDate() !== dayOfMonth) { newDate.setDate(0); } break; } case "years": { const isLeapDay = newDate.getMonth() === 1 && newDate.getDate() === 29; newDate.setFullYear(newDate.getFullYear() + amount); if (isLeapDay && !isLeapYear(newDate.getFullYear())) { newDate.setMonth(1, 28); } break; } case "hours": newDate.setHours(newDate.getHours() + amount); break; case "minutes": newDate.setMinutes(newDate.getMinutes() + amount); break; case "seconds": newDate.setSeconds(newDate.getSeconds() + amount); break; case "milliseconds": newDate.setMilliseconds(newDate.getMilliseconds() + amount); break; } return newDate; }; var set = (date, type, value) => { const newDate = new Date(date); switch (type) { case "days": newDate.setDate(value); break; case "months": { const currentDay = newDate.getDate(); const daysInNewMonth = daysInMonth( new Date(newDate.getFullYear(), value) ); newDate.setDate(Math.min(currentDay, daysInNewMonth)); newDate.setMonth(value); break; } case "years": newDate.setFullYear(value); break; case "hours": newDate.setHours(value); break; case "minutes": newDate.setMinutes(value); break; case "seconds": newDate.setSeconds(value); break; case "milliseconds": newDate.setMilliseconds(value); break; case "am/pm": newDate.setHours(value); break; } return newDate; }; var get = (date, type) => { switch (type) { case "days": return date.getDate(); case "months": return date.getMonth(); case "years": return date.getFullYear(); case "hours": return date.getHours(); case "minutes": return date.getMinutes(); case "seconds": return date.getSeconds(); case "milliseconds": return date.getMilliseconds(); case "am/pm": return date.getHours(); } }; var toggleAmPm = (date, force) => { const hours = date.getHours(); if (force === void 0) { return add(date, "hours", hours >= 12 ? -12 : 12); } if (force === "am" && hours >= 12) { return add(date, "hours", -12); } if (force === "pm" && hours < 12) { return add(date, "hours", 12); } return date; }; var format = (date, type, hour12, digits = "2-digit") => { const digitCount = digits === "2-digit" ? 2 : 1; switch (type) { case "years": { const year = date.getFullYear(); return year < 0 ? year.toString() : String(year).padStart(4, "0"); } case "months": return String(date.getMonth() + 1).padStart(digitCount, "0"); case "days": return String(date.getDate()).padStart(digitCount, "0"); case "hours": { const hours = hour12 ? date.getHours() % 12 || 12 : date.getHours(); return String(hours).padStart(digitCount, "0"); } case "minutes": return String(date.getMinutes()).padStart(2, "0"); case "seconds": return String(date.getSeconds()).padStart(2, "0"); case "milliseconds": return String(date.getMilliseconds()).padStart(3, "0"); case "am/pm": return date.getHours() < 12 ? "AM" : "PM"; } }; // src/util.ts var addElementListener = (node, type, listener, options) => { const typedListener = (ev) => listener(ev); node.addEventListener(type, typedListener, options); return () => node.removeEventListener(type, typedListener, options); }; var isTouchDevice = () => "ontouchstart" in window || navigator.maxTouchPoints > 0 || // biome-ignore lint/suspicious/noExplicitAny: this is a non-standard property navigator.msMaxTouchPoints > 0; var STOP_EVENT_PROPAGATION = Symbol("STOP_EVENT_PROPAGATION"); var createPubSub = () => ({ emit(event, data) { (this.events[event] || []).some( (cb) => cb(data) === STOP_EVENT_PROPAGATION ); }, events: {}, on(event, cb) { this.events[event] = this.events[event] ? [...this.events[event], cb] : [cb]; return () => { this.events[event] = this.events[event]?.filter((i) => cb !== i); }; } }); var createAmPmHandler = (manager) => ({ get value() { return manager.ampm; }, set: (value) => { manager.ampm = value; }, toggle: () => { const current = manager.ampm; if (current) { manager.ampm = current === "am" ? "pm" : "am"; } }, getSelectProps: () => ({ value: manager.ampm, onChange: (e) => { const target = e.target; manager.ampm = target.value; } }) }); // src/range.ts var marry = (from, to) => { from.on("focusWrap", (type) => { to.focusField(type === "start" ? -1 : 0); }); from.on("changeDate", (date) => { if (!date || !to.date) return; if (date > to.date) { from.date = to.date; return STOP_EVENT_PROPAGATION; } }); to.on("focusWrap", (type) => { from.focusField(type === "end" ? 0 : -1); }); to.on("changeDate", (date) => { if (!date || !from.date) return; if (date < from.date) { to.date = from.date; return STOP_EVENT_PROPAGATION; } }); }; // src/index.ts var $NOW = "$NOW"; var _instanceId, _timestamp, _prevTimestamp, _registry, _pubsub, _rootElement, _rootListener, _cursorPosition, _resizeObserver, _mutationObserver, _findByInputElement, _copyStyles, _TimescapeManager_instances, currentDate_get, getValue_fn, wrapDateAround_fn, clearIntermediateState_fn, handleKeyDown_fn, handleClick_fn, handleFocus_fn, handleBlur_fn, sortRegistryByElements_fn, syncAllElements_fn, syncElement_fn, createListeners_fn, setDate_fn, focusNextField_fn; var TimescapeManager = class { constructor(initialDate, options) { __privateAdd(this, _TimescapeManager_instances); __publicField(this, "minDate"); __publicField(this, "maxDate"); __publicField(this, "hour12", false); __publicField(this, "digits", "2-digit"); __publicField(this, "wrapAround", false); __publicField(this, "snapToStep", false); __publicField(this, "wheelControl", false); __publicField(this, "disallowPartial", false); // Virtual property handled by Proxy __publicField(this, "ampm"); __privateAdd(this, _instanceId, Math.random().toString(36).slice(2)); __privateAdd(this, _timestamp); // The previous timestamp is used to render partial dates when a field has been cleared. __privateAdd(this, _prevTimestamp); __privateAdd(this, _registry, /* @__PURE__ */ new Map()); __privateAdd(this, _pubsub); __privateAdd(this, _rootElement); __privateAdd(this, _rootListener); __privateAdd(this, _cursorPosition, 0); __privateAdd(this, _resizeObserver, typeof window !== "undefined" ? new ResizeObserver((entries) => { entries.forEach((entry) => { const inputElement = [...__privateGet(this, _registry).values()].find( ({ shadowElement }) => shadowElement === entry.target )?.inputElement; if (!inputElement || !entry.contentBoxSize[0]?.inlineSize) return; inputElement.style.width = `${entry.contentBoxSize[0].inlineSize}px`; }); }) : void 0); __privateAdd(this, _mutationObserver, typeof window !== "undefined" ? new MutationObserver((mutations) => { let added = 0; let removed = 0; mutations.forEach((mutation) => { added += mutation.addedNodes.length; removed += mutation.removedNodes.length; }); if (added > 0) { __privateMethod(this, _TimescapeManager_instances, sortRegistryByElements_fn).call(this); } if (removed > 0) { mutations.forEach((mutation) => { mutation.removedNodes.forEach((node) => { const entry = __privateGet(this, _findByInputElement).call(this, node); if (!entry) return; entry.inputElement.remove(); entry.shadowElement.remove(); entry.listeners.forEach((listener) => listener()); __privateGet(this, _registry).delete(entry.type); }); }); } }) : void 0); __privateAdd(this, _findByInputElement, (input) => [...__privateGet(this, _registry).values()].find( ({ inputElement }) => inputElement === input )); __privateAdd(this, _copyStyles, (from, to) => { const styles = [ "fontFamily", "fontSize", "fontWeight", "fontStyle", "fontVariant", "letterSpacing", "textTransform", "textIndent", "textOrientation" ]; requestAnimationFrame(() => { const computedStyles = window.getComputedStyle(from); for (const key of styles) { to.style[key] = computedStyles[key]; } }); }); __privateSet(this, _timestamp, initialDate?.getTime()); __privateSet(this, _pubsub, createPubSub()); if (options) { this.minDate = options.minDate; this.maxDate = options.maxDate; this.hour12 = options.hour12; this.digits = options.digits; this.wrapAround = options.wrapAround; this.snapToStep = options.snapToStep; this.wheelControl = options.wheelControl; this.disallowPartial = options.disallowPartial; } return new Proxy(this, { get: (target, property) => { if (property === "ampm") { if (!__privateGet(target, _timestamp)) return void 0; return new Date(__privateGet(target, _timestamp)).getHours() < 12 ? "am" : "pm"; } const original = target[property]; if (typeof original === "function") { return (...args) => original.apply(target, args); } return original; }, // biome-ignore lint/suspicious/noExplicitAny: nextValue can be any set: (target, property, nextValue) => { var _a, _b, _c, _d; switch (property) { case "minDate": case "maxDate": target[property] = nextValue; if (__privateGet(target, _timestamp)) { __privateMethod(_a = target, _TimescapeManager_instances, setDate_fn).call(_a, new Date(__privateGet(target, _timestamp))); } break; case "hour12": case "digits": target[property] = nextValue; __privateMethod(_b = target, _TimescapeManager_instances, syncAllElements_fn).call(_b); break; case "wheelControl": case "disallowPartial": if (nextValue === target[property]) return true; target[property] = nextValue; this.resync(); break; case "ampm": { const date = __privateGet(target, _TimescapeManager_instances, currentDate_get); __privateMethod(_c = target, _TimescapeManager_instances, setDate_fn).call(_c, toggleAmPm(date, nextValue)); __privateMethod(_d = target, _TimescapeManager_instances, syncAllElements_fn).call(_d); return true; } default: target[property] = nextValue; } return true; } }); } get date() { return __privateGet(this, _timestamp) && this.isCompleted() ? new Date(__privateGet(this, _timestamp)) : void 0; } set date(nextDate) { __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, nextDate ? new Date(nextDate) : void 0); } resync() { var _a; if (__privateGet(this, _rootElement)) { (_a = __privateGet(this, _rootListener)) == null ? void 0 : _a.call(this); this.registerRoot(__privateGet(this, _rootElement)); } Array.from(__privateGet(this, _registry)).forEach(([type, entry]) => { entry.listeners.forEach((listener) => listener()); __privateGet(this, _registry).delete(type); this.registerElement(entry.inputElement, type, entry.autofocus, true); }); } registerRoot(element) { element.tabIndex = -1; element.setAttribute("role", "group"); __privateSet(this, _rootElement, element); const hasOtherRoot = element.dataset.timescapeInstance && element.dataset.timescapeInstance !== __privateGet(this, _instanceId); if (!hasOtherRoot) element.dataset.timescapeInstance = __privateGet(this, _instanceId); __privateSet(this, _rootListener, addElementListener(element, "focus", (e) => { if (hasOtherRoot) return; const activeField = element.querySelector("input[aria-selected]"); if (activeField) { if (e.relatedTarget instanceof HTMLElement) e.relatedTarget.focus(); } else { this.focusField(0); } })); __privateGet(this, _mutationObserver)?.observe(element, { childList: true, subtree: true }); } registerElement(element, type, autofocus, domExists = false) { const registryEntry = __privateGet(this, _registry).get(type); if (!domExists && element === registryEntry?.inputElement) { return; } element.type = "text"; element.readOnly = !isTouchDevice(); element.tabIndex = 0; element.enterKeyHint = "next"; element.spellcheck = false; element.autocapitalize = "off"; element.setAttribute("role", "spinbutton"); element.dataset.timescapeInput = ""; switch (type) { case "days": element.placeholder || (element.placeholder = "dd"); break; case "months": element.placeholder || (element.placeholder = "mm"); break; case "years": element.placeholder || (element.placeholder = "yyyy"); break; case "hours": case "minutes": case "seconds": element.placeholder || (element.placeholder = "--"); break; case "milliseconds": element.placeholder || (element.placeholder = "----"); break; case "am/pm": element.placeholder || (element.placeholder = "am"); break; } if (autofocus) { requestAnimationFrame(() => element.focus()); } if (type !== "am/pm") { element.inputMode = "numeric"; } let shadowElement; const sibling = element.nextElementSibling; if (sibling instanceof HTMLSpanElement && sibling.dataset.timescapeShadow === type) { shadowElement = sibling; } else if (!domExists || !registryEntry?.shadowElement) { shadowElement = document.createElement("span"); shadowElement.setAttribute("aria-hidden", "true"); shadowElement.textContent = element.value || element.placeholder; shadowElement.dataset.timescapeShadow = type; shadowElement.style.cssText = [ "display: inline-block", "position: absolute", "left: -9999px", "top: -9999px", "visibility: hidden", "pointer-events: none", "white-space: pre" ].join(";"); __privateGet(this, _copyStyles).call(this, element, shadowElement); __privateGet(this, _resizeObserver)?.observe(shadowElement); element.parentNode?.insertBefore(shadowElement, element.nextSibling); } else { shadowElement = registryEntry.shadowElement; } __privateGet(this, _registry).set(type, { type, inputElement: element, autofocus, shadowElement, intermediateValue: "", isUnset: !__privateGet(this, _timestamp) && !this.disallowPartial, listeners: __privateMethod(this, _TimescapeManager_instances, createListeners_fn).call(this, element, type) }); this.on("changeDate", () => __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, element)); __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, element); return element; } /** * Returns whether all fields are filled out. Can only be false in partial mode. * @returns {boolean} */ isCompleted() { return !this.disallowPartial ? [...__privateGet(this, _registry).values()].every((e) => !e.isUnset) : true; } remove() { var _a; (_a = __privateGet(this, _rootListener)) == null ? void 0 : _a.call(this); __privateGet(this, _registry).forEach(({ shadowElement, listeners }) => { listeners.forEach((remove) => remove()); shadowElement.remove(); }); __privateGet(this, _pubsub).events = {}; __privateGet(this, _resizeObserver)?.disconnect(); __privateGet(this, _mutationObserver)?.disconnect(); } focusField(which = 0) { const entries = [...__privateGet(this, _registry).values()]; const type = entries.at(which)?.type; type && __privateGet(this, _registry).get(type)?.inputElement.focus(); } on(event, callback) { return __privateGet(this, _pubsub).on(event, callback); } }; _instanceId = new WeakMap(); _timestamp = new WeakMap(); _prevTimestamp = new WeakMap(); _registry = new WeakMap(); _pubsub = new WeakMap(); _rootElement = new WeakMap(); _rootListener = new WeakMap(); _cursorPosition = new WeakMap(); _resizeObserver = new WeakMap(); _mutationObserver = new WeakMap(); _findByInputElement = new WeakMap(); _copyStyles = new WeakMap(); _TimescapeManager_instances = new WeakSet(); currentDate_get = function() { return __privateGet(this, _timestamp) ? new Date(__privateGet(this, _timestamp)) : /* @__PURE__ */ new Date(); }; getValue_fn = function(type) { const registryEntry = __privateGet(this, _registry).get(type); const intermediateValue = registryEntry?.intermediateValue; if (registryEntry?.isUnset) return ""; const ts = __privateGet(this, _timestamp) ?? __privateGet(this, _prevTimestamp); return intermediateValue ? type === "years" ? intermediateValue.padStart(4, "0") : type === "milliseconds" ? intermediateValue.padStart(3, "0") : intermediateValue.padStart( type === "minutes" || type === "seconds" ? 2 : this.digits === "2-digit" ? 2 : 1, "0" ) : ts ? format(new Date(ts), type, this.hour12, this.digits) : ""; }; wrapDateAround_fn = function(step, type) { const ranges = { seconds: 60, minutes: 60, hours: this.hour12 ? 12 : 24, months: 12, milliseconds: 1e3 }; let date = __privateGet(this, _TimescapeManager_instances, currentDate_get); if (type === "years" || type === "am/pm") { return add(date, "years", step); } if (type === "days") { const daysMonth = daysInMonth(date); const newValue = (date.getDate() + step - 1 + daysMonth) % daysMonth + 1; date.setDate(newValue); } else { const newValue = (get(date, type) + step + ranges[type]) % ranges[type]; date = set(date, type, newValue); } return date; }; clearIntermediateState_fn = function(registryEntry) { const { intermediateValue, type } = registryEntry; if (intermediateValue) { __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, set( __privateGet(this, _TimescapeManager_instances, currentDate_get), type, type === "months" ? Number(intermediateValue) - 1 : Number(intermediateValue) )); registryEntry.intermediateValue = ""; __privateSet(this, _cursorPosition, 0); } }; handleKeyDown_fn = function(e) { if (e.defaultPrevented) return; const registryEntry = __privateGet(this, _findByInputElement).call(this, e.target); if (!registryEntry) return; const { inputElement, intermediateValue, type } = registryEntry; let allowNativeEvent = false; const key = e.key; switch (true) { case key === "ArrowUp": case key === "ArrowDown": { __privateMethod(this, _TimescapeManager_instances, clearIntermediateState_fn).call(this, registryEntry); const date = __privateGet(this, _TimescapeManager_instances, currentDate_get); const elementStep = !this.disallowPartial && registryEntry.isUnset ? 0 : Number(inputElement.step) || 1; registryEntry.isUnset = false; if (type === "am/pm") { __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, elementStep === 0 ? date : toggleAmPm(date)); __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, inputElement); break; } let step; if (this.snapToStep) { const value = get(date, type); if (e.key === "ArrowUp") { step = Math.ceil((value + 1) / elementStep) * elementStep - value; } else { step = Math.floor((value - 1) / elementStep) * elementStep - value; } } else { const factor = e.key === "ArrowUp" ? 1 : -1; step = elementStep * factor; } __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, this.wrapAround || !this.isCompleted() ? __privateMethod(this, _TimescapeManager_instances, wrapDateAround_fn).call(this, step, type) : add(date, type, step)); __privateMethod(this, _TimescapeManager_instances, syncAllElements_fn).call(this); break; } case key === "ArrowRight": case key === "Enter": __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type, 1, true); break; case key === "Tab": { const tabOffset = e.shiftKey ? -1 : 1; allowNativeEvent = !__privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type, tabOffset); break; } case key === "ArrowLeft": __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type, -1, true); break; case (type === "am/pm" && ["a", "p"].includes(key.toLowerCase())): { const force = key.toLowerCase() === "a" ? "am" : "pm"; registryEntry.isUnset = false; __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, toggleAmPm(__privateGet(this, _TimescapeManager_instances, currentDate_get), force)); __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, inputElement); break; } case key === "Delete": case (key === "Backspace" && type === "am/pm"): if (this.disallowPartial) return; registryEntry.isUnset = true; if (__privateGet(this, _timestamp)) __privateSet(this, _prevTimestamp, __privateGet(this, _timestamp)); __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, void 0); __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, inputElement); break; case key === "Backspace": { if (this.disallowPartial) return; if (type === "am/pm") return; const newValue = intermediateValue || inputElement.value; registryEntry.intermediateValue = newValue.slice(0, -1).replace(/^0+/, ""); if (!registryEntry.intermediateValue) { registryEntry.isUnset = true; if (__privateGet(this, _timestamp)) __privateSet(this, _prevTimestamp, __privateGet(this, _timestamp)); __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, void 0); } __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, inputElement); break; } case /^\d$/.test(key): { const number = Number(key); if (e.metaKey || e.ctrlKey) { allowNativeEvent = true; break; } const setIntermediateValue = (value) => { registryEntry.intermediateValue = value; registryEntry.isUnset = false; __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, inputElement); }; const setValue = (unit, value) => { const newDate = set(__privateGet(this, _TimescapeManager_instances, currentDate_get), unit, value); registryEntry.intermediateValue = ""; registryEntry.isUnset = false; __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, newDate); __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, inputElement); __privateSet(this, _cursorPosition, 0); }; switch (type) { case "days": if (__privateGet(this, _cursorPosition) === 0) { setIntermediateValue(key); if (number > 3) { setValue("days", number); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } else { __privateSet(this, _cursorPosition, 1); } } else { const finalValue = Math.max( 1, Math.min( Number(intermediateValue + key), daysInMonth(__privateGet(this, _TimescapeManager_instances, currentDate_get)) ) ); setValue("days", finalValue); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } break; case "months": if (__privateGet(this, _cursorPosition) === 0) { setIntermediateValue(key); if (number > 1) { setValue("months", number - 1); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } else { __privateSet(this, _cursorPosition, 1); } } else { const finalValue = Math.max( 0, // Subtract 1 because JS months are 0-based // Prevent negative so years are not wrapped around Math.min(Number(intermediateValue + key), 12) - 1 ); setValue("months", finalValue); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } break; case "years": if (__privateGet(this, _cursorPosition) < 4) { const newValue = intermediateValue + key; setIntermediateValue(newValue); __privateSet(this, _cursorPosition, __privateGet(this, _cursorPosition) + 1); if (__privateGet(this, _cursorPosition) === 4) { setValue("years", Number(newValue)); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } } break; case "hours": { const isPM = __privateGet(this, _TimescapeManager_instances, currentDate_get).getHours() >= 12; if (__privateGet(this, _cursorPosition) === 0) { setIntermediateValue(key); const maxFirstDigit = this.hour12 ? 1 : 2; if (number > maxFirstDigit) { setValue("hours", this.hour12 && isPM ? number + 12 : number); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); break; } __privateSet(this, _cursorPosition, 1); } else { const inputValue = Number(intermediateValue + key); const maxHours = this.hour12 ? 12 : 24; let finalValue = inputValue > maxHours ? number : inputValue; if (this.hour12) { const date = set(__privateGet(this, _TimescapeManager_instances, currentDate_get), "hours", finalValue); finalValue = toggleAmPm(date, isPM ? "pm" : "am").getHours(); } setValue("hours", finalValue); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } break; } case "minutes": case "seconds": if (__privateGet(this, _cursorPosition) === 0) { setIntermediateValue(key); if (number > 5) { setValue(type, number); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } else { __privateSet(this, _cursorPosition, 1); } } else { const finalValue = Math.min(Number(intermediateValue + key), 59); setValue(type, finalValue); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type, 1); } break; case "milliseconds": if (__privateGet(this, _cursorPosition) < 3) { const newValue = intermediateValue + key; setIntermediateValue(newValue); __privateSet(this, _cursorPosition, __privateGet(this, _cursorPosition) + 1); if (__privateGet(this, _cursorPosition) === 3) { setValue("milliseconds", Number(newValue)); __privateMethod(this, _TimescapeManager_instances, focusNextField_fn).call(this, type); } } break; } break; } default: allowNativeEvent = true; break; } if (!allowNativeEvent) { e.preventDefault(); e.stopPropagation(); } }; handleClick_fn = function(e) { const target = e.target; target.focus(); }; handleFocus_fn = function(e) { const target = e.target; target.setAttribute("aria-selected", "true"); __privateSet(this, _cursorPosition, 0); }; handleBlur_fn = function(e) { requestAnimationFrame(() => { if (e.target !== document.activeElement) { const registryEntry = __privateGet(this, _findByInputElement).call(this, e.target); if (registryEntry) __privateMethod(this, _TimescapeManager_instances, clearIntermediateState_fn).call(this, registryEntry); const target = e.target; target.removeAttribute("aria-selected"); } }); }; // Because the order of insertion is important for which field is selected when tabbing, // we need to sort the registry by the order of the input elements in the DOM. sortRegistryByElements_fn = function() { __privateSet(this, _registry, new Map( [...__privateGet(this, _registry).entries()].sort( ([, a], [, b]) => a.inputElement.compareDocumentPosition(b.inputElement) & (Node.DOCUMENT_POSITION_FOLLOWING | Node.DOCUMENT_POSITION_CONTAINED_BY) ? -1 : 1 ) )); }; syncAllElements_fn = function() { __privateGet(this, _registry).forEach((entry) => __privateMethod(this, _TimescapeManager_instances, syncElement_fn).call(this, entry.inputElement)); }; syncElement_fn = function(element) { const entry = __privateGet(this, _findByInputElement).call(this, element); if (!entry) return; const { type, shadowElement } = entry; const value = __privateMethod(this, _TimescapeManager_instances, getValue_fn).call(this, type); if (element.value === value) return; element.value = value; element.setAttribute("aria-label", type); if (type !== "am/pm") { element.setAttribute("aria-valuenow", value.replace(/^0/, "")); element.setAttribute( "aria-valuemin", ["days", "months", "years"].includes(type) ? "1" : "0" ); element.setAttribute( "aria-valuemax", (type === "days" ? daysInMonth(__privateGet(this, _TimescapeManager_instances, currentDate_get)) : type === "months" ? 12 : type === "years" ? 9999 : type === "hours" ? 23 : type === "minutes" || type === "seconds" ? 59 : type === "milliseconds" ? 999 : "").toString() ); } if (shadowElement.textContent !== value) { shadowElement.textContent = value || element.placeholder; } }; createListeners_fn = function(element, type) { const listeners = [ addElementListener(element, "keydown", (e) => __privateMethod(this, _TimescapeManager_instances, handleKeyDown_fn).call(this, e)), addElementListener(element, "click", (e) => __privateMethod(this, _TimescapeManager_instances, handleClick_fn).call(this, e)), addElementListener(element, "focus", (e) => __privateMethod(this, _TimescapeManager_instances, handleFocus_fn).call(this, e)), addElementListener(element, "focusout", (e) => __privateMethod(this, _TimescapeManager_instances, handleBlur_fn).call(this, e)) ]; if (this.wheelControl) { listeners.push( addElementListener(element, "wheel", (e) => { e.preventDefault(); const step = Math.sign(e.deltaY); __privateMethod(this, _TimescapeManager_instances, setDate_fn).call(this, this.wrapAround ? __privateMethod(this, _TimescapeManager_instances, wrapDateAround_fn).call(this, step, type) : add(__privateGet(this, _TimescapeManager_instances, currentDate_get), type, step)); }) ); } return listeners; }; /** * Sets a validated date and emits a changeDate event. * It also caps the date to the minDate and maxDate if they are set. * Only emits the changeDate event if the date is complete (in partial mode). */ setDate_fn = function(date) { if (!date) { __privateSet(this, _timestamp, void 0); __privateGet(this, _pubsub).emit("changeDate", void 0); return; } const minDate = this.minDate === $NOW ? /* @__PURE__ */ new Date() : this.minDate; const maxDate = this.maxDate === $NOW ? /* @__PURE__ */ new Date() : this.maxDate; let validatedDate = date; if (minDate && validatedDate < minDate) { validatedDate = minDate; } else if (maxDate && validatedDate > maxDate) { validatedDate = maxDate; } if (__privateGet(this, _timestamp) && isSameSeconds(validatedDate.getTime(), __privateGet(this, _timestamp)) && !this.isCompleted()) { return; } __privateSet(this, _timestamp, validatedDate.getTime()); __privateSet(this, _prevTimestamp, void 0); if (!this.isCompleted()) return; __privateGet(this, _pubsub).emit("changeDate", validatedDate); }; /** * * @returns {Boolean} Whether the next field was focused or not */ focusNextField_fn = function(type, offset = 1, wrap) { const types = [...__privateGet(this, _registry).keys()]; const index = types.indexOf(type); const nextIndex = wrap ? types[(index + offset + types.length) % types.length] : types[index + offset]; if (nextIndex) __privateGet(this, _registry).get(nextIndex)?.inputElement.focus(); if (wrap && (index === 0 && offset <= -1 || index === types.length - 1 && offset >= 1)) { __privateGet(this, _pubsub).emit("focusWrap", offset === -1 ? "start" : "end"); } return !!nextIndex; }; // src/integrations/react.ts var useTimescape = (options = {}) => { const { date, onChangeDate, ...rest } = options; const [manager] = (0, import_react.useState)(() => new TimescapeManager(date, rest)); const onChangeDateRef = (0, import_react.useRef)(onChangeDate); (0, import_react.useLayoutEffect)(() => { onChangeDateRef.current = onChangeDate; }, [onChangeDate]); const [optionsState, update] = (0, import_react.useState)(() => ({ date, ...rest })); (0, import_react.useEffect)(() => { manager.resync(); return () => { manager.remove(); }; }, [manager]); (0, import_react.useEffect)(() => { return manager.on("changeDate", (nextDate) => { onChangeDateRef.current?.(nextDate); update((value) => ({ ...value, date: nextDate })); }); }, [manager]); const timestamp = optionsState.date?.getTime(); (0, import_react.useEffect)(() => { manager.date = timestamp; manager.minDate = optionsState.minDate; manager.maxDate = optionsState.maxDate; manager.hour12 = optionsState.hour12; manager.wrapAround = optionsState.wrapAround; manager.digits = optionsState.digits; manager.snapToStep = optionsState.snapToStep; manager.wheelControl = optionsState.wheelControl; manager.disallowPartial = optionsState.disallowPartial; }, [ manager, timestamp, optionsState.minDate, optionsState.maxDate, optionsState.hour12, optionsState.wrapAround, optionsState.digits, optionsState.snapToStep, optionsState.wheelControl, optionsState.disallowPartial ]); return { _manager: manager, getInputProps: (type, opts) => ({ ref: (element) => { if (!element) return; manager.registerElement(element, type, opts?.autofocus); if (opts?.ref) opts.ref.current = element; } }), getRootProps: () => ({ ref: (element) => element && manager.registerRoot(element) }), ampm: createAmPmHandler(manager), options: optionsState, update }; }; var useTimescapeRange = (options) => { const from = useTimescape(options.from); const to = useTimescape(options.to); (0, import_react.useEffect)(() => { marry(from._manager, to._manager); }, [from._manager, to._manager]); return { getRootProps: () => ({ ref: (element) => { if (!element) return; from._manager.registerRoot(element); to._manager.registerRoot(element); } }), from: { getInputProps: from.getInputProps, options: from.options, update: from.update }, to: { getInputProps: to.getInputProps, options: to.options, update: to.update } }; }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { $NOW, useTimescape, useTimescapeRange });