@cocoar/vue-ui 3.1.0 → 3.2.0-beta.6

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.
Files changed (179) hide show
  1. package/dist/components/checkbox/CoarCheckbox.vue.d.ts +1 -1
  2. package/dist/components/checkbox/CoarCheckbox.vue.d.ts.map +1 -1
  3. package/dist/components/code-block/CoarCodeBlock.vue.d.ts +1 -1
  4. package/dist/components/code-block/CoarCodeBlock.vue.d.ts.map +1 -1
  5. package/dist/components/data-list/CoarDataList.vue.d.ts +158 -0
  6. package/dist/components/data-list/CoarDataList.vue.d.ts.map +1 -0
  7. package/dist/components/data-list/CoarDataListToolbar.vue.d.ts +30 -0
  8. package/dist/components/data-list/CoarDataListToolbar.vue.d.ts.map +1 -0
  9. package/dist/components/data-list/data-list-builder.d.ts +288 -0
  10. package/dist/components/data-list/data-list-builder.d.ts.map +1 -0
  11. package/dist/components/data-list/index.d.ts +12 -0
  12. package/dist/components/data-list/index.d.ts.map +1 -0
  13. package/dist/components/data-list/internal/compare.d.ts +14 -0
  14. package/dist/components/data-list/internal/compare.d.ts.map +1 -0
  15. package/dist/components/data-list/internal/reorder-core.d.ts +46 -0
  16. package/dist/components/data-list/internal/reorder-core.d.ts.map +1 -0
  17. package/dist/components/data-list/internal/search.d.ts +16 -0
  18. package/dist/components/data-list/internal/search.d.ts.map +1 -0
  19. package/dist/components/data-list/internal/useDataListLines.d.ts +92 -0
  20. package/dist/components/data-list/internal/useDataListLines.d.ts.map +1 -0
  21. package/dist/components/data-list/internal/useDataListReorder.d.ts +68 -0
  22. package/dist/components/data-list/internal/useDataListReorder.d.ts.map +1 -0
  23. package/dist/components/data-list/internal/useSearchHighlight.d.ts +19 -0
  24. package/dist/components/data-list/internal/useSearchHighlight.d.ts.map +1 -0
  25. package/dist/components/data-list/types.d.ts +185 -0
  26. package/dist/components/data-list/types.d.ts.map +1 -0
  27. package/dist/components/data-list/useDataListModel.d.ts +79 -0
  28. package/dist/components/data-list/useDataListModel.d.ts.map +1 -0
  29. package/dist/components/date-time/_shared/maskito-config.d.ts +7 -2
  30. package/dist/components/date-time/_shared/maskito-config.d.ts.map +1 -1
  31. package/dist/components/date-time/_shared/time-helpers.d.ts +11 -4
  32. package/dist/components/date-time/_shared/time-helpers.d.ts.map +1 -1
  33. package/dist/components/date-time/_shared/use-date-picker-base.d.ts +1 -1
  34. package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts +1 -1
  35. package/dist/components/date-time/plain-date-picker/CoarPlainDatePicker.vue.d.ts.map +1 -1
  36. package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts +1 -1
  37. package/dist/components/date-time/plain-date-time-picker/CoarPlainDateTimePicker.vue.d.ts.map +1 -1
  38. package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts +1 -1
  39. package/dist/components/date-time/zoned-date-time-picker/CoarZonedDateTimePicker.vue.d.ts.map +1 -1
  40. package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts +3 -0
  41. package/dist/components/dual-listbox/CoarDualListbox.vue.d.ts.map +1 -1
  42. package/dist/components/form-field/CoarFormField.vue.d.ts +1 -1
  43. package/dist/components/form-field/CoarFormField.vue.d.ts.map +1 -1
  44. package/dist/components/icon/CoarIcon.vue.d.ts +2 -2
  45. package/dist/components/icon/CoarIcon.vue.d.ts.map +1 -1
  46. package/dist/components/listbox/CoarListbox.vue.d.ts +7 -0
  47. package/dist/components/listbox/CoarListbox.vue.d.ts.map +1 -1
  48. package/dist/components/number-input/CoarNumberInput.vue.d.ts +1 -1
  49. package/dist/components/number-input/CoarNumberInput.vue.d.ts.map +1 -1
  50. package/dist/components/otp-input/CoarOtpInput.vue.d.ts +1 -1
  51. package/dist/components/otp-input/CoarOtpInput.vue.d.ts.map +1 -1
  52. package/dist/components/password-input/CoarPasswordInput.vue.d.ts +1 -1
  53. package/dist/components/password-input/CoarPasswordInput.vue.d.ts.map +1 -1
  54. package/dist/components/switch/CoarSwitch.vue.d.ts +1 -1
  55. package/dist/components/switch/CoarSwitch.vue.d.ts.map +1 -1
  56. package/dist/components/text-input/CoarTextInput.vue.d.ts +1 -1
  57. package/dist/components/text-input/CoarTextInput.vue.d.ts.map +1 -1
  58. package/dist/composables/dragRegistry.d.ts +12 -0
  59. package/dist/composables/dragRegistry.d.ts.map +1 -1
  60. package/dist/composables/index.d.ts +2 -2
  61. package/dist/composables/index.d.ts.map +1 -1
  62. package/dist/composables/useDragDrop.d.ts +62 -6
  63. package/dist/composables/useDragDrop.d.ts.map +1 -1
  64. package/dist/composables/useVirtualList.d.ts +21 -1
  65. package/dist/composables/useVirtualList.d.ts.map +1 -1
  66. package/dist/index.css +1 -1
  67. package/dist/index.d.ts +4 -2
  68. package/dist/index.d.ts.map +1 -1
  69. package/dist/index.js +4508 -2039
  70. package/package.json +5 -3
  71. package/skills/cocoar-vue-ui/SKILL.md +244 -0
  72. package/skills/cocoar-vue-ui/references/components/avatar.md +178 -0
  73. package/skills/cocoar-vue-ui/references/components/badge.md +290 -0
  74. package/skills/cocoar-vue-ui/references/components/breadcrumb.md +238 -0
  75. package/skills/cocoar-vue-ui/references/components/button.md +252 -0
  76. package/skills/cocoar-vue-ui/references/components/calendar/agenda-view.md +353 -0
  77. package/skills/cocoar-vue-ui/references/components/calendar/coar-calendar.md +1645 -0
  78. package/skills/cocoar-vue-ui/references/components/calendar/day-view.md +361 -0
  79. package/skills/cocoar-vue-ui/references/components/calendar/index.md +312 -0
  80. package/skills/cocoar-vue-ui/references/components/calendar/month-view.md +475 -0
  81. package/skills/cocoar-vue-ui/references/components/calendar/performance.md +186 -0
  82. package/skills/cocoar-vue-ui/references/components/calendar/timeline-view.md +301 -0
  83. package/skills/cocoar-vue-ui/references/components/calendar/week-view.md +382 -0
  84. package/skills/cocoar-vue-ui/references/components/calendar/work-week-view.md +145 -0
  85. package/skills/cocoar-vue-ui/references/components/calendar/year-view.md +63 -0
  86. package/skills/cocoar-vue-ui/references/components/card.md +151 -0
  87. package/skills/cocoar-vue-ui/references/components/checkbox-group.md +103 -0
  88. package/skills/cocoar-vue-ui/references/components/checkbox.md +279 -0
  89. package/skills/cocoar-vue-ui/references/components/code-block.md +241 -0
  90. package/skills/cocoar-vue-ui/references/components/context-menu.md +355 -0
  91. package/skills/cocoar-vue-ui/references/components/data-grid/checkbox.md +213 -0
  92. package/skills/cocoar-vue-ui/references/components/data-grid/date-columns.md +272 -0
  93. package/skills/cocoar-vue-ui/references/components/data-grid/editing.md +236 -0
  94. package/skills/cocoar-vue-ui/references/components/data-grid/multi-select.md +193 -0
  95. package/skills/cocoar-vue-ui/references/components/data-grid/number.md +147 -0
  96. package/skills/cocoar-vue-ui/references/components/data-grid/select.md +202 -0
  97. package/skills/cocoar-vue-ui/references/components/data-grid/text.md +127 -0
  98. package/skills/cocoar-vue-ui/references/components/data-grid.md +1225 -0
  99. package/skills/cocoar-vue-ui/references/components/data-list.md +2121 -0
  100. package/skills/cocoar-vue-ui/references/components/date-or-time-picker.md +200 -0
  101. package/skills/cocoar-vue-ui/references/components/date-picker.md +203 -0
  102. package/skills/cocoar-vue-ui/references/components/date-time-picker.md +154 -0
  103. package/skills/cocoar-vue-ui/references/components/date-views.md +212 -0
  104. package/skills/cocoar-vue-ui/references/components/dialog.md +148 -0
  105. package/skills/cocoar-vue-ui/references/components/divider.md +157 -0
  106. package/skills/cocoar-vue-ui/references/components/document-viewer/annotations.md +400 -0
  107. package/skills/cocoar-vue-ui/references/components/document-viewer/coar-document-viewer.md +327 -0
  108. package/skills/cocoar-vue-ui/references/components/document-viewer/index.md +234 -0
  109. package/skills/cocoar-vue-ui/references/components/document-viewer/toolbar.md +221 -0
  110. package/skills/cocoar-vue-ui/references/components/drag-drop.md +267 -0
  111. package/skills/cocoar-vue-ui/references/components/dual-listbox.md +449 -0
  112. package/skills/cocoar-vue-ui/references/components/file-explorer/asset-store.md +327 -0
  113. package/skills/cocoar-vue-ui/references/components/file-explorer/in-memory-store.md +176 -0
  114. package/skills/cocoar-vue-ui/references/components/file-explorer/index.md +311 -0
  115. package/skills/cocoar-vue-ui/references/components/file-explorer/use-file-explorer.md +300 -0
  116. package/skills/cocoar-vue-ui/references/components/form-field.md +1025 -0
  117. package/skills/cocoar-vue-ui/references/components/fragment-parser.md +298 -0
  118. package/skills/cocoar-vue-ui/references/components/link.md +235 -0
  119. package/skills/cocoar-vue-ui/references/components/listbox.md +575 -0
  120. package/skills/cocoar-vue-ui/references/components/map/editor.md +448 -0
  121. package/skills/cocoar-vue-ui/references/components/map/index.md +351 -0
  122. package/skills/cocoar-vue-ui/references/components/markdown-diagrams.md +210 -0
  123. package/skills/cocoar-vue-ui/references/components/markdown-editor.md +1478 -0
  124. package/skills/cocoar-vue-ui/references/components/markdown-embeds.md +387 -0
  125. package/skills/cocoar-vue-ui/references/components/markdown-form.md +447 -0
  126. package/skills/cocoar-vue-ui/references/components/markdown.md +276 -0
  127. package/skills/cocoar-vue-ui/references/components/menu.md +380 -0
  128. package/skills/cocoar-vue-ui/references/components/mermaid.md +172 -0
  129. package/skills/cocoar-vue-ui/references/components/navbar.md +147 -0
  130. package/skills/cocoar-vue-ui/references/components/note.md +96 -0
  131. package/skills/cocoar-vue-ui/references/components/notice.md +131 -0
  132. package/skills/cocoar-vue-ui/references/components/number-input.md +215 -0
  133. package/skills/cocoar-vue-ui/references/components/otp-input.md +324 -0
  134. package/skills/cocoar-vue-ui/references/components/page-builder/authoring-contract.md +271 -0
  135. package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-builder.md +476 -0
  136. package/skills/cocoar-vue-ui/references/components/page-builder/coar-page-renderer.md +677 -0
  137. package/skills/cocoar-vue-ui/references/components/page-builder/custom-elements.md +441 -0
  138. package/skills/cocoar-vue-ui/references/components/page-builder/idp-integration.md +122 -0
  139. package/skills/cocoar-vue-ui/references/components/page-builder/index.md +573 -0
  140. package/skills/cocoar-vue-ui/references/components/pagination.md +150 -0
  141. package/skills/cocoar-vue-ui/references/components/panel-layout.md +372 -0
  142. package/skills/cocoar-vue-ui/references/components/password-input.md +175 -0
  143. package/skills/cocoar-vue-ui/references/components/popconfirm.md +186 -0
  144. package/skills/cocoar-vue-ui/references/components/popover.md +173 -0
  145. package/skills/cocoar-vue-ui/references/components/progress-bar.md +178 -0
  146. package/skills/cocoar-vue-ui/references/components/radio-group.md +225 -0
  147. package/skills/cocoar-vue-ui/references/components/script-editor.md +1271 -0
  148. package/skills/cocoar-vue-ui/references/components/segmented-control.md +238 -0
  149. package/skills/cocoar-vue-ui/references/components/select.md +463 -0
  150. package/skills/cocoar-vue-ui/references/components/sidebar.md +421 -0
  151. package/skills/cocoar-vue-ui/references/components/spinner.md +132 -0
  152. package/skills/cocoar-vue-ui/references/components/switch.md +195 -0
  153. package/skills/cocoar-vue-ui/references/components/table.md +170 -0
  154. package/skills/cocoar-vue-ui/references/components/tabs.md +382 -0
  155. package/skills/cocoar-vue-ui/references/components/tag.md +178 -0
  156. package/skills/cocoar-vue-ui/references/components/text-input.md +256 -0
  157. package/skills/cocoar-vue-ui/references/components/toast.md +160 -0
  158. package/skills/cocoar-vue-ui/references/components/tooltip.md +121 -0
  159. package/skills/cocoar-vue-ui/references/components/transitions.md +193 -0
  160. package/skills/cocoar-vue-ui/references/components/tree.md +2388 -0
  161. package/skills/cocoar-vue-ui/references/components/virtual-list.md +212 -0
  162. package/skills/cocoar-vue-ui/references/components/wizard.md +251 -0
  163. package/skills/cocoar-vue-ui/references/components/zoned-date-time-picker.md +177 -0
  164. package/skills/cocoar-vue-ui/references/foundations/colors.md +708 -0
  165. package/skills/cocoar-vue-ui/references/foundations/design-principles.md +115 -0
  166. package/skills/cocoar-vue-ui/references/foundations/icons.md +381 -0
  167. package/skills/cocoar-vue-ui/references/foundations/localization/formatting.md +530 -0
  168. package/skills/cocoar-vue-ui/references/foundations/localization/setup.md +86 -0
  169. package/skills/cocoar-vue-ui/references/foundations/localization/timezones.md +182 -0
  170. package/skills/cocoar-vue-ui/references/foundations/localization/translations.md +305 -0
  171. package/skills/cocoar-vue-ui/references/foundations/motion.md +549 -0
  172. package/skills/cocoar-vue-ui/references/foundations/spacing.md +330 -0
  173. package/skills/cocoar-vue-ui/references/foundations/theming.md +140 -0
  174. package/skills/cocoar-vue-ui/references/foundations/typography.md +206 -0
  175. package/skills/cocoar-vue-ui/references/guide/error-handling.md +162 -0
  176. package/skills/cocoar-vue-ui/references/guide/getting-started.md +119 -0
  177. package/skills/cocoar-vue-ui/references/guide/migration-page-builder-3.md +207 -0
  178. package/skills/cocoar-vue-ui/references/guide/migration.md +140 -0
  179. package/skills/cocoar-vue-ui/references/guide/theming.md +98 -0
