@workbench-kit/react 0.0.2-prototype.0.2.10 → 0.0.2-prototype.0.2.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workbench-kit/react",
3
- "version": "0.0.2-prototype.0.2.10",
3
+ "version": "0.0.2-prototype.0.2.11",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "exports": {
@@ -58,15 +58,15 @@
58
58
  "@vscode/codicons": "^0.0.45",
59
59
  "remark-gfm": "^4.0.1",
60
60
  "rehype-sanitize": "^6.0.0",
61
- "@workbench-kit/contracts": "0.0.2-prototype.0.2.10",
62
- "@workbench-kit/adapters": "0.0.2-prototype.0.2.10",
63
- "@workbench-kit/jdw": "0.0.2-prototype.0.2.10",
64
- "@workbench-kit/monaco": "0.0.2-prototype.0.2.10",
65
- "@workbench-kit/services": "0.0.2-prototype.0.2.10",
66
- "@workbench-kit/runtime": "0.0.2-prototype.0.2.10",
67
- "@workbench-kit/tokens": "0.0.2-prototype.0.2.10",
68
- "@workbench-kit/workspace": "0.0.2-prototype.0.2.10",
69
- "@workbench-kit/platform": "0.0.2-prototype.0.2.10"
61
+ "@workbench-kit/adapters": "0.0.2-prototype.0.2.11",
62
+ "@workbench-kit/monaco": "0.0.2-prototype.0.2.11",
63
+ "@workbench-kit/contracts": "0.0.2-prototype.0.2.11",
64
+ "@workbench-kit/platform": "0.0.2-prototype.0.2.11",
65
+ "@workbench-kit/runtime": "0.0.2-prototype.0.2.11",
66
+ "@workbench-kit/services": "0.0.2-prototype.0.2.11",
67
+ "@workbench-kit/tokens": "0.0.2-prototype.0.2.11",
68
+ "@workbench-kit/workspace": "0.0.2-prototype.0.2.11",
69
+ "@workbench-kit/jdw": "0.0.2-prototype.0.2.11"
70
70
  },
