@vobs/ui 0.3.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 (212) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +60 -0
  3. package/package.json +52 -110
  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 -30
  165. package/dist/app-shell-dom.js +0 -913
  166. package/dist/app-shell.d.ts +0 -235
  167. package/dist/app-shell.js +0 -415
  168. package/dist/base.css +0 -51
  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 -866
  175. package/dist/components.css +0 -4756
  176. package/dist/data-display.d.ts +0 -1094
  177. package/dist/data-display.js +0 -2341
  178. package/dist/dialog.d.ts +0 -26
  179. package/dist/dialog.js +0 -94
  180. package/dist/feedback.d.ts +0 -88
  181. package/dist/feedback.js +0 -281
  182. package/dist/forms.d.ts +0 -378
  183. package/dist/forms.js +0 -1019
  184. package/dist/icons-config.d.ts +0 -25
  185. package/dist/icons-config.js +0 -14
  186. package/dist/icons.d.ts +0 -41
  187. package/dist/icons.js +0 -185
  188. package/dist/index.d.ts +0 -31
  189. package/dist/index.js +0 -40
  190. package/dist/locale/en-US.d.ts +0 -31
  191. package/dist/locale/en-US.js +0 -20
  192. package/dist/locale/zh-CN.d.ts +0 -31
  193. package/dist/locale/zh-CN.js +0 -20
  194. package/dist/navigation.d.ts +0 -609
  195. package/dist/navigation.js +0 -1707
  196. package/dist/overlay.d.ts +0 -308
  197. package/dist/overlay.js +0 -974
  198. package/dist/page-state.d.ts +0 -23
  199. package/dist/page-state.js +0 -96
  200. package/dist/primitives.d.ts +0 -268
  201. package/dist/primitives.js +0 -764
  202. package/dist/theme-data.d.ts +0 -394
  203. package/dist/theme-data.js +0 -417
  204. package/dist/theme.d.ts +0 -97
  205. package/dist/theme.js +0 -301
  206. package/dist/tokens.css +0 -570
  207. package/dist/tree.d.ts +0 -144
  208. package/dist/tree.js +0 -475
  209. package/dist/virtual-list.d.ts +0 -190
  210. package/dist/virtual-list.js +0 -538
  211. package/dist/workbench.d.ts +0 -136
  212. package/dist/workbench.js +0 -156
