@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.1.0-pr258.9ee8dfb",
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.0
1
+ 2.2.0
@@ -1 +1 @@
1
- Synced from framework at commit 3a32dc437 on 2026-06-22T17:27:53.738Z
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 resource-by-time Timeline view.
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(), title: "New event", ...c });
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. [Overlapping Events and Overflow](#overlapping-events-and-overflow)
59
- 5. [Custom Slots](#custom-slots)
60
- 6. [Timeline View](#timeline-view)
61
- 7. [Props](#props)
62
- 8. [Events](#events)
63
- 9. [Slots](#slots)
64
- 10. [CSS Custom Properties](#css-custom-properties)
65
- 11. [Recipes](#recipes)
66
- 12. [Common Mistakes](#common-mistakes)
67
- 13. [Accessibility](#accessibility)
68
- 14. [Related Components](#related-components)
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 | Data props | Description |
81
- | ------------ | ------------------- | ------------------------------------------------------------------------------------------ |
82
- | `"month"` | `events` | Default. A 6-week grid; all-day events as bars, short events as chips. |
83
- | `"timeline"` | `resources`, `bars` | Resource rows with date-bound bars packed into lanes. See [Timeline View](#timeline-view). |
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 anchor). Bind it with `v-model:date` so the toolbar's prev/next/today controls update your state:
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
- Both view and data props are independent -- passing `resources`/`bars` while `view` is (or defaults to) `"month"` renders nothing from them, and vice versa. See [Common Mistakes](#common-mistakes).
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 CSS var() reference; defaults to var(--primary-500). */
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 in Month view, plus drag-to-create by pointer-dragging across empty day cells (commits via `event-create`). Moves and resizes commit on pointer-up via `event-update`.
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
- Timeline view has its own set of slots (`resource`, `bar`, `header-cell`, `empty`) -- see [Timeline View](#timeline-view).
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="timeline"` and pass `resources`/`bars` (instead of `events`) to render a resource-by-time timeline: each resource is a row, and bars are periods placed on it via `bar.resourceId`. Overlapping bars on the same resource pack into stacked lanes, the same way Month-view events do.
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
- ```ts
156
- interface ISchedulerResource {
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
- `zoom` controls tick granularity (`"hour" | "day" | "week" | "month" | "quarter" | "year"`, fine to coarse) -- bind with `v-model:zoom` so the toolbar's zoom in/out buttons stay in sync. `range` is the visible date window; when omitted it derives from the min/max span of `bars`, or `[now, now + 30 days]` when empty. `snap` (milliseconds, or `"auto"`) controls the drag/resize snap step, and `isBarEditable` is the per-bar analog of `isEventEditable`.
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
- Timeline-only slots: `resource` (`{ resource }`, replaces a resource-panel cell), `bar` (`{ bar }`, replaces bar content), `header-cell` (`{ tick }`, replaces a fine-tick header cell), `empty` (replaces the empty-state shown when `bars` is empty).
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
- See the [Timeline recipe](#timeline-promotions-with-per-pricelist-lock) below for a full example.
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[]` | `[]` | Month view. Events to render. |
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 (and, in Month view, drag-to-create). |
189
- | `firstDayOfWeek` | `number` | `1` | First column of the Month grid (0 = Sunday, 1 = Monday). |
190
- | `isEventEditable` | `(e: ISchedulerEvent) => boolean` | `undefined` | Month view. Per-event override of `editable`. Re-evaluated every render. |
191
- | `resources` | `ISchedulerResource[]` | `[]` | Timeline view. Rows of the timeline. |
192
- | `bars` | `ISchedulerBar[]` | `[]` | Timeline view. Periods placed on the timeline via `bar.resourceId`. |
193
- | `zoom` | `SchedulerZoom` | `"day"` | Timeline view. Tick granularity. Bind with `v-model:zoom`. |
194
- | `range` | `{ start: Date; end: Date }` | auto-derived | Timeline view. Visible date window. |
195
- | `snap` | `number \| "auto"` | `"auto"` | Timeline view. Snap step in milliseconds for drag/resize. |
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 | Description |
203
- | -------------- | --------------------------------------------- | -------------------------------------------------------------------------------- |
204
- | `update:view` | `SchedulerView` | Fires when the toolbar's view switcher changes the active view. |
205
- | `update:date` | `Date` | Fires when the toolbar's prev/next/today controls change the focused date. |
206
- | `event-click` | `ISchedulerEvent` | Month view. Fires when an event bar or chip is activated (click or Enter). |
207
- | `event-create` | `{ start: Date; end: Date; allDay: boolean }` | Month view. Fires when a drag-to-create gesture is committed. |
208
- | `event-update` | `{ id: string; start: Date; end: Date }` | Month view. Fires after a drag-move or drag-resize is committed. |
209
- | `bar-click` | `ISchedulerBar` | Timeline view. Fires when a bar is activated (click or Enter). |
210
- | `bar-select` | `ISchedulerBar \| null` | Timeline view. Fires on selection change; `null` when cleared. |
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 | Scope | Description |
220
- | ------------- | ----------------- | ------------------------------------------------------------------------- |
221
- | `event` | `{ event }` | Month view. Replaces an event's inner content. Defaults to `event.title`. |
222
- | `toolbar` | `{ title, view }` | Replaces the entire built-in toolbar. |
223
- | `resource` | `{ resource }` | Timeline view. Replaces a resource-panel cell's content. |
224
- | `bar` | `{ bar }` | Timeline view. Replaces a bar's inner content. |
225
- | `header-cell` | `{ tick }` | Timeline view. Replaces a fine-tick header cell's content. |
226
- | `empty` | -- | Timeline view. Replaces the empty-state shown when `bars` is 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, resource panel, and row separators. |
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 resource panel. |
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/bar fill color comes from `color` (any CSS color or `var(...)` reference) and defaults to `var(--primary-500)`. Bar/chip label text is white by default -- see [Common Mistakes](#common-mistakes).
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(), title: "New promotion", ...c });
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: promotions with per-pricelist lock
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
- :resources="pricelists"
296
- :bars="promotions"
297
- v-model:zoom="zoom"
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
- :is-bar-editable="(bar) => !lockedPricelistIds.has(bar.resourceId)"
300
- @bar-update="onBarUpdate"
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 { ISchedulerResource, ISchedulerBar, SchedulerZoom, IBarUpdate } from "@vc-shell/framework";
426
+ import type { ISchedulerEvent } from "@vc-shell/framework";
310
427
 
311
- const pricelists: ISchedulerResource[] = [
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 promotions = ref<ISchedulerBar[]>([
318
- { id: "b1", resourceId: "pl-summer", start: new Date("2026-07-01"), end: new Date("2026-07-14"), label: "Summer Sale" },
319
- { id: "b2", resourceId: "pl-loyalty", start: new Date("2026-07-01"), end: new Date("2026-07-31"), label: "July Prices" },
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 lockedPricelistIds = new Set(["pl-clearance"]);
323
- const zoom = ref<SchedulerZoom>("week");
324
- const selectedPromotion = ref<ISchedulerBar | null>(null);
435
+ const selectedReview = ref<ISchedulerEvent | null>(null);
325
436
 
326
- function onBarUpdate(u: IBarUpdate) {
327
- const promo = promotions.value.find((p) => p.id === u.id);
328
- if (promo) {
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. Forgetting `view="timeline"` -- `resources`/`bars` render nothing
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
- ### 3. Using a light event color and failing color contrast
456
+ ### 2. Using a light CSS-variable event color
361
457
 
362
- Event/bar labels default to white text (`--vc-scheduler-event-ink`, `#fff`). A light `color` fails readability with white text. Contrast is a **manual design responsibility** -- the `color-contrast` a11y rule is disabled repo-wide, so a poor color choice won't be caught automatically.
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 tint, white text on it fails 4.5:1 contrast -->
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 dark color, e.g. var(--primary-600), var(--primary-700), or var(--accent-600). Or omit color to use the default var(--primary-500). -->
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: "var(--primary-600)" }];
468
+ const events = [{ id: "a", title: "Promo", start, end, allDay: true, color: "#a21caf" }];
373
469
  </script>
374
470
  ```
375
471
 
376
- ### 4. Using `view`/`date` as one-way props and expecting the toolbar to work
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
- ### 5. Forgetting to give the container a height
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 (and Timeline bar move/resize) is a pointer-drag interaction with no keyboard equivalent yet; use a form outside VcScheduler for keyboard-only date edits.
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.