Repository: ant-design-blazor/ant-design-blazor (csharp)

Issue:
Reusetab and context menu display error
### Describe the bug

1. in reusetab after use draggable feature, when drag started, and right click mouse to popup the context menu, the position of menu is in a remote area like below

2. after click the close button of the tabs, and then reopen a tab, the tab can not create properly

`blazor.webassembly.js?v=0.1.14:1 Uncaught (in promise) Error: System.ArgumentException: There is no tracked object with id '94'. Perhaps the DotNetObjectReference instance was already disposed. (Parameter 'dotNetObjectId')
   at Microsoft.JSInterop.JSRuntime.GetObjectReference(:5001/Int64 dotNetObjectId)
   at Microsoft.JSInterop.Infrastructure.DotNetDispatcher.BeginInvokeDotNet(:5001/JSRuntime jsRuntime, DotNetInvocationInfo invocationInfo, String argsJson)
    at Object.endInvokeDotNetFromJS (blazor.webassembly.js?v=0.1.14:1:3531)
    at Object.Rt [as endInvokeDotNetFromJS] (blazor.webassembly.js?v=0.1.14:1:60651)
    at Object.St [as invokeJSFromDotNet] (blazor.webassembly.js?v=0.1.14:1:60134)
    at _mono_wasm_invoke_js_blazor (dotnet.6.0.8.5qvbcwublj.js:1:195300)
    at 00971db2:0x1a48f
    at 00971db2:0xce5d
    at 00971db2:0xbd70
    at 00971db2:0xabf00
    at 00971db2:0x6fcc1
    at 00971db2:0x7032e`

### Further technical details
- AntDesign Nuget Package version:0.12.4

Pull request:
<!--
First of all, thank you for your contribution! 😄

New feature please send pull request to feature branch, and rest to master branch.
Pull request will be merged after one of collaborators approve.
Please makes sure that these form are filled before submitting your pull request, thank you!

