cynx-lib 1.3.78 → 1.3.80

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/package.json CHANGED
@@ -1,72 +1,73 @@
1
- {
2
- "name": "cynx-lib",
3
- "version": "1.3.78",
4
- "type": "module",
5
- "main": "./index.cjs",
6
- "module": "./index.js",
7
- "bin": {
8
- "cynx-gen": "./gen.js"
9
- },
10
- "scripts": {
11
- "build": "node scripts/build-cjs.js",
12
- "prepublishOnly": "node scripts/build-cjs.js"
13
- },
14
- "exports": {
15
- ".": {
16
- "import": "./index.js",
17
- "require": "./index.cjs"
18
- },
19
- "./vue": {
20
- "import": "./vue/index.js",
21
- "require": "./vue/index.js"
22
- },
23
- "./ui": {
24
- "import": "./ui/index.js",
25
- "require": "./ui/index.cjs"
26
- },
27
- "./utils": {
28
- "import": "./utils/index.js",
29
- "require": "./utils/index.cjs"
30
- },
31
- "./db": {
32
- "import": "./utils/utils/db.js",
33
- "require": "./utils/utils/db.cjs"
34
- },
35
- "./storage": {
36
- "import": "./utils/utils/storage_manager.js",
37
- "require": "./utils/utils/storage_manager.cjs"
38
- },
39
- "./gen": "./gen.js"
40
- },
41
- "peerDependencies": {
42
- "lucide-react": ">=0.300",
43
- "react": ">=18",
44
- "react-dom": ">=18",
45
- "react-router-dom": ">=6"
46
- },
47
- "peerDependenciesMeta": {
48
- "react": {
49
- "optional": true
50
- },
51
- "react-dom": {
52
- "optional": true
53
- },
54
- "react-router-dom": {
55
- "optional": true
56
- },
57
- "lucide-react": {
58
- "optional": true
59
- }
60
- },
61
- "devDependencies": {
62
- "esbuild": "^0.25.12"
63
- },
64
- "files": [
65
- "ui/",
66
- "vue/",
67
- "utils/",
68
- "index.js",
69
- "index.cjs",
70
- "gen.js"
71
- ]
72
- }
1
+ {
2
+ "name": "cynx-lib",
3
+ "version": "1.3.80",
4
+ "type": "module",
5
+ "main": "./index.cjs",
6
+ "module": "./index.js",
7
+ "bin": {
8
+ "cynx-gen": "./gen.js"
9
+ },
10
+ "scripts": {
11
+ "build": "node scripts/build-cjs.js",
12
+ "release": "node scripts/release.js",
13
+ "prepublishOnly": "node scripts/build-cjs.js"
14
+ },
15
+ "exports": {
16
+ ".": {
17
+ "import": "./index.js",
18
+ "require": "./index.cjs"
19
+ },
20
+ "./vue": {
21
+ "import": "./vue/index.js",
22
+ "require": "./vue/index.js"
23
+ },
24
+ "./ui": {
25
+ "import": "./ui/index.js",
26
+ "require": "./ui/index.cjs"
27
+ },
28
+ "./utils": {
29
+ "import": "./utils/index.js",
30
+ "require": "./utils/index.cjs"
31
+ },
32
+ "./db": {
33
+ "import": "./utils/utils/db.js",
34
+ "require": "./utils/utils/db.cjs"
35
+ },
36
+ "./storage": {
37
+ "import": "./utils/utils/storage_manager.js",
38
+ "require": "./utils/utils/storage_manager.cjs"
39
+ },
40
+ "./gen": "./gen.js"
41
+ },
42
+ "peerDependencies": {
43
+ "lucide-react": ">=0.300",
44
+ "react": ">=18",
45
+ "react-dom": ">=18",
46
+ "react-router-dom": ">=6"
47
+ },
48
+ "peerDependenciesMeta": {
49
+ "react": {
50
+ "optional": true
51
+ },
52
+ "react-dom": {
53
+ "optional": true
54
+ },
55
+ "react-router-dom": {
56
+ "optional": true
57
+ },
58
+ "lucide-react": {
59
+ "optional": true
60
+ }
61
+ },
62
+ "devDependencies": {
63
+ "esbuild": "^0.25.12"
64
+ },
65
+ "files": [
66
+ "ui/",
67
+ "vue/",
68
+ "utils/",
69
+ "index.js",
70
+ "index.cjs",
71
+ "gen.js"
72
+ ]
73
+ }
@@ -142,6 +142,8 @@ ensureStyles();
142
142
 
