@marianmeres/stuic 3.66.1 → 3.68.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.
- package/dist/actions/autoscroll.d.ts +7 -0
- package/dist/actions/autoscroll.js +7 -0
- package/dist/actions/focus-trap.d.ts +7 -0
- package/dist/actions/focus-trap.js +8 -3
- package/dist/actions/typeahead.svelte.js +40 -4
- package/dist/components/Carousel/Carousel.svelte +9 -2
- package/dist/components/Carousel/README.md +8 -2
- package/dist/components/Cart/Cart.svelte +3 -0
- package/dist/components/Cart/README.md +18 -1
- package/dist/components/Checkout/CheckoutOrderReview.svelte +4 -14
- package/dist/components/Checkout/README.md +184 -0
- package/dist/components/Checkout/_internal/checkout-utils.d.ts +6 -0
- package/dist/components/Checkout/_internal/checkout-utils.js +24 -0
- package/dist/components/Checkout/index.d.ts +1 -1
- package/dist/components/Checkout/index.js +1 -1
- package/dist/components/CommandMenu/CommandMenu.svelte +23 -7
- package/dist/components/CommandMenu/CommandMenu.svelte.d.ts +2 -0
- package/dist/components/CronInput/CronInput.svelte +44 -9
- package/dist/components/CronInput/CronInput.svelte.d.ts +2 -0
- package/dist/components/CronInput/README.md +145 -0
- package/dist/components/CronInput/cron-next-run.svelte.d.ts +11 -0
- package/dist/components/CronInput/cron-next-run.svelte.js +11 -0
- package/dist/components/CronInput/index.css +0 -8
- package/dist/components/DataTable/DataTable.svelte +276 -83
- package/dist/components/DataTable/DataTable.svelte.d.ts +58 -6
- package/dist/components/DataTable/README.md +155 -25
- package/dist/components/DataTable/index.css +31 -0
- package/dist/components/DropdownMenu/DropdownMenu.svelte +43 -26
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +5 -1
- package/dist/components/DropdownMenu/README.md +37 -9
- package/dist/components/Input/FieldAssets.svelte +9 -7
- package/dist/components/Input/FieldAssets.svelte.d.ts +3 -7
- package/dist/components/Input/FieldFile.svelte +13 -7
- package/dist/components/Input/FieldFile.svelte.d.ts +4 -7
- package/dist/components/Input/FieldInput.svelte +10 -8
- package/dist/components/Input/FieldInput.svelte.d.ts +3 -8
- package/dist/components/Input/FieldInputLocalized.svelte +8 -7
- package/dist/components/Input/FieldInputLocalized.svelte.d.ts +2 -7
- package/dist/components/Input/FieldKeyValues.svelte +8 -7
- package/dist/components/Input/FieldKeyValues.svelte.d.ts +2 -7
- package/dist/components/Input/FieldLikeButton.svelte +9 -7
- package/dist/components/Input/FieldLikeButton.svelte.d.ts +3 -7
- package/dist/components/Input/FieldObject.svelte +8 -7
- package/dist/components/Input/FieldObject.svelte.d.ts +2 -7
- package/dist/components/Input/FieldOptions.svelte +9 -7
- package/dist/components/Input/FieldOptions.svelte.d.ts +3 -7
- package/dist/components/Input/FieldPhoneNumber.svelte +7 -8
- package/dist/components/Input/FieldPhoneNumber.svelte.d.ts +3 -8
- package/dist/components/Input/FieldSelect.svelte +9 -8
- package/dist/components/Input/FieldSelect.svelte.d.ts +3 -8
- package/dist/components/Input/FieldSwitch.svelte +9 -7
- package/dist/components/Input/FieldSwitch.svelte.d.ts +3 -7
- package/dist/components/Input/FieldTextarea.svelte +7 -8
- package/dist/components/Input/FieldTextarea.svelte.d.ts +3 -8
- package/dist/components/Input/README.md +20 -0
- package/dist/components/Input/_internal/InputWrap.svelte +2 -10
- package/dist/components/Input/_internal/InputWrap.svelte.d.ts +2 -10
- package/dist/components/Input/types.d.ts +28 -0
- package/dist/components/Nav/Nav.svelte +5 -4
- package/dist/components/Nav/Nav.svelte.d.ts +2 -2
- package/dist/components/Nav/README.md +2 -2
- package/dist/components/Nav/index.css +4 -0
- package/dist/components/Tree/README.md +189 -0
- package/dist/components/Tree/Tree.svelte +46 -2
- package/dist/components/Tree/Tree.svelte.d.ts +5 -0
- package/dist/utils/input-history.svelte.d.ts +12 -0
- package/dist/utils/input-history.svelte.js +12 -0
- package/dist/utils/observe-exists.svelte.d.ts +1 -0
- package/dist/utils/observe-exists.svelte.js +11 -3
- package/dist/utils/switch.svelte.d.ts +12 -0
- package/dist/utils/switch.svelte.js +12 -1
- package/docs/architecture.md +0 -1
- package/docs/testing.md +72 -0
- package/docs/upgrading.md +281 -0
- package/package.json +12 -13
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
classOption?: string;
|
|
59
59
|
classOptionActive?: string;
|
|
60
60
|
showAllOnEmptyQ?: boolean;
|
|
61
|
+
/** Skip stuic base classes (user provides their own styling) */
|
|
62
|
+
unstyled?: boolean;
|
|
61
63
|
}
|
|
62
64
|
</script>
|
|
63
65
|
|
|
@@ -81,6 +83,7 @@
|
|
|
81
83
|
itemIdPropName = "id",
|
|
82
84
|
searchPlaceholder,
|
|
83
85
|
showAllOnEmptyQ,
|
|
86
|
+
unstyled = false,
|
|
84
87
|
}: Props = $props();
|
|
85
88
|
|
|
86
89
|
function _renderOptionLabel(item: Item): string {
|
|
@@ -138,10 +141,16 @@
|
|
|
138
141
|
_optionsColl.clear().addMany(res);
|
|
139
142
|
})
|
|
140
143
|
.catch((e) => {
|
|
144
|
+
// Surface errors only for the latest in-flight request
|
|
145
|
+
if (currentRequest !== fetchRequestId) return;
|
|
141
146
|
console.error(e);
|
|
142
147
|
notifications?.error(`${e}`);
|
|
143
148
|
})
|
|
144
|
-
.finally(() =>
|
|
149
|
+
.finally(() => {
|
|
150
|
+
// Only the latest request toggles the fetching state back off
|
|
151
|
+
// so the spinner doesn't flicker when a stale response arrives late.
|
|
152
|
+
if (currentRequest === fetchRequestId) isFetching = false;
|
|
153
|
+
});
|
|
145
154
|
});
|
|
146
155
|
|
|
147
156
|
//
|
|
@@ -263,14 +272,20 @@
|
|
|
263
272
|
>
|
|
264
273
|
{#snippet inputBefore()}
|
|
265
274
|
<div
|
|
266
|
-
class=
|
|
275
|
+
class={twMerge(
|
|
276
|
+
"flex flex-col items-center justify-center pl-3",
|
|
277
|
+
!unstyled && "stuic-command-menu-muted"
|
|
278
|
+
)}
|
|
267
279
|
>
|
|
268
280
|
{@html iconSearch({ size: 19 })}
|
|
269
281
|
</div>
|
|
270
282
|
{/snippet}
|
|
271
283
|
{#snippet inputAfter()}
|
|
272
284
|
<div
|
|
273
|
-
class=
|
|
285
|
+
class={twMerge(
|
|
286
|
+
"flex pl-2 items-center justify-center",
|
|
287
|
+
!unstyled && "stuic-command-menu-placeholder"
|
|
288
|
+
)}
|
|
274
289
|
>
|
|
275
290
|
{#if isFetching}
|
|
276
291
|
<Spinner class="w-4" />
|
|
@@ -302,7 +317,7 @@
|
|
|
302
317
|
{#if options.size}
|
|
303
318
|
<div
|
|
304
319
|
class={twMerge(
|
|
305
|
-
"stuic-command-menu-options",
|
|
320
|
+
!unstyled && "stuic-command-menu-options",
|
|
306
321
|
"block space-y-1 p-1",
|
|
307
322
|
"overflow-y-auto overflow-x-hidden mb-1",
|
|
308
323
|
"border-t"
|
|
@@ -314,11 +329,13 @@
|
|
|
314
329
|
aria-label="Search results"
|
|
315
330
|
>
|
|
316
331
|
{#each _normalize_and_group_options(options.items) as [_optgroup, _opts]}
|
|
317
|
-
<!-- {console.log(11111, _optgroup, _opts)} -->
|
|
318
332
|
<div class="p-1">
|
|
319
333
|
{#if _optgroup}
|
|
320
334
|
<div
|
|
321
|
-
class=
|
|
335
|
+
class={twMerge(
|
|
336
|
+
"mb-1 p-1 font-semibold uppercase tracking-wide",
|
|
337
|
+
!unstyled && "stuic-command-menu-group-header"
|
|
338
|
+
)}
|
|
322
339
|
>
|
|
323
340
|
{_optgroup}
|
|
324
341
|
</div>
|
|
@@ -327,7 +344,6 @@
|
|
|
327
344
|
{#each _opts as item (item[itemIdPropName])}
|
|
328
345
|
{@const active =
|
|
329
346
|
item[itemIdPropName] === options.active?.[itemIdPropName]}
|
|
330
|
-
<!-- {@const isSelected = false} -->
|
|
331
347
|
<li class:active role="presentation">
|
|
332
348
|
<ListItemButton
|
|
333
349
|
id={btn_id(item[itemIdPropName])}
|
|
@@ -16,6 +16,8 @@ export interface Props {
|
|
|
16
16
|
classOption?: string;
|
|
17
17
|
classOptionActive?: string;
|
|
18
18
|
showAllOnEmptyQ?: boolean;
|
|
19
|
+
/** Skip stuic base classes (user provides their own styling) */
|
|
20
|
+
unstyled?: boolean;
|
|
19
21
|
}
|
|
20
22
|
declare const CommandMenu: import("svelte").Component<Props, {
|
|
21
23
|
close: () => void;
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
value?: string;
|
|
18
18
|
el?: HTMLElement;
|
|
19
19
|
id?: string;
|
|
20
|
+
/** Opt out of stuic base classes for full styling control */
|
|
21
|
+
unstyled?: boolean;
|
|
20
22
|
|
|
21
23
|
// Mode toggle (overrides show* flags when defined)
|
|
22
24
|
mode?: CronInputMode;
|
|
@@ -185,6 +187,7 @@
|
|
|
185
187
|
value = $bindable("* * * * *"),
|
|
186
188
|
el = $bindable(),
|
|
187
189
|
id = getId(),
|
|
190
|
+
unstyled = false,
|
|
188
191
|
//
|
|
189
192
|
mode = $bindable<CronInputMode | undefined>("predefined"),
|
|
190
193
|
//
|
|
@@ -435,14 +438,21 @@
|
|
|
435
438
|
>
|
|
436
439
|
<div class="w-full flex">
|
|
437
440
|
<div
|
|
438
|
-
class={
|
|
441
|
+
class={unstyled
|
|
442
|
+
? error
|
|
443
|
+
? "has-error"
|
|
444
|
+
: undefined
|
|
445
|
+
: twMerge("stuic-cron-input-content", error && "has-error")}
|
|
439
446
|
data-presets-only={presetsOnly ? "" : undefined}
|
|
440
447
|
>
|
|
441
448
|
{#if _showPresets}
|
|
442
449
|
<select
|
|
443
|
-
class={
|
|
450
|
+
class={unstyled
|
|
451
|
+
? classPreset
|
|
452
|
+
: twMerge("stuic-cron-input-preset", classPreset)}
|
|
444
453
|
bind:value={selectedPreset}
|
|
445
454
|
onchange={onPresetChange}
|
|
455
|
+
aria-label="Cron schedule preset"
|
|
446
456
|
{disabled}
|
|
447
457
|
>
|
|
448
458
|
<option value="">Custom</option>
|
|
@@ -453,18 +463,33 @@
|
|
|
453
463
|
{/if}
|
|
454
464
|
|
|
455
465
|
{#if _showFields}
|
|
456
|
-
<div
|
|
466
|
+
<div
|
|
467
|
+
class={unstyled
|
|
468
|
+
? classFields
|
|
469
|
+
: twMerge("stuic-cron-input-fields", classFields)}
|
|
470
|
+
>
|
|
457
471
|
{#each FIELD_DEFS as def}
|
|
458
|
-
<div
|
|
459
|
-
|
|
472
|
+
<div
|
|
473
|
+
class={unstyled
|
|
474
|
+
? classField
|
|
475
|
+
: twMerge("stuic-cron-input-field", classField)}
|
|
476
|
+
>
|
|
477
|
+
<span
|
|
478
|
+
class={unstyled
|
|
479
|
+
? classFieldLabel
|
|
480
|
+
: twMerge("stuic-cron-input-field-label", classFieldLabel)}
|
|
481
|
+
>
|
|
460
482
|
{def.label}
|
|
461
483
|
</span>
|
|
462
484
|
<input
|
|
463
485
|
type="text"
|
|
464
|
-
class={
|
|
486
|
+
class={unstyled
|
|
487
|
+
? classFieldInput
|
|
488
|
+
: twMerge("stuic-cron-input-field-input", classFieldInput)}
|
|
465
489
|
bind:value={fields[def.key]}
|
|
466
490
|
oninput={onFieldInput}
|
|
467
491
|
placeholder={def.placeholder}
|
|
492
|
+
aria-label={`${def.label} (${def.placeholder})`}
|
|
468
493
|
{disabled}
|
|
469
494
|
autocomplete="off"
|
|
470
495
|
spellcheck={false}
|
|
@@ -477,10 +502,13 @@
|
|
|
477
502
|
{#if _showRawInput}
|
|
478
503
|
<input
|
|
479
504
|
type="text"
|
|
480
|
-
class={
|
|
505
|
+
class={unstyled
|
|
506
|
+
? classRaw
|
|
507
|
+
: twMerge("stuic-cron-input-raw", classRaw)}
|
|
481
508
|
bind:value={rawValue}
|
|
482
509
|
oninput={onRawInput}
|
|
483
510
|
placeholder="* * * * *"
|
|
511
|
+
aria-label="Raw cron expression"
|
|
484
512
|
{disabled}
|
|
485
513
|
autocomplete="off"
|
|
486
514
|
spellcheck={false}
|
|
@@ -488,7 +516,11 @@
|
|
|
488
516
|
{/if}
|
|
489
517
|
|
|
490
518
|
{#if (_showDescription || _showNextRun) && humanDescription}
|
|
491
|
-
<div
|
|
519
|
+
<div
|
|
520
|
+
class={unstyled
|
|
521
|
+
? classSummary
|
|
522
|
+
: twMerge("stuic-cron-input-summary", classSummary)}
|
|
523
|
+
>
|
|
492
524
|
{humanDescription}
|
|
493
525
|
</div>
|
|
494
526
|
{/if}
|
|
@@ -497,8 +529,11 @@
|
|
|
497
529
|
{#if hasModeToggle}
|
|
498
530
|
<button
|
|
499
531
|
type="button"
|
|
500
|
-
class={
|
|
532
|
+
class={unstyled
|
|
533
|
+
? classToggleButton
|
|
534
|
+
: twMerge(BTN_CLS, classToggleButton)}
|
|
501
535
|
onclick={toggleMode}
|
|
536
|
+
aria-label={mode === "predefined" ? "Switch to manual input" : "Switch to presets"}
|
|
502
537
|
{disabled}
|
|
503
538
|
use:tooltip={() => ({
|
|
504
539
|
enabled: true,
|
|
@@ -13,6 +13,8 @@ export interface Props {
|
|
|
13
13
|
value?: string;
|
|
14
14
|
el?: HTMLElement;
|
|
15
15
|
id?: string;
|
|
16
|
+
/** Opt out of stuic base classes for full styling control */
|
|
17
|
+
unstyled?: boolean;
|
|
16
18
|
mode?: CronInputMode;
|
|
17
19
|
label?: SnippetWithId | THC;
|
|
18
20
|
description?: SnippetWithId | THC;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# CronInput
|
|
2
|
+
|
|
3
|
+
A cron-expression editor with three progressive surfaces: **preset picker**, **5-field visual editor**, and **raw expression input** — plus a human-readable summary and the next-run preview. Backed by [`@marianmeres/cron`](https://www.npmjs.com/package/@marianmeres/cron).
|
|
4
|
+
|
|
5
|
+
The cron expression string is the single source of truth; field / preset / raw surfaces are all views over it. All three stay in sync automatically when the bound `value` changes.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
### Basic (preset picker + manual editor toggle)
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<script lang="ts">
|
|
13
|
+
import { CronInput } from "@marianmeres/stuic";
|
|
14
|
+
|
|
15
|
+
let expression = $state("0 9 * * 1-5");
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<CronInput bind:value={expression} label="Schedule" />
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Default `mode` is `"predefined"` — users see the preset picker with a toggle button to flip into `"manual"` (5 fields + raw + summary). Setting `mode={undefined}` removes the toggle and shows all surfaces that the `show*` flags enable.
|
|
22
|
+
|
|
23
|
+
### Flat layout (no toggle, explicit surfaces)
|
|
24
|
+
|
|
25
|
+
```svelte
|
|
26
|
+
<CronInput
|
|
27
|
+
bind:value={expression}
|
|
28
|
+
mode={undefined}
|
|
29
|
+
showPresets
|
|
30
|
+
showFields
|
|
31
|
+
showRawInput
|
|
32
|
+
showDescription
|
|
33
|
+
showNextRun
|
|
34
|
+
/>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Custom presets
|
|
38
|
+
|
|
39
|
+
```svelte
|
|
40
|
+
<script lang="ts">
|
|
41
|
+
import { CronInput, type CronPreset } from "@marianmeres/stuic";
|
|
42
|
+
|
|
43
|
+
const presets: CronPreset[] = [
|
|
44
|
+
{ label: "Top of every hour", value: "0 * * * *" },
|
|
45
|
+
{ label: "Daily at 09:00", value: "0 9 * * *" },
|
|
46
|
+
{ label: "Every 5 minutes", value: "*/5 * * * *" },
|
|
47
|
+
];
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<CronInput bind:value={expression} {presets} />
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Watching next-run externally
|
|
54
|
+
|
|
55
|
+
For places outside the CronInput that need the next-fire time (e.g. a dashboard card), use the exported `CronNextRun` helper:
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script lang="ts">
|
|
59
|
+
import { CronNextRun } from "@marianmeres/stuic";
|
|
60
|
+
import { onDestroy } from "svelte";
|
|
61
|
+
|
|
62
|
+
const nr = new CronNextRun("0 9 * * 1-5");
|
|
63
|
+
onDestroy(() => nr.destroy());
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<p>Next run: {nr.nextRunFormatted}</p>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> ⚠️ `CronNextRun` starts a 60-second interval in its constructor. **Always call `destroy()` on teardown**, or it will leak timers for the lifetime of the page.
|
|
70
|
+
|
|
71
|
+
## Modes
|
|
72
|
+
|
|
73
|
+
The `mode` prop controls which surfaces are visible and whether the toggle button is shown:
|
|
74
|
+
|
|
75
|
+
| `mode` | Toggle shown | Visible surfaces |
|
|
76
|
+
| ------------------- | ------------ | ---------------------------------------------------- |
|
|
77
|
+
| `"predefined"` | yes | preset picker |
|
|
78
|
+
| `"manual"` | yes | fields + raw + description + next-run |
|
|
79
|
+
| `undefined` | no | whatever the `show*` flags enable (full control) |
|
|
80
|
+
|
|
81
|
+
When `mode` is defined, it **overrides** the individual `show*` flags. Set `mode={undefined}` if you want to pick surfaces yourself.
|
|
82
|
+
|
|
83
|
+
## Expression ↔ fields sync
|
|
84
|
+
|
|
85
|
+
The `value` string is canonical. Internally:
|
|
86
|
+
|
|
87
|
+
- User edits a field → fields are composed back into an expression; `value` is set only if the result validates.
|
|
88
|
+
- User edits the raw input → fields are re-derived from it; invalid input surfaces as a validation message.
|
|
89
|
+
- User picks a preset → `value` is set directly.
|
|
90
|
+
- External `value` change → fields & raw mirror it (unless the change came from this component in the same tick).
|
|
91
|
+
|
|
92
|
+
Off-by-one conventions match standard cron:
|
|
93
|
+
|
|
94
|
+
| Field | Range | Notes |
|
|
95
|
+
| ------------ | ------ | ------------------------------------ |
|
|
96
|
+
| minute | 0-59 | |
|
|
97
|
+
| hour | 0-23 | |
|
|
98
|
+
| day of month | 1-31 | |
|
|
99
|
+
| month | 1-12 | 1 = January |
|
|
100
|
+
| day of week | 0-6 | 0 = Sunday |
|
|
101
|
+
|
|
102
|
+
## Validation
|
|
103
|
+
|
|
104
|
+
The component uses `new CronParser(value)` to validate. When invalid:
|
|
105
|
+
|
|
106
|
+
- The visual invalid state comes from `InputWrap`'s standard validation wrapper (the same mechanism used by every `Field*` component — override via `classInputBoxWrapInvalid`).
|
|
107
|
+
- The content div gets a `has-error` class hook you can target with your own CSS. No built-in style is applied to it — it's purely an extension point.
|
|
108
|
+
- The `validate` prop (same shape as `FieldInput`'s) is supported; the component reports the parser's error message through the usual validation channel.
|
|
109
|
+
|
|
110
|
+
## Timezone
|
|
111
|
+
|
|
112
|
+
All next-run calculations use the **host's local timezone**. There is no `timezone` prop today. DST transitions are handled correctly by `@marianmeres/cron`, but the displayed "next run" will naturally follow local-time semantics.
|
|
113
|
+
|
|
114
|
+
## Props
|
|
115
|
+
|
|
116
|
+
| Prop | Type | Default | Description |
|
|
117
|
+
| --------------------- | ------------------------------------- | --------------- | ---------------------------------------------------- |
|
|
118
|
+
| `value` | `string` | `"* * * * *"` | Cron expression (bindable) |
|
|
119
|
+
| `mode` | `"predefined" \| "manual" \| undefined` | `"predefined"` | Editor mode; `undefined` hides the toggle |
|
|
120
|
+
| `presets` | `CronPreset[]` | `DEFAULT_PRESETS` | Preset options |
|
|
121
|
+
| `showPresets` | `boolean` | `true` | (Ignored when `mode` is defined) |
|
|
122
|
+
| `showFields` | `boolean` | `true` | (Ignored when `mode` is defined) |
|
|
123
|
+
| `showRawInput` | `boolean` | `true` | (Ignored when `mode` is defined) |
|
|
124
|
+
| `showDescription` | `boolean` | `true` | (Ignored when `mode` is defined) |
|
|
125
|
+
| `showNextRun` | `boolean` | `true` | (Ignored when `mode` is defined) |
|
|
126
|
+
| `onchange` | `(expr, valid) => void` | - | Fires on every valid or invalid change |
|
|
127
|
+
| `unstyled` | `boolean` | `false` | Skip stuic base classes (user provides all styling) |
|
|
128
|
+
| `class` | `string` | - | Wrapper class |
|
|
129
|
+
| `el` | `HTMLElement` | - | Bindable wrapper element |
|
|
130
|
+
| `classFields`/`classField`/`classFieldLabel`/`classFieldInput`/`classPreset`/`classRaw`/`classSummary`/`classToggleButton` | `string` | - | Per-element class overrides |
|
|
131
|
+
|
|
132
|
+
Plus all standard `InputWrap` wrapper class props (`classLabel`, `classLabelBox`, `classInputBox`, `classInputBoxWrap`, `classInputBoxWrapInvalid`, `classDescBox`, `classBelowBox`) — same pattern as every other `Field*` component.
|
|
133
|
+
|
|
134
|
+
## Accessibility notes
|
|
135
|
+
|
|
136
|
+
- The preset select has `aria-label="Cron schedule preset"`.
|
|
137
|
+
- Each of the 5 field inputs has `aria-label` combining the short label and its range (e.g. `"Min (0-59)"`).
|
|
138
|
+
- The mode toggle button has a dynamic `aria-label` that reflects the target mode.
|
|
139
|
+
- Validation errors are announced via the underlying `InputWrap`'s validation message region.
|
|
140
|
+
|
|
141
|
+
## Limitations
|
|
142
|
+
|
|
143
|
+
- **No timezone override** — uses host local time.
|
|
144
|
+
- **Validation happens on full expression** — no per-field pre-validation; a malformed single field makes the whole expression invalid.
|
|
145
|
+
- **Preset match is exact** — if a user's expression is semantically equivalent but formatted differently (e.g. `"0 0 * * 0"` vs `"0 0 * * 7"`) the preset picker shows "Custom".
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A reactive helper that parses a cron expression and computes the next run time,
|
|
3
3
|
* updating automatically every minute.
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ **You must call `destroy()` when done** — the internal interval does not
|
|
6
|
+
* clean itself up. In a Svelte component:
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { onDestroy } from "svelte";
|
|
10
|
+
* const nr = new CronNextRun("0 9 * * *");
|
|
11
|
+
* onDestroy(() => nr.destroy());
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Forgetting this will leak a 60s-interval timer for the lifetime of the page.
|
|
4
15
|
*/
|
|
5
16
|
export declare class CronNextRun {
|
|
6
17
|
#private;
|
|
@@ -2,6 +2,17 @@ import { CronParser } from "@marianmeres/cron";
|
|
|
2
2
|
/**
|
|
3
3
|
* A reactive helper that parses a cron expression and computes the next run time,
|
|
4
4
|
* updating automatically every minute.
|
|
5
|
+
*
|
|
6
|
+
* ⚠️ **You must call `destroy()` when done** — the internal interval does not
|
|
7
|
+
* clean itself up. In a Svelte component:
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { onDestroy } from "svelte";
|
|
11
|
+
* const nr = new CronNextRun("0 9 * * *");
|
|
12
|
+
* onDestroy(() => nr.destroy());
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Forgetting this will leak a 60s-interval timer for the lifetime of the page.
|
|
5
16
|
*/
|
|
6
17
|
export class CronNextRun {
|
|
7
18
|
#expression = $state("");
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
--stuic-cron-input-section-gap: 0.25rem;
|
|
11
11
|
--stuic-cron-input-field-label-text: var(--stuic-color-muted-foreground);
|
|
12
12
|
--stuic-cron-input-summary-text: var(--stuic-color-muted-foreground);
|
|
13
|
-
--stuic-cron-input-error-text: var(--stuic-color-destructive);
|
|
14
13
|
--stuic-cron-input-field-bg: var(--stuic-color-background);
|
|
15
14
|
--stuic-cron-input-field-border: var(--stuic-color-border);
|
|
16
15
|
--stuic-cron-input-field-border-focus: var(--stuic-color-primary);
|
|
@@ -192,13 +191,6 @@
|
|
|
192
191
|
line-height: 1.4;
|
|
193
192
|
}
|
|
194
193
|
|
|
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
194
|
/* Mode toggle button */
|
|
203
195
|
.stuic-cron-input-content + .toggle-btn {
|
|
204
196
|
color: var(--stuic-input-localized-toggle-text);
|