@harborclient/sdk 1.0.66 → 1.0.68

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 (54) hide show
  1. package/dist/components/FooterButton/index.d.ts +2 -2
  2. package/dist/components/FooterButton/index.d.ts.map +1 -1
  3. package/dist/components/FooterButton/index.js +1 -1
  4. package/dist/components/FooterIcon/index.d.ts +2 -1
  5. package/dist/components/FooterIcon/index.d.ts.map +1 -1
  6. package/dist/components/FooterIcon/index.js +3 -2
  7. package/dist/components/FooterPanel/index.d.ts +50 -0
  8. package/dist/components/FooterPanel/index.d.ts.map +1 -0
  9. package/dist/components/FooterPanel/index.js +12 -0
  10. package/dist/components/Resizable/Resizable.d.ts +5 -1
  11. package/dist/components/Resizable/Resizable.d.ts.map +1 -1
  12. package/dist/components/Resizable/Resizable.js +2 -2
  13. package/dist/components/TabBar/ClosingTabShell.d.ts +20 -0
  14. package/dist/components/TabBar/ClosingTabShell.d.ts.map +1 -0
  15. package/dist/components/TabBar/ClosingTabShell.js +126 -0
  16. package/dist/components/TabBar/TabBarShell.d.ts +58 -0
  17. package/dist/components/TabBar/TabBarShell.d.ts.map +1 -0
  18. package/dist/components/TabBar/TabBarShell.js +37 -0
  19. package/dist/components/TabBar/TabContextMenu.d.ts +32 -0
  20. package/dist/components/TabBar/TabContextMenu.d.ts.map +1 -0
  21. package/dist/components/TabBar/TabContextMenu.js +133 -0
  22. package/dist/components/TabBar/TabNewButton.d.ts +26 -0
  23. package/dist/components/TabBar/TabNewButton.d.ts.map +1 -0
  24. package/dist/components/TabBar/TabNewButton.js +14 -0
  25. package/dist/components/TabBar/index.d.ts +92 -0
  26. package/dist/components/TabBar/index.d.ts.map +1 -0
  27. package/dist/components/TabBar/index.js +224 -0
  28. package/dist/components/TabBar/tabCloseMenuHelpers.d.ts +41 -0
  29. package/dist/components/TabBar/tabCloseMenuHelpers.d.ts.map +1 -0
  30. package/dist/components/TabBar/tabCloseMenuHelpers.js +48 -0
  31. package/dist/components/TabBar/types.d.ts +60 -0
  32. package/dist/components/TabBar/types.d.ts.map +1 -0
  33. package/dist/components/TabBar/types.js +1 -0
  34. package/dist/components/TabBar/useExitingTabItems.d.ts +73 -0
  35. package/dist/components/TabBar/useExitingTabItems.d.ts.map +1 -0
  36. package/dist/components/TabBar/useExitingTabItems.js +103 -0
  37. package/dist/components/TabBar/useSortableTabItem.d.ts +31 -0
  38. package/dist/components/TabBar/useSortableTabItem.d.ts.map +1 -0
  39. package/dist/components/TabBar/useSortableTabItem.js +29 -0
  40. package/dist/components/Toolbar/index.d.ts.map +1 -1
  41. package/dist/components/Toolbar/index.js +21 -5
  42. package/dist/components/classes.d.ts +9 -0
  43. package/dist/components/classes.d.ts.map +1 -1
  44. package/dist/components/classes.js +15 -2
  45. package/dist/components/footerBarUtils.d.ts +11 -0
  46. package/dist/components/footerBarUtils.d.ts.map +1 -0
  47. package/dist/components/footerBarUtils.js +10 -0
  48. package/dist/components/index.d.ts +4 -1
  49. package/dist/components/index.d.ts.map +1 -1
  50. package/dist/components/index.js +4 -1
  51. package/dist/styles.css +12 -0
  52. package/dist/types.d.ts +2 -1
  53. package/dist/types.d.ts.map +1 -1
  54. package/package.json +7 -1
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx } from "@harborclient/sdk/jsx-runtime";
2
+ import { faPlus } from '@fortawesome/free-solid-svg-icons';
3
+ import { FaIcon } from '../FaIcon/index.js';
4
+ import { tabItem } from '../classes.js';
5
+ import { cn } from '../utils.js';
6
+ /**
7
+ * New-tab "+" control shared by request editor and AI chat tab bars.
8
+ */
9
+ export function TabNewButton({ ariaLabel, title, wrapped, onClick }) {
10
+ if (wrapped) {
11
+ return (_jsx("div", { className: "flex shrink-0", children: _jsx("button", { type: "button", className: cn('app-no-drag -mb-1 inline-flex min-h-12 shrink-0 cursor-pointer items-center justify-center self-end rounded-t-lg border border-b-2 px-3 text-[14px]', tabItem(false)), title: title, "aria-label": ariaLabel, onClick: onClick, children: _jsx(FaIcon, { icon: faPlus, className: "h-3.5 w-3.5" }) }) }));
12
+ }
13
+ return (_jsx("div", { className: "-mb-1 flex shrink-0 items-end px-1", children: _jsx("button", { type: "button", className: "hc-tab-new-button app-no-drag mb-2.5 inline-flex shrink-0 cursor-pointer items-center justify-center border-none bg-transparent text-[14px] text-muted hover:bg-selection hover:text-text focus-visible:bg-selection focus-visible:text-text", title: title, "aria-label": ariaLabel, onClick: onClick, children: _jsx(FaIcon, { icon: faPlus, className: "h-3.5 w-3.5" }) }) }));
14
+ }
@@ -0,0 +1,92 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import type { MenuItem } from '../RowActionsMenu/index.js';
3
+ import type { TabBarItem, TabBarNewTab, TabBarSortableCursor } from './types.js';
4
+ interface Props<TId extends string | number> {
5
+ /**
6
+ * Open tabs to render in display order.
7
+ */
8
+ tabs: TabBarItem<TId>[];
9
+ /**
10
+ * ID of the currently active tab.
11
+ */
12
+ activeId: TId;
13
+ /**
14
+ * When true, tabs wrap to multiple rows; otherwise they scroll horizontally.
15
+ */
16
+ wrap: boolean;
17
+ /**
18
+ * Accessible name for the tab list.
19
+ */
20
+ ariaLabel: string;
21
+ /**
22
+ * Prefix for tab element ids (for example `request-tab-`).
23
+ */
24
+ tabIdPrefix: string;
25
+ /**
26
+ * Prefix for linked tab panel ids (for example `request-tabpanel-`).
27
+ */
28
+ panelIdPrefix: string;
29
+ /**
30
+ * Prefix for dnd-kit sortable ids. Defaults to `${tabIdPrefix}sort:`.
31
+ */
32
+ sortablePrefix?: string;
33
+ /**
34
+ * Configuration for the new-tab control.
35
+ */
36
+ newTab: TabBarNewTab;
37
+ /**
38
+ * Called when the user selects a tab.
39
+ */
40
+ onSelect: (id: TId) => void;
41
+ /**
42
+ * Called when the user closes a tab.
43
+ */
44
+ onClose: (id: TId) => void;
45
+ /**
46
+ * Persists a new tab order after drag-and-drop reordering.
47
+ */
48
+ onReorder: (orderedIds: TId[]) => void;
49
+ /**
50
+ * Builds grouped context menu entries when a tab is right-clicked.
51
+ */
52
+ buildContextMenuGroups?: (targetId: TId, orderedIds: TId[]) => MenuItem[][];
53
+ /**
54
+ * When ArrowDown is pressed on a focused tab, returns true if focus moved into
55
+ * the linked panel (for example request editor content).
56
+ */
57
+ onArrowDownIntoPanel?: (id: TId) => boolean;
58
+ /**
59
+ * Focuses the tab control for keyboard navigation and focus restoration.
60
+ */
61
+ onFocusTab?: (id: TId) => void;
62
+ /**
63
+ * Wraps the tab row for horizontal scrolling. Defaults to overflow-x-auto.
64
+ */
65
+ renderScrollContainer?: (row: ReactNode) => ReactNode;
66
+ /**
67
+ * Maximum width class applied to each tab shell.
68
+ */
69
+ maxTabWidthClass?: string;
70
+ /**
71
+ * Cursor style when sortable drag is enabled.
72
+ */
73
+ sortableCursor?: TabBarSortableCursor;
74
+ /**
75
+ * Additional classes for the outer tab bar container.
76
+ */
77
+ className?: string;
78
+ }
79
+ /**
80
+ * Document-style tab bar with drag reorder, keyboard navigation, close animation,
81
+ * optional context menu, and a new-tab control.
82
+ */
83
+ export declare function TabBar<TId extends string | number>({ tabs, activeId, wrap, ariaLabel, tabIdPrefix, panelIdPrefix, sortablePrefix, newTab, onSelect, onClose, onReorder, buildContextMenuGroups, onArrowDownIntoPanel, onFocusTab, renderScrollContainer, maxTabWidthClass, sortableCursor, className }: Props<TId>): JSX.Element;
84
+ export type { TabBarItem, TabBarNewTab, TabBarSortableCursor } from './types.js';
85
+ export { TabNewButton } from './TabNewButton.js';
86
+ export { TabContextMenu } from './TabContextMenu.js';
87
+ export { ClosingTabShell } from './ClosingTabShell.js';
88
+ export { TabBarShell } from './TabBarShell.js';
89
+ export { useSortableTabItem } from './useSortableTabItem.js';
90
+ export { useExitingTabItems, detectRemovedTabItems, resolveInsertBeforeId, type ExitingTabItem } from './useExitingTabItems.js';
91
+ export { buildTabCloseMenuGroups, tabIdsToCloseOthers, tabIdsToCloseToTheRight } from './tabCloseMenuHelpers.js';
92
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAkB,GAAG,EAA6B,SAAS,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAM3D,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAgCjF,UAAU,KAAK,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM;IACzC;;OAEG;IACH,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;IAExB;;OAEG;IACH,QAAQ,EAAE,GAAG,CAAC;IAEd;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,MAAM,EAAE,YAAY,CAAC;IAErB;;OAEG;IACH,QAAQ,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE5B;;OAEG;IACH,OAAO,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE3B;;OAEG;IACH,SAAS,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IAEvC;;OAEG;IACH,sBAAsB,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,EAAE,CAAC;IAE5E;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC;IAE5C;;OAEG;IACH,UAAU,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,KAAK,IAAI,CAAC;IAE/B;;OAEG;IACH,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,SAAS,CAAC;IAEtD;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC;IAEtC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqDD;;;GAGG;AACH,wBAAgB,MAAM,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,EAClD,IAAI,EACJ,QAAQ,EACR,IAAI,EACJ,SAAS,EACT,WAAW,EACX,aAAa,EACb,cAAc,EACd,MAAM,EACN,QAAQ,EACR,OAAO,EACP,SAAS,EACT,sBAAsB,EACtB,oBAAoB,EACpB,UAAU,EACV,qBAAqB,EACrB,gBAAgB,EAChB,cAAc,EACd,SAAS,EACV,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,OAAO,CAqT1B;AAED,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,qBAAqB,EACrB,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,uBAAuB,EACxB,MAAM,0BAA0B,CAAC"}
@@ -0,0 +1,224 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "@harborclient/sdk/jsx-runtime";
2
+ import { DndContext as DndContextBase, DragOverlay as DragOverlayBase, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from '@dnd-kit/core';
3
+ import { SortableContext, arrayMove, horizontalListSortingStrategy, rectSortingStrategy, sortableKeyboardCoordinates } from '@dnd-kit/sortable';
4
+ import { useEffect, useMemo, useState } from '@harborclient/sdk/react';
5
+ import { cn, resolveTabListKeyAction } from '../utils.js';
6
+ import { ClosingTabShell } from './ClosingTabShell.js';
7
+ import { TabBarShell } from './TabBarShell.js';
8
+ import { TabContextMenu } from './TabContextMenu.js';
9
+ import { TabNewButton } from './TabNewButton.js';
10
+ import { useExitingTabItems } from './useExitingTabItems.js';
11
+ const DndContext = DndContextBase;
12
+ const DragOverlay = DragOverlayBase;
13
+ /**
14
+ * Builds a stable dnd-kit sortable id for a tab row.
15
+ *
16
+ * @param prefix - Sortable id prefix.
17
+ * @param id - Open tab id.
18
+ */
19
+ function tabSortableId(prefix, id) {
20
+ return `${prefix}${String(id)}`;
21
+ }
22
+ /**
23
+ * Parses a sortable drag id back to its tab id string.
24
+ *
25
+ * @param prefix - Sortable id prefix.
26
+ * @param dragId - Sortable id from dnd-kit.
27
+ */
28
+ function parseTabSortableId(prefix, dragId) {
29
+ if (!dragId.startsWith(prefix)) {
30
+ return null;
31
+ }
32
+ return dragId.slice(prefix.length);
33
+ }
34
+ /**
35
+ * Resolves the tab list index for arrow-key navigation from keyboard focus.
36
+ *
37
+ * @param tabs - Open tabs in display order.
38
+ * @param activeId - Currently selected tab id.
39
+ * @param tabIdPrefix - Prefix for tab element ids.
40
+ * @returns Index into `tabs`, or `-1` when the list is empty.
41
+ */
42
+ function resolveFocusedTabIndex(tabs, activeId, tabIdPrefix) {
43
+ const activeElement = document.activeElement;
44
+ if (activeElement instanceof HTMLElement) {
45
+ const tabElement = activeElement.closest('[role="tab"]');
46
+ if (tabElement instanceof HTMLElement && tabElement.id.startsWith(tabIdPrefix)) {
47
+ const tabId = tabElement.id.slice(tabIdPrefix.length);
48
+ const focusedIndex = tabs.findIndex((tab) => String(tab.id) === tabId);
49
+ if (focusedIndex >= 0) {
50
+ return focusedIndex;
51
+ }
52
+ }
53
+ }
54
+ return tabs.findIndex((tab) => tab.id === activeId);
55
+ }
56
+ /**
57
+ * Document-style tab bar with drag reorder, keyboard navigation, close animation,
58
+ * optional context menu, and a new-tab control.
59
+ */
60
+ export function TabBar({ tabs, activeId, wrap, ariaLabel, tabIdPrefix, panelIdPrefix, sortablePrefix, newTab, onSelect, onClose, onReorder, buildContextMenuGroups, onArrowDownIntoPanel, onFocusTab, renderScrollContainer, maxTabWidthClass, sortableCursor, className }) {
61
+ const resolvedSortablePrefix = sortablePrefix ?? `${tabIdPrefix}sort:`;
62
+ const [activeDragId, setActiveDragId] = useState(null);
63
+ const [contextMenu, setContextMenu] = useState(null);
64
+ const getId = useMemo(() => (item) => item.id, []);
65
+ const sortableEnabled = tabs.length >= 2;
66
+ const { completeExit, getExitingBefore, removedIds } = useExitingTabItems(tabs, getId);
67
+ /**
68
+ * Stable sortable ids for open tabs.
69
+ */
70
+ const sortableIds = useMemo(() => tabs.map((tab) => tabSortableId(resolvedSortablePrefix, tab.id)), [tabs, resolvedSortablePrefix]);
71
+ /**
72
+ * Stable tab ids in display order for context menu close actions.
73
+ */
74
+ const orderedIds = useMemo(() => tabs.map((tab) => tab.id), [tabs]);
75
+ /**
76
+ * Menu groups for the open tab context menu, when one is visible.
77
+ */
78
+ const contextMenuGroups = useMemo(() => {
79
+ if (contextMenu == null || buildContextMenuGroups == null) {
80
+ return [];
81
+ }
82
+ return buildContextMenuGroups(contextMenu.targetId, orderedIds);
83
+ }, [buildContextMenuGroups, contextMenu, orderedIds]);
84
+ /**
85
+ * Moves focus to the active tab when the focused tab row was just removed.
86
+ */
87
+ useEffect(() => {
88
+ if (removedIds.length === 0 || onFocusTab == null) {
89
+ return;
90
+ }
91
+ const focusedTab = document.activeElement?.closest('[role="tab"]');
92
+ if (!(focusedTab instanceof HTMLElement) || !focusedTab.id.startsWith(tabIdPrefix)) {
93
+ return;
94
+ }
95
+ const focusedTabId = focusedTab.id.slice(tabIdPrefix.length);
96
+ if (!removedIds.some((id) => String(id) === focusedTabId)) {
97
+ return;
98
+ }
99
+ requestAnimationFrame(() => {
100
+ onFocusTab(activeId);
101
+ });
102
+ }, [activeId, onFocusTab, removedIds, tabIdPrefix]);
103
+ const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 6 } }), useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }));
104
+ /**
105
+ * Tab currently being dragged for overlay preview.
106
+ */
107
+ const activeDragTab = useMemo(() => {
108
+ if (activeDragId == null) {
109
+ return null;
110
+ }
111
+ return tabs.find((tab) => tab.id === activeDragId) ?? null;
112
+ }, [activeDragId, tabs]);
113
+ /**
114
+ * Records the tab being dragged for overlay preview.
115
+ */
116
+ const handleDragStart = (event) => {
117
+ const tabId = parseTabSortableId(resolvedSortablePrefix, String(event.active.id));
118
+ if (tabId == null) {
119
+ setActiveDragId(null);
120
+ return;
121
+ }
122
+ const match = tabs.find((tab) => String(tab.id) === tabId);
123
+ setActiveDragId(match?.id ?? null);
124
+ };
125
+ /**
126
+ * Persists a new tab order when a tab is dropped.
127
+ */
128
+ const handleDragEnd = (event) => {
129
+ const { active, over } = event;
130
+ setActiveDragId(null);
131
+ if (!over || !sortableEnabled) {
132
+ return;
133
+ }
134
+ const activeTabIdFromDrag = parseTabSortableId(resolvedSortablePrefix, String(active.id));
135
+ const overTabId = parseTabSortableId(resolvedSortablePrefix, String(over.id));
136
+ if (activeTabIdFromDrag == null || overTabId == null || activeTabIdFromDrag === overTabId) {
137
+ return;
138
+ }
139
+ const tabIds = tabs.map((tab) => tab.id);
140
+ const oldIndex = tabIds.findIndex((id) => String(id) === activeTabIdFromDrag);
141
+ const newIndex = tabIds.findIndex((id) => String(id) === overTabId);
142
+ if (oldIndex < 0 || newIndex < 0) {
143
+ return;
144
+ }
145
+ onReorder(arrayMove(tabIds, oldIndex, newIndex));
146
+ };
147
+ /**
148
+ * Moves focus and selection across open tabs with arrow, Home, and End keys.
149
+ */
150
+ const handleTabListKeyDown = (event) => {
151
+ if (event.key === 'ArrowDown' && onArrowDownIntoPanel != null) {
152
+ const activeElement = document.activeElement;
153
+ if (activeElement instanceof HTMLElement) {
154
+ const tabElement = activeElement.closest('[role="tab"]');
155
+ if (tabElement instanceof HTMLElement && tabElement.id.startsWith(tabIdPrefix)) {
156
+ const tabId = tabElement.id.slice(tabIdPrefix.length);
157
+ const match = tabs.find((tab) => String(tab.id) === tabId);
158
+ if (match != null && onArrowDownIntoPanel(match.id)) {
159
+ event.preventDefault();
160
+ return;
161
+ }
162
+ }
163
+ }
164
+ }
165
+ const currentIndex = resolveFocusedTabIndex(tabs, activeId, tabIdPrefix);
166
+ if (currentIndex < 0) {
167
+ return;
168
+ }
169
+ const nextIndex = resolveTabListKeyAction(event.key, currentIndex, tabs.length);
170
+ if (nextIndex === null) {
171
+ return;
172
+ }
173
+ event.preventDefault();
174
+ const nextTab = tabs[nextIndex];
175
+ onSelect(nextTab.id);
176
+ if (onFocusTab != null) {
177
+ requestAnimationFrame(() => {
178
+ onFocusTab(nextTab.id);
179
+ });
180
+ }
181
+ };
182
+ const tabRowClassName = wrap ? 'w-full min-w-0 py-1' : 'flex w-max flex-nowrap items-end py-1';
183
+ const tabListClassName = wrap
184
+ ? 'flex min-w-0 w-full flex-wrap items-end gap-y-2'
185
+ : 'flex items-end';
186
+ const sortStrategy = wrap ? rectSortingStrategy : horizontalListSortingStrategy;
187
+ const newTabButton = (_jsx(TabNewButton, { wrapped: wrap, ariaLabel: newTab.ariaLabel, title: newTab.title, onClick: newTab.onClick }));
188
+ /**
189
+ * Renders one tab shell, optionally inside a close-animation wrapper.
190
+ */
191
+ const renderTabShell = (item, options) => (_jsx(TabBarShell, { item: item, tabIndex: options.tabIndex, sortableId: tabSortableId(resolvedSortablePrefix, item.id), sortableDisabled: options.sortableDisabled, exiting: options.exiting, tabIdPrefix: tabIdPrefix, panelIdPrefix: panelIdPrefix, maxTabWidthClass: maxTabWidthClass, sortableCursor: sortableCursor, onSelect: onSelect, onClose: onClose, onContextMenu: options.onContextMenu }));
192
+ const tabRow = (_jsxs("div", { className: tabRowClassName, children: [_jsxs(DndContext, { sensors: sensors, collisionDetection: closestCenter, onDragStart: handleDragStart, onDragEnd: handleDragEnd, onDragCancel: () => setActiveDragId(null), children: [_jsxs("div", { role: "tablist", "aria-label": ariaLabel, className: tabListClassName, onKeyDown: handleTabListKeyDown, children: [_jsxs(SortableContext, { items: sortableIds, strategy: sortStrategy, children: [tabs.flatMap((tab) => {
193
+ const nodes = getExitingBefore(tab.id).map((exitingTab) => (_jsx(ClosingTabShell, { onComplete: () => completeExit(exitingTab.exitKey), children: renderTabShell(exitingTab.item, {
194
+ tabIndex: -1,
195
+ sortableDisabled: true,
196
+ exiting: true
197
+ }) }, exitingTab.exitKey)));
198
+ nodes.push(_jsx(TabBarShell, { item: tab, tabIndex: 0, sortableId: tabSortableId(resolvedSortablePrefix, tab.id), sortableDisabled: !sortableEnabled, tabIdPrefix: tabIdPrefix, panelIdPrefix: panelIdPrefix, maxTabWidthClass: maxTabWidthClass, sortableCursor: sortableCursor, onSelect: onSelect, onClose: onClose, onContextMenu: buildContextMenuGroups == null
199
+ ? undefined
200
+ : (id, event) => {
201
+ setContextMenu({
202
+ targetId: id,
203
+ x: event.clientX,
204
+ y: event.clientY
205
+ });
206
+ } }, `live-${String(tab.id)}`));
207
+ return nodes;
208
+ }), getExitingBefore(null).map((exitingTab) => (_jsx(ClosingTabShell, { onComplete: () => completeExit(exitingTab.exitKey), children: renderTabShell(exitingTab.item, {
209
+ tabIndex: -1,
210
+ sortableDisabled: true,
211
+ exiting: true
212
+ }) }, exitingTab.exitKey)))] }), wrap ? newTabButton : null] }), _jsx(DragOverlay, { children: activeDragTab ? (_jsx("div", { className: "rounded-t-lg border border-separator bg-surface px-3 py-2 text-[14px] font-medium shadow-md", children: activeDragTab.dragLabel })) : null })] }), wrap ? null : newTabButton] }));
213
+ const defaultScrollContainer = (row) => (_jsx("div", { className: "min-w-0 flex-1 overflow-x-auto", children: row }));
214
+ const scrollContainer = renderScrollContainer ?? defaultScrollContainer;
215
+ const containerClassName = cn('app-no-drag flex shrink-0 items-end border-b border-separator bg-sidebar px-2', className);
216
+ return (_jsxs("div", { className: containerClassName, children: [wrap ? _jsx("div", { className: "min-w-0 flex-1", children: tabRow }) : scrollContainer(tabRow), contextMenu != null && buildContextMenuGroups != null && (_jsx(TabContextMenu, { groups: contextMenuGroups, position: { x: contextMenu.x, y: contextMenu.y }, onClose: () => setContextMenu(null) }))] }));
217
+ }
218
+ export { TabNewButton } from './TabNewButton.js';
219
+ export { TabContextMenu } from './TabContextMenu.js';
220
+ export { ClosingTabShell } from './ClosingTabShell.js';
221
+ export { TabBarShell } from './TabBarShell.js';
222
+ export { useSortableTabItem } from './useSortableTabItem.js';
223
+ export { useExitingTabItems, detectRemovedTabItems, resolveInsertBeforeId } from './useExitingTabItems.js';
224
+ export { buildTabCloseMenuGroups, tabIdsToCloseOthers, tabIdsToCloseToTheRight } from './tabCloseMenuHelpers.js';
@@ -0,0 +1,41 @@
1
+ import type { MenuItem } from '../RowActionsMenu/index.js';
2
+ interface TabCloseMenuActions<T extends string | number> {
3
+ /**
4
+ * Closes a single tab.
5
+ */
6
+ onClose: (id: T) => void;
7
+ /**
8
+ * Closes every tab in `ids`.
9
+ */
10
+ onCloseMany: (ids: T[]) => void;
11
+ /**
12
+ * Closes tabs that are considered saved (non-dirty for requests, with messages for AI).
13
+ */
14
+ onCloseSaved: () => void;
15
+ }
16
+ /**
17
+ * Builds grouped context menu items shared by request and AI tab bars.
18
+ *
19
+ * @param orderedIds - Tab ids in display order.
20
+ * @param targetId - Tab that was right-clicked.
21
+ * @param actions - Close handlers for single, bulk, and saved-only operations.
22
+ */
23
+ export declare function buildTabCloseMenuGroups<T extends string | number>(orderedIds: readonly T[], targetId: T, actions: TabCloseMenuActions<T>): MenuItem[][];
24
+ /**
25
+ * Returns tab ids for every tab except the one at `targetId`.
26
+ *
27
+ * @param orderedIds - Tab ids in display order.
28
+ * @param targetId - Tab that should remain open.
29
+ * @returns Ids of all other tabs.
30
+ */
31
+ export declare function tabIdsToCloseOthers<T extends string | number>(orderedIds: readonly T[], targetId: T): T[];
32
+ /**
33
+ * Returns tab ids positioned to the right of `targetId` in display order.
34
+ *
35
+ * @param orderedIds - Tab ids in display order.
36
+ * @param targetId - Tab whose right-hand neighbors should be closed.
37
+ * @returns Ids of tabs after `targetId`, or an empty array when not found.
38
+ */
39
+ export declare function tabIdsToCloseToTheRight<T extends string | number>(orderedIds: readonly T[], targetId: T): T[];
40
+ export {};
41
+ //# sourceMappingURL=tabCloseMenuHelpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tabCloseMenuHelpers.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/tabCloseMenuHelpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAE3D,UAAU,mBAAmB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM;IACrD;;OAEG;IACH,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC;IAEzB;;OAEG;IACH,WAAW,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC;IAEhC;;OAEG;IACH,YAAY,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC/D,UAAU,EAAE,SAAS,CAAC,EAAE,EACxB,QAAQ,EAAE,CAAC,EACX,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAC9B,QAAQ,EAAE,EAAE,CAuBd;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC3D,UAAU,EAAE,SAAS,CAAC,EAAE,EACxB,QAAQ,EAAE,CAAC,GACV,CAAC,EAAE,CAEL;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,EAC/D,UAAU,EAAE,SAAS,CAAC,EAAE,EACxB,QAAQ,EAAE,CAAC,GACV,CAAC,EAAE,CAML"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Builds grouped context menu items shared by request and AI tab bars.
3
+ *
4
+ * @param orderedIds - Tab ids in display order.
5
+ * @param targetId - Tab that was right-clicked.
6
+ * @param actions - Close handlers for single, bulk, and saved-only operations.
7
+ */
8
+ export function buildTabCloseMenuGroups(orderedIds, targetId, actions) {
9
+ const targetIndex = orderedIds.indexOf(targetId);
10
+ const others = tabIdsToCloseOthers(orderedIds, targetId);
11
+ const toTheRight = tabIdsToCloseToTheRight(orderedIds, targetId);
12
+ const items = [{ label: 'Close', onSelect: () => actions.onClose(targetId) }];
13
+ if (orderedIds.length > 1) {
14
+ items.push({ label: 'Close others', onSelect: () => actions.onCloseMany(others) });
15
+ }
16
+ if (targetIndex >= 0 && targetIndex < orderedIds.length - 1) {
17
+ items.push({
18
+ label: 'Close to the right',
19
+ onSelect: () => actions.onCloseMany(toTheRight)
20
+ });
21
+ }
22
+ items.push({ label: 'Close saved', onSelect: () => actions.onCloseSaved() }, { label: 'Close all', onSelect: () => actions.onCloseMany([...orderedIds]) });
23
+ return [items];
24
+ }
25
+ /**
26
+ * Returns tab ids for every tab except the one at `targetId`.
27
+ *
28
+ * @param orderedIds - Tab ids in display order.
29
+ * @param targetId - Tab that should remain open.
30
+ * @returns Ids of all other tabs.
31
+ */
32
+ export function tabIdsToCloseOthers(orderedIds, targetId) {
33
+ return orderedIds.filter((id) => id !== targetId);
34
+ }
35
+ /**
36
+ * Returns tab ids positioned to the right of `targetId` in display order.
37
+ *
38
+ * @param orderedIds - Tab ids in display order.
39
+ * @param targetId - Tab whose right-hand neighbors should be closed.
40
+ * @returns Ids of tabs after `targetId`, or an empty array when not found.
41
+ */
42
+ export function tabIdsToCloseToTheRight(orderedIds, targetId) {
43
+ const index = orderedIds.indexOf(targetId);
44
+ if (index < 0) {
45
+ return [];
46
+ }
47
+ return orderedIds.slice(index + 1);
48
+ }
@@ -0,0 +1,60 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * Data for a single tab row rendered by {@link TabBar}.
4
+ */
5
+ export interface TabBarItem<TId extends string | number> {
6
+ /**
7
+ * Stable tab identifier used for selection, close, and reorder callbacks.
8
+ */
9
+ id: TId;
10
+ /**
11
+ * Whether this tab is the currently selected tab.
12
+ */
13
+ active: boolean;
14
+ /**
15
+ * Label and icon content rendered inside the tab shell.
16
+ */
17
+ content: ReactNode;
18
+ /**
19
+ * Accessible name for the tab control, including unsaved or status suffixes.
20
+ */
21
+ accessibleName: string;
22
+ /**
23
+ * Accessible name for the close button, typically including the tab title.
24
+ */
25
+ closeAccessibleName: string;
26
+ /**
27
+ * Native tooltip text when the label is truncated.
28
+ */
29
+ title?: string;
30
+ /**
31
+ * Plain-text or element preview shown in the drag overlay.
32
+ */
33
+ dragLabel: ReactNode;
34
+ /**
35
+ * When true, uses active styling even when not selected (for example tab group edit).
36
+ */
37
+ highlighted?: boolean;
38
+ }
39
+ /**
40
+ * Configuration for the new-tab "+" control at the end of the tab bar.
41
+ */
42
+ export interface TabBarNewTab {
43
+ /**
44
+ * Accessible name for the new-tab control.
45
+ */
46
+ ariaLabel: string;
47
+ /**
48
+ * Native tooltip text for the new-tab control.
49
+ */
50
+ title: string;
51
+ /**
52
+ * Called when the user opens a new tab or chat.
53
+ */
54
+ onClick: () => void;
55
+ }
56
+ /**
57
+ * Cursor style applied to draggable tab rows when reordering is enabled.
58
+ */
59
+ export type TabBarSortableCursor = 'pointer' | 'grab';
60
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM;IACrD;;OAEG;IACH,EAAE,EAAE,GAAG,CAAC;IAER;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,OAAO,EAAE,SAAS,CAAC;IAEnB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,mBAAmB,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,EAAE,SAAS,CAAC;IAErB;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,MAAM,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Snapshot of a tab row kept in the DOM while its close animation plays.
3
+ */
4
+ export interface ExitingTabItem<T, TId> {
5
+ /**
6
+ * Tab data captured at removal time.
7
+ */
8
+ item: T;
9
+ /**
10
+ * Id of the live tab immediately to the right before removal, or null when
11
+ * this tab was last in the row.
12
+ */
13
+ insertBeforeId: TId | null;
14
+ /**
15
+ * Stable React key for this exit animation instance.
16
+ */
17
+ exitKey: string;
18
+ }
19
+ /**
20
+ * Resolves the anchor id for an exiting tab: the first still-open tab that was
21
+ * to its right in the previous list, or null when none remain on the right.
22
+ *
23
+ * @param previousItems - Tab list before removal.
24
+ * @param removedIndex - Index of the tab being removed.
25
+ * @param currentIds - Ids that remain open after removal.
26
+ * @param getId - Reads a stable id from a tab item.
27
+ * @returns Anchor id for render placement, or null for end-of-row placement.
28
+ */
29
+ export declare function resolveInsertBeforeId<T, TId>(previousItems: T[], removedIndex: number, currentIds: Set<TId>, getId: (item: T) => TId): TId | null;
30
+ /**
31
+ * Detects tabs removed between two list snapshots and their render anchors.
32
+ *
33
+ * @param previousItems - Tab list before removal.
34
+ * @param currentItems - Tab list after removal.
35
+ * @param getId - Reads a stable id from a tab item.
36
+ * @returns Removed tabs with placement anchors for exit animation.
37
+ */
38
+ export declare function detectRemovedTabItems<T, TId>(previousItems: T[], currentItems: T[], getId: (item: T) => TId): Array<{
39
+ item: T;
40
+ insertBeforeId: TId | null;
41
+ }>;
42
+ interface UseExitingTabItemsResult<T, TId> {
43
+ /**
44
+ * Tabs currently playing their close animation.
45
+ */
46
+ exiting: ExitingTabItem<T, TId>[];
47
+ /**
48
+ * Ids removed in the latest list update (cleared on the next stable render).
49
+ */
50
+ removedIds: TId[];
51
+ /**
52
+ * Drops a finished exit snapshot from local state.
53
+ *
54
+ * @param exitKey - Key returned with the exiting tab entry.
55
+ */
56
+ completeExit: (exitKey: string) => void;
57
+ /**
58
+ * Returns exit snapshots that should render immediately before an anchor tab.
59
+ *
60
+ * @param anchorId - Live tab id to the right of the snapshot, or null for end-of-row.
61
+ */
62
+ getExitingBefore: (anchorId: TId | null) => ExitingTabItem<T, TId>[];
63
+ }
64
+ /**
65
+ * Tracks tab rows removed from a live list and keeps snapshots for exit animation.
66
+ *
67
+ * @param items - Current open tabs from Redux or parent state.
68
+ * @param getId - Reads a stable id from a tab item.
69
+ * @returns Exiting snapshots, placement helpers, and ids removed last update.
70
+ */
71
+ export declare function useExitingTabItems<T, TId>(items: T[], getId: (item: T) => TId): UseExitingTabItemsResult<T, TId>;
72
+ export {};
73
+ //# sourceMappingURL=useExitingTabItems.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useExitingTabItems.d.ts","sourceRoot":"","sources":["../../../src/components/TabBar/useExitingTabItems.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,EAAE,GAAG;IACpC;;OAEG;IACH,IAAI,EAAE,CAAC,CAAC;IAER;;;OAGG;IACH,cAAc,EAAE,GAAG,GAAG,IAAI,CAAC;IAE3B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAC1C,aAAa,EAAE,CAAC,EAAE,EAClB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC,EACpB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,GACtB,GAAG,GAAG,IAAI,CASZ;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,GAAG,EAC1C,aAAa,EAAE,CAAC,EAAE,EAClB,YAAY,EAAE,CAAC,EAAE,EACjB,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,GACtB,KAAK,CAAC;IAAE,IAAI,EAAE,CAAC,CAAC;IAAC,cAAc,EAAE,GAAG,GAAG,IAAI,CAAA;CAAE,CAAC,CAkBhD;AAED,UAAU,wBAAwB,CAAC,CAAC,EAAE,GAAG;IACvC;;OAEG;IACH,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAElC;;OAEG;IACH,UAAU,EAAE,GAAG,EAAE,CAAC;IAElB;;;;OAIG;IACH,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI,KAAK,cAAc,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;CACtE;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,GAAG,EACvC,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,GAAG,GACtB,wBAAwB,CAAC,CAAC,EAAE,GAAG,CAAC,CA8DlC"}