@djangocfg/ui-tools 2.1.133 → 2.1.135

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.
@@ -9,24 +9,29 @@ var lucideReact = require('lucide-react');
9
9
 
10
10
  // src/tools/CronScheduler/utils/cron-builder.ts
11
11
  function buildCron(state) {
12
- const { type, hour, minute, weekDays, monthDays, customCron } = state;
13
- const h = Math.max(0, Math.min(23, hour));
14
- const m = Math.max(0, Math.min(59, minute));
12
+ if (!state || typeof state !== "object") {
13
+ return "0 0 * * *";
14
+ }
15
+ const { type, hour = 0, minute = 0, weekDays = [], monthDays = [], customCron = "" } = state;
16
+ const h = Math.max(0, Math.min(23, Number(hour) || 0));
17
+ const m = Math.max(0, Math.min(59, Number(minute) || 0));
18
+ const safeWeekDays = Array.isArray(weekDays) ? weekDays : [];
19
+ const safeMonthDays = Array.isArray(monthDays) ? monthDays : [];
15
20
  switch (type) {
16
21
  case "daily":
17
22
  return `${m} ${h} * * *`;
18
23
  case "weekly": {
19
- const sortedDays = [...weekDays].sort((a, b) => a - b);
24
+ const sortedDays = [...safeWeekDays].sort((a, b) => a - b);
20
25
  const daysStr = sortedDays.length > 0 ? formatNumberList(sortedDays) : "*";
21
26
  return `${m} ${h} * * ${daysStr}`;
22
27
  }
23
28
  case "monthly": {
24
- const sortedDays = [...monthDays].sort((a, b) => a - b);
29
+ const sortedDays = [...safeMonthDays].sort((a, b) => a - b);
25
30
  const daysStr = sortedDays.length > 0 ? formatNumberList(sortedDays) : "1";
26
31
  return `${m} ${h} ${daysStr} * *`;
27
32
  }
28
33
  case "custom":
29
- return customCron.trim() || "* * * * *";
34
+ return (customCron || "").trim() || "* * * * *";
30
35
  default:
31
36
  return "0 0 * * *";
32
37
  }
@@ -59,10 +64,13 @@ function parseCron(cron) {
59
64
  const parts = cron.trim().split(/\s+/);
60
65
  if (parts.length !== 5) return null;
61
66
  const [minutePart, hourPart, dayOfMonthPart, monthPart, dayOfWeekPart] = parts;
62
- const minute = parseField(minutePart, 0, 59);
63
- if (minute === null && minutePart !== "*") return null;
64
- const hour = parseField(hourPart, 0, 23);
65
- if (hour === null && hourPart !== "*") return null;
67
+ if (!isValidCronField(minutePart, 0, 59)) return null;
68
+ if (!isValidCronField(hourPart, 0, 23)) return null;
69
+ if (!isValidCronField(dayOfMonthPart, 1, 31)) return null;
70
+ if (!isValidCronField(monthPart, 1, 12)) return null;
71
+ if (!isValidCronField(dayOfWeekPart, 0, 6)) return null;
72
+ const minute = parseField(minutePart);
73
+ const hour = parseField(hourPart);
66
74
  const result = detectScheduleType(
67
75
  minutePart,
68
76
  hourPart,
@@ -97,10 +105,31 @@ function detectScheduleType(_minutePart, _hourPart, dayOfMonthPart, monthPart, d
97
105
  return { type, weekDays, monthDays };
98
106
  }
99
107
  chunkWGEGR3DF_cjs.__name(detectScheduleType, "detectScheduleType");
100
- function parseField(part, min, max) {
108
+ function isValidCronField(part, min, max) {
109
+ if (part === "*") return true;
110
+ if (part.includes("/")) {
111
+ const [base, step] = part.split("/");
112
+ if (!step || !/^\d+$/.test(step)) return false;
113
+ if (base === "*") return true;
114
+ return isValidCronField(base, min, max);
115
+ }
116
+ if (part.includes("-") && !part.includes(",")) {
117
+ const [start, end] = part.split("-").map((s) => parseInt(s, 10));
118
+ return !isNaN(start) && !isNaN(end) && start >= min && end <= max && start <= end;
119
+ }
120
+ if (part.includes(",")) {
121
+ return part.split(",").every((p) => isValidCronField(p.trim(), min, max));
122
+ }
101
123
  if (/^\d+$/.test(part)) {
102
124
  const num = parseInt(part, 10);
103
- if (num >= min && num <= max) return num;
125
+ return num >= min && num <= max;
126
+ }
127
+ return false;
128
+ }
129
+ chunkWGEGR3DF_cjs.__name(isValidCronField, "isValidCronField");
130
+ function parseField(part, _min, _max) {
131
+ if (/^\d+$/.test(part)) {
132
+ return parseInt(part, 10);
104
133
  }
105
134
  return null;
106
135
  }
@@ -149,19 +178,8 @@ function isValidCron(cron) {
149
178
  if (!cron || typeof cron !== "string") return false;
150
179
  const parts = cron.trim().split(/\s+/);
151
180
  if (parts.length !== 5) return false;
152
- const patterns = [
153
- /^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
154
- // minute
155
- /^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
156
- // hour
157
- /^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
158
- // day of month
159
- /^(\*|\d{1,2}|\d{1,2}-\d{1,2}|\d{1,2}(,\d{1,2})*|\*\/\d{1,2})$/,
160
- // month
161
- /^(\*|\d{1}|\d{1}-\d{1}|\d{1}(,\d{1})*|\*\/\d{1,2})$/
162
- // day of week
163
- ];
164
- return parts.every((part, i) => patterns[i].test(part));
181
+ const [minutePart, hourPart, dayOfMonthPart, monthPart, dayOfWeekPart] = parts;
182
+ return isValidCronField(minutePart, 0, 59) && isValidCronField(hourPart, 0, 23) && isValidCronField(dayOfMonthPart, 1, 31) && isValidCronField(monthPart, 1, 12) && isValidCronField(dayOfWeekPart, 0, 6);
165
183
  }
166
184
  chunkWGEGR3DF_cjs.__name(isValidCron, "isValidCron");
167
185
 
@@ -311,21 +329,27 @@ function CronSchedulerProvider({
311
329
  const onChangeRef = react.useRef(onChange);
312
330
  onChangeRef.current = onChange;
313
331
  const [state, setState] = react.useState(() => {
314
- if (value) {
315
- const parsed = parseCron(value);
316
- if (parsed) return parsed;
332
+ try {
333
+ if (value) {
334
+ const parsed = parseCron(value);
335
+ if (parsed) return parsed;
336
+ }
337
+ } catch {
317
338
  }
318
339
  return { ...DEFAULT_STATE, type: defaultType };
319
340
  });
320
341
  react.useEffect(() => {
321
- if (value) {
322
- const parsed = parseCron(value);
323
- if (parsed) {
324
- const currentCron = buildCron(state);
325
- if (value !== currentCron) {
326
- setState(parsed);
342
+ try {
343
+ if (value) {
344
+ const parsed = parseCron(value);
345
+ if (parsed) {
346
+ const currentCron = buildCron(state);
347
+ if (value !== currentCron) {
348
+ setState(parsed);
349
+ }
327
350
  }
328
351
  }
352
+ } catch {
329
353
  }
330
354
  }, [value]);
331
355
  const cronExpression = react.useMemo(() => buildCron(state), [state]);
@@ -495,25 +519,32 @@ function ScheduleTypeSelector({
495
519
  className
496
520
  }) {
497
521
  const { type, setType } = useCronType();
522
+ const triggerClassName = lib.cn(
523
+ "text-xs font-medium px-2 py-1.5",
524
+ "data-[state=active]:shadow-sm",
525
+ "transition-all duration-150"
526
+ );
527
+ const triggers = SCHEDULE_TYPES.map(({ value, label }) => ({
528
+ value,
529
+ label,
530
+ disabled,
531
+ className: triggerClassName
532
+ }));
498
533
  return /* @__PURE__ */ jsxRuntime.jsx(
499
534
  components.Tabs,
500
535
  {
501
536
  value: type,
502
537
  onValueChange: (v) => setType(v),
503
538
  className: lib.cn("w-full", className),
504
- children: /* @__PURE__ */ jsxRuntime.jsx(components.TabsList, { className: "!grid w-full !grid-cols-4 h-9 p-0.5", children: SCHEDULE_TYPES.map(({ value, label }) => /* @__PURE__ */ jsxRuntime.jsx(
539
+ children: /* @__PURE__ */ jsxRuntime.jsx(components.TabsList, { className: "grid w-full grid-cols-4 h-9 p-0.5", children: triggers.map((trigger) => /* @__PURE__ */ jsxRuntime.jsx(
505
540
  components.TabsTrigger,
506
541
  {
507
- value,
508
- disabled,
509
- className: lib.cn(
510
- "text-xs font-medium px-2 py-1.5",
511
- "data-[state=active]:shadow-sm",
512
- "transition-all duration-150"
513
- ),
514
- children: label
542
+ value: trigger.value,
543
+ disabled: trigger.disabled,
544
+ className: trigger.className,
545
+ children: trigger.label
515
546
  },
516
- value
547
+ trigger.value
517
548
  )) })
518
549
  }
519
550
  );
@@ -614,66 +645,60 @@ function DayChips({
614
645
  className
615
646
  }) {
616
647
  const { weekDays, toggleWeekDay, setWeekDays } = useCronWeekDays();
617
- const isWeekdays2 = weekDays.length === 5 && [1, 2, 3, 4, 5].every((d) => weekDays.includes(d));
618
- const isWeekend2 = weekDays.length === 2 && [0, 6].every((d) => weekDays.includes(d));
619
- const isEveryday = weekDays.length === 7;
620
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("space-y-3", className), children: [
621
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: DAYS.map(({ value, label }) => {
622
- const isSelected = weekDays.includes(value);
623
- const isWeekend3 = value === 0 || value === 6;
624
- return /* @__PURE__ */ jsxRuntime.jsx(
625
- "button",
626
- {
627
- type: "button",
628
- disabled,
629
- onClick: () => toggleWeekDay(value),
630
- "aria-pressed": isSelected,
631
- className: lib.cn(
632
- "flex flex-col items-center justify-center",
633
- "py-2.5 rounded-lg text-xs font-medium",
634
- "transition-all duration-150",
635
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
636
- "active:scale-[0.97]",
637
- isSelected ? "bg-primary text-primary-foreground shadow-sm" : lib.cn(
638
- "bg-muted/50 hover:bg-muted",
639
- isWeekend3 ? "text-muted-foreground/70" : "text-muted-foreground"
640
- ),
641
- disabled && "opacity-50 cursor-not-allowed pointer-events-none"
642
- ),
643
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: label })
644
- },
645
- value
646
- );
647
- }) }),
648
- showPresets && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
649
- /* @__PURE__ */ jsxRuntime.jsx(
650
- PresetButton,
651
- {
652
- label: "Weekdays",
653
- isActive: isWeekdays2,
654
- onClick: () => setWeekDays([1, 2, 3, 4, 5]),
655
- disabled
656
- }
657
- ),
658
- /* @__PURE__ */ jsxRuntime.jsx(
659
- PresetButton,
660
- {
661
- label: "Weekends",
662
- isActive: isWeekend2,
663
- onClick: () => setWeekDays([0, 6]),
664
- disabled
665
- }
666
- ),
667
- /* @__PURE__ */ jsxRuntime.jsx(
668
- PresetButton,
669
- {
670
- label: "Every day",
671
- isActive: isEveryday,
672
- onClick: () => setWeekDays([0, 1, 2, 3, 4, 5, 6]),
673
- disabled
674
- }
648
+ const safeWeekDays = Array.isArray(weekDays) ? weekDays : [];
649
+ const isWeekdays2 = safeWeekDays.length === 5 && [1, 2, 3, 4, 5].every((d) => safeWeekDays.includes(d));
650
+ const isWeekendPreset = safeWeekDays.length === 2 && [0, 6].every((d) => safeWeekDays.includes(d));
651
+ const isEveryday = safeWeekDays.length === 7;
652
+ const dayButtons = DAYS.map(({ value, label }) => {
653
+ const isSelected = safeWeekDays.includes(value);
654
+ const isWeekendDay = value === 0 || value === 6;
655
+ return {
656
+ value,
657
+ label,
658
+ isSelected,
659
+ isWeekendDay,
660
+ className: lib.cn(
661
+ "flex flex-col items-center justify-center",
662
+ "py-2.5 rounded-lg text-xs font-medium",
663
+ "transition-all duration-150",
664
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
665
+ "active:scale-[0.97]",
666
+ isSelected ? "bg-primary text-primary-foreground shadow-sm" : lib.cn(
667
+ "bg-muted/50 hover:bg-muted",
668
+ isWeekendDay ? "text-muted-foreground/70" : "text-muted-foreground"
669
+ ),
670
+ disabled && "opacity-50 cursor-not-allowed pointer-events-none"
675
671
  )
676
- ] })
672
+ };
673
+ });
674
+ const presets = [
675
+ { label: "Weekdays", isActive: isWeekdays2, days: [1, 2, 3, 4, 5] },
676
+ { label: "Weekends", isActive: isWeekendPreset, days: [0, 6] },
677
+ { label: "Every day", isActive: isEveryday, days: [0, 1, 2, 3, 4, 5, 6] }
678
+ ];
679
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("space-y-3", className), children: [
680
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: dayButtons.map((day) => /* @__PURE__ */ jsxRuntime.jsx(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ disabled,
685
+ onClick: () => toggleWeekDay(day.value),
686
+ "aria-pressed": day.isSelected,
687
+ className: day.className,
688
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: day.label })
689
+ },
690
+ day.value
691
+ )) }),
692
+ showPresets && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2", children: presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
693
+ PresetButton,
694
+ {
695
+ label: preset.label,
696
+ isActive: preset.isActive,
697
+ onClick: () => setWeekDays(preset.days),
698
+ disabled
699
+ },
700
+ preset.label
701
+ )) })
677
702
  ] });
