@dowel-ui/react 0.3.0 → 0.5.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.
Files changed (206) hide show
  1. package/README.md +13 -8
  2. package/dist/components/ai-disclosure/ai-disclosure.d.ts +92 -0
  3. package/dist/components/ai-disclosure/ai-disclosure.d.ts.map +1 -0
  4. package/dist/components/ai-disclosure/ai-disclosure.js +122 -0
  5. package/dist/components/ai-disclosure/ai-disclosure.js.map +1 -0
  6. package/dist/components/ai-disclosure/index.d.ts +2 -0
  7. package/dist/components/ai-disclosure/index.js +2 -0
  8. package/dist/components/ai-disclosure/meta.js +17 -0
  9. package/dist/components/ai-disclosure/meta.js.map +1 -0
  10. package/dist/components/ai-extraction-review/ai-extraction-review.d.ts +52 -0
  11. package/dist/components/ai-extraction-review/ai-extraction-review.d.ts.map +1 -0
  12. package/dist/components/ai-extraction-review/ai-extraction-review.js +249 -0
  13. package/dist/components/ai-extraction-review/ai-extraction-review.js.map +1 -0
  14. package/dist/components/ai-extraction-review/extraction-model.d.ts +94 -0
  15. package/dist/components/ai-extraction-review/extraction-model.d.ts.map +1 -0
  16. package/dist/components/ai-extraction-review/extraction-model.js +91 -0
  17. package/dist/components/ai-extraction-review/extraction-model.js.map +1 -0
  18. package/dist/components/ai-extraction-review/index.d.ts +3 -0
  19. package/dist/components/ai-extraction-review/index.js +3 -0
  20. package/dist/components/ai-extraction-review/meta.js +17 -0
  21. package/dist/components/ai-extraction-review/meta.js.map +1 -0
  22. package/dist/components/ai-suggested-value/ai-suggested-value.d.ts +72 -0
  23. package/dist/components/ai-suggested-value/ai-suggested-value.d.ts.map +1 -0
  24. package/dist/components/ai-suggested-value/ai-suggested-value.js +126 -0
  25. package/dist/components/ai-suggested-value/ai-suggested-value.js.map +1 -0
  26. package/dist/components/ai-suggested-value/index.d.ts +2 -0
  27. package/dist/components/ai-suggested-value/index.js +2 -0
  28. package/dist/components/ai-suggested-value/meta.js +17 -0
  29. package/dist/components/ai-suggested-value/meta.js.map +1 -0
  30. package/dist/components/alert/alert.d.ts +1 -1
  31. package/dist/components/avatar/avatar.d.ts +1 -1
  32. package/dist/components/badge/badge.d.ts +1 -1
  33. package/dist/components/button/button.d.ts +1 -1
  34. package/dist/components/confirm-typed/confirm-typed.d.ts +47 -0
  35. package/dist/components/confirm-typed/confirm-typed.d.ts.map +1 -0
  36. package/dist/components/confirm-typed/confirm-typed.js +106 -0
  37. package/dist/components/confirm-typed/confirm-typed.js.map +1 -0
  38. package/dist/components/confirm-typed/index.d.ts +2 -0
  39. package/dist/components/confirm-typed/index.js +2 -0
  40. package/dist/components/confirm-typed/meta.js +17 -0
  41. package/dist/components/confirm-typed/meta.js.map +1 -0
  42. package/dist/components/cron-editor/cron-editor.d.ts +36 -0
  43. package/dist/components/cron-editor/cron-editor.d.ts.map +1 -0
  44. package/dist/components/cron-editor/cron-editor.js +493 -0
  45. package/dist/components/cron-editor/cron-editor.js.map +1 -0
  46. package/dist/components/cron-editor/cron-expression.d.ts +82 -0
  47. package/dist/components/cron-editor/cron-expression.d.ts.map +1 -0
  48. package/dist/components/cron-editor/cron-expression.js +352 -0
  49. package/dist/components/cron-editor/cron-expression.js.map +1 -0
  50. package/dist/components/cron-editor/index.d.ts +3 -0
  51. package/dist/components/cron-editor/index.js +3 -0
  52. package/dist/components/cron-editor/meta.js +17 -0
  53. package/dist/components/cron-editor/meta.js.map +1 -0
  54. package/dist/components/date-picker/date-picker.js +1 -1
  55. package/dist/components/diff-viewer/diff-model.d.ts +69 -0
  56. package/dist/components/diff-viewer/diff-model.d.ts.map +1 -0
  57. package/dist/components/diff-viewer/diff-model.js +180 -0
  58. package/dist/components/diff-viewer/diff-model.js.map +1 -0
  59. package/dist/components/diff-viewer/diff-viewer.d.ts +42 -0
  60. package/dist/components/diff-viewer/diff-viewer.d.ts.map +1 -0
  61. package/dist/components/diff-viewer/diff-viewer.js +212 -0
  62. package/dist/components/diff-viewer/diff-viewer.js.map +1 -0
  63. package/dist/components/diff-viewer/index.d.ts +3 -0
  64. package/dist/components/diff-viewer/index.js +3 -0
  65. package/dist/components/diff-viewer/meta.js +17 -0
  66. package/dist/components/diff-viewer/meta.js.map +1 -0
  67. package/dist/components/dns-record/dns-record.d.ts +61 -0
  68. package/dist/components/dns-record/dns-record.d.ts.map +1 -0
  69. package/dist/components/dns-record/dns-record.js +227 -0
  70. package/dist/components/dns-record/dns-record.js.map +1 -0
  71. package/dist/components/dns-record/index.d.ts +2 -0
  72. package/dist/components/dns-record/index.js +2 -0
  73. package/dist/components/dns-record/meta.js +17 -0
  74. package/dist/components/dns-record/meta.js.map +1 -0
  75. package/dist/components/log-viewer/index.d.ts +3 -0
  76. package/dist/components/log-viewer/index.js +3 -0
  77. package/dist/components/log-viewer/log-stream.d.ts +68 -0
  78. package/dist/components/log-viewer/log-stream.d.ts.map +1 -0
  79. package/dist/components/log-viewer/log-stream.js +120 -0
  80. package/dist/components/log-viewer/log-stream.js.map +1 -0
  81. package/dist/components/log-viewer/log-viewer.d.ts +51 -0
  82. package/dist/components/log-viewer/log-viewer.d.ts.map +1 -0
  83. package/dist/components/log-viewer/log-viewer.js +284 -0
  84. package/dist/components/log-viewer/log-viewer.js.map +1 -0
  85. package/dist/components/log-viewer/meta.js +17 -0
  86. package/dist/components/log-viewer/meta.js.map +1 -0
  87. package/dist/components/permission-matrix/index.d.ts +3 -0
  88. package/dist/components/permission-matrix/index.js +3 -0
  89. package/dist/components/permission-matrix/meta.js +17 -0
  90. package/dist/components/permission-matrix/meta.js.map +1 -0
  91. package/dist/components/permission-matrix/permission-matrix.d.ts +46 -0
  92. package/dist/components/permission-matrix/permission-matrix.d.ts.map +1 -0
  93. package/dist/components/permission-matrix/permission-matrix.js +199 -0
  94. package/dist/components/permission-matrix/permission-matrix.js.map +1 -0
  95. package/dist/components/permission-matrix/permission-model.d.ts +53 -0
  96. package/dist/components/permission-matrix/permission-model.d.ts.map +1 -0
  97. package/dist/components/permission-matrix/permission-model.js +64 -0
  98. package/dist/components/permission-matrix/permission-model.js.map +1 -0
  99. package/dist/components/progress/progress.d.ts +1 -1
  100. package/dist/components/secret-field/index.d.ts +2 -0
  101. package/dist/components/secret-field/index.js +2 -0
  102. package/dist/components/secret-field/meta.js +17 -0
  103. package/dist/components/secret-field/meta.js.map +1 -0
  104. package/dist/components/secret-field/secret-field.d.ts +58 -0
  105. package/dist/components/secret-field/secret-field.d.ts.map +1 -0
  106. package/dist/components/secret-field/secret-field.js +168 -0
  107. package/dist/components/secret-field/secret-field.js.map +1 -0
  108. package/dist/components/session-expiry/index.d.ts +2 -0
  109. package/dist/components/session-expiry/index.js +2 -0
  110. package/dist/components/session-expiry/meta.js +17 -0
  111. package/dist/components/session-expiry/meta.js.map +1 -0
  112. package/dist/components/session-expiry/session-expiry.d.ts +64 -0
  113. package/dist/components/session-expiry/session-expiry.d.ts.map +1 -0
  114. package/dist/components/session-expiry/session-expiry.js +185 -0
  115. package/dist/components/session-expiry/session-expiry.js.map +1 -0
  116. package/dist/components/shortcut-recorder/index.d.ts +3 -0
  117. package/dist/components/shortcut-recorder/index.js +3 -0
  118. package/dist/components/shortcut-recorder/meta.js +17 -0
  119. package/dist/components/shortcut-recorder/meta.js.map +1 -0
  120. package/dist/components/shortcut-recorder/shortcut-model.d.ts +67 -0
  121. package/dist/components/shortcut-recorder/shortcut-model.d.ts.map +1 -0
  122. package/dist/components/shortcut-recorder/shortcut-model.js +168 -0
  123. package/dist/components/shortcut-recorder/shortcut-model.js.map +1 -0
  124. package/dist/components/shortcut-recorder/shortcut-recorder.d.ts +47 -0
  125. package/dist/components/shortcut-recorder/shortcut-recorder.d.ts.map +1 -0
  126. package/dist/components/shortcut-recorder/shortcut-recorder.js +212 -0
  127. package/dist/components/shortcut-recorder/shortcut-recorder.js.map +1 -0
  128. package/dist/components/spinner/spinner.d.ts +1 -1
  129. package/dist/components/sync-status/index.d.ts +2 -0
  130. package/dist/components/sync-status/index.js +2 -0
  131. package/dist/components/sync-status/meta.js +17 -0
  132. package/dist/components/sync-status/meta.js.map +1 -0
  133. package/dist/components/sync-status/sync-status.d.ts +65 -0
  134. package/dist/components/sync-status/sync-status.d.ts.map +1 -0
  135. package/dist/components/sync-status/sync-status.js +103 -0
  136. package/dist/components/sync-status/sync-status.js.map +1 -0
  137. package/dist/components/time-range-picker/index.d.ts +3 -0
  138. package/dist/components/time-range-picker/index.js +3 -0
  139. package/dist/components/time-range-picker/meta.js +22 -0
  140. package/dist/components/time-range-picker/meta.js.map +1 -0
  141. package/dist/components/time-range-picker/time-expression.d.ts +88 -0
  142. package/dist/components/time-range-picker/time-expression.d.ts.map +1 -0
  143. package/dist/components/time-range-picker/time-expression.js +335 -0
  144. package/dist/components/time-range-picker/time-expression.js.map +1 -0
  145. package/dist/components/time-range-picker/time-range-picker.d.ts +65 -0
  146. package/dist/components/time-range-picker/time-range-picker.d.ts.map +1 -0
  147. package/dist/components/time-range-picker/time-range-picker.js +377 -0
  148. package/dist/components/time-range-picker/time-range-picker.js.map +1 -0
  149. package/dist/components/toast/toast.d.ts +1 -1
  150. package/dist/index.d.ts +36 -1
  151. package/dist/index.js +24 -3
  152. package/dist/registry/components.d.ts.map +1 -1
  153. package/dist/registry/components.js +78 -50
  154. package/dist/registry/components.js.map +1 -1
  155. package/package.json +6 -4
  156. package/src/components/ai-disclosure/ai-disclosure.tsx +244 -0
  157. package/src/components/ai-disclosure/index.ts +9 -0
  158. package/src/components/ai-disclosure/meta.ts +23 -0
  159. package/src/components/ai-extraction-review/ai-extraction-review.tsx +492 -0
  160. package/src/components/ai-extraction-review/extraction-model.ts +159 -0
  161. package/src/components/ai-extraction-review/index.ts +13 -0
  162. package/src/components/ai-extraction-review/meta.ts +24 -0
  163. package/src/components/ai-suggested-value/ai-suggested-value.tsx +295 -0
  164. package/src/components/ai-suggested-value/index.ts +6 -0
  165. package/src/components/ai-suggested-value/meta.ts +24 -0
  166. package/src/components/confirm-typed/confirm-typed.tsx +191 -0
  167. package/src/components/confirm-typed/index.ts +1 -0
  168. package/src/components/confirm-typed/meta.ts +22 -0
  169. package/src/components/cron-editor/cron-editor.tsx +676 -0
  170. package/src/components/cron-editor/cron-expression.ts +552 -0
  171. package/src/components/cron-editor/index.ts +23 -0
  172. package/src/components/cron-editor/meta.ts +23 -0
  173. package/src/components/diff-viewer/diff-model.ts +256 -0
  174. package/src/components/diff-viewer/diff-viewer.tsx +339 -0
  175. package/src/components/diff-viewer/index.ts +17 -0
  176. package/src/components/diff-viewer/meta.ts +20 -0
  177. package/src/components/dns-record/dns-record.tsx +315 -0
  178. package/src/components/dns-record/index.ts +7 -0
  179. package/src/components/dns-record/meta.ts +21 -0
  180. package/src/components/log-viewer/index.ts +21 -0
  181. package/src/components/log-viewer/log-stream.ts +176 -0
  182. package/src/components/log-viewer/log-viewer.tsx +424 -0
  183. package/src/components/log-viewer/meta.ts +21 -0
  184. package/src/components/permission-matrix/index.ts +14 -0
  185. package/src/components/permission-matrix/meta.ts +23 -0
  186. package/src/components/permission-matrix/permission-matrix.tsx +368 -0
  187. package/src/components/permission-matrix/permission-model.ts +120 -0
  188. package/src/components/secret-field/index.ts +6 -0
  189. package/src/components/secret-field/meta.ts +21 -0
  190. package/src/components/secret-field/secret-field.tsx +284 -0
  191. package/src/components/session-expiry/index.ts +9 -0
  192. package/src/components/session-expiry/meta.ts +23 -0
  193. package/src/components/session-expiry/session-expiry.tsx +321 -0
  194. package/src/components/shortcut-recorder/index.ts +19 -0
  195. package/src/components/shortcut-recorder/meta.ts +22 -0
  196. package/src/components/shortcut-recorder/shortcut-model.ts +204 -0
  197. package/src/components/shortcut-recorder/shortcut-recorder.tsx +336 -0
  198. package/src/components/sync-status/index.ts +9 -0
  199. package/src/components/sync-status/meta.ts +21 -0
  200. package/src/components/sync-status/sync-status.tsx +217 -0
  201. package/src/components/time-range-picker/index.ts +25 -0
  202. package/src/components/time-range-picker/meta.ts +22 -0
  203. package/src/components/time-range-picker/time-expression.ts +491 -0
  204. package/src/components/time-range-picker/time-range-picker.tsx +522 -0
  205. package/src/index.ts +14 -0
  206. package/src/registry/components.ts +28 -0
