@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
package/dist/overlay.js DELETED
@@ -1,969 +0,0 @@
1
- /** @license MIT
2
- * Copyright (c) 2026 vobsjs
3
- * @vobs/ui
4
- */
5
- import { classNames, createButtonPrimitive, createUiDomPlan, } from './primitives.js';
6
- export function createOverlayStack(options) {
7
- const entries = [];
8
- const portalTarget = options.portalTarget;
9
- const scrollTarget = options.scrollLockTarget ?? options.document.body;
10
- let scrollLockCount = 0;
11
- let previousOverflow = '';
12
- const onKeyDown = (event) => {
13
- const top = entries.at(-1);
14
- if (top === undefined)
15
- return;
16
- if (event.key === 'Escape' && top.closeOnEscape) {
17
- event.preventDefault();
18
- top.handle.close('escape');
19
- return;
20
- }
21
- if (event.key === 'Tab' && top.modal && top.element !== undefined) {
22
- trapFocus(event, top.element);
23
- }
24
- };
25
- const onPointerDown = (event) => {
26
- const top = entries.at(-1);
27
- const target = event.target;
28
- if (top === undefined ||
29
- !top.closeOnOutside ||
30
- top.element === undefined ||
31
- !isNode(target) ||
32
- top.element.contains(target)) {
33
- return;
34
- }
35
- event.preventDefault();
36
- top.handle.close('outside');
37
- };
38
- options.document.addEventListener('keydown', onKeyDown);
39
- options.document.addEventListener('pointerdown', onPointerDown);
40
- const stack = {
41
- get size() {
42
- return entries.length;
43
- },
44
- get top() {
45
- return entries.at(-1)?.handle;
46
- },
47
- open(openOptions) {
48
- const previouslyFocused = activeHtmlElement(options.document);
49
- const modal = openOptions.modal ?? (openOptions.kind === undefined || openOptions.kind === 'dialog');
50
- const entry = createEntry(openOptions, modal, entries.length + 1, previouslyFocused);
51
- entries.push(entry);
52
- if (portalTarget !== undefined && entry.element !== undefined)
53
- portalTarget.append(entry.element);
54
- if (modal)
55
- lockScroll();
56
- focusInitialElement(openOptions.initialFocus, entry.element);
57
- return entry.handle;
58
- },
59
- destroy() {
60
- for (const entry of [...entries].reverse())
61
- entry.handle.destroy();
62
- options.document.removeEventListener('keydown', onKeyDown);
63
- options.document.removeEventListener('pointerdown', onPointerDown);
64
- },
65
- };
66
- function createEntry(openOptions, modal, layer, previouslyFocused) {
67
- const plans = openOptions.kind === undefined || openOptions.kind === 'dialog'
68
- ? createDialogOverlayPlans({
69
- id: openOptions.id,
70
- titleId: `${openOptions.id}-title`,
71
- descriptionId: `${openOptions.id}-description`,
72
- modal,
73
- })
74
- : createPositionedOverlayPlans({
75
- id: openOptions.id,
76
- kind: openOptions.kind,
77
- });
78
- let active = true;
79
- const handle = {
80
- id: openOptions.id,
81
- kind: openOptions.kind ?? 'dialog',
82
- modal,
83
- layer,
84
- plans,
85
- ...(openOptions.element === undefined ? {} : { element: openOptions.element }),
86
- close(reason = 'programmatic') {
87
- if (!active)
88
- return;
89
- openOptions.onClose?.(reason);
90
- handle.destroy();
91
- },
92
- destroy() {
93
- if (!active)
94
- return;
95
- active = false;
96
- const index = entries.indexOf(entry);
97
- if (index >= 0)
98
- entries.splice(index, 1);
99
- if (modal)
100
- unlockScroll();
101
- openOptions.element?.remove();
102
- if (openOptions.restoreFocus !== false)
103
- previouslyFocused?.focus();
104
- },
105
- };
106
- const entry = {
107
- handle,
108
- ...(openOptions.element === undefined ? {} : { element: openOptions.element }),
109
- closeOnEscape: openOptions.closeOnEscape ?? true,
110
- closeOnOutside: openOptions.closeOnOutside ?? true,
111
- modal,
112
- };
113
- return entry;
114
- }
115
- function lockScroll() {
116
- scrollLockCount += 1;
117
- if (scrollLockCount !== 1)
118
- return;
119
- previousOverflow = scrollTarget.style.overflow;
120
- scrollTarget.style.overflow = 'hidden';
121
- }
122
- function unlockScroll() {
123
- if (scrollLockCount === 0)
124
- return;
125
- scrollLockCount -= 1;
126
- if (scrollLockCount === 0)
127
- scrollTarget.style.overflow = previousOverflow;
128
- }
129
- return stack;
130
- }
131
- export function createDialogOverlayPlans(options) {
132
- const modal = options.modal ?? true;
133
- return {
134
- root: createUiDomPlan('div', 'overlay-root', {
135
- state: 'open',
136
- attrs: { 'data-overlay-root': options.id },
137
- }),
138
- ...(modal
139
- ? {
140
- backdrop: createUiDomPlan('div', 'overlay-backdrop', {
141
- state: 'open',
142
- attrs: { 'data-overlay-backdrop': options.id },
143
- }),
144
- }
145
- : {}),
146
- surface: createUiDomPlan('div', 'dialog', {
147
- state: 'open',
148
- attrs: {
149
- id: options.id,
150
- role: 'dialog',
151
- 'aria-modal': modal ? 'true' : undefined,
152
- 'aria-labelledby': options.titleId,
153
- 'aria-describedby': options.descriptionId,
154
- tabindex: -1,
155
- },
156
- }),
157
- };
158
- }
159
- export function createModalOverlayPlans(options) {
160
- return createDialogOverlayPlans({ ...options, modal: true });
161
- }
162
- export function createPositionedOverlayPlans(options) {
163
- const attrs = {
164
- id: options.id,
165
- role: overlayRole(options.kind),
166
- 'data-placement': options.placement ?? 'bottom-start',
167
- tabindex: -1,
168
- };
169
- return {
170
- root: createUiDomPlan('div', 'overlay-root', {
171
- state: 'open',
172
- attrs: { 'data-overlay-root': options.id },
173
- }),
174
- surface: createUiDomPlan('div', options.kind, {
175
- state: 'open',
176
- attrs,
177
- }),
178
- };
179
- }
180
- export function createDialogPlans(options) {
181
- const overlay = createDialogOverlayPlans(options);
182
- const actions = (options.actions ?? []).map((action) => createButtonPrimitive({
183
- label: action.label,
184
- variant: action.tone === 'primary' ? 'solid' : 'soft',
185
- disabled: action.disabled === true,
186
- className: classNames('kui-dialog-action', action.tone === undefined ? undefined : `kui-dialog-action--${action.tone}`),
187
- attrs: {
188
- id: `${options.id}-${action.id}-action`,
189
- 'data-action-id': action.id,
190
- 'data-tone': action.tone ?? 'default',
191
- },
192
- }));
193
- return {
194
- ...overlay,
195
- surface: {
196
- ...overlay.surface,
197
- attrs: {
198
- ...overlay.surface.attrs,
199
- 'data-size': options.size ?? 'md',
200
- 'data-tone': options.tone ?? 'default',
201
- },
202
- },
203
- header: createUiDomPlan('header', 'dialog-header', {
204
- attrs: { 'data-dialog-section': 'header' },
205
- }),
206
- title: createUiDomPlan('h2', 'dialog-title', {
207
- attrs: { id: options.titleId },
208
- }),
209
- ...(options.descriptionId === undefined
210
- ? {}
211
- : {
212
- description: createUiDomPlan('p', 'dialog-description', {
213
- attrs: { id: options.descriptionId },
214
- }),
215
- }),
216
- ...(options.closeLabel === undefined
217
- ? {}
218
- : {
219
- closeButton: createButtonPrimitive({
220
- label: options.closeLabel,
221
- variant: 'ghost',
222
- iconOnly: true,
223
- className: 'kui-dialog-close',
224
- attrs: { id: `${options.id}-close`, 'data-dialog-close': '' },
225
- }),
226
- }),
227
- body: createUiDomPlan('div', 'dialog-body', {
228
- attrs: { 'data-dialog-section': 'body' },
229
- }),
230
- ...(actions.length === 0
231
- ? {}
232
- : {
233
- footer: createUiDomPlan('footer', 'dialog-footer', {
234
- attrs: { 'data-dialog-section': 'footer' },
235
- }),
236
- }),
237
- actions,
238
- };
239
- }
240
- export function createOverlayTriggerPlan(options) {
241
- const open = options.open === true;
242
- return createButtonPrimitive({
243
- label: options.label,
244
- disabled: options.disabled === true,
245
- variant: 'soft',
246
- className: classNames('kui-overlay-trigger', options.kind === undefined ? undefined : `kui-${options.kind}-trigger`),
247
- attrs: {
248
- id: options.id,
249
- 'aria-haspopup': triggerPopupKind(options.kind),
250
- 'aria-expanded': options.kind === 'tooltip' ? undefined : open ? 'true' : 'false',
251
- 'aria-controls': open && options.kind !== 'tooltip' ? options.controlsId : undefined,
252
- 'aria-describedby': options.kind === 'tooltip' && open ? options.controlsId : undefined,
253
- 'data-overlay-trigger': options.controlsId,
254
- },
255
- });
256
- }
257
- export function createTooltipPlans(options) {
258
- const overlay = createPositionedOverlayPlans({
259
- id: options.id,
260
- kind: 'tooltip',
261
- placement: options.placement ?? 'bottom-start',
262
- });
263
- return {
264
- ...overlay,
265
- root: rootWithOpenState(overlay.root, options.open),
266
- surface: {
267
- ...surfaceWithOpenState(overlay.surface, options.open),
268
- attrs: {
269
- ...overlay.surface.attrs,
270
- 'data-state': options.open === true ? 'open' : 'closed',
271
- 'data-strategy': options.strategy ?? 'hover',
272
- 'data-delay-ms': options.delayMs ?? 400,
273
- hidden: options.open === true ? undefined : true,
274
- },
275
- },
276
- trigger: createOverlayTriggerPlan({
277
- id: options.triggerId,
278
- controlsId: options.id,
279
- label: options.label,
280
- open: options.open === true,
281
- disabled: options.disabled === true,
282
- kind: 'tooltip',
283
- }),
284
- arrow: createOverlayArrowPlan(options.placement),
285
- };
286
- }
287
- export function createPopoverPlans(options) {
288
- const overlay = createPositionedOverlayPlans({
289
- id: options.id,
290
- kind: 'popover',
291
- placement: options.placement ?? 'bottom-start',
292
- });
293
- return {
294
- ...overlay,
295
- root: rootWithOpenState(overlay.root, options.open),
296
- surface: {
297
- ...surfaceWithOpenState(overlay.surface, options.open),
298
- attrs: {
299
- ...overlay.surface.attrs,
300
- 'data-state': options.open === true ? 'open' : 'closed',
301
- 'aria-labelledby': options.titleId,
302
- 'aria-describedby': options.descriptionId,
303
- hidden: options.open === true ? undefined : true,
304
- },
305
- },
306
- trigger: createOverlayTriggerPlan({
307
- id: options.triggerId,
308
- controlsId: options.id,
309
- label: options.label,
310
- open: options.open === true,
311
- kind: 'popover',
312
- }),
313
- arrow: createOverlayArrowPlan(options.placement),
314
- ...(options.titleId === undefined
315
- ? {}
316
- : {
317
- header: createUiDomPlan('header', 'popover-header'),
318
- title: createUiDomPlan('h3', 'popover-title', { attrs: { id: options.titleId } }),
319
- }),
320
- body: createUiDomPlan('div', 'popover-body'),
321
- ...(options.descriptionId === undefined
322
- ? {}
323
- : {
324
- footer: createUiDomPlan('footer', 'popover-footer', {
325
- attrs: { id: options.descriptionId },
326
- }),
327
- }),
328
- };
329
- }
330
- export function createDropdownMenuPlans(options) {
331
- const overlay = createPositionedOverlayPlans({
332
- id: options.id,
333
- kind: 'dropdown',
334
- placement: options.placement ?? 'bottom-start',
335
- });
336
- const activeId = activeDropdownItemId(options.items, options.activeId);
337
- const menu = dropdownMenuPlan(overlay.surface, options.open, 'dropdown');
338
- return {
339
- ...overlay,
340
- root: rootWithOpenState(overlay.root, options.open),
341
- surface: menu,
342
- trigger: createOverlayTriggerPlan({
343
- id: options.triggerId,
344
- controlsId: options.id,
345
- label: options.label,
346
- open: options.open === true,
347
- kind: 'dropdown',
348
- }),
349
- menu,
350
- items: options.items.map((item) => dropdownItemPlan(options.id, item, activeId)),
351
- shortcuts: options.items.map((item) => dropdownShortcutPlan(options.id, item)),
352
- };
353
- }
354
- export function createContextMenuPlans(options) {
355
- const overlay = createPositionedOverlayPlans({
356
- id: options.id,
357
- kind: 'context-menu',
358
- placement: options.placement ?? 'bottom-start',
359
- });
360
- const activeId = activeDropdownItemId(options.items, options.activeId);
361
- const style = options.x === undefined || options.y === undefined
362
- ? undefined
363
- : `--kui-context-menu-x: ${options.x}px; --kui-context-menu-y: ${options.y}px;`;
364
- const menu = dropdownMenuPlan({
365
- ...overlay.surface,
366
- className: `${overlay.surface.className} kui-context-menu`,
367
- attrs: { ...overlay.surface.attrs, style },
368
- }, options.open, 'context-menu');
369
- return {
370
- ...overlay,
371
- root: rootWithOpenState(overlay.root, options.open),
372
- surface: menu,
373
- menu,
374
- items: options.items.map((item) => dropdownItemPlan(options.id, item, activeId)),
375
- shortcuts: options.items.map((item) => dropdownShortcutPlan(options.id, item)),
376
- };
377
- }
378
- export function createDropdownMenuPermissionRemoteActionContract(options) {
379
- const disabled = options.disabled === true;
380
- const knownIds = new Set(options.items.map((item) => item.id));
381
- const allowedInputIds = normalizeOverlayMenuIds(options.allowedIds, knownIds);
382
- const deniedInputIds = normalizeOverlayMenuIds(options.deniedIds, knownIds);
383
- const remoteActionInputIds = normalizeOverlayMenuIds(options.remoteActionIds, knownIds);
384
- const remoteActionDisabledInputIds = normalizeOverlayMenuIds(options.remoteActionDisabledIds, knownIds);
385
- const remoteActionLoadingInputIds = normalizeOverlayMenuIds(options.remoteActionLoadingIds, knownIds);
386
- const submenuLoadingInputIds = normalizeOverlayMenuIds(options.submenuLoadingIds, knownIds);
387
- const allowedSet = new Set(allowedInputIds);
388
- const deniedSet = new Set(deniedInputIds);
389
- const remoteActionSet = new Set(remoteActionInputIds);
390
- const remoteActionDisabledSet = new Set(remoteActionDisabledInputIds);
391
- const remoteActionLoadingSet = new Set(remoteActionLoadingInputIds);
392
- const submenuLoadingSet = new Set(submenuLoadingInputIds);
393
- const hasAllowedIds = options.allowedIds !== undefined;
394
- const items = options.items.map((item) => dropdownPermissionRemoteActionItemContract(item, {
395
- allowed: allowedSet.has(item.id),
396
- denied: deniedSet.has(item.id),
397
- disabled,
398
- disabledReason: normalizedOverlayMenuReason(options.disabledReasons?.[item.id]),
399
- hasAllowedIds,
400
- remoteAction: remoteActionSet.has(item.id),
401
- remoteActionDisabled: remoteActionDisabledSet.has(item.id),
402
- remoteActionDisabledReason: normalizedOverlayMenuReason(options.remoteActionDisabledReasons?.[item.id]),
403
- remoteActionError: normalizedOverlayMenuReason(options.remoteActionErrors?.[item.id]),
404
- remoteActionLoading: remoteActionLoadingSet.has(item.id),
405
- submenuError: normalizedOverlayMenuReason(options.submenuErrors?.[item.id]),
406
- submenuItemCount: normalizedOverlayMenuItemCount(options.submenuItemCounts?.[item.id]),
407
- submenuLoading: submenuLoadingSet.has(item.id),
408
- }));
409
- const allowedIds = items.filter((item) => item.permission === 'allowed').map((item) => item.id);
410
- const deniedIds = items.filter((item) => item.permission === 'denied').map((item) => item.id);
411
- const remoteActionIds = items.filter((item) => item.remoteAction).map((item) => item.id);
412
- const remoteActionDisabledIds = items
413
- .filter((item) => item.remoteAction && item.remoteActionState === 'disabled')
414
- .map((item) => item.id);
415
- const remoteActionLoadingIds = items
416
- .filter((item) => item.remoteAction && item.remoteActionState === 'pending')
417
- .map((item) => item.id);
418
- const remoteActionErrorIds = items
419
- .filter((item) => item.remoteActionError !== '')
420
- .map((item) => item.id);
421
- const submenuLoadingIds = items
422
- .filter((item) => item.submenuState === 'loading')
423
- .map((item) => item.id);
424
- const submenuErrorIds = items
425
- .filter((item) => item.submenuState === 'error')
426
- .map((item) => item.id);
427
- const disabledReasonCount = items.filter((item) => item.disabledReason !== '').length;
428
- const state = dropdownMenuPermissionRemoteActionState({
429
- deniedCount: deniedIds.length,
430
- itemCount: options.items.length,
431
- remoteActionLoadingCount: remoteActionLoadingIds.length,
432
- submenuErrorCount: submenuErrorIds.length,
433
- });
434
- return {
435
- state,
436
- disabled,
437
- itemCount: options.items.length,
438
- allowedCount: allowedIds.length,
439
- deniedCount: deniedIds.length,
440
- disabledReasonCount,
441
- remoteActionCount: remoteActionIds.length,
442
- remoteActionDisabledCount: remoteActionDisabledIds.length,
443
- remoteActionLoadingCount: remoteActionLoadingIds.length,
444
- remoteActionErrorCount: remoteActionErrorIds.length,
445
- submenuLoadingCount: submenuLoadingIds.length,
446
- submenuErrorCount: submenuErrorIds.length,
447
- allowedIds,
448
- deniedIds,
449
- remoteActionIds,
450
- remoteActionDisabledIds,
451
- remoteActionLoadingIds,
452
- remoteActionErrorIds,
453
- submenuLoadingIds,
454
- submenuErrorIds,
455
- items,
456
- attrs: {
457
- 'data-dropdown-permission-remote-contract': 'true',
458
- 'data-dropdown-permission-remote-state': state,
459
- 'data-state': state,
460
- 'data-disabled': disabled ? 'true' : undefined,
461
- 'data-item-count': options.items.length,
462
- 'data-allowed-count': allowedIds.length,
463
- 'data-denied-count': deniedIds.length,
464
- 'data-disabled-reason-count': disabledReasonCount,
465
- 'data-remote-action-count': remoteActionIds.length,
466
- 'data-remote-action-disabled-count': remoteActionDisabledIds.length,
467
- 'data-remote-action-loading-count': remoteActionLoadingIds.length,
468
- 'data-remote-action-error-count': remoteActionErrorIds.length,
469
- 'data-submenu-loading-count': submenuLoadingIds.length,
470
- 'data-submenu-error-count': submenuErrorIds.length,
471
- 'data-allowed-ids': allowedIds.join(','),
472
- 'data-denied-ids': deniedIds.join(','),
473
- 'data-remote-action-ids': remoteActionIds.join(','),
474
- 'data-remote-action-disabled-ids': remoteActionDisabledIds.join(','),
475
- 'data-remote-action-loading-ids': remoteActionLoadingIds.join(','),
476
- 'data-remote-action-error-ids': remoteActionErrorIds.join(','),
477
- 'data-submenu-loading-ids': submenuLoadingIds.join(','),
478
- 'data-submenu-error-ids': submenuErrorIds.join(','),
479
- 'aria-busy': remoteActionLoadingIds.length > 0 || submenuLoadingIds.length > 0 ? 'true' : undefined,
480
- },
481
- };
482
- }
483
- export function createDropdownMenuSubmenuPortalStateContract(options) {
484
- const submenuIds = new Set(options.items.filter((item) => item.kind === 'submenu').map((item) => item.id));
485
- const openPathIds = normalizeOverlayMenuIds(options.openPathIds, submenuIds);
486
- const mountedInputIds = normalizeOverlayMenuIds(options.mountedIds, submenuIds);
487
- const openSet = new Set(openPathIds);
488
- const mountedSet = new Set(mountedInputIds);
489
- const items = options.items
490
- .filter((item) => item.kind === 'submenu')
491
- .map((item) => dropdownMenuSubmenuPortalItemContract(item, {
492
- focusReturnId: normalizedOverlayMenuReason(options.focusReturnIds?.[item.id]),
493
- mounted: mountedSet.has(item.id),
494
- openPathIndex: openPathIds.indexOf(item.id),
495
- parentOverlayId: normalizedOverlayMenuReason(options.parentOverlayIds?.[item.id]),
496
- placement: options.placements?.[item.id],
497
- portalTargetId: normalizedOverlayMenuReason(options.portalTargetIds?.[item.id]),
498
- open: openSet.has(item.id),
499
- }));
500
- const mountedIds = items.filter((item) => item.mounted).map((item) => item.id);
501
- const portalMissingIds = items
502
- .filter((item) => item.state === 'portal-missing')
503
- .map((item) => item.id);
504
- const portalTargetIds = items
505
- .map((item) => item.portalTargetId)
506
- .filter((id, index, ids) => id !== '' && ids.indexOf(id) === index);
507
- const state = dropdownMenuSubmenuPortalContractState({
508
- mountedCount: mountedIds.length,
509
- openCount: openPathIds.length,
510
- portalMissingCount: portalMissingIds.length,
511
- submenuCount: items.length,
512
- });
513
- return {
514
- state,
515
- submenuCount: items.length,
516
- openCount: openPathIds.length,
517
- mountedCount: mountedIds.length,
518
- portalMissingCount: portalMissingIds.length,
519
- openPathIds,
520
- mountedIds,
521
- portalMissingIds,
522
- portalTargetIds,
523
- items,
524
- attrs: {
525
- 'data-dropdown-submenu-portal-contract': 'true',
526
- 'data-dropdown-submenu-portal-state': state,
527
- 'data-submenu-count': items.length,
528
- 'data-open-submenu-count': openPathIds.length,
529
- 'data-mounted-submenu-count': mountedIds.length,
530
- 'data-portal-missing-count': portalMissingIds.length,
531
- 'data-open-path-ids': openPathIds.join(','),
532
- 'data-mounted-submenu-ids': mountedIds.join(','),
533
- 'data-portal-missing-ids': portalMissingIds.join(','),
534
- 'data-portal-target-ids': portalTargetIds.join(','),
535
- },
536
- };
537
- }
538
- export function createPopconfirmPlans(options) {
539
- const overlay = createPositionedOverlayPlans({
540
- id: options.id,
541
- kind: 'popover',
542
- placement: options.placement ?? 'bottom-start',
543
- });
544
- return {
545
- ...overlay,
546
- root: rootWithOpenState(overlay.root, options.open),
547
- surface: {
548
- ...surfaceWithOpenState(overlay.surface, options.open),
549
- className: `${overlay.surface.className} kui-popconfirm`,
550
- attrs: {
551
- ...overlay.surface.attrs,
552
- 'data-state': options.open === true ? 'open' : 'closed',
553
- role: 'dialog',
554
- 'aria-labelledby': options.titleId,
555
- 'aria-describedby': options.descriptionId,
556
- 'data-tone': options.tone ?? 'danger',
557
- hidden: options.open === true ? undefined : true,
558
- },
559
- },
560
- trigger: createOverlayTriggerPlan({
561
- id: options.triggerId,
562
- controlsId: options.id,
563
- label: options.label,
564
- open: options.open === true,
565
- kind: 'popover',
566
- }),
567
- arrow: createOverlayArrowPlan(options.placement),
568
- title: createUiDomPlan('h3', 'popconfirm-title', { attrs: { id: options.titleId } }),
569
- ...(options.descriptionId === undefined
570
- ? {}
571
- : {
572
- description: createUiDomPlan('p', 'popconfirm-description', {
573
- attrs: { id: options.descriptionId },
574
- }),
575
- }),
576
- footer: createUiDomPlan('footer', 'popconfirm-footer'),
577
- cancelButton: createButtonPrimitive({
578
- label: options.cancelLabel ?? 'Cancel',
579
- variant: 'ghost',
580
- size: 'sm',
581
- attrs: { 'data-popconfirm-action': 'cancel' },
582
- }),
583
- confirmButton: createButtonPrimitive({
584
- label: options.confirmLabel ?? 'Confirm',
585
- variant: options.tone === 'primary' ? 'solid' : 'soft',
586
- size: 'sm',
587
- className: classNames('kui-popconfirm-confirm', `kui-popconfirm-confirm--${options.tone ?? 'danger'}`),
588
- attrs: { 'data-popconfirm-action': 'confirm', 'data-tone': options.tone ?? 'danger' },
589
- }),
590
- };
591
- }
592
- export function getNextDropdownItemId(items, activeId, key) {
593
- const enabled = dropdownFocusableItems(items);
594
- if (enabled.length === 0)
595
- return activeId;
596
- const currentIndex = Math.max(-1, enabled.findIndex((item) => item.id === activeId));
597
- if (key === 'Home')
598
- return enabled[0]?.id;
599
- if (key === 'End')
600
- return enabled.at(-1)?.id;
601
- if (key === 'ArrowUp')
602
- return enabled.at(currentIndex - 1)?.id ?? enabled.at(-1)?.id;
603
- if (key === 'ArrowDown')
604
- return enabled[(currentIndex + 1) % enabled.length]?.id;
605
- return activeId;
606
- }
607
- export function shouldOpenOverlayForTriggerKey(key) {
608
- return key === 'Enter' || key === ' ' || key === 'ArrowDown' || key === 'ArrowUp';
609
- }
610
- export function shouldCloseOverlayForKey(key) {
611
- return key === 'Escape';
612
- }
613
- export function shouldSelectDropdownItem(key) {
614
- return key === 'Enter' || key === ' ';
615
- }
616
- function activeHtmlElement(document) {
617
- const active = document.activeElement;
618
- return isHTMLElement(active) ? active : undefined;
619
- }
620
- function focusInitialElement(initialFocus, element) {
621
- if (initialFocus !== undefined) {
622
- initialFocus.focus();
623
- return;
624
- }
625
- const target = element === undefined ? undefined : (firstFocusable(element) ?? element);
626
- target?.focus();
627
- }
628
- function trapFocus(event, root) {
629
- const focusable = findFocusable(root);
630
- if (focusable.length === 0) {
631
- event.preventDefault();
632
- root.focus();
633
- return;
634
- }
635
- const current = root.ownerDocument.activeElement;
636
- const currentIndex = focusable.findIndex((item) => item === current);
637
- const nextIndex = event.shiftKey
638
- ? currentIndex <= 0
639
- ? focusable.length - 1
640
- : currentIndex - 1
641
- : currentIndex === focusable.length - 1
642
- ? 0
643
- : currentIndex + 1;
644
- if (currentIndex < 0 || nextIndex !== currentIndex + (event.shiftKey ? -1 : 1)) {
645
- event.preventDefault();
646
- focusable[nextIndex]?.focus();
647
- }
648
- }
649
- function firstFocusable(root) {
650
- return findFocusable(root)[0];
651
- }
652
- function findFocusable(root) {
653
- return Array.from(root.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((element) => !element.hasAttribute('disabled') && element.tabIndex >= 0);
654
- }
655
- function createOverlayArrowPlan(placement = 'bottom-start') {
656
- return createUiDomPlan('span', 'overlay-arrow', {
657
- attrs: { 'aria-hidden': 'true', 'data-placement': placement },
658
- });
659
- }
660
- function rootWithOpenState(plan, open = false) {
661
- return {
662
- ...plan,
663
- attrs: { ...plan.attrs, 'data-state': open ? 'open' : 'closed' },
664
- };
665
- }
666
- function surfaceWithOpenState(plan, open = false) {
667
- return {
668
- ...plan,
669
- attrs: { ...plan.attrs, 'data-state': open ? 'open' : 'closed' },
670
- };
671
- }
672
- function triggerPopupKind(kind) {
673
- if (kind === 'tooltip')
674
- return undefined;
675
- if (kind === 'dropdown' || kind === 'context-menu')
676
- return 'menu';
677
- return 'dialog';
678
- }
679
- function dropdownMenuPlan(surface, open, kind) {
680
- return {
681
- ...surfaceWithOpenState(surface, open),
682
- attrs: {
683
- ...surface.attrs,
684
- 'data-state': open === true ? 'open' : 'closed',
685
- role: 'menu',
686
- 'data-menu-kind': kind,
687
- hidden: open === true ? undefined : true,
688
- },
689
- };
690
- }
691
- function dropdownItemPlan(rootId, item, activeId) {
692
- const kind = item.kind ?? 'item';
693
- const active = item.id === activeId;
694
- const separator = kind === 'separator';
695
- const remoteActionState = dropdownItemRemoteActionState(item);
696
- const submenuState = dropdownItemSubmenuState(item);
697
- return createUiDomPlan('div', 'dropdown-item', {
698
- state: separator || item.disabled === true
699
- ? 'disabled'
700
- : active
701
- ? 'active'
702
- : item.checked === true
703
- ? 'checked'
704
- : 'idle',
705
- ...(item.disabled === true || separator ? { disabled: true } : {}),
706
- className: classNames(item.danger === true ? 'kui-dropdown-item--danger' : undefined),
707
- attrs: {
708
- id: `${rootId}-${item.id}-item`,
709
- role: separator ? 'separator' : menuItemRole(kind),
710
- tabindex: active && !separator && item.disabled !== true ? 0 : -1,
711
- 'aria-disabled': item.disabled === true ? 'true' : undefined,
712
- 'aria-haspopup': kind === 'submenu' ? 'menu' : undefined,
713
- 'aria-expanded': kind === 'submenu' ? (item.expanded === true ? 'true' : 'false') : undefined,
714
- 'aria-busy': item.remoteActionLoading === true || item.submenuLoading === true ? 'true' : undefined,
715
- 'aria-checked': kind === 'checkbox' || kind === 'radio' ? String(item.checked === true) : undefined,
716
- 'data-item-id': item.id,
717
- 'data-kind': kind,
718
- 'data-label': item.label,
719
- 'data-group-id': item.groupId,
720
- 'data-danger': item.danger === true ? '' : undefined,
721
- 'data-shortcut': item.shortcut,
722
- 'data-permission': item.permission,
723
- 'data-disabled-reason': item.disabledReason,
724
- 'data-remote-action': item.remoteAction === true ? 'true' : undefined,
725
- 'data-remote-action-state': remoteActionState,
726
- 'data-remote-action-disabled-reason': item.remoteActionDisabledReason,
727
- 'data-remote-action-error': item.remoteActionError,
728
- 'data-can-invoke-remote-action': remoteActionState === 'available' && item.disabled !== true ? 'true' : undefined,
729
- 'data-submenu-state': submenuState,
730
- 'data-submenu-error': item.submenuError,
731
- 'data-submenu-item-count': item.submenuItemCount,
732
- 'data-submenu-portal-state': item.submenuPortalState,
733
- 'data-submenu-portal-target': item.submenuPortalTargetId,
734
- 'data-submenu-open-path-index': item.submenuOpenPathIndex,
735
- 'data-submenu-focus-return': item.submenuFocusReturnId,
736
- 'data-submenu-parent-overlay': item.submenuParentOverlayId,
737
- 'data-submenu-placement': item.submenuPlacement,
738
- 'aria-controls': item.submenuPortalTargetId,
739
- },
740
- });
741
- }
742
- function dropdownShortcutPlan(rootId, item) {
743
- return createUiDomPlan('kbd', 'dropdown-shortcut', {
744
- attrs: {
745
- id: `${rootId}-${item.id}-shortcut`,
746
- 'aria-hidden': 'true',
747
- 'data-shortcut': item.shortcut,
748
- },
749
- });
750
- }
751
- function activeDropdownItemId(items, activeId) {
752
- const enabled = dropdownFocusableItems(items);
753
- return enabled.some((item) => item.id === activeId) ? activeId : enabled[0]?.id;
754
- }
755
- function dropdownFocusableItems(items) {
756
- return items.filter((item) => item.disabled !== true && item.kind !== 'separator');
757
- }
758
- function menuItemRole(kind) {
759
- if (kind === 'checkbox')
760
- return 'menuitemcheckbox';
761
- if (kind === 'radio')
762
- return 'menuitemradio';
763
- return 'menuitem';
764
- }
765
- function dropdownMenuSubmenuPortalItemContract(item, options) {
766
- const mounted = options.mounted || options.open;
767
- const state = options.open && options.portalTargetId === ''
768
- ? 'portal-missing'
769
- : options.open
770
- ? 'open'
771
- : mounted
772
- ? 'mounted'
773
- : 'closed';
774
- const openPathIndex = options.openPathIndex < 0 ? undefined : options.openPathIndex;
775
- const contractItem = {
776
- ...item,
777
- ...(options.open ? { expanded: true } : {}),
778
- submenuPortalState: state,
779
- ...(options.portalTargetId === '' ? {} : { submenuPortalTargetId: options.portalTargetId }),
780
- ...(openPathIndex === undefined ? {} : { submenuOpenPathIndex: openPathIndex }),
781
- ...(options.focusReturnId === '' ? {} : { submenuFocusReturnId: options.focusReturnId }),
782
- ...(options.parentOverlayId === '' ? {} : { submenuParentOverlayId: options.parentOverlayId }),
783
- ...(options.placement === undefined ? {} : { submenuPlacement: options.placement }),
784
- };
785
- return {
786
- id: item.id,
787
- item: contractItem,
788
- state,
789
- open: options.open,
790
- mounted,
791
- portalTargetId: options.portalTargetId,
792
- focusReturnId: options.focusReturnId,
793
- parentOverlayId: options.parentOverlayId,
794
- placement: options.placement,
795
- openPathIndex,
796
- attrs: {
797
- 'data-item-id': item.id,
798
- 'data-submenu-portal-state': state,
799
- 'data-submenu-portal-target': options.portalTargetId,
800
- 'data-submenu-open-path-index': openPathIndex,
801
- 'data-submenu-focus-return': options.focusReturnId,
802
- 'data-submenu-parent-overlay': options.parentOverlayId,
803
- 'data-submenu-placement': options.placement,
804
- 'aria-controls': options.portalTargetId === '' ? undefined : options.portalTargetId,
805
- 'aria-expanded': options.open ? 'true' : item.expanded === true ? 'true' : 'false',
806
- },
807
- };
808
- }
809
- function dropdownPermissionRemoteActionItemContract(item, options) {
810
- const permission = options.denied || (options.hasAllowedIds && !options.allowed) ? 'denied' : 'allowed';
811
- const baseDisabledReason = options.disabledReason || normalizedOverlayMenuReason(item.disabledReason);
812
- const disabledReason = permission === 'denied' && baseDisabledReason === ''
813
- ? 'Permission required'
814
- : baseDisabledReason;
815
- const disabled = options.disabled || item.disabled === true || permission === 'denied';
816
- const remoteAction = options.remoteAction ||
817
- options.remoteActionDisabled ||
818
- options.remoteActionLoading ||
819
- options.remoteActionError !== '' ||
820
- item.remoteAction === true ||
821
- item.remoteActionDisabled === true ||
822
- item.remoteActionLoading === true ||
823
- normalizedOverlayMenuReason(item.remoteActionError) !== '';
824
- const remoteActionError = options.remoteActionError || normalizedOverlayMenuReason(item.remoteActionError);
825
- const remoteActionDisabled = options.remoteActionDisabled || item.remoteActionDisabled === true;
826
- const remoteActionLoading = options.remoteActionLoading || item.remoteActionLoading === true;
827
- const remoteActionDisabledReason = options.remoteActionDisabledReason ||
828
- normalizedOverlayMenuReason(item.remoteActionDisabledReason);
829
- const remoteActionState = remoteActionLoading && remoteActionError === ''
830
- ? 'pending'
831
- : remoteActionDisabled || disabled || remoteActionError !== ''
832
- ? 'disabled'
833
- : 'available';
834
- const submenuError = options.submenuError || normalizedOverlayMenuReason(item.submenuError);
835
- const submenuLoading = options.submenuLoading || item.submenuLoading === true;
836
- const submenuItemCount = options.submenuItemCount ?? item.submenuItemCount;
837
- const contractItem = {
838
- ...item,
839
- disabled,
840
- ...(disabledReason === '' ? {} : { disabledReason }),
841
- permission,
842
- ...(remoteAction ? { remoteAction: true } : {}),
843
- ...(remoteActionState === 'disabled' ? { remoteActionDisabled: true } : {}),
844
- ...(remoteActionState === 'pending' ? { remoteActionLoading: true } : {}),
845
- ...(remoteActionDisabledReason === '' ? {} : { remoteActionDisabledReason }),
846
- ...(remoteActionError === '' ? {} : { remoteActionError }),
847
- ...(submenuError === '' ? {} : { submenuError }),
848
- ...(submenuItemCount === undefined ? {} : { submenuItemCount }),
849
- ...(submenuLoading ? { submenuLoading: true } : {}),
850
- };
851
- const submenuState = dropdownItemSubmenuState(contractItem);
852
- return {
853
- id: item.id,
854
- item: contractItem,
855
- permission,
856
- disabled,
857
- disabledReason,
858
- remoteAction,
859
- remoteActionState,
860
- remoteActionDisabledReason,
861
- remoteActionError,
862
- submenuState,
863
- submenuError,
864
- submenuItemCount,
865
- attrs: {
866
- 'data-item-id': item.id,
867
- 'data-permission': permission,
868
- 'data-disabled-reason': disabledReason,
869
- 'data-remote-action': remoteAction ? 'true' : undefined,
870
- 'data-remote-action-state': remoteAction ? remoteActionState : undefined,
871
- 'data-remote-action-disabled-reason': remoteActionDisabledReason,
872
- 'data-remote-action-error': remoteActionError,
873
- 'data-can-invoke-remote-action': remoteAction && remoteActionState === 'available' && !disabled ? 'true' : undefined,
874
- 'data-submenu-state': submenuState,
875
- 'data-submenu-error': submenuError,
876
- 'data-submenu-item-count': submenuItemCount,
877
- 'aria-disabled': disabled ? 'true' : undefined,
878
- 'aria-busy': remoteActionState === 'pending' || submenuState === 'loading' ? 'true' : undefined,
879
- },
880
- };
881
- }
882
- function dropdownMenuPermissionRemoteActionState(options) {
883
- if (options.itemCount === 0)
884
- return 'empty';
885
- if (options.remoteActionLoadingCount > 0)
886
- return 'remote-pending';
887
- if (options.submenuErrorCount > 0)
888
- return 'submenu-error';
889
- if (options.deniedCount > 0)
890
- return 'permission-limited';
891
- return 'ready';
892
- }
893
- function dropdownMenuSubmenuPortalContractState(options) {
894
- if (options.submenuCount === 0)
895
- return 'empty';
896
- if (options.portalMissingCount > 0)
897
- return 'portal-missing';
898
- if (options.openCount > 0)
899
- return 'open';
900
- if (options.mountedCount > 0)
901
- return 'mounted';
902
- return 'closed';
903
- }
904
- function dropdownItemRemoteActionState(item) {
905
- if (item.remoteAction !== true)
906
- return undefined;
907
- if (item.remoteActionLoading === true &&
908
- normalizedOverlayMenuReason(item.remoteActionError) === '') {
909
- return 'pending';
910
- }
911
- if (item.remoteActionDisabled === true ||
912
- item.disabled === true ||
913
- normalizedOverlayMenuReason(item.remoteActionError) !== '') {
914
- return 'disabled';
915
- }
916
- return 'available';
917
- }
918
- function dropdownItemSubmenuState(item) {
919
- if (item.kind !== 'submenu')
920
- return undefined;
921
- if (item.submenuLoading === true)
922
- return 'loading';
923
- if (normalizedOverlayMenuReason(item.submenuError) !== '')
924
- return 'error';
925
- if (item.expanded === true)
926
- return 'open';
927
- if ((item.submenuItemCount ?? 0) > 0)
928
- return 'ready';
929
- return 'closed';
930
- }
931
- function normalizeOverlayMenuIds(value, knownIds) {
932
- if (value === undefined)
933
- return [];
934
- const values = Array.isArray(value)
935
- ? value
936
- : [...value];
937
- const result = [];
938
- for (const id of values) {
939
- if (!knownIds.has(id) || result.includes(id))
940
- continue;
941
- result.push(id);
942
- }
943
- return result;
944
- }
945
- function normalizedOverlayMenuReason(value) {
946
- return value?.trim() ?? '';
947
- }
948
- function normalizedOverlayMenuItemCount(value) {
949
- if (value === undefined || !Number.isFinite(value))
950
- return undefined;
951
- return Math.max(0, Math.trunc(value));
952
- }
953
- function overlayRole(kind) {
954
- switch (kind) {
955
- case 'tooltip':
956
- return 'tooltip';
957
- case 'dropdown':
958
- case 'context-menu':
959
- return 'menu';
960
- default:
961
- return 'dialog';
962
- }
963
- }
964
- function isNode(value) {
965
- return typeof Node !== 'undefined' && value instanceof Node;
966
- }
967
- function isHTMLElement(value) {
968
- return typeof HTMLElement !== 'undefined' && value instanceof HTMLElement;
969
- }