678
703
  }
679
704
  chunkWGEGR3DF_cjs.__name(DayChips, "DayChips");
@@ -704,74 +729,74 @@ function MonthDayGrid({
704
729
  className
705
730
  }) {
706
731
  const { monthDays, toggleMonthDay, setMonthDays } = useCronMonthDays();
707
- const is1st = monthDays.length === 1 && monthDays[0] === 1;
708
- const is15th = monthDays.length === 1 && monthDays[0] === 15;
709
- const is1stAnd15th = monthDays.length === 2 && monthDays.includes(1) && monthDays.includes(15);
710
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("space-y-3", className), children: [
711
- showPresets && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
712
- /* @__PURE__ */ jsxRuntime.jsx(
713
- PresetButton2,
714
- {
715
- label: "1st",
716
- isActive: is1st,
717
- onClick: () => setMonthDays([1]),
718
- disabled
719
- }
720
- ),
721
- /* @__PURE__ */ jsxRuntime.jsx(
722
- PresetButton2,
723
- {
724
- label: "15th",
725
- isActive: is15th,
726
- onClick: () => setMonthDays([15]),
727
- disabled
728
- }
729
- ),
730
- /* @__PURE__ */ jsxRuntime.jsx(
731
- PresetButton2,
732
- {
733
- label: "1st & 15th",
734
- isActive: is1stAnd15th,
735
- onClick: () => setMonthDays([1, 15]),
736
- disabled
737
- }
732
+ const safeMonthDays = Array.isArray(monthDays) ? monthDays : [];
733
+ const is1st = safeMonthDays.length === 1 && safeMonthDays[0] === 1;
734
+ const is15th = safeMonthDays.length === 1 && safeMonthDays[0] === 15;
735
+ const is1stAnd15th = safeMonthDays.length === 2 && safeMonthDays.includes(1) && safeMonthDays.includes(15);
736
+ const presets = [
737
+ { label: "1st", isActive: is1st, days: [1] },
738
+ { label: "15th", isActive: is15th, days: [15] },
739
+ { label: "1st & 15th", isActive: is1stAnd15th, days: [1, 15] }
740
+ ];
741
+ const gridCells = Array.from({ length: GRID_SIZE }, (_, i) => {
742
+ const day = i + 1;
743
+ const isValidDay = day <= 31;
744
+ const isSelected = isValidDay && safeMonthDays.includes(day);
745
+ const isPartialMonth = day > 28;
746
+ if (!isValidDay) {
747
+ return { type: "empty", key: i };
748
+ }
749
+ return {
750
+ type: "day",
751
+ key: day,
752
+ day,
753
+ isSelected,
754
+ className: lib.cn(
755
+ "aspect-square flex items-center justify-center",
756
+ "rounded-lg text-sm font-medium",
757
+ "transition-all duration-150",
758
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
759
+ "active:scale-[0.95]",
760
+ isSelected ? "bg-primary text-primary-foreground shadow-sm" : lib.cn(
761
+ "bg-muted/30 hover:bg-muted/60",
762
+ isPartialMonth ? "text-muted-foreground/50" : "text-muted-foreground"
763
+ ),
764
+ disabled && "opacity-50 cursor-not-allowed pointer-events-none"
738
765
  )
739
- ] }),
740
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: Array.from({ length: GRID_SIZE }, (_, i) => {
741
- const day = i + 1;
742
- const isValidDay = day <= 31;
743
- const isSelected = isValidDay && monthDays.includes(day);
744
- const isPartialMonth = day > 28;
745
- if (!isValidDay) {
746
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-square" }, i);
766
+ };
767
+ });
768
+ const selectionCount = safeMonthDays.length;
769
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: lib.cn("space-y-3", className), children: [
770
+ showPresets && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2", children: presets.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
771
+ PresetButton2,
772
+ {
773
+ label: preset.label,
774
+ isActive: preset.isActive,
775
+ onClick: () => setMonthDays(preset.days),
776
+ disabled
777
+ },
778
+ preset.label
779
+ )) }),
780
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-7 gap-1", children: gridCells.map((cell) => {
781
+ if (cell.type === "empty") {
782
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-square" }, cell.key);
747
783
  }