@@ -0,0 +1,493 @@
1
+ "use client";
2
+ import { focusRing } from "../../lib/styles.js";
3
+ import { cn } from "../../lib/utils.js";
4
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../select/select.js";
5
+ import { Input } from "../input/input.js";
6
+ import { CronExpressionError, describeCron, nextRuns, parseCron } from "./cron-expression.js";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import { createContext, useContext, useId, useMemo, useState, useSyncExternalStore } from "react";
9
+ //#region src/components/cron-editor/cron-editor.tsx
10
+ const CronContext = createContext(null);
11
+ function useCron(component) {
12
+ const context = useContext(CronContext);
13
+ if (!context) throw new Error(`${component} must be used inside <Cron>.`);
14
+ return context;
15
+ }
16
+ const subscribeToNothing = () => () => void 0;
17
+ let sharedClock = null;
18
+ const readSharedClock = () => sharedClock ??= /* @__PURE__ */ new Date();
19
+ const readNoClock = () => null;
20
+ function Cron({ className, value: valueProp, defaultValue = "0 9 * * 1", onValueChange, timeZone, locale, now, children, ...props }) {
21
+ const [uncontrolled, setUncontrolled] = useState(defaultValue);
22
+ const value = valueProp ?? uncontrolled;
23
+ const fallbackClock = useSyncExternalStore(subscribeToNothing, readSharedClock, readNoClock);
24
+ const clock = now ?? fallbackClock;
25
+ const schedule = useMemo(() => {
26
+ try {
27
+ return parseCron(value);
28
+ } catch {
29
+ return null;
30
+ }
31
+ }, [value]);
32
+ const context = useMemo(() => ({
33
+ value,
34
+ setValue: (expression) => {
35
+ setUncontrolled(expression);
36
+ onValueChange?.(expression);
37
+ },
38
+ schedule,
39
+ timeZone,
40
+ locale,
41
+ now: clock
42
+ }), [
43
+ value,
44
+ schedule,
45
+ timeZone,
46
+ locale,
47
+ clock,
48
+ onValueChange
49
+ ]);
50
+ return /* @__PURE__ */ jsx(CronContext.Provider, {
51
+ value: context,
52
+ children: /* @__PURE__ */ jsx("div", {
53
+ "data-slot": "cron-editor",
54
+ className: cn("flex flex-col gap-4", className),
55
+ ...props,
56
+ children
57
+ })
58
+ });
59
+ }
60
+ const FREQUENCIES = [
61
+ {
62
+ value: "minute",
63
+ label: "Every minute"
64
+ },
65
+ {
66
+ value: "hour",
67
+ label: "Every hour"
68
+ },
69
+ {
70
+ value: "day",
71
+ label: "Every day"
72
+ },
73
+ {
74
+ value: "week",
75
+ label: "Every week"
76
+ },
77
+ {
78
+ value: "month",
79
+ label: "Every month"
80
+ },
81
+ {
82
+ value: "year",
83
+ label: "Every year"
84
+ },
85
+ {
86
+ value: "custom",
87
+ label: "Custom"
88
+ }
89
+ ];
90
+ const isNumber = (token) => /^\d+$/.test(token);
91
+ const isStar = (token) => token === "*";
92
+ const stepOf = (token) => /^\*\/\d+$/.test(token) ? Number(token.slice(2)) : null;
93
+ /**
94
+ * Which builder shape an expression fits, if any. Anything the controls
95
+ * cannot express — every 15 minutes during office hours, say — is "custom",
96
+ * and the expression field becomes the editor rather than the builder quietly
97
+ * showing something else.
98
+ */
99
+ function classify(schedule) {
100
+ const state = {
101
+ frequency: "custom",
102
+ minuteStep: 1,
103
+ hourStep: 1,
104
+ minute: 0,
105
+ hour: 9,
106
+ days: [1],
107
+ dayOfMonth: 1,
108
+ month: 1
109
+ };
110
+ if (!schedule) return state;
111
+ const [m = "", h = "", dom = "", mon = "", dow = ""] = schedule.expression.split(" ");
112
+ if (isNumber(m)) state.minute = schedule.minutes[0] ?? 0;
113
+ if (isNumber(h)) state.hour = schedule.hours[0] ?? 0;
114
+ const restStar = isStar(dom) && isStar(mon) && isStar(dow);
115
+ const timeFixed = isNumber(m) && isNumber(h);
116
+ if ((isStar(m) || stepOf(m) !== null) && isStar(h) && restStar) {
117
+ state.frequency = "minute";
118
+ state.minuteStep = stepOf(m) ?? 1;
119
+ } else if (isNumber(m) && (isStar(h) || stepOf(h) !== null) && restStar) {
120
+ state.frequency = "hour";
121
+ state.hourStep = stepOf(h) ?? 1;
122
+ } else if (timeFixed && restStar) state.frequency = "day";
123
+ else if (timeFixed && isStar(dom) && isStar(mon) && !dow.includes("/")) {
124
+ state.frequency = "week";
125
+ state.days = schedule.daysOfWeek;
126
+ } else if (timeFixed && isNumber(dom) && isStar(mon) && isStar(dow)) {
127
+ state.frequency = "month";
128
+ state.dayOfMonth = schedule.daysOfMonth[0] ?? 1;
129
+ } else if (timeFixed && isNumber(dom) && schedule.months.length === 1 && !/[-,/]/.test(mon) && isStar(dow)) {
130
+ state.frequency = "year";
131
+ state.dayOfMonth = schedule.daysOfMonth[0] ?? 1;
132
+ state.month = schedule.months[0] ?? 1;
133
+ }
134
+ return state;
135
+ }
136
+ /** `1,2,3,4,5` written the way people write it: `1-5`. Runs of two stay a list. */
137
+ function compressDays(days) {
138
+ const sorted = [...new Set(days)].sort((a, b) => a - b);
139
+ const parts = [];
140
+ for (let i = 0; i < sorted.length;) {
141
+ let j = i;
142
+ while (j + 1 < sorted.length && sorted[j + 1] === (sorted[j] ?? 0) + 1) j += 1;
143
+ const first = String(sorted[i] ?? 0);
144
+ const last = String(sorted[j] ?? 0);
145
+ parts.push(j - i >= 2 ? `${first}-${last}` : j > i ? `${first},${last}` : first);
146
+ i = j + 1;
147
+ }
148
+ return parts.join(",");
149
+ }
150
+ function build(state, current) {
151
+ const { minute, hour, dayOfMonth, month } = state;
152
+ switch (state.frequency) {
153
+ case "minute": return `${state.minuteStep > 1 ? `*/${String(state.minuteStep)}` : "*"} * * * *`;
154
+ case "hour": return `${String(minute)} ${state.hourStep > 1 ? `*/${String(state.hourStep)}` : "*"} * * *`;
155
+ case "day": return `${String(minute)} ${String(hour)} * * *`;
156
+ case "week": return `${String(minute)} ${String(hour)} * * ${compressDays(state.days)}`;
157
+ case "month": return `${String(minute)} ${String(hour)} ${String(dayOfMonth)} * *`;
158
+ case "year": return `${String(minute)} ${String(hour)} ${String(dayOfMonth)} ${String(month)} *`;
159
+ case "custom": return current;
160
+ }
161
+ }
162
+ /** Monday first, Sunday last. */
163
+ const WEEK = [
164
+ 1,
165
+ 2,
166
+ 3,
167
+ 4,
168
+ 5,
169
+ 6,
170
+ 0
171
+ ];
172
+ const weekdayName = (day, locale, style) => new Intl.DateTimeFormat(locale, { weekday: style }).format(new Date(2026, 0, 4 + day));
173
+ const monthName = (month, locale) => new Intl.DateTimeFormat(locale, { month: "long" }).format(new Date(2026, month - 1, 1));
174
+ function CronBuilder({ className, ...props }) {
175
+ const { value, setValue, schedule, locale } = useCron("CronBuilder");
176
+ const uid = useId();
177
+ const state = useMemo(() => classify(schedule), [schedule]);
178
+ const update = (patch) => {
179
+ setValue(build({
180
+ ...state,
181
+ ...patch
182
+ }, value));
183
+ };
184
+ const numberField = (key, label, min, max, describedBy) => /* @__PURE__ */ jsx(NumberField, {
185
+ id: `${uid}-${key}`,
186
+ label,
187
+ min,
188
+ max,
189
+ value: state[key],
190
+ describedBy,
191
+ onCommit: (next) => {
192
+ update({ [key]: next });
193
+ }
194
+ });
195
+ const hasTime = [
196
+ "day",
197
+ "week",
198
+ "month",
199
+ "year"
200
+ ].includes(state.frequency);
201
+ const hasDayOfMonth = state.frequency === "month" || state.frequency === "year";
202
+ const dayHintId = `${uid}-day-hint`;
203
+ const daysHintId = `${uid}-days-hint`;
204
+ return /* @__PURE__ */ jsxs("div", {
205
+ "data-slot": "cron-builder",
206
+ "data-frequency": state.frequency,
207
+ className: cn("flex flex-wrap items-end gap-3", className),
208
+ ...props,
209
+ children: [
210
+ /* @__PURE__ */ jsxs("div", {
211
+ className: "flex flex-col gap-1.5",
212
+ children: [/* @__PURE__ */ jsx("label", {
213
+ htmlFor: `${uid}-frequency`,
214
+ className: "text-xs font-medium",
215
+ children: "Frequency"
216
+ }), /* @__PURE__ */ jsxs(Select, {
217
+ value: state.frequency,
218
+ onValueChange: (frequency) => {
219
+ update({ frequency });
220
+ },
221
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
222
+ id: `${uid}-frequency`,
223
+ className: "w-40",
224
+ children: /* @__PURE__ */ jsx(SelectValue, {})
225
+ }), /* @__PURE__ */ jsx(SelectContent, { children: FREQUENCIES.map((option) => /* @__PURE__ */ jsx(SelectItem, {
226
+ value: option.value,
227
+ children: option.label
228
+ }, option.value)) })]
229
+ })]
230
+ }),
231
+ state.frequency === "minute" ? numberField("minuteStep", "Every how many minutes", 1, 59) : null,
232
+ state.frequency === "hour" ? /* @__PURE__ */ jsxs(Fragment, { children: [numberField("hourStep", "Every how many hours", 1, 23), numberField("minute", "At minute", 0, 59)] }) : null,
233
+ hasTime ? /* @__PURE__ */ jsxs("div", {
234
+ className: "flex flex-col gap-1.5",
235
+ children: [/* @__PURE__ */ jsx("label", {
236
+ htmlFor: `${uid}-time`,
237
+ className: "text-xs font-medium",
238
+ children: "At"
239
+ }), /* @__PURE__ */ jsx(Input, {
240
+ id: `${uid}-time`,
241
+ type: "time",
242
+ inputSize: "sm",
243
+ className: "w-32",
244
+ value: `${String(state.hour).padStart(2, "0")}:${String(state.minute).padStart(2, "0")}`,
245
+ onChange: (event) => {
246
+ const [hour, minute] = event.target.value.split(":").map(Number);
247
+ if (hour === void 0 || minute === void 0 || Number.isNaN(hour) || Number.isNaN(minute)) return;
248
+ update({
249
+ hour,
250
+ minute
251
+ });
252
+ }
253
+ })]
254
+ }) : null,
255
+ state.frequency === "week" ? /* @__PURE__ */ jsxs("div", {
256
+ className: "flex flex-col gap-1.5",
257
+ children: [
258
+ /* @__PURE__ */ jsx("span", {
259
+ id: `${uid}-days-label`,
260
+ className: "text-xs font-medium",
261
+ children: "On"
262
+ }),
263
+ /* @__PURE__ */ jsx("div", {
264
+ role: "group",
265
+ "aria-labelledby": `${uid}-days-label`,
266
+ "aria-describedby": daysHintId,
267
+ className: "flex gap-1",
268
+ children: WEEK.map((day) => {
269
+ const pressed = state.days.includes(day);
270
+ const last = pressed && state.days.length === 1;
271
+ return /* @__PURE__ */ jsx("button", {
272
+ type: "button",
273
+ "aria-pressed": pressed,
274
+ "aria-label": weekdayName(day, locale, "long"),
275
+ className: cn("h-8 min-w-9 rounded-md border px-2 text-xs font-medium transition-colors", pressed ? "border-primary bg-primary text-primary-foreground" : "border-input bg-background hover:bg-accent hover:text-accent-foreground", focusRing),
276
+ onClick: () => {
277
+ if (last) return;
278
+ update({ days: pressed ? state.days.filter((d) => d !== day) : [...state.days, day] });
279
+ },
280
+ children: weekdayName(day, locale, "short")
281
+ }, day);
282
+ })
283
+ }),
284
+ /* @__PURE__ */ jsx("p", {
285
+ id: daysHintId,
286
+ className: "text-xs text-muted-foreground",
287
+ children: "Pick at least one day."
288
+ })
289
+ ]
290
+ }) : null,
291
+ hasDayOfMonth ? /* @__PURE__ */ jsxs("div", {
292
+ className: "flex flex-col gap-1.5",
293
+ children: [numberField("dayOfMonth", "On day", 1, 31, state.dayOfMonth > 28 ? dayHintId : void 0), state.dayOfMonth > 28 ? /* @__PURE__ */ jsx("p", {
294
+ id: dayHintId,
295
+ "data-slot": "cron-day-hint",
296
+ className: "text-xs text-warning",
297
+ children: "Months with fewer days skip this run."
298
+ }) : null]
299
+ }) : null,
300
+ state.frequency === "year" ? /* @__PURE__ */ jsxs("div", {
301
+ className: "flex flex-col gap-1.5",
302
+ children: [/* @__PURE__ */ jsx("label", {
303
+ htmlFor: `${uid}-month`,
304
+ className: "text-xs font-medium",
305
+ children: "In"
306
+ }), /* @__PURE__ */ jsxs(Select, {
307
+ value: String(state.month),
308
+ onValueChange: (month) => {
309
+ update({ month: Number(month) });
310
+ },
311
+ children: [/* @__PURE__ */ jsx(SelectTrigger, {
312
+ id: `${uid}-month`,
313
+ className: "w-36",
314
+ children: /* @__PURE__ */ jsx(SelectValue, {})
315
+ }), /* @__PURE__ */ jsx(SelectContent, { children: Array.from({ length: 12 }, (_, index) => index + 1).map((month) => /* @__PURE__ */ jsx(SelectItem, {
316
+ value: String(month),
317
+ children: monthName(month, locale)
318
+ }, month)) })]
319
+ })]
320
+ }) : null,
321
+ state.frequency === "custom" ? /* @__PURE__ */ jsx("p", {
322
+ className: "text-xs text-muted-foreground",
323
+ children: "This schedule needs the expression. Edit it below."
324
+ }) : null
325
+ ]
326
+ });
327
+ }
328
+ /**
329
+ * A number the builder owns, with the text the reader is typing kept apart
330
+ * from the value — so clearing the field to type a new number does not snap
331
+ * it back to the old one after the first keystroke.
332
+ */
333
+ function NumberField({ id, label, min, max, value, describedBy, onCommit }) {
334
+ const [draft, setDraft] = useState(String(value));
335
+ const [seen, setSeen] = useState(value);
336
+ if (seen !== value) {
337
+ setSeen(value);
338
+ setDraft(String(value));
339
+ }
340
+ return /* @__PURE__ */ jsxs("div", {
341
+ className: "flex flex-col gap-1.5",
342
+ children: [/* @__PURE__ */ jsx("label", {
343
+ htmlFor: id,
344
+ className: "text-xs font-medium",
345
+ children: label
346
+ }), /* @__PURE__ */ jsx(Input, {
347
+ id,
348
+ type: "number",
349
+ inputSize: "sm",
350
+ min,
351
+ max,
352
+ value: draft,
353
+ "aria-describedby": describedBy,
354
+ className: "w-24",
355
+ onChange: (event) => {
356
+ const text = event.target.value;
357
+ setDraft(text);
358
+ const next = Number(text);
359
+ if (text === "" || !Number.isInteger(next) || next < min || next > max) return;
360
+ if (next !== value) onCommit(next);
361
+ },
362
+ onBlur: () => {
363
+ if (draft !== String(value)) setDraft(String(value));
364
+ }
365
+ })]
366
+ });
367
+ }
368
+ /**
369
+ * The expression, editable, with its reading underneath — or the reason it is
370
+ * invalid. An invalid entry is never applied: the last valid schedule stands
371
+ * until the field says something that parses.
372
+ */
373
+ function CronExpression({ className, ...props }) {
374
+ const { value, setValue, locale } = useCron("CronExpression");
375
+ const id = useId();
376
+ const [draft, setDraft] = useState(value);
377
+ const [lastValue, setLastValue] = useState(value);
378
+ if (value !== lastValue) {
379
+ setLastValue(value);
380
+ setDraft(value);
381
+ }
382
+ const feedback = useMemo(() => {
383
+ try {
384
+ return {
385
+ text: describeCron(draft, { locale }),
386
+ error: null
387
+ };
388
+ } catch (thrown) {
389
+ return {
390
+ text: null,
391
+ error: thrown instanceof CronExpressionError ? thrown.message : "Invalid schedule."
392
+ };
393
+ }
394
+ }, [draft, locale]);
395
+ return /* @__PURE__ */ jsxs("div", {
396
+ "data-slot": "cron-expression",
397
+ className: cn("flex flex-col gap-1.5", className),
398
+ ...props,
399
+ children: [
400
+ /* @__PURE__ */ jsx("label", {
401
+ htmlFor: `${id}-input`,
402
+ className: "text-xs font-medium",
403
+ children: "Expression"
404
+ }),
405
+ /* @__PURE__ */ jsx(Input, {
406
+ id: `${id}-input`,
407
+ inputSize: "sm",
408
+ value: draft,
409
+ spellCheck: false,
410
+ autoComplete: "off",
411
+ "aria-invalid": feedback.error ? true : void 0,
412
+ "aria-describedby": `${id}-feedback`,
413
+ className: "font-mono",
414
+ onChange: (event) => {
415
+ const next = event.target.value;
416
+ setDraft(next);
417
+ try {
418
+ parseCron(next);
419
+ setValue(next.trim());
420
+ } catch {}
421
+ }
422
+ }),
423
+ /* @__PURE__ */ jsx("p", {
424
+ id: `${id}-feedback`,
425
+ "data-slot": "cron-feedback",
426
+ className: cn("text-xs", feedback.error ? "text-destructive" : "text-muted-foreground"),
427
+ children: feedback.error ?? feedback.text
428
+ })
429
+ ]
430
+ });
431
+ }
432
+ /**
433
+ * When it will fire, in the zone it is defined in. The zone is in the
434
+ * heading because a time with no zone is the classic scheduling mistake, and
435
+ * a schedule that never runs says so rather than showing an empty list.
436
+ */
437
+ function CronNextRuns({ className, count = 5, ...props }) {
438
+ const { value, schedule, timeZone, locale, now } = useCron("CronNextRuns");
439
+ const headingId = useId();
440
+ const runs = useMemo(() => now && schedule ? nextRuns(value, {
441
+ from: now,
442
+ count,
443
+ timeZone
444
+ }) : null, [
445
+ value,
446
+ schedule,
447
+ now,
448
+ count,
449
+ timeZone
450
+ ]);
451
+ if (!runs) return null;
452
+ const format = new Intl.DateTimeFormat(locale, {
453
+ dateStyle: "medium",
454
+ timeStyle: "short",
455
+ timeZone
456
+ });
457
+ return /* @__PURE__ */ jsxs("div", {
458
+ "data-slot": "cron-next-runs",
459
+ className: cn("flex flex-col gap-1.5", className),
460
+ ...props,
461
+ children: [/* @__PURE__ */ jsxs("p", {
462
+ id: headingId,
463
+ className: "text-xs font-medium",
464
+ children: [
465
+ "Next ",
466
+ count === 1 ? "run" : `${String(count)} runs`,
467
+ /* @__PURE__ */ jsxs("span", {
468
+ className: "font-normal text-muted-foreground",
469
+ children: [" · ", timeZone ?? "local time"]
470
+ })
471
+ ]
472
+ }), runs.length === 0 ? /* @__PURE__ */ jsx("p", {
473
+ "data-slot": "cron-never",
474
+ className: "text-xs text-destructive",
475
+ children: "Never runs: no day matches this schedule."
476
+ }) : /* @__PURE__ */ jsxs("ol", {
477
+ "aria-labelledby": headingId,
478
+ className: "m-0 flex list-none flex-col gap-0.5 p-0 text-sm",
479
+ children: [runs.map((run) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("time", {
480
+ dateTime: run.toISOString(),
481
+ className: "tabular-nums",
482
+ children: format.format(run)
483
+ }) }, run.toISOString())), runs.length < count ? /* @__PURE__ */ jsx("li", {
484
+ className: "text-xs text-muted-foreground",
485
+ children: "Then nothing within five years."
486
+ }) : null]
487
+ })]
488
+ });
489
+ }
490
+ //#endregion
491
+ export { Cron, CronBuilder, CronExpression, CronNextRuns };
492
+
493
+ //# sourceMappingURL=cron-editor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron-editor.js","names":[],"sources":["../../../src/components/cron-editor/cron-editor.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useId,\n useMemo,\n useState,\n useSyncExternalStore,\n type ComponentPropsWithRef,\n type ReactNode,\n} from \"react\";\n\nimport { Input } from \"@/components/input\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/select\";\nimport { focusRing } from \"@/lib/styles\";\nimport { cn } from \"@/lib/utils\";\n\nimport {\n CronExpressionError,\n describeCron,\n nextRuns,\n parseCron,\n type CronSchedule,\n} from \"./cron-expression\";\n\n/**\n * The \"run this nightly\" control.\n *\n * GitHub Actions, Vercel, Airflow, Sentry and every admin panel with a\n * schedule draws one by hand: a few frequency controls that write a cron\n * expression, the expression itself for anyone who can read it, the sentence\n * beside it for everyone else, and the next few runs so a mistake is visible\n * before it is saved. The packages that exist are bound to Ant Design or ship\n * their own stylesheet — see `cron-expression.ts` for the value model, which\n * is where the two mistakes every reimplementation makes actually live.\n *\n * Composed from the library's own Select and Input, and split into parts so\n * an app can show the expression and its reading without the builder, or the\n * next runs beside a saved schedule.\n */\n\ninterface CronContextValue {\n value: string;\n setValue: (expression: string) => void;\n schedule: CronSchedule | null;\n timeZone?: string;\n locale?: string;\n /** Null until the clock is known, which on a server render is never. */\n now: Date | null;\n}\n\nconst CronContext = createContext<CronContextValue | null>(null);\n\nfunction useCron(component: string): CronContextValue {\n const context = useContext(CronContext);\n if (!context) throw new Error(`${component} must be used inside <Cron>.`);\n return context;\n}\n\n/* The clock, for callers who did not supply one — the same arrangement as\n time-range-picker: a null server snapshot, so nothing clock-dependent is\n rendered until the real instant arrives after hydration. */\nconst subscribeToNothing = () => () => undefined;\nlet sharedClock: Date | null = null;\nconst readSharedClock = () => (sharedClock ??= new Date());\nconst readNoClock = () => null;\n\nexport interface CronProps extends Omit<\n ComponentPropsWithRef<\"div\">,\n \"defaultValue\" | \"onChange\" | \"children\"\n> {\n /** The expression. Controlled. */\n value?: string;\n defaultValue?: string;\n onValueChange?: (expression: string) => void;\n /** IANA zone the expression's times are in. Defaults to the runtime zone. */\n timeZone?: string;\n locale?: string;\n /** The instant next runs are counted from. Pass one that changes when you refresh. */\n now?: Date;\n children: ReactNode;\n}\n\nexport function Cron({\n className,\n value: valueProp,\n defaultValue = \"0 9 * * 1\",\n onValueChange,\n timeZone,\n locale,\n now,\n children,\n ...props\n}: CronProps) {\n const [uncontrolled, setUncontrolled] = useState(defaultValue);\n const value = valueProp ?? uncontrolled;\n\n const fallbackClock = useSyncExternalStore(subscribeToNothing, readSharedClock, readNoClock);\n const clock = now ?? fallbackClock;\n\n const schedule = useMemo(() => {\n try {\n return parseCron(value);\n } catch {\n return null;\n }\n }, [value]);\n\n const context = useMemo<CronContextValue>(\n () => ({\n value,\n setValue: (expression) => {\n setUncontrolled(expression);\n onValueChange?.(expression);\n },\n schedule,\n timeZone,\n locale,\n now: clock,\n }),\n [value, schedule, timeZone, locale, clock, onValueChange],\n );\n\n return (\n <CronContext.Provider value={context}>\n <div data-slot=\"cron-editor\" className={cn(\"flex flex-col gap-4\", className)} {...props}>\n {children}\n </div>\n </CronContext.Provider>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Builder */\n/* ------------------------------------------------------------------ */\n\ntype Frequency = \"minute\" | \"hour\" | \"day\" | \"week\" | \"month\" | \"year\" | \"custom\";\n\nconst FREQUENCIES: { value: Frequency; label: string }[] = [\n { value: \"minute\", label: \"Every minute\" },\n { value: \"hour\", label: \"Every hour\" },\n { value: \"day\", label: \"Every day\" },\n { value: \"week\", label: \"Every week\" },\n { value: \"month\", label: \"Every month\" },\n { value: \"year\", label: \"Every year\" },\n { value: \"custom\", label: \"Custom\" },\n];\n\ninterface BuilderState {\n frequency: Frequency;\n minuteStep: number;\n hourStep: number;\n minute: number;\n hour: number;\n /** 0 = Sunday. */\n days: number[];\n dayOfMonth: number;\n month: number;\n}\n\nconst isNumber = (token: string) => /^\\d+$/.test(token);\nconst isStar = (token: string) => token === \"*\";\nconst stepOf = (token: string) => (/^\\*\\/\\d+$/.test(token) ? Number(token.slice(2)) : null);\n\n/**\n * Which builder shape an expression fits, if any. Anything the controls\n * cannot express — every 15 minutes during office hours, say — is \"custom\",\n * and the expression field becomes the editor rather than the builder quietly\n * showing something else.\n */\nfunction classify(schedule: CronSchedule | null): BuilderState {\n const state: BuilderState = {\n frequency: \"custom\",\n minuteStep: 1,\n hourStep: 1,\n minute: 0,\n hour: 9,\n days: [1],\n dayOfMonth: 1,\n month: 1,\n };\n if (!schedule) return state;\n\n const [m = \"\", h = \"\", dom = \"\", mon = \"\", dow = \"\"] = schedule.expression.split(\" \");\n if (isNumber(m)) state.minute = schedule.minutes[0] ?? 0;\n if (isNumber(h)) state.hour = schedule.hours[0] ?? 0;\n\n const restStar = isStar(dom) && isStar(mon) && isStar(dow);\n const timeFixed = isNumber(m) && isNumber(h);\n\n if ((isStar(m) || stepOf(m) !== null) && isStar(h) && restStar) {\n state.frequency = \"minute\";\n state.minuteStep = stepOf(m) ?? 1;\n } else if (isNumber(m) && (isStar(h) || stepOf(h) !== null) && restStar) {\n state.frequency = \"hour\";\n state.hourStep = stepOf(h) ?? 1;\n } else if (timeFixed && restStar) {\n state.frequency = \"day\";\n } else if (timeFixed && isStar(dom) && isStar(mon) && !dow.includes(\"/\")) {\n state.frequency = \"week\";\n state.days = schedule.daysOfWeek;\n } else if (timeFixed && isNumber(dom) && isStar(mon) && isStar(dow)) {\n state.frequency = \"month\";\n state.dayOfMonth = schedule.daysOfMonth[0] ?? 1;\n } else if (\n timeFixed &&\n isNumber(dom) &&\n schedule.months.length === 1 &&\n !/[-,/]/.test(mon) &&\n isStar(dow)\n ) {\n state.frequency = \"year\";\n state.dayOfMonth = schedule.daysOfMonth[0] ?? 1;\n state.month = schedule.months[0] ?? 1;\n }\n return state;\n}\n\n/** `1,2,3,4,5` written the way people write it: `1-5`. Runs of two stay a list. */\nfunction compressDays(days: number[]): string {\n const sorted = [...new Set(days)].sort((a, b) => a - b);\n const parts: string[] = [];\n for (let i = 0; i < sorted.length;) {\n let j = i;\n while (j + 1 < sorted.length && sorted[j + 1] === (sorted[j] ?? 0) + 1) j += 1;\n const first = String(sorted[i] ?? 0);\n const last = String(sorted[j] ?? 0);\n parts.push(j - i >= 2 ? `${first}-${last}` : j > i ? `${first},${last}` : first);\n i = j + 1;\n }\n return parts.join(\",\");\n}\n\nfunction build(state: BuilderState, current: string): string {\n const { minute, hour, dayOfMonth, month } = state;\n switch (state.frequency) {\n case \"minute\":\n return `${state.minuteStep > 1 ? `*/${String(state.minuteStep)}` : \"*\"} * * * *`;\n case \"hour\":\n return `${String(minute)} ${state.hourStep > 1 ? `*/${String(state.hourStep)}` : \"*\"} * * *`;\n case \"day\":\n return `${String(minute)} ${String(hour)} * * *`;\n case \"week\":\n return `${String(minute)} ${String(hour)} * * ${compressDays(state.days)}`;\n case \"month\":\n return `${String(minute)} ${String(hour)} ${String(dayOfMonth)} * *`;\n case \"year\":\n return `${String(minute)} ${String(hour)} ${String(dayOfMonth)} ${String(month)} *`;\n case \"custom\":\n return current;\n }\n}\n\n/** Monday first, Sunday last. */\nconst WEEK = [1, 2, 3, 4, 5, 6, 0];\n// 4 January 2026 is a Sunday; 1 January 2026 is in month 1.\nconst weekdayName = (day: number, locale: string | undefined, style: \"long\" | \"short\") =>\n new Intl.DateTimeFormat(locale, { weekday: style }).format(new Date(2026, 0, 4 + day));\nconst monthName = (month: number, locale: string | undefined) =>\n new Intl.DateTimeFormat(locale, { month: \"long\" }).format(new Date(2026, month - 1, 1));\n\nexport type CronBuilderProps = Omit<ComponentPropsWithRef<\"div\">, \"children\">;\n\nexport function CronBuilder({ className, ...props }: CronBuilderProps) {\n const { value, setValue, schedule, locale } = useCron(\"CronBuilder\");\n const uid = useId();\n const state = useMemo(() => classify(schedule), [schedule]);\n\n const update = (patch: Partial<BuilderState>) => {\n setValue(build({ ...state, ...patch }, value));\n };\n\n const numberField = (\n key: \"minuteStep\" | \"hourStep\" | \"minute\" | \"dayOfMonth\",\n label: string,\n min: number,\n max: number,\n describedBy?: string,\n ) => (\n <NumberField\n id={`${uid}-${key}`}\n label={label}\n min={min}\n max={max}\n value={state[key]}\n describedBy={describedBy}\n onCommit={(next) => {\n update({ [key]: next });\n }}\n />\n );\n\n const hasTime = [\"day\", \"week\", \"month\", \"year\"].includes(state.frequency);\n const hasDayOfMonth = state.frequency === \"month\" || state.frequency === \"year\";\n const dayHintId = `${uid}-day-hint`;\n const daysHintId = `${uid}-days-hint`;\n\n return (\n <div\n data-slot=\"cron-builder\"\n data-frequency={state.frequency}\n className={cn(\"flex flex-wrap items-end gap-3\", className)}\n {...props}\n >\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor={`${uid}-frequency`} className=\"text-xs font-medium\">\n Frequency\n </label>\n <Select\n value={state.frequency}\n onValueChange={(frequency) => {\n update({ frequency: frequency as Frequency });\n }}\n >\n <SelectTrigger id={`${uid}-frequency`} className=\"w-40\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {FREQUENCIES.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {state.frequency === \"minute\"\n ? numberField(\"minuteStep\", \"Every how many minutes\", 1, 59)\n : null}\n\n {state.frequency === \"hour\" ? (\n <>\n {numberField(\"hourStep\", \"Every how many hours\", 1, 23)}\n {numberField(\"minute\", \"At minute\", 0, 59)}\n </>\n ) : null}\n\n {hasTime ? (\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor={`${uid}-time`} className=\"text-xs font-medium\">\n At\n </label>\n <Input\n id={`${uid}-time`}\n type=\"time\"\n inputSize=\"sm\"\n className=\"w-32\"\n value={`${String(state.hour).padStart(2, \"0\")}:${String(state.minute).padStart(2, \"0\")}`}\n onChange={(event) => {\n const [hour, minute] = event.target.value.split(\":\").map(Number);\n if (\n hour === undefined ||\n minute === undefined ||\n Number.isNaN(hour) ||\n Number.isNaN(minute)\n ) {\n return;\n }\n update({ hour, minute });\n }}\n />\n </div>\n ) : null}\n\n {state.frequency === \"week\" ? (\n <div className=\"flex flex-col gap-1.5\">\n <span id={`${uid}-days-label`} className=\"text-xs font-medium\">\n On\n </span>\n <div\n role=\"group\"\n aria-labelledby={`${uid}-days-label`}\n aria-describedby={daysHintId}\n className=\"flex gap-1\"\n >\n {WEEK.map((day) => {\n const pressed = state.days.includes(day);\n const last = pressed && state.days.length === 1;\n return (\n <button\n key={day}\n type=\"button\"\n aria-pressed={pressed}\n aria-label={weekdayName(day, locale, \"long\")}\n className={cn(\n \"h-8 min-w-9 rounded-md border px-2 text-xs font-medium transition-colors\",\n pressed\n ? \"border-primary bg-primary text-primary-foreground\"\n : \"border-input bg-background hover:bg-accent hover:text-accent-foreground\",\n focusRing,\n )}\n onClick={() => {\n // The last day stays: a week schedule with no day is not a\n // schedule. The group's description says so, so the refusal\n // is not silent.\n if (last) return;\n update({\n days: pressed\n ? state.days.filter((d) => d !== day)\n : [...state.days, day],\n });\n }}\n >\n {weekdayName(day, locale, \"short\")}\n </button>\n );\n })}\n </div>\n <p id={daysHintId} className=\"text-xs text-muted-foreground\">\n Pick at least one day.\n </p>\n </div>\n ) : null}\n\n {hasDayOfMonth ? (\n <div className=\"flex flex-col gap-1.5\">\n {numberField(\n \"dayOfMonth\",\n \"On day\",\n 1,\n 31,\n state.dayOfMonth > 28 ? dayHintId : undefined,\n )}\n {state.dayOfMonth > 28 ? (\n <p id={dayHintId} data-slot=\"cron-day-hint\" className=\"text-xs text-warning\">\n Months with fewer days skip this run.\n </p>\n ) : null}\n </div>\n ) : null}\n\n {state.frequency === \"year\" ? (\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor={`${uid}-month`} className=\"text-xs font-medium\">\n In\n </label>\n <Select\n value={String(state.month)}\n onValueChange={(month) => {\n update({ month: Number(month) });\n }}\n >\n <SelectTrigger id={`${uid}-month`} className=\"w-36\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Array.from({ length: 12 }, (_, index) => index + 1).map((month) => (\n <SelectItem key={month} value={String(month)}>\n {monthName(month, locale)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ) : null}\n\n {state.frequency === \"custom\" ? (\n <p className=\"text-xs text-muted-foreground\">\n This schedule needs the expression. Edit it below.\n </p>\n ) : null}\n </div>\n );\n}\n\n/**\n * A number the builder owns, with the text the reader is typing kept apart\n * from the value — so clearing the field to type a new number does not snap\n * it back to the old one after the first keystroke.\n */\nfunction NumberField({\n id,\n label,\n min,\n max,\n value,\n describedBy,\n onCommit,\n}: {\n id: string;\n label: string;\n min: number;\n max: number;\n value: number;\n describedBy?: string;\n onCommit: (value: number) => void;\n}) {\n const [draft, setDraft] = useState(String(value));\n const [seen, setSeen] = useState(value);\n if (seen !== value) {\n setSeen(value);\n setDraft(String(value));\n }\n\n return (\n <div className=\"flex flex-col gap-1.5\">\n <label htmlFor={id} className=\"text-xs font-medium\">\n {label}\n </label>\n <Input\n id={id}\n type=\"number\"\n inputSize=\"sm\"\n min={min}\n max={max}\n value={draft}\n aria-describedby={describedBy}\n className=\"w-24\"\n onChange={(event) => {\n const text = event.target.value;\n setDraft(text);\n const next = Number(text);\n if (text === \"\" || !Number.isInteger(next) || next < min || next > max) return;\n if (next !== value) onCommit(next);\n }}\n onBlur={() => {\n // Whatever was left half-typed goes back to the value in force.\n if (draft !== String(value)) setDraft(String(value));\n }}\n />\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Expression */\n/* ------------------------------------------------------------------ */\n\nexport type CronExpressionProps = Omit<ComponentPropsWithRef<\"div\">, \"children\">;\n\n/**\n * The expression, editable, with its reading underneath — or the reason it is\n * invalid. An invalid entry is never applied: the last valid schedule stands\n * until the field says something that parses.\n */\nexport function CronExpression({ className, ...props }: CronExpressionProps) {\n const { value, setValue, locale } = useCron(\"CronExpression\");\n const id = useId();\n\n const [draft, setDraft] = useState(value);\n // The value can change under the field — the builder, or the consumer.\n const [lastValue, setLastValue] = useState(value);\n if (value !== lastValue) {\n setLastValue(value);\n setDraft(value);\n }\n\n const feedback = useMemo(() => {\n try {\n return { text: describeCron(draft, { locale }), error: null as string | null };\n } catch (thrown) {\n return {\n text: null,\n error: thrown instanceof CronExpressionError ? thrown.message : \"Invalid schedule.\",\n };\n }\n }, [draft, locale]);\n\n return (\n <div\n data-slot=\"cron-expression\"\n className={cn(\"flex flex-col gap-1.5\", className)}\n {...props}\n >\n <label htmlFor={`${id}-input`} className=\"text-xs font-medium\">\n Expression\n </label>\n <Input\n id={`${id}-input`}\n inputSize=\"sm\"\n value={draft}\n spellCheck={false}\n autoComplete=\"off\"\n aria-invalid={feedback.error ? true : undefined}\n aria-describedby={`${id}-feedback`}\n className=\"font-mono\"\n onChange={(event) => {\n const next = event.target.value;\n setDraft(next);\n try {\n parseCron(next);\n setValue(next.trim());\n } catch {\n // Said in the feedback; the last valid schedule stands.\n }\n }}\n />\n {/* One element for both, so a reader hears the error replace the\n reading rather than both at once. */}\n <p\n id={`${id}-feedback`}\n data-slot=\"cron-feedback\"\n className={cn(\"text-xs\", feedback.error ? \"text-destructive\" : \"text-muted-foreground\")}\n >\n {feedback.error ?? feedback.text}\n </p>\n </div>\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Next runs */\n/* ------------------------------------------------------------------ */\n\nexport interface CronNextRunsProps extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n count?: number;\n}\n\n/**\n * When it will fire, in the zone it is defined in. The zone is in the\n * heading because a time with no zone is the classic scheduling mistake, and\n * a schedule that never runs says so rather than showing an empty list.\n */\nexport function CronNextRuns({ className, count = 5, ...props }: CronNextRunsProps) {\n const { value, schedule, timeZone, locale, now } = useCron(\"CronNextRuns\");\n const headingId = useId();\n\n const runs = useMemo(\n () => (now && schedule ? nextRuns(value, { from: now, count, timeZone }) : null),\n [value, schedule, now, count, timeZone],\n );\n\n // Nothing clock-dependent before the clock is known.\n if (!runs) return null;\n\n const format = new Intl.DateTimeFormat(locale, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n timeZone,\n });\n\n return (\n <div\n data-slot=\"cron-next-runs\"\n className={cn(\"flex flex-col gap-1.5\", className)}\n {...props}\n >\n <p id={headingId} className=\"text-xs font-medium\">\n Next {count === 1 ? \"run\" : `${String(count)} runs`}\n <span className=\"font-normal text-muted-foreground\">\n {\" · \"}\n {timeZone ?? \"local time\"}\n </span>\n </p>\n {runs.length === 0 ? (\n <p data-slot=\"cron-never\" className=\"text-xs text-destructive\">\n Never runs: no day matches this schedule.\n </p>\n ) : (\n <ol\n aria-labelledby={headingId}\n className=\"m-0 flex list-none flex-col gap-0.5 p-0 text-sm\"\n >\n {runs.map((run) => (\n <li key={run.toISOString()}>\n <time dateTime={run.toISOString()} className=\"tabular-nums\">\n {format.format(run)}\n </time>\n </li>\n ))}\n {runs.length < count ? (\n <li className=\"text-xs text-muted-foreground\">Then nothing within five years.</li>\n ) : null}\n </ol>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AA0DA,MAAM,cAAc,cAAuC,IAAI;AAE/D,SAAS,QAAQ,WAAqC;CACpD,MAAM,UAAU,WAAW,WAAW;CACtC,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,GAAG,UAAU,6BAA6B;CACxE,OAAO;AACT;AAKA,MAAM,iCAAiC,KAAA;AACvC,IAAI,cAA2B;AAC/B,MAAM,wBAAyB,gCAAgB,IAAI,KAAK;AACxD,MAAM,oBAAoB;AAkB1B,SAAgB,KAAK,EACnB,WACA,OAAO,WACP,eAAe,aACf,eACA,UACA,QACA,KACA,UACA,GAAG,SACS;CACZ,MAAM,CAAC,cAAc,mBAAmB,SAAS,YAAY;CAC7D,MAAM,QAAQ,aAAa;CAE3B,MAAM,gBAAgB,qBAAqB,oBAAoB,iBAAiB,WAAW;CAC3F,MAAM,QAAQ,OAAO;CAErB,MAAM,WAAW,cAAc;EAC7B,IAAI;GACF,OAAO,UAAU,KAAK;EACxB,QAAQ;GACN,OAAO;EACT;CACF,GAAG,CAAC,KAAK,CAAC;CAEV,MAAM,UAAU,eACP;EACL;EACA,WAAW,eAAe;GACxB,gBAAgB,UAAU;GAC1B,gBAAgB,UAAU;EAC5B;EACA;EACA;EACA;EACA,KAAK;CACP,IACA;EAAC;EAAO;EAAU;EAAU;EAAQ;EAAO;CAAa,CAC1D;CAEA,OACE,oBAAC,YAAY,UAAb;EAAsB,OAAO;EAC3B,UAAA,oBAAC,OAAD;GAAK,aAAU;GAAc,WAAW,GAAG,uBAAuB,SAAS;GAAG,GAAI;GAC/E;EACE,CAAA;CACe,CAAA;AAE1B;AAQA,MAAM,cAAqD;CACzD;EAAE,OAAO;EAAU,OAAO;CAAe;CACzC;EAAE,OAAO;EAAQ,OAAO;CAAa;CACrC;EAAE,OAAO;EAAO,OAAO;CAAY;CACnC;EAAE,OAAO;EAAQ,OAAO;CAAa;CACrC;EAAE,OAAO;EAAS,OAAO;CAAc;CACvC;EAAE,OAAO;EAAQ,OAAO;CAAa;CACrC;EAAE,OAAO;EAAU,OAAO;CAAS;AACrC;AAcA,MAAM,YAAY,UAAkB,QAAQ,KAAK,KAAK;AACtD,MAAM,UAAU,UAAkB,UAAU;AAC5C,MAAM,UAAU,UAAmB,YAAY,KAAK,KAAK,IAAI,OAAO,MAAM,MAAM,CAAC,CAAC,IAAI;;;;;;;AAQtF,SAAS,SAAS,UAA6C;CAC7D,MAAM,QAAsB;EAC1B,WAAW;EACX,YAAY;EACZ,UAAU;EACV,QAAQ;EACR,MAAM;EACN,MAAM,CAAC,CAAC;EACR,YAAY;EACZ,OAAO;CACT;CACA,IAAI,CAAC,UAAU,OAAO;CAEtB,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM,IAAI,MAAM,MAAM,SAAS,WAAW,MAAM,GAAG;CACpF,IAAI,SAAS,CAAC,GAAG,MAAM,SAAS,SAAS,QAAQ,MAAM;CACvD,IAAI,SAAS,CAAC,GAAG,MAAM,OAAO,SAAS,MAAM,MAAM;CAEnD,MAAM,WAAW,OAAO,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO,GAAG;CACzD,MAAM,YAAY,SAAS,CAAC,KAAK,SAAS,CAAC;CAE3C,KAAK,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,SAAS,OAAO,CAAC,KAAK,UAAU;EAC9D,MAAM,YAAY;EAClB,MAAM,aAAa,OAAO,CAAC,KAAK;CAClC,OAAO,IAAI,SAAS,CAAC,MAAM,OAAO,CAAC,KAAK,OAAO,CAAC,MAAM,SAAS,UAAU;EACvE,MAAM,YAAY;EAClB,MAAM,WAAW,OAAO,CAAC,KAAK;CAChC,OAAO,IAAI,aAAa,UACtB,MAAM,YAAY;MACb,IAAI,aAAa,OAAO,GAAG,KAAK,OAAO,GAAG,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG;EACxE,MAAM,YAAY;EAClB,MAAM,OAAO,SAAS;CACxB,OAAO,IAAI,aAAa,SAAS,GAAG,KAAK,OAAO,GAAG,KAAK,OAAO,GAAG,GAAG;EACnE,MAAM,YAAY;EAClB,MAAM,aAAa,SAAS,YAAY,MAAM;CAChD,OAAO,IACL,aACA,SAAS,GAAG,KACZ,SAAS,OAAO,WAAW,KAC3B,CAAC,QAAQ,KAAK,GAAG,KACjB,OAAO,GAAG,GACV;EACA,MAAM,YAAY;EAClB,MAAM,aAAa,SAAS,YAAY,MAAM;EAC9C,MAAM,QAAQ,SAAS,OAAO,MAAM;CACtC;CACA,OAAO;AACT;;AAGA,SAAS,aAAa,MAAwB;CAC5C,MAAM,SAAS,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CACtD,MAAM,QAAkB,CAAC;CACzB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,SAAS;EAClC,IAAI,IAAI;EACR,OAAO,IAAI,IAAI,OAAO,UAAU,OAAO,IAAI,QAAQ,OAAO,MAAM,KAAK,GAAG,KAAK;EAC7E,MAAM,QAAQ,OAAO,OAAO,MAAM,CAAC;EACnC,MAAM,OAAO,OAAO,OAAO,MAAM,CAAC;EAClC,MAAM,KAAK,IAAI,KAAK,IAAI,GAAG,MAAM,GAAG,SAAS,IAAI,IAAI,GAAG,MAAM,GAAG,SAAS,KAAK;EAC/E,IAAI,IAAI;CACV;CACA,OAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAAS,MAAM,OAAqB,SAAyB;CAC3D,MAAM,EAAE,QAAQ,MAAM,YAAY,UAAU;CAC5C,QAAQ,MAAM,WAAd;EACE,KAAK,UACH,OAAO,GAAG,MAAM,aAAa,IAAI,KAAK,OAAO,MAAM,UAAU,MAAM,IAAI;EACzE,KAAK,QACH,OAAO,GAAG,OAAO,MAAM,EAAE,GAAG,MAAM,WAAW,IAAI,KAAK,OAAO,MAAM,QAAQ,MAAM,IAAI;EACvF,KAAK,OACH,OAAO,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,IAAI,EAAE;EAC3C,KAAK,QACH,OAAO,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,IAAI,EAAE,OAAO,aAAa,MAAM,IAAI;EACzE,KAAK,SACH,OAAO,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,IAAI,EAAE,GAAG,OAAO,UAAU,EAAE;EACjE,KAAK,QACH,OAAO,GAAG,OAAO,MAAM,EAAE,GAAG,OAAO,IAAI,EAAE,GAAG,OAAO,UAAU,EAAE,GAAG,OAAO,KAAK,EAAE;EAClF,KAAK,UACH,OAAO;CACX;AACF;;AAGA,MAAM,OAAO;CAAC;CAAG;CAAG;CAAG;CAAG;CAAG;CAAG;AAAC;AAEjC,MAAM,eAAe,KAAa,QAA4B,UAC5D,IAAI,KAAK,eAAe,QAAQ,EAAE,SAAS,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC;AACvF,MAAM,aAAa,OAAe,WAChC,IAAI,KAAK,eAAe,QAAQ,EAAE,OAAO,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,KAAK,MAAM,QAAQ,GAAG,CAAC,CAAC;AAIxF,SAAgB,YAAY,EAAE,WAAW,GAAG,SAA2B;CACrE,MAAM,EAAE,OAAO,UAAU,UAAU,WAAW,QAAQ,aAAa;CACnE,MAAM,MAAM,MAAM;CAClB,MAAM,QAAQ,cAAc,SAAS,QAAQ,GAAG,CAAC,QAAQ,CAAC;CAE1D,MAAM,UAAU,UAAiC;EAC/C,SAAS,MAAM;GAAE,GAAG;GAAO,GAAG;EAAM,GAAG,KAAK,CAAC;CAC/C;CAEA,MAAM,eACJ,KACA,OACA,KACA,KACA,gBAEA,oBAAC,aAAD;EACE,IAAI,GAAG,IAAI,GAAG;EACP;EACF;EACA;EACL,OAAO,MAAM;EACA;EACb,WAAW,SAAS;GAClB,OAAO,GAAG,MAAM,KAAK,CAAC;EACxB;CACD,CAAA;CAGH,MAAM,UAAU;EAAC;EAAO;EAAQ;EAAS;CAAM,CAAC,CAAC,SAAS,MAAM,SAAS;CACzE,MAAM,gBAAgB,MAAM,cAAc,WAAW,MAAM,cAAc;CACzE,MAAM,YAAY,GAAG,IAAI;CACzB,MAAM,aAAa,GAAG,IAAI;CAE1B,OACE,qBAAC,OAAD;EACE,aAAU;EACV,kBAAgB,MAAM;EACtB,WAAW,GAAG,kCAAkC,SAAS;EACzD,GAAI;EAJN,UAAA;GAME,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,SAAD;KAAO,SAAS,GAAG,IAAI;KAAa,WAAU;KAAsB,UAAA;IAE7D,CAAA,GACP,qBAAC,QAAD;KACE,OAAO,MAAM;KACb,gBAAgB,cAAc;MAC5B,OAAO,EAAa,UAAuB,CAAC;KAC9C;KAJF,UAAA,CAME,oBAAC,eAAD;MAAe,IAAI,GAAG,IAAI;MAAa,WAAU;MAC/C,UAAA,oBAAC,aAAD,CAAc,CAAA;KACD,CAAA,GACf,oBAAC,eAAD,EAAA,UACG,YAAY,KAAK,WAChB,oBAAC,YAAD;MAA+B,OAAO,OAAO;MAC1C,UAAA,OAAO;KACE,GAFK,OAAO,KAEZ,CACb,EACY,CAAA,CACT;IACL,CAAA,CAAA;;GAEJ,MAAM,cAAc,WACjB,YAAY,cAAc,0BAA0B,GAAG,EAAE,IACzD;GAEH,MAAM,cAAc,SACnB,qBAAA,UAAA,EAAA,UAAA,CACG,YAAY,YAAY,wBAAwB,GAAG,EAAE,GACrD,YAAY,UAAU,aAAa,GAAG,EAAE,CACzC,EAAA,CAAA,IACA;GAEH,UACC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,SAAD;KAAO,SAAS,GAAG,IAAI;KAAQ,WAAU;KAAsB,UAAA;IAExD,CAAA,GACP,oBAAC,OAAD;KACE,IAAI,GAAG,IAAI;KACX,MAAK;KACL,WAAU;KACV,WAAU;KACV,OAAO,GAAG,OAAO,MAAM,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,OAAO,MAAM,MAAM,CAAC,CAAC,SAAS,GAAG,GAAG;KACrF,WAAW,UAAU;MACnB,MAAM,CAAC,MAAM,UAAU,MAAM,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;MAC/D,IACE,SAAS,KAAA,KACT,WAAW,KAAA,KACX,OAAO,MAAM,IAAI,KACjB,OAAO,MAAM,MAAM,GAEnB;MAEF,OAAO;OAAE;OAAM;MAAO,CAAC;KACzB;IACD,CAAA,CACE;GACH,CAAA,IAAA;GAEH,MAAM,cAAc,SACnB,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,QAAD;MAAM,IAAI,GAAG,IAAI;MAAc,WAAU;MAAsB,UAAA;KAEzD,CAAA;KACN,oBAAC,OAAD;MACE,MAAK;MACL,mBAAiB,GAAG,IAAI;MACxB,oBAAkB;MAClB,WAAU;MAET,UAAA,KAAK,KAAK,QAAQ;OACjB,MAAM,UAAU,MAAM,KAAK,SAAS,GAAG;OACvC,MAAM,OAAO,WAAW,MAAM,KAAK,WAAW;OAC9C,OACE,oBAAC,UAAD;QAEE,MAAK;QACL,gBAAc;QACd,cAAY,YAAY,KAAK,QAAQ,MAAM;QAC3C,WAAW,GACT,4EACA,UACI,sDACA,2EACJ,SACF;QACA,eAAe;SAIb,IAAI,MAAM;SACV,OAAO,EACL,MAAM,UACF,MAAM,KAAK,QAAQ,MAAM,MAAM,GAAG,IAClC,CAAC,GAAG,MAAM,MAAM,GAAG,EACzB,CAAC;QACH;QAEC,UAAA,YAAY,KAAK,QAAQ,OAAO;OAC3B,GAxBD,GAwBC;MAEZ,CAAC;KACE,CAAA;KACL,oBAAC,KAAD;MAAG,IAAI;MAAY,WAAU;MAAgC,UAAA;KAE1D,CAAA;IACA;GACH,CAAA,IAAA;GAEH,gBACC,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACG,YACC,cACA,UACA,GACA,IACA,MAAM,aAAa,KAAK,YAAY,KAAA,CACtC,GACC,MAAM,aAAa,KAClB,oBAAC,KAAD;KAAG,IAAI;KAAW,aAAU;KAAgB,WAAU;KAAuB,UAAA;IAE1E,CAAA,IACD,IACD;GACH,CAAA,IAAA;GAEH,MAAM,cAAc,SACnB,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,oBAAC,SAAD;KAAO,SAAS,GAAG,IAAI;KAAS,WAAU;KAAsB,UAAA;IAEzD,CAAA,GACP,qBAAC,QAAD;KACE,OAAO,OAAO,MAAM,KAAK;KACzB,gBAAgB,UAAU;MACxB,OAAO,EAAE,OAAO,OAAO,KAAK,EAAE,CAAC;KACjC;KAJF,UAAA,CAME,oBAAC,eAAD;MAAe,IAAI,GAAG,IAAI;MAAS,WAAU;MAC3C,UAAA,oBAAC,aAAD,CAAc,CAAA;KACD,CAAA,GACf,oBAAC,eAAD,EAAA,UACG,MAAM,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,UAAU,QAAQ,CAAC,CAAC,CAAC,KAAK,UACxD,oBAAC,YAAD;MAAwB,OAAO,OAAO,KAAK;MACxC,UAAA,UAAU,OAAO,MAAM;KACd,GAFK,KAEL,CACb,EACY,CAAA,CACT;IACL,CAAA,CAAA;GACH,CAAA,IAAA;GAEH,MAAM,cAAc,WACnB,oBAAC,KAAD;IAAG,WAAU;IAAgC,UAAA;GAE1C,CAAA,IACD;EACD;;AAET;;;;;;AAOA,SAAS,YAAY,EACnB,IACA,OACA,KACA,KACA,OACA,aACA,YASC;CACD,MAAM,CAAC,OAAO,YAAY,SAAS,OAAO,KAAK,CAAC;CAChD,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,IAAI,SAAS,OAAO;EAClB,QAAQ,KAAK;EACb,SAAS,OAAO,KAAK,CAAC;CACxB;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,SAAD;GAAO,SAAS;GAAI,WAAU;GAC3B,UAAA;EACI,CAAA,GACP,oBAAC,OAAD;GACM;GACJ,MAAK;GACL,WAAU;GACL;GACA;GACL,OAAO;GACP,oBAAkB;GAClB,WAAU;GACV,WAAW,UAAU;IACnB,MAAM,OAAO,MAAM,OAAO;IAC1B,SAAS,IAAI;IACb,MAAM,OAAO,OAAO,IAAI;IACxB,IAAI,SAAS,MAAM,CAAC,OAAO,UAAU,IAAI,KAAK,OAAO,OAAO,OAAO,KAAK;IACxE,IAAI,SAAS,OAAO,SAAS,IAAI;GACnC;GACA,cAAc;IAEZ,IAAI,UAAU,OAAO,KAAK,GAAG,SAAS,OAAO,KAAK,CAAC;GACrD;EACD,CAAA,CACE;;AAET;;;;;;AAaA,SAAgB,eAAe,EAAE,WAAW,GAAG,SAA8B;CAC3E,MAAM,EAAE,OAAO,UAAU,WAAW,QAAQ,gBAAgB;CAC5D,MAAM,KAAK,MAAM;CAEjB,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CAExC,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,IAAI,UAAU,WAAW;EACvB,aAAa,KAAK;EAClB,SAAS,KAAK;CAChB;CAEA,MAAM,WAAW,cAAc;EAC7B,IAAI;GACF,OAAO;IAAE,MAAM,aAAa,OAAO,EAAE,OAAO,CAAC;IAAG,OAAO;GAAsB;EAC/E,SAAS,QAAQ;GACf,OAAO;IACL,MAAM;IACN,OAAO,kBAAkB,sBAAsB,OAAO,UAAU;GAClE;EACF;CACF,GAAG,CAAC,OAAO,MAAM,CAAC;CAElB,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,yBAAyB,SAAS;EAChD,GAAI;EAHN,UAAA;GAKE,oBAAC,SAAD;IAAO,SAAS,GAAG,GAAG;IAAS,WAAU;IAAsB,UAAA;GAExD,CAAA;GACP,oBAAC,OAAD;IACE,IAAI,GAAG,GAAG;IACV,WAAU;IACV,OAAO;IACP,YAAY;IACZ,cAAa;IACb,gBAAc,SAAS,QAAQ,OAAO,KAAA;IACtC,oBAAkB,GAAG,GAAG;IACxB,WAAU;IACV,WAAW,UAAU;KACnB,MAAM,OAAO,MAAM,OAAO;KAC1B,SAAS,IAAI;KACb,IAAI;MACF,UAAU,IAAI;MACd,SAAS,KAAK,KAAK,CAAC;KACtB,QAAQ,CAER;IACF;GACD,CAAA;GAGD,oBAAC,KAAD;IACE,IAAI,GAAG,GAAG;IACV,aAAU;IACV,WAAW,GAAG,WAAW,SAAS,QAAQ,qBAAqB,uBAAuB;IAErF,UAAA,SAAS,SAAS,SAAS;GAC3B,CAAA;EACA;;AAET;;;;;;AAeA,SAAgB,aAAa,EAAE,WAAW,QAAQ,GAAG,GAAG,SAA4B;CAClF,MAAM,EAAE,OAAO,UAAU,UAAU,QAAQ,QAAQ,QAAQ,cAAc;CACzE,MAAM,YAAY,MAAM;CAExB,MAAM,OAAO,cACJ,OAAO,WAAW,SAAS,OAAO;EAAE,MAAM;EAAK;EAAO;CAAS,CAAC,IAAI,MAC3E;EAAC;EAAO;EAAU;EAAK;EAAO;CAAQ,CACxC;CAGA,IAAI,CAAC,MAAM,OAAO;CAElB,MAAM,SAAS,IAAI,KAAK,eAAe,QAAQ;EAC7C,WAAW;EACX,WAAW;EACX;CACF,CAAC;CAED,OACE,qBAAC,OAAD;EACE,aAAU;EACV,WAAW,GAAG,yBAAyB,SAAS;EAChD,GAAI;EAHN,UAAA,CAKE,qBAAC,KAAD;GAAG,IAAI;GAAW,WAAU;GAA5B,UAAA;IAAkD;IAC1C,UAAU,IAAI,QAAQ,GAAG,OAAO,KAAK,EAAE;IAC7C,qBAAC,QAAD;KAAM,WAAU;KAAhB,UAAA,CACG,OACA,YAAY,YACT;;GACL;EACF,CAAA,GAAA,KAAK,WAAW,IACf,oBAAC,KAAD;GAAG,aAAU;GAAa,WAAU;GAA2B,UAAA;EAE5D,CAAA,IAEH,qBAAC,MAAD;GACE,mBAAiB;GACjB,WAAU;GAFZ,UAAA,CAIG,KAAK,KAAK,QACT,oBAAC,MAAD,EAAA,UACE,oBAAC,QAAD;IAAM,UAAU,IAAI,YAAY;IAAG,WAAU;IAC1C,UAAA,OAAO,OAAO,GAAG;GACd,CAAA,EACJ,GAJK,IAAI,YAAY,CAIrB,CACL,GACA,KAAK,SAAS,QACb,oBAAC,MAAD;IAAI,WAAU;IAAgC,UAAA;GAAmC,CAAA,IAC/E,IACF;EAEH,CAAA,CAAA;;AAET"}
@@ -0,0 +1,82 @@
1
+ //#region src/components/cron-editor/cron-expression.d.ts
2
+ /**
3
+ * The five-field cron expression, as a value: parsed, described in words, and
4
+ * projected onto a calendar in a named time zone.
5
+ *
6
+ * The dialect is the POSIX one — minute, hour, day of month, month, day of
7
+ * week — which is what crontab, GitHub Actions, Vercel, Kubernetes and Airflow
8
+ * all read. Not the Quartz one: no seconds field and no `L`, `W` or `#`,
9
+ * because the products people schedule from a form do not accept them, and an
10
+ * expression this editor produces has to run where it is pasted. The `@daily`
11
+ * family of shortcuts is accepted, since crontab accepts it.
12
+ *
13
+ * Everything here is pure and takes its clock as an argument, so the
14
+ * description, the run times and the two things every reimplementation gets
15
+ * wrong — day-of-month OR day-of-week, and a wall-clock time that does not
16
+ * exist on the day the clocks go forward — are tested without rendering
17
+ * anything, and an application can compute the next run on the server from
18
+ * the same expression the editor produced.
19
+ */
20
+ type CronField = "minute" | "hour" | "dayOfMonth" | "month" | "dayOfWeek";
21
+ declare class CronExpressionError extends Error {
22
+ /** Which of the five fields is wrong, when one is. */
23
+ readonly field?: CronField | undefined;
24
+ constructor(message: string,
25
+ /** Which of the five fields is wrong, when one is. */
26
+ field?: CronField | undefined);
27
+ }
28
+ interface CronSchedule {
29
+ minutes: number[];
30
+ hours: number[];
31
+ daysOfMonth: number[];
32
+ months: number[];
33
+ /** 0 to 6, Sunday first. `7` in the source is folded into 0. */
34
+ daysOfWeek: number[];
35
+ /**
36
+ * Whether each day field was written as something other than `*`. Cron's
37
+ * rule, which every reimplementation gets wrong at least once: when both are
38
+ * restricted a day matches if EITHER does, not both.
39
+ */
40
+ dayOfMonthRestricted: boolean;
41
+ dayOfWeekRestricted: boolean;
42
+ /** The expression with any shortcut expanded. */
43
+ expression: string;
44
+ }
45
+ declare const CRON_SHORTCUTS: Record<string, string>;
46
+ declare function parseCron(expression: string): CronSchedule;
47
+ declare function isValidCron(expression: string): boolean;
48
+ interface DescribeOptions {
49
+ locale?: string;
50
+ }
51
+ /**
52
+ * The schedule in a sentence.
53
+ *
54
+ * Every product that has this control renders "Every Monday at 09:00" beside
55
+ * the expression, because `0 9 * * 1` is not something most people can read.
56
+ * Throws for an invalid expression; the editor turns that into the field's
57
+ * error text.
58
+ */
59
+ declare function describeCron(expression: string, options?: DescribeOptions): string;
60
+ interface NextRunsOptions {
61
+ /** Runs strictly after this instant. */
62
+ from: Date;
63
+ count?: number;
64
+ /** IANA zone the expression's wall-clock times are in. Defaults to the runtime zone. */
65
+ timeZone?: string;
66
+ }
67
+ /**
68
+ * The instant a wall-clock time names in a zone, or null when it names none —
69
+ * the hour the clocks skip in spring. An ambiguous time, in the hour that
70
+ * repeats in autumn, resolves to its first occurrence.
71
+ */
72
+ declare function zonedTimeToInstant(year: number, month: number, day: number, hour: number, minute: number, timeZone: string | undefined): number | null;
73
+ /**
74
+ * The next `count` instants the schedule fires, in order.
75
+ *
76
+ * Fewer than `count` are returned when the schedule cannot produce them —
77
+ * `0 0 30 2 *` never runs, and saying so beats a spinner.
78
+ */
79
+ declare function nextRuns(expression: string, options: NextRunsOptions): Date[];
80
+ //#endregion
81
+ export { CRON_SHORTCUTS, CronExpressionError, CronField, CronSchedule, DescribeOptions, NextRunsOptions, describeCron, isValidCron, nextRuns, parseCron, zonedTimeToInstant };
82
+ //# sourceMappingURL=cron-expression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cron-expression.d.ts","names":[],"sources":["../../../src/components/cron-editor/cron-expression.ts"],"mappings":";;;;;;;;;;;;;;;;;;;KAmBY;cAEC,4BAA4B;;WAIrB,QAAQ;EAFxB,YAAA,iBAEgB;;EAAA,QAAQ;;UAOX;EACf;EACA;EACA;EACA;;EAEA;;;;;;EAMA;EACA;;EAEA;;cA6BW,gBAAgB;iBAwGb,UAAU,qBAAqB;iBAkC/B,YAAY;UAaX;EACf;;;;;;;;;;iBAiEc,aAAa,oBAAoB,UAAS;UA+FzC;;EAEf,MAAM;EACN;;EAEA;;;;;;;iBA8Dc,mBACd,cACA,eACA,aACA,cACA,gBACA;;;;;;;iBAyCc,SAAS,oBAAoB,SAAS,kBAAkB"}