@manik02/vue3-timepicker 0.1.3 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Vue3 Timepicker
2
2
 
3
- A flexible, customisable timepicker component for Vue 3 with TypeScript support.
3
+ [![Live Playground](https://img.shields.io/badge/Live-Playground-4f46e5?style=for-the-badge&logo=storybook&logoColor=white)](https://manos02.github.io/vue3-time-picker/) [![npm version](https://img.shields.io/npm/v/@manik02/vue3-timepicker?style=for-the-badge)](https://www.npmjs.com/package/@manik02/vue3-timepicker) [![License: MIT](https://img.shields.io/badge/License-MIT-green?style=for-the-badge)](LICENSE)
4
4
 
5
+ A flexible, customisable timepicker component for Vue 3 with TypeScript support.
5
6
  | Demo | Default | Dark |
6
- |------|---------|------|
7
+ | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
7
8
  | ![Demo](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/demo.gif) | ![Default](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-default.png) | ![Dark](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-dark.png) |
8
9
 
9
10
  ![All themes](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-all.png)
@@ -21,6 +22,8 @@ A flexible, customisable timepicker component for Vue 3 with TypeScript support.
21
22
  npm install @manik02/vue3-timepicker
22
23
  ```
23
24
 
25
+ Try it live: [Interactive Playground](https://manos02.github.io/vue3-time-picker/)
26
+
24
27
  ## Quick start
25
28
 
26
29
  ```vue
@@ -37,17 +40,118 @@ const time = ref("14:30:00");
37
40
  </template>
38
41
  ```
39
42
 
43
+ ## Interactive Playground (Storybook)
44
+
45
+ Run Storybook locally and experiment with all props via controls:
46
+
47
+ ```bash
48
+ npm run storybook
49
+ ```
50
+
51
+ Build static Storybook:
52
+
53
+ ```bash
54
+ npm run build-storybook
55
+ ```
56
+
57
+ The repository includes [storybook deploy workflow](.github/workflows/storybook.yml) to publish Storybook to GitHub Pages on push to `main`.
58
+
40
59
  ## Props
41
60
 
42
- | Prop | Type | Default | Description |
43
- | ------------ | ------------------------------------ | ----------- | ------------------------------------------------------------------------ |
44
- | `modelValue` | `string \| [string, string] \| null` | `undefined` | Time value in `HH:mm:ss` format. Use a two-element array for range mode. |
45
- | `format` | `TimeFormat` | `"HH:mm"` | Display format (see format tokens below). |
46
- | `range` | `boolean` | `false` | Enable range selection with two time inputs. |
47
- | `hourStep` | `number` | `1` | Step interval for the hour column. |
48
- | `minuteStep` | `number` | `1` | Step interval for the minute column. |
49
- | `secondStep` | `number` | `1` | Step interval for the second column. |
50
- | `size` | `"sm" \| "md" \| "lg"` | `"md"` | Size preset that maps to CSS variables. |
61
+ | Prop | Type | Default | Description |
62
+ | ---------------- | -------------------------------------- | ----------- | --------------------------------------------------------------------------------------- |
63
+ | `modelValue` | `string \| [string, string] \| null` | `undefined` | Time value in `HH:mm:ss` format. Use a two-element array for range mode. |
64
+ | `format` | `TimeFormat` | `"HH:mm"` | Display format (see format tokens below). |
65
+ | `range` | `boolean` | `false` | Enable range selection with two time inputs. |
66
+ | `disabled` | `boolean` | `false` | Disables the timepicker input(s) and prevents opening/selecting. |
67
+ | `hourStep` | `number` | `1` | Step interval for the hour column. |
68
+ | `minuteStep` | `number` | `1` | Step interval for the minute column. |
69
+ | `secondStep` | `number` | `1` | Step interval for the second column. |
70
+ | `minTime` | `string` | `undefined` | Lower bound in `HH:mm` or `HH:mm:ss`; input and dropdown are constrained. |
71
+ | `maxTime` | `string` | `undefined` | Upper bound in `HH:mm` or `HH:mm:ss`; input and dropdown are constrained. |
72
+ | `disabledTimes` | `(string \| [string, string])[]` | `undefined` | Disabled points/ranges in `HH:mm:ss`; e.g. `"12:00:00"` or `[["13:00:00","14:00:00"]]`. |
73
+ | `isTimeDisabled` | `(time: InternalFormat) => boolean` | `undefined` | Callback for custom disable rules. Return `true` to block a time. |
74
+ | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size preset that maps to CSS variables. |
75
+
76
+ ## Validation API
77
+
78
+ The component exposes a validation state and emits validation events for predictable form handling:
79
+
80
+ - `update:validationState` emits one of: `"valid"`, `"invalid"`, `"out-of-range"`
81
+ - `validate` emits payload:
82
+
83
+ ```ts
84
+ {
85
+ target: "first" | "second";
86
+ state: "valid" | "invalid" | "out-of-range";
87
+ reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
88
+ value: string | null; // always HH:mm:ss when present
89
+ }
90
+ ```
91
+
92
+ `out-of-range` means the value was outside `minTime`/`maxTime` and got clamped.
93
+ `invalid` means bad/incomplete input or a blocked value from `disabledTimes`/`isTimeDisabled`.
94
+
95
+ ### Example
96
+
97
+ ```vue
98
+ <script setup lang="ts">
99
+ import { ref } from "vue";
100
+
101
+ const time = ref("12:00:00");
102
+ const validationState = ref<"valid" | "invalid" | "out-of-range">("valid");
103
+
104
+ function onValidate(payload: {
105
+ state: "valid" | "invalid" | "out-of-range";
106
+ reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
107
+ }) {
108
+ console.log("validate", payload.state, payload.reason);
109
+ }
110
+ </script>
111
+
112
+ <template>
113
+ <TimePicker
114
+ v-model="time"
115
+ v-model:validationState="validationState"
116
+ minTime="09:00:00"
117
+ maxTime="18:00:00"
118
+ @validate="onValidate"
119
+ />
120
+
121
+ <small>State: {{ validationState }}</small>
122
+ </template>
123
+ ```
124
+
125
+ ### Range Example
126
+
127
+ ```vue
128
+ <script setup lang="ts">
129
+ import { ref } from "vue";
130
+
131
+ const range = ref<[string, string]>(["09:00:00", "17:00:00"]);
132
+ const validationState = ref<"valid" | "invalid" | "out-of-range">("valid");
133
+
134
+ function onValidate(payload: {
135
+ target: "first" | "second";
136
+ state: "valid" | "invalid" | "out-of-range";
137
+ reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
138
+ }) {
139
+ console.log(payload.target, payload.state, payload.reason);
140
+ }
141
+ </script>
142
+
143
+ <template>
144
+ <TimePicker
145
+ v-model="range"
146
+ :range="true"
147
+ v-model:validationState="validationState"
148
+ :disabled-times="[["12:00:00", "13:00:00"]]"
149
+ @validate="onValidate"
150
+ />
151
+
152
+ <small>State: {{ validationState }}</small>
153
+ </template>
154
+ ```
51
155
 
52
156
  ## Format tokens
53
157
 
@@ -109,9 +213,11 @@ The dropdown columns will show values at the specified intervals (e.g. 00, 15, 3
109
213
  ```vue
110
214
  <template>
111
215
  <div class="sizes">
216
+ <TimePicker v-model="time" format="HH:mm" size="xs" />
112
217
  <TimePicker v-model="time" format="HH:mm" size="sm" />
113
218
  <TimePicker v-model="time" format="HH:mm" size="md" />
114
219
  <TimePicker v-model="time" format="HH:mm" size="lg" />
220
+ <TimePicker v-model="time" format="HH:mm" size="xl" />
115
221
  </div>
116
222
  </template>
117
223
  ```
@@ -1,3 +1,4 @@
1
+ import { InternalFormat, DisabledTimeInput, ValidationReason, ValidationState } from './types';
1
2
  declare const _default: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
2
3
  readonly modelValue: {
3
4
  readonly type: import('vue').PropType<string | [string, string] | null>;
@@ -8,6 +9,10 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
8
9
  readonly type: BooleanConstructor;
9
10
  readonly default: false;
10
11
  };
12
+ readonly disabled: {
13
+ readonly type: BooleanConstructor;
14
+ readonly default: false;
15
+ };
11
16
  readonly hourStep: {
12
17
  readonly type: NumberConstructor;
13
18
  readonly default: 1;
@@ -20,22 +25,48 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
20
25
  readonly type: NumberConstructor;
21
26
  readonly default: 1;
22
27
  };
28
+ readonly minTime: {
29
+ readonly type: import('vue').PropType<string | undefined>;
30
+ readonly default: undefined;
31
+ readonly validator: (v?: string) => boolean;
32
+ };
33
+ readonly maxTime: {
34
+ readonly type: import('vue').PropType<string | undefined>;
35
+ readonly default: undefined;
36
+ readonly validator: (v?: string) => boolean;
37
+ };
38
+ readonly disabledTimes: {
39
+ readonly type: import('vue').PropType<ReadonlyArray<DisabledTimeInput> | undefined>;
40
+ readonly default: undefined;
41
+ readonly validator: (v?: ReadonlyArray<DisabledTimeInput>) => boolean;
42
+ };
43
+ readonly isTimeDisabled: {
44
+ readonly type: import('vue').PropType<(time: InternalFormat) => boolean>;
45
+ readonly default: undefined;
46
+ };
23
47
  readonly format: {
24
48
  readonly type: import('vue').PropType<import('./types').TimeFormat>;
25
49
  readonly default: "HH:mm";
26
50
  readonly validator: (fmt: string) => boolean;
27
51
  };
28
52
  readonly size: {
29
- readonly type: import('vue').PropType<"sm" | "md" | "lg">;
53
+ readonly type: import('vue').PropType<"xs" | "sm" | "md" | "lg" | "xl">;
30
54
  readonly default: "md";
31
- readonly validator: (v: string) => v is "sm" | "md" | "lg";
55
+ readonly validator: (v: string) => v is "xs" | "sm" | "md" | "lg" | "xl";
32
56
  };
33
57
  }>, {}, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {} & {
34
58
  "update:modelValue": (v: string | [string, string] | null) => any;
59
+ "update:validationState": (v: ValidationState) => any;
60
+ validate: (payload: {
61
+ target: "first" | "second";
62
+ state: ValidationState;
63
+ reason?: ValidationReason;
64
+ value: string | null;
65
+ }) => any;
35
66
  open: () => any;
36
67
  close: () => any;
37
68
  error: (payload: {
38
- code: "BAD_TIME" | "OUT_OF_RANGE";
69
+ code: ValidationReason;
39
70
  message: string;
40
71
  }) => any;
41
72
  }, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
@@ -48,6 +79,10 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
48
79
  readonly type: BooleanConstructor;
49
80
  readonly default: false;
50
81
  };
82
+ readonly disabled: {
83
+ readonly type: BooleanConstructor;
84
+ readonly default: false;
85
+ };
51
86
  readonly hourStep: {
52
87
  readonly type: NumberConstructor;
53
88
  readonly default: 1;
@@ -60,32 +95,63 @@ declare const _default: import('vue').DefineComponent<import('vue').ExtractPropT
60
95
  readonly type: NumberConstructor;
61
96
  readonly default: 1;
62
97
  };
98
+ readonly minTime: {
99
+ readonly type: import('vue').PropType<string | undefined>;
100
+ readonly default: undefined;
101
+ readonly validator: (v?: string) => boolean;
102
+ };
103
+ readonly maxTime: {
104
+ readonly type: import('vue').PropType<string | undefined>;
105
+ readonly default: undefined;
106
+ readonly validator: (v?: string) => boolean;
107
+ };
108
+ readonly disabledTimes: {
109
+ readonly type: import('vue').PropType<ReadonlyArray<DisabledTimeInput> | undefined>;
110
+ readonly default: undefined;
111
+ readonly validator: (v?: ReadonlyArray<DisabledTimeInput>) => boolean;
112
+ };
113
+ readonly isTimeDisabled: {
114
+ readonly type: import('vue').PropType<(time: InternalFormat) => boolean>;
115
+ readonly default: undefined;
116
+ };
63
117
  readonly format: {
64
118
  readonly type: import('vue').PropType<import('./types').TimeFormat>;
65
119
  readonly default: "HH:mm";
66
120
  readonly validator: (fmt: string) => boolean;
67
121
  };
68
122
  readonly size: {
69
- readonly type: import('vue').PropType<"sm" | "md" | "lg">;
123
+ readonly type: import('vue').PropType<"xs" | "sm" | "md" | "lg" | "xl">;
70
124
  readonly default: "md";
71
- readonly validator: (v: string) => v is "sm" | "md" | "lg";
125
+ readonly validator: (v: string) => v is "xs" | "sm" | "md" | "lg" | "xl";
72
126
  };
73
127
  }>> & Readonly<{
74
128
  "onUpdate:modelValue"?: ((v: string | [string, string] | null) => any) | undefined;
129
+ "onUpdate:validationState"?: ((v: ValidationState) => any) | undefined;
130
+ onValidate?: ((payload: {
131
+ target: "first" | "second";
132
+ state: ValidationState;
133
+ reason?: ValidationReason;
134
+ value: string | null;
135
+ }) => any) | undefined;
75
136
  onOpen?: (() => any) | undefined;
76
137
  onClose?: (() => any) | undefined;
77
138
  onError?: ((payload: {
78
- code: "BAD_TIME" | "OUT_OF_RANGE";
139
+ code: ValidationReason;
79
140
  message: string;
80
141
  }) => any) | undefined;
81
142
  }>, {
82
143
  readonly modelValue: string | [string, string] | null;
83
144
  readonly range: boolean;
145
+ readonly disabled: boolean;
84
146
  readonly hourStep: number;
85
147
  readonly minuteStep: number;
86
148
  readonly secondStep: number;
149
+ readonly minTime: string | undefined;
150
+ readonly maxTime: string | undefined;
151
+ readonly disabledTimes: readonly DisabledTimeInput[] | undefined;
152
+ readonly isTimeDisabled: (time: InternalFormat) => boolean;
87
153
  readonly format: import('./types').TimeFormat;
88
- readonly size: "sm" | "md" | "lg";
154
+ readonly size: "xs" | "sm" | "md" | "lg" | "xl";
89
155
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {
90
156
  secondInputRef: HTMLInputElement;
91
157
  }, any>;
@@ -3,6 +3,10 @@ type __VLS_Props = {
3
3
  open: boolean;
4
4
  initTime: InternalFormat;
5
5
  format: string;
6
+ minTime?: InternalFormat | null;
7
+ maxTime?: InternalFormat | null;
8
+ disabledRanges?: Array<[InternalFormat, InternalFormat]>;
9
+ isTimeDisabled?: (time: InternalFormat) => boolean;
6
10
  hourStep?: number;
7
11
  minuteStep?: number;
8
12
  secondStep?: number;
@@ -13,6 +13,9 @@ export type InternalFormat = {
13
13
  m: number;
14
14
  s: number;
15
15
  };
16
+ export type DisabledTimeInput = string | [string, string];
17
+ export type ValidationState = "valid" | "invalid" | "out-of-range";
18
+ export type ValidationReason = "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
16
19
  export declare const FORMAT_SHAPE: RegExp;
17
20
  export declare const TIME_SHAPE: RegExp;
18
21
  export declare const timePickerProps: {
@@ -25,6 +28,10 @@ export declare const timePickerProps: {
25
28
  readonly type: BooleanConstructor;
26
29
  readonly default: false;
27
30
  };
31
+ readonly disabled: {
32
+ readonly type: BooleanConstructor;
33
+ readonly default: false;
34
+ };
28
35
  readonly hourStep: {
29
36
  readonly type: NumberConstructor;
30
37
  readonly default: 1;
@@ -37,15 +44,34 @@ export declare const timePickerProps: {
37
44
  readonly type: NumberConstructor;
38
45
  readonly default: 1;
39
46
  };
47
+ readonly minTime: {
48
+ readonly type: PropType<string | undefined>;
49
+ readonly default: undefined;
50
+ readonly validator: (v?: string) => boolean;
51
+ };
52
+ readonly maxTime: {
53
+ readonly type: PropType<string | undefined>;
54
+ readonly default: undefined;
55
+ readonly validator: (v?: string) => boolean;
56
+ };
57
+ readonly disabledTimes: {
58
+ readonly type: PropType<ReadonlyArray<DisabledTimeInput> | undefined>;
59
+ readonly default: undefined;
60
+ readonly validator: (v?: ReadonlyArray<DisabledTimeInput>) => boolean;
61
+ };
62
+ readonly isTimeDisabled: {
63
+ readonly type: PropType<(time: InternalFormat) => boolean>;
64
+ readonly default: undefined;
65
+ };
40
66
  readonly format: {
41
67
  readonly type: PropType<TimeFormat>;
42
68
  readonly default: "HH:mm";
43
69
  readonly validator: (fmt: string) => boolean;
44
70
  };
45
71
  readonly size: {
46
- readonly type: PropType<"sm" | "md" | "lg">;
72
+ readonly type: PropType<"xs" | "sm" | "md" | "lg" | "xl">;
47
73
  readonly default: "md";
48
- readonly validator: (v: string) => v is "sm" | "md" | "lg";
74
+ readonly validator: (v: string) => v is "xs" | "sm" | "md" | "lg" | "xl";
49
75
  };
50
76
  };
51
77
  export type TimePickerProps = ExtractPropTypes<typeof timePickerProps>;
@@ -80,10 +106,17 @@ export declare const TimeSelectionProps: {
80
106
  export type TimeSelectionProps = ExtractPropTypes<typeof TimeSelectionProps>;
81
107
  export interface TimePickerEmits {
82
108
  (e: "update:modelValue", v: string | [string, string] | null): void;
109
+ (e: "update:validationState", v: ValidationState): void;
110
+ (e: "validate", payload: {
111
+ target: "first" | "second";
112
+ state: ValidationState;
113
+ reason?: ValidationReason;
114
+ value: string | null;
115
+ }): void;
83
116
  (e: "open"): void;
84
117
  (e: "close"): void;
85
118
  (e: "error", payload: {
86
- code: "BAD_TIME" | "OUT_OF_RANGE";
119
+ code: ValidationReason;
87
120
  message: string;
88
121
  }): void;
89
122
  }
package/dist/helpers.d.ts CHANGED
@@ -13,3 +13,7 @@ export declare function to12(h24: number): number;
13
13
  export declare function to24(h12: number, isPM: boolean): number;
14
14
  export declare function hasSeconds(fmt: string): boolean;
15
15
  export declare function formatTime(fmt: string, time: InternalFormat): string;
16
+ export declare function compareTimes(a: InternalFormat, b: InternalFormat): number;
17
+ export declare function clampTimeToBounds(time: InternalFormat, minTime?: InternalFormat | null, maxTime?: InternalFormat | null): InternalFormat;
18
+ export declare function isTimeWithinBounds(time: InternalFormat, minTime?: InternalFormat | null, maxTime?: InternalFormat | null): boolean;
19
+ export declare function isTimeInRanges(time: InternalFormat, ranges: Array<[InternalFormat, InternalFormat]>): boolean;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { default as TimePicker } from './TimePicker/TimePicker.vue';
2
2
  export { TimePicker };
3
- export type { TimeFormat, InternalFormat, TimePickerProps, } from './TimePicker/types';
3
+ export type { DisabledTimeInput, TimeFormat, InternalFormat, TimePickerProps, ValidationReason, ValidationState, } from './TimePicker/types';