@vobs/ui 0.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.
- package/LICENSE +21 -0
- package/dist/app-shell-dom.d.ts +24 -0
- package/dist/app-shell-dom.js +573 -0
- package/dist/app-shell.d.ts +199 -0
- package/dist/app-shell.js +370 -0
- package/dist/base.css +52 -0
- package/dist/calendar-picker.d.ts +45 -0
- package/dist/calendar-picker.js +217 -0
- package/dist/calendar.d.ts +95 -0
- package/dist/calendar.js +194 -0
- package/dist/complex-inputs.d.ts +300 -0
- package/dist/complex-inputs.js +862 -0
- package/dist/components.css +4568 -0
- package/dist/data-display.d.ts +1063 -0
- package/dist/data-display.js +2298 -0
- package/dist/feedback.d.ts +88 -0
- package/dist/feedback.js +281 -0
- package/dist/forms.d.ts +378 -0
- package/dist/forms.js +1015 -0
- package/dist/icons-config.d.ts +25 -0
- package/dist/icons-config.js +14 -0
- package/dist/icons.d.ts +41 -0
- package/dist/icons.js +185 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +32 -0
- package/dist/locale/en-US.d.ts +31 -0
- package/dist/locale/en-US.js +20 -0
- package/dist/locale/zh-CN.d.ts +31 -0
- package/dist/locale/zh-CN.js +20 -0
- package/dist/navigation.d.ts +609 -0
- package/dist/navigation.js +1707 -0
- package/dist/overlay.d.ts +304 -0
- package/dist/overlay.js +969 -0
- package/dist/primitives.d.ts +268 -0
- package/dist/primitives.js +764 -0
- package/dist/styles.css +3 -0
- package/dist/theme-data.d.ts +363 -0
- package/dist/theme-data.js +374 -0
- package/dist/theme.d.ts +79 -0
- package/dist/theme.js +262 -0
- package/dist/tokens.css +449 -0
- package/dist/tree.d.ts +144 -0
- package/dist/tree.js +469 -0
- package/dist/virtual-list.d.ts +190 -0
- package/dist/virtual-list.js +521 -0
- package/dist/workbench.d.ts +136 -0
- package/dist/workbench.js +156 -0
- package/package.json +113 -0
package/dist/overlay.js
ADDED
|
@@ -0,0 +1,969 @@
|
|
|
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
|
+
}
|