Forms

ScheduleBuilder

Recurrence-definition control that authors a cron or fixed-interval schedule via presets, raw cron fields, or an interval, alongside an always-visible summary, next-fires preview, and timezone slot.

import { ScheduleBuilder } from '@lostgradient/cinder/schedule-builder';
schedulerecurrencecronform
01

Overview

A recurrence-definition control. Users author a schedule via friendly presets (every N minutes/hours, daily, weekly on selected days, or monthly on a day), raw five-field cron, or a fixed interval — all three authoring modes lower to the same portable emitted value: { mode: 'cron', expression } or { mode: 'interval', every, unit }. mode: 'preset' never appears in the emitted value; presets are sugar.

ScheduleBuilder ships no date, cron-parsing, or scheduling library. The always-visible "next N fires" preview is entirely consumer-supplied via computeNextFires, and the preview section is not rendered at all when that prop is omitted. Scope is recurrence only — overlap policy, jitter, and backfill belong to the consumer's surrounding form.

Usage

svelte
<script lang="ts">
  import ScheduleBuilder from '@lostgradient/cinder/schedule-builder';
  import type { ScheduleFire, ScheduleValue } from '@lostgradient/cinder/schedule-builder';

  let value = $state<ScheduleValue>({ mode: 'interval', every: 15, unit: 'minutes' });

  // Cinder ships no date math — bring your own cron/interval library here.
  // Labels are illustrative; a real implementation would compute actual fire times.
  function computeNextFires(current: ScheduleValue, count: number): ScheduleFire[] {
    if (current.mode === 'interval') {
      return Array.from({ length: count }, (_, index) => ({
        id: `fire-${index}`,
        label: `In ${(index + 1) * current.every} ${current.unit}`,
      }));
    }
    return Array.from({ length: count }, (_, index) => ({
      id: `fire-${index}`,
      label: `Next occurrence ${index + 1}`,
    }));
  }
</script>

<ScheduleBuilder
  {value}
  onchange={(next) => (value = next)}
  {computeNextFires}
  timezoneLabel="America/New_York"
  label="Job schedule"
/>

Authoring modes

Three modes live behind a tablist (Presets, Cron, Interval); presets is the default.

  • Presets — a preset-kind selector (Every N / Daily / Weekly / Monthly) reveals the matching fields. Every N lowers to an interval value; Daily, Weekly, and Monthly lower to a cron value.
  • Cron — five separate fields (minute, hour, day of month, month, day of week), each validated independently with an inline error and hint. The emitted value is the joined five-field expression.
  • Interval — a number and a unit (minutes / hours / days / weeks).

Switching modes never emits a change by itself — it re-seeds the destination mode's fields from the last value you actually committed, losslessly where representable. A minute or hour interval converts to an equivalent cron step only when the count evenly divides its field cycle (e.g. */15 * * * *); day/week intervals and non-dividing minute/hour intervals (like every 45 minutes) are not faithfully representable as a cron step, so Cron mode seeds a neutral daily default rather than a misleading expression. A cron pattern converts back to an interval only when it is such a dividing step pattern.

Regardless of mode, three things are always visible: a plain-English summary line, the next-fires preview (when computeNextFires is supplied), and a timezone display slot (timezone snippet, else timezoneLabel text, else a "Not set" placeholder).

Live preview
02

When to use

Use when
  • Letting a user define when a job or notification recurs, supplying your own date/cron library to compute upcoming fire times.
  • You want a friendly presets UI (every N, daily, weekly, monthly) that still round-trips to a portable cron or interval value.
Avoid when
03

Examples

Basic schedule builder

Author a recurrence via presets, raw cron, or a fixed interval. The summary line, next-fires preview, and timezone slot stay visible across every mode.

Cron mode

Open the Cron tab to see five independently validated fields seeded losslessly from the initial value. Each field carries a numeric hint and surfaces an inline error the moment it falls outside its valid range.

Interval mode

Open the Interval tab to author a fixed-cadence schedule. The interval value round-trips losslessly through every mode that can represent it; a cadence with no exact cron equivalent (like every 5 hours) seeds a neutral default in Cron mode rather than silently changing meaning.

Presets mode

Presets is the default authoring mode. Try each preset kind — "every N" lowers to an interval value, and daily/weekly/monthly all lower to a cron expression. The summary line always reflects the resulting cron or interval value, never a "preset".

04

Props

Props for schedule-builder
Name Type Default Required Bindable Description
value ScheduleValue | undefined The current recurrence value (controlled). Pass the value returned from onchange back in to commit an edit. When omitted, the component starts from a sensible default (interval, every 15 minutes).
onchange ((value: ScheduleValue) => void)undefined Called whenever the user edits the recurrence. Receives the next lossless {@link ScheduleValue}. The consumer owns persistence and validation.
allowedModes readonly ScheduleAuthoringMode[]undefined Authoring modes to expose. Defaults to ['presets', 'cron', 'interval']. Use ['cron'] for cron-only consumers; the component then renders only cron fields and never emits an interval value.
computeNextFires ((value: ScheduleValue, count: number) => ScheduleFire[])undefined Injected next-fires computation. The component passes the current value and the requested count and renders whatever fires the consumer returns. When omitted, the preview list is hidden (the component ships no date logic).
previewCount number | undefined 5 How many upcoming fires to request from computeNextFires. Defaults to 5.
timezoneLabel string | undefined Timezone label rendered in the always-visible timezone display slot, e.g. "America/New_York" or "UTC". Purely presentational — the component does not interpret it. When omitted, provide the timezone snippet instead; if neither is supplied the slot shows a "Not set" placeholder (the slot itself is always rendered).
timezone Snippet | undefined Custom content for the timezone display slot. Takes precedence over timezoneLabel when both are supplied.
label string | undefined 'Schedule' Accessible label for the whole control. Defaults to "Schedule".
05

Accessibility

The available authoring modes render as a tablist; each cron field reports validity via aria-invalid and an associated hint/error.