@vobs/ui 0.2.0 → 1.0.0

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 (208) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -102
  4. package/src/activity-rail.ts +125 -0
  5. package/src/alert.ts +65 -0
  6. package/src/assets/icons/alert-circle.svg +1 -0
  7. package/src/assets/icons/alert-triangle.svg +1 -0
  8. package/src/assets/icons/alert.d27e24.svg +1 -0
  9. package/src/assets/icons/alert.svg +1 -0
  10. package/src/assets/icons/apple.svg +1 -0
  11. package/src/assets/icons/arrow-down.svg +1 -0
  12. package/src/assets/icons/arrow-expand.svg +1 -0
  13. package/src/assets/icons/arrow-left.svg +1 -0
  14. package/src/assets/icons/arrow-minimize.svg +1 -0
  15. package/src/assets/icons/arrow-right-to-line.svg +1 -0
  16. package/src/assets/icons/arrow-right.svg +1 -0
  17. package/src/assets/icons/arrow-up-right.svg +1 -0
  18. package/src/assets/icons/arrow-up.0c0c0d.svg +1 -0
  19. package/src/assets/icons/arrow-up.svg +1 -0
  20. package/src/assets/icons/at.svg +1 -0
  21. package/src/assets/icons/atom.0c0c0d.svg +1 -0
  22. package/src/assets/icons/atom.svg +1 -0
  23. package/src/assets/icons/bar.svg +1 -0
  24. package/src/assets/icons/bell.svg +1 -0
  25. package/src/assets/icons/box.svg +1 -0
  26. package/src/assets/icons/bug.svg +1 -0
  27. package/src/assets/icons/calendar.svg +1 -0
  28. package/src/assets/icons/check-circle.33c192.svg +1 -0
  29. package/src/assets/icons/check-circle.svg +1 -0
  30. package/src/assets/icons/check.d1d3db.svg +1 -0
  31. package/src/assets/icons/check.svg +1 -0
  32. package/src/assets/icons/chevron-down.svg +1 -0
  33. package/src/assets/icons/chevron-right.svg +1 -0
  34. package/src/assets/icons/chevron-up.svg +1 -0
  35. package/src/assets/icons/circle.svg +1 -0
  36. package/src/assets/icons/clock.svg +1 -0
  37. package/src/assets/icons/cmd.svg +1 -0
  38. package/src/assets/icons/code.svg +1 -0
  39. package/src/assets/icons/columns.svg +1 -0
  40. package/src/assets/icons/copy.svg +1 -0
  41. package/src/assets/icons/cpu.svg +1 -0
  42. package/src/assets/icons/dashboard.0c0c0d.svg +1 -0
  43. package/src/assets/icons/dashboard.svg +1 -0
  44. package/src/assets/icons/dollar.svg +1 -0
  45. package/src/assets/icons/download.svg +1 -0
  46. package/src/assets/icons/edit.svg +1 -0
  47. package/src/assets/icons/extensions.svg +1 -0
  48. package/src/assets/icons/external.svg +1 -0
  49. package/src/assets/icons/eye.svg +1 -0
  50. package/src/assets/icons/file-text.svg +1 -0
  51. package/src/assets/icons/file.svg +1 -0
  52. package/src/assets/icons/files.svg +1 -0
  53. package/src/assets/icons/filter.svg +1 -0
  54. package/src/assets/icons/folder.svg +1 -0
  55. package/src/assets/icons/gear.svg +1 -0
  56. package/src/assets/icons/git.svg +1 -0
  57. package/src/assets/icons/github.svg +1 -0
  58. package/src/assets/icons/globe.svg +1 -0
  59. package/src/assets/icons/grid-2x2.svg +1 -0
  60. package/src/assets/icons/hash.svg +1 -0
  61. package/src/assets/icons/heart.svg +1 -0
  62. package/src/assets/icons/help.svg +1 -0
  63. package/src/assets/icons/home.svg +1 -0
  64. package/src/assets/icons/image.svg +1 -0
  65. package/src/assets/icons/info-circle.svg +1 -0
  66. package/src/assets/icons/info-square.svg +1 -0
  67. package/src/assets/icons/info.387bff.svg +1 -0
  68. package/src/assets/icons/info.svg +1 -0
  69. package/src/assets/icons/key.svg +1 -0
  70. package/src/assets/icons/layers.svg +1 -0
  71. package/src/assets/icons/layout.svg +1 -0
  72. package/src/assets/icons/link.svg +1 -0
  73. package/src/assets/icons/list.svg +1 -0
  74. package/src/assets/icons/lock.svg +1 -0
  75. package/src/assets/icons/log-out.svg +1 -0
  76. package/src/assets/icons/logo.svg +1 -0
  77. package/src/assets/icons/mail.svg +1 -0
  78. package/src/assets/icons/menu.svg +1 -0
  79. package/src/assets/icons/message-circle.svg +1 -0
  80. package/src/assets/icons/message-plus.svg +1 -0
  81. package/src/assets/icons/message-square.svg +1 -0
  82. package/src/assets/icons/mic.svg +1 -0
  83. package/src/assets/icons/minus.svg +1 -0
  84. package/src/assets/icons/moon.svg +1 -0
  85. package/src/assets/icons/more-h.svg +1 -0
  86. package/src/assets/icons/mouse.svg +1 -0
  87. package/src/assets/icons/palette.svg +1 -0
  88. package/src/assets/icons/panel-bottom.svg +1 -0
  89. package/src/assets/icons/pause.svg +1 -0
  90. package/src/assets/icons/play.svg +1 -0
  91. package/src/assets/icons/plug.svg +1 -0
  92. package/src/assets/icons/plus.0c0c0d.svg +1 -0
  93. package/src/assets/icons/plus.svg +1 -0
  94. package/src/assets/icons/refresh.svg +1 -0
  95. package/src/assets/icons/ruler.svg +1 -0
  96. package/src/assets/icons/scroll-text.svg +1 -0
  97. package/src/assets/icons/search-menu.svg +1 -0
  98. package/src/assets/icons/search.svg +1 -0
  99. package/src/assets/icons/send.0c0c0d.svg +1 -0
  100. package/src/assets/icons/send.svg +1 -0
  101. package/src/assets/icons/settings.0c0c0d.svg +1 -0
  102. package/src/assets/icons/settings.svg +1 -0
  103. package/src/assets/icons/shield.svg +1 -0
  104. package/src/assets/icons/sidebar-left.svg +1 -0
  105. package/src/assets/icons/sidebar-right.svg +1 -0
  106. package/src/assets/icons/sliders.svg +1 -0
  107. package/src/assets/icons/sparkles.32f08c.svg +1 -0
  108. package/src/assets/icons/sparkles.svg +1 -0
  109. package/src/assets/icons/square.svg +1 -0
  110. package/src/assets/icons/star.svg +1 -0
  111. package/src/assets/icons/sun.svg +1 -0
  112. package/src/assets/icons/terminal.svg +1 -0
  113. package/src/assets/icons/trash.svg +1 -0
  114. package/src/assets/icons/trending-down.svg +1 -0
  115. package/src/assets/icons/trending-up.svg +1 -0
  116. package/src/assets/icons/type.svg +1 -0
  117. package/src/assets/icons/upload.svg +1 -0
  118. package/src/assets/icons/user-circle.svg +1 -0
  119. package/src/assets/icons/user.svg +1 -0
  120. package/src/assets/icons/users.svg +1 -0
  121. package/src/assets/icons/wrench.svg +1 -0
  122. package/src/assets/icons/x-circle.svg +1 -0
  123. package/src/assets/icons/x.svg +1 -0
  124. package/src/assets/icons/zap.0c0c0d.svg +1 -0
  125. package/src/assets/icons/zap.svg +1 -0
  126. package/src/avatar.ts +64 -0
  127. package/src/button.ts +88 -0
  128. package/src/card.ts +59 -0
  129. package/src/chat-composer.ts +156 -0
  130. package/src/dialog.ts +127 -0
  131. package/src/drawer.ts +131 -0
  132. package/src/editor-tabs.ts +184 -0
  133. package/src/file-tree.ts +214 -0
  134. package/src/forms.ts +270 -0
  135. package/src/icon.ts +117 -0
  136. package/src/icons.js +165 -0
  137. package/src/index.test.ts +213 -0
  138. package/src/index.ts +94 -0
  139. package/src/kbd.ts +72 -0
  140. package/src/menu.ts +119 -0
  141. package/src/nav-list.ts +157 -0
  142. package/src/overlay.test.ts +66 -0
  143. package/src/overlay.ts +205 -0
  144. package/src/page-header.ts +97 -0
  145. package/src/pagination.ts +163 -0
  146. package/src/second-batch.test.ts +205 -0
  147. package/src/stat-card.ts +92 -0
  148. package/src/status-bar.ts +108 -0
  149. package/src/styles/base.css +240 -0
  150. package/src/styles/components.css +1234 -0
  151. package/{dist → src/styles}/styles.css +2 -0
  152. package/src/styles/toast.css +57 -0
  153. package/src/styles/tokens.css +438 -0
  154. package/src/styles/vui.css +47 -0
  155. package/src/table.ts +191 -0
  156. package/src/tabs.ts +100 -0
  157. package/src/tag.ts +33 -0
  158. package/src/third-batch.test.ts +137 -0
  159. package/src/toast.test.ts +55 -0
  160. package/src/toast.ts +79 -0
  161. package/src/types.ts +25 -0
  162. package/src/utils.ts +221 -0
  163. package/src/workbench-titlebar.ts +137 -0
  164. package/dist/app-shell-dom.d.ts +0 -24
  165. package/dist/app-shell-dom.js +0 -573
  166. package/dist/app-shell.d.ts +0 -199
  167. package/dist/app-shell.js +0 -370
  168. package/dist/base.css +0 -52
  169. package/dist/calendar-picker.d.ts +0 -45
  170. package/dist/calendar-picker.js +0 -217
  171. package/dist/calendar.d.ts +0 -95
  172. package/dist/calendar.js +0 -194
  173. package/dist/complex-inputs.d.ts +0 -300
  174. package/dist/complex-inputs.js +0 -862
  175. package/dist/components.css +0 -4568
  176. package/dist/data-display.d.ts +0 -1063
  177. package/dist/data-display.js +0 -2298
  178. package/dist/feedback.d.ts +0 -88
  179. package/dist/feedback.js +0 -281
  180. package/dist/forms.d.ts +0 -378
  181. package/dist/forms.js +0 -1015
  182. package/dist/icons-config.d.ts +0 -25
  183. package/dist/icons-config.js +0 -14
  184. package/dist/icons.d.ts +0 -41
  185. package/dist/icons.js +0 -185
  186. package/dist/index.d.ts +0 -27
  187. package/dist/index.js +0 -34
  188. package/dist/locale/en-US.d.ts +0 -31
  189. package/dist/locale/en-US.js +0 -20
  190. package/dist/locale/zh-CN.d.ts +0 -31
  191. package/dist/locale/zh-CN.js +0 -20
  192. package/dist/navigation.d.ts +0 -609
  193. package/dist/navigation.js +0 -1707
  194. package/dist/overlay.d.ts +0 -304
  195. package/dist/overlay.js +0 -969
  196. package/dist/primitives.d.ts +0 -268
  197. package/dist/primitives.js +0 -764
  198. package/dist/theme-data.d.ts +0 -363
  199. package/dist/theme-data.js +0 -374
  200. package/dist/theme.d.ts +0 -79
  201. package/dist/theme.js +0 -262
  202. package/dist/tokens.css +0 -449
  203. package/dist/tree.d.ts +0 -144
  204. package/dist/tree.js +0 -469
  205. package/dist/virtual-list.d.ts +0 -190
  206. package/dist/virtual-list.js +0 -521
  207. package/dist/workbench.d.ts +0 -136
  208. package/dist/workbench.js +0 -156
