@cocoar/vue-ui 3.1.0 → 3.2.0-beta.11
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/bin/cli.mjs +53 -0
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
- package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
- package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
- package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
- package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
- package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
- package/dist/components/data-list/data-list-builder.d.ts +288 -0
- package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
- package/dist/components/data-list/index.d.ts +12 -0
- package/dist/components/data-list/index.d.ts.map +1 -0
- package/dist/components/data-list/internal/compare.d.ts +14 -0
- package/dist/components/data-list/internal/compare.d.ts.map +1 -0
- package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
- package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
- package/dist/components/data-list/internal/search.d.ts +16 -0
- package/dist/components/data-list/internal/search.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
- package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
- package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
- package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
- package/dist/components/data-list/types.d.ts +185 -0
- package/dist/components/data-list/types.d.ts.map +1 -0
- package/dist/components/data-list/useDataListModel.d.ts +79 -0
- package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
- package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
- package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
- package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
- package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
- package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
- package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
- package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
- package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
- package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
- package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
- package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
- package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
- package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
- package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
- package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
- package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
- package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
- package/dist/composables/dragRegistry.d.ts +12 -0
- package/dist/composables/dragRegistry.d.ts.map +1 -1
- package/dist/composables/index.d.ts +2 -2
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useDragDrop.d.ts +62 -6
- package/dist/composables/useDragDrop.d.ts.map +1 -1
- package/dist/composables/useVirtualList.d.ts +21 -1
- package/dist/composables/useVirtualList.d.ts.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4508 -2039
- package/package.json +9 -3
- package/skills/cocoar-vue-ui/SKILL.md +244 -0
- package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
- package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
- package/skills/cocoar-vue-ui/references/components/button.md +252 -0
- package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +372 -0
- package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1647 -0
- package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
- package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
- package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +486 -0
- package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
- package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
- package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
- package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
- package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
- package/skills/cocoar-vue-ui/references/components/card.md +151 -0
- package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
- package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
- package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
- package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
- package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
- package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
- package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
- package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
- package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
- package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
- package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
- package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
- package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
- package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
- package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
- package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
- package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
- package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
- package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
- package/skills/cocoar-vue-ui/references/components/link.md +235 -0
- package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
- package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
- package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
- package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
- package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
- package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
- package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
- package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
- package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
- package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
- package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
- package/skills/cocoar-vue-ui/references/components/note.md +96 -0
- package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
- package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
- package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
- package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
- package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
- package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
- package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
- package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
- package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
- package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
- package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
- package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
- package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
- package/skills/cocoar-vue-ui/references/components/select.md +463 -0
- package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
- package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
- package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
- package/skills/cocoar-vue-ui/references/components/table.md +170 -0
- package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
- package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
- package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
- package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
- package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
- package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
- package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
- package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
- package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
- package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
- package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
- package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
- package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
- package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
- package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
- package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
- package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
- package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
- package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
- package/skills/cocoar-vue-ui/references/guide/getting-started.md +116 -0
- package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
- package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
- package/skills/cocoar-vue-ui/references/guide/theming.md +98 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/calendar/timeline-view.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# `<CoarTimelineView>` — Timeline View (Preview)
|
|
4
|
+
|
|
5
|
+
Gantt-lite layout: **one row per logical event**, horizontal time-axis. The left pane lists event titles; the right pane renders bars positioned by `[start, end)` against the visible window. Same `CalendarEvent` data as Day / Week / Month / Agenda — just rendered as a project-plan timeline instead of a calendar grid.
|
|
6
|
+
|
|
7
|
+
**Recurring series collapse to one row.** All occurrences of a recurring series (those with `meta.__recurrence.seriesId`) share a single row with one bar per occurrence — a weekly stand-up with 26 instances in the visible window renders as ONE row labelled "Standup ×26", not 26 separate rows. Standalone events (no recurrence metadata) keep one row per event. The grouping is automatic; consumer code doesn't need to do anything.
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<CoarTimelineView :builder="builder" />
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Tip: Gantt-lite vs. full Gantt**
|
|
14
|
+
>
|
|
15
|
+
> This view covers the **timeline-of-events** half of project planning: bars on a time-axis, sorted, with window-clamping and continues-indicators. It does **not** yet include the rest of a full Gantt: task hierarchy (parent / sub-task collapse), dependencies (arrows between bars), critical-path computation, milestones, or resource lanes. Those land in a separate `@cocoar/vue-gantt` package later, building on this view's primitives.
|
|
16
|
+
|
|
17
|
+
## Live example
|
|
18
|
+
|
|
19
|
+
**Demo — `calendar/demos/CalendarTimeline.vue`**
|
|
20
|
+
|
|
21
|
+
```vue
|
|
22
|
+
<template>
|
|
23
|
+
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
24
|
+
<div style="display: flex; gap: 12px; align-items: center; font-size: 13px;">
|
|
25
|
+
<span :style="{ color: 'var(--coar-text-neutral-secondary)' }">
|
|
26
|
+
Rows: <strong>{{ events.length + series.length }}</strong>
|
|
27
|
+
({{ events.length }} one-off + {{ series.length }} series)
|
|
28
|
+
</span>
|
|
29
|
+
</div>
|
|
30
|
+
<div style="height: 520px; border: 1px solid var(--coar-border-neutral-tertiary); border-radius: var(--coar-radius-xs); overflow: hidden;">
|
|
31
|
+
<CoarCalendar :builder="builder" />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
/**
|
|
38
|
+
* Timeline-view showcase — mixes a few one-off project milestones
|
|
39
|
+
* with a recurring standup series. The recurring occurrences
|
|
40
|
+
* collapse into one row with N bars (one per occurrence) — labelled
|
|
41
|
+
* "Daily Standup ×N" in the left pane. The one-off milestones get
|
|
42
|
+
* one row per event.
|
|
43
|
+
*
|
|
44
|
+
* Drag empty space horizontally to pan. Bars are coloured rectangles
|
|
45
|
+
* only; the row label on the left is the title source of truth.
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import { ref } from 'vue';
|
|
49
|
+
import {
|
|
50
|
+
CoarCalendar,
|
|
51
|
+
Temporal,
|
|
52
|
+
useCalendar,
|
|
53
|
+
type CalendarEvent,
|
|
54
|
+
type CalendarView,
|
|
55
|
+
type RecurringSeries,
|
|
56
|
+
} from '@cocoar/vue-calendar';
|
|
57
|
+
|
|
58
|
+
const events = ref<CalendarEvent[]>([
|
|
59
|
+
{
|
|
60
|
+
id: 'design',
|
|
61
|
+
start: Temporal.PlainDate.from('2026-06-01'),
|
|
62
|
+
end: Temporal.PlainDate.from('2026-06-08'),
|
|
63
|
+
meta: { title: 'Design phase', color: '#4f46e5' },
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'build',
|
|
67
|
+
start: Temporal.PlainDate.from('2026-06-08'),
|
|
68
|
+
end: Temporal.PlainDate.from('2026-06-22'),
|
|
69
|
+
meta: { title: 'Build phase', color: '#06b6d4' },
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'qa',
|
|
73
|
+
start: Temporal.PlainDate.from('2026-06-22'),
|
|
74
|
+
end: Temporal.PlainDate.from('2026-06-29'),
|
|
75
|
+
meta: { title: 'QA + bug bash', color: '#f59e0b' },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: 'launch',
|
|
79
|
+
start: Temporal.PlainDate.from('2026-06-29'),
|
|
80
|
+
end: Temporal.PlainDate.from('2026-06-30'),
|
|
81
|
+
meta: { title: 'Launch day', color: '#ef4444' },
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: 'retro',
|
|
85
|
+
start: Temporal.ZonedDateTime.from('2026-07-02T14:00:00[Europe/Vienna]'),
|
|
86
|
+
end: Temporal.ZonedDateTime.from('2026-07-02T15:00:00[Europe/Vienna]'),
|
|
87
|
+
meta: { title: 'Retrospective', color: '#a855f7' },
|
|
88
|
+
},
|
|
89
|
+
]);
|
|
90
|
+
|
|
91
|
+
const series = ref<RecurringSeries[]>([
|
|
92
|
+
{
|
|
93
|
+
id: 'standup',
|
|
94
|
+
rrule: 'FREQ=WEEKLY;BYDAY=MO,WE,FR',
|
|
95
|
+
dtstart: Temporal.ZonedDateTime.from('2026-06-01T09:00:00[Europe/Vienna]'),
|
|
96
|
+
duration: { minutes: 30 },
|
|
97
|
+
meta: { title: 'Daily standup', color: '#10b981' },
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const view = ref<CalendarView>('timeline');
|
|
102
|
+
const cursor = ref(Temporal.PlainDate.from('2026-06-01'));
|
|
103
|
+
|
|
104
|
+
const { builder } = useCalendar();
|
|
105
|
+
builder
|
|
106
|
+
.events(events)
|
|
107
|
+
.series(series)
|
|
108
|
+
.view(view)
|
|
109
|
+
.date(cursor)
|
|
110
|
+
.timezone('Europe/Vienna')
|
|
111
|
+
.locale('de-AT')
|
|
112
|
+
.firstDayOfWeek(1)
|
|
113
|
+
.timelineRangeDays(45)
|
|
114
|
+
.timelinePixelsPerDay(48);
|
|
115
|
+
</script>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The demo above mixes four one-off project-milestone events (design / build / QA / launch / retro) with one recurring "Daily standup" series. The recurring occurrences collapse into a single row labelled "Daily standup ×N" with one coloured bar per occurrence; the one-off milestones each get their own row. Drag empty space to pan; the view-switcher button bar at the top lets you flip between Timeline and the other views to see the same data rendered differently.
|
|
119
|
+
|
|
120
|
+
## Standalone usage
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { ref } from 'vue';
|
|
124
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
125
|
+
import {
|
|
126
|
+
CoarTimelineView,
|
|
127
|
+
useTimelineView,
|
|
128
|
+
type CalendarEvent,
|
|
129
|
+
} from '@cocoar/vue-calendar';
|
|
130
|
+
|
|
131
|
+
const events = ref<CalendarEvent[]>([
|
|
132
|
+
{
|
|
133
|
+
id: 'design',
|
|
134
|
+
start: Temporal.PlainDate.from('2026-06-01'),
|
|
135
|
+
end: Temporal.PlainDate.from('2026-06-08'),
|
|
136
|
+
meta: { title: 'Design phase', color: '#4f46e5' },
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
id: 'build',
|
|
140
|
+
start: Temporal.PlainDate.from('2026-06-08'),
|
|
141
|
+
end: Temporal.PlainDate.from('2026-06-22'),
|
|
142
|
+
meta: { title: 'Build phase', color: '#06b6d4' },
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'demo',
|
|
146
|
+
start: Temporal.ZonedDateTime.from('2026-06-22T14:00:00[Europe/Vienna]'),
|
|
147
|
+
end: Temporal.ZonedDateTime.from('2026-06-22T15:30:00[Europe/Vienna]'),
|
|
148
|
+
meta: { title: 'Client demo', color: '#f59e0b' },
|
|
149
|
+
},
|
|
150
|
+
]);
|
|
151
|
+
const date = ref('2026-06-01');
|
|
152
|
+
|
|
153
|
+
const { builder } = useTimelineView();
|
|
154
|
+
builder
|
|
155
|
+
.events(events)
|
|
156
|
+
.date(date)
|
|
157
|
+
.timezone('Europe/Vienna')
|
|
158
|
+
.timelineRangeDays(45)
|
|
159
|
+
.timelinePixelsPerDay(40);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```html
|
|
163
|
+
<CoarTimelineView :builder="builder" />
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## How bars are positioned
|
|
167
|
+
|
|
168
|
+
Each logical event becomes one row, sorted by its FIRST bar's `start` ascending (group id ascending as tie-break — `meta.__recurrence.seriesId` for recurring events, `event.id` for standalone). The bar's geometry comes from:
|
|
169
|
+
|
|
170
|
+
- **Left** = `(event.start − windowStart) days × pixelsPerDay`. Cross-zone timed events project into the calendar's `timezone` (display zone) before their date is taken, so a meeting scheduled at 23:00 Tokyo viewed from Vienna still lands on the correct visual day (~16:00 Vienna).
|
|
171
|
+
- **Width** = duration in days × `pixelsPerDay`. All-day events use their `[start, end)` date range; timed events use `start..end` projected into the display zone; missing `end` defaults to start + 30 min (same fallback as the other views).
|
|
172
|
+
- **Top** = row index × `rowHeight`.
|
|
173
|
+
|
|
174
|
+
Single-day all-day events get a one-day-wide bar (not zero). Multi-day all-day events span their full range. Timed events that fall entirely within one day in the display zone also get a one-day bar — sub-day precision is intentionally not in this view (use Day / Week for hour-grained scheduling).
|
|
175
|
+
|
|
176
|
+
## Window clamping
|
|
177
|
+
|
|
178
|
+
The visible window is `[cursor, cursor + timelineRangeDays)`. Events that fall outside are filtered out entirely; events that **straddle** the window get a `clippedStart` or `clippedEnd` flag on their row + a squared-off bar edge (no rounded corner on the clipped side). Renderers can use the flags to overlay a "continues" chevron.
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
// In a custom #bar slot:
|
|
182
|
+
<template #bar="{ event, row }">
|
|
183
|
+
<span v-if="row.clippedStart">←</span>
|
|
184
|
+
<span>{{ event.meta?.title }}</span>
|
|
185
|
+
<span v-if="row.clippedEnd">→</span>
|
|
186
|
+
</template>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Performance — row virtualization
|
|
190
|
+
|
|
191
|
+
The label column and bar area are vertically virtualized: only rows inside the viewport (plus a small buffer of 8 rows on each side) render to DOM. A 1000-task project plan still costs ~30-40 DOM rows worth of nodes regardless of total count; scroll and pan stay at ~constant frame cost.
|
|
192
|
+
|
|
193
|
+
Virtualization is automatic — no opt-in flag. The `rowHeight` setter controls the math: a uniform row height makes the visible-range computation `O(1)` (`floor(scrollTop / rowHeight)`), no per-row measurement required. Slot renderers (`label`, `bar`) only fire for visible rows, so expensive markup inside slots doesn't get instantiated for off-screen tasks.
|
|
194
|
+
|
|
195
|
+
See the [/calendar-timeline-perf](http://localhost:5188/calendar-timeline-perf) playground page for an interactive bench at 100 / 500 / 1 000 / 2 500 tasks.
|
|
196
|
+
|
|
197
|
+
## Panning
|
|
198
|
+
|
|
199
|
+
Click-and-drag anywhere on the timeline (empty grid cells, the date axis, label column, or row whitespace — anything that isn't an interactive child like an event bar) to pan both axes at once. The cursor switches to `grab` over empty areas and to `grabbing` during the active pan. Pointer-capture keeps the pan alive even when the cursor leaves the timeline element (e.g. drags up into the page chrome).
|
|
200
|
+
|
|
201
|
+
Bar clicks are NOT hijacked — clicking an event bar still fires `onEventClick`. The pan handler walks up from `e.target` to check for `button` / `a` / `input` / `select` / `textarea` / `role="button"` and exits early when it finds one. Custom slots that render their own interactive elements inherit this behavior automatically.
|
|
202
|
+
|
|
203
|
+
Touch panning uses the same pointer pipeline (`touch-action: none` on the container), so finger-drag on tablets feels identical to mouse-drag on desktop.
|
|
204
|
+
|
|
205
|
+
## Sizing the view
|
|
206
|
+
|
|
207
|
+
Three knobs control the visual density:
|
|
208
|
+
|
|
209
|
+
| Setter | Default | Effect |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| `timelineRangeDays(n)` | `60` | How many days the view spans starting from `cursor`. `next()` / `prev()` step by this much. |
|
|
212
|
+
| `timelinePixelsPerDay(p)` | `56` | Horizontal density. `24` = quarter overview, `56` = month (date labels readable on one line), `96` = sprint detail. |
|
|
213
|
+
| `timelineRowHeight(h)` | `32` | Vertical row height — pick to match your bar content + density theme. |
|
|
214
|
+
| `timelineLabelWidth(w)` | `200` | Left-pane label-column width. |
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
// Quarter overview: 90 days, narrow bars
|
|
218
|
+
builder.timelineRangeDays(90).timelinePixelsPerDay(16);
|
|
219
|
+
|
|
220
|
+
// Sprint detail: 14 days, wide bars
|
|
221
|
+
builder.timelineRangeDays(14).timelinePixelsPerDay(64);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Slots
|
|
225
|
+
|
|
226
|
+
`<CoarTimelineView>` exposes three slots for custom rendering — all optional, with sensible plain-text defaults:
|
|
227
|
+
|
|
228
|
+
| Slot | Scope | Default |
|
|
229
|
+
|---|---|---|
|
|
230
|
+
| `label` | `{ row, event }` | `meta.title ?? id` of `row.bars[0].event` + occurrence-count badge (e.g. `×26`) when `row.isRecurring`. |
|
|
231
|
+
| `bar` | `{ row, bar, event }` | `meta.title` over a `meta.color`-tinted bar. Fires once per bar (N times for recurring rows). |
|
|
232
|
+
| `dateHeader` | `{ date, isToday, isWeekend }` | `Intl.DateTimeFormat` "MMM d" in the header axis. |
|
|
233
|
+
|
|
234
|
+
The `row` payload is a `TimelineRow` with `{ id, top, height, isRecurring, bars[] }`. Each `bar` is a `TimelineBar` with `{ event, left, width, clippedStart, clippedEnd }`. The `event` prop on the `bar` slot is a convenience alias for `bar.event`. For a recurring row, the `label` slot sees `row.bars[0].event` — the first occurrence in the window, which carries the series-level meta (title, color).
|
|
235
|
+
|
|
236
|
+
## Inside `<CoarCalendar>`
|
|
237
|
+
|
|
238
|
+
The view-switcher includes a "Timeline" button by default. Same builder feeds the embedded `<CoarTimelineView>`; the timeline-specific setters (`timelineRangeDays` etc.) live on the shared builder and are no-ops outside the timeline view, same convention as `timeRange` for day/week.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const { builder } = useCalendar();
|
|
242
|
+
builder
|
|
243
|
+
.timelineRangeDays(90)
|
|
244
|
+
.timelinePixelsPerDay(24);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## `useTimelineView<TMeta>()`
|
|
248
|
+
|
|
249
|
+
```ts
|
|
250
|
+
function useTimelineView<TMeta>(): {
|
|
251
|
+
builder: CalendarBuilder<TMeta>;
|
|
252
|
+
api: CalendarApi<TMeta>;
|
|
253
|
+
};
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Returns a fresh standalone builder + its imperative api. Pre-sets `view: 'timeline'` and `availableViews: ['timeline']` so the standalone view doesn't render an inactive view-switcher.
|
|
257
|
+
|
|
258
|
+
## Builder setters (timeline-specific)
|
|
259
|
+
|
|
260
|
+
| Setter | Argument | Default | Notes |
|
|
261
|
+
|---|---|---|---|
|
|
262
|
+
| `timelineRangeDays(n)` | `MaybeRefOrGetter<number>` | `60` | Days the view spans starting from the cursor. Also the `next()` / `prev()` step. |
|
|
263
|
+
| `timelinePixelsPerDay(p)` | `MaybeRefOrGetter<number>` | `56` | Horizontal density. |
|
|
264
|
+
| `timelineRowHeight(h)` | `MaybeRefOrGetter<number>` | `32` | Per-event row height in pixels. |
|
|
265
|
+
| `timelineLabelWidth(w)` | `MaybeRefOrGetter<number>` | `200` | Left-pane label-column width. |
|
|
266
|
+
| `eventRenderer(r)` | `EventRenderer<TMeta>` | — | Universal renderer — fires for timeline bars too (no per-layout discriminator here yet — use the dedicated `#bar` slot if you need the `row` geometry). |
|
|
267
|
+
|
|
268
|
+
Universal setters (`events`, `eventsLoader`, `series`, `seriesLoader`, `recurrenceEngine`, `timezone`, `locale`, …) work identically to the other views.
|
|
269
|
+
|
|
270
|
+
## `layoutTimeline(events, options)`
|
|
271
|
+
|
|
272
|
+
Pure layout helper exported from `@cocoar/vue-calendar`. Returns the same row geometry the view uses internally — useful for custom timeline implementations or tests:
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
import { layoutTimeline, Temporal } from '@cocoar/vue-calendar';
|
|
276
|
+
|
|
277
|
+
const { rows, totalWidth, totalHeight } = layoutTimeline(events, {
|
|
278
|
+
windowStart: Temporal.PlainDate.from('2026-06-01'),
|
|
279
|
+
windowEnd: Temporal.PlainDate.from('2026-08-01'),
|
|
280
|
+
pixelsPerDay: 56,
|
|
281
|
+
rowHeight: 32,
|
|
282
|
+
displayZone: 'Europe/Vienna',
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
for (const row of rows) {
|
|
286
|
+
// row: { id, top, height, isRecurring, bars[] }
|
|
287
|
+
for (const bar of row.bars) {
|
|
288
|
+
// bar: { event, left, width, clippedStart, clippedEnd }
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Imperative API
|
|
294
|
+
|
|
295
|
+
Same surface as the other views — see [`<CoarWeekView>` Imperative API](./week-view.md#imperative-api). `next()` / `prev()` step by `timelineRangeDays`; `getVisibleRange()` returns the `[cursor, cursor + timelineRangeDays)` window.
|
|
296
|
+
|
|
297
|
+
## `<CoarTimelineView>` props
|
|
298
|
+
|
|
299
|
+
| Prop | Type | Description |
|
|
300
|
+
|---|---|---|
|
|
301
|
+
| `builder` | `CalendarBuilder` | **Required.** From `useTimelineView()` (or share the one from `useCalendar()`). |
|
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
<!-- Generated from apps/docs/components/calendar/week-view.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
|
|
2
|
+
|
|
3
|
+
# `<CoarWeekView>` — Week View (Preview)
|
|
4
|
+
|
|
5
|
+
7-day time-grid view — one hour-axis on the left, seven day columns on the right, all-day band pinned under the day-of-week header. The week's first day is locale-aware (`en-US` / `ja-JP` start on Sunday, `de-AT` / `fr-FR` start on Monday) but can be overridden via `firstDayOfWeek()`.
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<CoarWeekView :builder="builder" />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Standalone usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { ref } from 'vue';
|
|
15
|
+
import { Temporal } from '@js-temporal/polyfill';
|
|
16
|
+
import {
|
|
17
|
+
CoarWeekView,
|
|
18
|
+
useWeekView,
|
|
19
|
+
type CalendarEvent,
|
|
20
|
+
} from '@cocoar/vue-calendar';
|
|
21
|
+
|
|
22
|
+
const events = ref<CalendarEvent[]>([
|
|
23
|
+
{
|
|
24
|
+
id: 'standup',
|
|
25
|
+
start: Temporal.ZonedDateTime.from('2026-04-15T09:00:00[UTC]'),
|
|
26
|
+
end: Temporal.ZonedDateTime.from('2026-04-15T09:30:00[UTC]'),
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
const date = ref('2026-04-15');
|
|
30
|
+
|
|
31
|
+
const { builder, api } = useWeekView();
|
|
32
|
+
builder
|
|
33
|
+
.events(events)
|
|
34
|
+
.date(date)
|
|
35
|
+
.timezone('UTC')
|
|
36
|
+
.timeRange([7, 20])
|
|
37
|
+
.slotDuration(30)
|
|
38
|
+
.firstDayOfWeek(1); // Monday — overrides locale default
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
<CoarWeekView :builder="builder" />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The Week view shares its builder type, `CalendarBuilder`, with the [Day view](./day-view.md) — they differ only in the days array the wrapper computes (Week uses `weekDates(date, fdow)` to expand the cursor to the 7-day window).
|
|
46
|
+
|
|
47
|
+
**Demo — `calendar/demos/WeekViewBasic.vue`**
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<template>
|
|
51
|
+
<div style="height: 600px; border: 1px solid var(--coar-border-neutral-tertiary); border-radius: var(--coar-radius-xs); overflow: hidden;">
|
|
52
|
+
<CoarWeekView :builder="builder" />
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<script setup lang="ts">
|
|
57
|
+
import { ref } from 'vue';
|
|
58
|
+
import {
|
|
59
|
+
CoarWeekView,
|
|
60
|
+
useWeekView,
|
|
61
|
+
Temporal,
|
|
62
|
+
type CalendarEvent,
|
|
63
|
+
} from '@cocoar/vue-calendar';
|
|
64
|
+
|
|
65
|
+
const date = ref(Temporal.PlainDate.from('2026-04-15'));
|
|
66
|
+
|
|
67
|
+
const pd = (iso: string) => Temporal.PlainDate.from(iso);
|
|
68
|
+
const zdt = (iso: string, tz = 'Europe/Vienna') =>
|
|
69
|
+
Temporal.ZonedDateTime.from(`${iso}[${tz}]`);
|
|
70
|
+
|
|
71
|
+
const events = ref<CalendarEvent[]>([
|
|
72
|
+
// Multi-day all-day band entries.
|
|
73
|
+
{
|
|
74
|
+
id: 'devconf',
|
|
75
|
+
start: pd('2026-04-13'),
|
|
76
|
+
end: pd('2026-04-16'),
|
|
77
|
+
meta: { title: 'DevConf — Vienna', color: '#7c3aed' },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'sven-ooo',
|
|
81
|
+
start: pd('2026-04-15'),
|
|
82
|
+
end: pd('2026-04-18'),
|
|
83
|
+
meta: { title: 'Sven — OOO', color: '#9ca3af' },
|
|
84
|
+
},
|
|
85
|
+
// Daily standups Mon–Fri.
|
|
86
|
+
...['2026-04-13', '2026-04-14', '2026-04-15', '2026-04-16', '2026-04-17'].map(
|
|
87
|
+
(d): CalendarEvent => ({
|
|
88
|
+
id: `standup-${d}`,
|
|
89
|
+
start: zdt(`${d}T09:00:00`),
|
|
90
|
+
end: zdt(`${d}T09:30:00`),
|
|
91
|
+
meta: { title: 'Daily standup', color: '#10b981' },
|
|
92
|
+
}),
|
|
93
|
+
),
|
|
94
|
+
// Wed busy-day cluster.
|
|
95
|
+
{
|
|
96
|
+
id: 'wed-design',
|
|
97
|
+
start: zdt('2026-04-15T11:00:00'),
|
|
98
|
+
end: zdt('2026-04-15T12:30:00'),
|
|
99
|
+
meta: { title: 'Design review', color: '#8b5cf6' },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'wed-lunch',
|
|
103
|
+
start: zdt('2026-04-15T12:00:00'),
|
|
104
|
+
end: zdt('2026-04-15T13:00:00'),
|
|
105
|
+
meta: { title: 'Lunch with Anna', color: '#ef4444' },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'wed-1on1',
|
|
109
|
+
start: zdt('2026-04-15T15:00:00'),
|
|
110
|
+
end: zdt('2026-04-15T15:45:00'),
|
|
111
|
+
meta: { title: '1:1 with Bernhard', color: '#3b82f6' },
|
|
112
|
+
},
|
|
113
|
+
// Thu deep-work block.
|
|
114
|
+
{
|
|
115
|
+
id: 'thu-deep',
|
|
116
|
+
start: zdt('2026-04-16T09:00:00'),
|
|
117
|
+
end: zdt('2026-04-16T13:00:00'),
|
|
118
|
+
meta: { title: 'Deep work — Calendar', color: '#2563eb' },
|
|
119
|
+
},
|
|
120
|
+
// Fri client demo.
|
|
121
|
+
{
|
|
122
|
+
id: 'fri-demo',
|
|
123
|
+
start: zdt('2026-04-17T15:00:00'),
|
|
124
|
+
end: zdt('2026-04-17T16:30:00'),
|
|
125
|
+
meta: { title: 'Client demo', color: '#dc2626' },
|
|
126
|
+
},
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
const { builder } = useWeekView();
|
|
130
|
+
builder
|
|
131
|
+
.events(events)
|
|
132
|
+
.date(date)
|
|
133
|
+
.timezone('Europe/Vienna')
|
|
134
|
+
.firstDayOfWeek(1) // Monday — overrides the locale default
|
|
135
|
+
.onEventDrop(({ event, next }) => {
|
|
136
|
+
const idx = events.value.findIndex((e) => e.id === event.id);
|
|
137
|
+
if (idx < 0) return;
|
|
138
|
+
events.value = [
|
|
139
|
+
...events.value.slice(0, idx),
|
|
140
|
+
{ ...event, start: next.start, ...(next.end ? { end: next.end } : {}) },
|
|
141
|
+
...events.value.slice(idx + 1),
|
|
142
|
+
];
|
|
143
|
+
});
|
|
144
|
+
</script>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Custom day header
|
|
148
|
+
|
|
149
|
+
The `#dayHeader` slot replaces the default per-column header. Receives `{ date, isToday, isWeekend }` so you can render whatever fits your design — the example below stacks day-of-week + day-of-month with a today indicator and weekend muting.
|
|
150
|
+
|
|
151
|
+
**Demo — `calendar/demos/WeekViewCustomDayHeader.vue`**
|
|
152
|
+
|
|
153
|
+
```vue
|
|
154
|
+
<template>
|
|
155
|
+
<div>
|
|
156
|
+
<p class="hint">
|
|
157
|
+
Custom <code>#dayHeader</code> slot — day-of-week + day-of-month
|
|
158
|
+
stacked, today highlighted with a blue dot, weekends muted.
|
|
159
|
+
</p>
|
|
160
|
+
<div style="height: 540px; border: 1px solid var(--coar-border-neutral-tertiary); border-radius: var(--coar-radius-xs); overflow: hidden;">
|
|
161
|
+
<CoarWeekView :builder="builder">
|
|
162
|
+
<template #dayHeader="{ date: d, isToday, isWeekend }">
|
|
163
|
+
<div class="hdr" :class="{ 'hdr--today': isToday, 'hdr--weekend': isWeekend }">
|
|
164
|
+
<span class="hdr__dow">{{ formatDow(d) }}</span>
|
|
165
|
+
<span class="hdr__day">
|
|
166
|
+
{{ d.day }}
|
|
167
|
+
<span v-if="isToday" class="hdr__dot" />
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
</template>
|
|
171
|
+
</CoarWeekView>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
</template>
|
|
175
|
+
|
|
176
|
+
<script setup lang="ts">
|
|
177
|
+
import { ref } from 'vue';
|
|
178
|
+
import {
|
|
179
|
+
CoarWeekView,
|
|
180
|
+
useWeekView,
|
|
181
|
+
Temporal,
|
|
182
|
+
type CalendarEvent,
|
|
183
|
+
} from '@cocoar/vue-calendar';
|
|
184
|
+
|
|
185
|
+
const date = ref(Temporal.PlainDate.from('2026-04-15'));
|
|
186
|
+
|
|
187
|
+
const zdt = (iso: string, tz = 'Europe/Vienna') =>
|
|
188
|
+
Temporal.ZonedDateTime.from(`${iso}[${tz}]`);
|
|
189
|
+
|
|
190
|
+
const events = ref<CalendarEvent[]>([
|
|
191
|
+
...['2026-04-13', '2026-04-14', '2026-04-15', '2026-04-16', '2026-04-17'].map(
|
|
192
|
+
(d): CalendarEvent => ({
|
|
193
|
+
id: `standup-${d}`,
|
|
194
|
+
start: zdt(`${d}T09:00:00`),
|
|
195
|
+
end: zdt(`${d}T09:30:00`),
|
|
196
|
+
meta: { title: 'Standup', color: '#10b981' },
|
|
197
|
+
}),
|
|
198
|
+
),
|
|
199
|
+
{
|
|
200
|
+
id: 'review',
|
|
201
|
+
start: zdt('2026-04-15T14:00:00'),
|
|
202
|
+
end: zdt('2026-04-15T15:00:00'),
|
|
203
|
+
meta: { title: 'Sprint review', color: '#8b5cf6' },
|
|
204
|
+
},
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
const { builder } = useWeekView();
|
|
208
|
+
builder
|
|
209
|
+
.events(events)
|
|
210
|
+
.date(date)
|
|
211
|
+
.timezone('Europe/Vienna')
|
|
212
|
+
.firstDayOfWeek(1)
|
|
213
|
+
.timeRange({ startMinutes: 8 * 60, endMinutes: 18 * 60 })
|
|
214
|
+
.onEventDrop(({ event, next }) => {
|
|
215
|
+
const idx = events.value.findIndex((e) => e.id === event.id);
|
|
216
|
+
if (idx < 0) return;
|
|
217
|
+
events.value = [
|
|
218
|
+
...events.value.slice(0, idx),
|
|
219
|
+
{ ...event, start: next.start, ...(next.end ? { end: next.end } : {}) },
|
|
220
|
+
...events.value.slice(idx + 1),
|
|
221
|
+
];
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const dowFmt = new Intl.DateTimeFormat('en-US', { weekday: 'short', timeZone: 'UTC' });
|
|
225
|
+
function formatDow(d: Temporal.PlainDate): string {
|
|
226
|
+
return dowFmt
|
|
227
|
+
.format(new Date(Date.UTC(d.year, d.month - 1, d.day)))
|
|
228
|
+
.toUpperCase();
|
|
229
|
+
}
|
|
230
|
+
</script>
|
|
231
|
+
|
|
232
|
+
<style scoped>
|
|
233
|
+
.hint {
|
|
234
|
+
margin: 0 0 12px;
|
|
235
|
+
font-size: 13px;
|
|
236
|
+
color: var(--coar-text-subtle, #6b7280);
|
|
237
|
+
}
|
|
238
|
+
.hint code {
|
|
239
|
+
font-family: var(--coar-font-family-mono, monospace);
|
|
240
|
+
font-size: 12px;
|
|
241
|
+
background: var(--coar-background-neutral-tertiary, #f3f4f6);
|
|
242
|
+
padding: 1px 5px;
|
|
243
|
+
border-radius: 3px;
|
|
244
|
+
}
|
|
245
|
+
.hdr {
|
|
246
|
+
display: flex;
|
|
247
|
+
flex-direction: column;
|
|
248
|
+
align-items: flex-start;
|
|
249
|
+
gap: 2px;
|
|
250
|
+
padding: 4px 8px;
|
|
251
|
+
}
|
|
252
|
+
.hdr__dow {
|
|
253
|
+
font-size: 10px;
|
|
254
|
+
font-weight: 700;
|
|
255
|
+
letter-spacing: 0.06em;
|
|
256
|
+
color: var(--coar-text-subtle, #6b7280);
|
|
257
|
+
}
|
|
258
|
+
.hdr__day {
|
|
259
|
+
display: inline-flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
gap: 6px;
|
|
262
|
+
font-size: 18px;
|
|
263
|
+
font-weight: 600;
|
|
264
|
+
color: var(--coar-text-base, #1a1c1f);
|
|
265
|
+
}
|
|
266
|
+
.hdr__dot {
|
|
267
|
+
width: 6px;
|
|
268
|
+
height: 6px;
|
|
269
|
+
border-radius: 50%;
|
|
270
|
+
background: var(--coar-color-accent, #2563eb);
|
|
271
|
+
}
|
|
272
|
+
.hdr--today .hdr__day {
|
|
273
|
+
color: var(--coar-color-accent, #2563eb);
|
|
274
|
+
}
|
|
275
|
+
.hdr--weekend .hdr__day,
|
|
276
|
+
.hdr--weekend .hdr__dow {
|
|
277
|
+
color: var(--coar-text-subtle, #9ca3af);
|
|
278
|
+
}
|
|
279
|
+
</style>
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Inside `<CoarCalendar>`
|
|
283
|
+
|
|
284
|
+
`<CoarCalendar>` and `<CoarWeekView>` consume the SAME `CalendarBuilder` instance — there's no sub-builder forking. Set time-grid config directly on the composer's builder:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
const { builder } = useCalendar();
|
|
288
|
+
builder
|
|
289
|
+
.timeRange({ startMinutes: 8 * 60, endMinutes: 18 * 60 })
|
|
290
|
+
.slotDuration(15);
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
When the active view is week or day, the same builder feeds the embedded `<CoarTimeGrid>`. View-specific settings simply have no effect outside their view (e.g. `maxEventsPerCell` is a no-op in week view).
|
|
294
|
+
|
|
295
|
+
## `useWeekView<TMeta>()`
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
function useWeekView<TMeta>(): {
|
|
299
|
+
builder: CalendarBuilder<TMeta>;
|
|
300
|
+
api: CalendarApi<TMeta>;
|
|
301
|
+
};
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Returns a fresh standalone builder + its imperative api. The builder type is the same `CalendarBuilder` used by `<CoarCalendar>` — `useWeekView()` is a thin shorthand that pre-sets `view: 'week'`. The `next()` / `prev()` step is one **week** here, not one day.
|
|
305
|
+
|
|
306
|
+
## Builder setters
|
|
307
|
+
|
|
308
|
+
Full reference: see [the composer's API reference](./coar-calendar.md#api-reference). Highlights that matter for the week view:
|
|
309
|
+
|
|
310
|
+
| Setter | Argument | Default | Notes |
|
|
311
|
+
|---|---|---|---|
|
|
312
|
+
| `firstDayOfWeek(d)` | `0..6 \| undefined` | locale-aware | `0` = Sunday … `6` = Saturday. |
|
|
313
|
+
| `dayHeaderRenderer(r)` | `DayHeaderRenderer` | — | Per-day column header — typically the most-customised piece on a week view. |
|
|
314
|
+
| `timeRange(r)` | `MaybeRefOrGetter<{ startMinutes, endMinutes }>` | `{0, 1440}` | Visible hour range, in minutes from midnight. |
|
|
315
|
+
| `slotDuration(d)` | `MaybeRefOrGetter<number>` | `30` | Slot subdivision (minutes). Also the snap step when dragging. |
|
|
316
|
+
| `pixelsPerHour(p)` | `MaybeRefOrGetter<number>` | `60` | Vertical density. |
|
|
317
|
+
| `eventRenderer(r)` | `EventRenderer<TMeta>` | — | Universal renderer. For all-day-band bars, branch on `ctx.layout?.kind === 'allDayBar'`; for time-grid event cards, `ctx.layout?.kind === 'positioned'`. |
|
|
318
|
+
|
|
319
|
+
## Multi-day events & the all-day band
|
|
320
|
+
|
|
321
|
+
Multi-day events that touch any visible day are split into one bar per row, each clipped to the row. Single-day all-day events appear in the same band. Cluster-aware lane sizing means a busy day doesn't unfairly narrow events on quieter days in the same week.
|
|
322
|
+
|
|
323
|
+
### Lane cap and band height
|
|
324
|
+
|
|
325
|
+
The band shows at most `allDayMaxVisibleLanes` lanes — default **3**, like the system calendar. When the layout needs more, the last visible lane is given up for per-day **"+N"** markers; a click on a marker expands the band, and a **Show fewer** control under the axis label folds it back. `null` shows every lane.
|
|
326
|
+
|
|
327
|
+
`allDayBandMode` decides how much height the band claims. The hour axis starts below the band, so every height change moves the whole grid:
|
|
328
|
+
|
|
329
|
+
| Mode | Height | When |
|
|
330
|
+
|---|---|---|
|
|
331
|
+
| `fitsContent` (default) | as tall as needed, absent without all-day events | Dense weeks where every pixel counts. |
|
|
332
|
+
| `alwaysOneLane` | at least one lane | Removes the 0↔1 jump — the most frequent and the largest — when paging through days. |
|
|
333
|
+
| `reservesCap` | always `allDayMaxVisibleLanes` tall | The hour axis sits at the same place on every day; costs a few rows when nothing is in them. |
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
builder.allDayMaxVisibleLanes(3).allDayBandMode('reservesCap');
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Overlapping timed cards
|
|
340
|
+
|
|
341
|
+
Events in the same collision group form a content-aware cascade: later cards render in front, but only cover as much of the card behind them as its content block allows. When the width that stays **unobscured** falls below `timedEventDetailMinWidth` (default **112 px**, like iOS), the built-in card switches to a **compact anatomy**: one end-truncated title line, no location row, no time row. Every card remains a separate clickable, draggable target. With room, the built-in card shows the location (from 34 minutes of height at the default scale), the time span (from 52) and lets the title wrap once (from 70).
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
builder.timedEventDetailMinWidth(96); // switch a little later
|
|
345
|
+
builder.timedEventDetailMinWidth(0); // never compact
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
A custom `#event` slot or `eventRenderer` is never touched by the policy — the cascade then keeps the safe side-by-side widths and your content decides what to show. The Day view uses exactly the same layout.
|
|
349
|
+
## Point events (timed, no `end`)
|
|
350
|
+
|
|
351
|
+
A timed event without `end` keeps the default 30-minute slot geometry but renders distinguishably from a real 30-minute event: a solid start edge in the event color sits exactly on the start time, and the card body drops to ~38 % fill opacity — the title stays fully opaque. Resize handles are suppressed (there is no `end` to grab). Month and Agenda render point events unchanged. The look matches the SwiftUI port; tune it via `--coar-calendar-point-edge-height` / `--coar-calendar-point-body-opacity` (see [Theming](./index.md#theming)).
|
|
352
|
+
|
|
353
|
+
## Imperative API
|
|
354
|
+
|
|
355
|
+
```ts
|
|
356
|
+
interface CalendarApi<TMeta> {
|
|
357
|
+
goTo(iso: string): void;
|
|
358
|
+
goToToday(): void;
|
|
359
|
+
next(): void; // ±1 week
|
|
360
|
+
prev(): void;
|
|
361
|
+
getVisibleRange(): ViewWindow | null;
|
|
362
|
+
getVisibleEvents(): CalendarEvent<TMeta>[];
|
|
363
|
+
scrollToTime(hour: number): void;
|
|
364
|
+
refresh(): void;
|
|
365
|
+
refreshRange(start: string, end: string): void;
|
|
366
|
+
readonly loading: Readonly<Ref<boolean>>;
|
|
367
|
+
readonly visibleRange: Readonly<Ref<ViewWindow | null>>;
|
|
368
|
+
readonly gridReady: Readonly<Ref<boolean>>;
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## `<CoarWeekView>` props + slots
|
|
373
|
+
|
|
374
|
+
| Prop | Type | Description |
|
|
375
|
+
|---|---|---|
|
|
376
|
+
| `builder` | `CalendarBuilder` | **Required.** From `useWeekView()` (or share the one from `useCalendar()`). |
|
|
377
|
+
|
|
378
|
+
| Slot | Scope | Purpose |
|
|
379
|
+
|---|---|---|
|
|
380
|
+
| `event` | `{ event, layout }` | Per-event renderer. `layout` is the `PositionedEvent` (lane / startMinutes / endMinutes / clipping flags). |
|
|
381
|
+
| `allDayEvent` | `{ event, layout }` | All-day band renderer. `layout` is the `AllDayBar` (lane / startCol / endCol / clipping flags). |
|
|
382
|
+
| `dayHeader` | `{ date, isToday, isWeekend }` | Per-day column header. |
|