@harborclient/sdk 1.3.4 → 1.3.5

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 (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/components/Breadcrumb/SegmentShell.d.ts +12 -1
  3. package/dist/components/Breadcrumb/SegmentShell.d.ts.map +1 -1
  4. package/dist/components/Breadcrumb/SegmentShell.js +7 -5
  5. package/dist/components/CodeEditor/index.d.ts +7 -4
  6. package/dist/components/CodeEditor/index.d.ts.map +1 -1
  7. package/dist/components/CodeEditor/index.js +15 -176
  8. package/dist/components/CodeEditor/selectionActionToolbar.d.ts +162 -0
  9. package/dist/components/CodeEditor/selectionActionToolbar.d.ts.map +1 -0
  10. package/dist/components/CodeEditor/selectionActionToolbar.js +319 -0
  11. package/dist/components/CopyToChatButton/constants.d.ts +18 -0
  12. package/dist/components/CopyToChatButton/constants.d.ts.map +1 -0
  13. package/dist/components/CopyToChatButton/constants.js +19 -0
  14. package/dist/components/CopyToChatButton/index.d.ts +49 -0
  15. package/dist/components/CopyToChatButton/index.d.ts.map +1 -0
  16. package/dist/components/CopyToChatButton/index.js +43 -0
  17. package/dist/components/FloatingDialog/floatingDialogPosition.d.ts +52 -0
  18. package/dist/components/FloatingDialog/floatingDialogPosition.d.ts.map +1 -0
  19. package/dist/components/FloatingDialog/floatingDialogPosition.js +33 -0
  20. package/dist/components/FloatingDialog/index.d.ts +95 -0
  21. package/dist/components/FloatingDialog/index.d.ts.map +1 -0
  22. package/dist/components/FloatingDialog/index.js +275 -0
  23. package/dist/components/PageHeader/index.d.ts +2 -1
  24. package/dist/components/PageHeader/index.d.ts.map +1 -1
  25. package/dist/components/PageHeader/index.js +3 -2
  26. package/dist/components/SidebarItem/SidebarEnvironmentItem.d.ts +42 -4
  27. package/dist/components/SidebarItem/SidebarEnvironmentItem.d.ts.map +1 -1
  28. package/dist/components/SidebarItem/SidebarEnvironmentItem.js +34 -5
  29. package/dist/components/SidebarItem/SidebarWorkspaceItem.d.ts +9 -1
  30. package/dist/components/SidebarItem/SidebarWorkspaceItem.d.ts.map +1 -1
  31. package/dist/components/SidebarItem/SidebarWorkspaceItem.js +15 -2
  32. package/dist/components/TabBar/TabContextMenu.d.ts +3 -0
  33. package/dist/components/TabBar/TabContextMenu.d.ts.map +1 -1
  34. package/dist/components/TabBar/TabContextMenu.js +25 -16
  35. package/dist/components/VariableTable/index.d.ts +1 -1
  36. package/dist/components/VariableTable/index.d.ts.map +1 -1
  37. package/dist/components/VariableTable/index.js +14 -3
  38. package/dist/components/index.d.ts +4 -2
  39. package/dist/components/index.d.ts.map +1 -1
  40. package/dist/components/index.js +3 -1
  41. package/dist/runtime/createBridgedPluginContext.js +74 -0
  42. package/dist/snippets.d.ts +26 -2
  43. package/dist/styles.css +16 -0
  44. package/dist/types.d.ts +273 -1
  45. package/dist/types.d.ts.map +1 -1
  46. package/dist/ui/index.d.ts +1 -0
  47. package/dist/ui/index.d.ts.map +1 -1
  48. package/dist/ui/index.js +1 -0
  49. package/package.json +1 -1
  50. package/dist/components/SelectionActionToolbar/index.d.ts +0 -41
  51. package/dist/components/SelectionActionToolbar/index.d.ts.map +0 -1
  52. package/dist/components/SelectionActionToolbar/index.js +0 -13
@@ -0,0 +1,275 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from '@harborclient/sdk/react';
3
+ import { cn } from '../utils.js';
4
+ import { FLOATING_DIALOG_DEFAULT_LEFT, FLOATING_DIALOG_DEFAULT_TOP, FLOATING_DIALOG_VIEWPORT_MARGIN_PX, isFloatingDialogFullyOnScreen } from './floatingDialogPosition.js';
5
+ /**
6
+ * Clamps a proposed size into min/max and remaining viewport space.
7
+ *
8
+ * @param width - Proposed width.
9
+ * @param height - Proposed height.
10
+ * @param left - Current panel left.
11
+ * @param top - Current panel top.
12
+ * @param minWidth - Minimum width.
13
+ * @param minHeight - Minimum height.
14
+ * @param maxWidth - Optional max width cap.
15
+ * @param maxHeight - Optional max height cap.
16
+ * @returns Clamped size.
17
+ */
18
+ function clampFloatingDialogSize(width, height, left, top, minWidth, minHeight, maxWidth, maxHeight) {
19
+ const margin = FLOATING_DIALOG_VIEWPORT_MARGIN_PX;
20
+ const viewportWidth = typeof window !== 'undefined' ? window.innerWidth : width;
21
+ const viewportHeight = typeof window !== 'undefined' ? window.innerHeight : height;
22
+ const roomWidth = Math.max(minWidth, viewportWidth - left - margin);
23
+ const roomHeight = Math.max(minHeight, viewportHeight - top - margin);
24
+ const widthCap = maxWidth != null ? Math.min(maxWidth, roomWidth) : roomWidth;
25
+ const heightCap = maxHeight != null ? Math.min(maxHeight, roomHeight) : roomHeight;
26
+ return {
27
+ width: Math.min(Math.max(width, minWidth), widthCap),
28
+ height: Math.min(Math.max(height, minHeight), heightCap)
29
+ };
30
+ }
31
+ /**
32
+ * Non-blocking, draggable dialog panel with no modal mask.
33
+ *
34
+ * Clicks pass through to the UI behind the panel. Drag by the handle region.
35
+ * When `initialWidth` and `initialHeight` are provided, a southeast resize
36
+ * handle appears (pointer + arrow-key accessible). Parents persist geometry via
37
+ * `onPositionChange` / `onSizeChange`. If the panel would open or remain
38
+ * off-screen after a window resize, it relocates to the default corner.
39
+ *
40
+ * @param props - Dialog labelling, geometry, and content props.
41
+ * @returns Floating dialog element.
42
+ */
43
+ export function FloatingDialog({ label, labelledBy, onClose, disableEscape = false, className, bodyClassName, initialLeft = FLOATING_DIALOG_DEFAULT_LEFT, initialTop = FLOATING_DIALOG_DEFAULT_TOP, onPositionChange, initialWidth, initialHeight, minWidth = 240, minHeight = 120, maxWidth, maxHeight, onSizeChange, dragHandle, children }) {
44
+ const resizable = initialWidth != null && initialHeight != null;
45
+ const panelRef = useRef(null);
46
+ const [position, setPosition] = useState({
47
+ left: initialLeft,
48
+ top: initialTop
49
+ });
50
+ const [size, setSize] = useState(() => resizable
51
+ ? clampFloatingDialogSize(initialWidth, initialHeight, initialLeft, initialTop, minWidth, minHeight, maxWidth, maxHeight)
52
+ : null);
53
+ const positionRef = useRef(position);
54
+ const sizeRef = useRef(size);
55
+ const onPositionChangeRef = useRef(onPositionChange);
56
+ const onSizeChangeRef = useRef(onSizeChange);
57
+ const dragStateRef = useRef(null);
58
+ const resizeStateRef = useRef(null);
59
+ /**
60
+ * Keeps the latest position available to resize handlers without rebinding.
61
+ */
62
+ useEffect(() => {
63
+ positionRef.current = position;
64
+ }, [position]);
65
+ /**
66
+ * Keeps the latest size available to relocate / resize handlers.
67
+ */
68
+ useEffect(() => {
69
+ sizeRef.current = size;
70
+ }, [size]);
71
+ /**
72
+ * Keeps the latest position-change callback available to layout/resize handlers.
73
+ */
74
+ useEffect(() => {
75
+ onPositionChangeRef.current = onPositionChange;
76
+ }, [onPositionChange]);
77
+ /**
78
+ * Keeps the latest size-change callback available to resize handlers.
79
+ */
80
+ useEffect(() => {
81
+ onSizeChangeRef.current = onSizeChange;
82
+ }, [onSizeChange]);
83
+ /**
84
+ * Moves the panel to the default corner when it would sit outside the viewport.
85
+ */
86
+ const relocateToCornerIfOffScreen = useCallback(() => {
87
+ const panel = panelRef.current;
88
+ if (panel == null) {
89
+ return;
90
+ }
91
+ const measured = sizeRef.current ?? {
92
+ width: panel.offsetWidth,
93
+ height: panel.offsetHeight
94
+ };
95
+ const current = positionRef.current;
96
+ if (isFloatingDialogFullyOnScreen(current, measured)) {
97
+ return;
98
+ }
99
+ const corner = {
100
+ left: FLOATING_DIALOG_DEFAULT_LEFT,
101
+ top: FLOATING_DIALOG_DEFAULT_TOP
102
+ };
103
+ positionRef.current = corner;
104
+ setPosition(corner);
105
+ onPositionChangeRef.current?.(corner);
106
+ }, []);
107
+ /**
108
+ * After first paint and whenever the window resizes, relocates only when off-screen.
109
+ */
110
+ useLayoutEffect(() => {
111
+ relocateToCornerIfOffScreen();
112
+ window.addEventListener('resize', relocateToCornerIfOffScreen);
113
+ return () => window.removeEventListener('resize', relocateToCornerIfOffScreen);
114
+ }, [relocateToCornerIfOffScreen]);
115
+ /**
116
+ * Closes the dialog when Escape is pressed unless disabled.
117
+ */
118
+ useEffect(() => {
119
+ if (disableEscape) {
120
+ return;
121
+ }
122
+ /**
123
+ * Dismisses the floating dialog on Escape.
124
+ *
125
+ * @param event - Keyboard event.
126
+ */
127
+ const handleKeyDown = (event) => {
128
+ if (event.key === 'Escape') {
129
+ onClose();
130
+ }
131
+ };
132
+ document.addEventListener('keydown', handleKeyDown);
133
+ return () => document.removeEventListener('keydown', handleKeyDown);
134
+ }, [disableEscape, onClose]);
135
+ /**
136
+ * Begins a pointer drag from the header handle.
137
+ *
138
+ * @param event - Pointer down event on the drag handle.
139
+ */
140
+ const handlePointerDown = useCallback((event) => {
141
+ if (event.button !== 0) {
142
+ return;
143
+ }
144
+ event.currentTarget.setPointerCapture(event.pointerId);
145
+ dragStateRef.current = {
146
+ pointerId: event.pointerId,
147
+ originX: event.clientX,
148
+ originY: event.clientY,
149
+ startLeft: position.left,
150
+ startTop: position.top
151
+ };
152
+ }, [position.left, position.top]);
153
+ /**
154
+ * Updates panel position while dragging.
155
+ *
156
+ * @param event - Pointer move event.
157
+ */
158
+ const handlePointerMove = useCallback((event) => {
159
+ const drag = dragStateRef.current;
160
+ if (drag == null || drag.pointerId !== event.pointerId) {
161
+ return;
162
+ }
163
+ const next = {
164
+ left: Math.max(FLOATING_DIALOG_VIEWPORT_MARGIN_PX, drag.startLeft + (event.clientX - drag.originX)),
165
+ top: Math.max(FLOATING_DIALOG_VIEWPORT_MARGIN_PX, drag.startTop + (event.clientY - drag.originY))
166
+ };
167
+ positionRef.current = next;
168
+ setPosition(next);
169
+ }, []);
170
+ /**
171
+ * Ends an active pointer drag and notifies the parent of the final position.
172
+ *
173
+ * @param event - Pointer up/cancel event.
174
+ */
175
+ const handlePointerUp = useCallback((event) => {
176
+ const drag = dragStateRef.current;
177
+ if (drag == null || drag.pointerId !== event.pointerId) {
178
+ return;
179
+ }
180
+ dragStateRef.current = null;
181
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
182
+ event.currentTarget.releasePointerCapture(event.pointerId);
183
+ }
184
+ onPositionChangeRef.current?.(positionRef.current);
185
+ }, []);
186
+ /**
187
+ * Begins a southeast resize from the resize handle.
188
+ *
189
+ * @param event - Pointer down on the resize handle.
190
+ */
191
+ const handleResizePointerDown = useCallback((event) => {
192
+ if (!resizable || size == null || event.button !== 0) {
193
+ return;
194
+ }
195
+ event.stopPropagation();
196
+ event.currentTarget.setPointerCapture(event.pointerId);
197
+ resizeStateRef.current = {
198
+ pointerId: event.pointerId,
199
+ originX: event.clientX,
200
+ originY: event.clientY,
201
+ startWidth: size.width,
202
+ startHeight: size.height
203
+ };
204
+ }, [resizable, size]);
205
+ /**
206
+ * Updates panel size while resizing from the SE handle.
207
+ *
208
+ * @param event - Pointer move on the resize handle.
209
+ */
210
+ const handleResizePointerMove = useCallback((event) => {
211
+ const resize = resizeStateRef.current;
212
+ if (resize == null || resize.pointerId !== event.pointerId) {
213
+ return;
214
+ }
215
+ const next = clampFloatingDialogSize(resize.startWidth + (event.clientX - resize.originX), resize.startHeight + (event.clientY - resize.originY), positionRef.current.left, positionRef.current.top, minWidth, minHeight, maxWidth, maxHeight);
216
+ sizeRef.current = next;
217
+ setSize(next);
218
+ }, [maxHeight, maxWidth, minHeight, minWidth]);
219
+ /**
220
+ * Ends an active resize and notifies the parent of the final size.
221
+ *
222
+ * @param event - Pointer up/cancel on the resize handle.
223
+ */
224
+ const handleResizePointerUp = useCallback((event) => {
225
+ const resize = resizeStateRef.current;
226
+ if (resize == null || resize.pointerId !== event.pointerId) {
227
+ return;
228
+ }
229
+ resizeStateRef.current = null;
230
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) {
231
+ event.currentTarget.releasePointerCapture(event.pointerId);
232
+ }
233
+ if (sizeRef.current != null) {
234
+ onSizeChangeRef.current?.(sizeRef.current);
235
+ }
236
+ }, []);
237
+ /**
238
+ * Adjusts size with arrow keys when the resize handle is focused.
239
+ *
240
+ * @param event - Keyboard event on the resize handle.
241
+ */
242
+ const handleResizeKeyDown = useCallback((event) => {
243
+ if (size == null) {
244
+ return;
245
+ }
246
+ const step = event.shiftKey ? 24 : 8;
247
+ let deltaW = 0;
248
+ let deltaH = 0;
249
+ if (event.key === 'ArrowRight') {
250
+ deltaW = step;
251
+ }
252
+ else if (event.key === 'ArrowLeft') {
253
+ deltaW = -step;
254
+ }
255
+ else if (event.key === 'ArrowDown') {
256
+ deltaH = step;
257
+ }
258
+ else if (event.key === 'ArrowUp') {
259
+ deltaH = -step;
260
+ }
261
+ else {
262
+ return;
263
+ }
264
+ event.preventDefault();
265
+ const next = clampFloatingDialogSize(size.width + deltaW, size.height + deltaH, positionRef.current.left, positionRef.current.top, minWidth, minHeight, maxWidth, maxHeight);
266
+ sizeRef.current = next;
267
+ setSize(next);
268
+ onSizeChangeRef.current?.(next);
269
+ }, [maxHeight, maxWidth, minHeight, minWidth, size]);
270
+ return (_jsxs("div", { ref: panelRef, role: "dialog", "aria-modal": "false", "aria-label": labelledBy ? undefined : label, "aria-labelledby": labelledBy, className: cn('hc-floating-dialog fixed z-[60] flex min-w-[240px] flex-col overflow-hidden rounded-lg border border-separator bg-surface shadow-xl', className), style: {
271
+ left: position.left,
272
+ top: position.top,
273
+ ...(size != null ? { width: size.width, height: size.height } : {})
274
+ }, children: [_jsx("div", { className: "hc-floating-dialog-handle shrink-0 cursor-grab touch-none active:cursor-grabbing", onPointerDown: handlePointerDown, onPointerMove: handlePointerMove, onPointerUp: handlePointerUp, onPointerCancel: handlePointerUp, children: dragHandle }), _jsx("div", { className: cn('hc-floating-dialog-body min-h-0 flex-1 overflow-auto p-3', bodyClassName), children: children }), resizable ? (_jsx("button", { type: "button", "aria-label": "Resize dialog", className: "absolute right-0 bottom-0 h-4 w-4 cursor-se-resize border-0 bg-transparent p-0 focus-visible:outline focus-visible:outline-2 focus-visible:outline-accent", onPointerDown: handleResizePointerDown, onPointerMove: handleResizePointerMove, onPointerUp: handleResizePointerUp, onPointerCancel: handleResizePointerUp, onKeyDown: handleResizeKeyDown, children: _jsx("span", { className: "pointer-events-none absolute right-1 bottom-1 h-2 w-2 border-r-2 border-b-2 border-muted", "aria-hidden": true }) })) : null] }));
275
+ }
@@ -20,7 +20,8 @@ interface Props extends Omit<ComponentPropsWithoutRef<'div'>, 'children'> {
20
20
  }