@@ -1,521 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { Virtualizer, elementScroll } from '@tanstack/virtual-core';
6
- import { createUiDomPlan } from './primitives.js';
7
- export function createFixedVirtualListPlans(options) {
8
- const state = calculateFixedVirtualListState(options);
9
- return withVirtualListDomPlans(options, state);
10
- }
11
- export function createDynamicVirtualListPlans(options) {
12
- const state = calculateDynamicVirtualListState(options);
13
- return withVirtualListDomPlans(options, state);
14
- }
15
- export function createVirtualListLayoutPlans(options) {
16
- const state = calculateVirtualListLayoutState(options);
17
- return {
18
- ...state,
19
- root: createUiDomPlan('div', 'virtual-list', {
20
- ...(options.className === undefined ? {} : { className: options.className }),
21
- attrs: {
22
- id: options.id,
23
- role: options.layout === 'grid' ? 'grid' : 'list',
24
- 'aria-label': options.ariaLabel,
25
- style: `width: ${options.viewportWidth}px; height: ${options.viewportHeight}px;${styleSuffix(options.attrs?.style)}`,
26
- ...options.attrs,
27
- 'data-layout': state.layout,
28
- 'data-count': options.items.length,
29
- 'data-row-count': state.rowCount,
30
- 'data-column-count': state.columnCount,
31
- 'data-window-start': state.visibleStartIndex,
32
- 'data-window-end': state.visibleEndIndex,
33
- 'data-window-row-start': state.visibleRowStart,
34
- 'data-window-row-end': state.visibleRowEnd,
35
- 'data-window-column-start': state.visibleColumnStart,
36
- 'data-window-column-end': state.visibleColumnEnd,
37
- 'data-scroll-left': state.scrollLeft,
38
- 'data-scroll-top': state.scrollTop,
39
- 'data-total-width': state.totalWidth,
40
- 'data-total-height': state.totalHeight,
41
- 'data-layout-contract': 'true',
42
- },
43
- }),
44
- spacer: createUiDomPlan('div', 'virtual-list-spacer', {
45
- attrs: { style: `width: ${state.totalWidth}px; height: ${state.totalHeight}px;` },
46
- }),
47
- };
48
- }
49
- export function calculateVirtualListLayoutState(options) {
50
- const itemWidth = positiveNumber(options.itemWidth, 'itemWidth');
51
- const itemHeight = positiveNumber(options.itemHeight, 'itemHeight');
52
- const count = options.items.length;
53
- const viewportWidth = Math.max(0, options.viewportWidth);
54
- const viewportHeight = Math.max(0, options.viewportHeight);
55
- const overscan = options.overscan ?? 2;
56
- const columnCount = options.layout === 'horizontal'
57
- ? count
58
- : Math.max(1, Math.trunc(options.columnCount ?? Math.max(1, Math.floor(viewportWidth / itemWidth))));
59
- const rowCount = count === 0 ? 0 : Math.ceil(count / Math.max(1, columnCount));
60
- const totalWidth = options.layout === 'horizontal' ? count * itemWidth : columnCount * itemWidth;
61
- const totalHeight = options.layout === 'horizontal' ? itemHeight : rowCount * itemHeight;
62
- const scrollLeft = clamp(options.scrollLeft ?? 0, 0, Math.max(totalWidth - viewportWidth, 0));
63
- const scrollTop = clamp(options.scrollTop ?? 0, 0, Math.max(totalHeight - viewportHeight, 0));
64
- if (count === 0) {
65
- return createLayoutState(options, rowCount, columnCount, totalWidth, totalHeight, scrollLeft, scrollTop, 0, -1, 0, -1, []);
66
- }
67
- const visibleColumnStart = Math.max(0, Math.floor(scrollLeft / itemWidth));
68
- const visibleColumnEnd = Math.min(Math.max(0, columnCount - 1), Math.ceil((scrollLeft + viewportWidth) / itemWidth));
69
- const visibleRowStart = Math.max(0, Math.floor(scrollTop / itemHeight));
70
- const visibleRowEnd = Math.min(Math.max(0, rowCount - 1), Math.ceil((scrollTop + viewportHeight) / itemHeight));
71
- const ssrCount = options.ssrFallbackCount;
72
- const rowStart = ssrCount === undefined ? Math.max(0, visibleRowStart - overscan) : 0;
73
- const rowEnd = ssrCount === undefined
74
- ? Math.min(rowCount - 1, visibleRowEnd + overscan)
75
- : Math.min(rowCount - 1, Math.ceil(Math.max(0, ssrCount) / columnCount) - 1);
76
- const columnStart = ssrCount === undefined ? Math.max(0, visibleColumnStart - overscan) : 0;
77
- const columnEnd = ssrCount === undefined
78
- ? Math.min(columnCount - 1, visibleColumnEnd + overscan)
79
- : columnCount - 1;
80
- const items = layoutRangeItems(options, itemWidth, itemHeight, columnCount, rowStart, rowEnd, columnStart, columnEnd).slice(0, ssrCount === undefined ? undefined : Math.max(0, ssrCount));
81
- const first = items[0];
82
- const last = items.at(-1);
83
- return createLayoutState(options, rowCount, columnCount, totalWidth, totalHeight, scrollLeft, scrollTop, first?.index ?? 0, last?.index ?? -1, visibleRowStart, visibleRowEnd, items, visibleColumnStart, visibleColumnEnd);
84
- }
85
- export function createVirtualListResizeMeasurementContract(options) {
86
- const measuredKeys = options.items
87
- .map((item, index) => ({
88
- key: keyFor(options, index),
89
- height: options.getMeasuredItemHeight?.(item, index),
90
- }))
91
- .filter((entry) => entry.height !== undefined && Number.isFinite(entry.height) && entry.height > 0)
92
- .map((entry) => entry.key);
93
- const measuringKeys = uniqueKeys(options.measuringIds ?? []);
94
- const plans = createDynamicVirtualListPlans(options.getMeasuredItemHeight === undefined
95
- ? options
96
- : {
97
- ...options,
98
- getItemHeight: options.getMeasuredItemHeight,
99
- });
100
- const estimatedKeys = options.items
101
- .map((_, index) => keyFor(options, index))
102
- .filter((key) => !measuredKeys.includes(key));
103
- return {
104
- plans,
105
- measuredKeys,
106
- measuringKeys,
107
- estimatedKeys,
108
- attrs: {
109
- 'data-resize-measurement-contract': 'true',
110
- 'data-measured-count': measuredKeys.length,
111
- 'data-estimated-count': estimatedKeys.length,
112
- 'data-measuring-count': measuringKeys.length,
113
- 'data-measured-keys': measuredKeys.join(','),
114
- 'data-estimated-keys': estimatedKeys.join(','),
115
- 'data-measuring-keys': measuringKeys.join(','),
116
- 'data-total-size': plans.totalSize,
117
- 'data-viewport-height': options.viewportHeight,
118
- 'data-scroll-offset': plans.scrollOffset,
119
- 'aria-busy': measuringKeys.length > 0 ? 'true' : undefined,
120
- },
121
- };
122
- }
123
- export function createVirtualListAnchorStateContract(options) {
124
- const itemHeight = positiveNumber(options.itemHeight, 'itemHeight');
125
- const previousAnchorIndex = findItemIndexByKey(options.previousItems, options.anchorKey, options.getKey);
126
- const nextAnchorIndex = findItemIndexByKey(options.nextItems, options.anchorKey, options.getKey);
127
- const anchorOffset = Math.max(0, options.anchorOffset ?? 0);
128
- const viewportHeight = Math.max(0, options.viewportHeight ?? 0);
129
- const maxOffset = Math.max(options.nextItems.length * itemHeight - viewportHeight, 0);
130
- const anchorOffsetTarget = nextAnchorIndex < 0 ? 0 : nextAnchorIndex * itemHeight + anchorOffset;
131
- const scrollOffset = options.stickToEnd === true ? maxOffset : clamp(anchorOffsetTarget, 0, maxOffset);
132
- const insertedBeforeCount = previousAnchorIndex < 0 || nextAnchorIndex < 0
133
- ? 0
134
- : Math.max(0, nextAnchorIndex - previousAnchorIndex);
135
- const state = nextAnchorIndex < 0 ? 'missing-anchor' : options.stickToEnd === true ? 'end' : 'preserved';
136
- return {
137
- scrollOffset,
138
- previousAnchorIndex,
139
- nextAnchorIndex,
140
- insertedBeforeCount,
141
- attrs: {
142
- 'data-anchor-contract': 'true',
143
- 'data-anchor-mode': options.mode ?? 'preserve',
144
- 'data-anchor-state': state,
145
- 'data-anchor-key': String(options.anchorKey),
146
- 'data-previous-anchor-index': previousAnchorIndex,
147
- 'data-next-anchor-index': nextAnchorIndex,
148
- 'data-inserted-before-count': insertedBeforeCount,
149
- 'data-scroll-offset': scrollOffset,
150
- 'data-stick-to-end': options.stickToEnd === true ? 'true' : undefined,
151
- },
152
- };
153
- }
154
- export function calculateFixedVirtualListState(options) {
155
- const itemHeight = positiveNumber(options.itemHeight, 'itemHeight');
156
- const count = options.items.length;
157
- if (count === 0)
158
- return createState(options, itemHeight, 0, 0, -1, []);
159
- const viewportHeight = Math.max(0, options.viewportHeight);
160
- const scrollOffset = clamp(options.scrollOffset ?? 0, 0, Math.max(count * itemHeight - viewportHeight, 0));
161
- const visibleStart = Math.floor(scrollOffset / itemHeight);
162
- const visibleEnd = Math.min(count - 1, Math.ceil((scrollOffset + viewportHeight) / itemHeight));
163
- const ssrCount = options.ssrFallbackCount;
164
- const fallbackEnd = ssrCount === undefined ? visibleEnd : Math.min(count - 1, Math.max(0, ssrCount) - 1);
165
- const start = ssrCount === undefined ? Math.max(0, visibleStart - (options.overscan ?? 2)) : 0;
166
- const end = ssrCount === undefined
167
- ? Math.min(count - 1, visibleEnd + (options.overscan ?? 2))
168
- : fallbackEnd;
169
- return createState(options, itemHeight, scrollOffset, visibleStart, visibleEnd, rangeItems(options, start, end));
170
- }
171
- export function calculateDynamicVirtualListState(options) {
172
- const estimatedItemHeight = positiveNumber(options.estimatedItemHeight, 'estimatedItemHeight');
173
- const count = options.items.length;
174
- if (count === 0) {
175
- return createDynamicState(options, 0, 0, 0, -1, 0, []);
176
- }
177
- const metrics = dynamicItemMetrics(options, estimatedItemHeight);
178
- const totalSize = metrics.at(-1)?.end ?? 0;
179
- const viewportHeight = Math.max(0, options.viewportHeight);
180
- const scrollOffset = clamp(options.scrollOffset ?? 0, 0, Math.max(totalSize - viewportHeight, 0));
181
- const visibleStart = firstDynamicIndexAfter(metrics, scrollOffset);
182
- const visibleEnd = lastDynamicIndexBefore(metrics, scrollOffset + viewportHeight);
183
- const ssrCount = options.ssrFallbackCount;
184
- const fallbackEnd = ssrCount === undefined ? visibleEnd : Math.min(count - 1, Math.max(0, ssrCount) - 1);
185
- const start = ssrCount === undefined ? Math.max(0, visibleStart - (options.overscan ?? 2)) : 0;
186
- const end = ssrCount === undefined
187
- ? Math.min(count - 1, visibleEnd + (options.overscan ?? 2))
188
- : fallbackEnd;
189
- return createDynamicState(options, totalSize, scrollOffset, visibleStart, visibleEnd, metrics.filter((metric) => metric.measured).length, dynamicRangeItems(options, metrics, start, end));
190
- }
191
- export function createFixedVirtualListController(options) {
192
- let current = normalizeControllerOptions(options);
193
- let disposed = false;
194
- const virtualizer = new Virtualizer({
195
- count: current.items.length,
196
- getScrollElement: current.getScrollElement,
197
- estimateSize: () => current.itemHeight,
198
- scrollToFn: elementScroll,
199
- observeElementRect: (instance, callback) => {
200
- const element = instance.scrollElement;
201
- if (element === null)
202
- return undefined;
203
- callback({
204
- width: element.clientWidth,
205
- height: element.clientHeight || current.viewportHeight,
206
- });
207
- return undefined;
208
- },
209
- observeElementOffset: (instance, callback) => {
210
- const element = instance.scrollElement;
211
- if (element === null)
212
- return undefined;
213
- let frame = 0;
214
- let first = true;
215
- const readOffset = () => {
216
- if (first && element.scrollTop === 0 && current.initialOffset > 0)
217
- return current.initialOffset;
218
- return element.scrollTop;
219
- };
220
- const emit = (scrolling) => {
221
- if (frame !== 0)
222
- return;
223
- frame = requestFrame(() => {
224
- frame = 0;
225
- first = false;
226
- callback(readOffset(), scrolling);
227
- });
228
- };
229
- const onScroll = () => emit(true);
230
- element.addEventListener('scroll', onScroll, { passive: true });
231
- callback(readOffset(), false);
232
- first = false;
233
- return () => {
234
- if (frame !== 0)
235
- cancelFrame(frame);
236
- element.removeEventListener('scroll', onScroll);
237
- };
238
- },
239
- getItemKey: (index) => keyFor(current, index),
240
- overscan: current.overscan,
241
- initialOffset: current.initialOffset,
242
- initialRect: { width: 0, height: current.viewportHeight },
243
- anchorTo: 'start',
244
- onChange: (instance, sync) => {
245
- current.onChange?.(stateFromVirtualizer(current, instance), sync);
246
- },
247
- });
248
- const cleanup = virtualizer._didMount();
249
- virtualizer._willUpdate();
250
- return {
251
- read() {
252
- return withVirtualListDomPlans(current, stateFromVirtualizer(current, virtualizer));
253
- },
254
- update(next) {
255
- if (disposed)
256
- return;
257
- current = normalizeControllerOptions({ ...current, ...next });
258
- virtualizer.setOptions({
259
- ...virtualizer.options,
260
- count: current.items.length,
261
- overscan: current.overscan,
262
- });
263
- virtualizer._willUpdate();
264
- },
265
- scrollToOffset(offset) {
266
- if (!disposed)
267
- virtualizer.scrollToOffset(offset);
268
- },
269
- scrollToIndex(index) {
270
- if (!disposed)
271
- virtualizer.scrollToIndex(index);
272
- },
273
- takeScrollState() {
274
- const state = stateFromVirtualizer(current, virtualizer);
275
- return {
276
- scrollOffset: state.scrollOffset,
277
- visibleStartIndex: state.visibleStartIndex,
278
- visibleEndIndex: state.visibleEndIndex,
279
- };
280
- },
281
- destroy() {
282
- if (disposed)
283
- return;
284
- disposed = true;
285
- cleanup();
286
- },
287
- };
288
- }
289
- function stateFromVirtualizer(options, virtualizer) {
290
- const items = virtualizer
291
- .getVirtualItems()
292
- .map((virtualItem) => itemFromRange(options, virtualItem.index, virtualItem.start, virtualItem.size));
293
- const first = items[0];
294
- const last = items.at(-1);
295
- return createState(options, options.itemHeight, virtualizer.scrollOffset ?? 0, first?.index ?? 0, last?.index ?? -1, items);
296
- }
297
- function withVirtualListDomPlans(options, state) {
298
- return {
299
- ...state,
300
- root: createUiDomPlan('div', 'virtual-list', {
301
- ...(options.className === undefined ? {} : { className: options.className }),
302
- attrs: {
303
- id: options.id,
304
- role: 'list',
305
- 'aria-label': options.ariaLabel,
306
- style: `height: ${options.viewportHeight}px;${styleSuffix(options.attrs?.style)}`,
307
- ...options.attrs,
308
- 'data-count': options.items.length,
309
- 'data-window-start': state.visibleStartIndex,
310
- 'data-window-end': state.visibleEndIndex,
311
- 'data-dynamic': 'estimatedItemHeight' in options ? 'true' : undefined,
312
- 'data-measured-count': 'measuredCount' in state ? state.measuredCount : undefined,
313
- },
314
- }),
315
- spacer: createUiDomPlan('div', 'virtual-list-spacer', {
316
- attrs: { style: `height: ${state.totalSize}px;` },
317
- }),
318
- };
319
- }
320
- function createState(options, itemHeight, scrollOffset, visibleStartIndex, visibleEndIndex, items) {
321
- return {
322
- totalSize: options.items.length * itemHeight,
323
- scrollOffset,
324
- visibleStartIndex,
325
- visibleEndIndex,
326
- items,
327
- };
328
- }
329
- function rangeItems(options, startIndex, endIndex) {
330
- const result = [];
331
- for (let index = startIndex; index <= endIndex; index++) {
332
- result.push(itemFromRange(options, index, index * options.itemHeight, options.itemHeight));
333
- }
334
- return result;
335
- }
336
- function itemFromRange(options, index, start, size) {
337
- return {
338
- key: keyFor(options, index),
339
- index,
340
- item: options.items[index],
341
- start,
342
- size,
343
- end: start + size,
344
- plan: createUiDomPlan('div', 'virtual-list-item', {
345
- attrs: {
346
- role: 'listitem',
347
- 'aria-posinset': index + 1,
348
- 'aria-setsize': options.items.length,
349
- 'data-index': index,
350
- 'data-key': String(keyFor(options, index)),
351
- style: `height: ${size}px; transform: translateY(${start}px);`,
352
- },
353
- }),
354
- };
355
- }
356
- function createDynamicState(options, totalSize, scrollOffset, visibleStartIndex, visibleEndIndex, measuredCount, items) {
357
- return {
358
- totalSize,
359
- scrollOffset,
360
- visibleStartIndex,
361
- visibleEndIndex,
362
- measuredCount,
363
- items,
364
- };
365
- }
366
- function dynamicRangeItems(options, metrics, startIndex, endIndex) {
367
- const result = [];
368
- for (let index = startIndex; index <= endIndex; index++) {
369
- const metric = metrics[index];
370
- if (metric === undefined)
371
- continue;
372
- result.push(dynamicItemFromMetric(options, index, metric));
373
- }
374
- return result;
375
- }
376
- function dynamicItemFromMetric(options, index, metric) {
377
- return {
378
- key: keyFor(options, index),
379
- index,
380
- item: options.items[index],
381
- start: metric.start,
382
- size: metric.size,
383
- measured: metric.measured,
384
- end: metric.end,
385
- plan: createUiDomPlan('div', 'virtual-list-item', {
386
- attrs: {
387
- role: 'listitem',
388
- 'aria-posinset': index + 1,
389
- 'aria-setsize': options.items.length,
390
- 'data-index': index,
391
- 'data-key': String(keyFor(options, index)),
392
- 'data-measured': metric.measured ? 'true' : 'false',
393
- style: `height: ${metric.size}px; transform: translateY(${metric.start}px);`,
394
- },
395
- }),
396
- };
397
- }
398
- function dynamicItemMetrics(options, estimatedItemHeight) {
399
- const metrics = [];
400
- let start = 0;
401
- for (let index = 0; index < options.items.length; index++) {
402
- const measuredHeight = options.getItemHeight?.(options.items[index], index);
403
- const measured = measuredHeight !== undefined && Number.isFinite(measuredHeight) && measuredHeight > 0;
404
- const size = measured ? measuredHeight : estimatedItemHeight;
405
- metrics.push({ start, size, measured, end: start + size });
406
- start += size;
407
- }
408
- return metrics;
409
- }
410
- function createLayoutState(options, rowCount, columnCount, totalWidth, totalHeight, scrollLeft, scrollTop, visibleStartIndex, visibleEndIndex, visibleRowStart, visibleRowEnd, items, visibleColumnStart = 0, visibleColumnEnd = -1) {
411
- return {
412
- layout: options.layout,
413
- rowCount,
414
- columnCount,
415
- totalWidth,
416
- totalHeight,
417
- scrollLeft,
418
- scrollTop,
419
- visibleStartIndex,
420
- visibleEndIndex,
421
- visibleRowStart,
422
- visibleRowEnd,
423
- visibleColumnStart,
424
- visibleColumnEnd,
425
- items,
426
- };
427
- }
428
- function layoutRangeItems(options, itemWidth, itemHeight, columnCount, rowStart, rowEnd, columnStart, columnEnd) {
429
- const result = [];
430
- for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) {
431
- for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex++) {
432
- const index = rowIndex * columnCount + columnIndex;
433
- if (index >= options.items.length)
434
- continue;
435
- result.push(layoutItemFromRange(options, index, rowIndex, columnIndex, itemWidth, itemHeight));
436
- }
437
- }
438
- return result;
439
- }
440
- function layoutItemFromRange(options, index, rowIndex, columnIndex, width, height) {
441
- const x = columnIndex * width;
442
- const y = rowIndex * height;
443
- const key = keyFor(options, index);
444
- return {
445
- key,
446
- index,
447
- item: options.items[index],
448
- rowIndex,
449
- columnIndex,
450
- x,
451
- y,
452
- width,
453
- height,
454
- plan: createUiDomPlan('div', 'virtual-list-item', {
455
- attrs: {
456
- role: options.layout === 'grid' ? 'gridcell' : 'listitem',
457
- 'aria-posinset': options.layout === 'horizontal' ? index + 1 : undefined,
458
- 'aria-setsize': options.layout === 'horizontal' ? options.items.length : undefined,
459
- 'aria-rowindex': options.layout === 'grid' ? rowIndex + 1 : undefined,
460
- 'aria-colindex': options.layout === 'grid' ? columnIndex + 1 : undefined,
461
- 'data-index': index,
462
- 'data-key': String(key),
463
- 'data-row-index': rowIndex,
464
- 'data-column-index': columnIndex,
465
- style: `width: ${width}px; height: ${height}px; transform: translate(${x}px, ${y}px);`,
466
- },
467
- }),
468
- };
469
- }
470
- function firstDynamicIndexAfter(metrics, offset) {
471
- const index = metrics.findIndex((metric) => metric.end > offset);
472
- return index === -1 ? Math.max(0, metrics.length - 1) : index;
473
- }
474
- function lastDynamicIndexBefore(metrics, offset) {
475
- const index = metrics.findIndex((metric) => metric.start >= offset);
476
- if (index === -1)
477
- return metrics.length - 1;
478
- return Math.max(0, index);
479
- }
480
- function normalizeControllerOptions(options) {
481
- return {
482
- ...options,
483
- itemHeight: positiveNumber(options.itemHeight, 'itemHeight'),
484
- viewportHeight: options.viewportHeight ?? 0,
485
- overscan: options.overscan ?? 2,
486
- initialOffset: options.initialOffset ?? 0,
487
- };
488
- }
489
- function keyFor(options, index) {
490
- return options.getKey?.(options.items[index], index) ?? index;
491
- }
492
- function findItemIndexByKey(items, key, getKey) {
493
- return items.findIndex((item, index) => (getKey?.(item, index) ?? index) === key);
494
- }
495
- function uniqueKeys(keys) {
496
- return [...new Set(keys)];
497
- }
498
- function positiveNumber(value, name) {
499
- if (!Number.isFinite(value) || value <= 0)
500
- throw new Error(`${name} must be a positive number`);
501
- return value;
502
- }
503
- function clamp(value, min, max) {
504
- return Math.min(Math.max(value, min), max);
505
- }
506
- function requestFrame(callback) {
507
- if (typeof requestAnimationFrame === 'function')
508
- return requestAnimationFrame(callback);
509
- return setTimeout(callback, 16);
510
- }
511
- function cancelFrame(frame) {
512
- if (typeof cancelAnimationFrame === 'function')
513
- cancelAnimationFrame(frame);
514
- else
515
- clearTimeout(frame);
516
- }
517
- function styleSuffix(value) {
518
- if (typeof value !== 'string' || value.trim() === '')
519
- return '';
520
- return value.trim().endsWith(';') ? value.trim() : `${value.trim()};`;
521
- }
@@ -1,136 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { type UiAttributes, type UiDomPlan } from './primitives.js';
6
- /**
7
- * Workbench shell component plans — an IDE/workbench-style component family:
8
- * rich status bar (status-bar), activity rail (activity-rail), workbench titlebar (workbench-titlebar), chat composer (chat-composer).
9
- *
10
- * Spec sources: UIStyle/preview/component-status-bar.html, component-activity-rail.html,
11
- * component-workbench-titlebar.html, component-chat-composer.html (ds-* classes).
12
- *
13
- * Homogeneous with other plans components in the ui package: pure static DOM plans + derived state;
14
- * text/icons are carried via data-label / data-icon and mapped by the app at render time — plans hold no DOM text fields.
15
- */
16
- export type StatusDotTone = 'success' | 'warning' | 'error';
17
- export interface StatusBarItemPlan extends UiDomPlan<'span'> {
18
- readonly itemId: string;
19
- /** Icon identifier (optional). */
20
- readonly icon?: string;
21
- /** Status dot tone (optional). */
22
- readonly tone?: StatusDotTone;
23
- /** Text. */
24
- readonly label: string;
25
- }
26
- export interface StatusBarGroupPlan {
27
- readonly groupId: string;
28
- readonly items: readonly StatusBarItemPlan[];
29
- }
30
- export interface StatusBarPlans {
31
- readonly root: UiDomPlan<'div'>;
32
- readonly groups: readonly StatusBarGroupPlan[];
33
- }
34
- export interface StatusBarGroupInput {
35
- readonly id: string;
36
- readonly items: readonly {
37
- readonly id: string;
38
- readonly label: string;
39
- readonly icon?: string;
40
- readonly tone?: StatusDotTone;
41
- }[];
42
- }
43
- export interface StatusBarPlanOptions {
44
- readonly id: string;
45
- readonly groups: readonly StatusBarGroupInput[];
46
- readonly className?: string;
47
- readonly attrs?: UiAttributes;
48
- }
49
- export declare function createStatusBarPlans(options: StatusBarPlanOptions): StatusBarPlans;
50
- export type ActivityRailEntryKind = 'button' | 'divider' | 'spacer';
51
- export interface ActivityRailButtonPlan extends UiDomPlan<'button'> {
52
- readonly entryId: string;
53
- readonly kind: 'button';
54
- /** Icon identifier. */
55
- readonly icon: string;
56
- readonly active: boolean;
57
- }
58
- export interface ActivityRailDividerPlan extends UiDomPlan<'div'> {
59
- readonly entryId: string;
60
- readonly kind: 'divider';
61
- }
62
- export interface ActivityRailSpacerPlan extends UiDomPlan<'span'> {
63
- readonly entryId: string;
64
- readonly kind: 'spacer';
65
- }
66
- export type ActivityRailEntryPlan = ActivityRailButtonPlan | ActivityRailDividerPlan | ActivityRailSpacerPlan;
67
- export interface ActivityRailEntryInput {
68
- readonly id: string;
69
- readonly kind: ActivityRailEntryKind;
70
- /** Required when kind='button'. */
71
- readonly icon?: string;
72
- readonly active?: boolean;
73
- readonly disabled?: boolean;
74
- }
75
- export interface ActivityRailPlans {
76
- readonly root: UiDomPlan<'nav'>;
77
- readonly entries: readonly ActivityRailEntryPlan[];
78
- }
79
- export interface ActivityRailPlanOptions {
80
- readonly id: string;
81
- readonly entries: readonly ActivityRailEntryInput[];
82
- readonly className?: string;
83
- readonly attrs?: UiAttributes;
84
- }
85
- export declare function createActivityRailPlans(options: ActivityRailPlanOptions): ActivityRailPlans;
86
- export type TitlebarLightKind = 'close' | 'min' | 'max';
87
- export interface WorkbenchTitlebarPlans {
88
- readonly root: UiDomPlan<'div'>;
89
- readonly left: UiDomPlan<'div'>;
90
- /** Red/yellow/green window lights. */
91
- readonly lights: readonly UiDomPlan<'span'>[];
92
- readonly modeChip: UiDomPlan<'span'>;
93
- readonly projectSelector: UiDomPlan<'button'>;
94
- readonly right: UiDomPlan<'div'>;
95
- /** Right-side icon buttons (data-icon identifier). */
96
- readonly actions: readonly UiDomPlan<'button'>[];
97
- }
98
- export interface WorkbenchTitlebarPlanOptions {
99
- readonly id: string;
100
- /** Mode chip text (e.g. 'Agent Mode'). */
101
- readonly mode?: string;
102
- /** Project selector text. */
103
- readonly project?: string;
104
- /** Right-side action icon list. */
105
- readonly actions?: readonly string[];
106
- readonly className?: string;
107
- readonly attrs?: UiAttributes;
108
- }
109
- export declare function createWorkbenchTitlebarPlans(options: WorkbenchTitlebarPlanOptions): WorkbenchTitlebarPlans;
110
- export interface ChatComposerPlans {
111
- readonly root: UiDomPlan<'div'>;
112
- readonly input: UiDomPlan<'textarea'>;
113
- readonly toolbar: UiDomPlan<'div'>;
114
- readonly tools: UiDomPlan<'div'>;
115
- /** Left-side tool buttons (data-icon identifier). */
116
- readonly toolButtons: readonly UiDomPlan<'button'>[];
117
- readonly modelSelector: UiDomPlan<'button'>;
118
- readonly actions: UiDomPlan<'div'>;
119
- /** Right-side action buttons (including send). */
120
- readonly actionButtons: readonly UiDomPlan<'button'>[];
121
- }
122
- export interface ChatComposerPlanOptions {
123
- readonly id: string;
124
- /** Input placeholder. */
125
- readonly placeholder?: string;
126
- /** Left-side tool icon list. */
127
- readonly tools?: readonly string[];
128
- /** Model selector text. */
129
- readonly model?: string;
130
- /** Right-side action icon list (the send button is handled by the renderer via data-send). */
131
- readonly actions?: readonly string[];
132
- readonly disabled?: boolean;
133
- readonly className?: string;
134
- readonly attrs?: UiAttributes;
135
- }
136
- export declare function createChatComposerPlans(options: ChatComposerPlanOptions): ChatComposerPlans;