@@ -0,0 +1,475 @@
1
+ <!-- Generated from apps/docs/components/calendar/month-view.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Month Views (Preview)
4
+
5
+ The shell's Month view follows the iOS structure: months scroll continuously and expose **Compact**, **Stacked**, **Details**, and **List** display choices. Continuous sections render only the 4–6 weeks the month actually needs; leading and trailing positions stay blank instead of repeating neighbour dates.
6
+
7
+ | Choice | Rendering |
8
+ |---|---|
9
+ | Compact | 52 px base week rows; per-day events combine into a segmented colour capsule. |
10
+ | Stacked | 68 px base rows; compact individual event marks. |
11
+ | Details | 94 px base rows; titles, assignees, multi-day bars and row expansion. |
12
+ | List | Compact month selector plus the selected day's event list; stacked in narrow containers and side-by-side from 720 px. |
13
+
14
+ The regular Month choices use `<CoarContinuousMonthView>`. `<CoarMonthView>` remains exported as the lower-level single-month section for widgets and custom compositions.
15
+
16
+ ```ts
17
+ const { builder } = useCalendar();
18
+ builder
19
+ .view('month')
20
+ .availableViews(['month', 'monthList'])
21
+ .monthDensity('compact')
22
+ .shadeWeekends(true);
23
+ ```
24
+
25
+ ## Continuous month
26
+
27
+ Month navigation scrolls to the requested section. Scrolling updates the builder cursor when the next month's heading reaches the top, and event / recurrence loaders preload the adjacent months. The surface initially materializes 13 months and extends in either direction while keeping a bounded DOM window.
28
+
29
+ ```html
30
+ <CoarContinuousMonthView :builder="builder" />
31
+ ```
32
+
33
+ ## Month List {#month-list}
34
+
35
+ `monthList` is a real serialized `CalendarView`, but `<CoarCalendar>` nests it under Month as the **List** display choice. Selecting a date in the small calendar updates the builder cursor and the adjacent daily event list.
36
+
37
+ ```html
38
+ <CoarMonthListView :builder="builder" />
39
+ ```
40
+
41
+ ## Single month section {#single-month-section}
42
+
43
+ `<CoarMonthView>` renders the fixed single-section grid used internally by the continuous composition. Multi-day events render as continuous **bars** across the rows they touch; single-day events render as **pills** inside cells. Use it directly when the host owns pagination or needs a compact embedded month.
44
+
45
+ ```html
46
+ <CoarMonthView :builder="builder" />
47
+ ```
48
+
49
+ ## Standalone usage
50
+
51
+ ```ts
52
+ import { ref } from 'vue';
53
+ import { Temporal } from '@js-temporal/polyfill';
54
+ import {
55
+ CoarContinuousMonthView,
56
+ CoarMonthListView,
57
+ CoarMonthView,
58
+ useMonthView,
59
+ type CalendarEvent,
60
+ } from '@cocoar/vue-calendar';
61
+
62
+ const events = ref<CalendarEvent[]>([
63
+ {
64
+ id: 'devconf',
65
+ start: Temporal.PlainDate.from('2026-04-13'),
66
+ end: Temporal.PlainDate.from('2026-04-16'),
67
+ },
68
+ {
69
+ id: 'standup',
70
+ start: Temporal.ZonedDateTime.from('2026-04-15T09:00:00[UTC]'),
71
+ end: Temporal.ZonedDateTime.from('2026-04-15T09:30:00[UTC]'),
72
+ },
73
+ ]);
74
+ const date = ref('2026-04-15');
75
+
76
+ const { builder, api } = useMonthView();
77
+ builder
78
+ .events(events)
79
+ .date(date)
80
+ .timezone('UTC')
81
+ .maxEventsPerCell(5)
82
+ .eventRenderer((ctx) => {
83
+ if (ctx.layout?.kind === 'monthPill') return h(MyPill, { event: ctx.event, pill: ctx.layout.layout });
84
+ if (ctx.layout?.kind === 'monthBar') return h(MyBar, { event: ctx.event, bar: ctx.layout.layout });
85
+ return undefined; // fall through to lib default for other variants
86
+ });
87
+ ```
88
+
89
+ ```html
90
+ <CoarMonthView :builder="builder" />
91
+ ```
92
+
93
+ **Demo — `calendar/demos/MonthViewBasic.vue`**
94
+
95
+ ```vue
96
+ <template>
97
+ <div style="height: 640px; border: 1px solid var(--coar-border-neutral-tertiary); border-radius: var(--coar-radius-xs); overflow: hidden;">
98
+ <CoarMonthView :builder="builder" />
99
+ </div>
100
+ </template>
101
+
102
+ <script setup lang="ts">
103
+ import { ref } from 'vue';
104
+ import {
105
+ CoarMonthView,
106
+ useMonthView,
107
+ Temporal,
108
+ type CalendarEvent,
109
+ } from '@cocoar/vue-calendar';
110
+
111
+ const date = ref(Temporal.PlainDate.from('2026-04-15'));
112
+
113
+ const pd = (iso: string) => Temporal.PlainDate.from(iso);
114
+ const zdt = (iso: string, tz = 'Europe/Vienna') =>
115
+ Temporal.ZonedDateTime.from(`${iso}[${tz}]`);
116
+
117
+ const events = ref<CalendarEvent[]>([
118
+ // Multi-day bars across week boundaries.
119
+ {
120
+ id: 'devconf',
121
+ start: pd('2026-04-13'),
122
+ end: pd('2026-04-16'),
123
+ meta: { title: 'DevConf — Vienna', color: '#7c3aed' },
124
+ },
125
+ {
126
+ id: 'sven-ooo',
127
+ start: pd('2026-04-15'),
128
+ end: pd('2026-04-18'),
129
+ meta: { title: 'Sven — OOO', color: '#9ca3af' },
130
+ },
131
+ {
132
+ id: 'team-offsite',
133
+ start: pd('2026-04-22'),
134
+ end: pd('2026-04-28'),
135
+ meta: { title: 'Team offsite', color: '#0891b2' },
136
+ },
137
+ {
138
+ id: 'easter',
139
+ start: pd('2026-04-03'),
140
+ end: pd('2026-04-07'),
141
+ meta: { title: 'Easter break', color: '#84cc16' },
142
+ },
143
+ // Daily standups Mon–Fri.
144
+ ...['2026-04-06', '2026-04-07', '2026-04-08', '2026-04-09', '2026-04-10'].map(
145
+ (d): CalendarEvent => ({
146
+ id: `standup-${d}`,
147
+ start: zdt(`${d}T09:00:00`),
148
+ end: zdt(`${d}T09:30:00`),
149
+ meta: { title: 'Standup', color: '#10b981' },
150
+ }),
151
+ ),
152
+ ...['2026-04-13', '2026-04-14', '2026-04-15', '2026-04-16', '2026-04-17'].map(
153
+ (d): CalendarEvent => ({
154
+ id: `standup-w2-${d}`,
155
+ start: zdt(`${d}T09:00:00`),
156
+ end: zdt(`${d}T09:30:00`),
157
+ meta: { title: 'Standup', color: '#06b6d4' },
158
+ }),
159
+ ),
160
+ // Wed busy day — extra pills to trigger the per-cell scroll + kebab.
161
+ {
162
+ id: 'wed-design',
163
+ start: zdt('2026-04-15T11:00:00'),
164
+ end: zdt('2026-04-15T12:30:00'),
165
+ meta: { title: 'Design review', color: '#8b5cf6' },
166
+ },
167
+ {
168
+ id: 'wed-pair',
169
+ start: zdt('2026-04-15T11:30:00'),
170
+ end: zdt('2026-04-15T13:00:00'),
171
+ meta: { title: 'Pair: calendar', color: '#f59e0b' },
172
+ },
173
+ {
174
+ id: 'wed-lunch',
175
+ start: zdt('2026-04-15T12:00:00'),
176
+ end: zdt('2026-04-15T13:00:00'),
177
+ meta: { title: 'Lunch with Anna', color: '#ef4444' },
178
+ },
179
+ {
180
+ id: 'wed-1on1',
181
+ start: zdt('2026-04-15T15:00:00'),
182
+ end: zdt('2026-04-15T15:45:00'),
183
+ meta: { title: '1:1 with Bernhard', color: '#3b82f6' },
184
+ },
185
+ // Quarterly review crossing into May.
186
+ {
187
+ id: 'quarter-review',
188
+ start: pd('2026-04-29'),
189
+ end: pd('2026-05-02'),
190
+ meta: { title: 'Quarterly review', color: '#2563eb' },
191
+ },
192
+ ]);
193
+
194
+ const { builder } = useMonthView();
195
+ builder
196
+ .events(events)
197
+ .date(date)
198
+ .timezone('Europe/Vienna')
199
+ .onEventDrop(({ event, next }) => {
200
+ const idx = events.value.findIndex((e) => e.id === event.id);
201
+ if (idx < 0) return;
202
+ events.value = [
203
+ ...events.value.slice(0, idx),
204
+ { ...event, start: next.start, ...(next.end ? { end: next.end } : {}) },
205
+ ...events.value.slice(idx + 1),
206
+ ];
207
+ });
208
+ </script>
209
+ ```
210
+
211
+ ## Custom pills and bars
212
+
213
+ The month view exposes two slots — `#pill` for single-day events and `#multiDayBar` for multi-day events. Both receive `{ event, pill }` / `{ event, bar }` so you can branch on the layout payload (lane, col-span, clipping flags) when needed.
214
+
215
+ **Demo — `calendar/demos/MonthViewCustomPillBar.vue`**
216
+
217
+ ```vue
218
+ <template>
219
+ <div>
220
+ <p class="hint">
221
+ Custom <code>#pill</code> + <code>#multiDayBar</code> slots —
222
+ pills get a leading time chip; bars get a trailing day-count chip.
223
+ </p>
224
+ <div style="height: 600px; border: 1px solid var(--coar-border-neutral-tertiary); border-radius: var(--coar-radius-xs); overflow: hidden;">
225
+ <CoarMonthView :builder="builder">
226
+ <template #pill="{ event }">
227
+ <span class="pill">
228
+ <span class="pill__time">{{ formatTime(event) }}</span>
229
+ <span class="pill__title">{{ title(event) }}</span>
230
+ </span>
231
+ </template>
232
+ <template #multiDayBar="{ event, bar }">
233
+ <span class="bar">
234
+ <span class="bar__title">{{ title(event) }}</span>
235
+ <span class="bar__chip">{{ bar.endCol - bar.startCol + 1 }}d</span>
236
+ </span>
237
+ </template>
238
+ </CoarMonthView>
239
+ </div>
240
+ </div>
241
+ </template>
242
+
243
+ <script setup lang="ts">
244
+ import { ref } from 'vue';
245
+ import {
246
+ CoarMonthView,
247
+ useMonthView,
248
+ Temporal,
249
+ isTimedEvent,
250
+ type CalendarEvent,
251
+ } from '@cocoar/vue-calendar';
252
+
253
+ const date = ref(Temporal.PlainDate.from('2026-04-15'));
254
+
255
+ const pd = (iso: string) => Temporal.PlainDate.from(iso);
256
+ const zdt = (iso: string, tz = 'Europe/Vienna') =>
257
+ Temporal.ZonedDateTime.from(`${iso}[${tz}]`);
258
+
259
+ const events = ref<CalendarEvent[]>([
260
+ {
261
+ id: 'devconf',
262
+ start: pd('2026-04-13'),
263
+ end: pd('2026-04-16'),
264
+ meta: { title: 'DevConf — Vienna', color: '#7c3aed' },
265
+ },
266
+ {
267
+ id: 'sven-ooo',
268
+ start: pd('2026-04-15'),
269
+ end: pd('2026-04-18'),
270
+ meta: { title: 'Sven — OOO', color: '#9ca3af' },
271
+ },
272
+ {
273
+ id: 'team-offsite',
274
+ start: pd('2026-04-22'),
275
+ end: pd('2026-04-28'),
276
+ meta: { title: 'Team offsite', color: '#0891b2' },
277
+ },
278
+ {
279
+ id: 'standup-mon',
280
+ start: zdt('2026-04-13T09:00:00'),
281
+ end: zdt('2026-04-13T09:30:00'),
282
+ meta: { title: 'Standup', color: '#10b981' },
283
+ },
284
+ {
285
+ id: 'review',
286
+ start: zdt('2026-04-15T11:00:00'),
287
+ end: zdt('2026-04-15T12:00:00'),
288
+ meta: { title: 'Design review', color: '#8b5cf6' },
289
+ },
290
+ {
291
+ id: 'lunch',
292
+ start: zdt('2026-04-15T12:00:00'),
293
+ end: zdt('2026-04-15T13:00:00'),
294
+ meta: { title: 'Lunch', color: '#ef4444' },
295
+ },
296
+ {
297
+ id: 'demo',
298
+ start: zdt('2026-04-17T15:00:00'),
299
+ end: zdt('2026-04-17T16:30:00'),
300
+ meta: { title: 'Client demo', color: '#dc2626' },
301
+ },
302
+ ]);
303
+
304
+ const { builder } = useMonthView();
305
+ builder
306
+ .events(events)
307
+ .date(date)
308
+ .timezone('Europe/Vienna')
309
+ .onEventDrop(({ event, next }) => {
310
+ const idx = events.value.findIndex((e) => e.id === event.id);
311
+ if (idx < 0) return;
312
+ events.value = [
313
+ ...events.value.slice(0, idx),
314
+ { ...event, start: next.start, ...(next.end ? { end: next.end } : {}) },
315
+ ...events.value.slice(idx + 1),
316
+ ];
317
+ });
318
+
319
+ function title(event: CalendarEvent): string {
320
+ return (event.meta as { title?: string } | undefined)?.title ?? event.id;
321
+ }
322
+
323
+ const timeFmt = new Intl.DateTimeFormat('en-US', {
324
+ hour: 'numeric',
325
+ minute: '2-digit',
326
+ timeZone: 'Europe/Vienna',
327
+ });
328
+ function formatTime(event: CalendarEvent): string {
329
+ // All-day events (PlainDate start) have no clock time to show.
330
+ if (!isTimedEvent(event)) return '';
331
+ // ZonedDateTime → epoch ms via toInstant() so we can hand it to Intl.
332
+ return timeFmt
333
+ .format(new Date(event.start.toInstant().epochMilliseconds))
334
+ .replace(' ', ' ');
335
+ }
336
+ </script>
337
+
338
+ <style scoped>
339
+ .hint {
340
+ margin: 0 0 12px;
341
+ font-size: 13px;
342
+ color: var(--coar-text-subtle, #6b7280);
343
+ }
344
+ .hint code {
345
+ font-family: var(--coar-font-family-mono, monospace);
346
+ font-size: 12px;
347
+ background: var(--coar-background-neutral-tertiary, #f3f4f6);
348
+ padding: 1px 5px;
349
+ border-radius: 3px;
350
+ }
351
+ .pill {
352
+ display: inline-flex;
353
+ align-items: center;
354
+ gap: 4px;
355
+ width: 100%;
356
+ overflow: hidden;
357
+ }
358
+ .pill__time {
359
+ font-variant-numeric: tabular-nums;
360
+ font-weight: 700;
361
+ color: var(--coar-text-base, #1a1c1f);
362
+ white-space: nowrap;
363
+ }
364
+ .pill__title {
365
+ flex: 1 1 auto;
366
+ overflow: hidden;
367
+ text-overflow: ellipsis;
368
+ white-space: nowrap;
369
+ color: var(--coar-text-base, #1a1c1f);
370
+ }
371
+ .bar {
372
+ display: inline-flex;
373
+ align-items: center;
374
+ justify-content: space-between;
375
+ width: 100%;
376
+ gap: 6px;
377
+ }
378
+ .bar__title {
379
+ flex: 1 1 auto;
380
+ overflow: hidden;
381
+ text-overflow: ellipsis;
382
+ white-space: nowrap;
383
+ font-weight: 600;
384
+ color: var(--coar-text-base, #1a1c1f);
385
+ }
386
+ .bar__chip {
387
+ flex: 0 0 auto;
388
+ font-size: 10px;
389
+ font-weight: 700;
390
+ color: var(--coar-text-base, #1a1c1f);
391
+ background: rgba(0, 0, 0, 0.12);
392
+ padding: 0 6px;
393
+ border-radius: 999px;
394
+ }
395
+ </style>
396
+ ```
397
+
398
+ ## Inside `<CoarCalendar>`
399
+
400
+ `<CoarCalendar>` and `<CoarMonthView>` consume the SAME `CalendarBuilder` instance — there's no sub-builder forking. Set month-specific config (e.g. `maxEventsPerCell`) directly on the composer's builder:
401
+
402
+ ```ts
403
+ const { builder } = useCalendar();
404
+ builder.maxEventsPerCell(5);
405
+ ```
406
+
407
+ When the active view is `month`, the same builder feeds `<CoarContinuousMonthView>` and resolves `monthDensity`. Selecting List switches the serialized view to `monthList` and mounts `<CoarMonthListView>`. View-specific settings simply have no effect outside their view.
408
+
409
+ ## `useMonthView<TMeta>()`
410
+
411
+ ```ts
412
+ function useMonthView<TMeta>(): {
413
+ builder: CalendarBuilder<TMeta>;
414
+ api: CalendarApi<TMeta>;
415
+ };
416
+ ```
417
+
418
+ Returns a fresh standalone builder + its imperative api. The builder type is the same `CalendarBuilder` used by `<CoarCalendar>` — `useMonthView()` is just a thin shorthand that pre-sets `view: 'month'`.
419
+
420
+ ## Builder setters
421
+
422
+ Full reference: see [the composer's API reference](./coar-calendar.md#api-reference). Highlights that matter for the month view:
423
+
424
+ | Setter | Argument | Default | Notes |
425
+ |---|---|---|---|
426
+ | `firstDayOfWeek(d)` | `0..6 \| undefined` | locale-aware | `0` = Sunday, `1` = Monday, … |
427
+ | `monthDensity(d)` | `'compact' \| 'stacked' \| 'details'` | `'details'` | Presentation used by continuous Month. |
428
+ | `shadeWeekends(b)` | `MaybeRefOrGetter<boolean>` | `true` | Shades Saturday / Sunday cells and weekday headers. Set `false` for an unshaded appearance. |
429
+ | `maxEventsPerCell(n)` | `MaybeRefOrGetter<number>` | `3` | Pill cap hint. The library never truncates — pills always reach the DOM — but the collapsed-cell height reserves space for ~`n` pills before the cell starts to scroll. |
430
+ | `eventRenderer(r)` | `EventRenderer<TMeta>` | — | Universal renderer. Branch on `ctx.layout?.kind === 'monthPill' \| 'monthBar'` for variant-specific rendering — see the example above. |
431
+ | `dayHeaderRenderer(r)` | `DayHeaderRenderer` | — | Weekday-strip header (Mon / Tue / ...). |
432
+
433
+ ## Per-cell expansion
434
+
435
+ Each cell has a kebab trigger (top-right of the day-number row, hover-reveal on desktop, always visible on touch). Clicking it opens a context menu with **Show more events** / **Show fewer events**, which expands or collapses the entire **row** (single-row mode — opening one collapses any other previously-expanded row). Right-click / long-press on the cell body opens the same menu at the pointer.
436
+
437
+ The collapsed cell uses a height that fits ~`maxEventsPerCell` pills + the multi-day-bar lane area; expanded rows grow to a fixed maximum so all overflowing pills are reachable via scroll.
438
+
439
+ ## Drag and drop
440
+
441
+ - **Pills** — drag a single-day pill to another cell to shift its date. The library reflows the source cell as if the event were already gone, and renders a dashed-outline ghost pill at the target.
442
+ - **Bars** — drag a multi-day bar's body to shift the whole bar; drag the **left** or **right** edge handle to resize one side. Resize handles only appear on non-clipped edges (no point resizing from off-month).
443
+ - **Keyboard** — Tab to focus an event, Arrow keys to move ±1 day (Up / Down jump a full week-row). Shift + Arrow on an all-day event grows / shrinks the end side.
444
+ - **`canDrop`** — the universal drop validator on the builder; returning `false` paints a red dashed "invalid" ghost and silently swallows the drop on release.
445
+
446
+ ## Imperative API
447
+
448
+ ```ts
449
+ interface CalendarApi<TMeta> {
450
+ goTo(iso: string): void;
451
+ goToToday(): void;
452
+ next(): void; // ±1 month
453
+ prev(): void;
454
+ getVisibleRange(): ViewWindow | null;
455
+ getVisibleEvents(): CalendarEvent<TMeta>[];
456
+ refresh(): void;
457
+ refreshRange(start: string, end: string): void;
458
+ readonly loading: Readonly<Ref<boolean>>;
459
+ readonly visibleRange: Readonly<Ref<ViewWindow | null>>;
460
+ readonly gridReady: Readonly<Ref<boolean>>;
461
+ }
462
+ ```
463
+
464
+ `scrollToTime` / `scrollToDate` are not month operations. `api.goTo(...)`, `next()`, and `prev()` align the continuous surface to the requested month; a standalone `<CoarContinuousMonthView>` additionally exposes `scrollToMonth(...)` on its component ref.
465
+
466
+ ## Month component props + slots
467
+
468
+ | Prop | Type | Description |
469
+ |---|---|---|
470
+ | `builder` | `CalendarBuilder` | **Required.** From `useMonthView()` (or share the one from `useCalendar()`). |
471
+
472
+ | Slot | Scope | Purpose |
473
+ |---|---|---|
474
+ | `pill` | `{ event, pill }` | Single-day pill renderer. `pill` is the `MonthCellPill` (event + visual order in the cell). |
475
+ | `multiDayBar` | `{ event, bar }` | Multi-day bar renderer. `bar` is the `MonthMultiDayBar` (lane / startCol / endCol / clipping flags). |
@@ -0,0 +1,186 @@
1
+ <!-- Generated from apps/docs/components/calendar/performance.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Performance baseline (Preview)
4
+
5
+ The calendar's hot paths — virtualization, drag-and-drop with auto-scroll,
6
+ 2D scrolling — were instrumented via the [Long Animation Frame API][loaf]
7
+ during development. This page records the numbers that justified the
8
+ architectural choices, plus the targets you should expect on real
9
+ hardware.
10
+
11
+ [loaf]: https://developer.mozilla.org/docs/Web/API/Performance_API/Long_animation_frame_timing
12
+
13
+ ## How the numbers are measured
14
+
15
+ Two perf signals are reported throughout this page. They answer different
16
+ questions and have very different reliabilities:
17
+
18
+ | Metric | What it measures | Authoritative? |
19
+ |---|---|---|
20
+ | **Long frames (5 s window)** | Count of actual ≥ 50 ms frames over the last 5 s, from the browser's own pipeline (Long Animation Frame API). | **Yes.** Cannot be fooled by rAF scheduling. |
21
+ | **Worst frame (5 s window)** | Duration in ms of the longest frame in the last 5 s. | **Yes.** Same source. |
22
+ | **rAF FPS mean / min** | How often the JS `requestAnimationFrame` callback fires. | **No.** Chromium can defer rAF callbacks one or two vsync ticks during wheel-scroll input dispatch without producing visual jank — the rAF counter dips to 30 even on a smooth page. |
23
+
24
+ **Rule of thumb:** if Long-frames count is `0` and Worst-frame is under
25
+ 50 ms, the page is genuinely smooth — regardless of what rAF FPS shows.
26
+
27
+ The Long Animation Frame API was specified by the W3C precisely to
28
+ expose the distinction; it is the source of truth in the numbers below.
29
+
30
+ ## Targets
31
+
32
+ | Surface | Tier A (laptop / desktop) | Tier B (CI / cheap hardware) |
33
+ |---|---|---|
34
+ | **1D virtualization (≤ 10 000 fixed-size items)** | 0 long frames during wheel-scroll; worst frame < 16 ms | ≤ 1 long frame; worst frame < 30 ms |
35
+ | **1D virtualization (variable-size, 10 000 items)** | 0 long frames during wheel + measurement flushes | ≤ 2 long frames; worst frame < 30 ms |
36
+ | **2D virtualization (1 000 × 1 000 cells)** | 0 long frames during diagonal wheel-scroll | ≤ 2 long frames; worst frame < 30 ms |
37
+ | **Drag with auto-scroll (200-item list)** | 0 long frames over a 2.6 s scripted drag-and-hold session | ≤ 3 long frames; worst frame < 150 ms |
38
+ | **Memory (idle, 10 000 items)** | < 60 MB | < 80 MB |
39
+
40
+ ## Validation results
41
+
42
+ Measured on a Snapdragon X Elite X1E-78-100 dev box (Windows 11 ARM64,
43
+ Chrome ARM64), production build, against the components shipped in
44
+ `@cocoar/vue-calendar`.
45
+
46
+ ### 1D virtualization, 10 000 fixed-size items
47
+
48
+ | Scenario | Long frames | Worst frame |
49
+ |---|---|---|
50
+ | Idle | 0 | 0 ms |
51
+ | Wheel-scroll, mixed direction, ~ 30 s session | **0** | 0 ms |
52
+ | Bottom-jump from 10 000 items | **0** | 0 ms |
53
+
54
+ DOM cost: ≤ ~ 30 items mounted at any time. Bottom-jump still mounts
55
+ ~ 14 items, never the full 10 000. Add / remove 1 000 items at runtime
56
+ updates the surface in place without remounting visible slots.
57
+
58
+ ### 1D virtualization, 10 000 variable-size items
59
+
60
+ The variable-size path adds the `MeasurementCache` (Fenwick-tree-backed
61
+ prefix sums + interval search) and anchor-based scroll restoration.
62
+ Items mount with the `estimatedItemSize`, then `ResizeObserver` flushes
63
+ real heights inside one rAF.
64
+
65
+ | Scenario | Long frames | Worst frame |
66
+ |---|---|---|
67
+ | Wheel-scroll + first-paint measurement flushes | **0** | 0 ms |
68
+ | Manual size-toggle above the viewport (anchor restoration) | **0** | 0 ms |
69
+
70
+ ### 2D virtualization, 1 000 × 1 000 cells
71
+
72
+ | Scenario | Long frames | Worst frame | Cells in DOM |
73
+ |---|---|---|---|
74
+ | 30 ticks at 50 ms cadence (≈ real wheel) | **0** | 0 ms | 378 |
75
+ | 50 ticks at 30 ms cadence (synthetic burst) | 1 | 50 ms | 357 |
76
+ | 1D-variable regression under the same regime | **0** | 0 ms | 10 |
77
+
78
+ The single long frame in the synthetic burst (~ twice the cadence of
79
+ natural wheel input) lands exactly on the 50 ms LoAF threshold. Real-
80
+ user wheel-scroll does not produce it. Adding 2D had no effect on the
81
+ 1D path.
82
+
83
+ ### Drag with auto-scroll (200-item list)
84
+
85
+ A scripted session via Chrome DevTools:
86
+
87
+ 1. `pointerdown` on row 5
88
+ 2. 30-step diagonal drag down to the bottom hot zone (~ 600 ms)
89
+ 3. **Hold at bottom hot zone for 800 ms** — auto-scroll down fires each frame
90
+ 4. 30-step reverse drag back up to the top hot zone (~ 600 ms)
91
+ 5. **Hold at top hot zone for 600 ms** — auto-scroll up fires
92
+ 6. `pointerup`
93
+
94
+ Total session ~ 2.6 s. `surface.scrollTop` ended at 231 px from a start
95
+ of 0 — auto-scroll is real, not simulated.
96
+
97
+ | Metric | Value |
98
+ |---|---|
99
+ | Long frames over the session | **0** |
100
+ | Worst frame | **0 ms** (under the 50 ms LoAF threshold) |
101
+
102
+ ### Math kernel microbenchmarks
103
+
104
+ The pure-function primitives in `packages/calendar/src/core/`:
105
+
106
+ | Operation | mean | hz |
107
+ |---|---|---|
108
+ | `getVisibleRange1D` (variable, midpoint, overscan = 3) | 314 ns | 3.19 M / sec |
109
+ | `MeasurementCache.prefixSum` (10 k items, midpoint) | 75 ns | 13.26 M / sec |
110
+ | `MeasurementCache.indexAtOffset` (10 k items, midpoint) | 168 ns | 5.95 M / sec |
111
+ | `computeAnchorAdjustment` (10 k items, midpoint) | 89 ns | 11.24 M / sec |
112
+
113
+ The math kernel uses ≈ 0.002 % of a 60 fps frame budget at 10 k items.
114
+ The DOM composition pipeline is the bound, not the math.
115
+
116
+ ## Architectural decisions the numbers locked in
117
+
118
+ - **Fenwick-tree variable-size cache.** `prefixSum` / `indexAtOffset` /
119
+ `set` all in O(log n). Linear scans were measurably worse at 10 k+
120
+ items in early prototypes; the tree paid for itself.
121
+ - **Transform-only positioning.** Items render at `transform: translate3d(0, y, 0)`
122
+ rather than re-layouting on scroll. Composite-only updates per frame.
123
+ - **Keyed v-for over the visible range, no recycling pool.** Vue's
124
+ diff turned out faster than a stable pool for typical slot content
125
+ in benchmarks. Heavy custom renderers (charts, video) might benefit;
126
+ the surface accepts a custom recycling pool when needed.
127
+ - **rAF-throttled drag.** Pointermove batches into a single rAF tick;
128
+ multiple moves between frames coalesce. Auto-scroll velocity is
129
+ computed in a pure function (`computeAutoScrollVelocity`) and
130
+ applied to `scrollTop` / `scrollLeft` per tick.
131
+
132
+ ## Known accessibility gap — drop announcements
133
+
134
+ The calendar ships full keyboard-driven drag (Tab to focus, arrow keys
135
+ to move, Shift + arrow to resize, Enter to confirm, Escape to cancel),
136
+ but **non-sighted users currently get no audible confirmation that a
137
+ drop landed.** Sighted users see the event jump to the new slot;
138
+ screen-reader users hear nothing.
139
+
140
+ This is a known gap, not by-design. An earlier internal implementation
141
+ had a polite-live-region announcement that read e.g.
142
+ `"Daily Standup moved to Monday, 15. Juni 2026, 09:00"` after each
143
+ drop. The plumbing was lost during a reactivity refactor (the shell-
144
+ level handler that prepended the announcement before forwarding to
145
+ `state.onEventDrop` was bypassed when sub-views started reading
146
+ `state.onEventDrop` directly per the C7 read-on-every-call contract),
147
+ and shipping it cleanly requires plumbing the announcer into the
148
+ single drop pipeline (`useCalendarDnd`) so every drop path —
149
+ mouse, touch, keyboard — surfaces it once.
150
+
151
+ Estimated effort: 1 – 2 h. Tracked as a post-launch a11y task.
152
+
153
+ Until then, consumers who need this can wire their own announcement
154
+ inside `onEventDrop`:
155
+
156
+ ```ts
157
+ import { useA11yAnnouncer } from '@cocoar/vue-calendar';
158
+
159
+ const announcer = useA11yAnnouncer();
160
+ builder.onEventDrop(({ event, next }) => {
161
+ const title = (event.meta as { title?: string })?.title ?? event.id;
162
+ announcer.announce(`${title} moved to ${next.start.toString()}`);
163
+ // …persist the change
164
+ });
165
+ ```
166
+
167
+ ## Reproduce locally
168
+
169
+ A manual smoke test surface ships with the playground:
170
+
171
+ ```
172
+ pnpm --filter @cocoar/playground dev
173
+ # open http://localhost:5188/calendar-perf-bench
174
+ ```
175
+
176
+ Pick `100 / 500 / 1 000 / 2 500` events from the segmented control,
177
+ drag, switch view, wheel-scroll. Generation-ms, in-data count, and
178
+ visible-window count are surfaced in the toolbar so you can sanity-check
179
+ the workload at a glance.
180
+
181
+ For automated perf gating (run the same scripted scenarios against a
182
+ production build under Playwright + Chromium DevTools), wire up your
183
+ own LoAF observer using the same approach documented in the
184
+ [Long Animation Frame API spec][loaf]. The patterns from this page
185
+ (scripted drag-and-hold, mixed-direction wheel, bottom-jump) are a good
186
+ starting fixture set.