21
21
  /**
22
22
  * Full-bleed page header with a bordered bottom edge, title block on the left,
23
- * and optional action controls on the right.
23
+ * and optional action controls on the right. Sticks to the top of its nearest
24
+ * scroll ancestor so title and actions stay visible while page content scrolls.
24
25
  */
25
26
  export declare function PageHeader({ title, description, icon, children, className, ...props }: Props): JSX.Element;
26
27
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/PageHeader/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAItE,UAAU,KAAM,SAAQ,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACvE;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CA6BrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/PageHeader/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,wBAAwB,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAItE,UAAU,KAAM,SAAQ,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IACvE;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,CAAC,EAAE,cAAc,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,EACzB,KAAK,EACL,WAAW,EACX,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CA6BrB"}
@@ -3,8 +3,9 @@ import { FaIcon } from '../FaIcon/index.js';
3
3
  import { cn } from '../utils.js';
4
4
  /**
5
5
  * Full-bleed page header with a bordered bottom edge, title block on the left,
6
- * and optional action controls on the right.
6
+ * and optional action controls on the right. Sticks to the top of its nearest
7
+ * scroll ancestor so title and actions stay visible while page content scrolls.
7
8
  */
8
9
  export function PageHeader({ title, description, icon, children, className, ...props }) {
9
- return (_jsxs("div", { ...props, className: cn('hc-page-header -mx-6 mb-4 flex flex-wrap items-center gap-2 border-b border-separator px-6 py-4', className), children: [_jsxs("div", { className: "hc-page-header-content min-w-0 flex-1", children: [_jsxs("h2", { className: "hc-page-header-title m-0 flex items-center gap-2 text-[22px] leading-[1.15] font-bold tracking-[-0.01em] text-text", children: [icon ? (_jsx(FaIcon, { icon: icon, className: "hc-page-header-title-icon h-4 w-4 shrink-0 text-muted", "aria-hidden": true })) : null, title] }), description ? (_jsx("p", { className: "hc-page-header-description m-0 mt-1 leading-6 text-muted", children: description })) : null] }), children ? (_jsx("div", { className: "hc-page-header-actions flex flex-wrap items-center gap-2", children: children })) : null] }));
10
+ return (_jsxs("div", { ...props, className: cn('hc-page-header sticky top-0 z-10 -mx-6 mb-4 flex flex-wrap items-center gap-2 border-b border-separator bg-surface px-6 py-4', className), children: [_jsxs("div", { className: "hc-page-header-content min-w-0 flex-1", children: [_jsxs("h2", { className: "hc-page-header-title m-0 flex items-center gap-2 text-[22px] leading-[1.15] font-bold tracking-[-0.01em] text-text", children: [icon ? (_jsx(FaIcon, { icon: icon, className: "hc-page-header-title-icon h-4 w-4 shrink-0 text-muted", "aria-hidden": true })) : null, title] }), description ? (_jsx("p", { className: "hc-page-header-description m-0 mt-1 leading-6 text-muted", children: description })) : null] }), children ? (_jsx("div", { className: "hc-page-header-actions flex flex-wrap items-center gap-2", children: children })) : null] }));
10
11
  }
