@manik02/vue3-timepicker 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Manos Savvides
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,236 @@
1
+ # Vue Timepicker
2
+
3
+ A flexible, customisable timepicker component for Vue 3 with TypeScript support.
4
+
5
+ - Single and range time selection
6
+ - Multiple hour formats: 24-hour (`HH`/`H`), 12-hour (`hh`/`h` with AM/PM), 1-24 (`kk`/`k`)
7
+ - Optional seconds
8
+ - Inline masked input with overwrite-only editing
9
+ - Step intervals for hours, minutes, and seconds
10
+ - Fully styleable via CSS custom properties
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @manik02/vue3-timepicker
16
+ ```
17
+
18
+ ## Quick start
19
+
20
+ ```vue
21
+ <script setup>
22
+ import { ref } from "vue";
23
+ import { TimePicker } from "@manik02/vue3-timepicker";
24
+ import "@manik02/vue3-timepicker/style.css";
25
+
26
+ const time = ref("14:30:00");
27
+ </script>
28
+
29
+ <template>
30
+ <TimePicker v-model="time" format="HH:mm:ss" />
31
+ </template>
32
+ ```
33
+
34
+ ## Props
35
+
36
+ | Prop | Type | Default | Description |
37
+ | ------------ | ------------------------------------ | ----------- | ------------------------------------------------------------------------ |
38
+ | `modelValue` | `string \| [string, string] \| null` | `undefined` | Time value in `HH:mm:ss` format. Use a two-element array for range mode. |
39
+ | `format` | `TimeFormat` | `"HH:mm"` | Display format (see format tokens below). |
40
+ | `range` | `boolean` | `false` | Enable range selection with two time inputs. |
41
+ | `hourStep` | `number` | `1` | Step interval for the hour column. |
42
+ | `minuteStep` | `number` | `1` | Step interval for the minute column. |
43
+ | `secondStep` | `number` | `1` | Step interval for the second column. |
44
+
45
+ ## Format tokens
46
+
47
+ | Token | Output | Description |
48
+ | --------- | ---------------------- | ---------------------- |
49
+ | `HH` | `00`-`23` | 24-hour, zero-padded |
50
+ | `H` | `0`-`23` | 24-hour |
51
+ | `hh` | `01`-`12` | 12-hour, zero-padded |
52
+ | `h` | `1`-`12` | 12-hour |
53
+ | `kk` | `01`-`24` | 1-24 hour, zero-padded |
54
+ | `k` | `1`-`24` | 1-24 hour |
55
+ | `mm` | `00`-`59` | Minutes, zero-padded |
56
+ | `m` | `0`-`59` | Minutes |
57
+ | `ss` | `00`-`59` | Seconds, zero-padded |
58
+ | `s` | `0`-`59` | Seconds |
59
+ | `A` / `a` | `AM`/`PM` or `am`/`pm` | AM/PM indicator |
60
+
61
+ Combine tokens with `:` separators: `HH:mm`, `hh:mm:ss A`, `kk:mm`, etc.
62
+
63
+ ## Range mode
64
+
65
+ ```vue
66
+ <script setup>
67
+ import { ref } from "vue";
68
+ import { TimePicker } from "@manik02/vue3-timepicker";
69
+ import "@manik02/vue3-timepicker/style.css";
70
+
71
+ const range = ref(["09:00:00", "17:00:00"]);
72
+ </script>
73
+
74
+ <template>
75
+ <TimePicker v-model="range" format="HH:mm" :range="true" />
76
+ </template>
77
+ ```
78
+
79
+ When `range` is `true`, `modelValue` must be a `[string, string]` array.
80
+
81
+ ## 12-hour format
82
+
83
+ ```vue
84
+ <TimePicker v-model="time" format="hh:mm A" /> // AM/PM
85
+ <TimePicker v-model="time" format="hh:mm a" /> // am/pm
86
+ ```
87
+
88
+ Press `a` or `p` while focused to toggle between AM and PM.
89
+
90
+ ## Step intervals
91
+
92
+ ```vue
93
+ <TimePicker v-model="time" format="HH:mm" :minute-step="15" />
94
+ ```
95
+
96
+ The dropdown columns will show values at the specified intervals (e.g. 00, 15, 30, 45 for a 15-minute step).
97
+
98
+ ## CSS custom properties
99
+
100
+ Style the component by setting CSS custom properties on `.timepicker-shell` or any ancestor element.
101
+
102
+ ```css
103
+ .my-theme .timepicker-shell {
104
+ --vtp-font-family: "Inter", sans-serif;
105
+ --vtp-font-size: 14px;
106
+ --vtp-bg: #fff;
107
+ --vtp-color: #333;
108
+ --vtp-border: #d1d5db;
109
+ --vtp-border-radius: 8px;
110
+ --vtp-padding: 0.5rem 0.75rem;
111
+ --vtp-focus-border: #3b82f6;
112
+ --vtp-focus-ring: 0 0 0 3px rgba(59, 130, 246, 0.2);
113
+ --vtp-error-border: #ef4444;
114
+ --vtp-error-ring: 0 0 0 3px rgba(239, 68, 68, 0.15);
115
+ --vtp-separator-color: #9ca3af;
116
+ --vtp-dropdown-bg: #fff;
117
+ --vtp-dropdown-border: #e5e7eb;
118
+ --vtp-dropdown-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
119
+ --vtp-dropdown-radius: 8px;
120
+ --vtp-dropdown-max-height: 240px;
121
+ --vtp-option-padding: 0.375rem 0.75rem;
122
+ --vtp-option-radius: 6px;
123
+ --vtp-option-hover-bg: #f3f4f6;
124
+ --vtp-option-active-bg: #dbeafe;
125
+ --vtp-option-active-color: #1e40af;
126
+ --vtp-option-active-weight: 600;
127
+ --vtp-columns-gap: 0.5rem;
128
+ }
129
+ ```
130
+
131
+ All properties have sensible defaults and the component inherits font and colour from its parent by default.
132
+
133
+ ### Dark theme example
134
+
135
+ ```vue
136
+ <template>
137
+ <div class="dark-theme">
138
+ <TimePicker v-model="time" format="HH:mm:ss" />
139
+ </div>
140
+ </template>
141
+
142
+ <style>
143
+ .dark-theme .timepicker-shell {
144
+ --vtp-bg: #1e1e2e;
145
+ --vtp-color: #cdd6f4;
146
+ --vtp-border: #45475a;
147
+ --vtp-border-radius: 10px;
148
+ --vtp-focus-border: #89b4fa;
149
+ --vtp-focus-ring: 0 0 0 3px rgba(137, 180, 250, 0.25);
150
+ --vtp-separator-color: #6c7086;
151
+ --vtp-dropdown-bg: #1e1e2e;
152
+ --vtp-dropdown-border: #45475a;
153
+ --vtp-dropdown-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
154
+ --vtp-option-hover-bg: #313244;
155
+ --vtp-option-active-bg: #89b4fa;
156
+ --vtp-option-active-color: #1e1e2e;
157
+ }
158
+ </style>
159
+ ```
160
+
161
+ ### Minimal rounded example
162
+
163
+ ```vue
164
+ <template>
165
+ <div class="rounded-theme">
166
+ <TimePicker v-model="time" format="HH:mm" />
167
+ </div>
168
+ </template>
169
+
170
+ <style>
171
+ .rounded-theme .timepicker-shell {
172
+ --vtp-font-family: "Georgia", serif;
173
+ --vtp-font-size: 16px;
174
+ --vtp-border: #a78bfa;
175
+ --vtp-border-radius: 999px;
176
+ --vtp-padding: 0.5rem 1.25rem;
177
+ --vtp-focus-border: #7c3aed;
178
+ --vtp-focus-ring: 0 0 0 3px rgba(124, 58, 237, 0.2);
179
+ --vtp-dropdown-radius: 16px;
180
+ --vtp-option-radius: 12px;
181
+ --vtp-option-active-bg: #ede9fe;
182
+ --vtp-option-active-color: #5b21b6;
183
+ }
184
+ </style>
185
+ ```
186
+
187
+ ### Compact flat example
188
+
189
+ ```vue
190
+ <template>
191
+ <div class="flat-theme">
192
+ <TimePicker v-model="time" format="hh:mm A" />
193
+ </div>
194
+ </template>
195
+
196
+ <style>
197
+ .flat-theme .timepicker-shell {
198
+ --vtp-font-family: "Roboto Mono", monospace;
199
+ --vtp-font-size: 13px;
200
+ --vtp-bg: #f8fafc;
201
+ --vtp-color: #0f172a;
202
+ --vtp-border: transparent;
203
+ --vtp-border-radius: 4px;
204
+ --vtp-padding: 0.375rem 0.5rem;
205
+ --vtp-focus-border: #0ea5e9;
206
+ --vtp-focus-ring: none;
207
+ --vtp-dropdown-bg: #f8fafc;
208
+ --vtp-dropdown-border: #e2e8f0;
209
+ --vtp-dropdown-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
210
+ --vtp-dropdown-radius: 4px;
211
+ --vtp-option-radius: 2px;
212
+ --vtp-option-hover-bg: #e2e8f0;
213
+ --vtp-option-active-bg: #0ea5e9;
214
+ --vtp-option-active-color: #fff;
215
+ --vtp-option-active-weight: 500;
216
+ }
217
+ </style>
218
+ ```
219
+
220
+ You can scope the overrides to any CSS class, or apply them globally to `.timepicker-shell` to affect all instances.
221
+
222
+ ## TypeScript
223
+
224
+ The package exports the following types:
225
+
226
+ ```ts
227
+ import type {
228
+ TimeFormat,
229
+ InternalFormat,
230
+ TimePickerProps,
231
+ } from "@manik02/vue3-timepicker";
232
+ ```
233
+
234
+ ## License
235
+
236
+ MIT
@@ -0,0 +1,19 @@
1
+ type __VLS_Props = {
2
+ items: Array<{
3
+ key: string | number;
4
+ value: any;
5
+ text: string;
6
+ disabled?: boolean;
7
+ }>;
8
+ activeIndex: number;
9
+ };
10
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
11
+ "update:activeIndex": (index: number) => any;
12
+ select: (v: any) => any;
13
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
14
+ "onUpdate:activeIndex"?: ((index: number) => any) | undefined;
15
+ onSelect?: ((v: any) => any) | undefined;
16
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
17
+ menu: HTMLDivElement;
18
+ }, HTMLDivElement>;
19
+ export default _default;
@@ -0,0 +1,81 @@
1
+ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
2
+ readonly modelValue: {
3
+ readonly type: import('vue').PropType<string | [string, string] | null>;
4
+ readonly default: undefined;
5
+ readonly validator: (v: any) => boolean;
6
+ };
7
+ readonly range: {
8
+ readonly type: BooleanConstructor;
9
+ readonly default: false;
10
+ };
11
+ readonly hourStep: {
12
+ readonly type: NumberConstructor;
13
+ readonly default: 1;
14
+ };
15
+ readonly minuteStep: {
16
+ readonly type: NumberConstructor;
17
+ readonly default: 1;
18
+ };
19
+ readonly secondStep: {
20
+ readonly type: NumberConstructor;
21
+ readonly default: 1;
22
+ };
23
+ readonly format: {
24
+ readonly type: import('vue').PropType<import('./types').TimeFormat>;
25
+ readonly default: "HH:mm";
26
+ readonly validator: (fmt: string) => boolean;
27
+ };
28
+ }>, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
29
+ "update:modelValue": (v: string | [string, string] | null) => any;
30
+ open: () => any;
31
+ close: () => any;
32
+ error: (payload: {
33
+ code: "BAD_TIME" | "OUT_OF_RANGE";
34
+ message: string;
35
+ }) => any;
36
+ }, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
37
+ readonly modelValue: {
38
+ readonly type: import('vue').PropType<string | [string, string] | null>;
39
+ readonly default: undefined;
40
+ readonly validator: (v: any) => boolean;
41
+ };
42
+ readonly range: {
43
+ readonly type: BooleanConstructor;
44
+ readonly default: false;
45
+ };
46
+ readonly hourStep: {
47
+ readonly type: NumberConstructor;
48
+ readonly default: 1;
49
+ };
50
+ readonly minuteStep: {
51
+ readonly type: NumberConstructor;
52
+ readonly default: 1;
53
+ };
54
+ readonly secondStep: {
55
+ readonly type: NumberConstructor;
56
+ readonly default: 1;
57
+ };
58
+ readonly format: {
59
+ readonly type: import('vue').PropType<import('./types').TimeFormat>;
60
+ readonly default: "HH:mm";
61
+ readonly validator: (fmt: string) => boolean;
62
+ };
63
+ }>> & Readonly<{
64
+ "onUpdate:modelValue"?: ((v: string | [string, string] | null) => any) | undefined;
65
+ onOpen?: (() => any) | undefined;
66
+ onClose?: (() => any) | undefined;
67
+ onError?: ((payload: {
68
+ code: "BAD_TIME" | "OUT_OF_RANGE";
69
+ message: string;
70
+ }) => any) | undefined;
71
+ }>, {
72
+ readonly modelValue: string | [string, string] | null;
73
+ readonly range: boolean;
74
+ readonly hourStep: number;
75
+ readonly minuteStep: number;
76
+ readonly secondStep: number;
77
+ readonly format: import('./types').TimeFormat;
78
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
79
+ secondInputRef: HTMLInputElement;
80
+ }, any>;
81
+ export default _default;
@@ -0,0 +1,23 @@
1
+ import { InternalFormat } from './types';
2
+ type __VLS_Props = {
3
+ open: boolean;
4
+ initTime: InternalFormat;
5
+ format: string;
6
+ hourStep?: number;
7
+ minuteStep?: number;
8
+ secondStep?: number;
9
+ };
10
+ declare const _default: import('vue').DefineComponent<__VLS_Props, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
11
+ open: () => any;
12
+ close: () => any;
13
+ "update:initTime": (v: InternalFormat) => any;
14
+ "update:open": (v: boolean) => any;
15
+ }, string, import('vue').PublicProps, Readonly<__VLS_Props> & Readonly<{
16
+ onOpen?: (() => any) | undefined;
17
+ onClose?: (() => any) | undefined;
18
+ "onUpdate:initTime"?: ((v: InternalFormat) => any) | undefined;
19
+ "onUpdate:open"?: ((v: boolean) => any) | undefined;
20
+ }>, {}, {}, {}, {}, string, import('vue').ComponentProvideOptions, false, {
21
+ root: HTMLDivElement;
22
+ }, any>;
23
+ export default _default;
@@ -0,0 +1,91 @@
1
+ import { PropType, ExtractPropTypes } from 'vue';
2
+ type HourToken = "HH" | "H" | "hh" | "h" | "kk" | "k";
3
+ type MinuteToken = "mm" | "m";
4
+ type SecondToken = `:${"ss" | "s"}`;
5
+ type AmPmToken = ` ${"A" | "a" | "P" | "p"}`;
6
+ type Base = `${HourToken}:${MinuteToken}`;
7
+ type WithSeconds = `${Base}${SecondToken}`;
8
+ type WithAmPM = `${Base}${AmPmToken}`;
9
+ type WithSecondsAmPM = `${WithSeconds}${AmPmToken}`;
10
+ export type TimeFormat = Base | WithSeconds | WithAmPM | WithSecondsAmPM;
11
+ export type InternalFormat = {
12
+ h: number;
13
+ m: number;
14
+ s: number;
15
+ };
16
+ export declare const FORMAT_SHAPE: RegExp;
17
+ export declare const TIME_SHAPE: RegExp;
18
+ export declare const timePickerProps: {
19
+ readonly modelValue: {
20
+ readonly type: PropType<string | [string, string] | null>;
21
+ readonly default: undefined;
22
+ readonly validator: (v: any) => boolean;
23
+ };
24
+ readonly range: {
25
+ readonly type: BooleanConstructor;
26
+ readonly default: false;
27
+ };
28
+ readonly hourStep: {
29
+ readonly type: NumberConstructor;
30
+ readonly default: 1;
31
+ };
32
+ readonly minuteStep: {
33
+ readonly type: NumberConstructor;
34
+ readonly default: 1;
35
+ };
36
+ readonly secondStep: {
37
+ readonly type: NumberConstructor;
38
+ readonly default: 1;
39
+ };
40
+ readonly format: {
41
+ readonly type: PropType<TimeFormat>;
42
+ readonly default: "HH:mm";
43
+ readonly validator: (fmt: string) => boolean;
44
+ };
45
+ };
46
+ export type TimePickerProps = ExtractPropTypes<typeof timePickerProps>;
47
+ export declare const TimeSelectionProps: {
48
+ readonly modelValue: {
49
+ readonly type: PropType<string | [string, string] | null>;
50
+ readonly default: null;
51
+ readonly validator: (v: any) => boolean;
52
+ };
53
+ readonly range: {
54
+ readonly type: BooleanConstructor;
55
+ readonly default: false;
56
+ };
57
+ readonly hourStep: {
58
+ readonly type: NumberConstructor;
59
+ readonly default: 1;
60
+ };
61
+ readonly minuteStep: {
62
+ readonly type: NumberConstructor;
63
+ readonly default: 1;
64
+ };
65
+ readonly secondStep: {
66
+ readonly type: NumberConstructor;
67
+ readonly default: 1;
68
+ };
69
+ readonly format: {
70
+ readonly type: PropType<TimeFormat>;
71
+ readonly default: "HH:mm";
72
+ readonly validator: (fmt: string) => boolean;
73
+ };
74
+ };
75
+ export type TimeSelectionProps = ExtractPropTypes<typeof TimeSelectionProps>;
76
+ export interface TimePickerEmits {
77
+ (e: "update:modelValue", v: string | [string, string] | null): void;
78
+ (e: "open"): void;
79
+ (e: "close"): void;
80
+ (e: "error", payload: {
81
+ code: "BAD_TIME" | "OUT_OF_RANGE";
82
+ message: string;
83
+ }): void;
84
+ }
85
+ export type Item = {
86
+ key: number | string;
87
+ value: number | string;
88
+ text: string;
89
+ disabled?: boolean;
90
+ };
91
+ export {};
@@ -0,0 +1,15 @@
1
+ import { Ref, ComputedRef } from 'vue';
2
+ import { InternalFormat } from './types';
3
+ export declare function useTimeMask(format: Ref<string> | ComputedRef<string>): {
4
+ inputValue: Ref<string, string>;
5
+ handleKeydown: (e: KeyboardEvent) => void;
6
+ handleInput: (e: Event) => void;
7
+ handlePaste: (e: ClipboardEvent) => void;
8
+ setFromTime: (time: InternalFormat) => void;
9
+ getParsedTime: () => InternalFormat | null;
10
+ isComplete: ComputedRef<boolean>;
11
+ totalDigits: ComputedRef<number>;
12
+ displayPosToDigitIndex: (pos: number) => number;
13
+ ampm: Ref<"PM" | "AM", "PM" | "AM">;
14
+ ampmLowercase: ComputedRef<boolean>;
15
+ };
@@ -0,0 +1,15 @@
1
+ import { InternalFormat } from './TimePicker/types';
2
+ /** Check if format uses 12-hour clock (h or hh) */
3
+ export declare function is12h(fmt: string): boolean;
4
+ export declare function isPm(fmt: string): boolean;
5
+ export declare function hasK(fmt: string): boolean;
6
+ /** Parse time string into { h, m, s } numbers */
7
+ export declare function parseFromModel(str: string | null | undefined, fmt: string): {
8
+ h: number;
9
+ m: number;
10
+ s: number;
11
+ };
12
+ export declare function to12(h24: number): number;
13
+ export declare function to24(h12: number, isPM: boolean): number;
14
+ export declare function hasSeconds(fmt: string): boolean;
15
+ export declare function formatTime(fmt: string, time: InternalFormat): string;
@@ -0,0 +1,3 @@
1
+ import { default as TimePicker } from './TimePicker/TimePicker.vue';
2
+ export { TimePicker };
3
+ export type { TimeFormat, InternalFormat, TimePickerProps, } from './TimePicker/types';
@@ -0,0 +1 @@
1
+ .timepicker-shell,.timepicker-shell *,.timepicker-shell *:before,.timepicker-shell *:after{box-sizing:border-box}.timepicker-shell{position:relative;display:inline-block;font-family:var(--vtp-font-family, inherit);font-size:var(--vtp-font-size, inherit);color:var(--vtp-color, inherit)}.timepicker-shell__input{display:inline-flex;align-items:center;gap:.25rem;padding:var(--vtp-padding, .375rem .5rem);border-radius:var(--vtp-border-radius, 6px);background:var(--vtp-bg, #fff);border:1px solid var(--vtp-border, #ccc);color:var(--vtp-color, inherit);transition:border-color .15s,box-shadow .15s}.timepicker-shell__input:focus-within{border-color:var(--vtp-focus-border, #66afe9);box-shadow:var(--vtp-focus-ring, 0 0 0 2px rgba(102, 175, 233, .3))}.timepicker-shell__input--error{border-color:var(--vtp-error-border, #e74c3c);box-shadow:var(--vtp-error-ring, 0 0 0 2px rgba(231, 76, 60, .15))}.timepicker-shell__input .timepicker-separator{color:var(--vtp-separator-color, currentColor);opacity:.5;flex:0 0 auto;-webkit-user-select:none;user-select:none;line-height:1}.timepicker-field{flex:0 0 auto;width:auto;min-width:0;border:0;background:transparent;font:inherit;color:inherit;padding:0;margin:0;text-align:center}.timepicker-field:focus{outline:none}.timepicker-field::placeholder{color:var(--vtp-color, inherit);opacity:.4}.timepicker-dropdown{display:inline-block}.timepicker-dropdown__panel{min-width:48px;max-height:var(--vtp-dropdown-max-height, 220px);overflow-y:auto;overflow-x:hidden;padding:.25rem .125rem .25rem .25rem;border:1px solid var(--vtp-dropdown-border, var(--vtp-border, #ccc));border-radius:var(--vtp-dropdown-radius, .5rem);background:var(--vtp-dropdown-bg, var(--vtp-bg, #fff));box-shadow:var(--vtp-dropdown-shadow, 0 4px 12px rgba(0, 0, 0, .08));outline:none;scrollbar-width:thin;scrollbar-color:rgba(0,0,0,.25) transparent;scrollbar-gutter:stable}.timepicker-dropdown__panel::-webkit-scrollbar{width:5px}.timepicker-dropdown__panel::-webkit-scrollbar-thumb{background-color:#00000040;border-radius:3px}.timepicker-dropdown__panel::-webkit-scrollbar-track{background:transparent}.timepicker-option{padding:var(--vtp-option-padding, .3rem .5rem);border-radius:var(--vtp-option-radius, 4px);cursor:pointer;line-height:1.4;text-align:center;transition:background .1s}.timepicker-option--focused,.timepicker-option:hover{background:var(--vtp-option-hover-bg, rgba(0, 0, 0, .06))}.timepicker-option--active{background:var(--vtp-option-active-bg, #e8f0fe);color:var(--vtp-option-active-color, inherit);font-weight:var(--vtp-option-active-weight, 600)}.timepicker-option--disabled{opacity:.35;pointer-events:none}.vtp-cols{display:inline-flex;flex-wrap:nowrap;gap:var(--vtp-columns-gap, .25rem)}
@@ -0,0 +1,593 @@
1
+ import { defineComponent as Q, ref as T, onMounted as z, watch as L, createElementBlock as F, openBlock as $, createElementVNode as K, Fragment as me, renderList as ce, normalizeClass as q, toDisplayString as de, nextTick as X, computed as y, onBeforeUnmount as Z, createCommentVNode as U, createVNode as O, createBlock as W, unref as I, normalizeStyle as _ } from "vue";
2
+ const pe = { class: "timepicker-dropdown" }, ve = ["tabindex", "onClick", "onMousemove"], N = /* @__PURE__ */ Q({
3
+ __name: "TimeColumn",
4
+ props: {
5
+ items: {},
6
+ activeIndex: {}
7
+ },
8
+ emits: ["update:activeIndex", "select"],
9
+ setup(l, { emit: i }) {
10
+ const s = l, o = i, p = T(null);
11
+ function a() {
12
+ X(() => {
13
+ const v = p.value;
14
+ if (!v) return;
15
+ const g = v.querySelector(
16
+ ".timepicker-option--active"
17
+ );
18
+ if (g) {
19
+ const h = v.clientHeight, x = g.offsetTop, m = g.offsetHeight;
20
+ v.scrollTop = x - h / 2 + m / 2;
21
+ }
22
+ });
23
+ }
24
+ z(a), L(() => s.activeIndex, a);
25
+ function f(v) {
26
+ o("update:activeIndex", v), o("select", s.items[v]?.value);
27
+ }
28
+ const c = T(s.activeIndex ?? 0);
29
+ return (v, g) => ($(), F("div", pe, [
30
+ K("div", {
31
+ ref_key: "menu",
32
+ ref: p,
33
+ class: "timepicker-dropdown__panel",
34
+ role: "listbox",
35
+ tabindex: "-1"
36
+ }, [
37
+ ($(!0), F(me, null, ce(v.items, (h, x) => ($(), F("div", {
38
+ key: h.key,
39
+ class: q(["timepicker-option", {
40
+ "timepicker-option--active": x === v.activeIndex,
41
+ "timepicker-option--disabled": h.disabled,
42
+ "timepicker-option--focused": x === c.value
43
+ }]),
44
+ role: "option",
45
+ tabindex: h.disabled ? -1 : 0,
46
+ onClick: (m) => !h.disabled && f(x),
47
+ onMousemove: (m) => !h.disabled && (c.value = x)
48
+ }, de(h.text), 43, ve))), 128))
49
+ ], 512)
50
+ ]));
51
+ }
52
+ });
53
+ function ae(l) {
54
+ return /(a|A|p|P)/.test(l);
55
+ }
56
+ function fe(l) {
57
+ return /(p|P)/.test(l);
58
+ }
59
+ function J(l) {
60
+ return /k{1,2}/.test(l);
61
+ }
62
+ function j(l, i) {
63
+ if (!l) return { h: 0, m: 0, s: 0 };
64
+ const s = l.match(/\d+/g) || [];
65
+ let o = s[0] !== void 0 ? +s[0] : 0;
66
+ const p = +s[1] || 0, a = +s[2] || 0;
67
+ return { h: o, m: p, s: a };
68
+ }
69
+ function he(l) {
70
+ const i = l % 12;
71
+ return i === 0 ? 12 : i;
72
+ }
73
+ function ee(l, i) {
74
+ return i ? l % 12 + 12 : l % 12;
75
+ }
76
+ function ge(l) {
77
+ return /(s|ss)/.test(l);
78
+ }
79
+ function B(l, i) {
80
+ let { h: s, m: o, s: p } = i;
81
+ const a = ae(l), f = s >= 12 ? "PM" : "AM";
82
+ a && (s = he(s));
83
+ const c = s === 0 ? 24 : s, v = {
84
+ HH: String(s).padStart(2, "0"),
85
+ H: String(s),
86
+ hh: String(s).padStart(2, "0"),
87
+ h: String(s),
88
+ kk: String(c).padStart(2, "0"),
89
+ k: String(c),
90
+ mm: String(o).padStart(2, "0"),
91
+ m: String(o),
92
+ ss: String(p).padStart(2, "0"),
93
+ s: String(p),
94
+ A: f,
95
+ a: f.toLowerCase(),
96
+ P: f,
97
+ p: f.toLowerCase()
98
+ };
99
+ return l.replace(/HH|hh|kk|mm|ss|H|h|k|m|s|A|a|P|p/g, (g) => v[g] ?? g);
100
+ }
101
+ const te = /* @__PURE__ */ Q({
102
+ __name: "TimeSelection",
103
+ props: {
104
+ open: { type: Boolean },
105
+ initTime: {},
106
+ format: {},
107
+ hourStep: {},
108
+ minuteStep: {},
109
+ secondStep: {}
110
+ },
111
+ emits: ["update:initTime", "open", "close", "update:open"],
112
+ setup(l, { emit: i }) {
113
+ const s = y(() => ae(a.format)), o = y(() => ge(a.format)), p = y(() => J(a.format)), a = l, f = i, c = y({
114
+ get: () => a.open ?? !1,
115
+ set: (u) => {
116
+ const k = a.open ?? !1;
117
+ u !== k && (f("update:open", u), f(u ? "open" : "close"));
118
+ }
119
+ }), v = T(null);
120
+ function g(u) {
121
+ if (!c.value) return;
122
+ const k = u.target;
123
+ v.value && !v.value.contains(k) && (c.value = !1);
124
+ }
125
+ z(() => document.addEventListener("mousedown", g)), Z(
126
+ () => document.removeEventListener("mousedown", g)
127
+ );
128
+ function h(u) {
129
+ u.key === "Escape" && c.value && (c.value = !1);
130
+ }
131
+ z(() => document.addEventListener("keydown", h)), Z(() => document.removeEventListener("keydown", h));
132
+ const x = T(Math.floor(a.initTime.h / a.hourStep) || 0), m = T(Math.floor(a.initTime.m / a.minuteStep) || 0), V = T(Math.floor(a.initTime.s / a.secondStep) || 0);
133
+ L(
134
+ () => a.initTime,
135
+ (u) => {
136
+ const k = Math.max(1, a.hourStep), M = Math.max(1, a.minuteStep), A = Math.max(1, a.secondStep);
137
+ let w = u.h;
138
+ s.value ? (H.value = u.h >= 12 ? 1 : 0, w = u.h % 12) : p.value && u.h === 0 && (w = 24), x.value = Math.floor(w / k), m.value = Math.floor(u.m / M), V.value = Math.floor(u.s / A);
139
+ }
140
+ );
141
+ function P(u, k) {
142
+ const M = [];
143
+ for (let A = 0; A < u; A += Math.max(1, k))
144
+ M.push({ key: A, value: A, text: String(A).padStart(2, "0") });
145
+ return M;
146
+ }
147
+ function C(u, k) {
148
+ const M = Math.max(1, k), A = [];
149
+ for (let w = 0; w < 12; w += M) {
150
+ const ie = w === 0 ? 12 : w, Y = u ? w === 0 ? 12 : w + 12 : w;
151
+ A.push({ key: Y, value: Y, text: String(ie).padStart(2, "0") });
152
+ }
153
+ return A;
154
+ }
155
+ function E(u) {
156
+ const k = Math.max(1, u), M = [];
157
+ for (let A = 0; A < 24; A += k) {
158
+ const w = A === 0 ? 24 : A;
159
+ M.push({ key: A, value: A, text: String(w).padStart(2, "0") });
160
+ }
161
+ return M;
162
+ }
163
+ const H = T(fe(a.format) ? 1 : 0), D = y(() => {
164
+ if (!s.value)
165
+ return p.value ? E(a.hourStep) : P(24, a.hourStep);
166
+ const u = H.value === 1;
167
+ return C(u, a.hourStep);
168
+ }), b = y(() => P(60, a.minuteStep)), e = y(() => P(60, a.secondStep)), t = y(() => /\s[ap]$/.test(a.format)), n = y(() => {
169
+ const u = t.value ? "am" : "AM", k = t.value ? "pm" : "PM";
170
+ return [
171
+ { key: "AM", value: "AM", text: u },
172
+ { key: "PM", value: "PM", text: k }
173
+ ];
174
+ }), d = y(() => H.value === 1 ? "PM" : "AM"), r = y(() => {
175
+ const u = Number(D.value[x.value]?.value ?? 0);
176
+ return s.value ? d.value === "PM" ? ee(u, !0) : ee(u, !1) : p.value && u === 24 ? 0 : u;
177
+ }), S = y(
178
+ () => Number(b.value[m.value]?.value ?? 0)
179
+ ), G = y(
180
+ () => Number(e.value[V.value]?.value ?? 0)
181
+ );
182
+ function le(u) {
183
+ !o.value && !s.value && R();
184
+ }
185
+ function re(u) {
186
+ s.value || R();
187
+ }
188
+ function ue(u) {
189
+ R();
190
+ }
191
+ function R() {
192
+ c.value = !1;
193
+ }
194
+ return L(
195
+ [r, S, G],
196
+ ([u, k, M]) => {
197
+ f("update:initTime", { h: u, m: k, s: M });
198
+ },
199
+ { immediate: !0 }
200
+ ), (u, k) => c.value ? ($(), F("div", {
201
+ key: 0,
202
+ class: "vtp-cols",
203
+ ref_key: "root",
204
+ ref: v
205
+ }, [
206
+ O(N, {
207
+ activeIndex: x.value,
208
+ "onUpdate:activeIndex": k[0] || (k[0] = (M) => x.value = M),
209
+ items: D.value,
210
+ label: "Hours"
211
+ }, null, 8, ["activeIndex", "items"]),
212
+ O(N, {
213
+ activeIndex: m.value,
214
+ "onUpdate:activeIndex": k[1] || (k[1] = (M) => m.value = M),
215
+ items: b.value,
216
+ label: "Minutes",
217
+ onSelect: le
218
+ }, null, 8, ["activeIndex", "items"]),
219
+ o.value ? ($(), W(N, {
220
+ key: 0,
221
+ activeIndex: V.value,
222
+ "onUpdate:activeIndex": k[2] || (k[2] = (M) => V.value = M),
223
+ items: e.value,
224
+ label: "Seconds",
225
+ onSelect: re
226
+ }, null, 8, ["activeIndex", "items"])) : U("", !0),
227
+ s.value ? ($(), W(N, {
228
+ key: 1,
229
+ activeIndex: H.value,
230
+ "onUpdate:activeIndex": k[3] || (k[3] = (M) => H.value = M),
231
+ items: n.value,
232
+ label: "AM/PM",
233
+ onSelect: ue
234
+ }, null, 8, ["activeIndex", "items"])) : U("", !0)
235
+ ], 512)) : U("", !0);
236
+ }
237
+ }), se = /^(HH|H|hh|h|kk|k):(mm|m)(?::(ss|s))?(?:\s*(A|a|P|p))?$/, ne = /^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$/, ke = {
238
+ modelValue: {
239
+ type: [String, Array],
240
+ default: void 0,
241
+ validator: (l) => {
242
+ let i;
243
+ return Array.isArray(l) ? i = l.length === 2 && l.every((s) => ne.test(s)) : i = l == null || ne.test(l), i;
244
+ }
245
+ },
246
+ range: {
247
+ type: Boolean,
248
+ default: !1
249
+ },
250
+ hourStep: { type: Number, default: 1 },
251
+ minuteStep: { type: Number, default: 1 },
252
+ secondStep: { type: Number, default: 1 },
253
+ format: {
254
+ type: String,
255
+ default: "HH:mm",
256
+ validator: (l) => se.test(l)
257
+ }
258
+ };
259
+ function ye(l) {
260
+ const i = se.exec(l);
261
+ if (!i) throw new Error(`[useTimeMask] Invalid format: ${l}`);
262
+ const [, s, o, p, a] = i, f = [], c = !!a, v = /^k{1,2}$/.test(s);
263
+ let g = 0, h = 23;
264
+ return c ? (g = 1, h = 12) : v && (g = 1, h = 24), f.push({ token: s, min: g, max: h }), f.push({ token: o, min: 0, max: 59 }), p && f.push({ token: p, min: 0, max: 59 }), {
265
+ digitGroups: f,
266
+ hasAmPm: !!a,
267
+ ampmLowercase: a === "a" || a === "p"
268
+ };
269
+ }
270
+ function oe(l) {
271
+ const i = y(() => ye(l.value)), s = y(() => i.value.digitGroups.length * 2), o = T([]), p = T("AM"), a = T("");
272
+ function f() {
273
+ const { digitGroups: e, hasAmPm: t } = i.value;
274
+ let n = "", d = 0;
275
+ for (let r = 0; r < e.length; r++) {
276
+ for (let S = 0; S < 2; S++)
277
+ d < o.value.length && (n += String(o.value[d]), d++);
278
+ d === (r + 1) * 2 && r < e.length - 1 && (n += ":");
279
+ }
280
+ if (t && d >= s.value) {
281
+ const r = i.value.ampmLowercase ? p.value.toLowerCase() : p.value;
282
+ n += " " + r;
283
+ }
284
+ return n;
285
+ }
286
+ function c(e) {
287
+ const t = e * 2;
288
+ if (o.value.length < t + 2) return;
289
+ const n = o.value[t] * 10 + o.value[t + 1], { min: d, max: r } = i.value.digitGroups[e], S = Math.max(d, Math.min(r, n));
290
+ S !== n && (o.value[t] = Math.floor(S / 10), o.value[t + 1] = S % 10);
291
+ }
292
+ function v() {
293
+ for (let e = 0; e < i.value.digitGroups.length; e++)
294
+ c(e);
295
+ }
296
+ function g(e) {
297
+ const t = f();
298
+ let n = 0;
299
+ for (let d = 0; d < Math.min(e, t.length); d++)
300
+ /\d/.test(t[d]) && n++;
301
+ return n;
302
+ }
303
+ function h(e) {
304
+ return e + Math.floor(e / 2);
305
+ }
306
+ function x(e, t) {
307
+ if (e >= s.value) return s.value;
308
+ const n = [...o.value];
309
+ n[e] = t, o.value = n;
310
+ const d = Math.floor(e / 2);
311
+ return c(d), Math.min(e + 1, s.value);
312
+ }
313
+ function m(e, t) {
314
+ const n = f();
315
+ a.value = n, e.value = n;
316
+ const d = t !== void 0 ? Math.min(h(t), n.length) : n.length;
317
+ e.selectionStart = e.selectionEnd = d, X(() => {
318
+ document.activeElement === e && (e.selectionStart = e.selectionEnd = d);
319
+ });
320
+ }
321
+ function V(e) {
322
+ const t = e.key, n = e.target;
323
+ if (["Tab", "Escape", "ArrowLeft", "ArrowRight", "Home", "End"].includes(
324
+ t
325
+ ) || e.metaKey || e.ctrlKey)
326
+ return;
327
+ e.preventDefault();
328
+ const d = n.selectionStart ?? 0, r = g(d);
329
+ if (t === "Backspace") {
330
+ r > 0 && m(n, r - 1);
331
+ return;
332
+ }
333
+ if (t !== "Delete") {
334
+ if (i.value.hasAmPm) {
335
+ const S = t.toLowerCase();
336
+ if (S === "a") {
337
+ p.value = "AM", m(n, r);
338
+ return;
339
+ }
340
+ if (S === "p") {
341
+ p.value = "PM", m(n, r);
342
+ return;
343
+ }
344
+ }
345
+ if (/^\d$/.test(t)) {
346
+ const S = x(r, +t);
347
+ m(n, S);
348
+ return;
349
+ }
350
+ }
351
+ }
352
+ function P(e) {
353
+ const t = e.target, n = t.value.replace(/\D/g, "").split("").map(Number).slice(0, s.value);
354
+ o.value = n, v(), i.value.hasAmPm && (/p/i.test(t.value) ? p.value = "PM" : /a/i.test(t.value) && (p.value = "AM")), m(t);
355
+ }
356
+ function C(e) {
357
+ e.preventDefault();
358
+ const t = e.clipboardData?.getData("text") ?? "", n = e.target, d = n.selectionStart ?? 0, r = t.replace(/\D/g, "").split("").map(Number);
359
+ let S = g(d);
360
+ for (const G of r) {
361
+ if (S >= s.value) break;
362
+ S = x(S, G);
363
+ }
364
+ i.value.hasAmPm && (/p\.?m\.?/i.test(t) ? p.value = "PM" : /a\.?m\.?/i.test(t) && (p.value = "AM")), m(n, S);
365
+ }
366
+ function E(e) {
367
+ const { digitGroups: t, hasAmPm: n } = i.value, d = [];
368
+ let r = e.h;
369
+ n ? (p.value = e.h >= 12 ? "PM" : "AM", r = e.h % 12, r === 0 && (r = 12)) : J(l.value) && (r = e.h === 0 ? 24 : e.h), d.push(Math.floor(r / 10), r % 10), d.push(Math.floor(e.m / 10), e.m % 10), t.length > 2 && d.push(Math.floor(e.s / 10), e.s % 10), o.value = d, a.value = f();
370
+ }
371
+ function H() {
372
+ if (o.value.length < s.value) return null;
373
+ const e = [];
374
+ for (let r = 0; r < i.value.digitGroups.length; r++) {
375
+ const S = r * 2;
376
+ e.push(o.value[S] * 10 + o.value[S + 1]);
377
+ }
378
+ let t = e[0];
379
+ const n = e[1], d = e[2] ?? 0;
380
+ return i.value.hasAmPm && (t = p.value === "PM" ? t === 12 ? 12 : t + 12 : t === 12 ? 0 : t), J(l.value) && t === 24 && (t = 0), { h: t, m: n, s: d };
381
+ }
382
+ const D = y(
383
+ () => o.value.length >= s.value
384
+ ), b = y(() => i.value.ampmLowercase);
385
+ return {
386
+ inputValue: a,
387
+ handleKeydown: V,
388
+ handleInput: P,
389
+ handlePaste: C,
390
+ setFromTime: E,
391
+ getParsedTime: H,
392
+ isComplete: D,
393
+ totalDigits: s,
394
+ displayPosToDigitIndex: g,
395
+ ampm: p,
396
+ ampmLowercase: b
397
+ };
398
+ }
399
+ const Se = { class: "timepicker-shell" }, Me = ["value", "placeholder"], xe = ["value", "placeholder"], Ae = ["value", "placeholder"], Ie = /* @__PURE__ */ Q({
400
+ __name: "TimePicker",
401
+ props: ke,
402
+ emits: ["update:modelValue", "open", "close", "error"],
403
+ setup(l, { emit: i }) {
404
+ const s = T(null), o = l, p = i, a = T(!1), f = T(!1);
405
+ L(a, (e) => {
406
+ e && (f.value = !1);
407
+ }), L(f, (e) => {
408
+ e && (a.value = !1);
409
+ });
410
+ const c = y({
411
+ get() {
412
+ if (Array.isArray(o.modelValue)) {
413
+ const [e, t] = o.modelValue;
414
+ return [j(e, o.format), j(t, o.format)];
415
+ } else
416
+ return j(o.modelValue, o.format);
417
+ },
418
+ set(e) {
419
+ Array.isArray(e) ? p("update:modelValue", [
420
+ B("HH:mm:ss", e[0]),
421
+ B("HH:mm:ss", e[1])
422
+ ]) : p("update:modelValue", B("HH:mm:ss", e));
423
+ }
424
+ }), v = y({
425
+ get() {
426
+ return Array.isArray(c.value) ? c.value[0] : c.value;
427
+ },
428
+ set(e) {
429
+ Array.isArray(c.value) ? c.value = [e, c.value[1]] : c.value = e;
430
+ }
431
+ }), g = y({
432
+ get() {
433
+ return Array.isArray(c.value) ? c.value[1] : c.value;
434
+ },
435
+ set(e) {
436
+ Array.isArray(c.value) && (c.value = [c.value[0], e]);
437
+ }
438
+ });
439
+ y(() => {
440
+ if (!o.modelValue) return "—";
441
+ const e = (t) => B(o.format, t);
442
+ return o.range ? `${e(v.value)} → ${e(g.value)}` : e(v.value);
443
+ }), L(
444
+ () => o.range,
445
+ (e) => {
446
+ if (e) {
447
+ if (!Array.isArray(o.modelValue))
448
+ throw new RangeError(
449
+ `Model value must be an array for range selection: ${o.modelValue}`
450
+ );
451
+ } else if (Array.isArray(o.modelValue))
452
+ throw new RangeError(
453
+ `Model value must be a single string for single time selection: ${o.modelValue}`
454
+ );
455
+ },
456
+ { immediate: !0 }
457
+ );
458
+ const h = y(() => o.format ?? "HH:mm:ss"), x = y(() => {
459
+ let e = h.value.length;
460
+ return /[AaPp]$/.test(h.value) && (e += 1), `${Math.min(12, Math.max(4, e))}ch`;
461
+ }), m = oe(h), V = m.inputValue, P = oe(h), C = P.inputValue;
462
+ L(
463
+ () => [v.value, h.value],
464
+ ([e]) => {
465
+ m.setFromTime(e);
466
+ },
467
+ { immediate: !0 }
468
+ ), L(
469
+ () => [g.value, h.value, o.range],
470
+ ([e, , t]) => {
471
+ if (!t) {
472
+ P.setFromTime({ h: 0, m: 0, s: 0 });
473
+ return;
474
+ }
475
+ P.setFromTime(e);
476
+ },
477
+ { immediate: !0 }
478
+ );
479
+ const E = T(null);
480
+ function H(e) {
481
+ if (e.key === "Enter") {
482
+ e.preventDefault(), b("first");
483
+ return;
484
+ }
485
+ /^\d$/.test(e.key) && (a.value = !1, f.value = !1);
486
+ const t = e.target, n = m.displayPosToDigitIndex(t.selectionStart ?? 0), d = /^\d$/.test(e.key) && n >= m.totalDigits.value - 1;
487
+ if (m.handleKeydown(e), /^\d$/.test(e.key)) {
488
+ const r = m.getParsedTime();
489
+ r && (s.value = null, v.value = r);
490
+ }
491
+ o.range && d && E.value && (b("first"), X(() => {
492
+ const r = E.value;
493
+ r && (r.focus(), r.selectionStart = r.selectionEnd = 0);
494
+ }));
495
+ }
496
+ function D(e) {
497
+ if (e.key === "Enter") {
498
+ e.preventDefault(), b("second");
499
+ return;
500
+ }
501
+ if (/^\d$/.test(e.key) && (a.value = !1, f.value = !1), P.handleKeydown(e), /^\d$/.test(e.key)) {
502
+ const t = P.getParsedTime();
503
+ t && (s.value = null, g.value = t);
504
+ }
505
+ }
506
+ function b(e) {
507
+ const n = (e === "first" ? m : P).getParsedTime();
508
+ n && (s.value = null, e === "first" ? v.value = n : o.range && (g.value = n)), e === "first" ? m.setFromTime(v.value) : o.range && P.setFromTime(g.value);
509
+ }
510
+ return (e, t) => ($(), F("div", Se, [
511
+ o.range ? ($(), F("div", {
512
+ key: 1,
513
+ class: q(["timepicker-shell__input", { "timepicker-shell__input--error": s.value }])
514
+ }, [
515
+ K("input", {
516
+ type: "text",
517
+ class: "timepicker-field",
518
+ value: I(V),
519
+ placeholder: h.value,
520
+ style: _({ width: x.value }),
521
+ onFocus: t[4] || (t[4] = (n) => a.value = !0),
522
+ onKeydown: H,
523
+ onInput: t[5] || (t[5] = //@ts-ignore
524
+ (...n) => I(m).handleInput && I(m).handleInput(...n)),
525
+ onPaste: t[6] || (t[6] = //@ts-ignore
526
+ (...n) => I(m).handlePaste && I(m).handlePaste(...n)),
527
+ onBlur: t[7] || (t[7] = (n) => b("first"))
528
+ }, null, 44, xe),
529
+ t[16] || (t[16] = K("span", { class: "timepicker-separator" }, "–", -1)),
530
+ K("input", {
531
+ ref_key: "secondInputRef",
532
+ ref: E,
533
+ type: "text",
534
+ class: "timepicker-field",
535
+ value: I(C),
536
+ placeholder: h.value,
537
+ style: _({ width: x.value }),
538
+ onFocus: t[8] || (t[8] = (n) => f.value = !0),
539
+ onKeydown: D,
540
+ onInput: t[9] || (t[9] = //@ts-ignore
541
+ (...n) => I(P).handleInput && I(P).handleInput(...n)),
542
+ onPaste: t[10] || (t[10] = //@ts-ignore
543
+ (...n) => I(P).handlePaste && I(P).handlePaste(...n)),
544
+ onBlur: t[11] || (t[11] = (n) => b("second"))
545
+ }, null, 44, Ae)
546
+ ], 2)) : ($(), F("div", {
547
+ key: 0,
548
+ class: q(["timepicker-shell__input", { "timepicker-shell__input--error": s.value }])
549
+ }, [
550
+ K("input", {
551
+ type: "text",
552
+ class: "timepicker-field",
553
+ value: I(V),
554
+ placeholder: h.value,
555
+ style: _({ width: x.value }),
556
+ onFocus: t[0] || (t[0] = (n) => a.value = !0),
557
+ onKeydown: H,
558
+ onInput: t[1] || (t[1] = //@ts-ignore
559
+ (...n) => I(m).handleInput && I(m).handleInput(...n)),
560
+ onPaste: t[2] || (t[2] = //@ts-ignore
561
+ (...n) => I(m).handlePaste && I(m).handlePaste(...n)),
562
+ onBlur: t[3] || (t[3] = (n) => b("first"))
563
+ }, null, 44, Me)
564
+ ], 2)),
565
+ K("div", null, [
566
+ O(te, {
567
+ open: a.value,
568
+ "onUpdate:open": t[12] || (t[12] = (n) => a.value = n),
569
+ initTime: v.value,
570
+ "onUpdate:initTime": t[13] || (t[13] = (n) => v.value = n),
571
+ format: o.format,
572
+ "hour-step": o.hourStep,
573
+ "minute-step": o.minuteStep,
574
+ "second-step": o.secondStep
575
+ }, null, 8, ["open", "initTime", "format", "hour-step", "minute-step", "second-step"]),
576
+ o.range ? ($(), W(te, {
577
+ key: 0,
578
+ open: f.value,
579
+ "onUpdate:open": t[14] || (t[14] = (n) => f.value = n),
580
+ initTime: g.value,
581
+ "onUpdate:initTime": t[15] || (t[15] = (n) => g.value = n),
582
+ format: o.format,
583
+ "hour-step": o.hourStep,
584
+ "minute-step": o.minuteStep,
585
+ "second-step": o.secondStep
586
+ }, null, 8, ["open", "initTime", "format", "hour-step", "minute-step", "second-step"])) : U("", !0)
587
+ ])
588
+ ]));
589
+ }
590
+ });
591
+ export {
592
+ Ie as TimePicker
593
+ };
@@ -0,0 +1 @@
1
+ (function(E,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],e):(E=typeof globalThis<"u"?globalThis:E||self,e(E.VueTimepicker={},E.Vue))})(this,(function(E,e){"use strict";const q={class:"timepicker-dropdown"},O=["tabindex","onClick","onMousemove"],$=e.defineComponent({__name:"TimeColumn",props:{items:{},activeIndex:{}},emits:["update:activeIndex","select"],setup(s,{emit:c}){const r=s,a=c,f=e.ref(null);function l(){e.nextTick(()=>{const h=f.value;if(!h)return;const g=h.querySelector(".timepicker-option--active");if(g){const k=h.clientHeight,x=g.offsetTop,m=g.offsetHeight;h.scrollTop=x-k/2+m/2}})}e.onMounted(l),e.watch(()=>r.activeIndex,l);function v(h){a("update:activeIndex",h),a("select",r.items[h]?.value)}const d=e.ref(r.activeIndex??0);return(h,g)=>(e.openBlock(),e.createElementBlock("div",q,[e.createElementVNode("div",{ref_key:"menu",ref:f,class:"timepicker-dropdown__panel",role:"listbox",tabindex:"-1"},[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(h.items,(k,x)=>(e.openBlock(),e.createElementBlock("div",{key:k.key,class:e.normalizeClass(["timepicker-option",{"timepicker-option--active":x===h.activeIndex,"timepicker-option--disabled":k.disabled,"timepicker-option--focused":x===d.value}]),role:"option",tabindex:k.disabled?-1:0,onClick:m=>!k.disabled&&v(x),onMousemove:m=>!k.disabled&&(d.value=x)},e.toDisplayString(k.text),43,O))),128))],512)]))}});function K(s){return/(a|A|p|P)/.test(s)}function W(s){return/(p|P)/.test(s)}function N(s){return/k{1,2}/.test(s)}function C(s,c){if(!s)return{h:0,m:0,s:0};const r=s.match(/\d+/g)||[];let a=r[0]!==void 0?+r[0]:0;const f=+r[1]||0,l=+r[2]||0;return{h:a,m:f,s:l}}function J(s){const c=s%12;return c===0?12:c}function U(s,c){return c?s%12+12:s%12}function Q(s){return/(s|ss)/.test(s)}function L(s,c){let{h:r,m:a,s:f}=c;const l=K(s),v=r>=12?"PM":"AM";l&&(r=J(r));const d=r===0?24:r,h={HH:String(r).padStart(2,"0"),H:String(r),hh:String(r).padStart(2,"0"),h:String(r),kk:String(d).padStart(2,"0"),k:String(d),mm:String(a).padStart(2,"0"),m:String(a),ss:String(f).padStart(2,"0"),s:String(f),A:v,a:v.toLowerCase(),P:v,p:v.toLowerCase()};return s.replace(/HH|hh|kk|mm|ss|H|h|k|m|s|A|a|P|p/g,g=>h[g]??g)}const G=e.defineComponent({__name:"TimeSelection",props:{open:{type:Boolean},initTime:{},format:{},hourStep:{},minuteStep:{},secondStep:{}},emits:["update:initTime","open","close","update:open"],setup(s,{emit:c}){const r=e.computed(()=>K(l.format)),a=e.computed(()=>Q(l.format)),f=e.computed(()=>N(l.format)),l=s,v=c,d=e.computed({get:()=>l.open??!1,set:u=>{const y=l.open??!1;u!==y&&(v("update:open",u),v(u?"open":"close"))}}),h=e.ref(null);function g(u){if(!d.value)return;const y=u.target;h.value&&!h.value.contains(y)&&(d.value=!1)}e.onMounted(()=>document.addEventListener("mousedown",g)),e.onBeforeUnmount(()=>document.removeEventListener("mousedown",g));function k(u){u.key==="Escape"&&d.value&&(d.value=!1)}e.onMounted(()=>document.addEventListener("keydown",k)),e.onBeforeUnmount(()=>document.removeEventListener("keydown",k));const x=e.ref(Math.floor(l.initTime.h/l.hourStep)||0),m=e.ref(Math.floor(l.initTime.m/l.minuteStep)||0),V=e.ref(Math.floor(l.initTime.s/l.secondStep)||0);e.watch(()=>l.initTime,u=>{const y=Math.max(1,l.hourStep),M=Math.max(1,l.minuteStep),A=Math.max(1,l.secondStep);let w=u.h;r.value?(I.value=u.h>=12?1:0,w=u.h%12):f.value&&u.h===0&&(w=24),x.value=Math.floor(w/y),m.value=Math.floor(u.m/M),V.value=Math.floor(u.s/A)});function P(u,y){const M=[];for(let A=0;A<u;A+=Math.max(1,y))M.push({key:A,value:A,text:String(A).padStart(2,"0")});return M}function H(u,y){const M=Math.max(1,y),A=[];for(let w=0;w<12;w+=M){const se=w===0?12:w,_=u?w===0?12:w+12:w;A.push({key:_,value:_,text:String(se).padStart(2,"0")})}return A}function b(u){const y=Math.max(1,u),M=[];for(let A=0;A<24;A+=y){const w=A===0?24:A;M.push({key:A,value:A,text:String(w).padStart(2,"0")})}return M}const I=e.ref(W(l.format)?1:0),B=e.computed(()=>{if(!r.value)return f.value?b(l.hourStep):P(24,l.hourStep);const u=I.value===1;return H(u,l.hourStep)}),T=e.computed(()=>P(60,l.minuteStep)),t=e.computed(()=>P(60,l.secondStep)),n=e.computed(()=>/\s[ap]$/.test(l.format)),o=e.computed(()=>{const u=n.value?"am":"AM",y=n.value?"pm":"PM";return[{key:"AM",value:"AM",text:u},{key:"PM",value:"PM",text:y}]}),p=e.computed(()=>I.value===1?"PM":"AM"),i=e.computed(()=>{const u=Number(B.value[x.value]?.value??0);return r.value?p.value==="PM"?U(u,!0):U(u,!1):f.value&&u===24?0:u}),S=e.computed(()=>Number(T.value[m.value]?.value??0)),F=e.computed(()=>Number(t.value[V.value]?.value??0));function ae(u){!a.value&&!r.value&&D()}function le(u){r.value||D()}function re(u){D()}function D(){d.value=!1}return e.watch([i,S,F],([u,y,M])=>{v("update:initTime",{h:u,m:y,s:M})},{immediate:!0}),(u,y)=>d.value?(e.openBlock(),e.createElementBlock("div",{key:0,class:"vtp-cols",ref_key:"root",ref:h},[e.createVNode($,{activeIndex:x.value,"onUpdate:activeIndex":y[0]||(y[0]=M=>x.value=M),items:B.value,label:"Hours"},null,8,["activeIndex","items"]),e.createVNode($,{activeIndex:m.value,"onUpdate:activeIndex":y[1]||(y[1]=M=>m.value=M),items:T.value,label:"Minutes",onSelect:ae},null,8,["activeIndex","items"]),a.value?(e.openBlock(),e.createBlock($,{key:0,activeIndex:V.value,"onUpdate:activeIndex":y[2]||(y[2]=M=>V.value=M),items:t.value,label:"Seconds",onSelect:le},null,8,["activeIndex","items"])):e.createCommentVNode("",!0),r.value?(e.openBlock(),e.createBlock($,{key:1,activeIndex:I.value,"onUpdate:activeIndex":y[3]||(y[3]=M=>I.value=M),items:o.value,label:"AM/PM",onSelect:re},null,8,["activeIndex","items"])):e.createCommentVNode("",!0)],512)):e.createCommentVNode("",!0)}}),z=/^(HH|H|hh|h|kk|k):(mm|m)(?::(ss|s))?(?:\s*(A|a|P|p))?$/,R=/^([01]\d|2[0-3]):([0-5]\d)(:([0-5]\d))?$/,X={modelValue:{type:[String,Array],default:void 0,validator:s=>{let c;return Array.isArray(s)?c=s.length===2&&s.every(r=>R.test(r)):c=s==null||R.test(s),c}},range:{type:Boolean,default:!1},hourStep:{type:Number,default:1},minuteStep:{type:Number,default:1},secondStep:{type:Number,default:1},format:{type:String,default:"HH:mm",validator:s=>z.test(s)}};function Y(s){const c=z.exec(s);if(!c)throw new Error(`[useTimeMask] Invalid format: ${s}`);const[,r,a,f,l]=c,v=[],d=!!l,h=/^k{1,2}$/.test(r);let g=0,k=23;return d?(g=1,k=12):h&&(g=1,k=24),v.push({token:r,min:g,max:k}),v.push({token:a,min:0,max:59}),f&&v.push({token:f,min:0,max:59}),{digitGroups:v,hasAmPm:!!l,ampmLowercase:l==="a"||l==="p"}}function j(s){const c=e.computed(()=>Y(s.value)),r=e.computed(()=>c.value.digitGroups.length*2),a=e.ref([]),f=e.ref("AM"),l=e.ref("");function v(){const{digitGroups:t,hasAmPm:n}=c.value;let o="",p=0;for(let i=0;i<t.length;i++){for(let S=0;S<2;S++)p<a.value.length&&(o+=String(a.value[p]),p++);p===(i+1)*2&&i<t.length-1&&(o+=":")}if(n&&p>=r.value){const i=c.value.ampmLowercase?f.value.toLowerCase():f.value;o+=" "+i}return o}function d(t){const n=t*2;if(a.value.length<n+2)return;const o=a.value[n]*10+a.value[n+1],{min:p,max:i}=c.value.digitGroups[t],S=Math.max(p,Math.min(i,o));S!==o&&(a.value[n]=Math.floor(S/10),a.value[n+1]=S%10)}function h(){for(let t=0;t<c.value.digitGroups.length;t++)d(t)}function g(t){const n=v();let o=0;for(let p=0;p<Math.min(t,n.length);p++)/\d/.test(n[p])&&o++;return o}function k(t){return t+Math.floor(t/2)}function x(t,n){if(t>=r.value)return r.value;const o=[...a.value];o[t]=n,a.value=o;const p=Math.floor(t/2);return d(p),Math.min(t+1,r.value)}function m(t,n){const o=v();l.value=o,t.value=o;const p=n!==void 0?Math.min(k(n),o.length):o.length;t.selectionStart=t.selectionEnd=p,e.nextTick(()=>{document.activeElement===t&&(t.selectionStart=t.selectionEnd=p)})}function V(t){const n=t.key,o=t.target;if(["Tab","Escape","ArrowLeft","ArrowRight","Home","End"].includes(n)||t.metaKey||t.ctrlKey)return;t.preventDefault();const p=o.selectionStart??0,i=g(p);if(n==="Backspace"){i>0&&m(o,i-1);return}if(n!=="Delete"){if(c.value.hasAmPm){const S=n.toLowerCase();if(S==="a"){f.value="AM",m(o,i);return}if(S==="p"){f.value="PM",m(o,i);return}}if(/^\d$/.test(n)){const S=x(i,+n);m(o,S);return}}}function P(t){const n=t.target,o=n.value.replace(/\D/g,"").split("").map(Number).slice(0,r.value);a.value=o,h(),c.value.hasAmPm&&(/p/i.test(n.value)?f.value="PM":/a/i.test(n.value)&&(f.value="AM")),m(n)}function H(t){t.preventDefault();const n=t.clipboardData?.getData("text")??"",o=t.target,p=o.selectionStart??0,i=n.replace(/\D/g,"").split("").map(Number);let S=g(p);for(const F of i){if(S>=r.value)break;S=x(S,F)}c.value.hasAmPm&&(/p\.?m\.?/i.test(n)?f.value="PM":/a\.?m\.?/i.test(n)&&(f.value="AM")),m(o,S)}function b(t){const{digitGroups:n,hasAmPm:o}=c.value,p=[];let i=t.h;o?(f.value=t.h>=12?"PM":"AM",i=t.h%12,i===0&&(i=12)):N(s.value)&&(i=t.h===0?24:t.h),p.push(Math.floor(i/10),i%10),p.push(Math.floor(t.m/10),t.m%10),n.length>2&&p.push(Math.floor(t.s/10),t.s%10),a.value=p,l.value=v()}function I(){if(a.value.length<r.value)return null;const t=[];for(let i=0;i<c.value.digitGroups.length;i++){const S=i*2;t.push(a.value[S]*10+a.value[S+1])}let n=t[0];const o=t[1],p=t[2]??0;return c.value.hasAmPm&&(n=f.value==="PM"?n===12?12:n+12:n===12?0:n),N(s.value)&&n===24&&(n=0),{h:n,m:o,s:p}}const B=e.computed(()=>a.value.length>=r.value),T=e.computed(()=>c.value.ampmLowercase);return{inputValue:l,handleKeydown:V,handleInput:P,handlePaste:H,setFromTime:b,getParsedTime:I,isComplete:B,totalDigits:r,displayPosToDigitIndex:g,ampm:f,ampmLowercase:T}}const Z={class:"timepicker-shell"},ee=["value","placeholder"],te=["value","placeholder"],ne=["value","placeholder"],oe=e.defineComponent({__name:"TimePicker",props:X,emits:["update:modelValue","open","close","error"],setup(s,{emit:c}){const r=e.ref(null),a=s,f=c,l=e.ref(!1),v=e.ref(!1);e.watch(l,t=>{t&&(v.value=!1)}),e.watch(v,t=>{t&&(l.value=!1)});const d=e.computed({get(){if(Array.isArray(a.modelValue)){const[t,n]=a.modelValue;return[C(t,a.format),C(n,a.format)]}else return C(a.modelValue,a.format)},set(t){Array.isArray(t)?f("update:modelValue",[L("HH:mm:ss",t[0]),L("HH:mm:ss",t[1])]):f("update:modelValue",L("HH:mm:ss",t))}}),h=e.computed({get(){return Array.isArray(d.value)?d.value[0]:d.value},set(t){Array.isArray(d.value)?d.value=[t,d.value[1]]:d.value=t}}),g=e.computed({get(){return Array.isArray(d.value)?d.value[1]:d.value},set(t){Array.isArray(d.value)&&(d.value=[d.value[0],t])}});e.computed(()=>{if(!a.modelValue)return"—";const t=n=>L(a.format,n);return a.range?`${t(h.value)} → ${t(g.value)}`:t(h.value)}),e.watch(()=>a.range,t=>{if(t){if(!Array.isArray(a.modelValue))throw new RangeError(`Model value must be an array for range selection: ${a.modelValue}`)}else if(Array.isArray(a.modelValue))throw new RangeError(`Model value must be a single string for single time selection: ${a.modelValue}`)},{immediate:!0});const k=e.computed(()=>a.format??"HH:mm:ss"),x=e.computed(()=>{let t=k.value.length;return/[AaPp]$/.test(k.value)&&(t+=1),`${Math.min(12,Math.max(4,t))}ch`}),m=j(k),V=m.inputValue,P=j(k),H=P.inputValue;e.watch(()=>[h.value,k.value],([t])=>{m.setFromTime(t)},{immediate:!0}),e.watch(()=>[g.value,k.value,a.range],([t,,n])=>{if(!n){P.setFromTime({h:0,m:0,s:0});return}P.setFromTime(t)},{immediate:!0});const b=e.ref(null);function I(t){if(t.key==="Enter"){t.preventDefault(),T("first");return}/^\d$/.test(t.key)&&(l.value=!1,v.value=!1);const n=t.target,o=m.displayPosToDigitIndex(n.selectionStart??0),p=/^\d$/.test(t.key)&&o>=m.totalDigits.value-1;if(m.handleKeydown(t),/^\d$/.test(t.key)){const i=m.getParsedTime();i&&(r.value=null,h.value=i)}a.range&&p&&b.value&&(T("first"),e.nextTick(()=>{const i=b.value;i&&(i.focus(),i.selectionStart=i.selectionEnd=0)}))}function B(t){if(t.key==="Enter"){t.preventDefault(),T("second");return}if(/^\d$/.test(t.key)&&(l.value=!1,v.value=!1),P.handleKeydown(t),/^\d$/.test(t.key)){const n=P.getParsedTime();n&&(r.value=null,g.value=n)}}function T(t){const o=(t==="first"?m:P).getParsedTime();o&&(r.value=null,t==="first"?h.value=o:a.range&&(g.value=o)),t==="first"?m.setFromTime(h.value):a.range&&P.setFromTime(g.value)}return(t,n)=>(e.openBlock(),e.createElementBlock("div",Z,[a.range?(e.openBlock(),e.createElementBlock("div",{key:1,class:e.normalizeClass(["timepicker-shell__input",{"timepicker-shell__input--error":r.value}])},[e.createElementVNode("input",{type:"text",class:"timepicker-field",value:e.unref(V),placeholder:k.value,style:e.normalizeStyle({width:x.value}),onFocus:n[4]||(n[4]=o=>l.value=!0),onKeydown:I,onInput:n[5]||(n[5]=(...o)=>e.unref(m).handleInput&&e.unref(m).handleInput(...o)),onPaste:n[6]||(n[6]=(...o)=>e.unref(m).handlePaste&&e.unref(m).handlePaste(...o)),onBlur:n[7]||(n[7]=o=>T("first"))},null,44,te),n[16]||(n[16]=e.createElementVNode("span",{class:"timepicker-separator"},"–",-1)),e.createElementVNode("input",{ref_key:"secondInputRef",ref:b,type:"text",class:"timepicker-field",value:e.unref(H),placeholder:k.value,style:e.normalizeStyle({width:x.value}),onFocus:n[8]||(n[8]=o=>v.value=!0),onKeydown:B,onInput:n[9]||(n[9]=(...o)=>e.unref(P).handleInput&&e.unref(P).handleInput(...o)),onPaste:n[10]||(n[10]=(...o)=>e.unref(P).handlePaste&&e.unref(P).handlePaste(...o)),onBlur:n[11]||(n[11]=o=>T("second"))},null,44,ne)],2)):(e.openBlock(),e.createElementBlock("div",{key:0,class:e.normalizeClass(["timepicker-shell__input",{"timepicker-shell__input--error":r.value}])},[e.createElementVNode("input",{type:"text",class:"timepicker-field",value:e.unref(V),placeholder:k.value,style:e.normalizeStyle({width:x.value}),onFocus:n[0]||(n[0]=o=>l.value=!0),onKeydown:I,onInput:n[1]||(n[1]=(...o)=>e.unref(m).handleInput&&e.unref(m).handleInput(...o)),onPaste:n[2]||(n[2]=(...o)=>e.unref(m).handlePaste&&e.unref(m).handlePaste(...o)),onBlur:n[3]||(n[3]=o=>T("first"))},null,44,ee)],2)),e.createElementVNode("div",null,[e.createVNode(G,{open:l.value,"onUpdate:open":n[12]||(n[12]=o=>l.value=o),initTime:h.value,"onUpdate:initTime":n[13]||(n[13]=o=>h.value=o),format:a.format,"hour-step":a.hourStep,"minute-step":a.minuteStep,"second-step":a.secondStep},null,8,["open","initTime","format","hour-step","minute-step","second-step"]),a.range?(e.openBlock(),e.createBlock(G,{key:0,open:v.value,"onUpdate:open":n[14]||(n[14]=o=>v.value=o),initTime:g.value,"onUpdate:initTime":n[15]||(n[15]=o=>g.value=o),format:a.format,"hour-step":a.hourStep,"minute-step":a.minuteStep,"second-step":a.secondStep},null,8,["open","initTime","format","hour-step","minute-step","second-step"])):e.createCommentVNode("",!0)])]))}});E.TimePicker=oe,Object.defineProperty(E,Symbol.toStringTag,{value:"Module"})}));
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@manik02/vue3-timepicker",
3
+ "version": "0.1.0",
4
+ "description": "A flexible Vue 3 timepicker component with multiple format support, range selection, and fully customisable styling",
5
+ "license": "MIT",
6
+ "author": "Manos Savvides",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/manos02/vue3-time-picker.git"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/vue-timepicker.umd.cjs",
13
+ "module": "./dist/vue-timepicker.js",
14
+ "types": "./dist/index.d.ts",
15
+ "style": "./dist/vue-timepicker.css",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "import": "./dist/vue-timepicker.js",
20
+ "require": "./dist/vue-timepicker.umd.cjs"
21
+ },
22
+ "./style.css": "./dist/vue-timepicker.css"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "keywords": [
28
+ "vue",
29
+ "vue3",
30
+ "timepicker",
31
+ "time-picker",
32
+ "time",
33
+ "component",
34
+ "range",
35
+ "12h",
36
+ "24h"
37
+ ],
38
+ "scripts": {
39
+ "dev": "vite",
40
+ "build": "vue-tsc -b && vite build",
41
+ "preview": "vite preview",
42
+ "test": "vitest run",
43
+ "test:watch": "vitest"
44
+ },
45
+ "peerDependencies": {
46
+ "vue": "^3.3.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^24.3.0",
50
+ "@vitejs/plugin-vue": "^6.0.1",
51
+ "@vue/test-utils": "^2.4.6",
52
+ "@vue/tsconfig": "^0.7.0",
53
+ "happy-dom": "^20.6.3",
54
+ "typescript": "~5.8.3",
55
+ "vite": "^7.1.3",
56
+ "vite-plugin-dts": "^4.5.4",
57
+ "vitest": "^4.0.18",
58
+ "vue-tsc": "^3.0.6"
59
+ }
60
+ }