snowkit 0.0.0-stage → 0.1.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.
@@ -0,0 +1,338 @@
1
+ const NAMED_RAW =
2
+ "aliceblue:f0f8ff,antiquewhite:faebd7,aqua:00ffff,aquamarine:7fffd4,azure:f0ffff,beige:f5f5dc,bisque:ffe4c4,black:000000,blanchedalmond:ffebcd,blue:0000ff," +
3
+ "blueviolet:8a2be2,brown:a52a2a,burlywood:deb887,cadetblue:5f9ea0,chartreuse:7fff00,chocolate:d2691e,coral:ff7f50,cornflowerblue:6495ed,cornsilk:fff8dc," +
4
+ "crimson:dc143c,cyan:00ffff,darkblue:00008b,darkcyan:008b8b,darkgoldenrod:b8860b,darkgray:a9a9a9,darkgreen:006400,darkgrey:a9a9a9,darkkhaki:bdb76b," +
5
+ "darkmagenta:8b008b,darkolivegreen:556b2f,darkorange:ff8c00,darkorchid:9932cc,darkred:8b0000,darksalmon:e9967a,darkseagreen:8fbc8f,darkslateblue:483d8b," +
6
+ "darkslategray:2f4f4f,darkslategrey:2f4f4f,darkturquoise:00ced1,darkviolet:9400d3,deeppink:ff1493,deepskyblue:00bfff,dimgray:696969,dimgrey:696969," +
7
+ "dodgerblue:1e90ff,firebrick:b22222,floralwhite:fffaf0,forestgreen:228b22,fuchsia:ff00ff,gainsboro:dcdcdc,ghostwhite:f8f8ff,gold:ffd700,goldenrod:daa520," +
8
+ "gray:808080,green:008000,greenyellow:adff2f,grey:808080,honeydew:f0fff0,hotpink:ff69b4,indianred:cd5c5c,indigo:4b0082,ivory:fffff0,khaki:f0e68c," +
9
+ "lavender:e6e6fa,lavenderblush:fff0f5,lawngreen:7cfc00,lemonchiffon:fffacd,lightblue:add8e6,lightcoral:f08080,lightcyan:e0ffff,lightgoldenrodyellow:fafad2," +
10
+ "lightgray:d3d3d3,lightgreen:90ee90,lightgrey:d3d3d3,lightpink:ffb6c1,lightsalmon:ffa07a,lightseagreen:20b2aa,lightskyblue:87cefa,lightslategray:778899," +
11
+ "lightslategrey:778899,lightsteelblue:b0c4de,lightyellow:ffffe0,lime:00ff00,limegreen:32cd32,linen:faf0e6,magenta:ff00ff,maroon:800000," +
12
+ "mediumaquamarine:66cdaa,mediumblue:0000cd,mediumorchid:ba55d3,mediumpurple:9370db,mediumseagreen:3cb371,mediumslateblue:7b68ee,mediumspringgreen:00fa9a," +
13
+ "mediumturquoise:48d1cc,mediumvioletred:c71585,midnightblue:191970,mintcream:f5fffa,mistyrose:ffe4e1,moccasin:ffe4b5,navajowhite:ffdead,navy:000080," +
14
+ "oldlace:fdf5e6,olive:808000,olivedrab:6b8e23,orange:ffa500,orangered:ff4500,orchid:da70d6,palegoldenrod:eee8aa,palegreen:98fb98,paleturquoise:afeeee," +
15
+ "palevioletred:db7093,papayawhip:ffefd5,peachpuff:ffdab9,peru:cd853f,pink:ffc0cb,plum:dda0dd,powderblue:b0e0e6,purple:800080,rebeccapurple:663399," +
16
+ "red:ff0000,rosybrown:bc8f8f,royalblue:4169e1,saddlebrown:8b4513,salmon:fa8072,sandybrown:f4a460,seagreen:2e8b57,seashell:fff5ee,sienna:a0522d," +
17
+ "silver:c0c0c0,skyblue:87ceeb,slateblue:6a5acd,slategray:708090,slategrey:708090,snow:fffafa,springgreen:00ff7f,steelblue:4682b4,tan:d2b48c,teal:008080," +
18
+ "thistle:d8bfd8,tomato:ff6347,turquoise:40e0d0,violet:ee82ee,wheat:f5deb3,white:ffffff,whitesmoke:f5f5f5,yellow:ffff00,yellowgreen:9acd32";
19
+
20
+ export const NAMED_COLORS = Object.fromEntries(NAMED_RAW.split(",").map((p) => p.split(":")));
21
+
22
+ const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
23
+ const round = (v, d = 0) => {
24
+ const f = Math.pow(10, d);
25
+ const r = Math.round(v * f) / f;
26
+ return Object.is(r, -0) ? 0 : r;
27
+ };
28
+
29
+ function hexToRgb(h) {
30
+ let s = h.replace(/^#/, "");
31
+ if (s.length === 3 || s.length === 4) s = [...s].map((c) => c + c).join("");
32
+ const n = (i) => parseInt(s.slice(i, i + 2), 16);
33
+ return { r: n(0), g: n(2), b: n(4), a: s.length === 8 ? round(n(6) / 255, 3) : 1 };
34
+ }
35
+
36
+ function angle(s) {
37
+ const m = /^(-?[\d.]+(?:e-?\d+)?)(deg|rad|grad|turn)?$/.exec(s);
38
+ if (!m) throw new Error("Can't read the hue " + s);
39
+ const v = +m[1];
40
+ const deg = m[2] === "rad" ? (v * 180) / Math.PI : m[2] === "grad" ? v * 0.9 : m[2] === "turn" ? v * 360 : v;
41
+ return ((deg % 360) + 360) % 360;
42
+ }
43
+
44
+ function num(s, scale100 = 1) {
45
+ if (s === "none") return 0;
46
+ const m = /^(-?[\d.]+(?:e-?\d+)?)(%)?$/.exec(s);
47
+ if (!m || Number.isNaN(+m[1])) throw new Error("Can't read the value " + s);
48
+ return m[2] ? (+m[1] / 100) * scale100 : +m[1];
49
+ }
50
+
51
+ function alpha(s) {
52
+ if (s === undefined) return 1;
53
+ return clamp(num(s, 1), 0, 1);
54
+ }
55
+
56
+ export function hslToRgb(h, s, l) {
57
+ s = clamp(s, 0, 100) / 100;
58
+ l = clamp(l, 0, 100) / 100;
59
+ const k = (n) => (n + h / 30) % 12;
60
+ const a = s * Math.min(l, 1 - l);
61
+ const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, 9 - k(n), 1));
62
+ return { r: f(0) * 255, g: f(8) * 255, b: f(4) * 255 };
63
+ }
64
+
65
+ export function rgbToHsl(r, g, b) {
66
+ r /= 255;
67
+ g /= 255;
68
+ b /= 255;
69
+ const max = Math.max(r, g, b);
70
+ const min = Math.min(r, g, b);
71
+ const d = max - min;
72
+ const l = (max + min) / 2;
73
+ let h = 0;
74
+ let s = 0;
75
+ if (d > 1e-9) {
76
+ s = d / (1 - Math.abs(2 * l - 1));
77
+ if (max === r) h = ((g - b) / d) % 6;
78
+ else if (max === g) h = (b - r) / d + 2;
79
+ else h = (r - g) / d + 4;
80
+ h *= 60;
81
+ if (h < 0) h += 360;
82
+ }
83
+ return { h, s: s * 100, l: l * 100 };
84
+ }
85
+
86
+ export function hsvToRgb(h, s, v) {
87
+ s = clamp(s, 0, 100) / 100;
88
+ v = clamp(v, 0, 100) / 100;
89
+ const f = (n) => {
90
+ const k = (n + h / 60) % 6;
91
+ return v - v * s * Math.max(0, Math.min(k, 4 - k, 1));
92
+ };
93
+ return { r: f(5) * 255, g: f(3) * 255, b: f(1) * 255 };
94
+ }
95
+
96
+ export function rgbToHsv(r, g, b) {
97
+ r /= 255;
98
+ g /= 255;
99
+ b /= 255;
100
+ const max = Math.max(r, g, b);
101
+ const d = max - Math.min(r, g, b);
102
+ let h = 0;
103
+ if (d > 1e-9) {
104
+ if (max === r) h = ((g - b) / d) % 6;
105
+ else if (max === g) h = (b - r) / d + 2;
106
+ else h = (r - g) / d + 4;
107
+ h *= 60;
108
+ if (h < 0) h += 360;
109
+ }
110
+ return { h, s: max === 0 ? 0 : (d / max) * 100, v: max * 100 };
111
+ }
112
+
113
+ const toLinear = (c) => {
114
+ c /= 255;
115
+ return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
116
+ };
117
+ const fromLinear = (c) => 255 * (c <= 0.0031308 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055);
118
+
119
+ export function rgbToOklab(r, g, b) {
120
+ const lr = toLinear(r);
121
+ const lg = toLinear(g);
122
+ const lb = toLinear(b);
123
+ const l = Math.cbrt(0.4122214708 * lr + 0.5363325363 * lg + 0.0514459929 * lb);
124
+ const m = Math.cbrt(0.2119034982 * lr + 0.6806995451 * lg + 0.1073969566 * lb);
125
+ const s = Math.cbrt(0.0883024619 * lr + 0.2817188376 * lg + 0.6299787005 * lb);
126
+ return { L: 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s, a: 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s, b: 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s };
127
+ }
128
+
129
+ function oklabToLinear(L, a, b) {
130
+ const l = Math.pow(L + 0.3963377774 * a + 0.2158037573 * b, 3);
131
+ const m = Math.pow(L - 0.1055613458 * a - 0.0638541728 * b, 3);
132
+ const s = Math.pow(L - 0.0894841775 * a - 1.291485548 * b, 3);
133
+ return [4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s];
134
+ }
135
+
136
+ export function oklabToRgb(L, a, b) {
137
+ const [r, g, bl] = oklabToLinear(L, a, b);
138
+ return { r: fromLinear(r), g: fromLinear(g), b: fromLinear(bl) };
139
+ }
140
+
141
+ export function rgbToOklch(r, g, b) {
142
+ const lab = rgbToOklab(r, g, b);
143
+ const C = Math.sqrt(lab.a * lab.a + lab.b * lab.b);
144
+ let H = (Math.atan2(lab.b, lab.a) * 180) / Math.PI;
145
+ if (H < 0) H += 360;
146
+ return { L: lab.L, C: C < 1e-4 ? 0 : C, H: C < 1e-4 ? 0 : H };
147
+ }
148
+
149
+ function inGamut(lin) {
150
+ return lin.every((v) => v >= -1e-4 && v <= 1 + 1e-4);
151
+ }
152
+
153
+ export function oklchToRgb(L, C, H) {
154
+ const lab = (c) => [L, c * Math.cos((H * Math.PI) / 180), c * Math.sin((H * Math.PI) / 180)];
155
+ let lin = oklabToLinear(...lab(C));
156
+ let clipped = false;
157
+ if (!inGamut(lin)) {
158
+ clipped = true;
159
+ let lo = 0;
160
+ let hi = C;
161
+ for (let i = 0; i < 30; i++) {
162
+ const mid = (lo + hi) / 2;
163
+ if (inGamut(oklabToLinear(...lab(mid)))) lo = mid;
164
+ else hi = mid;
165
+ }
166
+ lin = oklabToLinear(...lab(lo));
167
+ }
168
+ const [r, g, b] = lin.map((v) => clamp(fromLinear(clamp(v, 0, 1)), 0, 255));
169
+ return { r, g, b, clipped };
170
+ }
171
+
172
+ export function parseColor(input) {
173
+ const s = String(input).trim().toLowerCase().replace(/;$/, "");
174
+ if (!s) throw new Error("Type a colour, like #3b82f6 or rgb(59 130 246)");
175
+ if (s === "transparent") return { r: 0, g: 0, b: 0, a: 0 };
176
+ if (NAMED_COLORS[s]) return hexToRgb(NAMED_COLORS[s]);
177
+ if (/^#?([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/.test(s)) return hexToRgb(s);
178
+ if (/^0x[0-9a-f]{6}$/.test(s)) return hexToRgb(s.slice(2));
179
+ let m = /^(rgba?|hsla?|hsv|hsva|hsb|oklch|oklab)\s*\(\s*(.*?)\s*\)$/.exec(s);
180
+ if (!m && /^-?[\d.]+%?(\s*,\s*|\s+)-?[\d.]+%?(\s*,\s*|\s+)-?[\d.]+%?$/.test(s)) m = [s, "rgb", s];
181
+ if (!m) throw new Error("Not a colour this tool understands. Try #hex, rgb(), hsl(), hsv(), oklch() or a CSS name");
182
+ const fn = m[1].replace(/a$/, "") === "hsv" || m[1] === "hsb" ? "hsv" : m[1].replace(/a$/, "");
183
+ const [main, slash] = m[2].split("/").map((x) => x.trim());
184
+ let parts = main.split(/\s*,\s*|\s+/).filter(Boolean);
185
+ let a = slash;
186
+ if (parts.length === 4 && a === undefined) a = parts.pop();
187
+ if (parts.length !== 3) throw new Error(fn + "() needs three values" + (fn === "rgb" ? ", like rgb(59, 130, 246)" : ""));
188
+ let rgb;
189
+ if (fn === "rgb") rgb = { r: clamp(num(parts[0], 255), 0, 255), g: clamp(num(parts[1], 255), 0, 255), b: clamp(num(parts[2], 255), 0, 255) };
190
+ else if (fn === "hsl") rgb = hslToRgb(angle(parts[0]), parseFloat(parts[1]), parseFloat(parts[2]));
191
+ else if (fn === "hsv") rgb = hsvToRgb(angle(parts[0]), parseFloat(parts[1]), parseFloat(parts[2]));
192
+ else if (fn === "oklch") {
193
+ let L = num(parts[0], 1);
194
+ if (!parts[0].endsWith("%") && L > 1) L /= 100;
195
+ const C = parts[1].endsWith("%") ? num(parts[1], 0.4) : num(parts[1]);
196
+ rgb = oklchToRgb(clamp(L, 0, 1), Math.max(0, C), angle(parts[2] === "none" ? "0" : parts[2]));
197
+ } else {
198
+ let L = num(parts[0], 1);
199
+ if (!parts[0].endsWith("%") && L > 1) L /= 100;
200
+ const A = parts[1].endsWith("%") ? num(parts[1], 0.4) : num(parts[1]);
201
+ const B = parts[2].endsWith("%") ? num(parts[2], 0.4) : num(parts[2]);
202
+ const o = oklabToRgb(L, A, B);
203
+ rgb = { r: clamp(o.r, 0, 255), g: clamp(o.g, 0, 255), b: clamp(o.b, 0, 255) };
204
+ }
205
+ for (const k of ["r", "g", "b"]) if (Number.isNaN(rgb[k])) throw new Error("Can't read " + input);
206
+ return { r: rgb.r, g: rgb.g, b: rgb.b, a: alpha(a), clipped: rgb.clipped || undefined };
207
+ }
208
+
209
+ const hex2 = (v) => Math.round(clamp(v, 0, 255)).toString(16).padStart(2, "0");
210
+
211
+ export function toHex(c) {
212
+ return "#" + hex2(c.r) + hex2(c.g) + hex2(c.b) + (c.a !== undefined && c.a < 1 ? hex2(c.a * 255) : "");
213
+ }
214
+
215
+ const alphaPart = (c, sep) => (c.a !== undefined && c.a < 1 ? sep + round(c.a, 3) : "");
216
+
217
+ export function formatColor(c, fmt) {
218
+ const R = Math.round(clamp(c.r, 0, 255));
219
+ const G = Math.round(clamp(c.g, 0, 255));
220
+ const B = Math.round(clamp(c.b, 0, 255));
221
+ if (fmt === "hex") return toHex(c);
222
+ if (fmt === "rgb") return c.a !== undefined && c.a < 1 ? "rgba(" + R + ", " + G + ", " + B + ", " + round(c.a, 3) + ")" : "rgb(" + R + ", " + G + ", " + B + ")";
223
+ if (fmt === "hsl") {
224
+ const h = rgbToHsl(c.r, c.g, c.b);
225
+ const body = round(h.h, 1) + ", " + round(h.s, 1) + "%, " + round(h.l, 1) + "%";
226
+ return c.a !== undefined && c.a < 1 ? "hsla(" + body + ", " + round(c.a, 3) + ")" : "hsl(" + body + ")";
227
+ }
228
+ if (fmt === "hsv") {
229
+ const h = rgbToHsv(c.r, c.g, c.b);
230
+ return "hsv(" + round(h.h, 1) + ", " + round(h.s, 1) + "%, " + round(h.v, 1) + "%" + alphaPart(c, ", ") + ")";
231
+ }
232
+ if (fmt === "oklch") {
233
+ const o = rgbToOklch(c.r, c.g, c.b);
234
+ return "oklch(" + round(o.L * 100, 2) + "% " + round(o.C, 4) + " " + round(o.H, 2) + alphaPart(c, " / ") + ")";
235
+ }
236
+ if (fmt === "name") {
237
+ const n = nearestName(c);
238
+ return n.exact ? n.name : "";
239
+ }
240
+ throw new Error("Unknown format " + fmt);
241
+ }
242
+
243
+ export function nearestName(c) {
244
+ const lab = rgbToOklab(c.r, c.g, c.b);
245
+ let best = null;
246
+ for (const [name, hex] of Object.entries(NAMED_COLORS)) {
247
+ const o = hexToRgb(hex);
248
+ const l2 = rgbToOklab(o.r, o.g, o.b);
249
+ const d = Math.hypot(lab.L - l2.L, lab.a - l2.a, lab.b - l2.b);
250
+ if (!best || d < best.d) best = { name, hex: "#" + hex, d };
251
+ }
252
+ const exact = toHex({ ...c, a: 1 }) === best.hex;
253
+ return { name: best.name, hex: best.hex, exact, distance: best.d };
254
+ }
255
+
256
+ export function luminance(c) {
257
+ return 0.2126 * toLinear(c.r) + 0.7152 * toLinear(c.g) + 0.0722 * toLinear(c.b);
258
+ }
259
+
260
+ export function blend(fg, bg) {
261
+ const a = fg.a === undefined ? 1 : fg.a;
262
+ return { r: fg.r * a + bg.r * (1 - a), g: fg.g * a + bg.g * (1 - a), b: fg.b * a + bg.b * (1 - a), a: 1 };
263
+ }
264
+
265
+ export function contrastRatio(fg, bg) {
266
+ const base = bg.a !== undefined && bg.a < 1 ? blend(bg, { r: 255, g: 255, b: 255 }) : bg;
267
+ const top = fg.a !== undefined && fg.a < 1 ? blend(fg, base) : fg;
268
+ const l1 = luminance(top);
269
+ const l2 = luminance(base);
270
+ return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
271
+ }
272
+
273
+ export function wcag(ratio) {
274
+ return { aaNormal: ratio >= 4.5, aaLarge: ratio >= 3, aaaNormal: ratio >= 7, aaaLarge: ratio >= 4.5, ui: ratio >= 3 };
275
+ }
276
+
277
+ export function ratioText(ratio) {
278
+ return (Math.floor(ratio * 100) / 100).toFixed(2) + ":1";
279
+ }
280
+
281
+ export function readableOn(c) {
282
+ return contrastRatio({ r: 0, g: 0, b: 0 }, c) >= contrastRatio({ r: 255, g: 255, b: 255 }, c) ? "#000000" : "#ffffff";
283
+ }
284
+
285
+ export function mix(c1, c2, t) {
286
+ const a = rgbToOklab(c1.r, c1.g, c1.b);
287
+ const b = rgbToOklab(c2.r, c2.g, c2.b);
288
+ const o = oklabToRgb(a.L + (b.L - a.L) * t, a.a + (b.a - a.a) * t, a.b + (b.b - a.b) * t);
289
+ return { r: clamp(o.r, 0, 255), g: clamp(o.g, 0, 255), b: clamp(o.b, 0, 255), a: 1 };
290
+ }
291
+
292
+ export function tintsAndShades(c, steps = 5) {
293
+ const white = { r: 255, g: 255, b: 255 };
294
+ const black = { r: 0, g: 0, b: 0 };
295
+ const tints = [];
296
+ const shades = [];
297
+ for (let i = 1; i <= steps; i++) {
298
+ tints.push(toHex(mix(c, white, i / (steps + 1))));
299
+ shades.push(toHex(mix(c, black, i / (steps + 1))));
300
+ }
301
+ return { tints, shades };
302
+ }
303
+
304
+ export function suggestContrast(fg, bg, target = 4.5) {
305
+ if (contrastRatio(fg, bg) >= target) return null;
306
+ const o = rgbToOklch(fg.r, fg.g, fg.b);
307
+ const tryL = (L) => {
308
+ const rgb = oklchToRgb(L, o.C, o.H);
309
+ return { ...rgb, a: 1 };
310
+ };
311
+ const found = [];
312
+ for (const dir of [-1, 1]) {
313
+ const end = dir < 0 ? 0 : 1;
314
+ if (contrastRatio(tryL(end), bg) < target) continue;
315
+ let lo = o.L;
316
+ let hi = end;
317
+ for (let i = 0; i < 30; i++) {
318
+ const mid = (lo + hi) / 2;
319
+ if (contrastRatio(tryL(mid), bg) >= target) hi = mid;
320
+ else lo = mid;
321
+ }
322
+ found.push({ c: tryL(hi), d: Math.abs(hi - o.L) });
323
+ }
324
+ if (!found.length) return null;
325
+ found.sort((a, b) => a.d - b.d);
326
+ let hex = toHex(found[0].c);
327
+ let c = parseColor(hex);
328
+ if (contrastRatio(c, bg) < target) {
329
+ const o2 = rgbToOklch(c.r, c.g, c.b);
330
+ const step = o2.L < rgbToOklch(bg.r, bg.g, bg.b).L ? -0.005 : 0.005;
331
+ for (let i = 0; i < 20 && contrastRatio(c, bg) < target; i++) {
332
+ c = { ...oklchToRgb(clamp(o2.L + step * (i + 1), 0, 1), o2.C, o2.H), a: 1 };
333
+ hex = toHex(c);
334
+ c = parseColor(hex);
335
+ }
336
+ }
337
+ return hex;
338
+ }
@@ -0,0 +1,148 @@
1
+ const MONTHS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
2
+ const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
3
+ const ALIASES = { "@yearly": "0 0 1 1 *", "@annually": "0 0 1 1 *", "@monthly": "0 0 1 * *", "@weekly": "0 0 * * 0", "@daily": "0 0 * * *", "@midnight": "0 0 * * *", "@hourly": "0 * * * *" };
4
+ const FIELDS = [
5
+ { name: "minute", min: 0, max: 59 },
6
+ { name: "hour", min: 0, max: 23 },
7
+ { name: "day of month", min: 1, max: 31 },
8
+ { name: "month", min: 1, max: 12, names: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"] },
9
+ { name: "day of week", min: 0, max: 7, names: ["sun", "mon", "tue", "wed", "thu", "fri", "sat"] },
10
+ ];
11
+
12
+ function value(token, f) {
13
+ const t = token.toLowerCase();
14
+ if (f.names) {
15
+ const i = f.names.indexOf(t);
16
+ if (i >= 0) return i + f.min;
17
+ }
18
+ if (!/^\d+$/.test(t)) throw new Error("Unknown value \u201C" + token + "\u201D in the " + f.name + " field");
19
+ const n = +t;
20
+ if (n < f.min || n > f.max) throw new Error(f.name[0].toUpperCase() + f.name.slice(1) + " must be between " + f.min + " and " + f.max + ", not " + n);
21
+ return n;
22
+ }
23
+
24
+ function parseField(text, f) {
25
+ const set = new Set();
26
+ for (const part of text.split(",")) {
27
+ const [range, stepText] = part.split("/");
28
+ const step = stepText === undefined ? 1 : +stepText;
29
+ if (!Number.isInteger(step) || step < 1) throw new Error("Bad step \u201C" + part + "\u201D in the " + f.name + " field");
30
+ let lo;
31
+ let hi;
32
+ if (range === "*" || range === "?") {
33
+ lo = f.min;
34
+ hi = f.name === "day of week" ? 6 : f.max;
35
+ } else if (range.includes("-")) {
36
+ const [a, b] = range.split("-");
37
+ lo = value(a, f);
38
+ hi = value(b, f);
39
+ if (lo > hi) throw new Error("Range \u201C" + range + "\u201D runs backwards in the " + f.name + " field");
40
+ } else {
41
+ lo = value(range, f);
42
+ hi = stepText === undefined ? lo : f.name === "day of week" ? 6 : f.max;
43
+ }
44
+ for (let v = lo; v <= hi; v += step) set.add(f.name === "day of week" && v === 7 ? 0 : v);
45
+ }
46
+ return { set, any: text === "*" || text === "?", text };
47
+ }
48
+
49
+ export function parse(expr) {
50
+ const raw = String(expr || "").trim().replace(/\s+/g, " ");
51
+ const full = ALIASES[raw.toLowerCase()] || raw;
52
+ const parts = full.split(" ");
53
+ if (parts.length === 6) parts.shift();
54
+ if (parts.length !== 5) throw new Error("A cron expression has 5 fields: minute, hour, day of month, month and day of week");
55
+ const fields = parts.map((p, i) => parseField(p, FIELDS[i]));
56
+ return { expr: full, minute: fields[0], hour: fields[1], dom: fields[2], month: fields[3], dow: fields[4] };
57
+ }
58
+
59
+ function listWords(nums, map) {
60
+ const words = nums.map(map);
61
+ return words.length <= 2 ? words.join(" and ") : words.slice(0, -1).join(", ") + " and " + words[words.length - 1];
62
+ }
63
+
64
+ const pad = (n) => String(n).padStart(2, "0");
65
+
66
+ function ordinal(n) {
67
+ const s = n % 100 >= 11 && n % 100 <= 13 ? "th" : ["th", "st", "nd", "rd"][n % 10] || "th";
68
+ return n + s;
69
+ }
70
+
71
+ function clock(h, m) {
72
+ return pad(h) + ":" + pad(m);
73
+ }
74
+
75
+ function stepOf(field, min) {
76
+ const m = field.text.match(/^(\*|\d+)\/(\d+)$/);
77
+ return m && (m[1] === "*" || +m[1] === min) ? +m[2] : null;
78
+ }
79
+
80
+ export function describe(expr) {
81
+ const c = parse(expr);
82
+ const mins = [...c.minute.set].sort((a, b) => a - b);
83
+ const hours = [...c.hour.set].sort((a, b) => a - b);
84
+ let time;
85
+ const mStep = stepOf(c.minute, 0);
86
+ const hStep = stepOf(c.hour, 0);
87
+ if (c.minute.any && c.hour.any) time = "Every minute";
88
+ else if (mStep && c.hour.any) time = "Every " + mStep + " minutes";
89
+ else if (mStep) time = "Every " + mStep + " minutes, " + (hours.length === 1 ? "during the " + pad(hours[0]) + ":00 hour" : "between " + clock(hours[0], 0) + " and " + clock(hours[hours.length - 1], 59));
90
+ else if (c.minute.any) time = "Every minute " + (hours.length === 1 ? "during the " + pad(hours[0]) + ":00 hour" : "of hours " + listWords(hours, (h) => pad(h)));
91
+ else if (c.hour.any) time = mins.length === 1 ? (mins[0] === 0 ? "Every hour, on the hour" : "Every hour at " + mins[0] + " minutes past") : "Every hour at minutes " + listWords(mins, String);
92
+ else if (hStep && mins.length === 1) time = "Every " + hStep + " hours" + (mins[0] ? " at " + mins[0] + " minutes past" : ", on the hour");
93
+ else if (hours.length * mins.length <= 6) time = "At " + listWords(hours.flatMap((h) => mins.map((m) => [h, m])), ([h, m]) => clock(h, m));
94
+ else time = "At minutes " + listWords(mins, String) + " past hours " + listWords(hours, (h) => pad(h));
95
+ const parts = [time];
96
+ const dows = [...c.dow.set].sort((a, b) => a - b);
97
+ const doms = [...c.dom.set].sort((a, b) => a - b);
98
+ if (!c.dom.any) parts.push("on the " + listWords(doms, ordinal) + " of the month");
99
+ if (!c.dow.any) {
100
+ const wk = dows.join(",") === "1,2,3,4,5" ? "on weekdays" : dows.join(",") === "0,6" ? "at weekends" : "on " + listWords(dows, (d) => DAYS[d]);
101
+ parts.push((!c.dom.any ? "or " : "") + wk);
102
+ }
103
+ if (!c.month.any) parts.push("in " + listWords([...c.month.set].sort((a, b) => a - b), (m) => MONTHS[m - 1]));
104
+ return parts.join(", ") + ".";
105
+ }
106
+
107
+ export function next(expr, count = 5, from = new Date(), { utc = false } = {}) {
108
+ const c = parse(expr);
109
+ const g = utc
110
+ ? { mo: (d) => d.getUTCMonth(), dt: (d) => d.getUTCDate(), dw: (d) => d.getUTCDay(), h: (d) => d.getUTCHours(), mi: (d) => d.getUTCMinutes() }
111
+ : { mo: (d) => d.getMonth(), dt: (d) => d.getDate(), dw: (d) => d.getDay(), h: (d) => d.getHours(), mi: (d) => d.getMinutes() };
112
+ const set = utc
113
+ ? { month: (d) => d.setUTCMonth(d.getUTCMonth() + 1, 1) && d.setUTCHours(0, 0, 0, 0), day: (d) => d.setUTCDate(d.getUTCDate() + 1) && d.setUTCHours(0, 0, 0, 0), hour: (d) => d.setUTCHours(d.getUTCHours() + 1, 0, 0, 0), minute: (d) => d.setUTCMinutes(d.getUTCMinutes() + 1, 0, 0) }
114
+ : { month: (d) => d.setMonth(d.getMonth() + 1, 1) && d.setHours(0, 0, 0, 0), day: (d) => d.setDate(d.getDate() + 1) && d.setHours(0, 0, 0, 0), hour: (d) => d.setHours(d.getHours() + 1, 0, 0, 0), minute: (d) => d.setMinutes(d.getMinutes() + 1, 0, 0) };
115
+ const out = [];
116
+ const d = new Date(from.getTime());
117
+ d.setSeconds(0, 0);
118
+ set.minute(d);
119
+ const domAny = c.dom.any;
120
+ const dowAny = c.dow.any;
121
+ for (let i = 0; i < 366 * 24 * 60 * 5 && out.length < count; i++) {
122
+ if (!c.month.set.has(g.mo(d) + 1)) {
123
+ set.month(d);
124
+ continue;
125
+ }
126
+ const domOk = c.dom.set.has(g.dt(d));
127
+ const dowOk = c.dow.set.has(g.dw(d));
128
+ if (!(domAny && dowAny ? true : domAny ? dowOk : dowAny ? domOk : domOk || dowOk)) {
129
+ set.day(d);
130
+ continue;
131
+ }
132
+ if (!c.hour.set.has(g.h(d))) {
133
+ set.hour(d);
134
+ continue;
135
+ }
136
+ if (!c.minute.set.has(g.mi(d))) {
137
+ set.minute(d);
138
+ continue;
139
+ }
140
+ out.push(new Date(d.getTime()));
141
+ set.minute(d);
142
+ }
143
+ return out;
144
+ }
145
+
146
+ export function build({ minute = "0", hour = "*", dom = "*", month = "*", dow = "*" } = {}) {
147
+ return [minute, hour, dom, month, dow].join(" ");
148
+ }