@ciwergrp/nuxid 1.9.0 → 1.11.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/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ciwergrp/nuxid",
3
3
  "configKey": "nuxid",
4
- "version": "1.9.0",
4
+ "version": "1.11.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1,33 +1,7 @@
1
1
  import type { FormatDistanceOptions, FormatDistanceToNowOptions, FormatISOOptions, FormatOptions, FormatRFC3339Options } from 'date-fns';
2
2
  import type { DateInput } from './input.js';
3
- declare const formatAliases: {
4
- readonly 'YYYY-MM-DD': "yyyy-MM-dd";
5
- readonly 'YYYY-MM-DD HH:mm': "yyyy-MM-dd HH:mm";
6
- readonly 'YYYY-MM-DD HH:mm:ss': "yyyy-MM-dd HH:mm:ss";
7
- readonly 'YYYY-MM-DD HH:mm:ss.SSS': "yyyy-MM-dd HH:mm:ss.SSS";
8
- readonly 'YYYY-MM-DD HH:mm:ssZ': "yyyy-MM-dd HH:mm:ssXXX";
9
- readonly 'YYYY-MM-DD HH:mm:ss.SSSZ': "yyyy-MM-dd HH:mm:ss.SSSXXX";
10
- readonly 'YYYY/MM/DD': "yyyy/MM/dd";
11
- readonly 'YYYY/MM/DD HH:mm': "yyyy/MM/dd HH:mm";
12
- readonly 'YYYY/MM/DD HH:mm:ss': "yyyy/MM/dd HH:mm:ss";
13
- readonly 'DD/MM/YYYY': "dd/MM/yyyy";
14
- readonly 'MM/DD/YYYY': "MM/dd/yyyy";
15
- readonly 'DD-MM-YYYY': "dd-MM-yyyy";
16
- readonly 'MM-DD-YYYY': "MM-dd-yyyy";
17
- readonly 'YYYY-MM': "yyyy-MM";
18
- readonly 'YYYY/MM': "yyyy/MM";
19
- readonly YYYY: "yyyy";
20
- readonly 'DD MMM YYYY': "dd MMM yyyy";
21
- readonly 'MMM DD, YYYY': "MMM dd, yyyy";
22
- readonly 'HH:mm': "HH:mm";
23
- readonly 'HH:mm:ss': "HH:mm:ss";
24
- readonly 'HH:mm:ss.SSS': "HH:mm:ss.SSS";
25
- readonly 'hh:mm A': "hh:mm a";
26
- readonly 'hh:mm:ss A': "hh:mm:ss a";
27
- };
28
- export type DateFormatPreset = keyof typeof formatAliases;
29
- export type DateFormatInput = DateFormatPreset | (string & {});
30
- export declare function resolveFormatString(formatString: DateFormatInput): "yyyy-MM-dd" | "yyyy-MM-dd HH:mm" | "yyyy-MM-dd HH:mm:ss" | "yyyy-MM-dd HH:mm:ss.SSS" | "yyyy-MM-dd HH:mm:ssXXX" | "yyyy-MM-dd HH:mm:ss.SSSXXX" | "yyyy/MM/dd" | "yyyy/MM/dd HH:mm" | "yyyy/MM/dd HH:mm:ss" | "dd/MM/yyyy" | "MM/dd/yyyy" | "dd-MM-yyyy" | "MM-dd-yyyy" | "yyyy-MM" | "yyyy/MM" | "yyyy" | "dd MMM yyyy" | "MMM dd, yyyy" | "HH:mm" | "HH:mm:ss" | "HH:mm:ss.SSS" | "hh:mm a" | "hh:mm:ss a";
3
+ export type DateFormatPreset = string;
4
+ export type DateFormatInput = string;
31
5
  export declare function format(date: DateInput, formatString: DateFormatInput, options?: FormatOptions): string;
32
6
  export declare function formatISO(date: DateInput, options?: FormatISOOptions): string;
