Component
Week planner
<DomWeekPlanner>A fixed-width week scheduler with days as horizontally scrolling swim lanes and time chunks as rows.
Playground
Configure the time grid
Week planner playground
Change the scrollport height, time range, row size, fixed day width, and number of horizontally scrolling weeks.
8 Jun 2026 – 28 Jun 2026
<script setup>
import { reactive } from 'vue';
import { DomWeekPlanner } from '@getdom/studio/vue';
const data = reactive({
"startDate": "2026-06-08",
"initialMonth": null,
"initialYear": null,
"weeks": 3,
"initialScrollDate": "",
"virtualWeeks": false,
"virtualWeekBuffer": 2,
"locale": "en-GB",
"weekStartsOn": 1,
"weekdayFormat": "short",
"startTime": "07:00",
"endTime": "19:00",
"timeStep": 60,
"slotHeight": 56,
"dayWidth": 192,
"timeRailWidth": 72,
"viewportHeight": 640,
"showWeekNumbers": true,
"showAllDayRow": true,
"showCurrentTime": true,
"loading": false,
"mutedBefore": "",
"mutedAfter": "",
"min": "",
"max": "",
"clickable": false,
"rangeSelection": false,
"selectionStep": 5,
"dragAndDrop": false,
"dropEffect": "move",
"disabledDate": null,
"dayClass": "",
"dayStyle": ""
});
</script>
<template>
<DomWeekPlanner
v-bind="data"
/>
</template>Demo
Team week planner
Open on three fixed-width weeks, switch to a virtualized full year, and use Today to return to the live current-time line. Timed records use the month-calendar record shape and can be moved to a precise day and time.
June 2026
3 fixed-width weeks
Drag an appointment to reschedule it, or select an empty time row.
8 Jun 2026 – 28 Jun 2026
Install window
confirmed09:00–11:00
Dock 4
Maya Chen
Replacement shutters and access lift.
Site survey
tentative13:30–15:00
Studio 2
Noah Williams
Check power and loading access.
Design review
confirmed10:00–12:30
Video call
Ava Brooks
Approve revised material palette.
Maintenance visit
confirmed08:30–10:00
North lobby
Leo Harris
Quarterly inspection and controls test.
Handover session
confirmed14:00–16:30
Atrium
Maya Chen
Client training and completion pack.
Parts collection
scheduled11:00–12:00
West depot
Noah Williams
Collect motors and spare controls.
Accessibility audit
confirmed09:30–12:00
Civic hall
Ava Brooks
Review entrances, controls, and signage.
Commissioning
scheduled13:00–16:00
East wing
Leo Harris
System calibration and sign-off.
Creation
Drag to create an event
Drag vertically across an empty day lane with five-minute precision, enter an event name in the dialog, then add it to local Vue state without coupling the planner to a server.
Drag to create
Drag across an empty time lane to create a draft event.
27 Jul 2026 – 9 Aug 2026
Weekly planning
confirmed09:00–10:00
Project review
confirmed13:30–15:00
Architecture
One calendar data contract, two views
DomWeekPlanner is a different visual representation of the month calendar contract. Both components are transport-free, identify a day with cell.value, and let the application own fetching, grouping, optimistic state, and persistence.
The same record can appear as compact day content in a month grid or as a precisely positioned appointment in the week planner. Keep date as a local date string, then add startTime and endTime when a record needs timed placement.
rangeStyle(record) translates those local times into vertical position and height. It clips records to the visible time range without changing their data, so the API remains suitable for month, week, multi-week, all-day, and server-loaded views.
Enable rangeSelection to let mouse and pen users drag across a time lane. The resulting time-range-select payload contains the local date plus snapped start and end times; selectionStep controls pointer precision independently of the visible timeStep rows. The application decides whether to store the range locally, open an editor, or persist it to an endpoint.
Every day column keeps the configured dayWidth. Increasing weeks adds more columns to the same horizontal scrollport instead of compressing the visible lanes.
The explicit viewportHeight keeps vertical time navigation inside the planner, even when a documentation frame or application shell would otherwise grow to the full grid height. The sticky day and all-day rows remain visible while both axes scroll.
For full-year or longer ranges, enable virtualWeeks. The planner uses the same fixed-item render-window calculation as the data grid, mounts only intersecting weeks plus virtualWeekBuffer, and emits render-window-change with the mounted date range for lazy API loading.
Shared read endpoint
Request the exact inclusive visible range from the same endpoint used by the month view:
GET /api/calendar-records?start=2026-06-08&end=2026-06-28&timezone=Europe/London Shared record shape
const records = [
{
id: 'evt_1042',
date: '2026-06-08',
title: 'Install window',
startTime: '09:00',
endTime: '11:00',
status: 'confirmed',
tone: 'primary',
resourceId: 'field-ops',
meta: {
customerId: 'cus_8821',
location: 'Dock 4',
},
},
];
/**
* Lists calendar records for a local date key.
*
* @param {string} date YYYY-MM-DD date key.
* @returns {Array<Record<string, unknown>>} Matching calendar records.
*/
function recordsFor(date) {
return records.filter((record) => record.date === date);
}Switch visual representations
<DomMonthCalendar
:start-date="rangeStart"
:months="2"
>
<template #default="{ cell }">
<MonthCard
v-for="record in recordsFor(cell.value)"
:key="record.id"
:record="record"
/>
</template>
</DomMonthCalendar>
<DomWeekPlanner
:start-date="rangeStart"
:weeks="3"
start-time="07:00"
end-time="20:00"
>
<template #default="{ cell, rangeStyle }">
<TimedCard
v-for="record in recordsFor(cell.value)"
:key="record.id"
:record="record"
:style="rangeStyle(record)"
/>
</template>
</DomWeekPlanner>Interaction
Precise app-owned rescheduling
The default slot exposes the same dragAttrs(data) pattern as the month calendar. Both components use the same native transfer type, so an application can carry a record between visual representations without translating its identity.
day-drop keeps the existing source and target date fields, then adds sourceTime, targetTime, and minute values. Timed drops snap to timeStep; drops in the all-day row return an empty target time.
<DomWeekPlanner
:start-date="rangeStart"
drag-and-drop
@day-drop="moveRecord"
>
<template #default="{ cell, dragAttrs, rangeStyle }">
<AppointmentCard
v-for="record in recordsFor(cell.value)"
:key="record.id"
v-bind="dragAttrs(record, { sourceTime: record.startTime })"
:record="record"
:style="rangeStyle(record)"
/>
</template>
</DomWeekPlanner>
/**
* Persists one app-owned record after a planner drop.
*
* @param {object} payload Planner day-drop payload.
* @returns {void}
*/
function moveRecord({ data, targetValue, targetTime }) {
// Persist the record in the application layer.
updateCalendarRecord(data.id, {
date: targetValue,
startTime: targetTime,
});
}Reference
Props
Control props
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
locale | string | string | 'en-GB' | Locale used for weekday, date, month, and time labels. |
weekStartsOn | 0 | 1 | 2 | 3 | 4 | 5 | 6 | number | 1 | First day of week. 0 is Sunday, 1 is Monday. |
weekdayFormat | 'narrow' | 'short' | 'long' | string | 'short' | Weekday label length. |
mutedBefore | string | string | '' | Dates before this YYYY-MM-DD value render muted. |
mutedAfter | string | string | '' | Dates after this YYYY-MM-DD value render muted. |
Interaction
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
min | string | string | '' | Minimum clickable or droppable date as YYYY-MM-DD. |
max | string | string | '' | Maximum clickable or droppable date as YYYY-MM-DD. |
clickable | boolean | boolean | false | Emit day-click with the selected day and snapped time. |
rangeSelection | boolean | boolean | false | Allow mouse or pen dragging across a time lane and emit time-range-select. |
selectionStep | number | number | 5 | Minutes used to snap drag-created time ranges independently of the visible time rows. |
dragAndDrop | boolean | boolean | false | Expose dragAttrs and emit day-drop with the target day and snapped time. |
dropEffect | 'copy' | 'move' | 'link' | string | 'move' | Native drop effect shown when a time lane can receive a dragged item. |
disabledDate | function | (cell: | — | Function that receives a day cell and returns true when the day should not be clickable or droppable. |
Layout
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
dayWidth | number | number | 192 | Fixed pixel width of every day swim lane. Additional weeks scroll horizontally. |
timeRailWidth | number | number | 72 | Pixel width of the sticky time-label rail. |
viewportHeight | number | number | 640 | Pixel height of the internal two-axis scroll viewport. |
showWeekNumbers | boolean | boolean | false | Show the ISO week number on the first day of each week. |
showAllDayRow | boolean | boolean | true | Render a sticky all-day row above the timed grid. |
showCurrentTime | boolean | boolean | true | Show a live current-time line in today’s time lane when the current time is visible. |
Range
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
startDate | string | string | '' | Date inside the first rendered week as YYYY-MM-DD. |
initialMonth | number | number | — | Initial month, 1-12. Used when startDate is not set. |
initialYear | number | number | — | Initial year. Used when startDate is not set. |
weeks | number | number | 3 | Number of consecutive weeks to render as fixed-width day columns. |
initialScrollDate | string | string | '' | Date to reveal after mount or a range reset. Useful when a virtual year starts before the focused date. |
virtualWeeks | boolean | boolean | false | Mount only the horizontally visible weeks plus the configured buffer. |
virtualWeekBuffer | number | number | 2 | Extra weeks mounted before and after the visible horizontal window. |
State
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
loading | boolean | boolean | false | Show an in-viewport loading indicator while the app fetches the requested render window. |
Styling
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
dayClass | string | array | object | function | string | Array<unknown> | Record<string, boolean> | ((cell: | '' | Extra classes, or a function that receives a day cell and returns classes for the day lane. |
dayStyle | string | object | function | string | Record<string, string | number> | ((cell: | '' | Extra styles, or a function that receives a day cell and returns styles for the day lane. |
Time grid
| Name | Type | TS | Default | Description |
|---|---|---|---|---|
startTime | string | string | '07:00' | First visible time as HH:mm. |
endTime | string | string | '20:00' | Exclusive final visible time as HH:mm. Use 24:00 to include the full day. |
timeStep | number | number | 60 | Minutes represented by each horizontal time row. |
slotHeight | number | number | 56 | Pixel height of each time row. |
Auto-generated from Week planner.props and inline _edit hints.
Events
| Name | Payload | Description |
|---|---|---|
| @day-click | ({ | Fired when a clickable day header, all-day cell, or timed row is activated. |
| @time-range-select | ({ | Fired after a mouse or pen drag selects a snapped time range. The planner does not create or persist records. |
| @day-drag-start | ({ | Fired when an item using dragAttrs starts dragging. |
| @day-drag-enter | ({ | Fired when a draggable item previews a day and time. |
| @day-drag-leave | ({ | Fired when a draggable item leaves a day lane. |
| @day-drop | ({ | Fired when an item is dropped. The planner does not mutate records. |
| @day-drag-end | ({ | Fired when a drag started with dragAttrs ends. |
| @viewport-scroll | ({ | Fired while the internal two-axis viewport scrolls horizontally. |
| @viewport-end | ({ | Fired when horizontal scrolling reaches the end threshold. Apps can append another date range. |
| @render-window-change | ({ | Fired when the mounted virtual week window changes so apps can fetch its record range. |
Names auto-detected from defineEmits and source emit() calls; payload and description from __doc.events when present.
Slots
| Name | Scope | Description |
|---|---|---|
| #(default) | { | Timed content layer for each day. Use rangeStyle(record) with records containing startTime and endTime. |
| #day-header | { cell, month, week } | Custom day-column header content. |
| #all-day | { cell, month, week, drag, dragAttrs } | All-day content rendered above the timed rows. |
| #time-label | { slot } | Custom sticky time-rail label. |
| #corner | { range } | Content above the sticky time rail. |
Keyboard
- TabMove through day headers, all-day cells, and time rows when clickable is enabled.
- Enter / SpaceActivate the focused day or time row.