@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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app-shell-dom.d.ts +24 -0
  3. package/dist/app-shell-dom.js +573 -0
  4. package/dist/app-shell.d.ts +199 -0
  5. package/dist/app-shell.js +370 -0
  6. package/dist/base.css +52 -0
  7. package/dist/calendar-picker.d.ts +45 -0
  8. package/dist/calendar-picker.js +217 -0
  9. package/dist/calendar.d.ts +95 -0
  10. package/dist/calendar.js +194 -0
  11. package/dist/complex-inputs.d.ts +300 -0
  12. package/dist/complex-inputs.js +862 -0
  13. package/dist/components.css +4568 -0
  14. package/dist/data-display.d.ts +1063 -0
  15. package/dist/data-display.js +2298 -0
  16. package/dist/feedback.d.ts +88 -0
  17. package/dist/feedback.js +281 -0
  18. package/dist/forms.d.ts +378 -0
  19. package/dist/forms.js +1015 -0
  20. package/dist/icons-config.d.ts +25 -0
  21. package/dist/icons-config.js +14 -0
  22. package/dist/icons.d.ts +41 -0
  23. package/dist/icons.js +185 -0
  24. package/dist/index.d.ts +22 -0
  25. package/dist/index.js +32 -0
  26. package/dist/locale/en-US.d.ts +31 -0
  27. package/dist/locale/en-US.js +20 -0
  28. package/dist/locale/zh-CN.d.ts +31 -0
  29. package/dist/locale/zh-CN.js +20 -0
  30. package/dist/navigation.d.ts +609 -0
  31. package/dist/navigation.js +1707 -0
  32. package/dist/overlay.d.ts +304 -0
  33. package/dist/overlay.js +969 -0
  34. package/dist/primitives.d.ts +268 -0
  35. package/dist/primitives.js +764 -0
  36. package/dist/styles.css +3 -0
  37. package/dist/theme-data.d.ts +363 -0
  38. package/dist/theme-data.js +374 -0
  39. package/dist/theme.d.ts +79 -0
  40. package/dist/theme.js +262 -0
  41. package/dist/tokens.css +449 -0
  42. package/dist/tree.d.ts +144 -0
  43. package/dist/tree.js +469 -0
  44. package/dist/virtual-list.d.ts +190 -0
  45. package/dist/virtual-list.js +521 -0
  46. package/dist/workbench.d.ts +136 -0
  47. package/dist/workbench.js +156 -0
  48. package/package.json +113 -0