@@ -1,3 +1,4 @@
1
+ import type { IconDefinition } from '@fortawesome/fontawesome-svg-core';
1
2
  import type { JSX, MouseEvent, ReactNode } from 'react';
2
3
  import { type SidebarItemSortableConfig } from './SidebarItem.js';
3
4
  interface Props {
@@ -21,13 +22,46 @@ interface Props {
21
22
  * Whether this row should use selected/highlighted row styling.
22
23
  */
23
24
  selected?: boolean;
25
+ /**
26
+ * Whether this environment has nested child environments.
27
+ */
28
+ hasChildren?: boolean;
29
+ /**
30
+ * Whether the child environment list is expanded.
31
+ */
32
+ expanded?: boolean;
33
+ /**
34
+ * Id of the child region controlled by this row (`SidebarTreeGroup` id).
35
+ */
36
+ childrenId?: string;
37
+ /**
38
+ * Nesting depth for indentation (`0` = root, no extra indent).
39
+ */
40
+ level?: number;
41
+ /**
42
+ * Total siblings at this level (tree a11y).
43
+ */
44
+ setSize?: number;
45
+ /**
46
+ * 1-based position among siblings at this level (tree a11y).
47
+ */
48
+ posInSet?: number;
49
+ /**
50
+ * Toggles child environment expand/collapse when {@link hasChildren} is true.
51
+ */
52
+ onToggleExpand?: () => void;
53
+ /**
54
+ * Icons for expand/collapse chevrons when {@link hasChildren} is true.
55
+ */
56
+ expandIcon?: IconDefinition;
57
+ collapseIcon?: IconDefinition;
24
58
  /**
25
59
  * dnd-kit sortable configuration for environment reordering.
26
60
  */
27
61
  sortable?: SidebarItemSortableConfig;
28
62
  /**
29
- * Accessible label for the listbox option. When omitted, the name is derived
30
- * from visible row content (name, variable summary).
63
+ * Accessible label for the listbox option or tree item. When omitted, the name
64
+ * is derived from visible row content (name, variable summary).
31
65
  */
32
66
  ariaLabel?: string;
33
67
  /**
@@ -64,15 +98,19 @@ interface Props {
64
98
  */
65
99
  dataSidebarEnvironmentId?: string | number;
66
100
  /**
67
- * HTML element for the row container. Use `li` inside {@link SidebarListbox}.
101
+ * HTML element for the row container. Use `li` inside {@link SidebarListbox}
102
+ * or {@link SidebarTree}.
68
103
  */
69
104
  as?: 'div' | 'li';
70
105
  }
71
106
  /**
72
107
  * Renders an environment row in the Collections sidebar Environments section.
73
108
  *
109
+ * Flat list rows use listbox option semantics. Nested tree rows (when `level` is
110
+ * set) use treeitem semantics with optional expand/collapse chevrons.
111
+ *
74
112
  * The accessible name is derived from visible row content (name, variable summary).
75
113
  */
76
- export declare function SidebarEnvironmentItem({ name, variableSummary, markerDot, selected, sortable, ariaLabel, ariaSelected, ariaCurrent, onContextMenu, onClick, onDoubleClick, onEnter, actions, dataSidebarEnvironmentId, as }: Props): JSX.Element;
114
+ export declare function SidebarEnvironmentItem({ name, variableSummary, markerDot, selected, hasChildren, expanded, childrenId, level, setSize, posInSet, onToggleExpand, expandIcon, collapseIcon, sortable, ariaLabel, ariaSelected, ariaCurrent, onContextMenu, onClick, onDoubleClick, onEnter, actions, dataSidebarEnvironmentId, as }: Props): JSX.Element;
77
115
  export {};
78
116
  //# sourceMappingURL=SidebarEnvironmentItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SidebarEnvironmentItem.d.ts","sourceRoot":"","sources":["../../../src/components/SidebarItem/SidebarEnvironmentItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAiB,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvE,OAAO,EAAe,KAAK,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAI/E,UAAU,KAAK;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;QAClC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IAErC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEnD;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3C;;OAEG;IACH,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,eAAe,EACf,SAAS,EACT,QAAgB,EAChB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,OAAO,EACP,aAAa,EACb,OAAO,EACP,OAAO,EACP,wBAAwB,EACxB,EAAS,EACV,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAwDrB"}
1
+ {"version":3,"file":"SidebarEnvironmentItem.d.ts","sourceRoot":"","sources":["../../../src/components/SidebarItem/SidebarEnvironmentItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,GAAG,EAAiB,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvE,OAAO,EAAe,KAAK,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAU/E,UAAU,KAAK;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,eAAe,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;QAClC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B;;OAEG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,YAAY,CAAC,EAAE,cAAc,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IAErC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEnD;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,wBAAwB,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE3C;;;OAGG;IACH,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACnB;AAOD;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,eAAe,EACf,SAAS,EACT,QAAgB,EAChB,WAAmB,EACnB,QAAgB,EAChB,UAAU,EACV,KAAK,EACL,OAAO,EACP,QAAQ,EACR,cAAc,EACd,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,OAAO,EACP,aAAa,EACb,OAAO,EACP,OAAO,EACP,wBAAwB,EACxB,EAAS,EACV,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAmGrB"}
@@ -1,14 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
+ import { FaIcon } from '../FaIcon/index.js';
2
3
  import { SidebarItem } from './SidebarItem.js';
3
4
  import { SidebarMarkerDot } from './SidebarMarkerDot.js';
4
- import { SIDEBAR_ITEM_BUTTON_CLASS } from './sidebarItemClasses.js';
5
+ import { SIDEBAR_CHEVRON_BUTTON_CLASS, SIDEBAR_CHEVRON_ICON_CLASS, SIDEBAR_CHEVRON_LABEL_OFFSET_CLASS, SIDEBAR_ITEM_BUTTON_CLASS } from './sidebarItemClasses.js';
6
+ import { stopSortableDragPointerDown } from './stopSortableDragPointerDown.js';
7
+ /**
8
+ * Nesting indent step matching Tailwind `ml-4` (1rem) per depth level.
9
+ */
10
+ const ENVIRONMENT_LEVEL_INDENT_PX = 16;
5
11
  /**
6
12
  * Renders an environment row in the Collections sidebar Environments section.
7
13
  *
14
+ * Flat list rows use listbox option semantics. Nested tree rows (when `level` is
15
+ * set) use treeitem semantics with optional expand/collapse chevrons.
16
+ *
8
17
  * The accessible name is derived from visible row content (name, variable summary).
9
18
  */
10
- export function SidebarEnvironmentItem({ name, variableSummary, markerDot, selected = false, sortable, ariaLabel, ariaSelected, ariaCurrent, onContextMenu, onClick, onDoubleClick, onEnter, actions, dataSidebarEnvironmentId, as = 'li' }) {
11
- const useListboxOption = as === 'li';
19
+ export function SidebarEnvironmentItem({ name, variableSummary, markerDot, selected = false, hasChildren = false, expanded = false, childrenId, level, setSize, posInSet, onToggleExpand, expandIcon, collapseIcon, sortable, ariaLabel, ariaSelected, ariaCurrent, onContextMenu, onClick, onDoubleClick, onEnter, actions, dataSidebarEnvironmentId, as = 'li' }) {
20
+ const useTreeItem = as === 'li' && level != null;
21
+ const useListboxOption = as === 'li' && !useTreeItem;
22
+ const showChevron = hasChildren && expandIcon != null && collapseIcon != null && onToggleExpand != null;
23
+ const indentPx = (level ?? 0) * ENVIRONMENT_LEVEL_INDENT_PX;
12
24
  /**
13
25
  * Opens environment settings when Enter is pressed on the name area.
14
26
  */
@@ -20,6 +32,8 @@ export function SidebarEnvironmentItem({ name, variableSummary, markerDot, selec
20
32
  event.stopPropagation();
21
33
  onEnter();
22
34
  };
35
+ const chevronLabel = expanded ? `Collapse environment "${name}"` : `Expand environment "${name}"`;
36
+ const labelOffsetClass = useTreeItem ? SIDEBAR_CHEVRON_LABEL_OFFSET_CLASS : '';
23
37
  return (_jsx(SidebarItem, { selected: selected, sortable: sortable, onContextMenu: onContextMenu, actions: actions, as: as, listboxOption: useListboxOption
24
38
  ? {
25
39
  ariaLabel,
@@ -29,7 +43,22 @@ export function SidebarEnvironmentItem({ name, variableSummary, markerDot, selec
29
43
  onDoubleClick,
30
44
  onKeyDown: onEnter != null ? handleKeyDown : undefined
31
45
  }
32
- : undefined, children: _jsxs("span", { className: SIDEBAR_ITEM_BUTTON_CLASS, ...(dataSidebarEnvironmentId != null
46
+ : undefined, treeItem: useTreeItem
47
+ ? {
48
+ ariaLabel,
49
+ expanded: hasChildren ? expanded : undefined,
50
+ controlsId: hasChildren ? childrenId : undefined,
51
+ level: (level ?? 0) + 1,
52
+ setSize,
53
+ posInSet,
54
+ onClick,
55
+ onDoubleClick,
56
+ onKeyDown: onEnter != null ? handleKeyDown : undefined
57
+ }
58
+ : undefined, children: _jsxs("span", { className: SIDEBAR_ITEM_BUTTON_CLASS, style: indentPx > 0 ? { paddingLeft: indentPx } : undefined, ...(dataSidebarEnvironmentId != null
33
59
  ? { 'data-sidebar-environment-id': String(dataSidebarEnvironmentId) }
34
- : {}), children: [_jsxs("span", { className: "inline-flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "min-w-0 truncate", children: name }), markerDot != null ? (_jsx(SidebarMarkerDot, { marker: markerDot.marker, visible: markerDot.visible, label: markerDot.label })) : null] }), _jsx("span", { className: "shrink-0 text-muted", children: variableSummary })] }) }));
60
+ : {}), children: [showChevron ? (_jsx("button", { type: "button", className: SIDEBAR_CHEVRON_BUTTON_CLASS, onClick: (event) => {
61
+ event.stopPropagation();
62
+ onToggleExpand();
63
+ }, onPointerDown: stopSortableDragPointerDown, tabIndex: -1, "aria-label": chevronLabel, children: _jsx(FaIcon, { icon: expanded ? collapseIcon : expandIcon, className: SIDEBAR_CHEVRON_ICON_CLASS }) })) : useTreeItem ? (_jsx("span", { className: "inline-flex h-4 w-4 shrink-0", "aria-hidden": true })) : null, _jsxs("span", { className: `inline-flex min-w-0 flex-1 items-center gap-1.5 ${labelOffsetClass}`, children: [_jsx("span", { className: "min-w-0 truncate", children: name }), markerDot != null ? (_jsx(SidebarMarkerDot, { marker: markerDot.marker, visible: markerDot.visible, label: markerDot.label })) : null] }), _jsx("span", { className: "shrink-0 text-muted", children: variableSummary })] }) }));
35
64
  }
@@ -38,6 +38,14 @@ interface Props {
38
38
  * Called when the primary row area is activated.
39
39
  */
40
40
  onClick?: (event: MouseEvent<HTMLElement>) => void;
41
+ /**
42
+ * Called when the primary row area is double-clicked.
43
+ */
44
+ onDoubleClick?: (event: MouseEvent<HTMLElement>) => void;
45
+ /**
46
+ * Called when Enter is pressed on the primary row area.
47
+ */
48
+ onEnter?: () => void;
41
49
  /**
42
50
  * Trailing actions slot, typically a row actions menu.
43
51
  */
@@ -50,6 +58,6 @@ interface Props {
50
58
  /**
51
59
  * Renders a workspace row in the Collections sidebar Workspaces section.
52
60
  */
53
- export declare function SidebarWorkspaceItem({ name, summary, icon, markerDot, selected, sortable, onContextMenu, onClick, actions, as }: Props): JSX.Element;
61
+ export declare function SidebarWorkspaceItem({ name, summary, icon, markerDot, selected, sortable, onContextMenu, onClick, onDoubleClick, onEnter, actions, as }: Props): JSX.Element;
54
62
  export {};
55
63
  //# sourceMappingURL=SidebarWorkspaceItem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SidebarWorkspaceItem.d.ts","sourceRoot":"","sources":["../../../src/components/SidebarItem/SidebarWorkspaceItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAExD,OAAO,EAAe,KAAK,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAI/E,UAAU,KAAK;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;QAClC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IAErC;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEnD;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,SAAS,EACT,QAAgB,EAChB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,OAAO,EACP,EAAS,EACV,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAkCrB"}
1
+ {"version":3,"file":"SidebarWorkspaceItem.d.ts","sourceRoot":"","sources":["../../../src/components/SidebarItem/SidebarWorkspaceItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,KAAK,EAAE,GAAG,EAAiB,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvE,OAAO,EAAe,KAAK,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAI/E,UAAU,KAAK;IACb;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,IAAI,EAAE,cAAc,CAAC;IAErB;;OAEG;IACH,SAAS,CAAC,EAAE;QACV,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;QAClC,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF;;OAEG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,yBAAyB,CAAC;IAErC;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEnD;;OAEG;IACH,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAEzD;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IAEpB;;OAEG;IACH,EAAE,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,OAAO,EACP,IAAI,EACJ,SAAS,EACT,QAAgB,EAChB,QAAQ,EACR,aAAa,EACb,OAAO,EACP,aAAa,EACb,OAAO,EACP,OAAO,EACP,EAAS,EACV,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CAiDrB"}
@@ -6,11 +6,24 @@ import { SIDEBAR_ITEM_BUTTON_CLASS } from './sidebarItemClasses.js';
6
6
  /**
7
7
  * Renders a workspace row in the Collections sidebar Workspaces section.
8
8
  */
9
- export function SidebarWorkspaceItem({ name, summary, icon, markerDot, selected = false, sortable, onContextMenu, onClick, actions, as = 'li' }) {
9
+ export function SidebarWorkspaceItem({ name, summary, icon, markerDot, selected = false, sortable, onContextMenu, onClick, onDoubleClick, onEnter, actions, as = 'li' }) {
10
10
  const useListboxOption = as === 'li';
11
+ /**
12
+ * Opens workspace settings when Enter is pressed on the row.
13
+ */
14
+ const handleKeyDown = (event) => {
15
+ if (event.key !== 'Enter' || onEnter == null) {
16
+ return;
17
+ }
18
+ event.preventDefault();
19
+ event.stopPropagation();
20
+ onEnter();
21
+ };
11
22
  return (_jsx(SidebarItem, { selected: selected, sortable: sortable, onContextMenu: onContextMenu, actions: actions, as: as, listboxOption: useListboxOption
12
23
  ? {
13
- onClick
24
+ onClick,
25
+ onDoubleClick,
26
+ onKeyDown: onEnter != null ? handleKeyDown : undefined
14
27
  }
15
28
  : undefined, children: _jsxs("span", { className: `${SIDEBAR_ITEM_BUTTON_CLASS} gap-2 rounded-md px-2 py-1`, children: [_jsx(FaIcon, { icon: icon, className: "h-3.5 w-3.5 shrink-0 text-muted", "aria-hidden": true }), _jsxs("span", { className: "inline-flex min-w-0 flex-1 items-center gap-1.5", children: [_jsx("span", { className: "min-w-0 truncate", children: name }), markerDot != null ? (_jsx(SidebarMarkerDot, { marker: markerDot.marker, visible: markerDot.visible, label: markerDot.label })) : null] }), _jsx("span", { className: "shrink-0 text-muted", children: summary })] }) }));
16
29
  }
@@ -17,6 +17,9 @@ interface Props {
17
17
  }
18
18
  /**
19
19
  * Cursor-positioned context menu for tab bars, rendered in a portal at the click point.
20
+ *
21
+ * @param props - Menu groups, cursor anchor, and close handler.
22
+ * @returns Portal menu panel, or null when there are no items.
20
23
  */
21
24
  export declare function TabContextMenu({ groups, position, onClose }: Props): JSX.Element | null;
22
25
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"TabContextMenu.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/TabContextMenu.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAI1E,UAAU,KAAK;IACb;;OAEG;IACH,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,YAAY,CAAC;IAEvB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAgBD;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAkJvF"}
1
+ {"version":3,"file":"TabContextMenu.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/TabContextMenu.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,GAAG,EAAiB,MAAM,OAAO,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAqB,MAAM,oBAAoB,CAAC;AAU1E,UAAU,KAAK;IACb;;OAEG;IACH,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,YAAY,CAAC;IAEvB;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,GAAG,IAAI,CAoKvF"}
@@ -2,20 +2,13 @@ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
2
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from '@harborclient/sdk/react';
3
3
  import { clampMenuPosition } from '../menuPosition.js';
4
4
  import { portalToBody } from '../portalToBody.js';
5
+ import { findAdjacentEnabledIndex, findEdgeEnabledIndex, isMenuItemEnabled, menuItemClass } from '../rowActionsMenuHelpers.js';
5
6
  import { cn, resolveTabListKeyAction } from '../utils.js';
6
- /**
7
- * Tailwind classes for a single menu item button.
8
- *
9
- * @param variant - Visual variant for default or destructive actions.
10
- */
11
- function menuItemClass(variant) {
12
- const base = 'block w-full cursor-pointer border-none bg-transparent px-3.5 py-1.5 text-left app-no-drag';
13
- return variant === 'danger'
14
- ? `${base} text-text hover:bg-danger/15 hover:text-danger`
15
- : `${base} text-text hover:bg-selection`;
16
- }
17
7
  /**
18
8
  * Cursor-positioned context menu for tab bars, rendered in a portal at the click point.
9
+ *
10
+ * @param props - Menu groups, cursor anchor, and close handler.
11
+ * @returns Portal menu panel, or null when there are no items.
19
12
  */
20
13
  export function TabContextMenu({ groups, position, onClose }) {
21
14
  const menuRef = useRef(null);
@@ -41,7 +34,7 @@ export function TabContextMenu({ groups, position, onClose }) {
41
34
  });
42
35
  }, []);
43
36
  /**
44
- * Re-clamps the menu after mount and focuses the first item once dimensions are known.
37
+ * Re-clamps the menu after mount and focuses the first enabled item once dimensions are known.
45
38
  */
46
39
  useLayoutEffect(() => {
47
40
  const menu = menuRef.current;
@@ -53,8 +46,11 @@ export function TabContextMenu({ groups, position, onClose }) {
53
46
  width: rect.width,
54
47
  height: rect.height
55
48
  }));
56
- itemRefs.current[0]?.focus();
57
- }, [position, groups]);
49
+ const firstEnabled = findEdgeEnabledIndex(flatItems, false);
50
+ if (firstEnabled != null) {
51
+ focusItem(firstEnabled);
52
+ }
53
+ }, [position, groups, flatItems, focusItem]);
58
54
  /**
59
55
  * Closes the menu on outside click or Escape while it is open.
60
56
  */
@@ -90,6 +86,15 @@ export function TabContextMenu({ groups, position, onClose }) {
90
86
  closeMenu();
91
87
  return;
92
88
  }
89
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
90
+ const direction = event.key === 'ArrowDown' ? 1 : -1;
91
+ const next = findAdjacentEnabledIndex(flatItems, focusedIndex, direction);
92
+ if (next != null) {
93
+ event.preventDefault();
94
+ focusItem(next);
95
+ }
96
+ return;
97
+ }
93
98
  const arrowIndex = resolveTabListKeyAction(event.key, focusedIndex, flatItems.length);
94
99
  if (arrowIndex !== null) {
95
100
  event.preventDefault();
@@ -99,7 +104,7 @@ export function TabContextMenu({ groups, position, onClose }) {
99
104
  if (flatItems.length === 0) {
100
105
  return null;
101
106
  }
102
- return portalToBody(_jsx("div", { ref: menuRef, role: "menu", "aria-label": "Tab actions", className: "hc-tab-context-menu app-no-drag fixed z-50 min-w-[200px] rounded-md border border-separator bg-surface py-1 shadow-md", style: { left: clampedPosition.x, top: clampedPosition.y }, onKeyDown: handleMenuKeyDown, children: groups.map((group, groupIndex) => {
107
+ return portalToBody(_jsx("div", { ref: menuRef, role: "menu", "aria-label": "Tab actions", className: "hc-tab-context-menu app-no-drag fixed z-[70] min-w-[200px] rounded-md border border-separator bg-surface py-1 shadow-md", style: { left: clampedPosition.x, top: clampedPosition.y }, onKeyDown: handleMenuKeyDown, children: groups.map((group, groupIndex) => {
103
108
  let flatIndex = groups
104
109
  .slice(0, groupIndex)
105
110
  .reduce((count, groupItems) => count + groupItems.length, 0);
@@ -107,10 +112,14 @@ export function TabContextMenu({ groups, position, onClose }) {
107
112
  ? 'hc-tab-context-menu-group border-t border-separator'
108
113
  : 'hc-tab-context-menu-group', children: group.map((item) => {
109
114
  const itemIndex = flatIndex++;
115
+ const disabled = !isMenuItemEnabled(item);
110
116
  return (_jsx("button", { ref: (element) => {
111
117
  itemRefs.current[itemIndex] = element;
112
- }, type: "button", role: "menuitem", tabIndex: itemIndex === focusedIndex ? 0 : -1, className: cn('hc-tab-context-menu-item', menuItemClass(item.variant)), onClick: (event) => {
118
+ }, type: "button", role: "menuitem", disabled: disabled, tabIndex: itemIndex === focusedIndex && !disabled ? 0 : -1, className: cn('hc-tab-context-menu-item', menuItemClass(item.variant, disabled)), onClick: (event) => {
113
119
  event.stopPropagation();
120
+ if (disabled) {
121
+ return;
122
+ }
114
123
  closeMenu();
115
124
  item.onSelect?.();
116
125
  }, children: item.label }, item.label));
@@ -19,7 +19,7 @@ interface Props extends Omit<ComponentPropsWithoutRef<'div'>, 'children' | 'onCh
19
19
  focusKey?: string;
20
20
  }
21
21
  /**
22
- * Editable table for key/value/default/share variable rows.
22
+ * Editable table for key/value/default/share variable rows with an enable toggle.
23
23
  */
24
24
  export declare function VariableTable({ variables, onChange, description, focusKey, className, ...props }: Props): JSX.Element;
25
25
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VariableTable/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,GAAG,EAAqB,MAAM,OAAO,CAAC;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAY/C,UAAU,KAAM,SAAQ,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACpF;;OAEG;IACH,SAAS,EAAE,QAAQ,EAAE,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAE1C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CA4JrB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/VariableTable/index.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,wBAAwB,EAAE,KAAK,GAAG,EAAqB,MAAM,OAAO,CAAC;AACnF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAkB/C,UAAU,KAAM,SAAQ,IAAI,CAAC,wBAAwB,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IACpF;;OAEG;IACH,SAAS,EAAE,QAAQ,EAAE,CAAC;IAEtB;;OAEG;IACH,QAAQ,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,KAAK,IAAI,CAAC;IAE1C;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,QAAQ,EACR,WAAW,EACX,QAAQ,EACR,SAAS,EACT,GAAG,KAAK,EACT,EAAE,KAAK,GAAG,GAAG,CAAC,OAAO,CA4KrB"}