748
784
  return /* @__PURE__ */ jsxRuntime.jsx(
749
785
  "button",
750
786
  {
751
787
  type: "button",
752
788
  disabled,
753
- onClick: () => toggleMonthDay(day),
754
- "aria-pressed": isSelected,
755
- "aria-label": `Day ${day}`,
756
- className: lib.cn(
757
- "aspect-square flex items-center justify-center",
758
- "rounded-lg text-sm font-medium",
759
- "transition-all duration-150",
760
- "focus:outline-none focus-visible:ring-2 focus-visible:ring-primary/50",
761
- "active:scale-[0.95]",
762
- isSelected ? "bg-primary text-primary-foreground shadow-sm" : lib.cn(
763
- "bg-muted/30 hover:bg-muted/60",
764
- isPartialMonth ? "text-muted-foreground/50" : "text-muted-foreground"
765
- ),
766
- disabled && "opacity-50 cursor-not-allowed pointer-events-none"
767
- ),
768
- children: day
789
+ onClick: () => toggleMonthDay(cell.day),
790
+ "aria-pressed": cell.isSelected,
791
+ "aria-label": `Day ${cell.day}`,
792
+ className: cell.className,
793
+ children: cell.day
769
794
  },
770
- day
795
+ cell.key
771
796
  );
772
797
  }) }),
773
- monthDays.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground text-center", children: [
774
- monthDays.length,
798
+ selectionCount > 1 && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground text-center", children: [
799
+ selectionCount,
775
800
  " days selected"
776
801
  ] })
777
802
  ] });
@@ -1011,5 +1036,5 @@ exports.useCronSchedulerContext = useCronSchedulerContext;
1011
1036
  exports.useCronTime = useCronTime;
1012
1037
  exports.useCronType = useCronType;
1013
1038
  exports.useCronWeekDays = useCronWeekDays;
1014
- //# sourceMappingURL=chunk-CBGBDVUG.cjs.map
1015
- //# sourceMappingURL=chunk-CBGBDVUG.cjs.map
1039
+ //# sourceMappingURL=chunk-WU6ZLOL4.cjs.map
1040
+ //# sourceMappingURL=chunk-WU6ZLOL4.cjs.map