@@ -1,538 +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 measuredKeySet = new Set(measuredKeys);
94
- const measuringKeys = uniqueKeys(options.measuringIds ?? []);
95
- const plans = createDynamicVirtualListPlans(options.getMeasuredItemHeight === undefined
96
- ? options
97
- : {
98
- ...options,
99
- getItemHeight: options.getMeasuredItemHeight,
100
- });
101
- const estimatedKeys = options.items
102
- .map((_, index) => keyFor(options, index))
103
- .filter((key) => !measuredKeySet.has(key));
104
- return {
105
- plans,
106
- measuredKeys,
107
- measuringKeys,
108
- estimatedKeys,
109
- attrs: {
110
- 'data-resize-measurement-contract': 'true',
111
- 'data-measured-count': measuredKeys.length,
112
- 'data-estimated-count': estimatedKeys.length,
113
- 'data-measuring-count': measuringKeys.length,
114
- 'data-measured-keys': measuredKeys.join(','),
115
- 'data-estimated-keys': estimatedKeys.join(','),
116
- 'data-measuring-keys': measuringKeys.join(','),
117
- 'data-total-size': plans.totalSize,
118
- 'data-viewport-height': options.viewportHeight,
119
- 'data-scroll-offset': plans.scrollOffset,
120
- 'aria-busy': measuringKeys.length > 0 ? 'true' : undefined,
121
- },
122
- };
123
- }
124
- export function createVirtualListAnchorStateContract(options) {
125
- const itemHeight = positiveNumber(options.itemHeight, 'itemHeight');
126
- const previousAnchorIndex = findItemIndexByKey(options.previousItems, options.anchorKey, options.getKey);
127
- const nextAnchorIndex = findItemIndexByKey(options.nextItems, options.anchorKey, options.getKey);
128
- const anchorOffset = Math.max(0, options.anchorOffset ?? 0);
129
- const viewportHeight = Math.max(0, options.viewportHeight ?? 0);
130
- const maxOffset = Math.max(options.nextItems.length * itemHeight - viewportHeight, 0);
131
- const anchorOffsetTarget = nextAnchorIndex < 0 ? 0 : nextAnchorIndex * itemHeight + anchorOffset;
132
- const scrollOffset = options.stickToEnd === true ? maxOffset : clamp(anchorOffsetTarget, 0, maxOffset);
133
- const insertedBeforeCount = previousAnchorIndex < 0 || nextAnchorIndex < 0
134
- ? 0
135
- : Math.max(0, nextAnchorIndex - previousAnchorIndex);
136
- const state = nextAnchorIndex < 0 ? 'missing-anchor' : options.stickToEnd === true ? 'end' : 'preserved';
137
- return {
138
- scrollOffset,
139
- previousAnchorIndex,
140
- nextAnchorIndex,
141
- insertedBeforeCount,
142
- attrs: {
143
- 'data-anchor-contract': 'true',
144
- 'data-anchor-mode': options.mode ?? 'preserve',
145
- 'data-anchor-state': state,
146
- 'data-anchor-key': String(options.anchorKey),
147
- 'data-previous-anchor-index': previousAnchorIndex,
148
- 'data-next-anchor-index': nextAnchorIndex,
149
- 'data-inserted-before-count': insertedBeforeCount,
150
- 'data-scroll-offset': scrollOffset,
151
- 'data-stick-to-end': options.stickToEnd === true ? 'true' : undefined,
152
- },
153
- };
154
- }
155
- export function calculateFixedVirtualListState(options) {
156
- const itemHeight = positiveNumber(options.itemHeight, 'itemHeight');
157
- const count = options.items.length;
158
- if (count === 0)
159
- return createState(options, itemHeight, 0, 0, -1, []);
160
- const viewportHeight = Math.max(0, options.viewportHeight);
161
- const scrollOffset = clamp(options.scrollOffset ?? 0, 0, Math.max(count * itemHeight - viewportHeight, 0));
162
- const visibleStart = Math.floor(scrollOffset / itemHeight);
163
- const visibleEnd = Math.min(count - 1, Math.ceil((scrollOffset + viewportHeight) / itemHeight));
164
- const ssrCount = options.ssrFallbackCount;
165
- const fallbackEnd = ssrCount === undefined ? visibleEnd : Math.min(count - 1, Math.max(0, ssrCount) - 1);
166
- const start = ssrCount === undefined ? Math.max(0, visibleStart - (options.overscan ?? 2)) : 0;
167
- const end = ssrCount === undefined
168
- ? Math.min(count - 1, visibleEnd + (options.overscan ?? 2))
169
- : fallbackEnd;
170
- return createState(options, itemHeight, scrollOffset, visibleStart, visibleEnd, rangeItems(options, start, end));
171
- }
172
- export function calculateDynamicVirtualListState(options) {
173
- const estimatedItemHeight = positiveNumber(options.estimatedItemHeight, 'estimatedItemHeight');
174
- const count = options.items.length;
175
- if (count === 0) {
176
- return createDynamicState(options, 0, 0, 0, -1, 0, []);
177
- }
178
- const { metrics, measuredCount, totalSize } = dynamicItemMetrics(options, estimatedItemHeight);
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, measuredCount, 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
- let measuredCount = 0;
402
- for (let index = 0; index < options.items.length; index++) {
403
- const measuredHeight = options.getItemHeight?.(options.items[index], index);
404
- const measured = measuredHeight !== undefined && Number.isFinite(measuredHeight) && measuredHeight > 0;
405
- const size = measured ? measuredHeight : estimatedItemHeight;
406
- metrics.push({ start, size, measured, end: start + size });
407
- start += size;
408
- if (measured)
409
- measuredCount += 1;
410
- }
411
- return { metrics, measuredCount, totalSize: start };
412
- }
413
- function createLayoutState(options, rowCount, columnCount, totalWidth, totalHeight, scrollLeft, scrollTop, visibleStartIndex, visibleEndIndex, visibleRowStart, visibleRowEnd, items, visibleColumnStart = 0, visibleColumnEnd = -1) {
414
- return {
415
- layout: options.layout,
416
- rowCount,
417
- columnCount,
418
- totalWidth,
419
- totalHeight,
420
- scrollLeft,
421
- scrollTop,
422
- visibleStartIndex,
423
- visibleEndIndex,
424
- visibleRowStart,
425
- visibleRowEnd,
426
- visibleColumnStart,
427
- visibleColumnEnd,
428
- items,
429
- };
430
- }
431
- function layoutRangeItems(options, itemWidth, itemHeight, columnCount, rowStart, rowEnd, columnStart, columnEnd) {
432
- const result = [];
433
- for (let rowIndex = rowStart; rowIndex <= rowEnd; rowIndex++) {
434
- for (let columnIndex = columnStart; columnIndex <= columnEnd; columnIndex++) {
435
- const index = rowIndex * columnCount + columnIndex;
436
- if (index >= options.items.length)
437
- continue;
438
- result.push(layoutItemFromRange(options, index, rowIndex, columnIndex, itemWidth, itemHeight));
439
- }
440
- }
441
- return result;
442
- }
443
- function layoutItemFromRange(options, index, rowIndex, columnIndex, width, height) {
444
- const x = columnIndex * width;
445
- const y = rowIndex * height;
446
- const key = keyFor(options, index);
447
- return {
448
- key,
449
- index,
450
- item: options.items[index],
451
- rowIndex,
452
- columnIndex,
453
- x,
454
- y,
455
- width,
456
- height,
457
- plan: createUiDomPlan('div', 'virtual-list-item', {
458
- attrs: {
459
- role: options.layout === 'grid' ? 'gridcell' : 'listitem',
460
- 'aria-posinset': options.layout === 'horizontal' ? index + 1 : undefined,
461
- 'aria-setsize': options.layout === 'horizontal' ? options.items.length : undefined,
462
- 'aria-rowindex': options.layout === 'grid' ? rowIndex + 1 : undefined,
463
- 'aria-colindex': options.layout === 'grid' ? columnIndex + 1 : undefined,
464
- 'data-index': index,
465
- 'data-key': String(key),
466
- 'data-row-index': rowIndex,
467
- 'data-column-index': columnIndex,
468
- style: `width: ${width}px; height: ${height}px; transform: translate(${x}px, ${y}px);`,
469
- },
470
- }),
471
- };
472
- }
473
- function firstDynamicIndexAfter(metrics, offset) {
474
- let low = 0;
475
- let high = metrics.length;
476
- while (low < high) {
477
- const mid = low + Math.floor((high - low) / 2);
478
- if (metrics[mid].end > offset)
479
- high = mid;
480
- else
481
- low = mid + 1;
482
- }
483
- return low === metrics.length ? Math.max(0, metrics.length - 1) : low;
484
- }
485
- function lastDynamicIndexBefore(metrics, offset) {
486
- let low = 0;
487
- let high = metrics.length;
488
- while (low < high) {
489
- const mid = low + Math.floor((high - low) / 2);
490
- if (metrics[mid].start >= offset)
491
- high = mid;
492
- else
493
- low = mid + 1;
494
- }
495
- return low === metrics.length ? metrics.length - 1 : Math.max(0, low);
496
- }
497
- function normalizeControllerOptions(options) {
498
- return {
499
- ...options,
500
- itemHeight: positiveNumber(options.itemHeight, 'itemHeight'),
501
- viewportHeight: options.viewportHeight ?? 0,
502
- overscan: options.overscan ?? 2,
503
- initialOffset: options.initialOffset ?? 0,
504
- };
505
- }
506
- function keyFor(options, index) {
507
- return options.getKey?.(options.items[index], index) ?? index;
508
- }
509
- function findItemIndexByKey(items, key, getKey) {
510
- return items.findIndex((item, index) => (getKey?.(item, index) ?? index) === key);
511
- }
512
- function uniqueKeys(keys) {
513
- return [...new Set(keys)];
514
- }
515
- function positiveNumber(value, name) {
516
- if (!Number.isFinite(value) || value <= 0)
517
- throw new Error(`${name} must be a positive number`);
518
- return value;
519
- }
520
- function clamp(value, min, max) {
521
- return Math.min(Math.max(value, min), max);
522
- }
523
- function requestFrame(callback) {
524
- if (typeof requestAnimationFrame === 'function')
525
- return requestAnimationFrame(callback);
526
- return setTimeout(callback, 16);
527
- }
528
- function cancelFrame(frame) {
529
- if (typeof cancelAnimationFrame === 'function')
530
- cancelAnimationFrame(frame);
531
- else
532
- clearTimeout(frame);
533
- }
534
- function styleSuffix(value) {
535
- if (typeof value !== 'string' || value.trim() === '')
536
- return '';
537
- return value.trim().endsWith(';') ? value.trim() : `${value.trim()};`;
538
- }
@@ -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;