@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 +21 -0
- package/README.md +236 -0
- package/dist/TimePicker/TimeColumn.vue.d.ts +19 -0
- package/dist/TimePicker/TimePicker.vue.d.ts +81 -0
- package/dist/TimePicker/TimeSelection.vue.d.ts +23 -0
- package/dist/TimePicker/types.d.ts +91 -0
- package/dist/TimePicker/useTimeMask.d.ts +15 -0
- package/dist/helpers.d.ts +15 -0
- package/dist/index.d.ts +3 -0
- package/dist/vue-timepicker.css +1 -0
- package/dist/vue-timepicker.js +593 -0
- package/dist/vue-timepicker.umd.cjs +1 -0
- package/package.json +60 -0
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;
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|