33
7
  export declare function formatRFC3339(date: DateInput, options?: FormatRFC3339Options): string;
@@ -40,4 +14,3 @@ export declare function toDateTimeString(date: DateInput): string;
40
14
  export declare function toIsoString(date: DateInput): string;
41
15
  export declare function toRfc3339String(date: DateInput): string;
42
16
  export declare function toRfc7231String(date: DateInput): string;
43
- export {};
@@ -7,36 +7,8 @@ import {
7
7
  formatRFC7231 as formatRFC7231Date
8
8
  } from "date-fns";
9
9
  import { toDate } from "./input.js";
10
- const formatAliases = {
11
- "YYYY-MM-DD": "yyyy-MM-dd",
12
- "YYYY-MM-DD HH:mm": "yyyy-MM-dd HH:mm",
13
- "YYYY-MM-DD HH:mm:ss": "yyyy-MM-dd HH:mm:ss",
14
- "YYYY-MM-DD HH:mm:ss.SSS": "yyyy-MM-dd HH:mm:ss.SSS",
15
- "YYYY-MM-DD HH:mm:ssZ": "yyyy-MM-dd HH:mm:ssXXX",
16
- "YYYY-MM-DD HH:mm:ss.SSSZ": "yyyy-MM-dd HH:mm:ss.SSSXXX",
17
- "YYYY/MM/DD": "yyyy/MM/dd",
18
- "YYYY/MM/DD HH:mm": "yyyy/MM/dd HH:mm",
19
- "YYYY/MM/DD HH:mm:ss": "yyyy/MM/dd HH:mm:ss",
20
- "DD/MM/YYYY": "dd/MM/yyyy",
21
- "MM/DD/YYYY": "MM/dd/yyyy",
22
- "DD-MM-YYYY": "dd-MM-yyyy",
23
- "MM-DD-YYYY": "MM-dd-yyyy",
24
- "YYYY-MM": "yyyy-MM",
25
- "YYYY/MM": "yyyy/MM",
26
- "YYYY": "yyyy",
27
- "DD MMM YYYY": "dd MMM yyyy",
28
- "MMM DD, YYYY": "MMM dd, yyyy",
29
- "HH:mm": "HH:mm",
30
- "HH:mm:ss": "HH:mm:ss",
31
- "HH:mm:ss.SSS": "HH:mm:ss.SSS",
32
- "hh:mm A": "hh:mm a",
33
- "hh:mm:ss A": "hh:mm:ss a"
34
- };
35
- export function resolveFormatString(formatString) {
36
- return formatAliases[formatString] ?? formatString;
37
- }
38
10
  export function format(date, formatString, options) {
39
- return formatDate(toDate(date), resolveFormatString(formatString), options);
11
+ return formatDate(toDate(date), formatString, options);
40
12
  }