71
71
  "peerDependencies": {
72
72
  "react": "^19.0.0",
package/src/index.ts CHANGED
@@ -276,6 +276,7 @@ export {
276
276
  SideBarHeaderControl,
277
277
  SideBarList,
278
278
  SideBarListItem,
279
+ SideBarTree,
279
280
  SideBarViewFrame,
280
281
  SideBarRow,
281
282
  SidebarActionIconBar,
@@ -286,12 +287,20 @@ export {
286
287
  WorkbenchSidebarSection,
287
288
  WorkbenchSidebarSectionHeader,
288
289
  WorkbenchSidebarSectionStack,
290
+ flattenVisibleSideBarTreeItems,
291
+ isSideBarTreeBranch,
292
+ selectSideBarTreeIds,
293
+ toggleSideBarTreeId,
289
294
  useSidebarSectionBaseDepth,
290
295
  } from './layout/sidebar';
291
296
  export type {
292
297
  SideBarHeaderControlProps,
293
298
  SideBarListItemProps,
294
299
  SideBarListProps,
300
+ SideBarTreeItem,
301
+ SideBarTreeProps,
302
+ SideBarTreeSelectionMode,
303
+ SideBarTreeVisibleNode,
295
304
  SideBarViewFrameProps,
296
305
  SideBarRowProps,
297
306
  SidebarActionIconBarProps,
@@ -643,8 +652,13 @@ export type {
643
652
  } from './primitives';
644
653
  export { CatalogFilterOverlay } from './primitives';
645
654
  export type { CatalogFilterOverlayProps } from './primitives';
646
- export { LibraryDetailLayout } from './primitives';
647
- export type { LibraryDetailLayoutMode, LibraryDetailLayoutProps } from './primitives';
655
+ export { LibraryDetailLayout, resolveLibraryDetailHeroCoverMedia } from './primitives';
656
+ export type {
657
+ LibraryDetailLayoutMode,
658
+ LibraryDetailLayoutProps,
659
+ ResolveLibraryDetailHeroCoverMediaInput,
660
+ ResolvedLibraryDetailHeroCoverMedia,
661
+ } from './primitives';
648
662
  export { WorkbenchMediaPlaceholder } from './primitives';
649
663
  export type { WorkbenchMediaPlaceholderProps } from './primitives';
650
664
  export { WorkbenchMediaSlot } from './primitives';
@@ -56,6 +56,7 @@ export {
56
56
  SideBarListItem,
57
57
  SideBarRow,
58
58
  SideBarScrollSpacer,
59
+ SideBarTree,
59
60
  SideBarViewFrame,
60
61
  SideBarViewTabStrip,
61
62
  SideBarViewTitleMenu,
@@ -68,7 +69,11 @@ export {
68
69
  WorkbenchSidebarSection,
69
70
  WorkbenchSidebarSectionHeader,
70
71
  WorkbenchSidebarSectionStack,
72
+ flattenVisibleSideBarTreeItems,
73
+ isSideBarTreeBranch,
74
+ selectSideBarTreeIds,
71
75
  sideBarTreeDepthStyle,
76
+ toggleSideBarTreeId,
72
77
  useSidebarSectionBaseDepth,
73
78
  } from './sidebar';
74
79
  export type {
@@ -77,6 +82,10 @@ export type {
77
82
  SideBarListProps,
78
83
  SideBarRowProps,
79
84
  SideBarScrollSpacerProps,
85
+ SideBarTreeItem,
86
+ SideBarTreeProps,
87
+ SideBarTreeSelectionMode,
88
+ SideBarTreeVisibleNode,
80
89
  SideBarViewFrameProps,
81
90
  SideBarViewTabDescriptor,
82
91
  SideBarViewTabStripProps,
@@ -0,0 +1,301 @@
1
+ import {
2
+ useCallback,
3
+ useMemo,
4
+ useState,
5
+ type KeyboardEvent,
6
+ type MouseEvent,
7
+ type ReactNode,
8
+ } from 'react';
9
+ import { cxCodicon } from '../../utils/codicon';
10
+ import { cx } from '../../utils/cx';
11
+ import { SideBarList, SideBarListItem } from './SideBarViewFrame';
12
+
13
+ /**
14
+ * Controlled sidebar tree for library / provider category rows.
15
+ *
16
+ * Branch vs leaf: items with `children` (even empty) render as expandable branches;
17
+ * items without `children` are leaves. Hosts own expand/selection sets.
18
+ *
19
+ * Keyboard (default on): ArrowUp/Down move focus+selection (single mode) or focus
20
+ * (multi mode); ArrowRight expands / moves into first child; ArrowLeft collapses /
21
+ * moves to parent; Enter/Space activates selection. Virtualization and DnD are
22
+ * out of scope for v1.
23
+ */
24
+ export interface SideBarTreeItem {
25
+ readonly id: string;
26
+ readonly label: ReactNode;
27
+ readonly children?: readonly SideBarTreeItem[];
28
+ readonly disabled?: boolean;
29
+ readonly icon?: ReactNode;
30
+ }
31
+
32
+ export type SideBarTreeSelectionMode = 'single' | 'multi';
33
+
34
+ export interface SideBarTreeProps {
35
+ readonly items: readonly SideBarTreeItem[];
36
+ readonly expandedIds: ReadonlySet<string>;
37
+ readonly selectedIds: ReadonlySet<string>;
38
+ readonly onExpandedIdsChange: (next: Set<string>) => void;
39
+ readonly onSelectedIdsChange: (next: Set<string>) => void;
40
+ readonly selectionMode?: SideBarTreeSelectionMode;
41
+ /** When false, arrow / activation keys are not handled. Default true. */
42
+ readonly keyboardNavigation?: boolean;
43
+ readonly 'aria-label'?: string;
44
+ readonly className?: string;
45
+ }
46
+
47
+ export interface SideBarTreeVisibleNode {
48
+ readonly depth: number;
49
+ readonly item: SideBarTreeItem;
50
+ readonly parentId: string | null;
51
+ readonly hasChildren: boolean;
52
+ }
53
+
54
+ export function isSideBarTreeBranch(item: SideBarTreeItem): boolean {
55
+ return item.children !== undefined;
56
+ }
57
+
58
+ export function flattenVisibleSideBarTreeItems(
59
+ items: readonly SideBarTreeItem[],
60
+ expandedIds: ReadonlySet<string>,
61
+ depth = 0,
62
+ parentId: string | null = null,
63
+ ): SideBarTreeVisibleNode[] {
64
+ const rows: SideBarTreeVisibleNode[] = [];
65
+
66
+ for (const item of items) {
67
+ const hasChildren = isSideBarTreeBranch(item);
68
+ rows.push({ depth, item, parentId, hasChildren });
69
+
70
+ if (hasChildren && expandedIds.has(item.id) && item.children) {
71
+ rows.push(...flattenVisibleSideBarTreeItems(item.children, expandedIds, depth + 1, item.id));
72
+ }
73
+ }
74
+
75
+ return rows;
76
+ }
77
+
78
+ export function toggleSideBarTreeId(ids: ReadonlySet<string>, id: string): Set<string> {
79
+ const next = new Set(ids);
80
+ if (next.has(id)) {
81
+ next.delete(id);
82
+ } else {
83
+ next.add(id);
84
+ }
85
+ return next;
86
+ }
87
+
88
+ export function selectSideBarTreeIds(
89
+ current: ReadonlySet<string>,
90
+ id: string,
91
+ mode: SideBarTreeSelectionMode,
92
+ additive: boolean,
93
+ ): Set<string> {
94
+ if (mode === 'single' || !additive) {
95
+ return new Set([id]);
96
+ }
97
+ return toggleSideBarTreeId(current, id);
98
+ }
99
+
100
+ export function SideBarTree({
101
+ items,
102
+ expandedIds,
103
+ selectedIds,
104
+ onExpandedIdsChange,
105
+ onSelectedIdsChange,
106
+ selectionMode = 'single',
107
+ keyboardNavigation = true,
108
+ 'aria-label': ariaLabel = 'Sidebar tree',
109
+ className,
110
+ }: SideBarTreeProps) {
111
+ const visibleNodes = useMemo(
112
+ () => flattenVisibleSideBarTreeItems(items, expandedIds),
113
+ [expandedIds, items],
114
+ );
115
+
116
+ const [focusedId, setFocusedId] = useState<string | null>(null);
117
+
118
+ const focusIndex = useMemo(() => {
119
+ if (focusedId == null) {
120
+ return visibleNodes.length > 0 ? 0 : -1;
121
+ }
122
+ const index = visibleNodes.findIndex((node) => node.item.id === focusedId);
123
+ return index >= 0 ? index : visibleNodes.length > 0 ? 0 : -1;
124
+ }, [focusedId, visibleNodes]);
125
+
126
+ const toggleExpanded = useCallback(
127
+ (id: string) => {
128
+ onExpandedIdsChange(toggleSideBarTreeId(expandedIds, id));
129
+ },
130
+ [expandedIds, onExpandedIdsChange],
131
+ );
132
+
133
+ const selectItem = useCallback(
134
+ (id: string, additive: boolean) => {
135
+ onSelectedIdsChange(selectSideBarTreeIds(selectedIds, id, selectionMode, additive));
136
+ setFocusedId(id);
137
+ },
138
+ [onSelectedIdsChange, selectedIds, selectionMode],
139
+ );
140
+
141
+ const handleItemClick = useCallback(
142
+ (event: MouseEvent<HTMLButtonElement>, node: SideBarTreeVisibleNode) => {
143
+ if (node.item.disabled) {
144
+ return;
145
+ }
146
+
147
+ const additive = selectionMode === 'multi' && (event.metaKey || event.ctrlKey);
148
+ selectItem(node.item.id, additive);
149
+
150
+ if (node.hasChildren && !additive) {
151
+ toggleExpanded(node.item.id);
152
+ }
153
+ },
154
+ [selectItem, selectionMode, toggleExpanded],
155
+ );
156
+
157
+ const handleKeyDown = useCallback(
158
+ (event: KeyboardEvent<HTMLUListElement>) => {
159
+ if (!keyboardNavigation || visibleNodes.length === 0 || focusIndex < 0) {
160
+ return;
161
+ }
162
+
163
+ const current = visibleNodes[focusIndex];
164
+ if (!current) {
165
+ return;
166
+ }
167
+
168
+ if (event.key === 'ArrowDown') {
169
+ event.preventDefault();
170
+ const next = visibleNodes[Math.min(visibleNodes.length - 1, focusIndex + 1)];
171
+ if (!next || next.item.disabled) {
172
+ return;
173
+ }
174
+ setFocusedId(next.item.id);
175
+ if (selectionMode === 'single') {
176
+ onSelectedIdsChange(new Set([next.item.id]));
177
+ }
178
+ return;
179
+ }
180
+
181
+ if (event.key === 'ArrowUp') {
182
+ event.preventDefault();
183
+ const next = visibleNodes[Math.max(0, focusIndex - 1)];
184
+ if (!next || next.item.disabled) {
185
+ return;
186
+ }
187
+ setFocusedId(next.item.id);
188
+ if (selectionMode === 'single') {
189
+ onSelectedIdsChange(new Set([next.item.id]));
190
+ }
191
+ return;
192
+ }
193
+
194
+ if (event.key === 'ArrowRight') {
195
+ event.preventDefault();
196
+ if (!current.hasChildren || current.item.disabled) {
197
+ return;
198
+ }
199
+ if (!expandedIds.has(current.item.id)) {
200
+ toggleExpanded(current.item.id);
201
+ return;
202
+ }
203
+ const firstChild = current.item.children?.[0];
204
+ if (firstChild && !firstChild.disabled) {
205
+ setFocusedId(firstChild.id);
206
+ if (selectionMode === 'single') {
207
+ onSelectedIdsChange(new Set([firstChild.id]));
208
+ }
209
+ }
210
+ return;
211
+ }
212
+
213
+ if (event.key === 'ArrowLeft') {
214
+ event.preventDefault();
215
+ if (current.hasChildren && expandedIds.has(current.item.id)) {
216
+ toggleExpanded(current.item.id);
217
+ return;
218
+ }
219
+ if (current.parentId) {
220
+ setFocusedId(current.parentId);
221
+ if (selectionMode === 'single') {
222
+ onSelectedIdsChange(new Set([current.parentId]));
223
+ }
224
+ }
225
+ return;
226
+ }
227
+
228
+ if (event.key === 'Enter' || event.key === ' ') {
229
+ event.preventDefault();
230
+ if (current.item.disabled) {
231
+ return;
232
+ }
233
+ selectItem(current.item.id, selectionMode === 'multi' && (event.metaKey || event.ctrlKey));
234
+ if (current.hasChildren && selectionMode === 'single') {
235
+ toggleExpanded(current.item.id);
236
+ }
237
+ }
238
+ },
239
+ [
240
+ expandedIds,
241
+ focusIndex,
242
+ keyboardNavigation,
243
+ onSelectedIdsChange,
244
+ selectItem,
245
+ selectionMode,
246
+ toggleExpanded,
247
+ visibleNodes,
248
+ ],
249
+ );
250
+
251
+ return (
252
+ <SideBarList
253
+ aria-label={ariaLabel}
254
+ className={cx('ui-sidebar-tree', className)}
255
+ fill
256
+ role="tree"
257
+ onKeyDown={handleKeyDown}
258
+ >
259
+ {visibleNodes.map((node, index) => {
260
+ const { item, depth, hasChildren } = node;
261
+ const expanded = hasChildren && expandedIds.has(item.id);
262
+ const selected = selectedIds.has(item.id);
263
+ const focused = focusIndex === index;
264
+
265
+ return (
266
+ <SideBarListItem
267
+ key={item.id}
268
+ depth={depth}
269
+ disabled={item.disabled}
270
+ selected={selected}
271
+ tabIndex={focused ? 0 : -1}
272
+ wrapperProps={{
273
+ role: 'treeitem',
274
+ 'aria-level': depth + 1,
275
+ 'aria-selected': selected,
276
+ ...(hasChildren ? { 'aria-expanded': expanded } : {}),
277
+ }}
278
+ onClick={(event) => handleItemClick(event, node)}
279
+ onFocus={() => setFocusedId(item.id)}
280
+ >
281
+ <span className="workbench-tree-prefix">
282
+ {hasChildren ? (
283
+ <i
284
+ aria-hidden="true"
285
+ className={cxCodicon(
286
+ expanded ? 'chevron-down' : 'chevron-right',
287
+ 'workbench-tree-chevron',
288
+ )}
289
+ />
290
+ ) : (
291
+ <span className="workbench-tree-spacer" />
292
+ )}
293
+ {item.icon}
294
+ </span>
295
+ <span className="workbench-tree-label">{item.label}</span>
296
+ </SideBarListItem>
297
+ );
298
+ })}
299
+ </SideBarList>
300
+ );
301
+ }
@@ -17,6 +17,19 @@ export type {
17
17
  SideBarScrollSpacerProps,
18
18
  SideBarViewFrameProps,
19
19
  } from './SideBarViewFrame';
20
+ export {
21
+ SideBarTree,
22
+ flattenVisibleSideBarTreeItems,
23
+ isSideBarTreeBranch,
24
+ selectSideBarTreeIds,
25
+ toggleSideBarTreeId,
26
+ } from './SideBarTree';
27
+ export type {
28
+ SideBarTreeItem,
29
+ SideBarTreeProps,
30
+ SideBarTreeSelectionMode,
31
+ SideBarTreeVisibleNode,
32
+ } from './SideBarTree';
20
33
  export { SidebarActionIconBar } from './SidebarActionIconBar';
21
34
  export { SidebarSlotChrome } from './SidebarSlotChrome';
22
35
  export type { SidebarSlotChromeProps } from './SidebarSlotChrome';
@@ -1,4 +1,4 @@
1
- import { useEffect, useRef, type ReactNode } from 'react';
1
+ import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type ReactNode } from 'react';
2
2
  import { Button } from '../primitives/button';
3
3
  import { cxCodicon } from '../utils/codicon';
4
4
  import { cx } from '../utils/cx';
@@ -44,6 +44,47 @@ function menuHasShortcuts(items: ContextMenuItem[]): boolean {
44
44
  );
45
45
  }
46
46
 
47
+ function isEnabledMenuItem(
48
+ item: ContextMenuItem,
49
+ ): item is Extract<ContextMenuItem, { onSelect: () => void }> {
50
+ return item.type !== 'separator' && !item.disabled;
51
+ }
52
+
53
+ function getEnabledItemIndexes(items: readonly ContextMenuItem[]): number[] {
54
+ return items.reduce<number[]>((indexes, item, index) => {
55
+ if (isEnabledMenuItem(item)) {
56
+ indexes.push(index);
57
+ }
58
+ return indexes;
59
+ }, []);
60
+ }
61
+
62
+ function stepEnabledIndex(
63
+ enabledIndexes: readonly number[],
64
+ currentIndex: number,
65
+ direction: 1 | -1,
66
+ ): number {
67
+ if (enabledIndexes.length === 0) {
68
+ return -1;
69
+ }
70
+
71
+ const position = enabledIndexes.indexOf(currentIndex);
72
+ if (position < 0) {
73
+ return direction === 1 ? enabledIndexes[0]! : enabledIndexes[enabledIndexes.length - 1]!;
74
+ }
75
+
76
+ const nextPosition = (position + direction + enabledIndexes.length) % enabledIndexes.length;
77
+ return enabledIndexes[nextPosition]!;
78
+ }
79
+
80
+ /**
81
+ * WAI-ARIA menu keyboard model (no nested submenus in this slice):
82
+ * - ArrowUp/ArrowDown move highlight (skip disabled + separators)
83
+ * - Home/End jump to first/last enabled item
84
+ * - Enter/Space activate the highlighted item
85
+ * - Escape closes via {@link useFixedOverlayDismiss}
86
+ * - Roving tabindex: only the highlighted enabled item is tabIndex=0
87
+ */
47
88
  export function ContextMenu({
48
89
  ariaLabel = 'Context menu',
49
90
  className,
@@ -56,6 +97,8 @@ export function ContextMenu({
56
97
  const position = useClampedFixedOverlayPosition(ref, { x, y }, items.length);
57
98
  const hasIcons = menuHasIcons(items);
58
99
  const hasShortcuts = menuHasShortcuts(items);
100
+ const enabledIndexes = useMemo(() => getEnabledItemIndexes(items), [items]);
101
+ const [highlightedIndex, setHighlightedIndex] = useState(() => enabledIndexes[0] ?? -1);
59
102
 
60
103
  useFixedOverlayDismiss({ containerRef: ref, onClose });
61
104
 
@@ -73,8 +116,64 @@ export function ContextMenu({
73
116
  }, [onClose]);
74
117
 
75
118
  useEffect(() => {
76
- ref.current?.querySelector<HTMLButtonElement>('.ui-context-menu__item:not(:disabled)')?.focus();
77
- }, []);
119
+ setHighlightedIndex((current) =>
120
+ enabledIndexes.includes(current) ? current : (enabledIndexes[0] ?? -1),
121
+ );
122
+ }, [enabledIndexes]);
123
+
124
+ useEffect(() => {
125
+ if (highlightedIndex < 0) {
126
+ return;
127
+ }
128
+
129
+ const item = ref.current?.querySelector<HTMLButtonElement>(
130
+ `[data-menu-index="${highlightedIndex}"]`,
131
+ );
132
+ item?.focus();
133
+ }, [highlightedIndex]);
134
+
135
+ const activateIndex = (index: number) => {
136
+ const item = items[index];
137
+ if (!item || !isEnabledMenuItem(item)) {
138
+ return;
139
+ }
140
+ item.onSelect();
141
+ onClose();
142
+ };
143
+
144
+ const handleMenuKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
145
+ if (event.key === 'ArrowDown') {
146
+ event.preventDefault();
147
+ setHighlightedIndex((current) => stepEnabledIndex(enabledIndexes, current, 1));
148
+ return;
149
+ }
150
+
151
+ if (event.key === 'ArrowUp') {
152
+ event.preventDefault();
153
+ setHighlightedIndex((current) => stepEnabledIndex(enabledIndexes, current, -1));
154
+ return;
155
+ }
156
+
157
+ if (event.key === 'Home') {
158
+ event.preventDefault();
159
+ setHighlightedIndex(enabledIndexes[0] ?? -1);
160
+ return;
161
+ }
162
+
163
+ if (event.key === 'End') {
164
+ event.preventDefault();
165
+ setHighlightedIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);
166
+ return;
167
+ }
168
+
169
+ if (event.key === 'Enter' || event.key === ' ') {
170
+ if (highlightedIndex < 0) {
171
+ return;
172
+ }
173
+ event.preventDefault();
174
+ activateIndex(highlightedIndex);
175
+ }
176
+ };
78
177
 
79
178
  if (items.length === 0) return null;
80
179
 
@@ -91,6 +190,7 @@ export function ContextMenu({
91
190
  top: position.y,
92
191
  }}
93
192
  onContextMenu={(event) => event.preventDefault()}
193
+ onKeyDown={handleMenuKeyDown}
94
194
  >
95
195
  {items.map((item, index) =>
96
196
  item.type === 'separator' ? (
@@ -100,11 +200,18 @@ export function ContextMenu({
100
200
  key={itemKey(item, index)}
101
201
  className="ui-context-menu__item"
102
202
  data-danger={item.danger ? 'true' : undefined}
203
+ data-highlighted={highlightedIndex === index ? 'true' : undefined}
204
+ data-menu-index={index}
103
205
  disabled={item.disabled}
104
206
  role="menuitem"
207
+ tabIndex={highlightedIndex === index ? 0 : -1}
105
208
  onClick={() => {
106
- item.onSelect();
107
- onClose();
209
+ activateIndex(index);
210
+ }}
211
+ onMouseEnter={() => {
212
+ if (!item.disabled) {
213
+ setHighlightedIndex(index);
214
+ }
108
215
  }}
109
216
  >
110
217
  {hasIcons ? (
@@ -72,7 +72,8 @@
72
72
  }
73
73
 
74
74
  .ui-context-menu__item:hover,
75
- .ui-context-menu__item:focus-visible {
75
+ .ui-context-menu__item:focus-visible,
76
+ .ui-context-menu__item[data-highlighted='true'] {
76
77
  color: var(--color-text);
77
78
  background: var(--color-surface-hover);
78
79
  outline: none;
@@ -92,7 +93,8 @@
92
93
  }
93
94
 
94
95
  .ui-context-menu__item[data-danger='true']:hover,
95
- .ui-context-menu__item[data-danger='true']:focus-visible {
96
+ .ui-context-menu__item[data-danger='true']:focus-visible,
97
+ .ui-context-menu__item[data-danger='true'][data-highlighted='true'] {
96
98
  color: #fff;
97
99
  background: var(--color-danger);
98
100
  }
@@ -110,6 +110,11 @@ export type {
110
110
  LibraryDetailLayoutMode,
111
111
  LibraryDetailLayoutProps,
112
112
  } from './library-detail-layout/LibraryDetailLayout';
113
+ export { resolveLibraryDetailHeroCoverMedia } from './library-detail-layout/resolve-hero-cover-media.js';
114
+ export type {
115
+ ResolveLibraryDetailHeroCoverMediaInput,
116
+ ResolvedLibraryDetailHeroCoverMedia,
117
+ } from './library-detail-layout/resolve-hero-cover-media.js';
113
118
  export { ScrollAreaInfiniteSentinel } from './scroll-area-infinite-load/ScrollAreaInfiniteSentinel';
114
119
  export type { ScrollAreaInfiniteSentinelProps } from './scroll-area-infinite-load/ScrollAreaInfiniteSentinel';
115
120
  export { useScrollAreaInfiniteLoad } from './scroll-area-infinite-load/useScrollAreaInfiniteLoad';