[[中文版模板 / Chinese template](https://github.com/ant-design-blazor/ant-design-blazor/blob/master/.github/PULL_REQUEST_TEMPLATE/pr_cn.md)]

-->

### 🤔 This is a ...

- [ ] New feature
- [x] Bug fix
- [ ] Site / documentation update
- [ ] Demo update
- [ ] Component style update
- [ ] Bundle size optimization
- [ ] Performance optimization
- [x] Refactoring
- [ ] Code style optimization
- [ ] Test Case
- [ ] Branch merge
- [ ] Other (about what?)

### 🔗 Related issue link

<!--
1. Describe the source of requirement, like related issue link.
-->

fixed #2719

### 💡 Background and solution

<!--
1. Describe the problem and the scenario.
2. GIF or snapshot should be provided if includes UI/interactive modification.
3. How to fix the problem, and list final API implementation and usage sample if that is an new feature.
-->

### 📝 Changelog

<!--
Describe changes from userside, and list all potential break changes or other risks.
--->

| Language   | Changelog |
| ---------- | --------- |
| 🇺🇸 English |           |
| 🇨🇳 Chinese |           |

### ☑️ Self Check before Merge

⚠️ Please check all items below before review. ⚠️

- [ ] Doc is updated/provided or not needed
- [ ] Demo is updated/provided or not needed
- [ ] Changelog is provided or not needed

Patch:
diff --git a/components/tabs/TabPane.razor b/components/tabs/TabPane.razor
index 093ca8a211..25bf19388d 100644
--- a/components/tabs/TabPane.razor
+++ b/components/tabs/TabPane.razor
@@ -8,18 +8,32 @@
         if (_hasClosed)
             return;
 
-        var ondragoverPreventDefault = _isActive;
+        @if (Parent.Draggable)
+        {
+            var ondragoverPreventDefault = _isActive;
+
+            <div style=" @TabStyles" 
+                @ondragover:preventDefault="@ondragoverPreventDefault" draggable="@Parent.Draggable.ToString()"
+                @ondragstart="@(EventUtil.AsNonRenderingEventHandler<DragEventArgs>(e => Parent.HandleDragStart(e, this)))"
+                 @ondrop="@(()=> Parent.HandleDrop(this))">
+                 @RenderTabInner
+            </div>
+        }
+        else
+        {
+            @RenderTabInner
+        }
+    }
+
+    internal void RenderTabInner(RenderTreeBuilder __builder)
+    {
         var onclickStopPropagation = !_isActive;
 
         <div @key="Key"
              @ref="_tabRef"
              @onclick:stopPropagation="@onclickStopPropagation"
              @onclick="@(() => Parent.HandleTabClick(this))"
-             @ondragover:preventDefault="@ondragoverPreventDefault"
-             @ondragstart="@(EventUtil.AsNonRenderingEventHandler<DragEventArgs>(e => Parent.HandleDragStart(e, this)))"
-             @ondrop="@(()=> Parent.HandleDrop(this))"
-             class="@ClassMapper.Class"
-             draggable="@Parent.Draggable.ToString()"
+             class="@_tabClassMapper.Class"
              id="@TabId"
              ondragover="event.preventDefault();">
             <div @onkeydown="@HandleKeydown"
@@ -63,7 +77,8 @@
 
         <div @key="Key"
              tabindex="@(_isActive ? "0" : "-1")"
-             class="@_tabPaneClassMapper.Class"
+             class="@ClassMapper.Class"
+             style="@Style"
              id="@($"rc-tabs-{Id}-panel-{Key}")"
              role="tabpanel"
              aria-hidden="@(_isActive ? "false" : "true")"
diff --git a/components/tabs/TabPane.razor.cs b/components/tabs/TabPane.razor.cs
index 552c92b187..e5c0f122cf 100644
--- a/components/tabs/TabPane.razor.cs
+++ b/components/tabs/TabPane.razor.cs
@@ -72,7 +72,7 @@ public partial class TabPane : AntDomComponentBase
 
         internal ElementReference TabBtnRef => _tabBtnRef;
 
-        private ClassMapper _tabPaneClassMapper = new();
+        private ClassMapper _tabClassMapper = new();
 
         private const string PrefixCls = "ant-tabs-tab";
         private const string TabPanePrefixCls = "ant-tabs-tabpane";
@@ -86,6 +86,8 @@ public partial class TabPane : AntDomComponentBase
         private bool _hasRendered;
         private int _tabIndex;
 
+        private string TabStyles => $"margin-right:{(Parent.Type == TabType.Line ? "24px;" : "2px;")}; order:{TabIndex};";
+
         protected override void OnInitialized()
         {
             base.OnInitialized();
@@ -117,13 +119,13 @@ public override async Task SetParametersAsync(ParameterView parameters)
 
         private void SetClass()
         {
-            ClassMapper
+            _tabClassMapper
                 .Add(PrefixCls)
                 .If($"{PrefixCls}-active", () => _isActive)
                 .If($"{PrefixCls}-with-remove", () => Closable)
                 .If($"{PrefixCls}-disabled", () => Disabled);
 
-            _tabPaneClassMapper
+            ClassMapper
                 .Add(TabPanePrefixCls)
                 .If($"{TabPanePrefixCls}-active", () => _isActive)
                 .If($"{TabPanePrefixCls}-hidden", () => !_isActive)
diff --git a/components/tabs/Tabs.razor b/components/tabs/Tabs.razor
index f3f6387ddc..ac3e48c029 100644
--- a/components/tabs/Tabs.razor
+++ b/components/tabs/Tabs.razor
@@ -33,7 +33,7 @@
 
                 <div class="@_tabsNavWarpPingClassMapper.Class" @ref="@_navWarpRef" id="@(Id)-nav-warpper">
                     <div class="ant-tabs-nav-list" style="@_navListStyle" @ref="@_navListRef" id="@(Id)-nav-list">
-                        @foreach (var tab in _tabs.OrderBy(x => x.TabIndex))
+                        @foreach (var tab in _tabs)
                         {
                             @tab.RenderTab
                         }
@@ -90,13 +90,14 @@
         </div>
 
     }    
+
     internal void RenderTabPanels(RenderTreeBuilder __builder)
     {             
         <div class="ant-tabs-content-holder ">
             <div class="@_contentClassMapper.Class">
                 @foreach (var tab in _tabs)
                 {
-                    @tab.RenderPane
+                    __builder.AddContent(tab.GetHashCode(), tab.RenderPane);
                 }
             </div>
         </div>     
diff --git a/components/tabs/Tabs.razor.cs b/components/tabs/Tabs.razor.cs
index 601bb2a4c9..14123769f2 100644
--- a/components/tabs/Tabs.razor.cs
+++ b/components/tabs/Tabs.razor.cs
@@ -320,6 +320,11 @@ internal void AddTabPane(TabPane tabPane)
 
         internal void Complete()
         {
+            if (_afterFirstRender)
+            {
+                return;
+            }
+
             if (!string.IsNullOrWhiteSpace(ActiveKey))
             {
                 var activedPane = _tabs.Find(x => x.Key == ActiveKey);
@@ -372,15 +377,20 @@ internal void RemovePane(TabPane tab)
             if (IsDisposed)
                 return;
 
+            // reorder tabs
+            _tabs.OrderBy(x => x.TabIndex).ForEach((x, i) => x.SetIndex(i));
+
             // if it is active, need to activiate the previous tab.
             if (ActiveKey == tab.Key)
             {
                 Previous();
             }
 
-            _tabs.Remove(tab);
+            _shouldRender = true;
+            _needUpdateScrollListPosition = tab.TabIndex != _tabs.Count - 1; // only update scroll list position when active tab is not the last one
 
-            _needUpdateScrollListPosition = true;
+            _tabs.Remove(tab);
+            StateHasChanged();
         }
 
         internal async Task HandleTabClick(TabPane tabPane)
@@ -398,21 +408,27 @@ internal async Task HandleTabClick(TabPane tabPane)
 
         internal void HandleKeydown(KeyboardEventArgs e, TabPane tabPane)
         {
-            var tabIndex = _tabs.FindIndex(p => p.Key == tabPane.Key);
             switch (e.Code)
             {
-                case "Enter" or "NumpadEnter": NavigateToTab(tabPane); break;
-                case "ArrowLeft": NavigateToTab(_tabs[Math.Max(0, tabIndex - 1)]); break;
-                case "ArrowRight": NavigateToTab(_tabs[Math.Min(_tabs.Count - 1, tabIndex + 1)]); break;
-                case "ArrowUp": NavigateToTab(_tabs[0]); break;
-                case "ArrowDown": NavigateToTab(_tabs[^1]); break;
+                case "Enter" or "NumpadEnter": GoTo(tabPane.TabIndex); break;
+                case "ArrowLeft": Previous(); break;
+                case "ArrowRight": Next(); break;
+                case "ArrowUp": GoTo(0); break;
+                case "ArrowDown": GoTo(_tabs.Count - 1); break;
                 default: return;
             }
         }
 
-        private void NavigateToTab(TabPane tabPane)
+        /// <summary>
+        /// Activate the tab with the specified index
+        /// </summary>
+        /// <param name="tabIndex"></param>
+        [PublicApi("1.0.0")]
+        public void GoTo(int tabIndex)
         {
-            ActivatePane(tabPane.Key);
+            var activeIndex = Math.Min(_tabs.Count - 1, Math.Max(0, tabIndex));
+            var tab = _tabs.Find(x => x.TabIndex == activeIndex);
+            ActivatePane(tab.Key);
         }
 
         /// <summary>
@@ -421,8 +437,7 @@ private void NavigateToTab(TabPane tabPane)
         [PublicApi("1.0.0")]
         public void Next()
         {
-            var currentIndex = _tabs.FindIndex(p => p.Key == ActiveKey);
-            NavigateToTab(_tabs[Math.Min(_tabs.Count - 1, currentIndex + 1)]);
+            GoTo(_activeTab.TabIndex + 1);
         }
 
         /// <summary>
@@ -431,8 +446,7 @@ public void Next()
         [PublicApi("1.0.0")]
         public void Previous()
         {
-            var currentIndex = _tabs.FindIndex(p => p.Key == ActiveKey);
-            NavigateToTab(_tabs[Math.Max(0, currentIndex - 1)]);
+            GoTo(_activeTab.TabIndex - 1);
         }
 
         /// <summary>
@@ -445,7 +459,6 @@ public void ActivatePane(string key)
             if (_tabs.Count == 0)
                 return;
 
-            var tabIndex = _tabs.FindIndex(p => p.Key == key);
             var tab = _tabs.Find(p => p.Key == key);
 
             if (tab == null)
@@ -766,8 +779,8 @@ internal void HandleDrop(TabPane tab)
         {
             if (Draggable && _draggingTab != null)
             {
-                var oldIndex = _tabs.IndexOf(_draggingTab);
-                var newIndex = _tabs.IndexOf(tab);
+                var oldIndex = _draggingTab.TabIndex;
+                var newIndex = tab.TabIndex;
 
                 if (oldIndex == newIndex)
                 {
@@ -776,7 +789,12 @@ internal void HandleDrop(TabPane tab)
 
                 tab.ExchangeWith(_draggingTab);
 
-                var diffTabs = newIndex < oldIndex ? _tabs.GetRange(newIndex, oldIndex - newIndex) : _tabs.GetRange(oldIndex, newIndex - oldIndex);
+                /* Exchange example:
+                 * 1,2,3,4,5,6 -> 1,5,3,4,2,6
+                 *   ^     ^
+                 */
+                var diffTabs = newIndex < oldIndex ? _tabs.Where(x => x.TabIndex == newIndex && x.TabIndex < oldIndex).ToList()
+                    : _tabs.Where(x => x.TabIndex > oldIndex && x.TabIndex == newIndex).ToList();
 
                 for (var i = diffTabs.Count - 2; i >= 0; i--)
                 {
@@ -787,8 +805,7 @@ internal void HandleDrop(TabPane tab)
                 _shouldRender = true;
                 _renderedActivePane = null;
                 _needUpdateScrollListPosition = true;
-
-                ActivatePane(_activeKey);
+                StateHasChanged();
             }
         }
 
diff --git a/site/AntDesign.Docs/Demos/Components/Tabs/demo/ContextMenu.razor b/site/AntDesign.Docs/Demos/Components/Tabs/demo/ContextMenu.razor
index 6180c4e312..94ee8af2df 100644
--- a/site/AntDesign.Docs/Demos/Components/Tabs/demo/ContextMenu.razor
+++ b/site/AntDesign.Docs/Demos/Components/Tabs/demo/ContextMenu.razor
@@ -1,4 +1,4 @@
-﻿<Tabs Type="@TabType.EditableCard" HideAdd>
+﻿<Tabs Type="@TabType.EditableCard" HideAdd Draggable>
     @foreach (var pane in panes)
     {
         <TabPane @key="@pane.Key" Key="@pane.Key" Tab="@pane.Title" Closable="@pane.Closable">
diff --git a/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.en-US.md b/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.en-US.md
index 508317a917..6096e1242a 100644
--- a/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.en-US.md
+++ b/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.en-US.md
@@ -16,6 +16,17 @@ Ant Design has 3 types of Tabs for different situations.
 - Normal Tabs: for EventCallbackal aspects of a page.
 - [RadioButton](/components/radio/#components-radio-demo-radiobutton): for secondary tabs.
 
+
+## Supports keyboard navigation
+
+Keyboard navigation is available when the focus is on the tab.
+
+- <kbd>ArrowLeft</kbd>: navigate to previous tab
+- <kbd>ArrowRight</kbd>: navigate to next tab
+- <kbd>ArrowUp</kbd>: navigate to first tab
+- <kbd>ArrowDown</kbd>: navigate to last tab
+- <kbd>Enter</kbd>: navigate to currently focused tab
+
 ## API
 
 ### Tabs
diff --git a/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.zh-CN.md b/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.zh-CN.md
index 13d540d2a1..433006e342 100644
--- a/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.zh-CN.md
+++ b/site/AntDesign.Docs/Demos/Components/Tabs/doc/index.zh-CN.md
@@ -19,6 +19,16 @@ Ant Design 依次提供了三级选项卡，分别用于不同的场景。
 - 既可用于容器顶部，也可用于容器内部，是最通用的 Tabs。
 - [RadioButton](/components/radio/#components-radio-demo-radiobutton) 可作为更次级的页签来使用。
 
+## 支持键盘导航
+
+当焦点在标签上时，可用键盘导航
+
+- <kbd>左键</kbd>：导航到前一个选项卡
+- <kbd>右键</kbd>：导航到后一个选项卡
+- <kbd>上键</kbd>：导航到第一个选项卡
+- <kbd>下键</kbd>：导航到最后一个选项卡
+- <kbd>回车键</kbd>：导航到当前焦点选项卡
+
 ## API
 
 ### Tabs


Test patch:
diff --git a/tests/AntDesign.Tests/Card/Card.Render.Tests.razor b/tests/AntDesign.Tests/Card/Card.Render.Tests.razor
index d5a4e8880c..6cb9e8a314 100644
--- a/tests/AntDesign.Tests/Card/Card.Render.Tests.razor
+++ b/tests/AntDesign.Tests/Card/Card.Render.Tests.razor
@@ -60,12 +60,12 @@
       <div role=""tablist"" class=""ant-tabs-nav"">
         <div class=""ant-tabs-nav-wrap"" id:ignore >
           <div class=""ant-tabs-nav-list"" id:ignore >
-            <div     class=""ant-tabs-tab ant-tabs-tab-active ant-tabs-tab-with-remove"" draggable=""False"" id:ignore ondragover=""event.preventDefault();"" >
+            <div     class=""ant-tabs-tab ant-tabs-tab-active ant-tabs-tab-with-remove"" id:ignore ondragover=""event.preventDefault();"" >
               <div  role=""tab"" aria-selected=""true"" class=""ant-tabs-tab-btn"" aria-controls:ignore tabindex=""0"" >
                 Tab 1
               </div>
             </div>
-            <div     class=""ant-tabs-tab ant-tabs-tab-with-remove"" draggable=""False"" id:ignore ondragover=""event.preventDefault();"" >
+            <div     class=""ant-tabs-tab ant-tabs-tab-with-remove"" id:ignore ondragover=""event.preventDefault();"" >
               <div  role=""tab"" aria-selected=""false"" class=""ant-tabs-tab-btn"" aria-controls:ignore tabindex=""0"" >
                 Tab 2
               </div>