@@ -0,0 +1,1707 @@
1
+ /** @license MIT
2
+ * Copyright (c) 2026 vobsjs
3
+ * @vobs/ui
4
+ */
5
+ import { createDialogOverlayPlans, createDialogPlans, } from './overlay.js';
6
+ import { classNames, createUiDomPlan } from './primitives.js';
7
+ export function createTabsPlans(options) {
8
+ const orientation = options.orientation ?? 'horizontal';
9
+ return {
10
+ root: createUiDomPlan('div', 'tabs', {
11
+ state: 'idle',
12
+ attrs: {
13
+ id: options.id,
14
+ role: 'tablist',
15
+ 'aria-orientation': orientation,
16
+ ...options.attrs,
17
+ },
18
+ }),
19
+ tabs: options.items.map((item) => {
20
+ const selected = item.id === options.activeId;
21
+ return createUiDomPlan('button', 'tab', {
22
+ state: selected ? 'active' : item.disabled === true ? 'disabled' : 'idle',
23
+ ...(item.disabled === true ? { disabled: true } : {}),
24
+ attrs: {
25
+ id: `${options.id}-${item.id}-tab`,
26
+ type: 'button',
27
+ role: 'tab',
28
+ 'aria-selected': selected ? 'true' : 'false',
29
+ 'aria-controls': `${options.id}-${item.id}-panel`,
30
+ tabindex: selected ? 0 : -1,
31
+ 'data-tab-id': item.id,
32
+ 'data-overflow': item.overflow === true ? 'true' : undefined,
33
+ 'data-persisted-active': item.persistedActive === true ? 'true' : undefined,
34
+ 'data-disabled-reason': item.disabledReason,
35
+ 'aria-disabled': item.disabledReason === undefined ? undefined : 'true',
36
+ },
37
+ });
38
+ }),
39
+ panels: options.items.map((item) => {
40
+ const selected = item.id === options.activeId;
41
+ return createUiDomPlan('div', 'tabpanel', {
42
+ state: selected ? 'active' : 'idle',
43
+ attrs: {
44
+ id: `${options.id}-${item.id}-panel`,
45
+ role: 'tabpanel',
46
+ 'aria-labelledby': `${options.id}-${item.id}-tab`,
47
+ tabindex: 0,
48
+ hidden: selected ? undefined : true,
49
+ },
50
+ });
51
+ }),
52
+ };
53
+ }
54
+ export function createTabsOverflowPersistenceContract(options) {
55
+ const knownIds = new Set(options.items.map((item) => item.id));
56
+ const overflowInputIds = normalizeKnownIds(options.overflowIds, knownIds);
57
+ const maxVisible = options.maxVisible === undefined
58
+ ? options.items.length
59
+ : Math.max(1, Math.trunc(options.maxVisible));
60
+ const derivedOverflowIds = options.items.length > maxVisible ? options.items.slice(maxVisible).map((item) => item.id) : [];
61
+ const overflowIds = normalizeKnownIds([...overflowInputIds, ...derivedOverflowIds], knownIds);
62
+ const overflowIdSet = new Set(overflowIds);
63
+ const persistedActiveId = options.persistedActiveId !== undefined && knownIds.has(options.persistedActiveId)
64
+ ? options.persistedActiveId
65
+ : '';
66
+ const activeId = knownIds.has(options.activeId)
67
+ ? options.activeId
68
+ : (options.items.find((item) => item.disabled !== true)?.id ?? '');
69
+ const orientation = options.orientation ?? 'horizontal';
70
+ const items = options.items.map((item) => tabsOverflowPersistenceItemContract(item, {
71
+ active: item.id === activeId,
72
+ disabledReason: normalizedDisabledReason(options.disabledReasons?.[item.id] ?? item.disabledReason),
73
+ overflow: overflowIdSet.has(item.id),
74
+ persistedActive: item.id === persistedActiveId,
75
+ }));
76
+ const visibleIds = items.filter((item) => !item.overflow).map((item) => item.id);
77
+ const disabledReasonIds = items
78
+ .filter((item) => item.disabledReason !== '')
79
+ .map((item) => item.id);
80
+ const overflowState = items.length === 0 ? 'empty' : overflowIds.length > 0 ? 'overflow' : 'expanded';
81
+ const persistenceState = items.length === 0
82
+ ? 'empty'
83
+ : persistedActiveId === ''
84
+ ? 'clean'
85
+ : persistedActiveId === activeId
86
+ ? 'restored'
87
+ : 'dirty';
88
+ return {
89
+ overflowState,
90
+ persistenceState,
91
+ activeId,
92
+ persistedActiveId,
93
+ orientation,
94
+ itemCount: items.length,
95
+ visibleCount: visibleIds.length,
96
+ overflowCount: overflowIds.length,
97
+ disabledReasonCount: disabledReasonIds.length,
98
+ overflowIds,
99
+ visibleIds,
100
+ disabledReasonIds,
101
+ items,
102
+ attrs: {
103
+ 'data-tabs-overflow-persistence-contract': 'true',
104
+ 'data-tabs-overflow-state': overflowState,
105
+ 'data-tabs-persistence-state': persistenceState,
106
+ 'data-active-id': activeId,
107
+ 'data-persisted-active-id': persistedActiveId,
108
+ 'data-orientation': orientation,
109
+ 'data-item-count': items.length,
110
+ 'data-visible-count': visibleIds.length,
111
+ 'data-overflow-count': overflowIds.length,
112
+ 'data-disabled-reason-count': disabledReasonIds.length,
113
+ 'data-overflow-ids': overflowIds.join(','),
114
+ 'data-visible-ids': visibleIds.join(','),
115
+ 'data-disabled-reason-ids': disabledReasonIds.join(','),
116
+ },
117
+ };
118
+ }
119
+ export function createCommandPalettePlans(options) {
120
+ const open = options.open === true;
121
+ const enabledItems = options.items.filter((item) => item.disabled !== true);
122
+ const activeId = open && enabledItems.some((item) => item.id === options.activeId)
123
+ ? options.activeId
124
+ : enabledItems[0]?.id;
125
+ const visibleGroups = commandPaletteGroupsForItems(options.items, options.groups ?? []);
126
+ return {
127
+ root: createUiDomPlan('div', 'command-palette', {
128
+ state: open ? 'open' : 'closed',
129
+ attrs: {
130
+ id: options.id,
131
+ role: 'dialog',
132
+ 'aria-modal': 'true',
133
+ 'aria-labelledby': options.labelId,
134
+ 'aria-describedby': options.describedBy,
135
+ hidden: open ? undefined : true,
136
+ 'data-count': options.items.length,
137
+ },
138
+ }),
139
+ input: createUiDomPlan('input', 'command-palette-input', {
140
+ attrs: {
141
+ id: `${options.id}-input`,
142
+ type: 'search',
143
+ role: 'combobox',
144
+ autocomplete: 'off',
145
+ value: options.query,
146
+ placeholder: options.placeholder ?? 'Search commands',
147
+ 'aria-expanded': open ? 'true' : 'false',
148
+ 'aria-controls': `${options.id}-listbox`,
149
+ 'aria-activedescendant': activeId === undefined ? undefined : commandPaletteItemDomId(options.id, activeId),
150
+ 'aria-labelledby': options.labelId,
151
+ 'aria-describedby': options.describedBy,
152
+ },
153
+ }),
154
+ listbox: createUiDomPlan('div', 'command-palette-listbox', {
155
+ state: options.items.length === 0 ? 'empty' : 'filled',
156
+ attrs: {
157
+ id: `${options.id}-listbox`,
158
+ role: 'listbox',
159
+ },
160
+ }),
161
+ groups: visibleGroups.map((group) => createUiDomPlan('div', 'command-palette-group', {
162
+ attrs: {
163
+ role: 'group',
164
+ 'aria-labelledby': `${options.id}-${group.id}-group-label`,
165
+ 'data-group-id': group.id,
166
+ },
167
+ })),
168
+ groupLabels: visibleGroups.map((group) => createUiDomPlan('div', 'command-palette-group-label', {
169
+ attrs: {
170
+ id: `${options.id}-${group.id}-group-label`,
171
+ 'data-label': group.label,
172
+ },
173
+ })),
174
+ items: options.items.map((item) => {
175
+ const active = item.id === activeId;
176
+ const selected = item.id === options.selectedId;
177
+ return createUiDomPlan('div', 'command-palette-item', {
178
+ state: item.disabled === true ? 'disabled' : active ? 'active' : selected ? 'selected' : 'idle',
179
+ ...(item.disabled === true ? { disabled: true } : {}),
180
+ attrs: {
181
+ id: commandPaletteItemDomId(options.id, item.id),
182
+ role: 'option',
183
+ tabindex: active && item.disabled !== true ? 0 : -1,
184
+ 'aria-selected': selected ? 'true' : 'false',
185
+ 'aria-disabled': item.disabled === true ? 'true' : undefined,
186
+ 'data-command-id': item.id,
187
+ 'data-kind': item.kind ?? 'action',
188
+ 'data-group-id': item.groupId,
189
+ 'data-label': item.label,
190
+ 'data-description': item.description,
191
+ 'data-href': item.href,
192
+ 'data-permission': item.permission,
193
+ 'data-recent': item.recent === true || item.kind === 'recent' ? 'true' : undefined,
194
+ 'data-disabled-reason': item.disabledReason,
195
+ },
196
+ });
197
+ }),
198
+ shortcuts: options.items.map((item) => createUiDomPlan('kbd', 'command-palette-shortcut', {
199
+ attrs: {
200
+ 'aria-hidden': 'true',
201
+ 'data-shortcut': item.shortcut,
202
+ },
203
+ })),
204
+ empty: options.items.length === 0
205
+ ? createUiDomPlan('div', 'command-palette-empty', {
206
+ attrs: {
207
+ role: 'status',
208
+ 'aria-live': 'polite',
209
+ 'data-label': 'No commands found',
210
+ },
211
+ })
212
+ : undefined,
213
+ };
214
+ }
215
+ export function createCommandPalettePermissionRecentContract(options) {
216
+ const query = options.query?.trim() ?? '';
217
+ const keyword = query.toLowerCase();
218
+ const allowedIds = normalizeCommandIds(options.allowedIds);
219
+ const deniedIds = normalizeCommandIds(options.deniedIds);
220
+ const knownIds = new Set(options.items.map((item) => item.id));
221
+ const recentIds = normalizeCommandPaletteRecentIds(options.recentIds ?? [], knownIds, options.maxRecent);
222
+ const recentOrder = new Map(recentIds.map((id, index) => [id, index]));
223
+ const allContracts = options.items.map((item, index) => commandPalettePermissionRecentItemContract(item, {
224
+ allowedIds,
225
+ deniedIds,
226
+ disabledReason: normalizedDisabledReason(options.disabledReasons?.[item.id]),
227
+ index,
228
+ recent: recentOrder.has(item.id),
229
+ recentGroupId: (options.recentGroup ?? defaultCommandPaletteRecentGroup).id,
230
+ }));
231
+ const visibleContracts = allContracts
232
+ .filter((item) => keyword === '' || commandPaletteItemMatches(item.item, keyword))
233
+ .filter((item) => options.hideDenied !== true || item.permission === 'allowed')
234
+ .sort((a, b) => commandPaletteRecentSort(a, b, recentOrder));
235
+ const selected = allContracts.find((item) => item.id === options.selectedId);
236
+ const canPersistSelected = selected !== undefined && selected.permission === 'allowed' && selected.item.disabled !== true;
237
+ const nextRecentIds = canPersistSelected
238
+ ? normalizeCommandPaletteRecentIds([selected.id, ...recentIds.filter((id) => id !== selected.id)], knownIds, options.maxRecent)
239
+ : recentIds;
240
+ const allowedCount = allContracts.filter((item) => item.permission === 'allowed').length;
241
+ const deniedCount = allContracts.length - allowedCount;
242
+ const disabledReasonCount = allContracts.filter((item) => item.disabledReason !== '').length;
243
+ const visibleGroups = commandPaletteGroupsForItems(visibleContracts.map((item) => item.item), [options.recentGroup ?? defaultCommandPaletteRecentGroup, ...(options.groups ?? [])]);
244
+ const state = commandPalettePermissionRecentState({
245
+ deniedCount,
246
+ totalCount: allContracts.length,
247
+ visibleCount: visibleContracts.length,
248
+ query,
249
+ });
250
+ return {
251
+ state,
252
+ query,
253
+ totalCount: allContracts.length,
254
+ visibleCount: visibleContracts.length,
255
+ allowedCount,
256
+ deniedCount,
257
+ recentCount: recentIds.length,
258
+ disabledReasonCount,
259
+ recentIds,
260
+ nextRecentIds,
261
+ groups: visibleGroups,
262
+ items: visibleContracts,
263
+ attrs: {
264
+ 'data-command-permission-recent-contract': 'true',
265
+ 'data-state': state,
266
+ 'data-command-query': query,
267
+ 'data-total-count': allContracts.length,
268
+ 'data-visible-count': visibleContracts.length,
269
+ 'data-allowed-count': allowedCount,
270
+ 'data-denied-count': deniedCount,
271
+ 'data-recent-count': recentIds.length,
272
+ 'data-disabled-reason-count': disabledReasonCount,
273
+ 'data-recent-ids': recentIds.join(','),
274
+ 'data-next-recent-ids': nextRecentIds.join(','),
275
+ },
276
+ };
277
+ }
278
+ export function getNextTabId(items, activeId, key, orientation = 'horizontal') {
279
+ return getNextRovingId(items, activeId, key, orientation, false);
280
+ }
281
+ export function filterCommandPaletteItems(items, query, limit = items.length) {
282
+ const keyword = query.trim().toLowerCase();
283
+ const result = [];
284
+ for (const item of items) {
285
+ if (keyword === '' || commandPaletteItemMatches(item, keyword)) {
286
+ result.push(item);
287
+ if (result.length >= limit)
288
+ break;
289
+ }
290
+ }
291
+ return result;
292
+ }
293
+ export function getNextCommandPaletteItemId(items, activeId, key) {
294
+ const enabled = items.filter((item) => item.disabled !== true);
295
+ if (enabled.length === 0)
296
+ return activeId;
297
+ const currentIndex = Math.max(-1, enabled.findIndex((item) => item.id === activeId));
298
+ if (key === 'Home')
299
+ return enabled[0]?.id;
300
+ if (key === 'End')
301
+ return enabled.at(-1)?.id;
302
+ if (key === 'ArrowUp')
303
+ return enabled.at(currentIndex - 1)?.id ?? enabled.at(-1)?.id;
304
+ if (key === 'ArrowDown')
305
+ return enabled[(currentIndex + 1) % enabled.length]?.id;
306
+ return activeId;
307
+ }
308
+ export function shouldOpenCommandPaletteForKey(key, metaKey = false, ctrlKey = false) {
309
+ return key.toLowerCase() === 'k' && (metaKey || ctrlKey);
310
+ }
311
+ export function shouldCloseCommandPaletteForKey(key) {
312
+ return key === 'Escape';
313
+ }
314
+ export function shouldSelectCommandPaletteItem(key) {
315
+ return key === 'Enter';
316
+ }
317
+ export function createBreadcrumbPlans(items) {
318
+ return {
319
+ root: createUiDomPlan('nav', 'breadcrumb', {
320
+ attrs: { 'aria-label': 'Breadcrumb' },
321
+ }),
322
+ list: createUiDomPlan('ol', 'breadcrumb-list'),
323
+ items: items.map((item, index) => createUiDomPlan('li', 'breadcrumb-item', {
324
+ state: item.current === true || index === items.length - 1 ? 'current' : 'idle',
325
+ attrs: {
326
+ 'data-href': item.href,
327
+ 'aria-current': item.current === true ? 'page' : undefined,
328
+ 'data-item-id': item.id,
329
+ 'data-icon': item.icon === undefined ? undefined : String(item.icon),
330
+ 'data-collapsed': item.collapsed === true ? 'true' : undefined,
331
+ 'data-overflow': item.overflow === true ? 'true' : undefined,
332
+ 'data-path-index': item.pathIndex,
333
+ 'data-path-summary': item.pathSummary,
334
+ },
335
+ })),
336
+ };
337
+ }
338
+ export function createBreadcrumbResponsiveStateContract(options) {
339
+ const ids = breadcrumbIds(options.items);
340
+ const knownIds = new Set(ids);
341
+ const collapsedIds = normalizeKnownIds(options.collapsedIds, knownIds);
342
+ const iconIds = normalizeKnownIds(options.iconIds, knownIds);
343
+ const collapsedSet = new Set(collapsedIds);
344
+ const iconSet = new Set(iconIds);
345
+ const overflowId = options.overflowId !== undefined && knownIds.has(options.overflowId) ? options.overflowId : '';
346
+ const visibleLimit = options.maxVisible === undefined
347
+ ? options.items.length
348
+ : Math.max(1, Math.trunc(options.maxVisible));
349
+ const currentPathSummary = options.items.map((item) => item.label).join(' / ');
350
+ const items = options.items.map((item, index) => {
351
+ const id = ids[index] ?? String(index);
352
+ const overflow = id === overflowId;
353
+ const collapsed = !overflow &&
354
+ (collapsedSet.has(id) ||
355
+ (options.items.length > visibleLimit && index > 0 && index < options.items.length - 1));
356
+ return breadcrumbResponsiveItemContract(item, {
357
+ collapsed,
358
+ icon: iconSet.has(id) || item.icon !== undefined,
359
+ id,
360
+ overflow,
361
+ pathIndex: index,
362
+ pathSummary: currentPathSummary,
363
+ });
364
+ });
365
+ const visibleIds = items
366
+ .filter((item) => !item.collapsed || item.overflow)
367
+ .map((item) => item.id);
368
+ const state = items.length === 0
369
+ ? 'empty'
370
+ : items.some((item) => item.overflow)
371
+ ? 'overflow'
372
+ : items.some((item) => item.collapsed)
373
+ ? 'collapsed'
374
+ : 'expanded';
375
+ return {
376
+ state,
377
+ itemCount: items.length,
378
+ visibleCount: visibleIds.length,
379
+ collapsedCount: items.filter((item) => item.collapsed).length,
380
+ iconCount: items.filter((item) => item.icon).length,
381
+ overflowId,
382
+ currentPathSummary,
383
+ collapsedIds: items.filter((item) => item.collapsed).map((item) => item.id),
384
+ iconIds: items.filter((item) => item.icon).map((item) => item.id),
385
+ visibleIds,
386
+ items,
387
+ attrs: {
388
+ 'data-breadcrumb-responsive-contract': 'true',
389
+ 'data-breadcrumb-responsive-state': state,
390
+ 'data-item-count': items.length,
391
+ 'data-visible-count': visibleIds.length,
392
+ 'data-collapsed-count': items.filter((item) => item.collapsed).length,
393
+ 'data-icon-count': items.filter((item) => item.icon).length,
394
+ 'data-overflow-id': overflowId,
395
+ 'data-current-path-summary': currentPathSummary,
396
+ 'data-collapsed-ids': items
397
+ .filter((item) => item.collapsed)
398
+ .map((item) => item.id)
399
+ .join(','),
400
+ 'data-icon-ids': items
401
+ .filter((item) => item.icon)
402
+ .map((item) => item.id)
403
+ .join(','),
404
+ 'data-visible-ids': visibleIds.join(','),
405
+ },
406
+ };
407
+ }
408
+ export function createMenuPlans(options) {
409
+ const activeId = activeMenuItemId(options.items, options.activeId);
410
+ const orientation = options.orientation ?? 'vertical';
411
+ return {
412
+ root: createUiDomPlan('div', 'menu', {
413
+ ...(options.className === undefined ? {} : { className: options.className }),
414
+ attrs: {
415
+ id: options.id,
416
+ role: 'menu',
417
+ 'aria-label': options.label,
418
+ 'aria-orientation': orientation,
419
+ ...options.attrs,
420
+ 'data-orientation': orientation,
421
+ 'data-count': options.items.length,
422
+ },
423
+ }),
424
+ items: options.items.map((item) => createMenuItemPlan(options.id, item, item.id === activeId)),
425
+ shortcuts: options.items.map((item) => item.shortcut === undefined
426
+ ? undefined
427
+ : createUiDomPlan('kbd', 'menu-item-shortcut', {
428
+ attrs: {
429
+ 'aria-hidden': 'true',
430
+ 'data-shortcut': item.shortcut,
431
+ },
432
+ })),
433
+ };
434
+ }
435
+ export function createMenuPermissionRemoteActionContract(options) {
436
+ const disabled = options.disabled === true;
437
+ const knownIds = new Set(options.items.map((item) => item.id));
438
+ const allowedInputIds = normalizeKnownIds(options.allowedIds, knownIds);
439
+ const deniedInputIds = normalizeKnownIds(options.deniedIds, knownIds);
440
+ const remoteActionInputIds = normalizeKnownIds(options.remoteActionIds, knownIds);
441
+ const remoteActionDisabledInputIds = normalizeKnownIds(options.remoteActionDisabledIds, knownIds);
442
+ const remoteActionLoadingInputIds = normalizeKnownIds(options.remoteActionLoadingIds, knownIds);
443
+ const submenuLoadingInputIds = normalizeKnownIds(options.submenuLoadingIds, knownIds);
444
+ const allowedSet = new Set(allowedInputIds);
445
+ const deniedSet = new Set(deniedInputIds);
446
+ const remoteActionSet = new Set(remoteActionInputIds);
447
+ const remoteActionDisabledSet = new Set(remoteActionDisabledInputIds);
448
+ const remoteActionLoadingSet = new Set(remoteActionLoadingInputIds);
449
+ const submenuLoadingSet = new Set(submenuLoadingInputIds);
450
+ const hasAllowedIds = options.allowedIds !== undefined;
451
+ const items = options.items.map((item) => menuPermissionRemoteActionItemContract(item, {
452
+ denied: deniedSet.has(item.id),
453
+ disabled,
454
+ disabledReason: normalizedDisabledReason(options.disabledReasons?.[item.id]),
455
+ hasAllowedIds,
456
+ allowed: allowedSet.has(item.id),
457
+ remoteAction: remoteActionSet.has(item.id),
458
+ remoteActionDisabled: remoteActionDisabledSet.has(item.id),
459
+ remoteActionDisabledReason: normalizedDisabledReason(options.remoteActionDisabledReasons?.[item.id]),
460
+ remoteActionError: normalizedDisabledReason(options.remoteActionErrors?.[item.id]),
461
+ remoteActionLoading: remoteActionLoadingSet.has(item.id),
462
+ submenuError: normalizedDisabledReason(options.submenuErrors?.[item.id]),
463
+ submenuItemCount: normalizedMenuItemCount(options.submenuItemCounts?.[item.id]),
464
+ submenuLoading: submenuLoadingSet.has(item.id),
465
+ }));
466
+ const allowedIds = items.filter((item) => item.permission === 'allowed').map((item) => item.id);
467
+ const deniedIds = items.filter((item) => item.permission === 'denied').map((item) => item.id);
468
+ const remoteActionIds = items.filter((item) => item.remoteAction).map((item) => item.id);
469
+ const remoteActionDisabledIds = items
470
+ .filter((item) => item.remoteAction && item.remoteActionState === 'disabled')
471
+ .map((item) => item.id);
472
+ const remoteActionLoadingIds = items
473
+ .filter((item) => item.remoteAction && item.remoteActionState === 'pending')
474
+ .map((item) => item.id);
475
+ const remoteActionErrorIds = items
476
+ .filter((item) => item.remoteActionError !== '')
477
+ .map((item) => item.id);
478
+ const submenuLoadingIds = items
479
+ .filter((item) => item.submenuState === 'loading')
480
+ .map((item) => item.id);
481
+ const submenuErrorIds = items
482
+ .filter((item) => item.submenuState === 'error')
483
+ .map((item) => item.id);
484
+ const disabledReasonCount = items.filter((item) => item.disabledReason !== '').length;
485
+ const state = menuPermissionRemoteActionState({
486
+ deniedCount: deniedIds.length,
487
+ itemCount: options.items.length,
488
+ remoteActionLoadingCount: remoteActionLoadingIds.length,
489
+ submenuErrorCount: submenuErrorIds.length,
490
+ });
491
+ return {
492
+ state,
493
+ disabled,
494
+ itemCount: options.items.length,
495
+ allowedCount: allowedIds.length,
496
+ deniedCount: deniedIds.length,
497
+ disabledReasonCount,
498
+ remoteActionCount: remoteActionIds.length,
499
+ remoteActionDisabledCount: remoteActionDisabledIds.length,
500
+ remoteActionLoadingCount: remoteActionLoadingIds.length,
501
+ remoteActionErrorCount: remoteActionErrorIds.length,
502
+ submenuLoadingCount: submenuLoadingIds.length,
503
+ submenuErrorCount: submenuErrorIds.length,
504
+ allowedIds,
505
+ deniedIds,
506
+ remoteActionIds,
507
+ remoteActionDisabledIds,
508
+ remoteActionLoadingIds,
509
+ remoteActionErrorIds,
510
+ submenuLoadingIds,
511
+ submenuErrorIds,
512
+ items,
513
+ attrs: {
514
+ 'data-menu-permission-remote-contract': 'true',
515
+ 'data-menu-permission-remote-state': state,
516
+ 'data-state': state,
517
+ 'data-disabled': disabled ? 'true' : undefined,
518
+ 'data-item-count': options.items.length,
519
+ 'data-allowed-count': allowedIds.length,
520
+ 'data-denied-count': deniedIds.length,
521
+ 'data-disabled-reason-count': disabledReasonCount,
522
+ 'data-remote-action-count': remoteActionIds.length,
523
+ 'data-remote-action-disabled-count': remoteActionDisabledIds.length,
524
+ 'data-remote-action-loading-count': remoteActionLoadingIds.length,
525
+ 'data-remote-action-error-count': remoteActionErrorIds.length,
526
+ 'data-submenu-loading-count': submenuLoadingIds.length,
527
+ 'data-submenu-error-count': submenuErrorIds.length,
528
+ 'data-allowed-ids': allowedIds.join(','),
529
+ 'data-denied-ids': deniedIds.join(','),
530
+ 'data-remote-action-ids': remoteActionIds.join(','),
531
+ 'data-remote-action-disabled-ids': remoteActionDisabledIds.join(','),
532
+ 'data-remote-action-loading-ids': remoteActionLoadingIds.join(','),
533
+ 'data-remote-action-error-ids': remoteActionErrorIds.join(','),
534
+ 'data-submenu-loading-ids': submenuLoadingIds.join(','),
535
+ 'data-submenu-error-ids': submenuErrorIds.join(','),
536
+ 'aria-busy': remoteActionLoadingIds.length > 0 || submenuLoadingIds.length > 0 ? 'true' : undefined,
537
+ },
538
+ };
539
+ }
540
+ export function createMenuSubmenuPortalStateContract(options) {
541
+ const submenuIds = new Set(options.items.filter((item) => item.kind === 'submenu').map((item) => item.id));
542
+ const openPathIds = normalizeKnownIds(options.openPathIds, submenuIds);
543
+ const mountedInputIds = normalizeKnownIds(options.mountedIds, submenuIds);
544
+ const openSet = new Set(openPathIds);
545
+ const mountedSet = new Set(mountedInputIds);
546
+ const items = options.items
547
+ .filter((item) => item.kind === 'submenu')
548
+ .map((item) => menuSubmenuPortalItemContract(item, {
549
+ focusReturnId: normalizedDisabledReason(options.focusReturnIds?.[item.id]),
550
+ mounted: mountedSet.has(item.id),
551
+ openPathIndex: openPathIds.indexOf(item.id),
552
+ parentOverlayId: normalizedDisabledReason(options.parentOverlayIds?.[item.id]),
553
+ placement: options.placements?.[item.id],
554
+ portalTargetId: normalizedDisabledReason(options.portalTargetIds?.[item.id]),
555
+ open: openSet.has(item.id),
556
+ }));
557
+ const mountedIds = items.filter((item) => item.mounted).map((item) => item.id);
558
+ const portalMissingIds = items
559
+ .filter((item) => item.state === 'portal-missing')
560
+ .map((item) => item.id);
561
+ const portalTargetIds = items
562
+ .map((item) => item.portalTargetId)
563
+ .filter((id, index, ids) => id !== '' && ids.indexOf(id) === index);
564
+ const state = menuSubmenuPortalContractState({
565
+ mountedCount: mountedIds.length,
566
+ openCount: openPathIds.length,
567
+ portalMissingCount: portalMissingIds.length,
568
+ submenuCount: items.length,
569
+ });
570
+ return {
571
+ state,
572
+ submenuCount: items.length,
573
+ openCount: openPathIds.length,
574
+ mountedCount: mountedIds.length,
575
+ portalMissingCount: portalMissingIds.length,
576
+ openPathIds,
577
+ mountedIds,
578
+ portalMissingIds,
579
+ portalTargetIds,
580
+ items,
581
+ attrs: {
582
+ 'data-menu-submenu-portal-contract': 'true',
583
+ 'data-menu-submenu-portal-state': state,
584
+ 'data-submenu-count': items.length,
585
+ 'data-open-submenu-count': openPathIds.length,
586
+ 'data-mounted-submenu-count': mountedIds.length,
587
+ 'data-portal-missing-count': portalMissingIds.length,
588
+ 'data-open-path-ids': openPathIds.join(','),
589
+ 'data-mounted-submenu-ids': mountedIds.join(','),
590
+ 'data-portal-missing-ids': portalMissingIds.join(','),
591
+ 'data-portal-target-ids': portalTargetIds.join(','),
592
+ },
593
+ };
594
+ }
595
+ export function getNextMenuItemId(items, activeId, key) {
596
+ return getNextRovingId(menuFocusableItems(items), activeId, key, 'vertical', true);
597
+ }
598
+ export function createDrawerOverlayPlans(options) {
599
+ const side = options.side ?? 'right';
600
+ const size = options.size ?? 'md';
601
+ const plans = createDialogOverlayPlans({ ...options, modal: true });
602
+ return {
603
+ ...plans,
604
+ surface: {
605
+ ...plans.surface,
606
+ className: classNames(plans.surface.className, 'kui-drawer', `kui-drawer--${side}`, `kui-drawer--${size}`, options.className),
607
+ attrs: {
608
+ ...plans.surface.attrs,
609
+ ...options.attrs,
610
+ 'data-side': side,
611
+ 'data-size': size,
612
+ 'data-long-content': options.longContent === true ? 'true' : undefined,
613
+ },
614
+ },
615
+ };
616
+ }
617
+ export function createDrawerPlans(options) {
618
+ const side = options.side ?? 'right';
619
+ const size = options.size ?? 'md';
620
+ const plans = createDialogPlans({
621
+ id: options.id,
622
+ titleId: options.titleId,
623
+ modal: true,
624
+ size: size === 'full' ? 'lg' : size,
625
+ ...(options.descriptionId === undefined ? {} : { descriptionId: options.descriptionId }),
626
+ ...(options.closeLabel === undefined ? {} : { closeLabel: options.closeLabel }),
627
+ });
628
+ return {
629
+ ...plans,
630
+ side,
631
+ surface: {
632
+ ...plans.surface,
633
+ className: classNames(plans.surface.className, 'kui-drawer', `kui-drawer--${side}`, `kui-drawer--${size}`, options.className),
634
+ attrs: {
635
+ ...plans.surface.attrs,
636
+ ...options.attrs,
637
+ 'data-side': side,
638
+ 'data-size': size,
639
+ 'data-long-content': options.longContent === true ? 'true' : undefined,
640
+ },
641
+ },
642
+ };
643
+ }
644
+ export function createDrawerNestedOverlayCoordinationContract(options) {
645
+ const nestedOverlayIds = normalizeUniqueIds(options.nestedOverlayIds);
646
+ const nestedOverlaySet = new Set(nestedOverlayIds);
647
+ const nestedModalIds = normalizeKnownIds(options.nestedModalIds, nestedOverlaySet);
648
+ const focusReturnId = normalizedDisabledReason(options.focusReturnId);
649
+ const activeOverlayId = normalizedDisabledReason(options.activeOverlayId) || nestedOverlayIds.at(-1) || '';
650
+ const scrollContainerId = normalizedDisabledReason(options.scrollContainerId);
651
+ const scrollContained = options.scrollContained === true || scrollContainerId !== '' || nestedOverlayIds.length > 0;
652
+ const actions = drawerPinnedActionContracts(options);
653
+ const pinnedActionIds = actions.map((action) => action.id);
654
+ const pendingActionIds = actions
655
+ .filter((action) => action.state === 'pending')
656
+ .map((action) => action.id);
657
+ const disabledActionIds = actions
658
+ .filter((action) => action.state === 'disabled')
659
+ .map((action) => action.id);
660
+ const state = drawerNestedOverlayCoordinationState({
661
+ focusReturnId,
662
+ nestedOverlayCount: nestedOverlayIds.length,
663
+ pendingActionCount: pendingActionIds.length,
664
+ });
665
+ return {
666
+ state,
667
+ nestedOverlayCount: nestedOverlayIds.length,
668
+ nestedModalCount: nestedModalIds.length,
669
+ pinnedActionCount: actions.length,
670
+ pendingActionCount: pendingActionIds.length,
671
+ disabledActionCount: disabledActionIds.length,
672
+ activeOverlayId,
673
+ focusReturnId,
674
+ scrollContainerId,
675
+ scrollContained,
676
+ nestedOverlayIds,
677
+ nestedModalIds,
678
+ pinnedActionIds,
679
+ pendingActionIds,
680
+ disabledActionIds,
681
+ actions,
682
+ attrs: {
683
+ 'data-drawer-nested-overlay-contract': 'true',
684
+ 'data-drawer-coordination-state': state,
685
+ 'data-nested-overlay-count': nestedOverlayIds.length,
686
+ 'data-nested-modal-count': nestedModalIds.length,
687
+ 'data-active-overlay-id': activeOverlayId,
688
+ 'data-nested-overlay-ids': nestedOverlayIds.join(','),
689
+ 'data-nested-modal-ids': nestedModalIds.join(','),
690
+ 'data-focus-return-id': focusReturnId,
691
+ 'data-scroll-container-id': scrollContainerId,
692
+ 'data-scroll-contained': scrollContained ? 'true' : undefined,
693
+ 'data-pinned-action-count': actions.length,
694
+ 'data-pending-action-count': pendingActionIds.length,
695
+ 'data-disabled-action-count': disabledActionIds.length,
696
+ 'data-pinned-action-ids': pinnedActionIds.join(','),
697
+ 'data-pending-action-ids': pendingActionIds.join(','),
698
+ 'data-disabled-action-ids': disabledActionIds.join(','),
699
+ 'aria-busy': pendingActionIds.length > 0 ? 'true' : undefined,
700
+ },
701
+ };
702
+ }
703
+ export function createStepsPlans(options) {
704
+ const orientation = options.orientation ?? 'horizontal';
705
+ const currentId = options.currentId ?? options.items.find((item) => item.disabled !== true)?.id;
706
+ return {
707
+ root: createUiDomPlan('nav', 'steps', {
708
+ ...(options.className === undefined ? {} : { className: options.className }),
709
+ attrs: {
710
+ id: options.id,
711
+ 'aria-label': options.label ?? 'Steps',
712
+ ...options.attrs,
713
+ 'data-orientation': orientation,
714
+ 'data-current-id': currentId,
715
+ 'data-count': options.items.length,
716
+ },
717
+ }),
718
+ list: createUiDomPlan('ol', 'steps-list', { attrs: { role: 'list' } }),
719
+ items: options.items.map((item, index) => createUiDomPlan('li', 'step', {
720
+ state: stepStatus(item, currentId),
721
+ attrs: {
722
+ 'data-step-id': item.id,
723
+ 'data-index': index,
724
+ 'data-status': stepStatus(item, currentId),
725
+ 'data-timeline-index': item.timelineIndex,
726
+ 'data-timeline-state': item.timelineState,
727
+ 'data-previous-step-id': item.previousStepId,
728
+ 'data-next-step-id': item.nextStepId,
729
+ 'data-validation-state': item.validationState,
730
+ 'data-blocking-reason': item.blockingReason,
731
+ 'data-return-state': item.returnState,
732
+ 'data-returnable': item.returnable === true ? 'true' : undefined,
733
+ 'aria-invalid': item.validationState === 'invalid' ? 'true' : undefined,
734
+ },
735
+ })),
736
+ indicators: options.items.map((item, index) => createUiDomPlan('span', 'step-indicator', {
737
+ attrs: {
738
+ 'aria-hidden': 'true',
739
+ 'data-step-id': item.id,
740
+ 'data-index': index + 1,
741
+ 'data-status': stepStatus(item, currentId),
742
+ },
743
+ })),
744
+ triggers: options.items.map((item) => createStepTriggerPlan(item, currentId, options.clickable)),
745
+ descriptions: options.items.map((item) => item.description === undefined
746
+ ? undefined
747
+ : createUiDomPlan('p', 'step-description', {
748
+ attrs: { 'data-description': item.description },
749
+ })),
750
+ };
751
+ }
752
+ export function getNextStepId(items, activeId, key, orientation = 'horizontal') {
753
+ return getNextRovingId(items, activeId, key, orientation, false);
754
+ }
755
+ export function createStepsTimelineValidationContract(options) {
756
+ const knownIds = new Set(options.items.map((item) => item.id));
757
+ const completedIds = normalizeKnownIds(options.completedIds, knownIds);
758
+ const returnableInputIds = normalizeKnownIds(options.returnableIds, knownIds);
759
+ const completedSet = new Set(completedIds);
760
+ const returnableSet = new Set(returnableInputIds);
761
+ const currentId = options.currentId !== undefined && knownIds.has(options.currentId)
762
+ ? options.currentId
763
+ : (options.items.find((item) => item.disabled !== true)?.id ?? '');
764
+ const currentIndex = options.items.findIndex((item) => item.id === currentId);
765
+ const items = options.items.map((item, index) => stepsTimelineValidationItemContract(item, {
766
+ blockingReason: normalizedDisabledReason(options.blockingReasons?.[item.id]),
767
+ completed: completedSet.has(item.id),
768
+ current: item.id === currentId,
769
+ currentIndex,
770
+ index,
771
+ nextStepId: options.items[index + 1]?.id ?? '',
772
+ previousStepId: options.items[index - 1]?.id ?? '',
773
+ returnable: returnableSet.has(item.id),
774
+ validationState: options.validationStates?.[item.id],
775
+ }));
776
+ const returnableIds = items
777
+ .filter((item) => item.returnState === 'returnable')
778
+ .map((item) => item.id);
779
+ const blockedIds = items
780
+ .filter((item) => item.validationState === 'blocked')
781
+ .map((item) => item.id);
782
+ const invalidIds = items
783
+ .filter((item) => item.validationState === 'invalid')
784
+ .map((item) => item.id);
785
+ const warningIds = items
786
+ .filter((item) => item.validationState === 'warning')
787
+ .map((item) => item.id);
788
+ const state = stepsTimelineValidationState({
789
+ blockedCount: blockedIds.length,
790
+ invalidCount: invalidIds.length,
791
+ stepCount: options.items.length,
792
+ warningCount: warningIds.length,
793
+ });
794
+ return {
795
+ state,
796
+ currentId,
797
+ stepCount: options.items.length,
798
+ completedCount: completedIds.length,
799
+ returnableCount: returnableIds.length,
800
+ blockedCount: blockedIds.length,
801
+ invalidCount: invalidIds.length,
802
+ warningCount: warningIds.length,
803
+ completedIds,
804
+ returnableIds,
805
+ blockedIds,
806
+ invalidIds,
807
+ warningIds,
808
+ items,
809
+ attrs: {
810
+ 'data-steps-timeline-validation-contract': 'true',
811
+ 'data-steps-validation-state': state,
812
+ 'data-current-step-id': currentId,
813
+ 'data-step-count': options.items.length,
814
+ 'data-completed-step-count': completedIds.length,
815
+ 'data-returnable-step-count': returnableIds.length,
816
+ 'data-blocked-step-count': blockedIds.length,
817
+ 'data-invalid-step-count': invalidIds.length,
818
+ 'data-warning-step-count': warningIds.length,
819
+ 'data-completed-step-ids': completedIds.join(','),
820
+ 'data-returnable-step-ids': returnableIds.join(','),
821
+ 'data-blocked-step-ids': blockedIds.join(','),
822
+ 'data-invalid-step-ids': invalidIds.join(','),
823
+ 'data-warning-step-ids': warningIds.join(','),
824
+ },
825
+ };
826
+ }
827
+ export function createCollapsePlans(options) {
828
+ const mode = options.mode ?? 'multiple';
829
+ const openIds = normalizedOpenIds(options.openIds);
830
+ const effectiveOpenIds = mode === 'accordion' ? new Set([...openIds].slice(0, 1)) : new Set(openIds);
831
+ return {
832
+ root: createUiDomPlan('div', 'collapse', {
833
+ ...(options.className === undefined ? {} : { className: options.className }),
834
+ attrs: {
835
+ id: options.id,
836
+ role: mode === 'accordion' ? 'presentation' : undefined,
837
+ 'aria-label': options.label,
838
+ ...options.attrs,
839
+ 'data-mode': mode,
840
+ 'data-open-count': effectiveOpenIds.size,
841
+ 'data-animated': options.animated === true ? 'true' : undefined,
842
+ },
843
+ }),
844
+ items: options.items.map((item) => {
845
+ const open = effectiveOpenIds.has(item.id);
846
+ return createUiDomPlan('div', 'collapse-item', {
847
+ state: item.disabled === true ? 'disabled' : open ? 'open' : 'closed',
848
+ ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
849
+ attrs: {
850
+ 'data-item-id': item.id,
851
+ 'data-open': open ? 'true' : 'false',
852
+ 'data-lazy-state': item.lazyState,
853
+ 'data-lazy-error': item.lazyError,
854
+ 'data-persisted-open': item.persistedOpen === true ? 'true' : undefined,
855
+ 'data-nested-level': item.nestedLevel,
856
+ 'data-parent-panel-id': item.parentPanelId,
857
+ 'data-disabled-reason': item.disabledReason,
858
+ },
859
+ });
860
+ }),
861
+ triggers: options.items.map((item) => {
862
+ const open = effectiveOpenIds.has(item.id);
863
+ return createUiDomPlan('button', 'collapse-trigger', {
864
+ state: item.disabled === true ? 'disabled' : open ? 'open' : 'closed',
865
+ ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
866
+ attrs: {
867
+ id: `${options.id}-${item.id}-trigger`,
868
+ type: 'button',
869
+ 'aria-expanded': open ? 'true' : 'false',
870
+ 'aria-controls': `${options.id}-${item.id}-panel`,
871
+ 'data-item-id': item.id,
872
+ 'data-title': item.title,
873
+ 'data-lazy-state': item.lazyState,
874
+ 'data-lazy-error': item.lazyError,
875
+ 'data-persisted-open': item.persistedOpen === true ? 'true' : undefined,
876
+ 'data-nested-level': item.nestedLevel,
877
+ 'data-parent-panel-id': item.parentPanelId,
878
+ 'data-disabled-reason': item.disabledReason,
879
+ 'aria-busy': item.lazyState === 'loading' ? 'true' : undefined,
880
+ },
881
+ });
882
+ }),
883
+ indicators: options.items.map((item) => createUiDomPlan('span', 'collapse-indicator', {
884
+ attrs: {
885
+ 'aria-hidden': 'true',
886
+ 'data-item-id': item.id,
887
+ 'data-open': effectiveOpenIds.has(item.id) ? 'true' : 'false',
888
+ 'data-lazy-state': item.lazyState,
889
+ },
890
+ })),
891
+ panels: options.items.map((item) => {
892
+ const open = effectiveOpenIds.has(item.id);
893
+ return createUiDomPlan('div', 'collapse-panel', {
894
+ state: open ? 'open' : 'closed',
895
+ attrs: {
896
+ id: `${options.id}-${item.id}-panel`,
897
+ role: 'region',
898
+ 'aria-labelledby': `${options.id}-${item.id}-trigger`,
899
+ hidden: open ? undefined : true,
900
+ 'data-item-id': item.id,
901
+ 'data-description': item.description,
902
+ 'data-lazy-state': item.lazyState,
903
+ 'data-lazy-error': item.lazyError,
904
+ 'data-persisted-open': item.persistedOpen === true ? 'true' : undefined,
905
+ 'data-nested-level': item.nestedLevel,
906
+ 'data-parent-panel-id': item.parentPanelId,
907
+ 'aria-busy': item.lazyState === 'loading' ? 'true' : undefined,
908
+ },
909
+ });
910
+ }),
911
+ };
912
+ }
913
+ export function createCollapseLazyPersistenceContract(options) {
914
+ const knownIds = new Set(options.items.map((item) => item.id));
915
+ const openIds = normalizeKnownIds(options.openIds, knownIds);
916
+ const persistedOpenIds = normalizeKnownIds(options.persistedOpenIds, knownIds);
917
+ const lazyLoadingIds = normalizeKnownIds(options.lazyLoadingIds, knownIds);
918
+ const lazyLoadedIds = normalizeKnownIds(options.lazyLoadedIds, knownIds);
919
+ const persistedOpenSet = new Set(persistedOpenIds);
920
+ const lazyLoadingSet = new Set(lazyLoadingIds);
921
+ const lazyLoadedSet = new Set(lazyLoadedIds);
922
+ const items = options.items.map((item) => collapseLazyPersistenceItemContract(item, {
923
+ disabledReason: normalizedDisabledReason(options.disabledReasons?.[item.id]),
924
+ lazyError: normalizedDisabledReason(options.lazyErrors?.[item.id]),
925
+ lazyLoaded: lazyLoadedSet.has(item.id),
926
+ lazyLoading: lazyLoadingSet.has(item.id),
927
+ nestedLevel: normalizedMenuItemCount(options.nestedLevels?.[item.id]) ?? 0,
928
+ parentPanelId: normalizedDisabledReason(options.nestedParents?.[item.id]),
929
+ persistedOpen: persistedOpenSet.has(item.id),
930
+ }));
931
+ const lazyErrorIds = items.filter((item) => item.lazyState === 'error').map((item) => item.id);
932
+ const disabledReasonIds = items
933
+ .filter((item) => item.disabledReason !== '')
934
+ .map((item) => item.id);
935
+ const nestedIds = items.filter((item) => item.nestedLevel > 0).map((item) => item.id);
936
+ const state = lazyLoadingIds.length > 0
937
+ ? 'loading'
938
+ : lazyErrorIds.length > 0
939
+ ? 'lazy-error'
940
+ : disabledReasonIds.length > 0
941
+ ? 'disabled-limited'
942
+ : 'ready';
943
+ const persistenceState = persistedOpenIds.length === 0
944
+ ? 'empty'
945
+ : arraysEqual(openIds, persistedOpenIds)
946
+ ? 'restored'
947
+ : 'dirty';
948
+ return {
949
+ state,
950
+ persistenceState,
951
+ itemCount: items.length,
952
+ lazyLoadingCount: lazyLoadingIds.length,
953
+ lazyLoadedCount: lazyLoadedIds.length,
954
+ lazyErrorCount: lazyErrorIds.length,
955
+ persistedOpenCount: persistedOpenIds.length,
956
+ disabledReasonCount: disabledReasonIds.length,
957
+ nestedCount: nestedIds.length,
958
+ openIds,
959
+ persistedOpenIds,
960
+ lazyLoadingIds,
961
+ lazyLoadedIds,
962
+ lazyErrorIds,
963
+ disabledReasonIds,
964
+ nestedIds,
965
+ items,
966
+ attrs: {
967
+ 'data-collapse-lazy-persistence-contract': 'true',
968
+ 'data-collapse-advanced-state': state,
969
+ 'data-collapse-persistence-state': persistenceState,
970
+ 'data-item-count': items.length,
971
+ 'data-lazy-loading-count': lazyLoadingIds.length,
972
+ 'data-lazy-loaded-count': lazyLoadedIds.length,
973
+ 'data-lazy-error-count': lazyErrorIds.length,
974
+ 'data-persisted-open-count': persistedOpenIds.length,
975
+ 'data-disabled-reason-count': disabledReasonIds.length,
976
+ 'data-nested-count': nestedIds.length,
977
+ 'data-open-ids': openIds.join(','),
978
+ 'data-persisted-open-ids': persistedOpenIds.join(','),
979
+ 'data-lazy-loading-ids': lazyLoadingIds.join(','),
980
+ 'data-lazy-loaded-ids': lazyLoadedIds.join(','),
981
+ 'data-lazy-error-ids': lazyErrorIds.join(','),
982
+ 'data-disabled-reason-ids': disabledReasonIds.join(','),
983
+ 'data-nested-ids': nestedIds.join(','),
984
+ 'aria-busy': lazyLoadingIds.length > 0 ? 'true' : undefined,
985
+ },
986
+ };
987
+ }
988
+ export function getNextCollapseTriggerId(items, activeId, key) {
989
+ return getNextRovingId(items.map((item) => ({
990
+ id: item.id,
991
+ label: item.title,
992
+ ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
993
+ })), activeId, key, 'vertical', true);
994
+ }
995
+ export function getNextAccordionOpenIds(openIds, itemId) {
996
+ return openIds.has(itemId) ? new Set() : new Set([itemId]);
997
+ }
998
+ export function getNextCollapseOpenIds(openIds, itemId) {
999
+ const next = new Set(openIds);
1000
+ if (next.has(itemId)) {
1001
+ next.delete(itemId);
1002
+ }
1003
+ else {
1004
+ next.add(itemId);
1005
+ }
1006
+ return next;
1007
+ }
1008
+ function commandPaletteItemDomId(rootId, itemId) {
1009
+ return `${rootId}-${itemId}-command`;
1010
+ }
1011
+ const defaultCommandPaletteRecentGroup = { id: 'recent', label: 'Recent' };
1012
+ function commandPalettePermissionRecentItemContract(item, options) {
1013
+ const permission = options.deniedIds.has(item.id) ||
1014
+ (options.allowedIds.size > 0 && !options.allowedIds.has(item.id))
1015
+ ? 'denied'
1016
+ : 'allowed';
1017
+ const disabledReason = permission === 'denied' && options.disabledReason === ''
1018
+ ? 'Permission required'
1019
+ : options.disabledReason;
1020
+ const disabled = item.disabled === true || permission === 'denied';
1021
+ const contractItem = {
1022
+ ...item,
1023
+ ...(options.recent ? { groupId: options.recentGroupId, kind: 'recent', recent: true } : {}),
1024
+ disabled,
1025
+ ...(disabledReason === '' ? {} : { disabledReason }),
1026
+ permission,
1027
+ };
1028
+ return {
1029
+ id: item.id,
1030
+ index: options.index,
1031
+ item: contractItem,
1032
+ permission,
1033
+ recent: options.recent,
1034
+ disabledReason,
1035
+ attrs: {
1036
+ 'data-command-id': item.id,
1037
+ 'data-permission': permission,
1038
+ 'data-recent': options.recent ? 'true' : undefined,
1039
+ 'data-disabled-reason': disabledReason,
1040
+ 'aria-disabled': disabled ? 'true' : undefined,
1041
+ },
1042
+ };
1043
+ }
1044
+ function commandPaletteGroupsForItems(items, groups) {
1045
+ const seen = new Set();
1046
+ const result = [];
1047
+ for (const item of items) {
1048
+ const groupId = item.groupId;
1049
+ if (groupId === undefined || seen.has(groupId))
1050
+ continue;
1051
+ seen.add(groupId);
1052
+ result.push(groups.find((group) => group.id === groupId) ?? { id: groupId, label: groupId });
1053
+ }
1054
+ return result;
1055
+ }
1056
+ function commandPaletteItemMatches(item, keyword) {
1057
+ return (item.label.toLowerCase().includes(keyword) ||
1058
+ item.description?.toLowerCase().includes(keyword) === true ||
1059
+ item.href?.toLowerCase().includes(keyword) === true ||
1060
+ item.keywords?.some((value) => value.toLowerCase().includes(keyword)) === true);
1061
+ }
1062
+ function commandPaletteRecentSort(a, b, recentOrder) {
1063
+ const aRecent = recentOrder.get(a.id);
1064
+ const bRecent = recentOrder.get(b.id);
1065
+ if (aRecent !== undefined && bRecent !== undefined)
1066
+ return aRecent - bRecent;
1067
+ if (aRecent !== undefined)
1068
+ return -1;
1069
+ if (bRecent !== undefined)
1070
+ return 1;
1071
+ return a.index - b.index;
1072
+ }
1073
+ function commandPalettePermissionRecentState(options) {
1074
+ if (options.totalCount === 0)
1075
+ return 'empty';
1076
+ if (options.visibleCount === 0)
1077
+ return 'filtered';
1078
+ if (options.deniedCount > 0)
1079
+ return 'permission-limited';
1080
+ return 'ready';
1081
+ }
1082
+ function normalizeCommandIds(value) {
1083
+ if (value === undefined)
1084
+ return new Set();
1085
+ const values = Array.isArray(value)
1086
+ ? value
1087
+ : [...value];
1088
+ return new Set(values.filter((id) => id.length > 0));
1089
+ }
1090
+ function normalizeUniqueIds(value) {
1091
+ if (value === undefined)
1092
+ return [];
1093
+ const result = [];
1094
+ for (const id of value) {
1095
+ if (id === '' || result.includes(id))
1096
+ continue;
1097
+ result.push(id);
1098
+ }
1099
+ return result;
1100
+ }
1101
+ function normalizeKnownIds(value, knownIds) {
1102
+ if (value === undefined)
1103
+ return [];
1104
+ const values = Array.isArray(value)
1105
+ ? value
1106
+ : [...value];
1107
+ const result = [];
1108
+ for (const id of values) {
1109
+ if (!knownIds.has(id) || result.includes(id))
1110
+ continue;
1111
+ result.push(id);
1112
+ }
1113
+ return result;
1114
+ }
1115
+ function normalizeCommandPaletteRecentIds(value, knownIds, maxRecent = value.length) {
1116
+ const limit = Math.max(0, Math.trunc(maxRecent));
1117
+ if (limit === 0)
1118
+ return [];
1119
+ const result = [];
1120
+ for (const id of value) {
1121
+ if (!knownIds.has(id) || result.includes(id))
1122
+ continue;
1123
+ result.push(id);
1124
+ if (result.length >= limit)
1125
+ break;
1126
+ }
1127
+ return result;
1128
+ }
1129
+ function normalizedDisabledReason(value) {
1130
+ return value?.trim() ?? '';
1131
+ }
1132
+ function normalizedMenuItemCount(value) {
1133
+ if (value === undefined || !Number.isFinite(value))
1134
+ return undefined;
1135
+ return Math.max(0, Math.trunc(value));
1136
+ }
1137
+ function drawerPinnedActionContracts(options) {
1138
+ const actions = options.pinnedActions ?? [];
1139
+ const knownIds = new Set(actions.map((action) => action.id));
1140
+ const pendingIds = new Set(normalizeKnownIds(options.pendingActionIds, knownIds));
1141
+ const disabledIds = new Set(normalizeKnownIds(options.disabledActionIds, knownIds));
1142
+ return actions
1143
+ .filter((action, index) => action.id !== '' && actions.findIndex((item) => item.id === action.id) === index)
1144
+ .map((action) => drawerPinnedActionContract(action, {
1145
+ disabled: disabledIds.has(action.id),
1146
+ disabledReason: normalizedDisabledReason(options.disabledReasons?.[action.id]),
1147
+ pending: pendingIds.has(action.id),
1148
+ }));
1149
+ }
1150
+ function drawerPinnedActionContract(action, options) {
1151
+ const pending = options.pending || action.pending === true;
1152
+ const disabled = options.disabled || action.disabled === true;
1153
+ const disabledReason = options.disabledReason || normalizedDisabledReason(action.disabledReason);
1154
+ const state = pending ? 'pending' : disabled ? 'disabled' : 'available';
1155
+ const contractAction = {
1156
+ ...action,
1157
+ ...(disabled ? { disabled: true } : {}),
1158
+ ...(pending ? { pending: true } : {}),
1159
+ ...(disabledReason === '' ? {} : { disabledReason }),
1160
+ };
1161
+ return {
1162
+ id: action.id,
1163
+ action: contractAction,
1164
+ state,
1165
+ disabledReason,
1166
+ attrs: {
1167
+ 'data-action-id': action.id,
1168
+ 'data-pinned-action-state': state,
1169
+ 'data-disabled-reason': disabledReason,
1170
+ 'aria-disabled': state === 'disabled' ? 'true' : undefined,
1171
+ 'aria-busy': state === 'pending' ? 'true' : undefined,
1172
+ },
1173
+ };
1174
+ }
1175
+ function drawerNestedOverlayCoordinationState(options) {
1176
+ if (options.pendingActionCount > 0)
1177
+ return 'action-pending';
1178
+ if (options.nestedOverlayCount > 0)
1179
+ return 'nested-overlay-open';
1180
+ if (options.focusReturnId !== '')
1181
+ return 'focus-return-ready';
1182
+ return 'idle';
1183
+ }
1184
+ function collapseLazyPersistenceItemContract(item, options) {
1185
+ const lazyState = options.lazyLoading && options.lazyError === ''
1186
+ ? 'loading'
1187
+ : options.lazyError !== ''
1188
+ ? 'error'
1189
+ : options.lazyLoaded
1190
+ ? 'loaded'
1191
+ : (item.lazyState ?? 'idle');
1192
+ const disabledReason = options.disabledReason || normalizedDisabledReason(item.disabledReason);
1193
+ const nestedLevel = Math.max(0, options.nestedLevel);
1194
+ const contractItem = {
1195
+ ...item,
1196
+ lazyState,
1197
+ ...(options.lazyError === '' ? {} : { lazyError: options.lazyError }),
1198
+ ...(options.persistedOpen ? { persistedOpen: true } : {}),
1199
+ ...(nestedLevel === 0 ? {} : { nestedLevel }),
1200
+ ...(options.parentPanelId === '' ? {} : { parentPanelId: options.parentPanelId }),
1201
+ ...(disabledReason === '' ? {} : { disabled: true, disabledReason }),
1202
+ };
1203
+ return {
1204
+ id: item.id,
1205
+ item: contractItem,
1206
+ lazyState,
1207
+ lazyError: options.lazyError,
1208
+ persistedOpen: options.persistedOpen,
1209
+ disabledReason,
1210
+ nestedLevel,
1211
+ parentPanelId: options.parentPanelId,
1212
+ attrs: {
1213
+ 'data-item-id': item.id,
1214
+ 'data-lazy-state': lazyState,
1215
+ 'data-lazy-error': options.lazyError,
1216
+ 'data-persisted-open': options.persistedOpen ? 'true' : undefined,
1217
+ 'data-nested-level': nestedLevel,
1218
+ 'data-parent-panel-id': options.parentPanelId,
1219
+ 'data-disabled-reason': disabledReason,
1220
+ 'aria-disabled': disabledReason !== '' ? 'true' : undefined,
1221
+ 'aria-busy': lazyState === 'loading' ? 'true' : undefined,
1222
+ },
1223
+ };
1224
+ }
1225
+ function breadcrumbIds(items) {
1226
+ return items.map((item, index) => item.id ?? item.href ?? String(index));
1227
+ }
1228
+ function breadcrumbResponsiveItemContract(item, options) {
1229
+ const contractItem = {
1230
+ ...item,
1231
+ id: options.id,
1232
+ ...(options.icon ? { icon: item.icon ?? true } : {}),
1233
+ ...(options.collapsed ? { collapsed: true } : {}),
1234
+ ...(options.overflow ? { overflow: true } : {}),
1235
+ pathIndex: options.pathIndex,
1236
+ pathSummary: options.pathSummary,
1237
+ };
1238
+ return {
1239
+ id: options.id,
1240
+ item: contractItem,
1241
+ collapsed: options.collapsed,
1242
+ icon: options.icon,
1243
+ overflow: options.overflow,
1244
+ pathIndex: options.pathIndex,
1245
+ pathSummary: options.pathSummary,
1246
+ attrs: {
1247
+ 'data-item-id': options.id,
1248
+ 'data-icon': options.icon ? String(contractItem.icon ?? true) : undefined,
1249
+ 'data-collapsed': options.collapsed ? 'true' : undefined,
1250
+ 'data-overflow': options.overflow ? 'true' : undefined,
1251
+ 'data-path-index': options.pathIndex,
1252
+ 'data-path-summary': options.pathSummary,
1253
+ 'aria-hidden': options.collapsed && !options.overflow ? 'true' : undefined,
1254
+ },
1255
+ };
1256
+ }
1257
+ function tabsOverflowPersistenceItemContract(item, options) {
1258
+ const contractItem = {
1259
+ ...item,
1260
+ ...(options.disabledReason === ''
1261
+ ? {}
1262
+ : { disabled: true, disabledReason: options.disabledReason }),
1263
+ ...(options.overflow ? { overflow: true } : {}),
1264
+ ...(options.persistedActive ? { persistedActive: true } : {}),
1265
+ };
1266
+ return {
1267
+ id: item.id,
1268
+ item: contractItem,
1269
+ active: options.active,
1270
+ overflow: options.overflow,
1271
+ persistedActive: options.persistedActive,
1272
+ disabledReason: options.disabledReason,
1273
+ attrs: {
1274
+ 'data-tab-id': item.id,
1275
+ 'data-overflow': options.overflow ? 'true' : undefined,
1276
+ 'data-persisted-active': options.persistedActive ? 'true' : undefined,
1277
+ 'data-disabled-reason': options.disabledReason,
1278
+ 'aria-disabled': options.disabledReason !== '' ? 'true' : undefined,
1279
+ },
1280
+ };
1281
+ }
1282
+ function arraysEqual(a, b) {
1283
+ if (a.length !== b.length)
1284
+ return false;
1285
+ return a.every((value, index) => value === b[index]);
1286
+ }
1287
+ function getNextRovingId(items, activeId, key, orientation, verticalKeys) {
1288
+ const enabled = items.filter((item) => item.disabled !== true);
1289
+ if (enabled.length === 0)
1290
+ return activeId;
1291
+ const currentIndex = Math.max(0, enabled.findIndex((item) => item.id === activeId));
1292
+ const previousKey = orientation === 'vertical' || verticalKeys ? 'ArrowUp' : 'ArrowLeft';
1293
+ const nextKey = orientation === 'vertical' || verticalKeys ? 'ArrowDown' : 'ArrowRight';
1294
+ if (key === 'Home')
1295
+ return enabled[0]?.id ?? activeId;
1296
+ if (key === 'End')
1297
+ return enabled.at(-1)?.id ?? activeId;
1298
+ if (key === previousKey)
1299
+ return enabled.at(currentIndex - 1)?.id ?? enabled.at(-1)?.id ?? activeId;
1300
+ if (key === nextKey)
1301
+ return enabled[(currentIndex + 1) % enabled.length]?.id ?? activeId;
1302
+ return activeId;
1303
+ }
1304
+ function menuFocusableItems(items) {
1305
+ return items.filter((item) => item.disabled !== true && menuItemFocusable(item));
1306
+ }
1307
+ function activeMenuItemId(items, activeId) {
1308
+ const focusable = menuFocusableItems(items);
1309
+ return focusable.some((item) => item.id === activeId) ? activeId : focusable[0]?.id;
1310
+ }
1311
+ function menuItemFocusable(item) {
1312
+ return item.kind !== 'separator' && item.kind !== 'section';
1313
+ }
1314
+ function createMenuItemPlan(rootId, item, active) {
1315
+ const kind = item.kind ?? 'item';
1316
+ const separator = kind === 'separator';
1317
+ const section = kind === 'section';
1318
+ const submenuState = menuItemSubmenuState(item);
1319
+ const remoteActionState = menuItemRemoteActionState(item);
1320
+ return createUiDomPlan('div', section ? 'menu-section' : 'menu-item', {
1321
+ state: item.disabled === true
1322
+ ? 'disabled'
1323
+ : active
1324
+ ? 'active'
1325
+ : item.checked === true
1326
+ ? 'checked'
1327
+ : 'idle',
1328
+ ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
1329
+ ...(item.danger === true ? { className: 'kui-menu-item--danger' } : {}),
1330
+ attrs: {
1331
+ id: `${rootId}-${item.id}`,
1332
+ role: separator ? 'separator' : section ? 'presentation' : menuItemRole(kind),
1333
+ tabindex: menuItemFocusable(item) ? (active ? 0 : -1) : undefined,
1334
+ 'aria-checked': kind === 'checkbox' || kind === 'radio'
1335
+ ? item.checked === true
1336
+ ? 'true'
1337
+ : 'false'
1338
+ : undefined,
1339
+ 'aria-haspopup': kind === 'submenu' ? 'menu' : undefined,
1340
+ 'aria-expanded': kind === 'submenu' ? (item.expanded === true ? 'true' : 'false') : undefined,
1341
+ 'aria-disabled': item.disabled === true ? 'true' : undefined,
1342
+ 'aria-busy': item.remoteActionLoading === true || item.submenuLoading === true ? 'true' : undefined,
1343
+ 'data-menu-id': rootId,
1344
+ 'data-item-id': item.id,
1345
+ 'data-kind': kind,
1346
+ 'data-label': item.label,
1347
+ 'data-shortcut': item.shortcut,
1348
+ 'data-section-id': item.sectionId,
1349
+ 'data-danger': item.danger === true ? 'true' : undefined,
1350
+ 'data-permission': item.permission,
1351
+ 'data-disabled-reason': item.disabledReason,
1352
+ 'data-remote-action': item.remoteAction === true ? 'true' : undefined,
1353
+ 'data-remote-action-state': remoteActionState,
1354
+ 'data-remote-action-disabled-reason': item.remoteActionDisabledReason,
1355
+ 'data-remote-action-error': item.remoteActionError,
1356
+ 'data-can-invoke-remote-action': remoteActionState === 'available' && item.disabled !== true ? 'true' : undefined,
1357
+ 'data-submenu-state': submenuState,
1358
+ 'data-submenu-error': item.submenuError,
1359
+ 'data-submenu-item-count': item.submenuItemCount,
1360
+ 'data-submenu-portal-state': item.submenuPortalState,
1361
+ 'data-submenu-portal-target': item.submenuPortalTargetId,
1362
+ 'data-submenu-open-path-index': item.submenuOpenPathIndex,
1363
+ 'data-submenu-focus-return': item.submenuFocusReturnId,
1364
+ 'data-submenu-parent-overlay': item.submenuParentOverlayId,
1365
+ 'data-submenu-placement': item.submenuPlacement,
1366
+ 'aria-controls': item.submenuPortalTargetId,
1367
+ },
1368
+ });
1369
+ }
1370
+ function menuSubmenuPortalItemContract(item, options) {
1371
+ const mounted = options.mounted || options.open;
1372
+ const state = options.open && options.portalTargetId === ''
1373
+ ? 'portal-missing'
1374
+ : options.open
1375
+ ? 'open'
1376
+ : mounted
1377
+ ? 'mounted'
1378
+ : 'closed';
1379
+ const openPathIndex = options.openPathIndex < 0 ? undefined : options.openPathIndex;
1380
+ const contractItem = {
1381
+ ...item,
1382
+ ...(options.open ? { expanded: true } : {}),
1383
+ submenuPortalState: state,
1384
+ ...(options.portalTargetId === '' ? {} : { submenuPortalTargetId: options.portalTargetId }),
1385
+ ...(openPathIndex === undefined ? {} : { submenuOpenPathIndex: openPathIndex }),
1386
+ ...(options.focusReturnId === '' ? {} : { submenuFocusReturnId: options.focusReturnId }),
1387
+ ...(options.parentOverlayId === '' ? {} : { submenuParentOverlayId: options.parentOverlayId }),
1388
+ ...(options.placement === undefined ? {} : { submenuPlacement: options.placement }),
1389
+ };
1390
+ return {
1391
+ id: item.id,
1392
+ item: contractItem,
1393
+ state,
1394
+ open: options.open,
1395
+ mounted,
1396
+ portalTargetId: options.portalTargetId,
1397
+ focusReturnId: options.focusReturnId,
1398
+ parentOverlayId: options.parentOverlayId,
1399
+ placement: options.placement,
1400
+ openPathIndex,
1401
+ attrs: {
1402
+ 'data-item-id': item.id,
1403
+ 'data-submenu-portal-state': state,
1404
+ 'data-submenu-portal-target': options.portalTargetId,
1405
+ 'data-submenu-open-path-index': openPathIndex,
1406
+ 'data-submenu-focus-return': options.focusReturnId,
1407
+ 'data-submenu-parent-overlay': options.parentOverlayId,
1408
+ 'data-submenu-placement': options.placement,
1409
+ 'aria-controls': options.portalTargetId === '' ? undefined : options.portalTargetId,
1410
+ 'aria-expanded': options.open ? 'true' : item.expanded === true ? 'true' : 'false',
1411
+ },
1412
+ };
1413
+ }
1414
+ function menuPermissionRemoteActionItemContract(item, options) {
1415
+ const permission = options.denied || (options.hasAllowedIds && !options.allowed) ? 'denied' : 'allowed';
1416
+ const baseDisabledReason = options.disabledReason || normalizedDisabledReason(item.disabledReason);
1417
+ const disabledReason = permission === 'denied' && baseDisabledReason === ''
1418
+ ? 'Permission required'
1419
+ : baseDisabledReason;
1420
+ const disabled = options.disabled || item.disabled === true || permission === 'denied';
1421
+ const remoteAction = options.remoteAction ||
1422
+ options.remoteActionDisabled ||
1423
+ options.remoteActionLoading ||
1424
+ options.remoteActionError !== '' ||
1425
+ item.remoteAction === true ||
1426
+ item.remoteActionDisabled === true ||
1427
+ item.remoteActionLoading === true ||
1428
+ normalizedDisabledReason(item.remoteActionError) !== '';
1429
+ const remoteActionError = options.remoteActionError || normalizedDisabledReason(item.remoteActionError);
1430
+ const remoteActionDisabled = options.remoteActionDisabled || item.remoteActionDisabled === true;
1431
+ const remoteActionLoading = options.remoteActionLoading || item.remoteActionLoading === true;
1432
+ const remoteActionDisabledReason = options.remoteActionDisabledReason || normalizedDisabledReason(item.remoteActionDisabledReason);
1433
+ const remoteActionState = remoteActionLoading && remoteActionError === ''
1434
+ ? 'pending'
1435
+ : remoteActionDisabled || disabled || remoteActionError !== ''
1436
+ ? 'disabled'
1437
+ : 'available';
1438
+ const submenuError = options.submenuError || normalizedDisabledReason(item.submenuError);
1439
+ const submenuLoading = options.submenuLoading || item.submenuLoading === true;
1440
+ const submenuItemCount = options.submenuItemCount ?? item.submenuItemCount;
1441
+ const contractItem = {
1442
+ ...item,
1443
+ disabled,
1444
+ ...(disabledReason === '' ? {} : { disabledReason }),
1445
+ permission,
1446
+ ...(remoteAction ? { remoteAction: true } : {}),
1447
+ ...(remoteActionState === 'disabled' ? { remoteActionDisabled: true } : {}),
1448
+ ...(remoteActionState === 'pending' ? { remoteActionLoading: true } : {}),
1449
+ ...(remoteActionDisabledReason === '' ? {} : { remoteActionDisabledReason }),
1450
+ ...(remoteActionError === '' ? {} : { remoteActionError }),
1451
+ ...(submenuError === '' ? {} : { submenuError }),
1452
+ ...(submenuItemCount === undefined ? {} : { submenuItemCount }),
1453
+ ...(submenuLoading ? { submenuLoading: true } : {}),
1454
+ };
1455
+ const submenuState = menuItemSubmenuState(contractItem);
1456
+ return {
1457
+ id: item.id,
1458
+ item: contractItem,
1459
+ permission,
1460
+ disabled,
1461
+ disabledReason,
1462
+ remoteAction,
1463
+ remoteActionState,
1464
+ remoteActionDisabledReason,
1465
+ remoteActionError,
1466
+ submenuState,
1467
+ submenuError,
1468
+ submenuItemCount,
1469
+ attrs: {
1470
+ 'data-item-id': item.id,
1471
+ 'data-permission': permission,
1472
+ 'data-disabled-reason': disabledReason,
1473
+ 'data-remote-action': remoteAction ? 'true' : undefined,
1474
+ 'data-remote-action-state': remoteAction ? remoteActionState : undefined,
1475
+ 'data-remote-action-disabled-reason': remoteActionDisabledReason,
1476
+ 'data-remote-action-error': remoteActionError,
1477
+ 'data-can-invoke-remote-action': remoteAction && remoteActionState === 'available' && !disabled ? 'true' : undefined,
1478
+ 'data-submenu-state': submenuState,
1479
+ 'data-submenu-error': submenuError,
1480
+ 'data-submenu-item-count': submenuItemCount,
1481
+ 'aria-disabled': disabled ? 'true' : undefined,
1482
+ 'aria-busy': remoteActionState === 'pending' || submenuState === 'loading' ? 'true' : undefined,
1483
+ },
1484
+ };
1485
+ }
1486
+ function menuPermissionRemoteActionState(options) {
1487
+ if (options.itemCount === 0)
1488
+ return 'empty';
1489
+ if (options.remoteActionLoadingCount > 0)
1490
+ return 'remote-pending';
1491
+ if (options.submenuErrorCount > 0)
1492
+ return 'submenu-error';
1493
+ if (options.deniedCount > 0)
1494
+ return 'permission-limited';
1495
+ return 'ready';
1496
+ }
1497
+ function menuSubmenuPortalContractState(options) {
1498
+ if (options.submenuCount === 0)
1499
+ return 'empty';
1500
+ if (options.portalMissingCount > 0)
1501
+ return 'portal-missing';
1502
+ if (options.openCount > 0)
1503
+ return 'open';
1504
+ if (options.mountedCount > 0)
1505
+ return 'mounted';
1506
+ return 'closed';
1507
+ }
1508
+ function menuItemRemoteActionState(item) {
1509
+ if (item.remoteAction !== true)
1510
+ return undefined;
1511
+ if (item.remoteActionLoading === true &&
1512
+ normalizedDisabledReason(item.remoteActionError) === '') {
1513
+ return 'pending';
1514
+ }
1515
+ if (item.remoteActionDisabled === true ||
1516
+ item.disabled === true ||
1517
+ normalizedDisabledReason(item.remoteActionError) !== '') {
1518
+ return 'disabled';
1519
+ }
1520
+ return 'available';
1521
+ }
1522
+ function menuItemSubmenuState(item) {
1523
+ if (item.kind !== 'submenu')
1524
+ return undefined;
1525
+ if (item.submenuLoading === true)
1526
+ return 'loading';
1527
+ if (normalizedDisabledReason(item.submenuError) !== '')
1528
+ return 'error';
1529
+ if (item.expanded === true)
1530
+ return 'open';
1531
+ if ((item.submenuItemCount ?? 0) > 0)
1532
+ return 'ready';
1533
+ return 'closed';
1534
+ }
1535
+ function menuItemRole(kind) {
1536
+ if (kind === 'checkbox')
1537
+ return 'menuitemcheckbox';
1538
+ if (kind === 'radio')
1539
+ return 'menuitemradio';
1540
+ return 'menuitem';
1541
+ }
1542
+ function stepStatus(item, currentId) {
1543
+ if (item.disabled === true)
1544
+ return 'disabled';
1545
+ if (item.status !== undefined)
1546
+ return item.status;
1547
+ if (item.id === currentId)
1548
+ return 'current';
1549
+ return 'pending';
1550
+ }
1551
+ function stepsTimelineValidationItemContract(item, options) {
1552
+ const timelineState = options.current
1553
+ ? 'current'
1554
+ : options.currentIndex >= 0 && options.index < options.currentIndex
1555
+ ? 'past'
1556
+ : options.completed
1557
+ ? 'past'
1558
+ : 'future';
1559
+ const blockingReason = options.blockingReason || normalizedDisabledReason(item.blockingReason);
1560
+ const validationState = blockingReason !== ''
1561
+ ? 'blocked'
1562
+ : (options.validationState ??
1563
+ item.validationState ??
1564
+ (options.completed ? 'valid' : 'unchecked'));
1565
+ const returnable = options.returnable && !options.current && item.disabled !== true;
1566
+ const returnState = options.current
1567
+ ? 'current'
1568
+ : returnable
1569
+ ? 'returnable'
1570
+ : 'locked';
1571
+ const contractItem = {
1572
+ ...item,
1573
+ timelineIndex: options.index,
1574
+ timelineState,
1575
+ validationState,
1576
+ ...(blockingReason === '' ? {} : { blockingReason }),
1577
+ returnState,
1578
+ ...(returnable ? { returnable: true } : {}),
1579
+ ...(options.previousStepId === '' ? {} : { previousStepId: options.previousStepId }),
1580
+ ...(options.nextStepId === '' ? {} : { nextStepId: options.nextStepId }),
1581
+ ...(validationState === 'blocked' ? { disabled: true } : {}),
1582
+ ...(validationState === 'blocked' || validationState === 'invalid'
1583
+ ? { status: validationState === 'invalid' ? 'error' : 'disabled' }
1584
+ : {}),
1585
+ };
1586
+ return {
1587
+ id: item.id,
1588
+ item: contractItem,
1589
+ timelineIndex: options.index,
1590
+ timelineState,
1591
+ validationState,
1592
+ blockingReason,
1593
+ returnState,
1594
+ previousStepId: options.previousStepId,
1595
+ nextStepId: options.nextStepId,
1596
+ attrs: {
1597
+ 'data-step-id': item.id,
1598
+ 'data-timeline-index': options.index,
1599
+ 'data-timeline-state': timelineState,
1600
+ 'data-previous-step-id': options.previousStepId,
1601
+ 'data-next-step-id': options.nextStepId,
1602
+ 'data-validation-state': validationState,
1603
+ 'data-blocking-reason': blockingReason,
1604
+ 'data-return-state': returnState,
1605
+ 'data-returnable': returnable ? 'true' : undefined,
1606
+ 'aria-invalid': validationState === 'invalid' ? 'true' : undefined,
1607
+ 'aria-disabled': validationState === 'blocked' ? 'true' : undefined,
1608
+ },
1609
+ };
1610
+ }
1611
+ function stepsTimelineValidationState(options) {
1612
+ if (options.stepCount === 0)
1613
+ return 'empty';
1614
+ if (options.blockedCount > 0)
1615
+ return 'blocked';
1616
+ if (options.invalidCount > 0)
1617
+ return 'invalid';
1618
+ if (options.warningCount > 0)
1619
+ return 'warning';
1620
+ return 'ready';
1621
+ }
1622
+ function createStepTriggerPlan(item, currentId, clickable = false) {
1623
+ const status = stepStatus(item, currentId);
1624
+ const active = item.id === currentId;
1625
+ const interactive = clickable && item.disabled !== true;
1626
+ const tag = interactive ? (item.href === undefined ? 'button' : 'a') : 'span';
1627
+ return createUiDomPlan(tag, 'step-trigger', {
1628
+ ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
1629
+ state: status,
1630
+ attrs: {
1631
+ type: tag === 'button' ? 'button' : undefined,
1632
+ href: tag === 'a' ? item.href : undefined,
1633
+ tabindex: interactive ? 0 : undefined,
1634
+ 'aria-current': active ? 'step' : undefined,
1635
+ 'aria-disabled': item.disabled === true ? 'true' : undefined,
1636
+ 'data-step-id': item.id,
1637
+ 'data-label': item.label,
1638
+ 'data-status': status,
1639
+ 'data-clickable': interactive ? 'true' : undefined,
1640
+ 'data-timeline-index': item.timelineIndex,
1641
+ 'data-timeline-state': item.timelineState,
1642
+ 'data-previous-step-id': item.previousStepId,
1643
+ 'data-next-step-id': item.nextStepId,
1644
+ 'data-validation-state': item.validationState,
1645
+ 'data-blocking-reason': item.blockingReason,
1646
+ 'data-return-state': item.returnState,
1647
+ 'data-returnable': item.returnable === true ? 'true' : undefined,
1648
+ 'aria-invalid': item.validationState === 'invalid' ? 'true' : undefined,
1649
+ },
1650
+ });
1651
+ }
1652
+ function normalizedOpenIds(value) {
1653
+ if (value === undefined)
1654
+ return new Set();
1655
+ return value instanceof Set ? value : new Set(value);
1656
+ }
1657
+ export function createNavListPlans(options) {
1658
+ const groups = options.groups.map((group) => {
1659
+ const items = group.items.map((item) => {
1660
+ const state = item.disabled === true ? 'disabled' : item.active === true ? 'active' : 'idle';
1661
+ const plan = createUiDomPlan(item.href === undefined ? 'button' : 'a', 'nav-list-item', {
1662
+ state,
1663
+ ...(item.disabled === undefined ? {} : { disabled: item.disabled }),
1664
+ ...(item.href === undefined ? {} : { attrs: { href: item.href } }),
1665
+ });
1666
+ return {
1667
+ ...plan,
1668
+ itemId: item.id,
1669
+ state,
1670
+ ...(item.icon === undefined
1671
+ ? {}
1672
+ : {
1673
+ icon: createUiDomPlan('img', 'nav-list-icon', {
1674
+ attrs: { alt: '', 'data-icon': item.icon },
1675
+ }),
1676
+ }),
1677
+ label: createUiDomPlan('span', 'nav-list-label', {
1678
+ ...(item.label === undefined ? {} : { attrs: { 'data-label': item.label } }),
1679
+ }),
1680
+ ...(item.badge === undefined
1681
+ ? {}
1682
+ : {
1683
+ badge: createUiDomPlan('span', 'nav-list-badge', {
1684
+ attrs: { 'data-label': String(item.badge) },
1685
+ }),
1686
+ }),
1687
+ };
1688
+ });
1689
+ return {
1690
+ ...createUiDomPlan('div', 'nav-list-group', {
1691
+ attrs: { id: `${options.id}-group-${group.id}` },
1692
+ }),
1693
+ groupId: group.id,
1694
+ title: createUiDomPlan('div', 'nav-list-group-title', {
1695
+ attrs: { id: `${options.id}-group-${group.id}-title`, 'data-label': group.title },
1696
+ }),
1697
+ items,
1698
+ };
1699
+ });
1700
+ return {
1701
+ root: createUiDomPlan('nav', 'nav-list', {
1702
+ ...(options.className === undefined ? {} : { className: options.className }),
1703
+ attrs: { id: `${options.id}-nav`, ...options.attrs },
1704
+ }),
1705
+ groups,
1706
+ };
1707
+ }