41
13
  export function formatISO(date, options) {
42
14
  return formatISODate(toDate(date), options);
@@ -1,8 +1,7 @@
1
1
  import { parse as parseDate, parseISO as parseISODate } from "date-fns";
2
- import { resolveFormatString } from "./format.js";
3
2
  import { toDate } from "./input.js";
4
3
  export function parse(value, formatString, baseDate = /* @__PURE__ */ new Date(), options) {
5
- return parseDate(value, resolveFormatString(formatString), toDate(baseDate), options);
4
+ return parseDate(value, formatString, toDate(baseDate), options);
6
5
  }
7
6
  export function parseISO(value) {
8
7
  return parseISODate(value);
@@ -1,5 +1,5 @@
1
1
  import type { FormRules } from 'element-plus';
2
- export type ValidationRule = 'required' | 'time' | 'string' | 'nullable' | 'email' | 'accepted' | `accepted_if:${string}` | 'boolean' | 'filled' | 'present' | `required_if:${string}` | `required_unless:${string}` | `required_with:${string}` | `required_with_all:${string}` | `required_without:${string}` | `required_without_all:${string}` | 'prohibited' | `prohibited_if:${string}` | `prohibited_unless:${string}` | `prohibits:${string}` | `min:${number}` | `max:${number}` | `between:${number},${number}` | `size:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'confirmed' | `digits:${number}` | `digits_between:${number},${number}` | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | `date_format:${string}` | 'date' | `before:${string}` | `after:${string}` | `before_or_equal:${string}` | `after_or_equal:${string}` | 'array' | 'distinct' | `in_array:${string}` | `required_array_keys:${string}` | 'list' | 'url' | 'ip' | 'hex_color' | `in:${string}` | `multiple_of:${number}` | `decimal:${number}` | `decimal:${number},${number}` | 'lowercase' | 'uppercase' | 'uuid' | 'ulid' | `timezone:${string}` | 'timezone';
2
+ export type ValidationRule = 'required' | 'time' | 'datetime' | 'string' | 'nullable' | 'email' | 'accepted' | `accepted_if:${string}` | 'boolean' | 'filled' | 'present' | `required_if:${string}` | `required_unless:${string}` | `required_with:${string}` | `required_with_all:${string}` | `required_without:${string}` | `required_without_all:${string}` | 'prohibited' | `prohibited_if:${string}` | `prohibited_unless:${string}` | `prohibits:${string}` | `min:${number}` | `max:${number}` | `between:${number},${number}` | `size:${number}` | `same:${string}` | 'alpha' | 'alpha_num' | 'alpha_dash' | 'alphanumeric_space' | 'alpha_space' | 'numeric' | 'integer' | 'confirmed' | `digits:${number}` | `digits_between:${number},${number}` | 'bail' | `different:${string}` | `gt:${string}` | `gte:${string}` | `lt:${string}` | `lte:${string}` | `starts_with:${string}` | `ends_with:${string}` | `regex:${string}` | `date_format:${string}` | 'date' | `before:${string}` | `after:${string}` | `before_or_equal:${string}` | `after_or_equal:${string}` | 'array' | 'distinct' | `in_array:${string}` | `required_array_keys:${string}` | 'list' | 'url' | 'ip' | 'hex_color' | `in:${string}` | `multiple_of:${number}` | `decimal:${number}` | `decimal:${number},${number}` | 'lowercase' | 'uppercase' | 'uuid' | 'ulid' | `timezone:${string}` | 'timezone';
3
3
  export interface ValidationOptions {
4
4
  messages?: Record<string, string>;
5
5
  attributes?: Record<string, string>;
@@ -84,17 +84,43 @@ const isAcceptedValue = (value) => {
84
84
  const isBooleanValue = (value) => {
85
85
  return value === true || value === false || value === 0 || value === 1 || value === "0" || value === "1";
86
86
  };
87
- const parseTimeToMinutes = (value) => {
87
+ const parseTimeToComparable = (value) => {
88
88
  if (typeof value !== "string") {
89
89
  return null;
90
90
  }
91
- const match = value.match(/^([01]\d|2[0-3]):([0-5]\d)$/);
91
+ const match = value.match(
92
+ /^([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d)(?:\.(\d{1,3}))?)?(?:(z)|([+-])([01]\d|2[0-3]):?([0-5]\d))?$/i
93
+ );
92
94
  if (!match) {
93
95
  return null;
94
96
  }
95
97
  const hours = Number(match[1]);
96
98
  const minutes = Number(match[2]);
97
- return hours * 60 + minutes;
99
+ const seconds = match[3] ? Number(match[3]) : 0;
100
+ const milliseconds = match[4] ? Number(match[4].padEnd(3, "0")) : 0;
101
+ const hasZuluOffset = Boolean(match[5]);
102
+ const offsetSign = match[6];
103
+ const offsetHours = match[7] ? Number(match[7]) : 0;
104
+ const offsetMinutes = match[8] ? Number(match[8]) : 0;
105
+ const localComparableValue = hours * 36e5 + minutes * 6e4 + seconds * 1e3 + milliseconds;
106
+ if (hasZuluOffset) {
107
+ return {
108
+ comparableValue: localComparableValue,
109
+ hasOffset: true
110
+ };
111
+ }
112
+ if (offsetSign) {
113
+ const offsetComparable = (offsetHours * 60 + offsetMinutes) * 6e4;
114
+ const direction = offsetSign === "+" ? 1 : -1;
115
+ return {
116
+ comparableValue: localComparableValue - direction * offsetComparable,
117
+ hasOffset: true
118
+ };
119
+ }
120
+ return {
121
+ comparableValue: localComparableValue,
122
+ hasOffset: false
123
+ };
98
124
  };
99
125
  const parseDateToTimestamp = (value) => {
100
126
  if (typeof value !== "string") {
@@ -109,14 +135,53 @@ const parseDateToTimestamp = (value) => {
109
135
  }
110
136
  return date.getTime();
111
137
  };
138
+ const parseDateTimeToTimestamp = (value) => {
139
+ if (typeof value !== "string") {
140
+ return null;
141
+ }
142
+ const match = value.match(
143
+ /^(\d{4})-(\d{2})-(\d{2})[T ]([01]\d|2[0-3]):([0-5]\d)(?::([0-5]\d)(?:\.(\d{1,3}))?)?(?:([zZ])|([+-])([01]\d|2[0-3]):?([0-5]\d))?$/
144
+ );
145
+ if (!match) {
146
+ return null;
147
+ }
148
+ const year = Number(match[1]);
149
+ const month = Number(match[2]);
150
+ const day = Number(match[3]);
151
+ const hours = Number(match[4]);
152
+ const minutes = Number(match[5]);
153
+ const seconds = match[6] ? Number(match[6]) : 0;
154
+ const milliseconds = match[7] ? Number(match[7].padEnd(3, "0")) : 0;
155
+ const utcCandidate = Date.UTC(year, month - 1, day, hours, minutes, seconds, milliseconds);
156
+ const utcDate = new Date(utcCandidate);
157
+ if (utcDate.getUTCFullYear() !== year || utcDate.getUTCMonth() !== month - 1 || utcDate.getUTCDate() !== day || utcDate.getUTCHours() !== hours || utcDate.getUTCMinutes() !== minutes || utcDate.getUTCSeconds() !== seconds || utcDate.getUTCMilliseconds() !== milliseconds) {
158
+ return null;
159
+ }
160
+ const hasZuluOffset = Boolean(match[8]);
161
+ const offsetSign = match[9];
162
+ const offsetHour = match[10] ? Number(match[10]) : 0;
163
+ const offsetMinute = match[11] ? Number(match[11]) : 0;
164
+ if (hasZuluOffset) {
165
+ return utcCandidate;
166
+ }
167
+ if (offsetSign) {
168
+ const totalOffsetMinutes = offsetHour * 60 + offsetMinute;
169
+ const direction = offsetSign === "+" ? 1 : -1;
170
+ return utcCandidate - direction * totalOffsetMinutes * 6e4;
171
+ }
172
+ return utcCandidate;
173
+ };
112
174
  const compareFieldValues = (left, right, compare) => {
113
- const leftTime = parseTimeToMinutes(left);
114
- const rightTime = parseTimeToMinutes(right);
175
+ const leftTime = parseTimeToComparable(left);
176
+ const rightTime = parseTimeToComparable(right);
115
177
  if (leftTime !== null || rightTime !== null) {
116
178
  if (leftTime === null || rightTime === null) {
117
179
  return false;
118
180
  }
119
- return compare(leftTime, rightTime);
181
+ if (leftTime.hasOffset !== rightTime.hasOffset) {
182
+ return false;
183
+ }
184
+ return compare(leftTime.comparableValue, rightTime.comparableValue);
120
185
  }
121
186
  const leftDate = parseDateToTimestamp(left);
122
187
  const rightDate = parseDateToTimestamp(right);
@@ -126,6 +191,14 @@ const compareFieldValues = (left, right, compare) => {
126
191
  }
127
192
  return compare(leftDate, rightDate);
128
193
  }
194
+ const leftDateTime = parseDateTimeToTimestamp(left);
195
+ const rightDateTime = parseDateTimeToTimestamp(right);
196
+ if (leftDateTime !== null || rightDateTime !== null) {
197
+ if (leftDateTime === null || rightDateTime === null) {
198
+ return false;
199
+ }
200
+ return compare(leftDateTime, rightDateTime);
201
+ }
129
202
  const leftNumber = Number(left);
130
203
  const rightNumber = Number(right);
131
204
  if (Number.isNaN(leftNumber) || Number.isNaN(rightNumber)) {
@@ -173,7 +246,7 @@ const createTimeValidator = (message) => {
173
246
  if (isEmptyValue(value)) {
174
247
  return callback();
175
248
  }
176
- if (parseTimeToMinutes(value) === null) {
249
+ if (parseTimeToComparable(value) === null) {
177
250
  return callback(new Error(message));
178
251
  }
179
252
  callback();
@@ -232,6 +305,17 @@ const createDateValidator = (message) => {
232
305
  callback();
233
306
  };
234
307
  };
308
+ const createDateTimeValidator = (message) => {
309
+ return (_rule, value, callback) => {
310
+ if (isEmptyValue(value)) {
311
+ return callback();
312
+ }
313
+ if (parseDateTimeToTimestamp(value) === null) {
314
+ return callback(new Error(message));
315
+ }
316
+ callback();
317
+ };
318
+ };
235
319
  const createDateComparisonValidator = (compareDate, message, compare) => {
236
320
  return (_rule, value, callback) => {
237
321
  if (!value) {
@@ -665,7 +749,7 @@ export const createValidationRules = (definitions, formState, options = {}) => {
665
749
  break;
666
750
  case "time":
667
751
  fieldRules.push({
668
- validator: createTimeValidator(getMessage(fieldName, "time", `${attributeName} must be a valid time in HH:mm format`)),
752
+ validator: createTimeValidator(getMessage(fieldName, "time", `${attributeName} must be a valid time`)),
669
753
  trigger: pickTrigger(fieldName, "blur")
670
754
  });
671
755
  break;
@@ -784,6 +868,12 @@ export const createValidationRules = (definitions, formState, options = {}) => {
784
868
  trigger: pickTrigger(fieldName, "blur")
785
869
  });
786
870
  break;
871
+ case "datetime":
872
+ fieldRules.push({
873
+ validator: createDateTimeValidator(getMessage(fieldName, "datetime", `${attributeName} must be a valid datetime`)),
874
+ trigger: pickTrigger(fieldName, "blur")
875
+ });
876
+ break;
787
877
  case "before":
788
878
  if (!firstParam) {
789
879
  return;
@@ -62,6 +62,17 @@ const definitions = {
62
62
 
63
63
  Optional configuration for messages, attributes, and hooks.
64
64
 
65
+ ## Date and Time Comparison Behavior
66
+
67
+ Rules `gt`, `gte`, `lt`, and `lte` use type-aware parsing:
68
+
69
+ - `time` (supports `HH:mm`, optional seconds/milliseconds, optional `Z`/offset)
70
+ - `date` (`YYYY-MM-DD`)
71
+ - `datetime` (date + time, optional `Z`/offset)
72
+ - numeric fallback
73
+
74
+ For `time`, offset-aware and non-offset values cannot be mixed in one comparison.
75
+
65
76
  ### options.messages
66
77
  - Summary: Custom error messages for specific rule keys.
67
78
  - Technique: Lookup key format is `{field}.{rule}`.
@@ -64,4 +64,18 @@ const rules = createValidationRules(
64
64
  )
65
65
  ```
66
66
 
67
+ ## Time and Datetime Example
68
+
69
+ ```ts
70
+ const rules = createValidationRules(
71
+ {
72
+ start_time: ['required', 'time'],
73
+ end_time: ['required', 'time', 'gte:start_time'],
74
+ starts_at: ['required', 'datetime'],
75
+ ends_at: ['required', 'datetime', 'gte:starts_at'],
76
+ },
77
+ form,
78
+ )
79
+ ```
80
+
67
81
  See the full list of supported rules on the [Available Rules](/form-validation/rules) page.
@@ -237,6 +237,42 @@ Notes:
237
237
  - Use: `'digits_between:6,8'`
238
238
  - Example: `['digits_between:6,8']`
239
239
 
240
+ ### time
241
+ - Summary: Must be a valid time string.
242
+ - Technique: Supports strict `HH:mm`, optional seconds/milliseconds, and optional timezone offset.
243
+ - Use: `'time'`
244
+ - Example: `['time']`
245
+ - Supported formats:
246
+ - `HH:mm`
247
+ - `HH:mm:ss`
248
+ - `HH:mm:ss.SSS`
249
+ - `HH:mmZ`
250
+ - `HH:mm:ssZ`
251
+ - `HH:mm:ss.SSSZ`
252
+ - `HH:mm±HHmm`
253
+ - `HH:mm:ss±HHmm`
254
+ - `HH:mm:ss.SSS±HHmm`
255
+ - Offset notes:
256
+ - `Z` means UTC (example: `09:30Z`).
257
+ - `±HH:MM` and `±HHmm` are both accepted (examples: `+07:00`, `-0530`).
258
+
259
+ ### datetime
260
+ - Summary: Must be a valid datetime string.
261
+ - Technique: Strict date + time parser with optional timezone offset.
262
+ - Use: `'datetime'`
263
+ - Example: `['datetime']`
264
+ - Supported formats:
265
+ - `YYYY-MM-DDTHH:mm`
266
+ - `YYYY-MM-DD HH:mm`
267
+ - `YYYY-MM-DDTHH:mm:ss`
268
+ - `YYYY-MM-DD HH:mm:ss`
269
+ - `YYYY-MM-DDTHH:mm:ss.SSS`
270
+ - `YYYY-MM-DD HH:mm:ss.SSS`
271
+ - Datetime timezone suffix (optional for each format):
272
+ - `Z`
273
+ - `±HH:MM`
274
+ - `±HHmm`
275
+
240
276
  ### bail
241
277
  - Summary: Placeholder (no-op).
242
278
  - Technique: No behavior; included for parity.
@@ -245,27 +281,29 @@ Notes:
245
281
 
246
282
  ### gt:field
247
283
  - Summary: Must be greater than another field.
248
- - Technique: Numeric comparison via `Number`.
284
+ - Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
249
285
  - Use: `'gt:other'`
250
286
  - Example: `['gt:other']`
251
287
 
252
288
  ### gte:field
253
289
  - Summary: Must be greater than or equal to another field.
254
- - Technique: Numeric comparison via `Number`.
290
+ - Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
255
291
  - Use: `'gte:other'`
256
292
  - Example: `['gte:other']`
257
293
 
258
294
  ### lt:field
259
295
  - Summary: Must be less than another field.
260
- - Technique: Numeric comparison via `Number`.
296
+ - Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
261
297
  - Use: `'lt:other'`
262
298
  - Example: `['lt:other']`
263
299
 
264
300
  ### lte:field
265
301
  - Summary: Must be less than or equal to another field.
266
- - Technique: Numeric comparison via `Number`.
302
+ - Technique: Type-aware comparison in this order: `time` -> `date` (`YYYY-MM-DD`) -> `datetime` -> numeric.
267
303
  - Use: `'lte:other'`
268
304
  - Example: `['lte:other']`
305
+ - Comparison notes:
306
+ - For `time`, comparing offset-aware values against non-offset values fails (both sides must either include offset or not).
269
307
 
270
308
  ### starts_with:a,b,c
271
309
  - Summary: Must start with any of the given prefixes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ciwergrp/nuxid",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "All-in-one essential modules for Nuxt",
5
5
  "repository": {
6
6
  "type": "git",