JavaScript Schedule
@lemonadejs/schedule · ✓ 37 contract checks · framework-agnostic · zero dependencies
<Schedule /> — the week/day time-grid scheduler, ported from the v5
plugin (@lemonadejs/schedule). The v5 block is NOT a month calendar:
it is a vertical time grid (1px per minute) with day columns, and that
exact model is preserved here:
- views: type ‘week’ (Sun–Sat) | ‘weekdays’ (Mon–Fri) | ‘day’;
weekly mode swaps real dates for abstract weekdays (recurring
template schedules — events carry
weekdayinstead ofdate) - grid: minutes per row (default 15, row height = grid px); snap: create/resize step in minutes (defaults to grid)
- drag-create on empty cells, drag-move (top 25px zone) and drag-resize (bottom 5px zone) of events, with conflict blocking when overlap=false and read-only hour ranges
- selection (click, Ctrl+click multi), keyboard: arrows walk events, Enter opens the editor for the selected event, N creates an event at the first free slot, Alt+arrows move and Shift+Up/Down resize the selected event (the same commit path as the drag gestures), Delete removes, Ctrl+C/V copy/paste (+1 row shift), Ctrl+Z/Y undo/redo (full history: add/update/delete/setData)
- validrange hides hours outside the window; readonlyrange disables (striped) hour ranges; now-pointer line on today’s column
- the v5 Event editor (dist/event.js — a Modal with title/when/ start/end/location/color palette) is built in, composing @lemonadejs/modal; it opens on double click and after drag-create (v5’s onedition moments) — disable with editor=false
v5 → v6 mapping: validRange → validrange, readOnlyRange → readonlyrange,
render() → api.refresh(); onchangeevent(record, oldValue, newValue)
keeps its v5 name — it fires after every committed event update
(drag-move/resize commit, editor save, api.updateEvent), alongside
onupdate (same payload); onbeforechangeevent(record, oldValue,
newValue) is its cancellable pre-flight — return false and nothing
mutates (the drag snaps back). v5 fired onbeforechangeevent at
gesture START with the raw drag state; the v6 gesture-start veto is
onbeforedrag({ kind, record }) — onbeforechangeevent now guards the
concrete change at COMMIT, so it sees the real oldValue/newValue.
Callbacks drop the leading self argument (pure
components, no this); document.dictionary → weekdays prop; getEvent
returns the RECORD (not a DOM node). Data is BY REFERENCE: mutate the
array (or a record) and touch() — the grid re-renders once.
Example
import { html } from 'lemonadejs';
import Schedule from '@lemonadejs/schedule';
// This week's dates (offset from Sunday, local time), so the example is always populated
const day = (offset) => {
const d = new Date();
d.setDate(d.getDate() - d.getDay() + offset);
return [d.getFullYear(), String(d.getMonth() + 1).padStart(2, '0'), String(d.getDate()).padStart(2, '0')].join('-');
};
const App = (props, { state }) => {
const events = state([
{ date: day(1), start: '09:00', end: '09:30', title: 'Standup', color: '#3f51b5' },
{ date: day(1), start: '11:00', end: '12:30', title: 'Design review', color: '#009688', location: 'Room 2' },
{ date: day(2), start: '14:00', end: '16:00', title: 'Deep work', color: '#795548' },
{ date: day(3), start: '10:00', end: '11:00', title: 'Customer call', color: '#ff9800', location: 'Zoom' },
{ date: day(4), start: '15:00', end: '17:00', title: 'Sprint planning', color: '#e91e63', location: 'Room 4' },
{ date: day(5), start: '13:00', end: '14:00', title: 'All-hands', color: '#9c27b0', readonly: true },
]);
const note = state('Drag empty cells to create an event, drag events to move or resize, double-click to edit.');
return html`<div>
<${Schedule} data="${events}" type="weekdays" validrange="${['08:00', '18:00']}" grid="30"
oncreate="${(added) => (note.value = 'Created: ' + added.map((e) => e.title).join(', '))}"
onupdate="${(record) => (note.value = `${record.title} is now ${record.date} ${record.start}–${record.end}`)}"
ondelete="${(record) => (note.value = 'Deleted: ' + record.title)}" />
<p style="font-size:13px">${note}</p>
</div>`;
};Installation
npm install @lemonadejs/schedule
import Schedule from '@lemonadejs/schedule';
import '@lemonadejs/schedule/style.css';
Three deployment forms, one component:
html`<${Schedule} />` // by value (no registration)
setComponents({ Schedule }); // then <Schedule /> by name anywhere
createWebComponent(Schedule); // <lm-schedule> in plain HTML/any framework
Props
Every declared prop arrives as a live state — pass a value for a snapshot or a state for a two-way live wire. Attribute strings are coerced to the declared type.
| Prop | Type | Default | Description |
|---|---|---|---|
data | array | — | ScheduleEvent[] BY REFERENCE (mutate + touch()) |
value | string | '' | anchor date ‘YYYY-MM-DD’ (default: today) |
type | string | "week" | ’week' |
weekly | boolean | false | abstract weekday columns (no dates) |
grid | number | 15 | minutes per row (row height = grid px) |
snap | number | 0 | create/resize step in minutes (0 = grid) |
overlap | boolean | false | allow overlapping events (true staggers them) |
validrange | array | — | visible hours, e.g. [‘08:00’,‘20:00’] (v5: validRange) |
readonlyrange | array | — | disabled hours: [‘a’,‘b’] or [[‘a’,‘b’],…] (v5: readOnlyRange) |
editor | boolean | true | built-in event editor (v5 shipped it as lm-event) |
weekdays | array | — | 7 weekday names (v5: document.dictionary) |
Events
All event names are lowercase (the platform convention — LJS-305 warns otherwise).
onchange— (data) — any user/api change to the dataoncreate— (events) — events addedonbeforecreate— (events) — return false to cancelonbeforeinsert— (event) — drag-create template; false cancels, object replacesonupdate— (record, oldValue, newValue) — alias of onchangeeventonchangeevent— (record, oldValue, newValue) — v5 name, after a committed updateonbeforechange— ({ action, … }) — return false to cancelonbeforechangeevent— (record, oldValue, newValue) — return false to cancel the updateonbeforedrag— ({ kind, record }) — false cancels the gesture at its startondelete— (record) — per removed eventondblclick— (record)onedition— (record) — editor moment (dblclick / after drag-create)onerror— (message)
API
import { ref } from 'lemonadejs';
const schedule = ref();
html`<${Schedule} ref="${schedule}" />`;
// schedule.current.addEvents(...) · schedule.current.updateEvent(...) · schedule.current.deleteEvents(...) · schedule.current.getData(...) · schedule.current.setData(...) · schedule.current.getEvent(...) · schedule.current.getSelected(...) · schedule.current.resetSelection(...) · schedule.current.setRange(...) · schedule.current.setReadOnly(...) · schedule.current.undo(...) · schedule.current.redo(...) · schedule.current.next(...) · schedule.current.prev(...) · schedule.current.today(...) · schedule.current.openEditor(...) · schedule.current.refresh(...)
addEvents()updateEvent()deleteEvents()getData()setData()getEvent()getSelected()resetSelection()setRange()setReadOnly()undo()redo()next()prev()today()openEditor()refresh()
Styling
All classes follow the lm-schedule-* convention; visual variants are data-*
attributes on the root. Override freely — there is no styling engine to fight.
Contract
The machine-readable schema ships with the package:
import contract from '@lemonadejs/schedule/contract.json';
verify.json carries the conformance proof produced by verify(Schedule).
Looking for the v5 plugin? See the archived v5 documentation.