( state: RightSidebarLayoutState, tab: TabType, sourceTabsetId: string, targetTabsetId: string, edge: TabDockEdge )
| 686 | * - avoiding empty tabsets when a user drags out the last remaining tab |
| 687 | */ |
| 688 | export function dockTabToEdge( |
| 689 | state: RightSidebarLayoutState, |
| 690 | tab: TabType, |
| 691 | sourceTabsetId: string, |
| 692 | targetTabsetId: string, |
| 693 | edge: TabDockEdge |
| 694 | ): RightSidebarLayoutState { |
| 695 | const source = findTabset(state.root, sourceTabsetId); |
| 696 | const target = findTabset(state.root, targetTabsetId); |
| 697 | |
| 698 | if (source?.type !== "tabset" || target?.type !== "tabset") { |
| 699 | return state; |
| 700 | } |
| 701 | |
| 702 | if (!source.tabs.includes(tab)) { |
| 703 | return state; |
| 704 | } |
| 705 | |
| 706 | const splitDirection: "horizontal" | "vertical" = |
| 707 | edge === "top" || edge === "bottom" ? "horizontal" : "vertical"; |
| 708 | const insertBefore = edge === "top" || edge === "left"; |
| 709 | |
| 710 | const splitAlloc = allocId(state, "split"); |
| 711 | const tabsetAlloc = allocId({ ...state, nextId: splitAlloc.nextId }, "tabset"); |
| 712 | |
| 713 | const newTabset: Extract<RightSidebarLayoutNode, { type: "tabset" }> = { |
| 714 | type: "tabset", |
| 715 | id: tabsetAlloc.id, |
| 716 | tabs: [tab], |
| 717 | activeTab: tab, |
| 718 | }; |
| 719 | |
| 720 | const updateNode = (node: RightSidebarLayoutNode): RightSidebarLayoutNode | null => { |
| 721 | if (node.type === "tabset") { |
| 722 | if (node.id === targetTabsetId) { |
| 723 | let updatedTarget = node; |
| 724 | |
| 725 | // When dragging out of this tabset, remove the tab before splitting. |
| 726 | if (sourceTabsetId === targetTabsetId) { |
| 727 | const remaining = node.tabs.filter((t) => t !== tab); |
| 728 | const fallbackTab = getFallbackTabForEmptyTabset(tab); |
| 729 | const nextTabs = remaining.length > 0 ? remaining : [fallbackTab]; |
| 730 | const nextActiveTab = |
| 731 | node.activeTab === tab || !nextTabs.includes(node.activeTab) |
| 732 | ? nextTabs[0] |
| 733 | : node.activeTab; |
| 734 | updatedTarget = { ...node, tabs: nextTabs, activeTab: nextActiveTab }; |
| 735 | } |
| 736 | |
| 737 | const children: [RightSidebarLayoutNode, RightSidebarLayoutNode] = insertBefore |
| 738 | ? [newTabset, updatedTarget] |
| 739 | : [updatedTarget, newTabset]; |
| 740 | |
| 741 | return { |
| 742 | type: "split", |
| 743 | id: splitAlloc.id, |
| 744 | direction: splitDirection, |
| 745 | sizes: [50, 50], |
no test coverage detected