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