@vc-shell/vc-app-skill 2.1.0-pr258.9ee8dfb → 2.2.0-pr259.8bff59b
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vc-shell/vc-app-skill",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0-pr259.8bff59b",
|
|
4
4
|
"description": "AI coding skill for scaffolding and generating VirtoCommerce Shell applications. Works with Claude Code, OpenCode, Gemini, Codex, Cursor.",
|
|
5
5
|
"bin": "./bin/install.cjs",
|
|
6
6
|
"files": [
|
package/runtime/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.2.0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Synced from framework at commit
|
|
1
|
+
Synced from framework at commit 694fdf620 on 2026-07-20T17:03:47.353Z
|
|
@@ -8,8 +8,6 @@ group: navigation
|
|
|
8
8
|
|
|
9
9
|
Anchored floating panel (popover) positioned relative to an anchor element, with header, scrollable content, and optional footer. Built on `@floating-ui/vue`.
|
|
10
10
|
|
|
11
|
-
::storybook id="overlay-vcpopover--default"
|
|
12
|
-
|
|
13
11
|
## When to Use
|
|
14
12
|
|
|
15
13
|
- Rich dropdown content with a title bar and action buttons (e.g., filter panels, settings popovers)
|
|
@@ -78,8 +76,6 @@ const open = ref(false);
|
|
|
78
76
|
|
|
79
77
|
## Common Patterns
|
|
80
78
|
|
|
81
|
-
::storybook id="overlay-vcpopover--with-footer"
|
|
82
|
-
|
|
83
79
|
### Filter Panel with Footer Actions
|
|
84
80
|
|
|
85
81
|
```vue
|
|
@@ -6,7 +6,7 @@ group: data-display
|
|
|
6
6
|
|
|
7
7
|
# VcScheduler
|
|
8
8
|
|
|
9
|
-
A calendar organism for planning date-bound periods -- promotions, pricelist windows, campaigns. It defaults to a Month grid where all-day events render as bars (stacking into lanes when they overlap, with a "+N more" overflow popover) and shorter events render as timed chips. It can also switch to a
|
|
9
|
+
A calendar organism for planning date-bound periods -- promotions, pricelist windows, campaigns. It defaults to a Month grid where all-day events render as bars (stacking into lanes when they overlap, with a "+N more" overflow popover) and shorter events render as timed chips. It can also switch to a vertical time-grid Timeline (Day or Week) rendering of the same events.
|
|
10
10
|
|
|
11
11
|
## Quick Start
|
|
12
12
|
|
|
@@ -34,16 +34,13 @@ const date = ref(new Date());
|
|
|
34
34
|
|
|
35
35
|
const events = ref<ISchedulerEvent[]>([{ id: "e1", title: "Summer Sale", start: new Date("2026-07-01"), end: new Date("2026-07-06"), allDay: true }]);
|
|
36
36
|
|
|
37
|
-
function onEventUpdate(u: { id: string; start: Date; end: Date }) {
|
|
37
|
+
function onEventUpdate(u: { id: string; start: Date; end: Date; title?: string; allDay?: boolean; color?: string }) {
|
|
38
38
|
const event = events.value.find((e) => e.id === u.id);
|
|
39
|
-
if (event)
|
|
40
|
-
event.start = u.start;
|
|
41
|
-
event.end = u.end;
|
|
42
|
-
}
|
|
39
|
+
if (event) Object.assign(event, u);
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
function onEventCreate(c: { start: Date; end: Date; allDay: boolean }) {
|
|
46
|
-
events.value.push({ id: crypto.randomUUID(),
|
|
42
|
+
function onEventCreate(c: { start: Date; end: Date; allDay: boolean; title: string; color?: string }) {
|
|
43
|
+
events.value.push({ id: crypto.randomUUID(), ...c });
|
|
47
44
|
}
|
|
48
45
|
</script>
|
|
49
46
|
```
|
|
@@ -55,17 +52,19 @@ function onEventCreate(c: { start: Date; end: Date; allDay: boolean }) {
|
|
|
55
52
|
1. [Views](#views)
|
|
56
53
|
2. [Event Model](#event-model)
|
|
57
54
|
3. [Editable Events](#editable-events)
|
|
58
|
-
4. [
|
|
59
|
-
5. [
|
|
60
|
-
6. [
|
|
61
|
-
7. [
|
|
62
|
-
8. [
|
|
63
|
-
9. [
|
|
64
|
-
10. [
|
|
65
|
-
11. [
|
|
66
|
-
12. [
|
|
67
|
-
13. [
|
|
68
|
-
14. [
|
|
55
|
+
4. [Editing UX: Quick-Create Popover and Editor Modal](#editing-ux-quick-create-popover-and-editor-modal)
|
|
56
|
+
5. [Recurring Events](#recurring-events)
|
|
57
|
+
6. [Overlapping Events and Overflow](#overlapping-events-and-overflow)
|
|
58
|
+
7. [Custom Slots](#custom-slots)
|
|
59
|
+
8. [Timeline View](#timeline-view)
|
|
60
|
+
9. [Props](#props)
|
|
61
|
+
10. [Events](#events)
|
|
62
|
+
11. [Slots](#slots)
|
|
63
|
+
12. [CSS Custom Properties](#css-custom-properties)
|
|
64
|
+
13. [Recipes](#recipes)
|
|
65
|
+
14. [Common Mistakes](#common-mistakes)
|
|
66
|
+
15. [Accessibility](#accessibility)
|
|
67
|
+
16. [Related Components](#related-components)
|
|
69
68
|
|
|
70
69
|
---
|
|
71
70
|
|
|
@@ -77,18 +76,25 @@ function onEventCreate(c: { start: Date; end: Date; allDay: boolean }) {
|
|
|
77
76
|
<VcScheduler v-model:view="view" v-model:date="date" :events="events" />
|
|
78
77
|
```
|
|
79
78
|
|
|
80
|
-
| View
|
|
81
|
-
|
|
|
82
|
-
| `"month"`
|
|
83
|
-
| `"timeline"`
|
|
79
|
+
| View | Data props | Description |
|
|
80
|
+
| ----------------- | ---------- | -------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| `"month"` | `events` | Default. A 6-week grid; all-day events as bars, short events as chips. |
|
|
82
|
+
| `"timeline-day"` | `events` | The same events on a vertical hour grid for one focused day. See [Timeline View](#timeline-view). |
|
|
83
|
+
| `"timeline-week"` | `events` | The same events on a vertical hour grid across 7 day columns. See [Timeline View](#timeline-view). |
|
|
84
84
|
|
|
85
|
-
`date` is the focused date (which month is shown, or the timeline
|
|
85
|
+
`date` is the focused date (which month is shown, or the timeline's focused day/week). Bind it with `v-model:date` so the toolbar's prev/next/today controls update your state:
|
|
86
86
|
|
|
87
87
|
```vue
|
|
88
88
|
<VcScheduler v-model:date="date" :events="events" />
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
All three views render the same `events` array -- switching `view` only changes how they're laid out (month grid vs. vertical time grid), never which data prop is read.
|
|
92
|
+
|
|
93
|
+
By default only **Month** is offered and the toolbar's view switcher is hidden -- the hour-granular timelines are opt-in, best suited to intraday/timed scheduling rather than the multi-day promo/pricelist norm. Pass the `views` prop to expose them (order preserved; the switcher appears once more than one view is listed):
|
|
94
|
+
|
|
95
|
+
```vue
|
|
96
|
+
<VcScheduler :views="['month', 'timeline-day', 'timeline-week']" :events="events" />
|
|
97
|
+
```
|
|
92
98
|
|
|
93
99
|
## Event Model
|
|
94
100
|
|
|
@@ -100,20 +106,33 @@ interface ISchedulerEvent {
|
|
|
100
106
|
title: string;
|
|
101
107
|
/** Forces bar (true) or chip (false) rendering. Inferred from duration when omitted. */
|
|
102
108
|
allDay?: boolean;
|
|
103
|
-
/** CSS color or
|
|
109
|
+
/** Optional explicit color (CSS color or var()). Omit it and the event is auto-colored:
|
|
110
|
+
* a deterministic palette color is derived from the title at render time (same title →
|
|
111
|
+
* same color, recurring occurrences included), so you never store colors. Set this only
|
|
112
|
+
* to override. The editor's manual Color field is hidden unless `allow-color` is set. */
|
|
104
113
|
color?: string;
|
|
105
114
|
/** Per-event override of the global `editable` prop. */
|
|
106
115
|
editable?: boolean;
|
|
107
116
|
/** Free-form data for custom slots/handlers. */
|
|
108
117
|
meta?: Record<string, unknown>;
|
|
118
|
+
/** RRULE string on a master event, e.g. "FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU;COUNT=10". */
|
|
119
|
+
recurrence?: string;
|
|
120
|
+
/** Occurrence start dates removed from the master series. */
|
|
121
|
+
exceptionDates?: Date[];
|
|
122
|
+
/** On an override event: id of the master series it belongs to. */
|
|
123
|
+
recurrenceId?: string;
|
|
124
|
+
/** On an override event: the original occurrence start it replaces. */
|
|
125
|
+
originalStart?: Date;
|
|
109
126
|
}
|
|
110
127
|
```
|
|
111
128
|
|
|
112
129
|
An event renders as a full-width **bar** spanning its day columns when `allDay` is `true`, or when omitted and `end - start >= 24h`. Anything shorter renders as a compact **chip** showing `HH:mm` plus the title, listed under its day cell.
|
|
113
130
|
|
|
131
|
+
The last four fields (`recurrence`, `exceptionDates`, `recurrenceId`, `originalStart`) only matter for recurring series -- see [Recurring Events](#recurring-events).
|
|
132
|
+
|
|
114
133
|
## Editable Events
|
|
115
134
|
|
|
116
|
-
Set `editable` to allow drag-to-move and drag-to-resize on event bars
|
|
135
|
+
Set `editable` to allow drag-to-move and drag-to-resize on existing event bars, and to enable creating new events by clicking or dragging on an empty cell. Moves and resizes commit on pointer-up via `event-update`; empty-cell interactions open the built-in quick-create popover or editor modal, which commit via `event-create` -- see [Editing UX](#editing-ux-quick-create-popover-and-editor-modal) for the full interaction model.
|
|
117
136
|
|
|
118
137
|
```vue
|
|
119
138
|
<VcScheduler v-model:date="date" :events="events" editable @event-update="onEventUpdate" @event-create="onEventCreate" />
|
|
@@ -127,6 +146,122 @@ To make only _some_ events editable, use `isEventEditable` -- it is re-evaluated
|
|
|
127
146
|
|
|
128
147
|
An event can also opt out individually via its own `editable: false` field, independent of the global prop.
|
|
129
148
|
|
|
149
|
+
## Editing UX: Quick-Create Popover and Editor Modal
|
|
150
|
+
|
|
151
|
+
With `editable`, `VcScheduler` ships a complete built-in create/edit flow -- no host-side modal wiring required:
|
|
152
|
+
|
|
153
|
+
| Gesture | Result |
|
|
154
|
+
| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
155
|
+
| Toolbar **"+ New event"** button | Opens the **editor modal** (create mode) for an all-day event on the focused date. Shown whenever `editable`. |
|
|
156
|
+
| Single click on an empty cell | Opens the **quick-create popover**, anchored to the cell, with a title field. |
|
|
157
|
+
| Double click on an empty cell | Opens the **editor modal** directly (create mode) -- richer fields, no popover. |
|
|
158
|
+
| Drag across empty cells | Opens the **editor modal** (create mode) pre-filled with the dragged range. |
|
|
159
|
+
| Click an existing event | Opens the built-in quick-info popover (unless `quickInfo` is `false`): a color-tinted header with the title, the date/time, a human-readable recurrence summary for recurring events, an optional category line from `meta.category`/`meta.description`, and Edit/Delete. |
|
|
160
|
+
| Quick-info popover's "Edit" button | Opens the **editor modal** (edit mode), pre-filled from the event. |
|
|
161
|
+
| Quick-create popover's "More options" | Opens the **editor modal** (create mode), carrying over the typed title. |
|
|
162
|
+
|
|
163
|
+
The editor modal additionally exposes all-day toggle, start/end date-time, and color, plus a Delete action in edit mode (commits via `event-delete`). Saving from either surface emits the public `event-create`/`event-update` event -- see [Events](#events) for the exact payloads.
|
|
164
|
+
|
|
165
|
+
This entire flow is internal state: no `v-model` or extra event wiring is needed to make it work, beyond `editable` and the `event-create`/`event-update`/`event-delete` handlers you already have for persisting the result.
|
|
166
|
+
|
|
167
|
+
### `editorMode`: opting out of the built-in UI
|
|
168
|
+
|
|
169
|
+
Set `editorMode="emit"` to skip the quick-create popover and editor modal entirely and drive your own create/edit UI instead. In this mode:
|
|
170
|
+
|
|
171
|
+
- A create gesture (any of the rows above) emits `event-create` directly with an empty `title`, instead of opening a popover/modal.
|
|
172
|
+
- Clicking an event's quick-info "Edit" button emits `event-edit` with the full event, instead of opening the editor modal. (`event-edit` never fires in the default `"builtin"` mode.)
|
|
173
|
+
|
|
174
|
+
```vue
|
|
175
|
+
<VcScheduler :events="events" editable editor-mode="emit" @event-create="openMyCreateDialog" @event-edit="openMyEditDialog" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Customizing the built-in surfaces: `#quick-create` and `#event-editor`
|
|
179
|
+
|
|
180
|
+
To keep the built-in orchestration (state, anchoring, intent handling) but swap in your own markup, override the `#quick-create` and/or `#event-editor` slots:
|
|
181
|
+
|
|
182
|
+
```vue
|
|
183
|
+
<VcScheduler :events="events" editable>
|
|
184
|
+
<template #quick-create="{ open, anchorRect, draft, save, more, close }">
|
|
185
|
+
<MyQuickCreate :open="open" :anchor-rect="anchorRect" :draft="draft" @save="save" @more="more" @close="close" />
|
|
186
|
+
</template>
|
|
187
|
+
<template #event-editor="{ open, mode, draft, save, delete: onDelete, close }">
|
|
188
|
+
<MyEventEditor :open="open" :mode="mode" :draft="draft" @save="save" @delete="onDelete" @close="close" />
|
|
189
|
+
</template>
|
|
190
|
+
</VcScheduler>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Slot | Scope | Description |
|
|
194
|
+
| -------------- | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
195
|
+
| `quick-create` | `{ open, anchorRect, draft, save, more, close }` | Replaces the quick-create popover. `save({ title })` commits; `more({ title })` should route into your editor. |
|
|
196
|
+
| `event-editor` | `{ open, mode, draft, save, delete, close }` | Replaces the editor modal. `mode` is `"create"` \| `"edit"`; `save(draft)` commits; `delete({ id })` removes. |
|
|
197
|
+
|
|
198
|
+
`draft` is an `IEventDraft` (`{ id?, title, start, end, allDay, color? }`) -- the working copy being created or edited.
|
|
199
|
+
|
|
200
|
+
## Recurring Events
|
|
201
|
+
|
|
202
|
+
An event with a non-empty `recurrence` field is a **master**: `recurrence` is a bare [RRULE](https://icalendar.org/iCalendar-RFC-5545/3-3-10-recurrence-rule.html) string (e.g. `"FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=8"`) and the master's own `start`/`end` act as `DTSTART`/duration. `VcScheduler` expands each master into concrete **occurrences** for whatever window the active view renders -- occurrences are synthesized, not stored, and carry a "↻" marker (Month bar/chip, Timeline bar, mobile agenda row) so they're visually distinguishable from one-off events.
|
|
203
|
+
|
|
204
|
+
Occurrences are **not limited to a single day**: the master's `start`->`end` duration is preserved for every occurrence, so a multi-day master (all-day or timed) yields multi-day occurrences that render as spanning bars. For example, an all-day master `start: Fri 00:00`, `end: Mon 00:00` with `FREQ=WEEKLY;BYDAY=FR` repeats a 3-day Fri->Sun span every week (see the `RecurringMultiDay` story).
|
|
205
|
+
|
|
206
|
+
This is an **iCal-style storage model** -- your `events` array holds only masters and overrides, never the expanded occurrences:
|
|
207
|
+
|
|
208
|
+
- **Master**: one event with `recurrence` set. Its own `start`/`end` is also the first occurrence.
|
|
209
|
+
- **Exception dates**: `exceptionDates` on the master lists occurrence start times to skip entirely -- no occurrence is synthesized for them, and no override is required.
|
|
210
|
+
- **Override**: a separate, ordinary event (no `recurrence` of its own) with `recurrenceId` set to the master's `id` and `originalStart` set to the occurrence start it replaces. Any other field (`title`, `start`, `end`, `color`, ...) can differ from the synthesized occurrence -- this is how a single instance of a series gets moved, retitled, or recolored without touching the rest of the series.
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
const master: ISchedulerEvent = {
|
|
214
|
+
id: "m1",
|
|
215
|
+
title: "Weekly Pricing Sync",
|
|
216
|
+
start: new Date("2026-07-01T09:00:00"),
|
|
217
|
+
end: new Date("2026-07-01T10:00:00"),
|
|
218
|
+
recurrence: "FREQ=WEEKLY;BYDAY=MO,WE,FR;COUNT=8",
|
|
219
|
+
exceptionDates: [new Date("2026-07-08T09:00:00")], // that Wednesday is skipped
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const override: ISchedulerEvent = {
|
|
223
|
+
id: "o1",
|
|
224
|
+
title: "Pricing Sync (moved to afternoon)",
|
|
225
|
+
start: new Date("2026-07-13T14:00:00"),
|
|
226
|
+
end: new Date("2026-07-13T15:00:00"),
|
|
227
|
+
recurrenceId: "m1",
|
|
228
|
+
originalStart: new Date("2026-07-13T09:00:00"), // the occurrence it replaces
|
|
229
|
+
};
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Creating/editing recurrence: the editor's Repeat form
|
|
233
|
+
|
|
234
|
+
The built-in editor modal (see [Editing UX](#editing-ux-quick-create-popover-and-editor-modal)) exposes a **Repeat** field when creating a new event or editing a master's "All events" scope (see below): `None` / `Daily` / `Weekly` / `Monthly` / `Yearly`, an **every N** interval, weekday toggles (`Weekly` only, multi-select), and an **End** setting (`Never`, `After N occurrences`, or `On` a specific date). The editor bridges this form to/from the stored `recurrence` RRULE string -- hosts never construct or parse RRULE text themselves.
|
|
235
|
+
|
|
236
|
+
### Editing or deleting an occurrence: the This event / All events prompt
|
|
237
|
+
|
|
238
|
+
Clicking **Edit** or **Delete** on a recurring occurrence (in the quick-info popover or elsewhere) opens a scope dialog asking **This event** or **All events** before doing anything else:
|
|
239
|
+
|
|
240
|
+
| Action | Scope | Effect |
|
|
241
|
+
| ------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
242
|
+
| Edit | **This event** | Opens the editor for just that occurrence, with no recurrence field (an override isn't itself recurring). Saving creates or updates an override -- only that occurrence changes. |
|
|
243
|
+
| Edit | **All events** | Opens the editor pre-filled with the master's own fields and recurrence rule. Saving updates the master -- every occurrence reflects the change. |
|
|
244
|
+
| Delete | **This event** | Adds the occurrence's date to the master's `exceptionDates` and removes its override, if any -- only that occurrence disappears. |
|
|
245
|
+
| Delete | **All events** | Deletes the master itself -- the whole series (and any of its overrides) disappears. |
|
|
246
|
+
|
|
247
|
+
This scope choice only appears for occurrences of a recurring series (`recurrenceId` set); plain events and masters/overrides edited directly are unaffected.
|
|
248
|
+
|
|
249
|
+
### Extended `event-create` / `event-update` payloads
|
|
250
|
+
|
|
251
|
+
Recurrence routes through the same `event-create`/`event-update`/`event-delete` events already used for plain events (see [Events](#events)), with additional optional fields:
|
|
252
|
+
|
|
253
|
+
- `event-create` gains `recurrence?: string` (creating a new recurring master) and `recurrenceId?: string` / `originalStart?: Date` (creating a first-time override for a "This event" edit).
|
|
254
|
+
- `event-update` gains `recurrence?: string` (an "All events" edit changing the rule), `exceptionDates?: Date[]` (a "This event" delete), and `recurrenceId?: string` / `originalStart?: Date` (updating an existing override).
|
|
255
|
+
|
|
256
|
+
A host persisting events must apply these the same way the master/override model expects -- merge `event-update` onto the event matching `id`, and for `event-create`, push a master when `recurrence` is set or an override when `recurrenceId` is set. See the `RecurringEvents` story for a complete, working example of both.
|
|
257
|
+
|
|
258
|
+
### Out of scope
|
|
259
|
+
|
|
260
|
+
- **"This and following"** scope (edit/delete from an occurrence forward) -- only "This event" and "All events" are supported.
|
|
261
|
+
- **Advanced RRULE features** such as `BYSETPOS` or nth-weekday-of-month (e.g. "2nd Tuesday") -- the editor's Repeat form only produces `FREQ`/`INTERVAL`/`BYDAY`/`COUNT`/`UNTIL`.
|
|
262
|
+
- **`TZID`/timezone-aware recurrence** -- `recurrence` is evaluated against the master's own `start`/`end` as plain `Date` values, with no timezone conversion.
|
|
263
|
+
- **Dragging an occurrence** to move or resize it -- Month/Timeline drag-to-move/resize targets an event by `id`, and a synthesized occurrence has no `id` of its own in `events`; use the This event/All events edit flow instead.
|
|
264
|
+
|
|
130
265
|
## Overlapping Events and Overflow
|
|
131
266
|
|
|
132
267
|
All-day events that overlap in time on the same days are packed into separate stacked lanes automatically -- no configuration needed. When a day would need more lanes than fit, the extra events collapse into a "+N more" link; clicking it opens a popover listing every all-day event on that date.
|
|
@@ -146,96 +281,81 @@ Override event content or the entire toolbar without losing built-in interaction
|
|
|
146
281
|
</VcScheduler>
|
|
147
282
|
```
|
|
148
283
|
|
|
149
|
-
|
|
284
|
+
The `event` and `event-popover` slots work identically in Timeline view; `empty` is Timeline-only, shown when no events fall in the visible window -- see [Timeline View](#timeline-view).
|
|
150
285
|
|
|
151
286
|
## Timeline View
|
|
152
287
|
|
|
153
|
-
Set `view
|
|
288
|
+
Set `view` to `"timeline-day"` or `"timeline-week"` to render the same `events` array on a vertical time grid (hours down the Y axis, days across the X axis) instead of the Month grid -- no separate data props, and no `resources`/`bars` model. Switch views from the built-in toolbar (Month | Timeline Day | Timeline Week), or drive `view` directly:
|
|
154
289
|
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
id: string;
|
|
158
|
-
label: string;
|
|
159
|
-
meta?: Record<string, unknown>;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
interface ISchedulerBar {
|
|
163
|
-
id: string;
|
|
164
|
-
resourceId: string;
|
|
165
|
-
start: Date;
|
|
166
|
-
end: Date;
|
|
167
|
-
label?: string;
|
|
168
|
-
/** CSS color or CSS var() reference; defaults to var(--primary-500). */
|
|
169
|
-
color?: string;
|
|
170
|
-
/** Per-bar override of the global `editable` prop. */
|
|
171
|
-
editable?: boolean;
|
|
172
|
-
}
|
|
290
|
+
```vue
|
|
291
|
+
<VcScheduler v-model:view="view" v-model:date="date" :events="events" />
|
|
173
292
|
```
|
|
174
293
|
|
|
175
|
-
|
|
294
|
+
- **Timeline Day** shows one focused day (`date`) as a single vertical hour column.
|
|
295
|
+
- **Timeline Week** shows 7 day columns starting from `firstDayOfWeek` over a shared hour axis -- all 7 fit on screen, no horizontal scrolling.
|
|
296
|
+
|
|
297
|
+
Both render day header(s) with an hour gutter down the left, and scroll vertically on mount to the first event (or the working hours). Timed events are placed by their exact start/end time and split into side-by-side lanes when they overlap; all-day / multi-day events sit in a spanning strip above the grid (a single-day column can't represent a multi-day span). Clicking an event opens the same quick-info popover as Month view (unless `quickInfo` is `false`). When today is in view (and within the shown hour range), a thin red **current-time line** is drawn in the today column, updated each minute.
|
|
176
298
|
|
|
177
|
-
|
|
299
|
+
Narrow the rendered hour range with `dayStartHour`/`dayEndHour` (default `0`/`24`), e.g. to show only an 8 AM-8 PM business-hours window:
|
|
178
300
|
|
|
179
|
-
|
|
301
|
+
```vue
|
|
302
|
+
<VcScheduler view="timeline-day" :events="events" :day-start-hour="8" :day-end-hour="20" />
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The `empty` slot replaces the placeholder shown when no events fall in the visible window.
|
|
306
|
+
|
|
307
|
+
See the [Timeline recipe](#timeline-business-hours-review) below for a full example.
|
|
180
308
|
|
|
181
309
|
## Props
|
|
182
310
|
|
|
183
|
-
| Prop | Type | Default | Description
|
|
184
|
-
| ----------------- | --------------------------------- | ------------ |
|
|
185
|
-
| `events` | `ISchedulerEvent[]` | `[]` |
|
|
186
|
-
| `view` | `SchedulerView` | `"month"` | Active view (`"month"` \| `"timeline"`). Bind with `v-model:view`.
|
|
187
|
-
| `date` | `Date` | `new Date()` | Focused date. Bind with `v-model:date`.
|
|
188
|
-
| `editable` | `boolean` | `false` | Enables drag-to-move / drag-to-resize
|
|
189
|
-
| `firstDayOfWeek` | `number` | `1` | First column of the Month grid (0 = Sunday, 1 = Monday).
|
|
190
|
-
| `isEventEditable` | `(e: ISchedulerEvent) => boolean` | `undefined` |
|
|
191
|
-
| `
|
|
192
|
-
| `
|
|
193
|
-
| `
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `isBarEditable` | `(bar: ISchedulerBar) => boolean` | `undefined` | Timeline view. Per-bar override of `editable`. Re-evaluated every render. |
|
|
197
|
-
| `resourceWidth` | `number \| undefined` | `200` | Timeline view. Width in pixels of the left resource panel. |
|
|
198
|
-
| `barMinWidth` | `number \| undefined` | `undefined` | Timeline view. Reserved for future use. |
|
|
311
|
+
| Prop | Type | Default | Description |
|
|
312
|
+
| ----------------- | --------------------------------- | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
313
|
+
| `events` | `ISchedulerEvent[]` | `[]` | Events to render, in all views. |
|
|
314
|
+
| `view` | `SchedulerView` | `"month"` | Active view (`"month"` \| `"timeline-day"` \| `"timeline-week"`). Bind with `v-model:view`. |
|
|
315
|
+
| `date` | `Date` | `new Date()` | Focused date -- the month shown (Month), or the focused day/week (Timeline). Bind with `v-model:date`. |
|
|
316
|
+
| `editable` | `boolean` | `false` | Enables drag-to-move / drag-to-resize on events, and click/drag-to-create on empty cells. |
|
|
317
|
+
| `firstDayOfWeek` | `number` | `1` | First column of the Month grid, or first day of a Timeline Week (0 = Sunday, 1 = Monday). |
|
|
318
|
+
| `isEventEditable` | `(e: ISchedulerEvent) => boolean` | `undefined` | Per-event override of `editable`. Re-evaluated every render. |
|
|
319
|
+
| `quickInfo` | `boolean` | `true` | Opens the built-in quick-info popover on event click. Disable for a fully custom click flow. |
|
|
320
|
+
| `dayStartHour` | `number` | `0` | Timeline views. First rendered hour column (0-23). |
|
|
321
|
+
| `dayEndHour` | `number` | `24` | Timeline views. Last rendered hour column, exclusive (1-24). |
|
|
322
|
+
| `editorMode` | `"builtin"` \| `"emit"` | `"builtin"` | `"builtin"` opens the quick-create popover / editor modal for create and edit. `"emit"` skips both and re-emits the intent as `event-create`/`event-edit` instead. See [Editing UX](#editing-ux-quick-create-popover-and-editor-modal). |
|
|
323
|
+
| `loading` | `boolean` | `false` | Async loading. With no events yet, shows a view-shaped skeleton; while events already exist (a refresh), shows a loading overlay over the current content. Override the skeleton via the `#loading` slot. |
|
|
199
324
|
|
|
200
325
|
## Events
|
|
201
326
|
|
|
202
|
-
| Event | Payload
|
|
203
|
-
| -------------- |
|
|
204
|
-
| `update:view` | `SchedulerView`
|
|
205
|
-
| `update:date` | `Date`
|
|
206
|
-
| `event-click` | `ISchedulerEvent`
|
|
207
|
-
| `event-create` | `{ start: Date; end: Date; allDay: boolean }`
|
|
208
|
-
| `event-update` | `{ id: string; start: Date; end: Date }`
|
|
209
|
-
| `
|
|
210
|
-
| `
|
|
211
|
-
| `bar-update` | `IBarUpdate` (`{ id, start, end }`) | Timeline view. Fires after a drag-move or drag-resize is committed. |
|
|
212
|
-
| `bar-create` | `IBarCreate` (`{ resourceId, start, end }`) | Timeline view. Fires from the mobile agenda's inline "+" action. |
|
|
213
|
-
| `bar-delete` | `{ id: string }` | Timeline view. Fires when Delete is tapped in the mobile bar-edit sheet. |
|
|
214
|
-
| `update:zoom` | `SchedulerZoom` | Timeline view. Fires when the toolbar zoom in/out buttons change the zoom level. |
|
|
215
|
-
| `range-change` | `{ start: Date; end: Date }` | Timeline view. Fires on horizontal scroll with the currently visible date range. |
|
|
327
|
+
| Event | Payload | Description |
|
|
328
|
+
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
329
|
+
| `update:view` | `SchedulerView` | Fires when the toolbar's view switcher changes the active view. |
|
|
330
|
+
| `update:date` | `Date` | Fires when the toolbar's prev/next/today controls change the focused date. |
|
|
331
|
+
| `event-click` | `ISchedulerEvent` | Fires when an event bar or chip is activated (click or Enter), in any view. |
|
|
332
|
+
| `event-create` | `{ start: Date; end: Date; allDay: boolean; title: string; color?: string; recurrence?: string; recurrenceId?: string; originalStart?: Date }` | Fires when the quick-create popover or editor modal (create mode) is saved, in any view. `recurrence` is set for a new recurring master; `recurrenceId`/`originalStart` for a new occurrence override -- see [Recurring Events](#recurring-events). |
|
|
333
|
+
| `event-update` | `{ id: string; start: Date; end: Date; title?: string; allDay?: boolean; color?: string; recurrence?: string; exceptionDates?: Date[]; recurrenceId?: string; originalStart?: Date }` | Fires after a drag-move/drag-resize commits, or the editor modal (edit mode) is saved, in any view. The recurrence fields are only set by the This event/All events flow -- see [Recurring Events](#recurring-events). |
|
|
334
|
+
| `event-edit` | `ISchedulerEvent` | Only in `editorMode="emit"`: fires when the quick-info popover's "Edit" button is clicked, instead of opening the built-in editor. |
|
|
335
|
+
| `event-delete` | `{ id: string }` | Fires when the quick-info popover's "Delete" button, or the editor modal's "Delete" button, is clicked. |
|
|
216
336
|
|
|
217
337
|
## Slots
|
|
218
338
|
|
|
219
|
-
| Slot
|
|
220
|
-
|
|
|
221
|
-
| `event`
|
|
222
|
-
| `toolbar`
|
|
223
|
-
| `
|
|
224
|
-
| `
|
|
225
|
-
| `
|
|
226
|
-
| `empty`
|
|
339
|
+
| Slot | Scope | Description |
|
|
340
|
+
| --------------- | ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
|
341
|
+
| `event` | `{ event }` | Replaces an event's inner content, in any view. Defaults to `event.title`. |
|
|
342
|
+
| `toolbar` | `{ title, view }` | Replaces the entire built-in toolbar. |
|
|
343
|
+
| `event-popover` | `{ event, close }` | Replaces the built-in quick-info popover's content, in any view. |
|
|
344
|
+
| `quick-create` | `{ open, anchorRect, draft, save, more, close }` | Replaces the built-in quick-create popover. See [Editing UX](#editing-ux-quick-create-popover-and-editor-modal). |
|
|
345
|
+
| `event-editor` | `{ open, mode, draft, save, delete, close }` | Replaces the built-in editor modal. See [Editing UX](#editing-ux-quick-create-popover-and-editor-modal). |
|
|
346
|
+
| `empty` | -- | Timeline view. Replaces the empty-state shown when no events are in view. |
|
|
347
|
+
| `loading` | -- | Replaces the built-in first-load skeleton (shown when `loading` and no events yet). |
|
|
227
348
|
|
|
228
349
|
## CSS Custom Properties
|
|
229
350
|
|
|
230
|
-
| Property | Default | Description
|
|
231
|
-
| -------------------------- | --------------------- |
|
|
232
|
-
| `--scheduler-border-color` | `var(--neutrals-200)` | Border color for the grid, header,
|
|
233
|
-
| `--vc-scheduler-event-ink` | `#fff` | Text color on Month-view event bars/chips.
|
|
234
|
-
| `--z-critical-popup` | (theme z-index scale) | Stacking context for the "+N more" overflow popover.
|
|
235
|
-
| `--z-local-sticky` | (theme z-index scale) | Timeline view. Stacking context for the sticky
|
|
236
|
-
| `--z-critical-modal` | (theme z-index scale) | Timeline view. Stacking context for the mobile bottom-sheet editor. |
|
|
351
|
+
| Property | Default | Description |
|
|
352
|
+
| -------------------------- | --------------------- | --------------------------------------------------------------- |
|
|
353
|
+
| `--scheduler-border-color` | `var(--neutrals-200)` | Border color for the grid, header, and row separators. |
|
|
354
|
+
| `--vc-scheduler-event-ink` | `#fff` | Text color on Month-view event bars/chips. |
|
|
355
|
+
| `--z-critical-popup` | (theme z-index scale) | Stacking context for the "+N more" overflow popover. |
|
|
356
|
+
| `--z-local-sticky` | (theme z-index scale) | Timeline view. Stacking context for the sticky two-tier header. |
|
|
237
357
|
|
|
238
|
-
Event
|
|
358
|
+
Event fill color comes from `color` (any CSS color or `var(...)` reference) and defaults to `var(--primary-500)`. Event label text is white by default -- see [Common Mistakes](#common-mistakes).
|
|
239
359
|
|
|
240
360
|
## Recipes
|
|
241
361
|
|
|
@@ -271,34 +391,31 @@ const promotions = ref<ISchedulerEvent[]>([
|
|
|
271
391
|
const lockedIds = new Set(["p1"]);
|
|
272
392
|
const selectedPromotion = ref<ISchedulerEvent | null>(null);
|
|
273
393
|
|
|
274
|
-
function onEventUpdate(u: { id: string; start: Date; end: Date }) {
|
|
394
|
+
function onEventUpdate(u: { id: string; start: Date; end: Date; title?: string; allDay?: boolean; color?: string }) {
|
|
275
395
|
const promo = promotions.value.find((p) => p.id === u.id);
|
|
276
|
-
if (promo)
|
|
277
|
-
promo.start = u.start;
|
|
278
|
-
promo.end = u.end;
|
|
279
|
-
}
|
|
396
|
+
if (promo) Object.assign(promo, u);
|
|
280
397
|
}
|
|
281
398
|
|
|
282
|
-
function onEventCreate(c: { start: Date; end: Date; allDay: boolean }) {
|
|
283
|
-
promotions.value.push({ id: crypto.randomUUID(),
|
|
399
|
+
function onEventCreate(c: { start: Date; end: Date; allDay: boolean; title: string; color?: string }) {
|
|
400
|
+
promotions.value.push({ id: crypto.randomUUID(), ...c });
|
|
284
401
|
}
|
|
285
402
|
</script>
|
|
286
403
|
```
|
|
287
404
|
|
|
288
|
-
### Timeline:
|
|
405
|
+
### Timeline: business-hours review
|
|
289
406
|
|
|
290
407
|
```vue
|
|
291
408
|
<template>
|
|
292
409
|
<div style="height: 500px">
|
|
293
410
|
<VcScheduler
|
|
294
|
-
view="timeline"
|
|
295
|
-
:
|
|
296
|
-
:
|
|
297
|
-
|
|
411
|
+
view="timeline-day"
|
|
412
|
+
v-model:date="date"
|
|
413
|
+
:events="reviews"
|
|
414
|
+
:day-start-hour="8"
|
|
415
|
+
:day-end-hour="20"
|
|
298
416
|
editable
|
|
299
|
-
|
|
300
|
-
@
|
|
301
|
-
@bar-select="(bar) => (selectedPromotion = bar)"
|
|
417
|
+
@event-update="onEventUpdate"
|
|
418
|
+
@event-click="(e) => (selectedReview = e)"
|
|
302
419
|
/>
|
|
303
420
|
</div>
|
|
304
421
|
</template>
|
|
@@ -306,48 +423,27 @@ function onEventCreate(c: { start: Date; end: Date; allDay: boolean }) {
|
|
|
306
423
|
<script setup lang="ts">
|
|
307
424
|
import { ref } from "vue";
|
|
308
425
|
import { VcScheduler } from "@vc-shell/framework";
|
|
309
|
-
import type {
|
|
426
|
+
import type { ISchedulerEvent } from "@vc-shell/framework";
|
|
310
427
|
|
|
311
|
-
const
|
|
312
|
-
{ id: "pl-summer", label: "Summer Pricelist" },
|
|
313
|
-
{ id: "pl-loyalty", label: "Loyalty Pricelist" },
|
|
314
|
-
{ id: "pl-clearance", label: "Clearance" },
|
|
315
|
-
];
|
|
428
|
+
const date = ref(new Date(2026, 6, 15));
|
|
316
429
|
|
|
317
|
-
const
|
|
318
|
-
{ id: "
|
|
319
|
-
{ id: "
|
|
430
|
+
const reviews = ref<ISchedulerEvent[]>([
|
|
431
|
+
{ id: "r1", title: "Pricing sync", start: new Date("2026-07-15T09:00:00"), end: new Date("2026-07-15T10:00:00") },
|
|
432
|
+
{ id: "r2", title: "Loyalty review", start: new Date("2026-07-15T14:00:00"), end: new Date("2026-07-15T15:00:00") },
|
|
320
433
|
]);
|
|
321
434
|
|
|
322
|
-
const
|
|
323
|
-
const zoom = ref<SchedulerZoom>("week");
|
|
324
|
-
const selectedPromotion = ref<ISchedulerBar | null>(null);
|
|
435
|
+
const selectedReview = ref<ISchedulerEvent | null>(null);
|
|
325
436
|
|
|
326
|
-
function
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
329
|
-
promo.start = u.start;
|
|
330
|
-
promo.end = u.end;
|
|
331
|
-
}
|
|
437
|
+
function onEventUpdate(u: { id: string; start: Date; end: Date; title?: string; allDay?: boolean; color?: string }) {
|
|
438
|
+
const review = reviews.value.find((r) => r.id === u.id);
|
|
439
|
+
if (review) Object.assign(review, u);
|
|
332
440
|
}
|
|
333
441
|
</script>
|
|
334
442
|
```
|
|
335
443
|
|
|
336
444
|
## Common Mistakes
|
|
337
445
|
|
|
338
|
-
### 1.
|
|
339
|
-
|
|
340
|
-
`view` defaults to `"month"`, which only reads `events`. If you're migrating existing `resources`/`bars` usage (or copying an old timeline example), the grid renders empty unless you set the view explicitly.
|
|
341
|
-
|
|
342
|
-
```vue
|
|
343
|
-
<!-- WRONG: view defaults to "month", so resources/bars are silently ignored -->
|
|
344
|
-
<VcScheduler :resources="resources" :bars="bars" />
|
|
345
|
-
|
|
346
|
-
<!-- CORRECT -->
|
|
347
|
-
<VcScheduler view="timeline" :resources="resources" :bars="bars" />
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### 2. Passing `isEventEditable`/`isBarEditable` as a value instead of a getter function
|
|
446
|
+
### 1. Passing `isEventEditable` as a value instead of a getter function
|
|
351
447
|
|
|
352
448
|
```vue
|
|
353
449
|
<!-- WRONG: evaluates once, becomes a static boolean-ish value -->
|
|
@@ -357,23 +453,23 @@ function onBarUpdate(u: IBarUpdate) {
|
|
|
357
453
|
<VcScheduler :is-event-editable="(e) => e.meta?.locked !== true" ... />
|
|
358
454
|
```
|
|
359
455
|
|
|
360
|
-
###
|
|
456
|
+
### 2. Using a light CSS-variable event color
|
|
361
457
|
|
|
362
|
-
|
|
458
|
+
Bar/label ink is chosen automatically from the fill's luminance (near-white or near-black, whichever gives higher WCAG contrast), so a **hex** `color` — light or dark — always gets a legible label. The one gap: a **CSS-variable** fill (e.g. `var(--primary-300)`) can't be measured at runtime, so it falls back to white ink; a light var then fails contrast. Past events render as a pale tint of the fill with dark text (also AA), so this only affects live events with a light var fill.
|
|
363
459
|
|
|
364
460
|
```vue
|
|
365
|
-
<!-- WRONG: light
|
|
461
|
+
<!-- WRONG: a light CSS-var fill can't be measured, falls back to white ink -->
|
|
366
462
|
<script setup>
|
|
367
463
|
const events = [{ id: "a", title: "Promo", start, end, allDay: true, color: "var(--primary-300)" }];
|
|
368
464
|
</script>
|
|
369
465
|
|
|
370
|
-
<!-- CORRECT: use a
|
|
466
|
+
<!-- CORRECT: use a hex (auto ink handles it), a dark var, or omit color for the auto palette. -->
|
|
371
467
|
<script setup>
|
|
372
|
-
const events = [{ id: "a", title: "Promo", start, end, allDay: true, color: "
|
|
468
|
+
const events = [{ id: "a", title: "Promo", start, end, allDay: true, color: "#a21caf" }];
|
|
373
469
|
</script>
|
|
374
470
|
```
|
|
375
471
|
|
|
376
|
-
###
|
|
472
|
+
### 3. Using `view`/`date` as one-way props and expecting the toolbar to work
|
|
377
473
|
|
|
378
474
|
```vue
|
|
379
475
|
<!-- WRONG: :view/:date without v-model -- the toolbar's prev/next/today and
|
|
@@ -384,7 +480,7 @@ const events = [{ id: "a", title: "Promo", start, end, allDay: true, color: "var
|
|
|
384
480
|
<VcScheduler v-model:view="view" v-model:date="date" :events="events" />
|
|
385
481
|
```
|
|
386
482
|
|
|
387
|
-
###
|
|
483
|
+
### 4. Forgetting to give the container a height
|
|
388
484
|
|
|
389
485
|
```vue
|
|
390
486
|
<!-- WRONG: no height -- the grid/scroll container collapses to 0px -->
|
|
@@ -401,12 +497,13 @@ const events = [{ id: "a", title: "Promo", start, end, allDay: true, color: "var
|
|
|
401
497
|
- The Month grid uses `role="grid"` / `role="row"` / `role="gridcell"` / `role="columnheader"` for the weekday header and day cells, each `gridcell` carrying a full formatted-date `aria-label`.
|
|
402
498
|
- Each event bar and timed chip is a focusable `role="button"` element (`tabindex="0"`) with an `aria-label` built from its title and formatted start/end dates -- activate with click or `Enter`.
|
|
403
499
|
- The "+N more" overflow popover uses `role="dialog"` with an `aria-label` set to the formatted date.
|
|
500
|
+
- The built-in quick-create popover and editor modal reuse `VcPopover`/`VcPopup` respectively, so they inherit those components' accessible naming, focus trapping, and Escape-to-close behavior.
|
|
501
|
+
- The This event/All events scope dialog is a titled `VcPopup` as well. The recurrence editor's weekday toggle buttons expose `aria-pressed` (selected state) and an `aria-label` with the full weekday name, since their visible narrow labels ("S", "M", "T", ...) collide (Sun/Sat, Tue/Thu). The "↻" recurring-occurrence marker is `aria-hidden` -- purely visual, redundant with the event's own accessible name.
|
|
404
502
|
- The toolbar's prev/next buttons carry an explicit `aria-label` ("Previous" / "Next") since they are icon-only; the today/view-switch buttons show text labels.
|
|
405
|
-
- Event move/resize (
|
|
503
|
+
- Event move/resize (Month view's `editable`) is a pointer-drag interaction with no keyboard equivalent yet; use a form outside VcScheduler for keyboard-only date edits. Timeline view is read-only (click still opens the quick-info popover).
|
|
406
504
|
- Respects `prefers-reduced-motion` -- event/bar transitions and chip hover transitions are disabled.
|
|
407
505
|
|
|
408
506
|
## Related Components
|
|
409
507
|
|
|
410
508
|
- [VcDataTable](../vc-data-table/) -- for tabular (non-calendar) views of the same periods.
|
|
411
|
-
- [VcDatePicker](../../molecules/vc-date-picker/) -- used internally by the Timeline mobile bottom-sheet editor for date-range input.
|
|
412
509
|
- [VcGallery](../vc-gallery/) -- another organism with drag interaction, for image reordering rather than time-based events.
|