143
143
  const props = withDefaults(
144
144
  defineProps<{
145
+ type?: "date" | "month" | "year";
146
+ mode?: "date" | "month" | "year";
145
147
  modelValue?: any;
146
148
  value?: any;
147
149
  label?: string;
@@ -164,10 +166,12 @@ const props = withDefaults(
164
166
  range?: boolean;
165
167
  }>(),
166
168
  {
169
+ type: "date",
170
+ mode: undefined,
167
171
  modelValue: undefined,
168
172
  value: undefined,
169
173
  label: "",
170
- placeholder: "Select date",
174
+ placeholder: undefined,
171
175
  badges: () => ({}),
172
176
  minDate: undefined,
173
177
  maxDate: undefined,
@@ -196,10 +200,34 @@ const emit = defineEmits<{
196
200
  (e: "clear"): void;
197
201
  }>();
198
202
 
203
+ const emitValue = (val: any) => {
204
+ emit("update:modelValue", val);
205
+ emit("update:value", val);
206
+ emit("change", val);
207
+ };
208
+
199
209
  const currentVal = computed(() => {
200
210
  return props.modelValue !== undefined ? props.modelValue : props.value;
201
211
  });
202
212
 
213
+ const pickerType = computed(() => props.type || props.mode || "date");
214
+ const initialView = computed<"day" | "month" | "year">(() => {
215
+ if (pickerType.value === "month") return "month";
216
+ if (pickerType.value === "year") return "year";
217
+ return "day";
218
+ });
219
+
220
+ const defaultPlaceholder = computed(() =>
221
+ pickerType.value === "month"
222
+ ? "Select month"
223
+ : pickerType.value === "year"
224
+ ? "Select year"
225
+ : "Select date"
226
+ );
227
+ const effectivePlaceholder = computed(() =>
228
+ props.placeholder !== undefined ? props.placeholder : defaultPlaceholder.value
229
+ );
230
+
203
231
  const loc = computed<DatePickerLocale>(() => {
204
232
  if (typeof props.locale === "object" && props.locale !== null) {
205
233
  return props.locale;
@@ -209,7 +237,10 @@ const loc = computed<DatePickerLocale>(() => {
209
237
 
210
238
  const s = computed(() => SIZES[props.size] || SIZES.md);
211
239
  const open = ref(false);
212
- const view = ref<"day" | "month" | "year">("day");
240
+ const view = ref<"day" | "month" | "year">(initialView.value);
241
+ watch(pickerType, (pt) => {
242
+ view.value = pt === "month" ? "month" : pt === "year" ? "year" : "day";
243
+ });
213
244
  const cursor = ref<Date>(new Date());
214
245
  const pos = ref({ top: 0, left: 0, width: 0 });
215
246
 
@@ -282,13 +313,13 @@ const handleWindowClick = (e: MouseEvent) => {
282
313
  if (dropRef.value?.contains(target)) return;
283
314
  if (target.closest?.(".cynx-timepicker-drop")) return;
284
315
  open.value = false;
285
- view.value = "day";
316
+ view.value = initialView.value;
286
317
  };
287
318
 
288
319
  const handleKeyDown = (e: KeyboardEvent) => {
289
320
  if (e.key === "Escape" && open.value) {
290
321
  open.value = false;
291
- view.value = "day";
322
+ view.value = initialView.value;
292
323
  }
293
324
  };
294
325
 
@@ -317,7 +348,10 @@ onBeforeUnmount(() => {
317
348
 
318
349
  const toggleOpen = () => {
319
350
  if (props.disabled) return;
320
- if (!open.value) calcPos();
351
+ if (!open.value) {
352
+ calcPos();
353
+ view.value = initialView.value;
354
+ }
321
355
  open.value = !open.value;
322
356
  };
323
357
 
@@ -344,12 +378,10 @@ function pick(date: Date) {
344
378
  rangeEnd.value = end;
345
379
 
346
380
  const result = [formatDateStr(start), formatDateStr(end)];
347
- emit("update:modelValue", result);
348
- emit("update:value", result);
349
- emit("change", result);
381
+ emitValue(result);
350
382
 
351
383
  open.value = false;
352
- view.value = "day";
384
+ view.value = initialView.value;
353
385
  }
354
386
  return;
355
387
  }
@@ -360,18 +392,14 @@ function pick(date: Date) {
360
392
  const strVal = `${y}-${m}-${d}`;
361
393
 
362
394
  if (typeof currentVal.value === "string" || currentVal.value === undefined) {
363
- emit("update:modelValue", strVal);
364
- emit("update:value", strVal);
365
- emit("change", strVal);
395
+ emitValue(strVal);
366
396
  } else {
367
- emit("update:modelValue", date);
368
- emit("update:value", date);
369
- emit("change", date);
397
+ emitValue(date);
370
398
  }
371
399
 
372
400
  if (!props.showTime) {
373
401
  open.value = false;
374
- view.value = "day";
402
+ view.value = initialView.value;
375
403
  }
376
404
  }
377
405
 
@@ -381,27 +409,91 @@ function handleClear(e: MouseEvent) {
381
409
  rangeStart.value = null;
382
410
  rangeEnd.value = null;
383
411
  }
384
- emit("update:modelValue", null);
385
- emit("update:value", null);
386
- emit("change", null);
412
+ emitValue(null);
387
413
  emit("clear");
388
414
  }
389
415
 
416
+ const isPrevDayDisabled = () => {
417
+ const minD = toDate(props.minDate);
418
+ if (!minD) return false;
419
+ const current = toDate(currentVal.value) || new Date();
420
+ if (pickerType.value === "year") {
421
+ return current.getFullYear() - 1 < minD.getFullYear();
422
+ }
423
+ if (pickerType.value === "month") {
424
+ const prevMonthEnd = new Date(current.getFullYear(), current.getMonth(), 0, 23, 59, 59, 999);
425
+ return prevMonthEnd < new Date(new Date(minD).setHours(0, 0, 0, 0));
426
+ }
427
+ const prev = new Date(current.getFullYear(), current.getMonth(), current.getDate() - 1);
428
+ return prev < new Date(new Date(minD).setHours(0, 0, 0, 0));
429
+ };
430
+
431
+ const isNextDayDisabled = () => {
432
+ const maxD = toDate(props.maxDate);
433
+ if (!maxD) return false;
434
+ const current = toDate(currentVal.value) || new Date();
435
+ if (pickerType.value === "year") {
436
+ return current.getFullYear() + 1 > maxD.getFullYear();
437
+ }
438
+ if (pickerType.value === "month") {
439
+ const nextMonthStart = new Date(current.getFullYear(), current.getMonth() + 1, 1);
440
+ return nextMonthStart > new Date(new Date(maxD).setHours(23, 59, 59, 999));
441
+ }
442
+ const next = new Date(current.getFullYear(), current.getMonth(), current.getDate() + 1);
443
+ return next > new Date(new Date(maxD).setHours(23, 59, 59, 999));
444
+ };
445
+
390
446
  const changeDay = (delta: number) => {
391
447
  if (props.disabled) return;
392
448
  const current = toDate(currentVal.value) || new Date();
449
+ const minD = toDate(props.minDate);
450
+ const maxD = toDate(props.maxDate);
451
+
452
+ if (pickerType.value === "year") {
453
+ const next = new Date(current.getFullYear() + delta, 0, 1);
454
+ if (minD && next.getFullYear() < minD.getFullYear()) return;
455
+ if (maxD && next.getFullYear() > maxD.getFullYear()) return;
456
+ if (typeof currentVal.value === "number") {
457
+ emitValue(next.getFullYear());
458
+ } else if (typeof currentVal.value === "string") {
459
+ emitValue(`${next.getFullYear()}`);
460
+ } else {
461
+ emitValue(next);
462
+ }
463
+ return;
464
+ }
465
+
466
+ if (pickerType.value === "month") {
467
+ const next = new Date(current.getFullYear(), current.getMonth() + delta, 1);
468
+ const nextEnd = new Date(next.getFullYear(), next.getMonth() + 1, 0, 23, 59, 59, 999);
469
+ if (minD && nextEnd < new Date(new Date(minD).setHours(0, 0, 0, 0))) return;
470
+ if (maxD && next > new Date(new Date(maxD).setHours(23, 59, 59, 999))) return;
471
+ if (typeof currentVal.value === "string") {
472
+ const y = next.getFullYear();
473
+ const m = pad(next.getMonth() + 1);
474
+ emitValue(`${y}-${m}`);
475
+ } else {
476
+ emitValue(next);
477
+ }
478
+ return;
479
+ }
480
+
393
481
  const next = new Date(
394
482
  current.getFullYear(),
395
483
  current.getMonth(),
396
484
  current.getDate() + delta
397
485
  );
398
-
399
- const minD = toDate(props.minDate);
400
- const maxD = toDate(props.maxDate);
401
486
  if (minD && next < new Date(new Date(minD).setHours(0, 0, 0, 0))) return;
402
487
  if (maxD && next > new Date(new Date(maxD).setHours(23, 59, 59, 999))) return;
403
488
 
404
- pick(next);
489
+ if (typeof currentVal.value === "string") {
490
+ const y = next.getFullYear();
491
+ const m = pad(next.getMonth() + 1);
492
+ const d = pad(next.getDate());
493
+ emitValue(`${y}-${m}-${d}`);
494
+ } else {
495
+ emitValue(next);
496
+ }
405
497
  };
406
498
 
407
499
  const isNavNextDisabled = () => {
@@ -456,9 +548,6 @@ function nav(dir: number) {
456
548
  cursor.value = c;
457
549
  }
458
550
 
459
- function cycleView() {
460
- view.value = view.value === "day" ? "month" : "year";
461
- }
462
551
 
463
552
  const valDate = computed(() => toDate(currentVal.value));
464
553
 
@@ -559,6 +648,21 @@ const formatted = computed(() => {
559
648
  const vd = valDate.value;
560
649
  const formattedTime =
561
650
  props.showTime && props.timeValue ? ` ${props.timeValue}` : "";
651
+
652
+ if (pickerType.value === "year") {
653
+ return vd
654
+ ? `${vd.getFullYear()}`
655
+ : currentVal.value !== null && currentVal.value !== undefined && currentVal.value !== ""
656
+ ? `${currentVal.value}`
657
+ : null;
658
+ }
659
+ if (pickerType.value === "month") {
660
+ return vd
661
+ ? `${loc.value.months[vd.getMonth()]} ${vd.getFullYear()}`
662
+ : typeof currentVal.value === "string" && currentVal.value
663
+ ? `${currentVal.value}`
664
+ : null;
665
+ }
562
666
  if (vd) {
563
667
  const mName = loc.value.months[vd.getMonth()] || "";
564
668
  return `${mName} ${vd.getDate()}, ${vd.getFullYear()}${formattedTime}`;
@@ -569,18 +673,6 @@ const formatted = computed(() => {
569
673
  return null;
570
674
  });
571
675
 
572
- const headerLabel = computed(() => {
573
- const y = cursor.value.getFullYear();
574
- const m = cursor.value.getMonth();
575
- if (view.value === "day") {
576
- return `${loc.value.months[m]} ${y}`;
577
- }
578
- if (view.value === "month") {
579
- return `${y}`;
580
- }
581
- const base = Math.floor(y / 12) * 12;
582
- return `${base} – ${base + 11}`;
583
- });
584
676
 
585
677
  const daysCells = computed(() => {
586
678
  const y = cursor.value.getFullYear();
@@ -689,7 +781,7 @@ const isYearDisabled = (yr: number) => {
689
781
  <button
690
782
  v-if="props.stepper === 'around' || props.stepper === 'left'"
691
783
  type="button"
692
- :disabled="disabled || isCellDisabled(new Date(new Date(valDate || new Date()).setDate((valDate || new Date()).getDate() - 1)))"
784
+ :disabled="disabled || isPrevDayDisabled()"
693
785
  style="
694
786
  display: flex;
695
787
  align-items: center;
@@ -717,7 +809,7 @@ const isYearDisabled = (yr: number) => {
717
809
  <button
718
810
  v-if="props.stepper === 'left'"
719
811
  type="button"
720
- :disabled="disabled || isCellDisabled(new Date(new Date(valDate || new Date()).setDate((valDate || new Date()).getDate() + 1)))"
812
+ :disabled="disabled || isNextDayDisabled()"
721
813
  style="
722
814
  display: flex;
723
815
  align-items: center;
@@ -787,7 +879,7 @@ const isYearDisabled = (yr: number) => {
787
879
  min-width: 0;
788
880
  "
789
881
  >
790
- {{ formatted || placeholder }}
882
+ {{ formatted || effectivePlaceholder }}
791
883
  </span>
792
884
  <span
793
885
  v-if="currentVal && clearable"
@@ -813,7 +905,7 @@ const isYearDisabled = (yr: number) => {
813
905
  <button
814
906
  v-if="props.stepper === 'right'"
815
907
  type="button"
816
- :disabled="disabled || isCellDisabled(new Date(new Date(valDate || new Date()).setDate((valDate || new Date()).getDate() - 1)))"
908
+ :disabled="disabled || isPrevDayDisabled()"
817
909
  style="
818
910
  display: flex;
819
911
  align-items: center;
@@ -841,7 +933,7 @@ const isYearDisabled = (yr: number) => {
841
933
  <button
842
934
  v-if="props.stepper === 'around' || props.stepper === 'right'"
843
935
  type="button"
844
- :disabled="disabled || isCellDisabled(new Date(new Date(valDate || new Date()).setDate((valDate || new Date()).getDate() + 1)))"
936
+ :disabled="disabled || isNextDayDisabled()"
845
937
  style="
846
938
  display: flex;
847
939
  align-items: center;
@@ -921,23 +1013,84 @@ const isYearDisabled = (yr: number) => {
921
1013
  <ChevronLeft :size="15" />
922
1014
  </button>
923
1015
 
924
- <button
925
- type="button"
926
- style="
927
- background: none;
928
- border: none;
929
- cursor: pointer;
930
- font-size: .82rem;
931
- font-weight: 700;
932
- color: var(--primary-text, #0f172a);
933
- padding: 4px 8px;
934
- border-radius: var(--radxs, 6px);
935
- font-family: var(--sans, inherit);
936
- "
937
- @click="cycleView"
938
- >
939
- {{ headerLabel }}
940
- </button>
1016
+ <div style="display: flex; align-items: center; gap: 4px; min-width: 0;">
1017
+ <template v-if="view === 'day'">
1018
+ <button
1019
+ type="button"
1020
+ style="
1021
+ background: none;
1022
+ border: none;
1023
+ cursor: pointer;
1024
+ font-size: .82rem;
1025
+ font-weight: 700;
1026
+ color: var(--primary-text, #0f172a);
1027
+ padding: 4px 6px;
1028
+ border-radius: var(--radxs, 6px);
1029
+ font-family: var(--sans, inherit);
1030
+ transition: background .12s;
1031
+ "
1032
+ @click="view = 'month'"
1033
+ @mouseenter="(e: MouseEvent) => ((e.currentTarget as HTMLElement).style.background = 'var(--grey-1, #f5f5f8)')"
1034
+ @mouseleave="(e: MouseEvent) => ((e.currentTarget as HTMLElement).style.background = 'none')"
1035
+ >
1036
+ {{ loc.months[cursor.getMonth()] }}
1037
+ </button>
1038
+ <button
1039
+ type="button"
1040
+ style="
1041
+ background: none;
1042
+ border: none;
1043
+ cursor: pointer;
1044
+ font-size: .82rem;
1045
+ font-weight: 700;
1046
+ color: var(--primary-text, #0f172a);
1047
+ padding: 4px 6px;
1048
+ border-radius: var(--radxs, 6px);
1049
+ font-family: var(--sans, inherit);
1050
+ transition: background .12s;
1051
+ "
1052
+ @click="view = 'year'"
1053
+ @mouseenter="(e: MouseEvent) => ((e.currentTarget as HTMLElement).style.background = 'var(--grey-1, #f5f5f8)')"
1054
+ @mouseleave="(e: MouseEvent) => ((e.currentTarget as HTMLElement).style.background = 'none')"
1055
+ >
1056
+ {{ cursor.getFullYear() }}
1057
+ </button>
1058
+ </template>
1059
+
1060
+ <button
1061
+ v-else-if="view === 'month'"
1062
+ type="button"
1063
+ style="
1064
+ background: none;
1065
+ border: none;
1066
+ cursor: pointer;
1067
+ font-size: .82rem;
1068
+ font-weight: 700;
1069
+ color: var(--primary-text, #0f172a);
1070
+ padding: 4px 8px;
1071
+ border-radius: var(--radxs, 6px);
1072
+ font-family: var(--sans, inherit);
1073
+ transition: background .12s;
1074
+ "
1075
+ @click="view = 'year'"
1076
+ @mouseenter="(e: MouseEvent) => ((e.currentTarget as HTMLElement).style.background = 'var(--grey-1, #f5f5f8)')"
1077
+ @mouseleave="(e: MouseEvent) => ((e.currentTarget as HTMLElement).style.background = 'none')"
1078
+ >
1079
+ {{ cursor.getFullYear() }}
1080
+ </button>
1081
+
1082
+ <span
1083
+ v-else-if="view === 'year'"
1084
+ style="
1085
+ font-size: .82rem;
1086
+ font-weight: 700;
1087
+ color: var(--primary-text, #0f172a);
1088
+ padding: 4px 8px;
1089
+ "
1090
+ >
1091
+ {{ Math.floor(cursor.getFullYear() / 12) * 12 }} – {{ Math.floor(cursor.getFullYear() / 12) * 12 + 11 }}
1092
+ </span>
1093
+ </div>
941
1094
 
942
1095
  <button
943
1096
  type="button"
@@ -1084,7 +1237,18 @@ const isYearDisabled = (yr: number) => {
1084
1237
  const c = new Date(cursor);
1085
1238
  c.setMonth(idx);
1086
1239
  cursor = c;
1087
- view = 'day';
1240
+ if (pickerType === 'month') {
1241
+ if (typeof currentVal === 'string') {
1242
+ const ym = `${c.getFullYear()}-${pad(idx + 1)}`;
1243
+ emitValue(ym);
1244
+ } else {
1245
+ emitValue(c);
1246
+ }
1247
+ open = false;
1248
+ view = 'month';
1249
+ } else {
1250
+ view = 'day';
1251
+ }
1088
1252
  }
1089
1253
  "
1090
1254
  >
@@ -1127,7 +1291,21 @@ const isYearDisabled = (yr: number) => {
1127
1291
  const c = new Date(cursor);
1128
1292
  c.setFullYear(yr);
1129
1293
  cursor = c;
1130
- view = 'month';
1294
+ if (pickerType === 'year') {
1295
+ if (typeof currentVal === 'number') {
1296
+ emitValue(yr);
1297
+ } else if (typeof currentVal === 'string') {
1298
+ emitValue(`${yr}`);
1299
+ } else {
1300
+ emitValue(c);
1301
+ }
1302
+ open = false;
1303
+ view = 'year';
1304
+ } else if (pickerType === 'month') {
1305
+ view = 'month';
1306
+ } else {
1307
+ view = 'day';
1308
+ }
1131
1309
  }
1132
1310
  "
1133
1311
  >