@marianmeres/stuic 3.46.0 → 3.47.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Book/Book.svelte +2 -8
- package/dist/components/CronInput/CronInput.svelte +517 -0
- package/dist/components/CronInput/CronInput.svelte.d.ts +56 -0
- package/dist/components/CronInput/cron-next-run.svelte.d.ts +18 -0
- package/dist/components/CronInput/cron-next-run.svelte.js +63 -0
- package/dist/components/CronInput/index.css +221 -0
- package/dist/components/CronInput/index.d.ts +2 -0
- package/dist/components/CronInput/index.js +2 -0
- package/dist/components/DropdownMenu/index.css +1 -1
- package/dist/icons/index.d.ts +2 -0
- package/dist/icons/index.js +2 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +99 -100
|
@@ -318,15 +318,9 @@
|
|
|
318
318
|
$effect(() => {
|
|
319
319
|
const current = activeSpread;
|
|
320
320
|
clearTimeout(_settleTimer);
|
|
321
|
-
|
|
322
|
-
if (Math.abs(current - settledSpread) <= 1) {
|
|
321
|
+
_settleTimer = setTimeout(() => {
|
|
323
322
|
settledSpread = current;
|
|
324
|
-
}
|
|
325
|
-
// Large jump (slider drag): debounce to avoid intermediate downloads
|
|
326
|
-
_settleTimer = setTimeout(() => {
|
|
327
|
-
settledSpread = current;
|
|
328
|
-
}, 120);
|
|
329
|
-
}
|
|
323
|
+
}, 120);
|
|
330
324
|
});
|
|
331
325
|
|
|
332
326
|
$effect(() => () => clearTimeout(_settleTimer));
|
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
4
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
|
|
6
|
+
type SnippetWithId = Snippet<[{ id: string }]>;
|
|
7
|
+
|
|
8
|
+
export interface CronPreset {
|
|
9
|
+
label: string;
|
|
10
|
+
value: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type CronInputMode = "predefined" | "manual";
|
|
14
|
+
|
|
15
|
+
export interface Props {
|
|
16
|
+
// Core
|
|
17
|
+
value?: string;
|
|
18
|
+
el?: HTMLElement;
|
|
19
|
+
id?: string;
|
|
20
|
+
|
|
21
|
+
// Mode toggle (overrides show* flags when defined)
|
|
22
|
+
mode?: CronInputMode;
|
|
23
|
+
|
|
24
|
+
// InputWrap standard props
|
|
25
|
+
label?: SnippetWithId | THC;
|
|
26
|
+
description?: SnippetWithId | THC;
|
|
27
|
+
renderSize?: "sm" | "md" | "lg" | string;
|
|
28
|
+
required?: boolean;
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
|
|
31
|
+
class?: string;
|
|
32
|
+
style?: string;
|
|
33
|
+
|
|
34
|
+
// InputWrap layout
|
|
35
|
+
labelLeft?: boolean;
|
|
36
|
+
labelLeftWidth?: "normal" | "wide";
|
|
37
|
+
labelLeftBreakpoint?: number;
|
|
38
|
+
labelAfter?: SnippetWithId | THC;
|
|
39
|
+
below?: SnippetWithId | THC;
|
|
40
|
+
|
|
41
|
+
// CronInput-specific
|
|
42
|
+
showPresets?: boolean;
|
|
43
|
+
showFields?: boolean;
|
|
44
|
+
showRawInput?: boolean;
|
|
45
|
+
showDescription?: boolean;
|
|
46
|
+
showNextRun?: boolean;
|
|
47
|
+
presets?: CronPreset[];
|
|
48
|
+
onchange?: (expression: string, valid: boolean) => void;
|
|
49
|
+
|
|
50
|
+
// Sub-element class overrides
|
|
51
|
+
classLabel?: string;
|
|
52
|
+
classLabelBox?: string;
|
|
53
|
+
classInputBox?: string;
|
|
54
|
+
classInputBoxWrap?: string;
|
|
55
|
+
classInputBoxWrapInvalid?: string;
|
|
56
|
+
classDescBox?: string;
|
|
57
|
+
classBelowBox?: string;
|
|
58
|
+
classFields?: string;
|
|
59
|
+
classField?: string;
|
|
60
|
+
classFieldLabel?: string;
|
|
61
|
+
classFieldInput?: string;
|
|
62
|
+
classPreset?: string;
|
|
63
|
+
classRaw?: string;
|
|
64
|
+
classSummary?: string;
|
|
65
|
+
classToggleButton?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const DEFAULT_PRESETS: CronPreset[] = [
|
|
69
|
+
{ label: "Every minute", value: "* * * * *" },
|
|
70
|
+
{ label: "Every 5 minutes", value: "*/5 * * * *" },
|
|
71
|
+
{ label: "Every 15 minutes", value: "*/15 * * * *" },
|
|
72
|
+
{ label: "Every 30 minutes", value: "*/30 * * * *" },
|
|
73
|
+
{ label: "Every hour", value: "0 * * * *" },
|
|
74
|
+
{ label: "Every 6 hours", value: "0 */6 * * *" },
|
|
75
|
+
{ label: "Daily at midnight", value: "0 0 * * *" },
|
|
76
|
+
{ label: "Daily at noon", value: "0 12 * * *" },
|
|
77
|
+
{ label: "Weekdays at 9:00", value: "0 9 * * 1-5" },
|
|
78
|
+
{ label: "Weekly (Sunday midnight)", value: "0 0 * * 0" },
|
|
79
|
+
{ label: "Monthly (1st at midnight)", value: "0 0 1 * *" },
|
|
80
|
+
];
|
|
81
|
+
|
|
82
|
+
const FIELD_DEFS = [
|
|
83
|
+
{ key: "minute", label: "Min", placeholder: "0-59" },
|
|
84
|
+
{ key: "hour", label: "Hour", placeholder: "0-23" },
|
|
85
|
+
{ key: "dayOfMonth", label: "Day", placeholder: "1-31" },
|
|
86
|
+
{ key: "month", label: "Month", placeholder: "1-12" },
|
|
87
|
+
{ key: "dayOfWeek", label: "Wday", placeholder: "0-6" },
|
|
88
|
+
] as const;
|
|
89
|
+
|
|
90
|
+
const MONTH_NAMES = [
|
|
91
|
+
"", "Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
|
92
|
+
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
const DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
96
|
+
|
|
97
|
+
type FieldKey = (typeof FIELD_DEFS)[number]["key"];
|
|
98
|
+
|
|
99
|
+
function cronToHuman(expression: string): string {
|
|
100
|
+
const parts = expression.trim().split(/\s+/);
|
|
101
|
+
if (parts.length !== 5) return "";
|
|
102
|
+
|
|
103
|
+
const [minute, hour, dom, month, dow] = parts;
|
|
104
|
+
const segments: string[] = [];
|
|
105
|
+
|
|
106
|
+
// Minute
|
|
107
|
+
if (minute === "*") {
|
|
108
|
+
segments.push("every minute");
|
|
109
|
+
} else if (minute.startsWith("*/")) {
|
|
110
|
+
segments.push(`every ${minute.slice(2)} minutes`);
|
|
111
|
+
} else {
|
|
112
|
+
segments.push(`at minute ${minute}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Hour
|
|
116
|
+
if (hour === "*") {
|
|
117
|
+
// implied by "every minute/N minutes"
|
|
118
|
+
} else if (hour.startsWith("*/")) {
|
|
119
|
+
segments.push(`every ${hour.slice(2)} hours`);
|
|
120
|
+
} else {
|
|
121
|
+
segments.push(`at ${hour.padStart(2, "0")}:${minute === "*" ? "00" : minute.padStart(2, "0")}`);
|
|
122
|
+
// remove the minute segment if we have a specific hour
|
|
123
|
+
if (minute !== "*" && !minute.includes("/")) {
|
|
124
|
+
segments.length = 0;
|
|
125
|
+
segments.push(`at ${hour.padStart(2, "0")}:${minute.padStart(2, "0")}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Day of month
|
|
130
|
+
if (dom !== "*") {
|
|
131
|
+
if (dom.includes("-")) {
|
|
132
|
+
segments.push(`on days ${dom} of the month`);
|
|
133
|
+
} else if (dom.includes(",")) {
|
|
134
|
+
segments.push(`on days ${dom} of the month`);
|
|
135
|
+
} else {
|
|
136
|
+
const n = parseInt(dom);
|
|
137
|
+
const suffix = n === 1 ? "st" : n === 2 ? "nd" : n === 3 ? "rd" : "th";
|
|
138
|
+
segments.push(`on the ${n}${suffix}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Month
|
|
143
|
+
if (month !== "*") {
|
|
144
|
+
if (month.includes("-")) {
|
|
145
|
+
const [s, e] = month.split("-").map(Number);
|
|
146
|
+
segments.push(`in ${MONTH_NAMES[s]}-${MONTH_NAMES[e]}`);
|
|
147
|
+
} else if (month.includes(",")) {
|
|
148
|
+
const names = month.split(",").map((m) => MONTH_NAMES[parseInt(m)] || m);
|
|
149
|
+
segments.push(`in ${names.join(", ")}`);
|
|
150
|
+
} else {
|
|
151
|
+
segments.push(`in ${MONTH_NAMES[parseInt(month)] || month}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Day of week
|
|
156
|
+
if (dow !== "*") {
|
|
157
|
+
if (dow.includes("-")) {
|
|
158
|
+
const [s, e] = dow.split("-").map(Number);
|
|
159
|
+
segments.push(`on ${DAY_NAMES[s]}-${DAY_NAMES[e]}`);
|
|
160
|
+
} else if (dow.includes(",")) {
|
|
161
|
+
const names = dow.split(",").map((d) => DAY_NAMES[parseInt(d)] || d);
|
|
162
|
+
segments.push(`on ${names.join(", ")}`);
|
|
163
|
+
} else {
|
|
164
|
+
segments.push(`on ${DAY_NAMES[parseInt(dow)] || dow}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return segments.length ? segments[0].charAt(0).toUpperCase() + segments.join(", ").slice(1) : "";
|
|
169
|
+
}
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<script lang="ts">
|
|
173
|
+
import { CronParser } from "@marianmeres/cron";
|
|
174
|
+
import {
|
|
175
|
+
validate as validateAction,
|
|
176
|
+
type ValidationResult,
|
|
177
|
+
} from "../../actions/validate.svelte.js";
|
|
178
|
+
import { tooltip } from "../../actions/index.js";
|
|
179
|
+
import { iconList, iconSlidersHorizontal } from "../../icons/index.js";
|
|
180
|
+
import { getId } from "../../utils/get-id.js";
|
|
181
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
182
|
+
import InputWrap from "../Input/_internal/InputWrap.svelte";
|
|
183
|
+
|
|
184
|
+
let {
|
|
185
|
+
value = $bindable("* * * * *"),
|
|
186
|
+
el = $bindable(),
|
|
187
|
+
id = getId(),
|
|
188
|
+
//
|
|
189
|
+
mode = $bindable<CronInputMode | undefined>("predefined"),
|
|
190
|
+
//
|
|
191
|
+
label,
|
|
192
|
+
description,
|
|
193
|
+
renderSize = "md",
|
|
194
|
+
required = false,
|
|
195
|
+
disabled = false,
|
|
196
|
+
validate,
|
|
197
|
+
class: classProp,
|
|
198
|
+
style,
|
|
199
|
+
//
|
|
200
|
+
labelLeft = false,
|
|
201
|
+
labelLeftWidth = "normal",
|
|
202
|
+
labelLeftBreakpoint = 480,
|
|
203
|
+
labelAfter,
|
|
204
|
+
below,
|
|
205
|
+
//
|
|
206
|
+
showPresets = true,
|
|
207
|
+
showFields = true,
|
|
208
|
+
showRawInput = true,
|
|
209
|
+
showDescription = true,
|
|
210
|
+
showNextRun = true,
|
|
211
|
+
presets,
|
|
212
|
+
onchange,
|
|
213
|
+
//
|
|
214
|
+
classLabel,
|
|
215
|
+
classLabelBox,
|
|
216
|
+
classInputBox,
|
|
217
|
+
classInputBoxWrap,
|
|
218
|
+
classInputBoxWrapInvalid,
|
|
219
|
+
classDescBox,
|
|
220
|
+
classBelowBox,
|
|
221
|
+
classFields,
|
|
222
|
+
classField,
|
|
223
|
+
classFieldLabel,
|
|
224
|
+
classFieldInput,
|
|
225
|
+
classPreset,
|
|
226
|
+
classRaw,
|
|
227
|
+
classSummary,
|
|
228
|
+
classToggleButton,
|
|
229
|
+
}: Props = $props();
|
|
230
|
+
|
|
231
|
+
// Mode toggle
|
|
232
|
+
const hasModeToggle = $derived(mode !== undefined);
|
|
233
|
+
|
|
234
|
+
function toggleMode() {
|
|
235
|
+
if (mode === "predefined") mode = "manual";
|
|
236
|
+
else mode = "predefined";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Effective show flags — mode overrides explicit props when defined
|
|
240
|
+
let _showPresets = $derived(
|
|
241
|
+
hasModeToggle ? mode === "predefined" : showPresets
|
|
242
|
+
);
|
|
243
|
+
let _showFields = $derived(
|
|
244
|
+
hasModeToggle ? mode === "manual" : showFields
|
|
245
|
+
);
|
|
246
|
+
let _showRawInput = $derived(
|
|
247
|
+
hasModeToggle ? false : showRawInput
|
|
248
|
+
);
|
|
249
|
+
let _showDescription = $derived(
|
|
250
|
+
hasModeToggle ? mode === "manual" : showDescription
|
|
251
|
+
);
|
|
252
|
+
let _showNextRun = $derived(
|
|
253
|
+
hasModeToggle ? mode === "manual" : showNextRun
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const BTN_CLS = [
|
|
257
|
+
"toggle-btn",
|
|
258
|
+
"px-2 rounded-r block",
|
|
259
|
+
"min-w-[44px] min-h-[44px]",
|
|
260
|
+
"flex items-center justify-center",
|
|
261
|
+
].join(" ");
|
|
262
|
+
|
|
263
|
+
// Internal field state
|
|
264
|
+
let fields = $state<Record<FieldKey, string>>({
|
|
265
|
+
minute: "*",
|
|
266
|
+
hour: "*",
|
|
267
|
+
dayOfMonth: "*",
|
|
268
|
+
month: "*",
|
|
269
|
+
dayOfWeek: "*",
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
let rawValue = $state("* * * * *");
|
|
273
|
+
let selectedPreset = $state("");
|
|
274
|
+
let error = $state("");
|
|
275
|
+
|
|
276
|
+
// Validation integration
|
|
277
|
+
let validation: ValidationResult | undefined = $state();
|
|
278
|
+
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
279
|
+
|
|
280
|
+
// Track sync source to avoid loops
|
|
281
|
+
let _syncSource: "value" | "fields" | "raw" | "preset" | "" = "";
|
|
282
|
+
|
|
283
|
+
const _presets = $derived(presets ?? DEFAULT_PRESETS);
|
|
284
|
+
|
|
285
|
+
// Combine fields into expression
|
|
286
|
+
function fieldsToExpression(): string {
|
|
287
|
+
return FIELD_DEFS.map((d) => fields[d.key] || "*").join(" ");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Split expression into fields
|
|
291
|
+
function expressionToFields(expr: string) {
|
|
292
|
+
const parts = expr.trim().split(/\s+/);
|
|
293
|
+
if (parts.length === 5) {
|
|
294
|
+
FIELD_DEFS.forEach((d, i) => {
|
|
295
|
+
fields[d.key] = parts[i];
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Match current expression to a preset
|
|
301
|
+
function matchPreset(expr: string): string {
|
|
302
|
+
const normalized = expr.trim().replace(/\s+/g, " ");
|
|
303
|
+
return _presets.find((p) => p.value === normalized)?.value ?? "";
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Validate expression using CronParser
|
|
307
|
+
function validateExpression(expr: string): boolean {
|
|
308
|
+
try {
|
|
309
|
+
new CronParser(expr);
|
|
310
|
+
error = "";
|
|
311
|
+
validation = undefined;
|
|
312
|
+
return true;
|
|
313
|
+
} catch (e: any) {
|
|
314
|
+
error = e.message || "Invalid cron expression";
|
|
315
|
+
validation = { valid: false, message: error };
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Sync external value → internal state
|
|
321
|
+
$effect(() => {
|
|
322
|
+
const v = value;
|
|
323
|
+
if (_syncSource === "fields" || _syncSource === "raw" || _syncSource === "preset") {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
_syncSource = "value";
|
|
327
|
+
expressionToFields(v);
|
|
328
|
+
rawValue = v;
|
|
329
|
+
selectedPreset = matchPreset(v);
|
|
330
|
+
validateExpression(v);
|
|
331
|
+
_syncSource = "";
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Handle field input changes
|
|
335
|
+
function onFieldInput() {
|
|
336
|
+
_syncSource = "fields";
|
|
337
|
+
const expr = fieldsToExpression();
|
|
338
|
+
rawValue = expr;
|
|
339
|
+
selectedPreset = matchPreset(expr);
|
|
340
|
+
const valid = validateExpression(expr);
|
|
341
|
+
if (valid) {
|
|
342
|
+
value = expr;
|
|
343
|
+
}
|
|
344
|
+
onchange?.(expr, valid);
|
|
345
|
+
_syncSource = "";
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Handle raw input changes
|
|
349
|
+
function onRawInput() {
|
|
350
|
+
_syncSource = "raw";
|
|
351
|
+
const expr = rawValue;
|
|
352
|
+
expressionToFields(expr);
|
|
353
|
+
selectedPreset = matchPreset(expr);
|
|
354
|
+
const valid = validateExpression(expr);
|
|
355
|
+
if (valid) {
|
|
356
|
+
value = expr;
|
|
357
|
+
}
|
|
358
|
+
onchange?.(expr, valid);
|
|
359
|
+
_syncSource = "";
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Handle preset selection
|
|
363
|
+
function onPresetChange() {
|
|
364
|
+
if (!selectedPreset) return;
|
|
365
|
+
_syncSource = "preset";
|
|
366
|
+
const expr = selectedPreset;
|
|
367
|
+
expressionToFields(expr);
|
|
368
|
+
rawValue = expr;
|
|
369
|
+
const valid = validateExpression(expr);
|
|
370
|
+
if (valid) {
|
|
371
|
+
value = expr;
|
|
372
|
+
}
|
|
373
|
+
onchange?.(expr, valid);
|
|
374
|
+
_syncSource = "";
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// When only presets are visible, render as a plain select (no extra padding/border)
|
|
378
|
+
let presetsOnly = $derived(_showPresets && !_showFields && !_showRawInput && !_showDescription && !_showNextRun);
|
|
379
|
+
|
|
380
|
+
// Minute tick — triggers re-evaluation of "Next: ..." every 60s
|
|
381
|
+
let _tick = $state(0);
|
|
382
|
+
$effect(() => {
|
|
383
|
+
if (!_showNextRun) return;
|
|
384
|
+
const id = setInterval(() => _tick++, 60_000);
|
|
385
|
+
return () => clearInterval(id);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Human-readable description
|
|
389
|
+
let humanDescription = $derived.by(() => {
|
|
390
|
+
if (!_showDescription && !_showNextRun) return "";
|
|
391
|
+
const desc = _showDescription ? cronToHuman(rawValue) : "";
|
|
392
|
+
if (!_showNextRun || error) return desc;
|
|
393
|
+
// read _tick to create reactive dependency
|
|
394
|
+
void _tick;
|
|
395
|
+
try {
|
|
396
|
+
const parser = new CronParser(rawValue);
|
|
397
|
+
const next = parser.getNextRun();
|
|
398
|
+
const fmt = next.toLocaleString("sv-SE", {
|
|
399
|
+
year: "numeric",
|
|
400
|
+
month: "2-digit",
|
|
401
|
+
day: "2-digit",
|
|
402
|
+
hour: "2-digit",
|
|
403
|
+
minute: "2-digit",
|
|
404
|
+
});
|
|
405
|
+
return desc ? `${desc}. Next: ${fmt}` : `Next: ${fmt}`;
|
|
406
|
+
} catch {
|
|
407
|
+
return desc;
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
</script>
|
|
411
|
+
|
|
412
|
+
<div bind:this={el}>
|
|
413
|
+
<InputWrap
|
|
414
|
+
{description}
|
|
415
|
+
class={classProp}
|
|
416
|
+
size={renderSize}
|
|
417
|
+
{id}
|
|
418
|
+
{label}
|
|
419
|
+
{labelAfter}
|
|
420
|
+
{below}
|
|
421
|
+
{required}
|
|
422
|
+
{disabled}
|
|
423
|
+
{labelLeft}
|
|
424
|
+
{labelLeftWidth}
|
|
425
|
+
{labelLeftBreakpoint}
|
|
426
|
+
{classLabel}
|
|
427
|
+
{classLabelBox}
|
|
428
|
+
{classInputBox}
|
|
429
|
+
{classInputBoxWrap}
|
|
430
|
+
classInputBoxWrapInvalid={twMerge(classInputBoxWrapInvalid)}
|
|
431
|
+
{classDescBox}
|
|
432
|
+
{classBelowBox}
|
|
433
|
+
{validation}
|
|
434
|
+
{style}
|
|
435
|
+
>
|
|
436
|
+
<div class="w-full flex">
|
|
437
|
+
<div
|
|
438
|
+
class={twMerge("stuic-cron-input-content", error && "has-error")}
|
|
439
|
+
data-presets-only={presetsOnly ? "" : undefined}
|
|
440
|
+
>
|
|
441
|
+
{#if _showPresets}
|
|
442
|
+
<select
|
|
443
|
+
class={twMerge("stuic-cron-input-preset", classPreset)}
|
|
444
|
+
bind:value={selectedPreset}
|
|
445
|
+
onchange={onPresetChange}
|
|
446
|
+
{disabled}
|
|
447
|
+
>
|
|
448
|
+
<option value="">Custom</option>
|
|
449
|
+
{#each _presets as p}
|
|
450
|
+
<option value={p.value}>{p.label}</option>
|
|
451
|
+
{/each}
|
|
452
|
+
</select>
|
|
453
|
+
{/if}
|
|
454
|
+
|
|
455
|
+
{#if _showFields}
|
|
456
|
+
<div class={twMerge("stuic-cron-input-fields", classFields)}>
|
|
457
|
+
{#each FIELD_DEFS as def}
|
|
458
|
+
<div class={twMerge("stuic-cron-input-field", classField)}>
|
|
459
|
+
<span class={twMerge("stuic-cron-input-field-label", classFieldLabel)}>
|
|
460
|
+
{def.label}
|
|
461
|
+
</span>
|
|
462
|
+
<input
|
|
463
|
+
type="text"
|
|
464
|
+
class={twMerge("stuic-cron-input-field-input", classFieldInput)}
|
|
465
|
+
bind:value={fields[def.key]}
|
|
466
|
+
oninput={onFieldInput}
|
|
467
|
+
placeholder={def.placeholder}
|
|
468
|
+
{disabled}
|
|
469
|
+
autocomplete="off"
|
|
470
|
+
spellcheck={false}
|
|
471
|
+
/>
|
|
472
|
+
</div>
|
|
473
|
+
{/each}
|
|
474
|
+
</div>
|
|
475
|
+
{/if}
|
|
476
|
+
|
|
477
|
+
{#if _showRawInput}
|
|
478
|
+
<input
|
|
479
|
+
type="text"
|
|
480
|
+
class={twMerge("stuic-cron-input-raw", classRaw)}
|
|
481
|
+
bind:value={rawValue}
|
|
482
|
+
oninput={onRawInput}
|
|
483
|
+
placeholder="* * * * *"
|
|
484
|
+
{disabled}
|
|
485
|
+
autocomplete="off"
|
|
486
|
+
spellcheck={false}
|
|
487
|
+
/>
|
|
488
|
+
{/if}
|
|
489
|
+
|
|
490
|
+
{#if (_showDescription || _showNextRun) && humanDescription}
|
|
491
|
+
<div class={twMerge("stuic-cron-input-summary", classSummary)}>
|
|
492
|
+
{humanDescription}
|
|
493
|
+
</div>
|
|
494
|
+
{/if}
|
|
495
|
+
|
|
496
|
+
</div>
|
|
497
|
+
{#if hasModeToggle}
|
|
498
|
+
<button
|
|
499
|
+
type="button"
|
|
500
|
+
class={twMerge(BTN_CLS, classToggleButton)}
|
|
501
|
+
onclick={toggleMode}
|
|
502
|
+
{disabled}
|
|
503
|
+
use:tooltip={() => ({
|
|
504
|
+
enabled: true,
|
|
505
|
+
content: mode === "predefined" ? "Manual input" : "Predefined presets",
|
|
506
|
+
})}
|
|
507
|
+
>
|
|
508
|
+
{#if mode === "predefined"}
|
|
509
|
+
{@html iconSlidersHorizontal({ size: 19 })}
|
|
510
|
+
{:else}
|
|
511
|
+
{@html iconList({ size: 19 })}
|
|
512
|
+
{/if}
|
|
513
|
+
</button>
|
|
514
|
+
{/if}
|
|
515
|
+
</div>
|
|
516
|
+
</InputWrap>
|
|
517
|
+
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
3
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
4
|
+
type SnippetWithId = Snippet<[{
|
|
5
|
+
id: string;
|
|
6
|
+
}]>;
|
|
7
|
+
export interface CronPreset {
|
|
8
|
+
label: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}
|
|
11
|
+
export type CronInputMode = "predefined" | "manual";
|
|
12
|
+
export interface Props {
|
|
13
|
+
value?: string;
|
|
14
|
+
el?: HTMLElement;
|
|
15
|
+
id?: string;
|
|
16
|
+
mode?: CronInputMode;
|
|
17
|
+
label?: SnippetWithId | THC;
|
|
18
|
+
description?: SnippetWithId | THC;
|
|
19
|
+
renderSize?: "sm" | "md" | "lg" | string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
validate?: boolean | Omit<ValidateOptions, "setValidationResult">;
|
|
23
|
+
class?: string;
|
|
24
|
+
style?: string;
|
|
25
|
+
labelLeft?: boolean;
|
|
26
|
+
labelLeftWidth?: "normal" | "wide";
|
|
27
|
+
labelLeftBreakpoint?: number;
|
|
28
|
+
labelAfter?: SnippetWithId | THC;
|
|
29
|
+
below?: SnippetWithId | THC;
|
|
30
|
+
showPresets?: boolean;
|
|
31
|
+
showFields?: boolean;
|
|
32
|
+
showRawInput?: boolean;
|
|
33
|
+
showDescription?: boolean;
|
|
34
|
+
showNextRun?: boolean;
|
|
35
|
+
presets?: CronPreset[];
|
|
36
|
+
onchange?: (expression: string, valid: boolean) => void;
|
|
37
|
+
classLabel?: string;
|
|
38
|
+
classLabelBox?: string;
|
|
39
|
+
classInputBox?: string;
|
|
40
|
+
classInputBoxWrap?: string;
|
|
41
|
+
classInputBoxWrapInvalid?: string;
|
|
42
|
+
classDescBox?: string;
|
|
43
|
+
classBelowBox?: string;
|
|
44
|
+
classFields?: string;
|
|
45
|
+
classField?: string;
|
|
46
|
+
classFieldLabel?: string;
|
|
47
|
+
classFieldInput?: string;
|
|
48
|
+
classPreset?: string;
|
|
49
|
+
classRaw?: string;
|
|
50
|
+
classSummary?: string;
|
|
51
|
+
classToggleButton?: string;
|
|
52
|
+
}
|
|
53
|
+
export declare const DEFAULT_PRESETS: CronPreset[];
|
|
54
|
+
declare const CronInput: import("svelte").Component<Props, {}, "el" | "value" | "mode">;
|
|
55
|
+
type CronInput = ReturnType<typeof CronInput>;
|
|
56
|
+
export default CronInput;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A reactive helper that parses a cron expression and computes the next run time,
|
|
3
|
+
* updating automatically every minute.
|
|
4
|
+
*/
|
|
5
|
+
export declare class CronNextRun {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(expression?: string);
|
|
8
|
+
get expression(): string;
|
|
9
|
+
set expression(v: string);
|
|
10
|
+
/** The next Date when the cron expression matches, or null if invalid. */
|
|
11
|
+
get nextRun(): Date | null;
|
|
12
|
+
/** Formatted next run string (YYYY-MM-DD HH:MM), or empty string if invalid. */
|
|
13
|
+
get nextRunFormatted(): string;
|
|
14
|
+
/** Whether the current expression is valid. */
|
|
15
|
+
get valid(): boolean;
|
|
16
|
+
/** Stop the internal timer. Call when no longer needed. */
|
|
17
|
+
destroy(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { CronParser } from "@marianmeres/cron";
|
|
2
|
+
/**
|
|
3
|
+
* A reactive helper that parses a cron expression and computes the next run time,
|
|
4
|
+
* updating automatically every minute.
|
|
5
|
+
*/
|
|
6
|
+
export class CronNextRun {
|
|
7
|
+
#expression = $state("");
|
|
8
|
+
#tick = $state(0);
|
|
9
|
+
#interval;
|
|
10
|
+
constructor(expression = "* * * * *") {
|
|
11
|
+
this.#expression = expression;
|
|
12
|
+
this.#interval = setInterval(() => this.#tick++, 60_000);
|
|
13
|
+
}
|
|
14
|
+
get expression() {
|
|
15
|
+
return this.#expression;
|
|
16
|
+
}
|
|
17
|
+
set expression(v) {
|
|
18
|
+
this.#expression = v;
|
|
19
|
+
}
|
|
20
|
+
/** The next Date when the cron expression matches, or null if invalid. */
|
|
21
|
+
get nextRun() {
|
|
22
|
+
// read tick to create reactive dependency
|
|
23
|
+
void this.#tick;
|
|
24
|
+
try {
|
|
25
|
+
const parser = new CronParser(this.#expression);
|
|
26
|
+
return parser.getNextRun();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Formatted next run string (YYYY-MM-DD HH:MM), or empty string if invalid. */
|
|
33
|
+
get nextRunFormatted() {
|
|
34
|
+
const next = this.nextRun;
|
|
35
|
+
if (!next)
|
|
36
|
+
return "";
|
|
37
|
+
// the "sv-SE" is a trick/hack to get ISO 8601-ish local time (not UTC)
|
|
38
|
+
return next.toLocaleString("sv-SE", {
|
|
39
|
+
year: "numeric",
|
|
40
|
+
month: "2-digit",
|
|
41
|
+
day: "2-digit",
|
|
42
|
+
hour: "2-digit",
|
|
43
|
+
minute: "2-digit",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Whether the current expression is valid. */
|
|
47
|
+
get valid() {
|
|
48
|
+
try {
|
|
49
|
+
new CronParser(this.#expression);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** Stop the internal timer. Call when no longer needed. */
|
|
57
|
+
destroy() {
|
|
58
|
+
if (this.#interval) {
|
|
59
|
+
clearInterval(this.#interval);
|
|
60
|
+
this.#interval = undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
CRON INPUT COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-cron-input-fields-gap: 1rem; }
|
|
4
|
+
Override locally: <CronInput style="--stuic-cron-input-fields-gap: 1rem;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
/* prettier-ignore */
|
|
8
|
+
:root {
|
|
9
|
+
--stuic-cron-input-fields-gap: 0.375rem;
|
|
10
|
+
--stuic-cron-input-section-gap: 0.25rem;
|
|
11
|
+
--stuic-cron-input-field-label-text: var(--stuic-color-muted-foreground);
|
|
12
|
+
--stuic-cron-input-summary-text: var(--stuic-color-muted-foreground);
|
|
13
|
+
--stuic-cron-input-error-text: var(--stuic-color-destructive);
|
|
14
|
+
--stuic-cron-input-field-bg: var(--stuic-color-background);
|
|
15
|
+
--stuic-cron-input-field-border: var(--stuic-color-border);
|
|
16
|
+
--stuic-cron-input-field-border-focus: var(--stuic-color-primary);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@layer components {
|
|
20
|
+
/* Content wrapper inside InputWrap — must fill the flex parent (.input-wrap > .flex) */
|
|
21
|
+
.stuic-cron-input-content {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
gap: var(--stuic-cron-input-section-gap);
|
|
25
|
+
flex: 1;
|
|
26
|
+
min-width: 0;
|
|
27
|
+
padding: 0.5rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* No padding in presets-only mode (the select handles its own) */
|
|
31
|
+
.stuic-cron-input-content[data-presets-only] {
|
|
32
|
+
padding: 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* Preset select — styled as inner sub-input by default */
|
|
36
|
+
.stuic-cron-input-preset {
|
|
37
|
+
display: block;
|
|
38
|
+
width: 100%;
|
|
39
|
+
appearance: none;
|
|
40
|
+
-webkit-appearance: none;
|
|
41
|
+
border: 1px solid var(--stuic-cron-input-field-border);
|
|
42
|
+
border-radius: var(--stuic-input-radius);
|
|
43
|
+
background: var(--stuic-cron-input-field-bg);
|
|
44
|
+
color: var(--stuic-input-text);
|
|
45
|
+
font-family: var(--stuic-input-font-family);
|
|
46
|
+
font-size: var(--stuic-input-font-size-sm);
|
|
47
|
+
padding: 0.375rem 2rem 0.375rem 0.5rem;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
50
|
+
background-position: right 0.5rem center;
|
|
51
|
+
background-repeat: no-repeat;
|
|
52
|
+
background-size: 1.5em 1.5em;
|
|
53
|
+
transition: border-color var(--stuic-input-transition);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.stuic-cron-input-preset:focus {
|
|
57
|
+
outline: none;
|
|
58
|
+
border-color: var(--stuic-cron-input-field-border-focus);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.stuic-cron-input-preset:disabled {
|
|
62
|
+
cursor: not-allowed;
|
|
63
|
+
opacity: 0.5;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Presets-only mode: select renders like a native FieldSelect (no inner border) */
|
|
67
|
+
.stuic-cron-input-content[data-presets-only] .stuic-cron-input-preset {
|
|
68
|
+
border: 0;
|
|
69
|
+
border-radius: 0;
|
|
70
|
+
background-color: transparent;
|
|
71
|
+
background-position: right 0.5rem center;
|
|
72
|
+
background-size: 1.5em 1.5em;
|
|
73
|
+
padding: var(--stuic-input-padding-y-md) var(--stuic-input-padding-x-md);
|
|
74
|
+
padding-right: 2.5rem;
|
|
75
|
+
font-size: var(--stuic-input-font-size-md);
|
|
76
|
+
min-height: var(--stuic-input-min-height-md);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.stuic-input[data-size="sm"]
|
|
80
|
+
.stuic-cron-input-content[data-presets-only]
|
|
81
|
+
.stuic-cron-input-preset {
|
|
82
|
+
padding: var(--stuic-input-padding-y-sm) var(--stuic-input-padding-x-sm);
|
|
83
|
+
padding-right: 2.5rem;
|
|
84
|
+
font-size: var(--stuic-input-font-size-sm);
|
|
85
|
+
min-height: var(--stuic-input-min-height-sm);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.stuic-input[data-size="lg"]
|
|
89
|
+
.stuic-cron-input-content[data-presets-only]
|
|
90
|
+
.stuic-cron-input-preset {
|
|
91
|
+
padding: var(--stuic-input-padding-y-lg) var(--stuic-input-padding-x-lg);
|
|
92
|
+
padding-right: 2.5rem;
|
|
93
|
+
font-size: var(--stuic-input-font-size-lg);
|
|
94
|
+
min-height: var(--stuic-input-min-height-lg);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.stuic-cron-input-content[data-presets-only] .stuic-cron-input-preset:focus {
|
|
98
|
+
border: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* 5-column grid for fields */
|
|
102
|
+
.stuic-cron-input-fields {
|
|
103
|
+
display: grid;
|
|
104
|
+
grid-template-columns: repeat(5, 1fr);
|
|
105
|
+
gap: var(--stuic-cron-input-fields-gap);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/* Individual field */
|
|
109
|
+
.stuic-cron-input-field {
|
|
110
|
+
display: flex;
|
|
111
|
+
flex-direction: column;
|
|
112
|
+
gap: 0.125rem;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Field label */
|
|
116
|
+
.stuic-cron-input-field-label {
|
|
117
|
+
font-size: 0.6875rem;
|
|
118
|
+
color: var(--stuic-cron-input-field-label-text);
|
|
119
|
+
text-transform: uppercase;
|
|
120
|
+
text-align: center;
|
|
121
|
+
letter-spacing: 0.05em;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Field input — must match base specificity of .stuic-input input:not(…)×4 */
|
|
125
|
+
.stuic-input
|
|
126
|
+
input.stuic-cron-input-field-input:not([type="checkbox"]):not([type="radio"]):not(
|
|
127
|
+
[type="range"]
|
|
128
|
+
):not([type="file"]) {
|
|
129
|
+
display: block;
|
|
130
|
+
width: 100%;
|
|
131
|
+
border: 1px solid var(--stuic-cron-input-field-border);
|
|
132
|
+
border-radius: var(--stuic-input-radius);
|
|
133
|
+
background: var(--stuic-cron-input-field-bg);
|
|
134
|
+
color: var(--stuic-input-text);
|
|
135
|
+
font-family: var(--font-mono, monospace);
|
|
136
|
+
font-size: var(--stuic-input-font-size-sm);
|
|
137
|
+
padding: 0.25rem;
|
|
138
|
+
min-height: 0;
|
|
139
|
+
text-align: center;
|
|
140
|
+
transition: border-color var(--stuic-input-transition);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.stuic-cron-input-field-input:focus {
|
|
144
|
+
outline: none;
|
|
145
|
+
border-color: var(--stuic-cron-input-field-border-focus);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.stuic-cron-input-field-input:disabled {
|
|
149
|
+
cursor: not-allowed;
|
|
150
|
+
opacity: 0.5;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.stuic-cron-input-field-input::placeholder {
|
|
154
|
+
color: var(--stuic-input-placeholder);
|
|
155
|
+
opacity: 0.5;
|
|
156
|
+
font-family: var(--stuic-input-font-family);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Raw expression input */
|
|
160
|
+
.stuic-cron-input-raw {
|
|
161
|
+
display: block;
|
|
162
|
+
width: 100%;
|
|
163
|
+
border: 1px solid var(--stuic-cron-input-field-border);
|
|
164
|
+
border-radius: var(--stuic-input-radius);
|
|
165
|
+
background: var(--stuic-cron-input-field-bg);
|
|
166
|
+
color: var(--stuic-input-text);
|
|
167
|
+
font-family: var(--font-mono, monospace);
|
|
168
|
+
font-size: var(--stuic-input-font-size-sm);
|
|
169
|
+
padding: 0.375rem 0.5rem;
|
|
170
|
+
transition: border-color var(--stuic-input-transition);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.stuic-cron-input-raw:focus {
|
|
174
|
+
outline: none;
|
|
175
|
+
border-color: var(--stuic-cron-input-field-border-focus);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.stuic-cron-input-raw:disabled {
|
|
179
|
+
cursor: not-allowed;
|
|
180
|
+
opacity: 0.5;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.stuic-cron-input-raw::placeholder {
|
|
184
|
+
color: var(--stuic-input-placeholder);
|
|
185
|
+
opacity: 0.5;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* Summary / human description */
|
|
189
|
+
.stuic-cron-input-summary {
|
|
190
|
+
color: var(--stuic-cron-input-summary-text);
|
|
191
|
+
font-size: 0.8125rem;
|
|
192
|
+
line-height: 1.4;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Error message */
|
|
196
|
+
.stuic-cron-input-error {
|
|
197
|
+
color: var(--stuic-cron-input-error-text);
|
|
198
|
+
font-size: 0.8125rem;
|
|
199
|
+
line-height: 1.4;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Mode toggle button */
|
|
203
|
+
.stuic-cron-input-content + .toggle-btn {
|
|
204
|
+
color: var(--stuic-input-localized-toggle-text);
|
|
205
|
+
transition:
|
|
206
|
+
background var(--stuic-input-transition),
|
|
207
|
+
color var(--stuic-input-transition);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.stuic-cron-input-content + .toggle-btn:hover:not(:disabled) {
|
|
211
|
+
color: var(--stuic-input-localized-toggle-text-hover);
|
|
212
|
+
background: var(--stuic-input-localized-toggle-hover-bg);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* Responsive: stack on narrow */
|
|
216
|
+
@media (max-width: 400px) {
|
|
217
|
+
.stuic-cron-input-fields {
|
|
218
|
+
grid-template-columns: repeat(3, 1fr);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
--stuic-dropdown-menu-padding: calc(var(--spacing) * 1);
|
|
11
11
|
--stuic-dropdown-menu-gap: calc(var(--spacing) * 0.5);
|
|
12
12
|
--stuic-dropdown-menu-min-width: 12rem;
|
|
13
|
-
--stuic-dropdown-menu-transition:
|
|
13
|
+
--stuic-dropdown-menu-transition: 100ms;
|
|
14
14
|
|
|
15
15
|
/* Dropdown container colors */
|
|
16
16
|
--stuic-dropdown-menu-bg: var(--stuic-color-background);
|
package/dist/icons/index.d.ts
CHANGED
|
@@ -39,8 +39,10 @@ export { iconLucideGrip as iconGrip } from "@marianmeres/icons-fns/lucide/iconLu
|
|
|
39
39
|
export { iconLucideGripHorizontal as iconGripHorizontal } from "@marianmeres/icons-fns/lucide/iconLucideGripHorizontal.js";
|
|
40
40
|
export { iconLucideGripVertical as iconGripVertical } from "@marianmeres/icons-fns/lucide/iconLucideGripVertical.js";
|
|
41
41
|
export { iconLucideLanguages as iconLanguages } from "@marianmeres/icons-fns/lucide/iconLucideLanguages.js";
|
|
42
|
+
export { iconLucideList as iconList } from "@marianmeres/icons-fns/lucide/iconLucideList.js";
|
|
42
43
|
export { iconLucideMenu as iconMenu } from "@marianmeres/icons-fns/lucide/iconLucideMenu.js";
|
|
43
44
|
export { iconLucideSearch as iconSearch } from "@marianmeres/icons-fns/lucide/iconLucideSearch.js";
|
|
45
|
+
export { iconLucideSlidersHorizontal as iconSlidersHorizontal } from "@marianmeres/icons-fns/lucide/iconLucideSlidersHorizontal.js";
|
|
44
46
|
export { iconLucideSettings as iconSettings } from "@marianmeres/icons-fns/lucide/iconLucideSettings.js";
|
|
45
47
|
export { iconLucideSquare as iconSquare } from "@marianmeres/icons-fns/lucide/iconLucideSquare.js";
|
|
46
48
|
export { iconLucideUser as iconUser } from "@marianmeres/icons-fns/lucide/iconLucideUser.js";
|
package/dist/icons/index.js
CHANGED
|
@@ -44,8 +44,10 @@ export { iconLucideGrip as iconGrip } from "@marianmeres/icons-fns/lucide/iconLu
|
|
|
44
44
|
export { iconLucideGripHorizontal as iconGripHorizontal } from "@marianmeres/icons-fns/lucide/iconLucideGripHorizontal.js";
|
|
45
45
|
export { iconLucideGripVertical as iconGripVertical } from "@marianmeres/icons-fns/lucide/iconLucideGripVertical.js";
|
|
46
46
|
export { iconLucideLanguages as iconLanguages } from "@marianmeres/icons-fns/lucide/iconLucideLanguages.js";
|
|
47
|
+
export { iconLucideList as iconList } from "@marianmeres/icons-fns/lucide/iconLucideList.js";
|
|
47
48
|
export { iconLucideMenu as iconMenu } from "@marianmeres/icons-fns/lucide/iconLucideMenu.js";
|
|
48
49
|
export { iconLucideSearch as iconSearch } from "@marianmeres/icons-fns/lucide/iconLucideSearch.js";
|
|
50
|
+
export { iconLucideSlidersHorizontal as iconSlidersHorizontal } from "@marianmeres/icons-fns/lucide/iconLucideSlidersHorizontal.js";
|
|
49
51
|
export { iconLucideSettings as iconSettings } from "@marianmeres/icons-fns/lucide/iconLucideSettings.js";
|
|
50
52
|
export { iconLucideSquare as iconSquare } from "@marianmeres/icons-fns/lucide/iconLucideSquare.js";
|
|
51
53
|
export { iconLucideUser as iconUser } from "@marianmeres/icons-fns/lucide/iconLucideUser.js";
|
package/dist/index.css
CHANGED
|
@@ -37,6 +37,7 @@ In practice:
|
|
|
37
37
|
@import "./components/LoginForm/index.css";
|
|
38
38
|
@import "./components/Checkout/index.css";
|
|
39
39
|
@import "./components/CommandMenu/index.css";
|
|
40
|
+
@import "./components/CronInput/index.css";
|
|
40
41
|
@import "./components/DataTable/index.css";
|
|
41
42
|
@import "./components/DismissibleMessage/index.css";
|
|
42
43
|
@import "./components/DropdownMenu/index.css";
|
package/dist/index.d.ts
CHANGED
|
@@ -37,6 +37,7 @@ export * from "./components/Checkout/index.js";
|
|
|
37
37
|
export * from "./components/Collapsible/index.js";
|
|
38
38
|
export * from "./components/ColorScheme/index.js";
|
|
39
39
|
export * from "./components/CommandMenu/index.js";
|
|
40
|
+
export * from "./components/CronInput/index.js";
|
|
40
41
|
export * from "./components/DataTable/index.js";
|
|
41
42
|
export * from "./components/DismissibleMessage/index.js";
|
|
42
43
|
export * from "./components/Drawer/index.js";
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ export * from "./components/Checkout/index.js";
|
|
|
38
38
|
export * from "./components/Collapsible/index.js";
|
|
39
39
|
export * from "./components/ColorScheme/index.js";
|
|
40
40
|
export * from "./components/CommandMenu/index.js";
|
|
41
|
+
export * from "./components/CronInput/index.js";
|
|
41
42
|
export * from "./components/DataTable/index.js";
|
|
42
43
|
export * from "./components/DismissibleMessage/index.js";
|
|
43
44
|
export * from "./components/Drawer/index.js";
|
package/package.json
CHANGED
|
@@ -1,101 +1,100 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
2
|
+
"name": "@marianmeres/stuic",
|
|
3
|
+
"version": "3.47.2",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist",
|
|
6
|
+
"!dist/**/*.test.*",
|
|
7
|
+
"!dist/**/*.spec.*",
|
|
8
|
+
"docs",
|
|
9
|
+
"AGENTS.md",
|
|
10
|
+
"CLAUDE.md",
|
|
11
|
+
"API.md"
|
|
12
|
+
],
|
|
13
|
+
"sideEffects": [
|
|
14
|
+
"**/*.css"
|
|
15
|
+
],
|
|
16
|
+
"svelte": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"type": "module",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"svelte": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./utils": {
|
|
25
|
+
"types": "./dist/utils/index.d.ts",
|
|
26
|
+
"default": "./dist/utils/index.js"
|
|
27
|
+
},
|
|
28
|
+
"./themes/*": {
|
|
29
|
+
"types": "./dist/themes/*.d.ts",
|
|
30
|
+
"default": "./dist/themes/*.js"
|
|
31
|
+
},
|
|
32
|
+
"./themes/css/*": "./dist/themes/css/*",
|
|
33
|
+
"./phone-validation": {
|
|
34
|
+
"types": "./dist/components/Input/phone-validation.d.ts",
|
|
35
|
+
"default": "./dist/components/Input/phone-validation.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"svelte": "^5.0.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@eslint/js": "^9.39.4",
|
|
43
|
+
"@marianmeres/random-human-readable": "^1.6.1",
|
|
44
|
+
"@sveltejs/adapter-auto": "^4.0.0",
|
|
45
|
+
"@sveltejs/kit": "^2.55.0",
|
|
46
|
+
"@sveltejs/package": "^2.5.7",
|
|
47
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
|
|
48
|
+
"@tailwindcss/cli": "^4.2.1",
|
|
49
|
+
"@tailwindcss/forms": "^0.5.11",
|
|
50
|
+
"@tailwindcss/typography": "^0.5.19",
|
|
51
|
+
"@tailwindcss/vite": "^4.2.1",
|
|
52
|
+
"@types/node": "^25.5.0",
|
|
53
|
+
"dotenv": "^16.6.1",
|
|
54
|
+
"eslint": "^9.39.4",
|
|
55
|
+
"globals": "^16.5.0",
|
|
56
|
+
"prettier": "^3.8.1",
|
|
57
|
+
"prettier-plugin-svelte": "^3.5.1",
|
|
58
|
+
"publint": "^0.3.18",
|
|
59
|
+
"svelte": "^5.53.11",
|
|
60
|
+
"svelte-check": "^4.4.5",
|
|
61
|
+
"tailwindcss": "^4.2.1",
|
|
62
|
+
"tsx": "^4.21.0",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"typescript-eslint": "^8.57.0",
|
|
65
|
+
"vite": "^7.3.1",
|
|
66
|
+
"vitest": "^3.2.4"
|
|
67
|
+
},
|
|
68
|
+
"dependencies": {
|
|
69
|
+
"@marianmeres/clog": "^3.15.2",
|
|
70
|
+
"@marianmeres/cron": "^1.1.0",
|
|
71
|
+
"@marianmeres/icons-fns": "^5.0.0",
|
|
72
|
+
"@marianmeres/item-collection": "^1.3.5",
|
|
73
|
+
"@marianmeres/paging-store": "^2.0.2",
|
|
74
|
+
"@marianmeres/parse-boolean": "^2.0.5",
|
|
75
|
+
"@marianmeres/ticker": "^1.16.5",
|
|
76
|
+
"@marianmeres/tree": "^2.2.5",
|
|
77
|
+
"esm-env": "^1.2.2",
|
|
78
|
+
"libphonenumber-js": "^1.12.40",
|
|
79
|
+
"runed": "^0.23.4",
|
|
80
|
+
"tailwind-merge": "^3.5.0"
|
|
81
|
+
},
|
|
82
|
+
"scripts": {
|
|
83
|
+
"dev": "vite dev",
|
|
84
|
+
"build": "vite build && pnpm run prepack",
|
|
85
|
+
"preview": "vite preview",
|
|
86
|
+
"package": "pnpm run prepack",
|
|
87
|
+
"package:watch": "svelte-kit sync && svelte-package --watch && publint",
|
|
88
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
89
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
|
90
|
+
"format": "prettier --write .",
|
|
91
|
+
"lint": "eslint . && prettier --check .",
|
|
92
|
+
"test": "vitest --dir src/",
|
|
93
|
+
"svelte-check": "svelte-check",
|
|
94
|
+
"svelte-package": "svelte-package",
|
|
95
|
+
"rp": "pnpm run build && ./release.sh patch",
|
|
96
|
+
"rpm": "pnpm run build && ./release.sh minor",
|
|
97
|
+
"build:theme": "tsx scripts/generate-theme.ts",
|
|
98
|
+
"build:theme:all": "pnpm run build:theme --indir=src/lib/themes --outdir=src/lib/themes/css"
|
|
99
|
+
}
|
|
100
|
+
}
|