@manik02/vue3-timepicker 0.1.2 → 0.2.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/README.md CHANGED
@@ -1,9 +1,11 @@
1
1
  # Vue3 Timepicker
2
2
 
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/)
4
+
3
5
  A flexible, customisable timepicker component for Vue 3 with TypeScript support.
4
6
 
5
- | Demo | Default | Dark |
6
- |------|---------|------|
7
+ | Demo | Default | Dark |
8
+ | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
7
9
  | ![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
10
 
9
11
  ![All themes](https://raw.githubusercontent.com/manos02/vue3-time-picker/main/assets/screenshot-all.png)
@@ -21,6 +23,8 @@ A flexible, customisable timepicker component for Vue 3 with TypeScript support.
21
23
  npm install @manik02/vue3-timepicker
22
24
  ```
23
25
 
26
+ Try it live: [Interactive Playground](https://manos02.github.io/vue3-time-picker/)
27
+
24
28
  ## Quick start
25
29
 
26
30
  ```vue
@@ -37,17 +41,118 @@ const time = ref("14:30:00");
37
41
  </template>
38
42
  ```
39
43
 
44
+ ## Interactive Playground (Storybook)
45
+
46
+ Run Storybook locally and experiment with all props via controls:
47
+
48
+ ```bash
49
+ npm run storybook
50
+ ```
51
+
52
+ Build static Storybook:
53
+
54
+ ```bash
55
+ npm run build-storybook
56
+ ```
57
+
58
+ The repository includes [storybook deploy workflow](.github/workflows/storybook.yml) to publish Storybook to GitHub Pages on push to `main`.
59
+
40
60
  ## Props
41
61
 
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. |
62
+ | Prop | Type | Default | Description |
63
+ | ---------------- | -------------------------------------- | ----------- | --------------------------------------------------------------------------------------- |
64
+ | `modelValue` | `string \| [string, string] \| null` | `undefined` | Time value in `HH:mm:ss` format. Use a two-element array for range mode. |
65
+ | `format` | `TimeFormat` | `"HH:mm"` | Display format (see format tokens below). |
66
+ | `range` | `boolean` | `false` | Enable range selection with two time inputs. |
67
+ | `disabled` | `boolean` | `false` | Disables the timepicker input(s) and prevents opening/selecting. |
68
+ | `hourStep` | `number` | `1` | Step interval for the hour column. |
69
+ | `minuteStep` | `number` | `1` | Step interval for the minute column. |
70
+ | `secondStep` | `number` | `1` | Step interval for the second column. |
71
+ | `minTime` | `string` | `undefined` | Lower bound in `HH:mm` or `HH:mm:ss`; input and dropdown are constrained. |
72
+ | `maxTime` | `string` | `undefined` | Upper bound in `HH:mm` or `HH:mm:ss`; input and dropdown are constrained. |
73
+ | `disabledTimes` | `(string \| [string, string])[]` | `undefined` | Disabled points/ranges in `HH:mm:ss`; e.g. `"12:00:00"` or `[["13:00:00","14:00:00"]]`. |
74
+ | `isTimeDisabled` | `(time: InternalFormat) => boolean` | `undefined` | Callback for custom disable rules. Return `true` to block a time. |
75
+ | `size` | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Size preset that maps to CSS variables. |
76
+
77
+ ## Validation API
78
+
79
+ The component exposes a validation state and emits validation events for predictable form handling:
80
+
81
+ - `update:validationState` emits one of: `"valid"`, `"invalid"`, `"out-of-range"`
82
+ - `validate` emits payload:
83
+
84
+ ```ts
85
+ {
86
+ target: "first" | "second";
87
+ state: "valid" | "invalid" | "out-of-range";
88
+ reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
89
+ value: string | null; // always HH:mm:ss when present
90
+ }
91
+ ```
92
+
93
+ `out-of-range` means the value was outside `minTime`/`maxTime` and got clamped.
94
+ `invalid` means bad/incomplete input or a blocked value from `disabledTimes`/`isTimeDisabled`.
95
+
96
+ ### Example
97
+
98
+ ```vue
99
+ <script setup lang="ts">
100
+ import { ref } from "vue";
101
+
102
+ const time = ref("12:00:00");
103
+ const validationState = ref<"valid" | "invalid" | "out-of-range">("valid");
104
+
105
+ function onValidate(payload: {
106
+ state: "valid" | "invalid" | "out-of-range";
107
+ reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
108
+ }) {
109
+ console.log("validate", payload.state, payload.reason);
110
+ }
111
+ </script>
112
+
113
+ <template>
114
+ <TimePicker
115
+ v-model="time"
116
+ v-model:validationState="validationState"
117
+ minTime="09:00:00"
118
+ maxTime="18:00:00"
119
+ @validate="onValidate"
120
+ />
121
+
122
+ <small>State: {{ validationState }}</small>
123
+ </template>
124
+ ```
125
+
126
+ ### Range Example
127
+
128
+ ```vue
129
+ <script setup lang="ts">
130
+ import { ref } from "vue";
131
+
132
+ const range = ref<[string, string]>(["09:00:00", "17:00:00"]);
133
+ const validationState = ref<"valid" | "invalid" | "out-of-range">("valid");
134
+
135
+ function onValidate(payload: {
136
+ target: "first" | "second";
137
+ state: "valid" | "invalid" | "out-of-range";
138
+ reason?: "BAD_TIME" | "OUT_OF_RANGE" | "DISABLED";
139
+ }) {
140
+ console.log(payload.target, payload.state, payload.reason);
141
+ }
142
+ </script>
143
+
144
+ <template>
145
+ <TimePicker
146
+ v-model="range"
147
+ :range="true"
148
+ v-model:validationState="validationState"
149
+ :disabled-times="[["12:00:00", "13:00:00"]]"
150
+ @validate="onValidate"
151
+ />
152
+
153
+ <small>State: {{ validationState }}</small>
154
+ </template>
155
+ ```
51
156
 
52
157
  ## Format tokens
53
158
 
@@ -109,9 +214,11 @@ The dropdown columns will show values at the specified intervals (e.g. 00, 15, 3
109
214
  ```vue
110
215
  <template>
111
216
  <div class="sizes">
217
+ <TimePicker v-model="time" format="HH:mm" size="xs" />
112
218
  <TimePicker v-model="time" format="HH:mm" size="sm" />
113
219
  <TimePicker v-model="time" format="HH:mm" size="md" />
114
220
  <TimePicker v-model="time" format="HH:mm" size="lg" />
221
+ <TimePicker v-model="time" format="HH:mm" size="xl" />
115
222
  </div>
116
223
  </template>
117
224
  ```
@@ -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';