@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,212 @@
1
+ <!-- Generated from apps/docs/components/virtual-list.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Virtual List
4
+
5
+ `useVirtualList` is a framework-agnostic composable that returns the slice of rows currently inside the viewport (plus an overscan buffer). Use it to render very large lists without putting thousands of DOM nodes in the tree. It's the primitive behind [`CoarListbox`'s virtual mode](./listbox.md#virtual-scrolling), but is independently exported and usable in any Vue component that scrolls.
6
+
7
+ ```ts
8
+ import { useVirtualList } from '@cocoar/vue-ui';
9
+ ```
10
+
11
+ ## Standalone example
12
+
13
+ The demo below is a plain `<div>` scroller — **no listbox involved**. 50,000 synthetic log lines are described by data, but only ~20 rows are ever in the DOM:
14
+
15
+ **Demo — `virtual-list/demos/StandaloneVirtualLog.vue`**
16
+
17
+ ```vue
18
+ <template>
19
+ <div>
20
+ <div class="toolbar">
21
+ <button type="button" @click="scrollToMiddle">Scroll to 25,000</button>
22
+ <button type="button" @click="scrollToEnd">Scroll to end</button>
23
+ <span class="muted">Rendered: {{ virtualRows.length }} / {{ logs.length.toLocaleString() }} rows</span>
24
+ </div>
25
+ <div ref="scrollRef" class="log">
26
+ <!-- Spacer provides the full virtual height so the browser's native scrollbar is accurate. -->
27
+ <div class="spacer" :style="{ height: totalSize + 'px' }">
28
+ <div
29
+ v-for="row in virtualRows"
30
+ :key="row.index"
31
+ class="line"
32
+ :class="`line--${logs[row.index].level}`"
33
+ :style="{ transform: `translateY(${row.start}px)`, height: row.size + 'px' }"
34
+ >
35
+ <span class="idx">{{ String(row.index + 1).padStart(5, '0') }}</span>
36
+ <span class="lvl">{{ logs[row.index].level.toUpperCase() }}</span>
37
+ <span class="msg">{{ logs[row.index].message }}</span>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ </div>
42
+ </template>
43
+
44
+ <script setup lang="ts">
45
+ import { useTemplateRef } from 'vue';
46
+ import { useVirtualList } from '@cocoar/vue-ui';
47
+
48
+ interface LogLine { level: 'info' | 'warn' | 'error'; message: string }
49
+
50
+ const LEVELS: LogLine['level'][] = ['info', 'info', 'info', 'info', 'warn', 'error'];
51
+ const MSG_POOL = [
52
+ 'Served GET /api/principals in 14ms',
53
+ 'Cache miss on token-introspect; re-fetching',
54
+ 'Scheduled cleanup tick — no candidates',
55
+ 'POST /teams/42/members (batch 12)',
56
+ 'Upstream 502 — retry 2/3 queued',
57
+ 'Rate limit 4/sec threshold reached',
58
+ 'Invalidated cache segment users:42',
59
+ 'WS heartbeat latency 83ms',
60
+ 'Released connection back to pool (id=17)',
61
+ 'User alice.mueller logged in from 10.0.3.22',
62
+ ];
63
+
64
+ const logs: LogLine[] = Array.from({ length: 50_000 }, (_, i) => ({
65
+ level: LEVELS[i % LEVELS.length],
66
+ message: `${MSG_POOL[i % MSG_POOL.length]} — seq ${i}`,
67
+ }));
68
+
69
+ const scrollRef = useTemplateRef<HTMLElement>('scrollRef');
70
+
71
+ const { virtualRows, totalSize, scrollToIndex } = useVirtualList({
72
+ count: logs.length,
73
+ itemSize: 22,
74
+ overscan: 8,
75
+ scrollElement: scrollRef,
76
+ });
77
+
78
+ function scrollToMiddle() { scrollToIndex(25000, 'start'); }
79
+ function scrollToEnd() { scrollToIndex(logs.length - 1, 'end'); }
80
+ </script>
81
+
82
+ <style scoped>
83
+ .toolbar {
84
+ display: flex; align-items: center; gap: 10px;
85
+ margin-bottom: 8px; font-size: 12px;
86
+ }
87
+ .toolbar button {
88
+ border: 1px solid #d1d5db; background: white;
89
+ padding: 4px 10px; border-radius: 3px; cursor: pointer;
90
+ font-size: 12px;
91
+ }
92
+ .toolbar button:hover { background: #f3f4f6; }
93
+ .muted { color: #64748b; margin-left: auto; }
94
+ .log {
95
+ height: 300px;
96
+ overflow-y: auto;
97
+ border: 1px solid #d1d5db;
98
+ border-radius: 4px;
99
+ background: #0f172a;
100
+ color: #cbd5e1;
101
+ font-family: 'Cascadia Code', Consolas, Monaco, monospace;
102
+ font-size: 12px;
103
+ line-height: 22px;
104
+ }
105
+ .spacer { position: relative; }
106
+ .line {
107
+ position: absolute;
108
+ left: 0; right: 0;
109
+ display: flex; gap: 10px; padding: 0 10px;
110
+ white-space: nowrap;
111
+ overflow: hidden;
112
+ }
113
+ .idx { color: #475569; flex-shrink: 0; }
114
+ .lvl { flex-shrink: 0; font-weight: 600; width: 48px; }
115
+ .line--info .lvl { color: #60a5fa; }
116
+ .line--warn .lvl { color: #fbbf24; }
117
+ .line--error .lvl { color: #f87171; }
118
+ .msg { overflow: hidden; text-overflow: ellipsis; }
119
+ </style>
120
+ ```
121
+
122
+ ## When to reach for it
123
+
124
+ - Pre-loaded catalogs of a few thousand+ items that would otherwise jank the browser
125
+ - Chat history, timelines, command logs
126
+ - Large custom tables you build yourself (not for `CoarDataGrid`, which handles this internally)
127
+ - Any component with a scroll container where DOM-node count is the bottleneck
128
+
129
+ If your data is backend-paginated and can be filtered server-side, virtual scrolling is rarely necessary — the DOM stays small on its own.
130
+
131
+ ## Usage
132
+
133
+ ```ts
134
+ import { useTemplateRef } from 'vue';
135
+ import { useVirtualList } from '@cocoar/vue-ui';
136
+
137
+ const scrollRef = useTemplateRef<HTMLElement>('scrollRef');
138
+
139
+ const { virtualRows, totalSize, scrollToIndex } = useVirtualList({
140
+ count: () => items.value.length,
141
+ itemSize: 32,
142
+ overscan: 5,
143
+ scrollElement: scrollRef,
144
+ });
145
+ ```
146
+
147
+ Template skeleton — a spacer establishes the full scroll height and each visible row is absolutely positioned at its offset:
148
+
149
+ ```vue
150
+ <div ref="scrollRef" style="overflow: auto; height: 400px;">
151
+ <div :style="{ height: totalSize + 'px', position: 'relative' }">
152
+ <div
153
+ v-for="row in virtualRows"
154
+ :key="row.index"
155
+ :style="{
156
+ position: 'absolute', left: 0, right: 0,
157
+ transform: `translateY(${row.start}px)`,
158
+ height: row.size + 'px',
159
+ }"
160
+ >
161
+ {{ items[row.index] }}
162
+ </div>
163
+ </div>
164
+ </div>
165
+ ```
166
+
167
+ ## Variable item heights
168
+
169
+ `itemSize` accepts either a fixed number or a per-index callback. Groups, headings, or mixed content get different heights while the cumulative-offset math stays O(log n) per scroll event:
170
+
171
+ ```ts
172
+ useVirtualList({
173
+ count: () => entries.value.length,
174
+ itemSize: (index) => entries.value[index].isHeading ? 28 : 44,
175
+ scrollElement: scrollRef,
176
+ });
177
+ ```
178
+
179
+ ## API
180
+
181
+ ### `UseVirtualListOptions`
182
+
183
+ | Option | Type | Default | Description |
184
+ |---|---|---|---|
185
+ | `count` | `MaybeRefOrGetter<number>` | — | Total item count. Reactive — changes trigger a recomputation of the offset table. |
186
+ | `itemSize` | `MaybeRefOrGetter<number \| (index) => number>` | — | Fixed pixel height, or a per-index function for variable heights. |
187
+ | `overscan` | `MaybeRefOrGetter<number>` | `5` | Extra rows rendered above/below the viewport as a scroll buffer. |
188
+ | `scrollElement` | `Ref<HTMLElement \| null>` | — | The scrolling container. Attach via `useTemplateRef`. |
189
+
190
+ ### Return value
191
+
192
+ | Field | Type | Description |
193
+ |---|---|---|
194
+ | `virtualRows` | `ComputedRef<VirtualRow[]>` | The rows currently in the viewport + overscan window. |
195
+ | `totalSize` | `ComputedRef<number>` | Sum of all item heights — bind this to your spacer's `height`. |
196
+ | `scrollToIndex` | `(index, align?) => void` | Programmatically scroll an index into view. `align`: `'auto'` (default), `'start'`, `'center'`, `'end'`. |
197
+ | `offsetFor` | `(index) => number` | Debug/test helper — returns the cumulative pixel offset for an index. |
198
+
199
+ ### `VirtualRow`
200
+
201
+ | Field | Type | Description |
202
+ |---|---|---|
203
+ | `index` | `number` | Position in the underlying list (0-based). |
204
+ | `start` | `number` | Pixel offset from the top of the spacer. Use `translateY(start)`. |
205
+ | `size` | `number` | Row height in pixels. |
206
+
207
+ ## Behavior notes
208
+
209
+ - **Fixed viewport:** the composable tracks the container's `clientHeight` on every scroll event and — when available — via a `ResizeObserver`. In environments without `ResizeObserver`, it falls back to the `window.resize` event.
210
+ - **SSR-safe:** the `ResizeObserver` and DOM access are all guarded; the composable returns empty rows until the scroll element is mounted.
211
+ - **Binary-search offset table:** `count` and `itemSize` are reactive. Changing them rebuilds the cumulative offset array (`O(n)` once); scroll events then binary-search (`O(log n)`) for the visible window.
212
+ - **Dynamic measurement** (rendering items and measuring their actual heights) is *not* supported — pick an `itemSize` or function that matches your row heights. For the common "some rows are 32px, some are 48px" case, per-index function is enough.
@@ -0,0 +1,251 @@
1
+ <!-- Generated from apps/docs/components/wizard.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Wizard (Preview)
4
+
5
+ A multi-step flow shell — built to live **inside a modal** (it renders no modal of its own). Three things make it a wizard rather than a plain stepper:
6
+
7
+ - **Animated body resize.** Only the active step is mounted, and the body smoothly animates its height between steps — so the surrounding modal grows / shrinks to fit each page.
8
+ - **A scrollable indicator that follows you.** The step indicator can be far wider (or taller) than the modal; it scrolls and auto-centers the active step on every move.
9
+ - **Edge-placeable indicator.** `indicatorPosition` puts the progress strip on any of the four edges.
10
+
11
+ ```ts
12
+ import { CoarWizard, type CoarWizardStep } from '@cocoar/vue-ui';
13
+ ```
14
+
15
+ > **Warning: Preview**
16
+ >
17
+ > `CoarWizard` is new and on the `0.0.x` line. The API (props, slots, events) may still change before it stabilises — pin a version if you depend on it.
18
+
19
+ ## Basic Usage
20
+
21
+ Pass an ordered `steps` array — each step's `id` is the name of its content slot, and only the active step is rendered. Bind the active step with `v-model:step`. Try the **Top / Right / Bottom / Left** toggle, and watch the modal resize as you move between the short and tall steps. Step 1 gates **Next** via `canAdvance` until a name is entered.
22
+
23
+ **Demo — `wizard/demos/WizardBasic.vue`**
24
+
25
+ ```vue
26
+ <template>
27
+ <div class="wiz-demo">
28
+ <div class="wiz-demo__controls">
29
+ <span class="wiz-demo__label">Indicator:</span>
30
+ <CoarSegmentedControl v-model="pos" :options="positions" size="xs" />
31
+ </div>
32
+
33
+ <!-- A bounded frame standing in for a routed modal. The wizard resizes it
34
+ per step (animated); the indicator scrolls inside its own width. -->
35
+ <div class="wiz-modal">
36
+ <div class="wiz-modal__header">Create OAuth API</div>
37
+ <div class="wiz-modal__body">
38
+ <CoarWizard
39
+ :steps="steps"
40
+ v-model:step="step"
41
+ :indicator-position="pos"
42
+ finish-label="Create"
43
+ @finish="created = true"
44
+ >
45
+ <template #basics>
46
+ <div class="wiz-page">
47
+ <p class="wiz-page__hint">Name your API client. Next is blocked until a name is entered.</p>
48
+ <CoarTextInput v-model="form.name" placeholder="My API client" />
49
+ <CoarTextInput v-model="form.desc" placeholder="Description (optional)" />
50
+ </div>
51
+ </template>
52
+
53
+ <template #type>
54
+ <div class="wiz-page">
55
+ <p class="wiz-page__hint">What kind of client is this?</p>
56
+ <label v-for="t in types" :key="t.id" class="wiz-radio">
57
+ <input type="radio" :value="t.id" v-model="form.type" />
58
+ <span><strong>{{ t.label }}</strong><br /><small>{{ t.desc }}</small></span>
59
+ </label>
60
+ </div>
61
+ </template>
62
+
63
+ <template #redirects>
64
+ <div class="wiz-page">
65
+ <p class="wiz-page__hint">Allowed redirect URIs (this step is intentionally tall).</p>
66
+ <CoarTextInput
67
+ v-for="(_, i) in form.redirects"
68
+ :key="i"
69
+ v-model="form.redirects[i]"
70
+ placeholder="https://app.example.com/callback"
71
+ />
72
+ <button class="wiz-add" type="button" @click="form.redirects.push('')">+ Add URI</button>
73
+ </div>
74
+ </template>
75
+
76
+ <template #scopes>
77
+ <div class="wiz-page">
78
+ <p class="wiz-page__hint">Pick the scopes this client may request.</p>
79
+ <label v-for="s in scopeList" :key="s" class="wiz-check">
80
+ <input type="checkbox" :value="s" v-model="form.scopes" /> {{ s }}
81
+ </label>
82
+ </div>
83
+ </template>
84
+
85
+ <template #review>
86
+ <div class="wiz-page">
87
+ <p class="wiz-page__hint">Review — a short step again, so the modal shrinks back.</p>
88
+ <ul class="wiz-review">
89
+ <li><b>Name:</b> {{ form.name || '—' }}</li>
90
+ <li><b>Type:</b> {{ form.type }}</li>
91
+ <li><b>Redirects:</b> {{ form.redirects.filter(Boolean).length }}</li>
92
+ <li><b>Scopes:</b> {{ form.scopes.length }}</li>
93
+ </ul>
94
+ <p v-if="created" class="wiz-done">✓ Created!</p>
95
+ </div>
96
+ </template>
97
+ </CoarWizard>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ </template>
102
+
103
+ <script setup lang="ts">
104
+ import { computed, reactive, ref } from 'vue';
105
+ import {
106
+ CoarWizard,
107
+ CoarSegmentedControl,
108
+ CoarTextInput,
109
+ type CoarWizardStep,
110
+ type WizardIndicatorPosition,
111
+ type CoarSegmentedControlOption,
112
+ } from '@cocoar/vue-ui';
113
+
114
+ const pos = ref<WizardIndicatorPosition>('top');
115
+ const positions: CoarSegmentedControlOption<WizardIndicatorPosition>[] = [
116
+ { value: 'top', label: 'Top' },
117
+ { value: 'right', label: 'Right' },
118
+ { value: 'bottom', label: 'Bottom' },
119
+ { value: 'left', label: 'Left' },
120
+ ];
121
+
122
+ const step = ref('basics');
123
+ const created = ref(false);
124
+
125
+ const form = reactive({
126
+ name: '',
127
+ desc: '',
128
+ type: 'spa',
129
+ redirects: ['', '', ''],
130
+ scopes: [] as string[],
131
+ });
132
+
133
+ const types = [
134
+ { id: 'spa', label: 'Single-page app', desc: 'Public client, PKCE' },
135
+ { id: 'web', label: 'Web server', desc: 'Confidential client with a secret' },
136
+ { id: 'machine', label: 'Machine-to-machine', desc: 'Client credentials grant' },
137
+ ];
138
+ const scopeList = ['openid', 'profile', 'email', 'offline_access', 'api:read', 'api:write', 'admin'];
139
+
140
+ // `canAdvance` on the first step gates Next until a name is typed.
141
+ const steps = computed<CoarWizardStep[]>(() => [
142
+ { id: 'basics', label: 'Basics', description: 'Name', canAdvance: form.name.trim().length > 0 },
143
+ { id: 'type', label: 'Client type' },
144
+ { id: 'redirects', label: 'Redirect URIs', optional: true },
145
+ { id: 'scopes', label: 'Scopes' },
146
+ { id: 'review', label: 'Review' },
147
+ ]);
148
+ </script>
149
+
150
+ <style scoped>
151
+ .wiz-demo { display: flex; flex-direction: column; gap: 12px; }
152
+ .wiz-demo__controls { display: flex; align-items: center; gap: 8px; }
153
+ .wiz-demo__label { font-size: 13px; color: var(--coar-text-neutral-secondary); }
154
+
155
+ /* Modal stand-in: fixed width, content-driven height (so the resize shows). */
156
+ .wiz-modal {
157
+ width: 460px;
158
+ max-width: 100%;
159
+ border: 1px solid var(--coar-border-neutral);
160
+ border-radius: var(--coar-radius-xl);
161
+ background: var(--coar-background-neutral-primary);
162
+ box-shadow: var(--coar-shadow-m, 0 8px 24px rgba(0, 0, 0, 0.12));
163
+ overflow: hidden;
164
+ }
165
+ .wiz-modal__header {
166
+ padding: 14px 18px;
167
+ font-weight: 600;
168
+ border-bottom: 1px solid var(--coar-border-neutral);
169
+ }
170
+ .wiz-modal__body { padding: 18px; }
171
+
172
+ .wiz-page { display: flex; flex-direction: column; gap: 10px; }
173
+ .wiz-page__hint { margin: 0; font-size: 13px; color: var(--coar-text-neutral-secondary); }
174
+
175
+ .wiz-radio, .wiz-check {
176
+ display: flex; gap: 8px; align-items: flex-start;
177
+ font-size: 14px; cursor: pointer;
178
+ }
179
+ .wiz-check { align-items: center; }
180
+ .wiz-add {
181
+ align-self: flex-start; border: none; background: transparent; cursor: pointer;
182
+ color: var(--coar-text-accent-primary); font: inherit; padding: 2px 0;
183
+ }
184
+ .wiz-review { margin: 0; padding-left: 1.1em; font-size: 14px; line-height: 1.7; }
185
+ .wiz-done { margin: 4px 0 0; color: var(--coar-text-semantic-success-bold, #16a34a); font-weight: 600; }
186
+ </style>
187
+ ```
188
+
189
+ ```vue
190
+ <template>
191
+ <CoarWizard :steps="steps" v-model:step="step" @finish="create">
192
+ <template #basics>…</template>
193
+ <template #scopes>…</template>
194
+ <template #review>…</template>
195
+ </CoarWizard>
196
+ </template>
197
+
198
+ <script setup lang="ts">
199
+ import { ref } from 'vue';
200
+ import { CoarWizard, type CoarWizardStep } from '@cocoar/vue-ui';
201
+
202
+ const step = ref('basics');
203
+ const steps: CoarWizardStep[] = [
204
+ { id: 'basics', label: 'Basics', canAdvance: true },
205
+ { id: 'scopes', label: 'Scopes' },
206
+ { id: 'review', label: 'Review' },
207
+ ];
208
+ </script>
209
+ ```
210
+
211
+ > **Tip: Designed for modals**
212
+ >
213
+ > The wizard renders no modal — drop it into your modal / routed-modal body. Give that modal a **content-driven height** (and a `max-height` for very tall steps) so the per-step resize animation is visible.
214
+
215
+ ## Props
216
+
217
+ | Prop | Type | Default | Description |
218
+ |---|---|---|---|
219
+ | `steps` | `CoarWizardStep[]` | — | Ordered steps. Each `id` names that step's content slot. |
220
+ | `step` (`v-model:step`) | `string` | _first step_ | The active step id. Uncontrolled (defaults to the first step) when not bound. |
221
+ | `indicatorPosition` | `'top' \| 'right' \| 'bottom' \| 'left'` | `'top'` | Which edge the indicator sits on. `top`/`bottom` scroll horizontally; `left`/`right` vertically. |
222
+ | `freeNavigation` | `boolean` | `false` | Allow jumping to any non-disabled step from the indicator (default: only completed steps are clickable). |
223
+ | `hideFooter` | `boolean` | `false` | Hide the built-in Back / Next / Finish footer. |
224
+ | `disableAnimation` | `boolean` | `false` | Skip the height + content transition (also auto-skipped under `prefers-reduced-motion`). |
225
+ | `backLabel` / `nextLabel` / `finishLabel` | `string` | `Back` / `Next` / `Finish` | Built-in button labels. |
226
+
227
+ ### `CoarWizardStep`
228
+
229
+ | Field | Type | Description |
230
+ |---|---|---|
231
+ | `id` | `string` | Stable id + content slot name. |
232
+ | `label` | `string` | Indicator label. |
233
+ | `description` | `string?` | Optional second line under the label. |
234
+ | `optional` | `boolean?` | Marks the step optional in the indicator. |
235
+ | `canAdvance` | `boolean?` | When `false`, the built-in Next is disabled on this step (per-step gate). Defaults to allowed. |
236
+ | `disabled` | `boolean?` | Prevents navigating to this step from the indicator. |
237
+
238
+ ## Slots
239
+
240
+ | Slot | Props | Description |
241
+ |---|---|---|
242
+ | `<step id>` | `{ step, index }` | Content for that step. Only the active step's slot is rendered. |
243
+ | `footer` | `{ next, back, goTo, isFirst, isLast, canAdvance, activeStep, activeIndex }` | Replace the built-in footer with your own controls. |
244
+
245
+ ## Events
246
+
247
+ | Event | Payload | Description |
248
+ |---|---|---|
249
+ | `update:step` | `string` | Active step id changed. |
250
+ | `step-change` | `(id, index)` | Active step changed. |
251
+ | `finish` | — | Next pressed on the last step. |
@@ -0,0 +1,177 @@
1
+ <!-- Generated from apps/docs/components/zoned-date-time-picker.md by apps/docs/scripts/sync-skill.mjs. Do not edit; edit the docs page. -->
2
+
3
+ # Zoned DateTime Picker
4
+
5
+ The full-featured datetime picker for timezone-aware values. It captures a date, time, and IANA timezone as a single `Temporal.ZonedDateTime`, making it easy to derive UTC instants for storage while preserving the user's original intent.
6
+
7
+ ```ts
8
+ import { CoarZonedDateTimePicker } from '@cocoar/vue-ui';
9
+ ```
10
+
11
+ > **Info**
12
+ >
13
+ > **Store intent, derive math.** Persist the user's chosen local time and timezone. You can always recalculate the UTC instant later -- and if DST rules change in the future, the recalculation will still be correct.
14
+
15
+ ## Basic Usage
16
+
17
+ The picker defaults to the user's system timezone. Select a date, adjust the time, and optionally change the timezone. The bound value carries all three pieces of information.
18
+
19
+ **Demo — `zoned-date-time-picker/demos/BasicZonedDateTimePicker.vue`**
20
+
21
+ ```vue
22
+ <template>
23
+ <div style="display: flex; flex-direction: column; gap: 12px; max-width: 360px;">
24
+ <CoarFormField label="Meeting Time">
25
+ <CoarZonedDateTimePicker
26
+ v-model="value"
27
+ placeholder="DD.MM.YYYY HH:mm"
28
+ />
29
+ </CoarFormField>
30
+ <span style="font-size: 13px; color: #64748b;">
31
+ ZonedDateTime: {{ value?.toString() ?? 'none' }}
32
+ </span>
33
+ <span v-if="value" style="font-size: 13px; color: #64748b;">
34
+ UTC Instant: {{ value.toInstant().toString() }}
35
+ </span>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { ref } from 'vue';
41
+ import { CoarZonedDateTimePicker, CoarFormField } from '@cocoar/vue-ui';
42
+ import type { Temporal } from '@js-temporal/polyfill';
43
+
44
+ const value = ref<Temporal.ZonedDateTime | null>(null);
45
+ </script>
46
+ ```
47
+
48
+ ## Working with UTC
49
+
50
+ Converting to a UTC instant for API calls or database storage is a one-liner:
51
+
52
+ ```ts
53
+ // Get UTC instant from ZonedDateTime
54
+ const utcInstant = value.value?.toInstant().toString();
55
+ // → "2024-03-15T14:30:00Z"
56
+
57
+ // Get ISO string with offset
58
+ const isoString = value.value?.toString();
59
+ // → "2024-03-15T15:30:00+01:00[Europe/Berlin]"
60
+ ```
61
+
62
+ ## States
63
+
64
+ All standard form states are supported: `required`, `error`, `disabled`, and `readonly`.
65
+
66
+ **Demo — `zoned-date-time-picker/demos/ZonedDateTimePickerStates.vue`**
67
+
68
+ ```vue
69
+ <template>
70
+ <div style="display: flex; flex-direction: column; gap: 12px; max-width: 360px;">
71
+ <CoarFormField label="Required" required>
72
+ <CoarZonedDateTimePicker placeholder="DD.MM.YYYY HH:mm" :required="true" />
73
+ </CoarFormField>
74
+ <CoarFormField label="With Error" error="Invalid date/time">
75
+ <CoarZonedDateTimePicker placeholder="DD.MM.YYYY HH:mm" />
76
+ </CoarFormField>
77
+ <CoarFormField label="Disabled">
78
+ <CoarZonedDateTimePicker placeholder="DD.MM.YYYY HH:mm" :disabled="true" />
79
+ </CoarFormField>
80
+ <CoarFormField label="Readonly">
81
+ <CoarZonedDateTimePicker placeholder="DD.MM.YYYY HH:mm" :readonly="true" />
82
+ </CoarFormField>
83
+ </div>
84
+ </template>
85
+
86
+ <script setup lang="ts">
87
+ import { CoarZonedDateTimePicker, CoarFormField } from '@cocoar/vue-ui';
88
+ </script>
89
+ ```
90
+
91
+ ## Sizes
92
+
93
+ Four sizes that stay visually aligned with every other Cocoar input component.
94
+
95
+ **Demo — `zoned-date-time-picker/demos/ZonedDateTimePickerSizes.vue`**
96
+
97
+ ```vue
98
+ <template>
99
+ <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px;">
100
+ <CoarFormField label="Extra Small">
101
+ <CoarZonedDateTimePicker size="xs" placeholder="DD.MM.YYYY HH:mm" />
102
+ </CoarFormField>
103
+ <CoarFormField label="Small">
104
+ <CoarZonedDateTimePicker size="s" placeholder="DD.MM.YYYY HH:mm" />
105
+ </CoarFormField>
106
+ <CoarFormField label="Medium">
107
+ <CoarZonedDateTimePicker size="m" placeholder="DD.MM.YYYY HH:mm" />
108
+ </CoarFormField>
109
+ <CoarFormField label="Large">
110
+ <CoarZonedDateTimePicker size="l" placeholder="DD.MM.YYYY HH:mm" />
111
+ </CoarFormField>
112
+ </div>
113
+ </template>
114
+
115
+ <script setup lang="ts">
116
+ import { CoarZonedDateTimePicker, CoarFormField } from '@cocoar/vue-ui';
117
+ </script>
118
+ ```
119
+
120
+ ## Accessibility
121
+
122
+ ### Keyboard Navigation
123
+
124
+ | Key | Action |
125
+ |-----|--------|
126
+ | `Tab` | Move focus between date, time, and timezone inputs |
127
+ | `Enter` | Open calendar / confirm selection |
128
+ | `Escape` | Close calendar dropdown |
129
+ | `Arrow Keys` | Navigate within the calendar |
130
+
131
+ ### Screen Reader Support
132
+
133
+ - Label text announces on focus
134
+ - Date, time, and timezone portions are independently accessible
135
+ - Selected timezone is announced
136
+ - Required and error states announced
137
+
138
+ ## i18n Keys
139
+
140
+ These keys can be translated via [`@cocoar/vue-localization`](../foundations/localization/translations.md).
141
+
142
+ | Key | Default (English) | Used as |
143
+ |-----|-------------------|---------|
144
+ | `coar.ui.zonedDateTimePicker.dialog` | `'Date, time and timezone picker'` | Overlay dialog `aria-label` |
145
+ | `coar.ui.zonedDateTimePicker.clearValue` | `'Clear value'` | Clear button `aria-label` |
146
+ | `coar.ui.zonedDateTimePicker.openPicker` | `'Open date and time picker'` | Calendar button `aria-label` |
147
+ | `coar.ui.zonedDateTimePicker.timezoneIndicator` | `'Timezone: {tz}'` | Timezone indicator `aria-label` |
148
+ | `coar.ui.zonedDateTimePicker.clickToToggle` | `'Click to toggle.'` | Timezone indicator `aria-label` suffix |
149
+ | `coar.ui.zonedDateTimePicker.searchTimezone` | `'Search timezone...'` | Timezone search placeholder |
150
+ | `coar.ui.zonedDateTimePicker.closeTimezoneSearch` | `'Close timezone search'` | Close search button `aria-label` |
151
+ | `coar.ui.zonedDateTimePicker.displayTimezone` | `'Display Timezone'` | Display timezone section label |
152
+ | `coar.ui.zonedDateTimePicker.eventTimezone` | `'Event timezone'` | Footer placeholder text |
153
+ | `coar.ui.zonedDateTimePicker.cancelTimezoneEdit` | `'Cancel timezone edit'` | Cancel button `aria-label` |
154
+ | `coar.ui.zonedDateTimePicker.changeEventTimezone` | `'Change event timezone'` | Settings button `aria-label` |
155
+ | `coar.ui.datePicker.jumpToToday` | `'Jump to today\'s month'` | Scroll-to-today button `aria-label` |
156
+ | `coar.ui.datePicker.previousYear` | `'Previous year'` | Previous year button `aria-label` |
157
+ | `coar.ui.datePicker.nextYear` | `'Next year'` | Next year button `aria-label` |
158
+ | `coar.ui.datePicker.months` | `'Months'` | Month grid `aria-label` |
159
+ | `coar.ui.timePicker.*` | *(see DateTime Picker)* | Time picker spinbutton labels |
160
+
161
+ ## API
162
+
163
+ ### Props
164
+
165
+ | Prop | Type | Default | Description |
166
+ |------|------|---------|-------------|
167
+ | `v-model` | `Temporal.ZonedDateTime \| null` | `null` | Selected zoned datetime |
168
+ | `timezone` | `string` | user's timezone | IANA timezone ID (e.g. `'Europe/Berlin'`) |
169
+ | `placeholder` | `string` | `''` | Placeholder text |
170
+ | `size` | `'xs' \| 's' \| 'm' \| 'l'` | `'m'` | Input size |
171
+ | `disabled` | `boolean` | `false` | Disable the picker |
172
+ | `readonly` | `boolean` | `false` | Make read-only |
173
+ | `required` | `boolean` | `false` | Mark as required |
174
+ | `error` | `boolean` | `false` | Error state (red border + `aria-invalid`). Auto-injected from a wrapping [`CoarFormField`](./form-field.md). |
175
+ | `id` | `string` | `''` | Explicit input id (else taken from `CoarFormField`, else auto). |
176
+
177
+ > **Label, hint, error message and the status icon live on the wrapping [`CoarFormField`](./form-field.md)** — not on the picker. Wrap the picker in a `CoarFormField` for a label, the required asterisk, validation messages and the inline status indicator; the picker auto-adopts the field's id, error state and `aria-describedby` via injection.