@vobs/ui 0.3.0 → 1.1.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 +118 -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,866 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { createInputPrimitive, createUiDomPlan, } from './primitives.js';
6
- export function createComboboxPlans(options) {
7
- const open = options.open === true;
8
- const activeId = open ? options.activeId : undefined;
9
- return {
10
- root: createUiDomPlan('div', 'combobox', {
11
- ...(options.className === undefined ? {} : { className: options.className }),
12
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
13
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
14
- state: options.disabled === true ? 'disabled' : open ? 'open' : 'closed',
15
- attrs: {
16
- id: options.id,
17
- ...options.attrs,
18
- },
19
- }),
20
- input: createUiDomPlan('input', 'combobox-input', {
21
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
22
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
23
- attrs: {
24
- id: `${options.id}-input`,
25
- type: 'text',
26
- role: 'combobox',
27
- autocomplete: 'off',
28
- 'aria-autocomplete': 'list',
29
- 'aria-expanded': open ? 'true' : 'false',
30
- 'aria-controls': `${options.id}-listbox`,
31
- 'aria-activedescendant': activeId === undefined ? undefined : optionDomId(options.id, activeId),
32
- 'aria-labelledby': options.labelId,
33
- 'aria-describedby': options.describedBy,
34
- 'aria-required': options.required === true ? 'true' : undefined,
35
- value: options.inputValue,
36
- placeholder: options.placeholder,
37
- },
38
- }),
39
- listbox: createUiDomPlan('ul', 'combobox-listbox', {
40
- state: open ? 'open' : 'closed',
41
- attrs: {
42
- id: `${options.id}-listbox`,
43
- role: 'listbox',
44
- hidden: open ? undefined : true,
45
- },
46
- }),
47
- options: options.options.map((option) => {
48
- const active = option.id === activeId;
49
- const selected = option.value === options.selectedValue;
50
- return createUiDomPlan('li', 'combobox-option', {
51
- ...(option.disabled === undefined ? {} : { disabled: option.disabled }),
52
- state: option.disabled === true
53
- ? 'disabled'
54
- : active
55
- ? 'active'
56
- : selected
57
- ? 'selected'
58
- : 'idle',
59
- attrs: {
60
- id: optionDomId(options.id, option.id),
61
- role: 'option',
62
- 'aria-selected': selected ? 'true' : 'false',
63
- 'data-option-id': option.id,
64
- 'data-value': option.value,
65
- },
66
- });
67
- }),
68
- };
69
- }
70
- export function createUploadPlans(options) {
71
- const files = options.files ?? [];
72
- const disabled = options.disabled === true;
73
- return {
74
- root: createUiDomPlan('div', 'upload', {
75
- ...(options.className === undefined ? {} : { className: options.className }),
76
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
77
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
78
- state: stateFromUpload(options, files.length),
79
- attrs: {
80
- id: `${options.id}-upload`,
81
- ...options.attrs,
82
- },
83
- }),
84
- input: createUiDomPlan('input', 'upload-input', {
85
- disabled,
86
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
87
- attrs: {
88
- id: options.id,
89
- type: 'file',
90
- name: options.name,
91
- accept: options.accept,
92
- multiple: options.multiple,
93
- required: options.required,
94
- disabled,
95
- 'aria-labelledby': options.labelId,
96
- 'aria-describedby': options.describedBy,
97
- 'aria-invalid': options.invalid === true ? 'true' : undefined,
98
- },
99
- }),
100
- dropzone: createUiDomPlan('label', 'upload-dropzone', {
101
- disabled,
102
- state: options.dragActive === true ? 'drag-active' : disabled ? 'disabled' : 'idle',
103
- attrs: {
104
- for: options.id,
105
- 'data-label': options.label,
106
- },
107
- }),
108
- fileList: createUiDomPlan('ul', 'upload-list', {
109
- state: files.length === 0 ? 'empty' : 'filled',
110
- attrs: {
111
- id: `${options.id}-list`,
112
- role: 'list',
113
- 'aria-live': 'polite',
114
- hidden: files.length === 0 ? true : undefined,
115
- },
116
- }),
117
- files: files.map((file) => createUiDomPlan('li', 'upload-file', {
118
- state: file.state ?? 'done',
119
- attrs: {
120
- id: uploadFileDomId(options.id, file.id),
121
- role: 'listitem',
122
- 'data-file-id': file.id,
123
- 'data-name': file.name,
124
- 'data-size': file.size,
125
- 'data-type': file.type,
126
- 'data-size-label': file.size === undefined ? undefined : formatUploadFileSize(file.size),
127
- 'data-progress': file.progress,
128
- 'data-retry-count': file.retryCount,
129
- 'data-retryable': file.retryable === true ? 'true' : undefined,
130
- 'data-server-error': file.serverError,
131
- 'data-message': file.message,
132
- },
133
- })),
134
- };
135
- }
136
- export function createUploadTransportStateContract(options = {}) {
137
- const disabled = options.disabled === true;
138
- const maxRetries = normalizeRetryCount(options.maxRetries);
139
- const retryingIds = normalizeIds(options.retryingIds);
140
- const retryingSet = new Set(retryingIds);
141
- const files = (options.files ?? []).map((file) => uploadTransportFileContract(file, {
142
- disabled,
143
- maxRetries,
144
- retrying: retryingSet.has(file.id),
145
- serverErrors: normalizeServerErrors(options.serverErrors?.[file.id], file.serverError),
146
- }));
147
- const fileCount = files.length;
148
- const pendingCount = files.filter((file) => file.state === 'pending').length;
149
- const uploadingCount = files.filter((file) => file.state === 'uploading').length;
150
- const doneCount = files.filter((file) => file.state === 'done').length;
151
- const errorCount = files.filter((file) => file.state === 'error').length;
152
- const retryableCount = files.filter((file) => file.canRetry).length;
153
- const serverErrorCount = files.reduce((count, file) => count + file.serverErrors.length, 0);
154
- const progress = fileCount === 0
155
- ? 0
156
- : Math.round(files.reduce((total, file) => total + file.progress, 0) / fileCount);
157
- const state = uploadTransportState({
158
- disabled,
159
- doneCount,
160
- errorCount,
161
- fileCount,
162
- pendingCount,
163
- retryingCount: retryingIds.length,
164
- uploadingCount,
165
- });
166
- return {
167
- state,
168
- disabled,
169
- fileCount,
170
- pendingCount,
171
- uploadingCount,
172
- doneCount,
173
- errorCount,
174
- retryableCount,
175
- serverErrorCount,
176
- retryingIds,
177
- progress,
178
- files,
179
- attrs: {
180
- 'data-upload-transport-contract': 'true',
181
- 'data-state': state,
182
- 'data-disabled': disabled ? 'true' : undefined,
183
- 'data-file-count': fileCount,
184
- 'data-pending-count': pendingCount,
185
- 'data-uploading-count': uploadingCount,
186
- 'data-done-count': doneCount,
187
- 'data-error-count': errorCount,
188
- 'data-retryable-count': retryableCount,
189
- 'data-server-error-count': serverErrorCount,
190
- 'data-retrying-ids': retryingIds.join(','),
191
- 'data-progress': progress,
192
- 'aria-busy': state === 'uploading' || state === 'retrying' ? 'true' : undefined,
193
- },
194
- };
195
- }
196
- export function createCascaderPlans(options) {
197
- const open = options.open === true;
198
- const selectedPath = findCascaderPathByValue(options.options, options.selectedValue);
199
- const activePath = options.activePath === undefined || options.activePath.length === 0
200
- ? selectedPath.map((item) => item.id)
201
- : [...options.activePath];
202
- const columns = getCascaderColumns(options.options, activePath);
203
- const activeId = activePath.at(-1);
204
- const selectedId = selectedPath.at(-1)?.id;
205
- const selectedLabel = selectedPath.map((item) => item.label).join(' / ');
206
- const valueLabel = selectedLabel === '' ? (options.placeholder ?? 'Select option') : selectedLabel;
207
- const disabled = options.disabled === true;
208
- return {
209
- root: createUiDomPlan('div', 'cascader', {
210
- ...(options.className === undefined ? {} : { className: options.className }),
211
- disabled,
212
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
213
- state: disabled ? 'disabled' : open ? 'open' : 'closed',
214
- attrs: {
215
- id: `${options.id}-cascader`,
216
- ...options.attrs,
217
- 'data-value': options.selectedValue,
218
- 'data-label': valueLabel,
219
- 'data-levels': columns.length,
220
- },
221
- }),
222
- trigger: createUiDomPlan('button', 'cascader-trigger', {
223
- disabled,
224
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
225
- attrs: {
226
- id: `${options.id}-trigger`,
227
- type: 'button',
228
- role: 'combobox',
229
- 'aria-haspopup': 'tree',
230
- 'aria-expanded': open ? 'true' : 'false',
231
- 'aria-controls': `${options.id}-popup`,
232
- 'aria-labelledby': options.labelId,
233
- 'aria-describedby': options.describedBy,
234
- 'aria-required': options.required === true ? 'true' : undefined,
235
- 'data-value': options.selectedValue,
236
- 'data-label': valueLabel,
237
- },
238
- }),
239
- popup: createUiDomPlan('div', 'cascader-popup', {
240
- state: open ? 'open' : 'closed',
241
- attrs: {
242
- id: `${options.id}-popup`,
243
- role: 'tree',
244
- hidden: open ? undefined : true,
245
- 'aria-labelledby': options.labelId,
246
- 'data-levels': columns.length,
247
- },
248
- }),
249
- columns: columns.map((column, index) => createUiDomPlan('div', 'cascader-column', {
250
- attrs: {
251
- id: cascaderColumnDomId(options.id, index),
252
- role: 'group',
253
- 'data-level': index + 1,
254
- 'data-count': column.length,
255
- },
256
- })),
257
- options: columns.flatMap((column, columnIndex) => column.map((item) => {
258
- const active = item.id === activeId;
259
- const selected = item.id === selectedId;
260
- const expandable = (item.children?.length ?? 0) > 0;
261
- return createUiDomPlan('button', 'cascader-option', {
262
- ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
263
- state: item.disabled === true
264
- ? 'disabled'
265
- : active
266
- ? 'active'
267
- : selected
268
- ? 'selected'
269
- : 'idle',
270
- attrs: {
271
- id: cascaderOptionDomId(options.id, item.id),
272
- type: 'button',
273
- role: 'treeitem',
274
- 'aria-level': columnIndex + 1,
275
- 'aria-expanded': expandable ? String(active) : undefined,
276
- 'aria-selected': selected ? 'true' : 'false',
277
- 'data-column': columnIndex,
278
- 'data-option-id': item.id,
279
- 'data-value': item.value,
280
- 'data-label': item.label,
281
- 'data-has-children': expandable ? 'true' : undefined,
282
- },
283
- });
284
- })),
285
- };
286
- }
287
- export function createCascaderAdvancedStateContract(options) {
288
- const flattenedOptions = flattenCascaderOptions(options.options);
289
- const query = (options.query ?? '').trim();
290
- const resultCount = normalizeCount(options.resultCount);
291
- const totalNodeCount = normalizeCount(options.totalNodeCount) ?? flattenedOptions.length;
292
- const visibleIds = options.visibleIds === undefined
293
- ? flattenedOptions.map((option) => option.id)
294
- : normalizeCascaderIds(options.visibleIds, new Set(flattenedOptions.map((option) => option.id)));
295
- const visibleSet = new Set(visibleIds);
296
- const allowedSet = options.allowedIds === undefined
297
- ? undefined
298
- : new Set(normalizeCascaderIds(options.allowedIds, new Set(flattenedOptions.map((option) => option.id))));
299
- const deniedIds = normalizeCascaderIds(options.deniedIds, new Set(flattenedOptions.map((option) => option.id)));
300
- const deniedSet = new Set(deniedIds);
301
- const optionContracts = flattenedOptions.map((option) => {
302
- const visible = visibleSet.has(option.id);
303
- const allowed = !deniedSet.has(option.id) && (allowedSet === undefined || allowedSet.has(option.id));
304
- const reason = allowed ? '' : (options.disabledReasons?.[option.id] ?? 'Permission denied');
305
- return {
306
- id: option.id,
307
- visible,
308
- allowed,
309
- reason,
310
- attrs: {
311
- 'data-option-id': option.id,
312
- 'data-visible': visible ? 'true' : 'false',
313
- 'data-permission': allowed ? 'allowed' : 'denied',
314
- 'data-disabled-reason': reason === '' ? undefined : reason,
315
- 'aria-disabled': allowed ? undefined : 'true',
316
- },
317
- };
318
- });
319
- const deniedOptionIds = optionContracts
320
- .filter((option) => !option.allowed)
321
- .map((option) => option.id);
322
- const virtualStart = normalizeVirtualIndex(options.virtualStart, 0, totalNodeCount);
323
- const virtualEnd = normalizeVirtualIndex(options.virtualEnd, totalNodeCount - 1, totalNodeCount);
324
- const virtualState = virtualStart > 0 || virtualEnd < Math.max(totalNodeCount - 1, 0) ? 'windowed' : 'full';
325
- const remoteState = cascaderRemoteState({
326
- error: options.error,
327
- loading: options.loading === true,
328
- query,
329
- resultCount,
330
- });
331
- const persistenceState = cascaderPersistenceState({
332
- currentValue: options.currentValue,
333
- dirty: options.dirty === true,
334
- persistedValue: options.persistedValue,
335
- restored: options.restored === true,
336
- });
337
- const permissionState = deniedOptionIds.length > 0 ? 'restricted' : 'allowed';
338
- const currentValue = options.currentValue ?? '';
339
- const persistedValue = options.persistedValue ?? '';
340
- return {
341
- remoteState,
342
- permissionState,
343
- virtualState,
344
- persistenceState,
345
- query,
346
- resultCount,
347
- deniedIds: deniedOptionIds,
348
- visibleIds,
349
- totalNodeCount,
350
- virtualStart,
351
- virtualEnd,
352
- currentValue,
353
- persistedValue,
354
- optionContracts,
355
- attrs: {
356
- 'data-cascader-advanced-contract': 'true',
357
- 'data-remote-state': remoteState,
358
- 'data-query': query,
359
- 'data-result-count': resultCount,
360
- 'data-error-message': options.error,
361
- 'data-permission-state': permissionState,
362
- 'data-denied-option-ids': deniedOptionIds.join(','),
363
- 'data-denied-count': deniedOptionIds.length,
364
- 'data-visible-option-ids': visibleIds.join(','),
365
- 'data-visible-count': visibleIds.length,
366
- 'data-virtual-state': virtualState,
367
- 'data-total-node-count': totalNodeCount,
368
- 'data-window-start': virtualStart,
369
- 'data-window-end': virtualEnd,
370
- 'data-window-size': Math.max(virtualEnd - virtualStart + 1, 0),
371
- 'data-estimated-row-height': normalizeCount(options.estimatedRowHeight),
372
- 'data-persistence-state': persistenceState,
373
- 'data-current-value': currentValue,
374
- 'data-persisted-value': persistedValue,
375
- 'data-dirty': options.dirty === true ? 'true' : undefined,
376
- 'data-restored': options.restored === true ? 'true' : undefined,
377
- 'aria-busy': options.loading === true ? 'true' : undefined,
378
- },
379
- };
380
- }
381
- export function createDateTimeFieldPlans(options) {
382
- const inputType = options.kind === 'datetime' ? 'datetime-local' : options.kind === 'time' ? 'time' : 'date';
383
- return {
384
- root: createUiDomPlan('div', `${options.kind}-field`, {
385
- ...(options.className === undefined ? {} : { className: options.className }),
386
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
387
- ...(options.readonly === undefined ? {} : { readonly: options.readonly }),
388
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
389
- ...(options.density === undefined ? {} : { density: options.density }),
390
- state: stateFromDateTimeField(options),
391
- attrs: {
392
- id: `${options.id}-field`,
393
- },
394
- }),
395
- input: createInputPrimitive({
396
- type: 'text',
397
- ...(options.name === undefined ? {} : { name: options.name }),
398
- ...(options.value === undefined ? {} : { value: options.value }),
399
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
400
- ...(options.readonly === undefined ? {} : { readonly: options.readonly }),
401
- ...(options.required === undefined ? {} : { required: options.required }),
402
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
403
- ...(options.density === undefined ? {} : { density: options.density }),
404
- ...(options.size === undefined ? {} : { className: `kui-input--${options.size}` }),
405
- attrs: {
406
- id: options.id,
407
- type: inputType,
408
- min: options.min,
409
- max: options.max,
410
- step: options.step,
411
- placeholder: options.placeholder,
412
- 'aria-labelledby': options.labelId,
413
- 'aria-describedby': options.describedBy,
414
- 'aria-invalid': options.invalid === true ? 'true' : undefined,
415
- ...options.attrs,
416
- },
417
- }),
418
- };
419
- }
420
- export function formatDateInputValue(value) {
421
- return [
422
- value.getFullYear().toString().padStart(4, '0'),
423
- (value.getMonth() + 1).toString().padStart(2, '0'),
424
- value.getDate().toString().padStart(2, '0'),
425
- ].join('-');
426
- }
427
- export function formatTimeInputValue(value, includeSeconds = false) {
428
- const formatted = [
429
- value.getHours().toString().padStart(2, '0'),
430
- value.getMinutes().toString().padStart(2, '0'),
431
- ];
432
- if (includeSeconds)
433
- formatted.push(value.getSeconds().toString().padStart(2, '0'));
434
- return formatted.join(':');
435
- }
436
- export function formatDateTimeInputValue(value, includeSeconds = false) {
437
- return `${formatDateInputValue(value)}T${formatTimeInputValue(value, includeSeconds)}`;
438
- }
439
- export function parseDateInputValue(value) {
440
- const match = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(value);
441
- if (match === null)
442
- return undefined;
443
- return createValidDate(Number(match[1]), Number(match[2]) - 1, Number(match[3]));
444
- }
445
- export function parseTimeInputValue(value) {
446
- const match = /^(\d{2}):(\d{2})(?::(\d{2}))?$/u.exec(value);
447
- if (match === null)
448
- return undefined;
449
- const hours = Number(match[1]);
450
- const minutes = Number(match[2]);
451
- const seconds = Number(match[3] ?? '0');
452
- if (hours > 23 || minutes > 59 || seconds > 59)
453
- return undefined;
454
- const date = new Date(0);
455
- date.setHours(hours, minutes, seconds, 0);
456
- return date;
457
- }
458
- export function parseDateTimeInputValue(value) {
459
- const match = /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}(?::\d{2})?)$/u.exec(value);
460
- if (match === null)
461
- return undefined;
462
- const date = parseDateInputValue(match[1] ?? '');
463
- const time = parseTimeInputValue(match[2] ?? '');
464
- if (date === undefined || time === undefined)
465
- return undefined;
466
- date.setHours(time.getHours(), time.getMinutes(), time.getSeconds(), 0);
467
- return date;
468
- }
469
- export function clampDateTimeInputValue(value, min, max) {
470
- if (min !== undefined && value < min)
471
- return min;
472
- if (max !== undefined && value > max)
473
- return max;
474
- return value;
475
- }
476
- export function formatUploadFileSize(bytes) {
477
- if (!Number.isFinite(bytes) || bytes <= 0)
478
- return '0 B';
479
- const units = ['B', 'KB', 'MB', 'GB'];
480
- let value = bytes;
481
- let unitIndex = 0;
482
- while (value >= 1024 && unitIndex < units.length - 1) {
483
- value /= 1024;
484
- unitIndex += 1;
485
- }
486
- const rounded = unitIndex === 0 || value >= 10 ? Math.round(value) : Math.round(value * 10) / 10;
487
- return `${rounded} ${units[unitIndex]}`;
488
- }
489
- export function isUploadFileAccepted(file, accept) {
490
- if (accept === undefined || accept.trim() === '')
491
- return true;
492
- const rules = accept
493
- .split(',')
494
- .map((rule) => rule.trim().toLowerCase())
495
- .filter((rule) => rule !== '');
496
- const fileName = file.name.toLowerCase();
497
- const fileType = file.type?.toLowerCase() ?? '';
498
- return rules.some((rule) => {
499
- if (rule.startsWith('.'))
500
- return fileName.endsWith(rule);
501
- if (rule.endsWith('/*'))
502
- return fileType.startsWith(rule.slice(0, -1));
503
- return fileType === rule;
504
- });
505
- }
506
- export function filterAutocompleteOptions(options, query, limit = options.length) {
507
- const keyword = query.trim().toLowerCase();
508
- const result = [];
509
- for (const option of options) {
510
- if (keyword === '' ||
511
- option.label.toLowerCase().includes(keyword) ||
512
- option.value.toLowerCase().includes(keyword)) {
513
- result.push(option);
514
- if (result.length >= limit)
515
- break;
516
- }
517
- }
518
- return result;
519
- }
520
- export function getNextComboboxOptionId(options, activeId, key) {
521
- const enabled = options.filter((option) => option.disabled !== true);
522
- if (enabled.length === 0)
523
- return activeId;
524
- const currentIndex = Math.max(-1, enabled.findIndex((option) => option.id === activeId));
525
- if (key === 'Home')
526
- return enabled[0]?.id;
527
- if (key === 'End')
528
- return enabled.at(-1)?.id;
529
- if (key === 'ArrowUp')
530
- return enabled.at(currentIndex - 1)?.id ?? enabled.at(-1)?.id;
531
- if (key === 'ArrowDown')
532
- return enabled[(currentIndex + 1) % enabled.length]?.id;
533
- return activeId;
534
- }
535
- export function getCascaderColumns(options, activePath = []) {
536
- const columns = [[...options]];
537
- let currentOptions = options;
538
- for (const activeId of activePath) {
539
- const active = currentOptions.find((option) => option.id === activeId);
540
- if (active?.children === undefined || active.children.length === 0)
541
- break;
542
- columns.push([...active.children]);
543
- currentOptions = active.children;
544
- }
545
- return columns;
546
- }
547
- export function findCascaderPathByValue(options, value) {
548
- if (value === undefined)
549
- return [];
550
- return findCascaderPath(options, (option) => option.value === value);
551
- }
552
- export function findCascaderPathById(options, id) {
553
- if (id === undefined)
554
- return [];
555
- return findCascaderPath(options, (option) => option.id === id);
556
- }
557
- export function getNextCascaderOptionId(options, activeId, key) {
558
- const enabled = options.filter((option) => option.disabled !== true);
559
- if (enabled.length === 0)
560
- return activeId;
561
- const currentIndex = Math.max(-1, enabled.findIndex((option) => option.id === activeId));
562
- if (key === 'Home')
563
- return enabled[0]?.id;
564
- if (key === 'End')
565
- return enabled.at(-1)?.id;
566
- if (key === 'ArrowUp')
567
- return enabled.at(currentIndex - 1)?.id ?? enabled.at(-1)?.id;
568
- if (key === 'ArrowDown')
569
- return enabled[(currentIndex + 1) % enabled.length]?.id;
570
- return activeId;
571
- }
572
- export function shouldOpenCascaderForKey(key) {
573
- return key === 'ArrowDown' || key === 'ArrowUp' || key === 'Enter' || key === ' ';
574
- }
575
- export function shouldSelectCascaderOption(key) {
576
- return key === 'Enter' || key === ' ';
577
- }
578
- export function shouldCloseCascaderForKey(key) {
579
- return key === 'Escape' || key === 'Tab';
580
- }
581
- export function shouldOpenComboboxForKey(key) {
582
- return key === 'ArrowDown' || key === 'ArrowUp';
583
- }
584
- export function shouldSelectComboboxOption(key) {
585
- return key === 'Enter';
586
- }
587
- export function shouldCloseComboboxForKey(key) {
588
- return key === 'Escape' || key === 'Tab';
589
- }
590
- function optionDomId(comboboxId, optionId) {
591
- return `${comboboxId}-${optionId}-option`;
592
- }
593
- function cascaderColumnDomId(cascaderId, columnIndex) {
594
- return `${cascaderId}-column-${columnIndex + 1}`;
595
- }
596
- function cascaderOptionDomId(cascaderId, optionId) {
597
- return `${cascaderId}-${optionId}-cascader-option`;
598
- }
599
- function uploadFileDomId(uploadId, fileId) {
600
- return `${uploadId}-${fileId}-file`;
601
- }
602
- function uploadTransportFileContract(file, options) {
603
- const state = file.state ?? 'done';
604
- const progress = normalizeProgress(file.progress, state);
605
- const retryCount = normalizeRetryCount(file.retryCount);
606
- const retryable = file.retryable === true || options.serverErrors.length > 0;
607
- const canRetry = !options.disabled &&
608
- state === 'error' &&
609
- retryable &&
610
- retryCount < options.maxRetries &&
611
- !options.retrying;
612
- return {
613
- id: file.id,
614
- state,
615
- progress,
616
- retryCount,
617
- retrying: options.retrying,
618
- retryable,
619
- canRetry,
620
- serverErrors: options.serverErrors,
621
- attrs: {
622
- 'data-file-id': file.id,
623
- 'data-state': options.retrying ? 'uploading' : state,
624
- 'data-progress': progress,
625
- 'data-retry-count': retryCount,
626
- 'data-retryable': retryable ? 'true' : 'false',
627
- 'data-can-retry': canRetry ? 'true' : 'false',
628
- 'data-server-error-count': options.serverErrors.length,
629
- 'data-server-errors': options.serverErrors.join('|'),
630
- 'aria-busy': options.retrying || state === 'uploading' ? 'true' : undefined,
631
- },
632
- };
633
- }
634
- function findCascaderPath(options, predicate) {
635
- for (const option of options) {
636
- if (predicate(option))
637
- return [option];
638
- const childPath = option.children === undefined ? [] : findCascaderPath(option.children, predicate);
639
- if (childPath.length > 0)
640
- return [option, ...childPath];
641
- }
642
- return [];
643
- }
644
- function flattenCascaderOptions(options) {
645
- return options.flatMap((option) => [
646
- option,
647
- ...(option.children === undefined ? [] : flattenCascaderOptions(option.children)),
648
- ]);
649
- }
650
- function normalizeCount(value) {
651
- if (value === undefined || !Number.isFinite(value))
652
- return undefined;
653
- return Math.max(0, Math.trunc(value));
654
- }
655
- function normalizeVirtualIndex(value, fallback, total) {
656
- const normalized = normalizeCount(value) ?? fallback;
657
- return Math.min(Math.max(normalized, 0), Math.max(total - 1, 0));
658
- }
659
- function normalizeCascaderIds(ids, knownIds) {
660
- if (ids === undefined)
661
- return [];
662
- const result = [];
663
- const seen = new Set();
664
- for (const id of ids) {
665
- if (!knownIds.has(id) || seen.has(id))
666
- continue;
667
- seen.add(id);
668
- result.push(id);
669
- }
670
- return result;
671
- }
672
- function cascaderRemoteState(options) {
673
- if (options.loading)
674
- return 'loading';
675
- if (options.error !== undefined && options.error.length > 0)
676
- return 'error';
677
- if (options.resultCount === 0)
678
- return 'empty';
679
- if (options.query.length > 0)
680
- return 'filtered';
681
- return 'ready';
682
- }
683
- function cascaderPersistenceState(options) {
684
- if (options.restored)
685
- return 'restored';
686
- if (options.dirty)
687
- return 'dirty';
688
- if (options.persistedValue === undefined)
689
- return 'unbound';
690
- return options.currentValue === options.persistedValue ? 'synced' : 'dirty';
691
- }
692
- function stateFromDateTimeField(options) {
693
- if (options.disabled === true)
694
- return 'disabled';
695
- if (options.readonly === true)
696
- return 'readonly';
697
- if (options.invalid === true)
698
- return 'invalid';
699
- return options.value === undefined || options.value === '' ? 'empty' : 'filled';
700
- }
701
- function stateFromUpload(options, fileCount) {
702
- if (options.disabled === true)
703
- return 'disabled';
704
- if (options.invalid === true)
705
- return 'invalid';
706
- if (options.dragActive === true)
707
- return 'drag-active';
708
- return fileCount === 0 ? 'empty' : 'filled';
709
- }
710
- function uploadTransportState(options) {
711
- if (options.disabled)
712
- return 'disabled';
713
- if (options.fileCount === 0)
714
- return 'empty';
715
- if (options.retryingCount > 0)
716
- return 'retrying';
717
- if (options.uploadingCount > 0)
718
- return 'uploading';
719
- if (options.errorCount > 0)
720
- return 'error';
721
- if (options.doneCount === options.fileCount)
722
- return 'complete';
723
- if (options.doneCount > 0)
724
- return 'partial';
725
- if (options.pendingCount > 0)
726
- return 'pending';
727
- return 'empty';
728
- }
729
- function normalizeProgress(value, state) {
730
- if (state === 'done')
731
- return 100;
732
- if (value === undefined || !Number.isFinite(value))
733
- return 0;
734
- return Math.min(100, Math.max(0, Math.trunc(value)));
735
- }
736
- function normalizeRetryCount(value) {
737
- if (value === undefined || !Number.isFinite(value))
738
- return 0;
739
- return Math.max(0, Math.trunc(value));
740
- }
741
- function normalizeIds(ids) {
742
- if (ids === undefined)
743
- return [];
744
- const values = Array.isArray(ids) ? ids : [...ids];
745
- return [...new Set(values.filter((id) => id.length > 0))];
746
- }
747
- function normalizeServerErrors(value, fallback) {
748
- const values = value === undefined
749
- ? fallback === undefined
750
- ? []
751
- : [fallback]
752
- : typeof value === 'string'
753
- ? [value]
754
- : value;
755
- return values.map((item) => item.trim()).filter((item) => item.length > 0);
756
- }
757
- function createValidDate(year, month, day) {
758
- const date = new Date(year, month, day);
759
- if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
760
- return undefined;
761
- }
762
- return date;
763
- }
764
- export function createColorPickerPlans(options) {
765
- return {
766
- root: createUiDomPlan('div', 'color-picker', {
767
- ...(options.className === undefined ? {} : { className: options.className }),
768
- attrs: { id: `${options.id}-colorpicker`, ...options.attrs },
769
- }),
770
- input: createUiDomPlan('input', 'color-picker-input', {
771
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
772
- attrs: {
773
- id: `${options.id}-input`,
774
- type: 'color',
775
- ...(options.value === undefined ? {} : { value: options.value }),
776
- },
777
- }),
778
- preview: createUiDomPlan('span', 'color-picker-preview', {
779
- attrs: {
780
- id: `${options.id}-preview`,
781
- 'aria-hidden': 'true',
782
- ...(options.value === undefined ? {} : { 'data-value': options.value }),
783
- },
784
- }),
785
- value: createUiDomPlan('span', 'color-picker-value', {
786
- attrs: {
787
- id: `${options.id}-value`,
788
- ...(options.value === undefined ? {} : { 'data-label': options.value }),
789
- },
790
- }),
791
- };
792
- }
793
- export function createTransferPlans(options) {
794
- return {
795
- root: createUiDomPlan('div', 'transfer', {
796
- ...(options.className === undefined ? {} : { className: options.className }),
797
- attrs: { id: `${options.id}-transfer`, ...options.attrs },
798
- }),
799
- source: createUiDomPlan('div', 'transfer-pane', {
800
- attrs: { id: `${options.id}-source` },
801
- }),
802
- sourceList: createUiDomPlan('ul', 'transfer-list', {
803
- attrs: { id: `${options.id}-source-list` },
804
- }),
805
- actions: createUiDomPlan('div', 'transfer-actions', {
806
- attrs: { id: `${options.id}-actions` },
807
- }),
808
- toRight: createUiDomPlan('button', 'transfer-action', {
809
- attrs: {
810
- id: `${options.id}-to-right`,
811
- type: 'button',
812
- 'data-direction': 'right',
813
- 'aria-label': 'Move to target',
814
- },
815
- }),
816
- toLeft: createUiDomPlan('button', 'transfer-action', {
817
- attrs: {
818
- id: `${options.id}-to-left`,
819
- type: 'button',
820
- 'data-direction': 'left',
821
- 'aria-label': 'Move to source',
822
- },
823
- }),
824
- target: createUiDomPlan('div', 'transfer-pane', {
825
- attrs: { id: `${options.id}-target` },
826
- }),
827
- targetList: createUiDomPlan('ul', 'transfer-list', {
828
- attrs: { id: `${options.id}-target-list` },
829
- }),
830
- };
831
- }
832
- const MASK_PRESETS = {
833
- amount: '###,###.##',
834
- date: '####-##-##',
835
- phone: '###-####-####',
836
- };
837
- export function resolveMaskInputMask(kind, mask) {
838
- if (mask !== undefined && mask !== '')
839
- return mask;
840
- return (kind === undefined ? 'phone' : kind === 'custom' ? '' : MASK_PRESETS[kind]) ?? '';
841
- }
842
- export function createMaskInputPlans(options) {
843
- const kind = options.kind ?? 'phone';
844
- const mask = resolveMaskInputMask(kind, options.mask);
845
- return {
846
- root: createUiDomPlan('div', 'mask-input', {
847
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
848
- ...(options.className === undefined ? {} : { className: options.className }),
849
- attrs: { id: `${options.id}-mask-input`, ...options.attrs },
850
- }),
851
- input: createUiDomPlan('input', 'mask-input-field', {
852
- ...(options.disabled === undefined ? {} : { disabled: options.disabled }),
853
- ...(options.readonly === undefined ? {} : { readonly: options.readonly }),
854
- ...(options.invalid === undefined ? {} : { invalid: options.invalid }),
855
- attrs: {
856
- id: `${options.id}-input`,
857
- type: 'text',
858
- inputmode: kind === 'phone' || kind === 'date' ? 'numeric' : 'decimal',
859
- 'data-kind': kind,
860
- ...(mask === '' ? {} : { 'data-mask': mask }),
861
- ...(options.value === undefined ? {} : { value: options.value }),
862
- ...(options.placeholder === undefined ? {} : { placeholder: options.placeholder ?? mask }),
863
- },
864
- }),
865
- };
866
- }