We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Error raised when removing last tab for pn.Tabs component
#### ALL software version info
Im currently on the panel 1.0.2 version

#### Description of expected behavior and the observed behavior
If i close the last tab of a Tabs component, no error should be thrown.
#### Complete, minimal, self-contained example code that reproduces the issue
```python
import panel as pn


pn.extension()

tabs = pn.Tabs(
    ("MyTab", "Hello"),
    ("MyOtherTab", "Hello2"),
    closable=True
)

tabs.servable()

```


#### Stack traceback and/or browser JavaScript console output
```
2023-05-23 09:50:01,867 ERROR: panel.reactive - Callback failed for object named "Tabs00121" changing properties {'active': -1, 'objects': []} 
Traceback (most recent call last):
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\panel\reactive.py", line 379, in _process_events
    self.param.update(**self_events)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 1895, in update
    setattr(self_or_cls, k, v)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 367, in _f
    instance_param.__set__(obj, val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\__init__.py", line 625, in __set__
    super(Dynamic,self).__set__(obj,val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 1201, in __set__
    self._validate(val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\__init__.py", line 947, in _validate
    self._validate_bounds(val, self.bounds, self.inclusive_bounds)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\__init__.py", line 920, in _validate_bounds
    raise ValueError("Parameter %r must be at least %s, "
ValueError: Parameter 'active' must be at least 0, not -1.
2023-05-23 09:50:01,872 Exception in callback functools.partial(<bound method IOLoop._discard_future_result of <tornado.platform.asyncio.AsyncIOMainLoop object at 0x000001A7A707EEE0>>, <Task finished name='Task-123' coro=<Server
Session.with_document_locked() done, defined at C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\bokeh\server\session.py:77> exception=ValueError("Parameter 'active' must be at least 0, not -1.")>)   
Traceback (most recent call last):
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\tornado\ioloop.py", line 738, in _run_callback
    ret = callback()
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\tornado\ioloop.py", line 762, in _discard_future_result
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 367, in _f
    instance_param.__set__(obj, val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\__init__.py", line 625, in __set__
    super(Dynamic,self).__set__(obj,val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 369, in _f
    return f(self, obj, val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\parameterized.py", line 1201, in __set__
    self._validate(val)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\__init__.py", line 947, in _validate
    self._validate_bounds(val, self.bounds, self.inclusive_bounds)
  File "C:\Users\R2D2\PycharmProjects\pythonProject\abahatchi\venv\lib\site-packages\param\__init__.py", line 920, in _validate_bounds
    raise ValueError("Parameter %r must be at least %s, "
ValueError: Parameter 'active' must be at least 0, not -1.

```


Setting closable=True for a Tabs component and them closing all tabs, raises an error.
This should not happen, as it breaks the compnent. Adding tabs dynamically after that does not work


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `panel/layout/tabs.py`
Content:
```
1 """
2 Layout component to lay out objects in a set of tabs.
3 """
4 from __future__ import annotations
5 
6 from collections import defaultdict
7 from typing import (
8     TYPE_CHECKING, ClassVar, List, Mapping, Type,
9 )
10 
11 import param
12 
13 from bokeh.models import Spacer as BkSpacer, TabPanel as BkTabPanel
14 
15 from ..models.tabs import Tabs as BkTabs
16 from ..viewable import Layoutable
17 from .base import NamedListPanel
18 
19 if TYPE_CHECKING:
20     from bokeh.model import Model
21 
22 
23 class Tabs(NamedListPanel):
24     """
25     The `Tabs` layout allows switching between multiple objects by clicking
26     on the corresponding tab header.
27 
28     Tab labels may be defined explicitly as part of a tuple or will be
29     inferred from the `name` parameter of the tab’s contents.
30 
31     Like `Column` and `Row`, `Tabs` has a list-like API with methods to
32     `append`, `extend`, `clear`, `insert`, `pop`, `remove` and `__setitem__`,
33     which make it possible to interactively update and modify the tabs.
34 
35     Reference: https://panel.holoviz.org/reference/layouts/Tabs.html
36 
37     :Example:
38 
39     >>> pn.Tabs(('Scatter', plot1), some_pane_with_a_name)
40     """
41 
42     closable = param.Boolean(default=False, doc="""
43         Whether it should be possible to close tabs.""")
44 
45     dynamic = param.Boolean(default=False, doc="""
46         Dynamically populate only the active tab.""")
47 
48     tabs_location = param.ObjectSelector(
49         default='above', objects=['above', 'below', 'left', 'right'], doc="""
50         The location of the tabs relative to the tab contents.""")
51 
52     height = param.Integer(default=None, bounds=(0, None))
53 
54     width = param.Integer(default=None, bounds=(0, None))
55 
56     _bokeh_model: ClassVar[Type[Model]] = BkTabs
57 
58     _direction: ClassVar[str | None] = 'vertical'
59 
60     _js_transforms: ClassVar[Mapping[str, str]] = {'tabs': """
61     var ids = [];
62     for (var t of value) {{ ids.push(t.id) }};
63     var value = ids;
64     """}
65 
66     _manual_params: ClassVar[List[str]] = ['closable']
67 
68     _rename: ClassVar[Mapping[str, str | None]] = {
69         'closable': None, 'dynamic': None, 'name': None, 'objects': 'tabs'
70     }
71 
72     _source_transforms: ClassVar[Mapping[str, str | None]] = {
73         'dynamic': None, 'objects': None
74     }
75 
76     def __init__(self, *objects, **params):
77         super().__init__(*objects, **params)
78         self._rendered = defaultdict(dict)
79         self.param.active.bounds = (0, len(self)-1)
80         self.param.watch(self._update_active, ['dynamic', 'active'])
81 
82     def _update_names(self, event):
83         self.param.active.bounds = (0, len(event.new)-1)
84         super()._update_names(event)
85 
86     def _cleanup(self, root: Model | None = None) -> None:
87         super()._cleanup(root)
88         if root:
89             if root.ref['id'] in self._panels:
90                 del self._panels[root.ref['id']]
91             if root.ref['id'] in self._rendered:
92                 del self._rendered[root.ref['id']]
93 
94     @property
95     def _preprocess_params(self):
96         return NamedListPanel._preprocess_params + (['active'] if self.dynamic else [])
97 
98     #----------------------------------------------------------------
99     # Callback API
100     #----------------------------------------------------------------
101 
102     def _process_close(self, ref, attr, old, new):
103         """
104         Handle closed tabs.
105         """
106         model, _ = self._models.get(ref)
107         if model:
108             try:
109                 inds = [old.index(tab) for tab in new]
110             except Exception:
111                 return old, None
112             old = self.objects
113             new = [old[i] for i in inds]
114         return old, new
115 
116     def _comm_change(self, doc, ref, comm, subpath, attr, old, new):
117         if attr in self._changing.get(ref, []):
118             self._changing[ref].remove(attr)
119             return
120         if attr == 'tabs':
121             old, new = self._process_close(ref, attr, old, new)
122             if new is None:
123                 return
124         super()._comm_change(doc, ref, comm, subpath, attr, old, new)
125 
126     def _server_change(self, doc, ref, subpath, attr, old, new):
127         if attr in self._changing.get(ref, []):
128             self._changing[ref].remove(attr)
129             return
130         if attr == 'tabs':
131             old, new = self._process_close(ref, attr, old, new)
132             if new is None:
133                 return
134         super()._server_change(doc, ref, subpath, attr, old, new)
135 
136     def _update_active(self, *events):
137         for event in events:
138             if event.name == 'dynamic' or (self.dynamic and event.name == 'active'):
139                 self.param.trigger('objects')
140                 return
141 
142     def _compute_sizing_mode(self, children, props):
143         children = [child.child for child in children]
144         return super()._compute_sizing_mode(children, props)
145 
146     #----------------------------------------------------------------
147     # Model API
148     #----------------------------------------------------------------
149 
150     def _manual_update(self, events, model, doc, root, parent, comm):
151         for event in events:
152             if event.name == 'closable':
153                 for child in model.tabs:
154                     child.closable = event.new
155 
156     def _get_objects(self, model, old_objects, doc, root, comm=None):
157         """
158         Returns new child models for the layout while reusing unchanged
159         models and cleaning up any dropped objects.
160         """
161         from ..pane.base import RerenderError, panel
162         new_models = []
163         if len(self._names) != len(self):
164             raise ValueError('Tab names do not match objects, ensure '
165                              'that the Tabs.objects are not modified '
166                              'directly. Found %d names, expected %d.' %
167                              (len(self._names), len(self)))
168         for i, (name, pane) in enumerate(zip(self._names, self)):
169             pane = panel(pane, name=name)
170             self.objects[i] = pane
171 
172         ref = root.ref['id']
173         panels = self._panels[ref]
174         rendered = self._rendered[ref]
175         for obj in old_objects:
176             if obj in self.objects:
177                 continue
178             obj._cleanup(root)
179             panels.pop(id(obj), None)
180             rendered.pop(id(obj), None)
181 
182         current_objects = list(self)
183         for i, (name, pane) in enumerate(zip(self._names, self)):
184             pref = id(pane)
185             hidden = self.dynamic and i != self.active
186             panel = panels.get(pref)
187             prev_hidden = (
188                 hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) and
189                 panel.child.tags == ['hidden']
190             )
191 
192             # If object has not changed, we have not toggled between
193             # hidden and unhidden state or the tabs are not
194             # dynamic then reuse the panel
195             if (pane in old_objects and pref in panels and
196                 (not (hidden ^ prev_hidden) or not (self.dynamic or prev_hidden))):
197                 new_models.append(panel)
198                 continue
199 
200             if prev_hidden and not hidden and pref in rendered:
201                 child = rendered[pref]
202             elif hidden:
203                 child = BkSpacer(**{k: v for k, v in pane.param.values().items()
204                                     if k in Layoutable.param and v is not None and
205                                     k not in ('name', 'design')})
206                 child.tags = ['hidden']
207             else:
208                 try:
209                     rendered[pref] = child = pane._get_model(doc, root, model, comm)
210                 except RerenderError:
211                     return self._get_objects(model, current_objects[:i], doc, root, comm)
212 
213             panel = panels[pref] = BkTabPanel(
214                 title=name, name=pane.name, child=child, closable=self.closable
215             )
216             new_models.append(panel)
217         return new_models
218
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py
--- a/panel/layout/tabs.py
+++ b/panel/layout/tabs.py
@@ -76,11 +76,12 @@
     def __init__(self, *objects, **params):
         super().__init__(*objects, **params)
         self._rendered = defaultdict(dict)
-        self.param.active.bounds = (0, len(self)-1)
+        self.param.active.bounds = (-1, len(self)-1)
         self.param.watch(self._update_active, ['dynamic', 'active'])
 
     def _update_names(self, event):
-        self.param.active.bounds = (0, len(event.new)-1)
+        # No active tabs will have a value of -1
+        self.param.active.bounds = (-1, len(event.new)-1)
         super()._update_names(event)
 
     def _cleanup(self, root: Model | None = None) -> None:

{"golden_diff": "diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py\n--- a/panel/layout/tabs.py\n+++ b/panel/layout/tabs.py\n@@ -76,11 +76,12 @@\n     def __init__(self, *objects, **params):\n         super().__init__(*objects, **params)\n         self._rendered = defaultdict(dict)\n-        self.param.active.bounds = (0, len(self)-1)\n+        self.param.active.bounds = (-1, len(self)-1)\n         self.param.watch(self._update_active, ['dynamic', 'active'])\n \n     def _update_names(self, event):\n-        self.param.active.bounds = (0, len(event.new)-1)\n+        # No active tabs will have a value of -1\n+        self.param.active.bounds = (-1, len(event.new)-1)\n         super()._update_names(event)\n \n     def _cleanup(self, root: Model | None = None) -> None:\n", "issue": "Error raised when removing last tab for pn.Tabs component\n#### ALL software version info\r\nIm currently on the panel 1.0.2 version\r\n\r\n#### Description of expected behavior and the observed behavior\r\nIf i close the last tab of a Tabs component, no error should be thrown.\r\n#### Complete, minimal, self-contained example code that reproduces the issue\r\n```python\r\nimport panel as pn\r\n\r\n\r\npn.extension()\r\n\r\ntabs = pn.Tabs(\r\n    (\"MyTab\", \"Hello\"),\r\n    (\"MyOtherTab\", \"Hello2\"),\r\n    closable=True\r\n)\r\n\r\ntabs.servable()\r\n\r\n```\r\n\r\n\r\n#### Stack traceback and/or browser JavaScript console output\r\n```\r\n2023-05-23 09:50:01,867 ERROR: panel.reactive - Callback failed for object named \"Tabs00121\" changing properties {'active': -1, 'objects': []} \r\nTraceback (most recent call last):\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\panel\\reactive.py\", line 379, in _process_events\r\n    self.param.update(**self_events)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 1895, in update\r\n    setattr(self_or_cls, k, v)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 367, in _f\r\n    instance_param.__set__(obj, val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 369, in _f\r\n    return f(self, obj, val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\__init__.py\", line 625, in __set__\r\n    super(Dynamic,self).__set__(obj,val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 369, in _f\r\n    return f(self, obj, val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 1201, in __set__\r\n    self._validate(val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\__init__.py\", line 947, in _validate\r\n    self._validate_bounds(val, self.bounds, self.inclusive_bounds)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\__init__.py\", line 920, in _validate_bounds\r\n    raise ValueError(\"Parameter %r must be at least %s, \"\r\nValueError: Parameter 'active' must be at least 0, not -1.\r\n2023-05-23 09:50:01,872 Exception in callback functools.partial(<bound method IOLoop._discard_future_result of <tornado.platform.asyncio.AsyncIOMainLoop object at 0x000001A7A707EEE0>>, <Task finished name='Task-123' coro=<Server\r\nSession.with_document_locked() done, defined at C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\bokeh\\server\\session.py:77> exception=ValueError(\"Parameter 'active' must be at least 0, not -1.\")>)   \r\nTraceback (most recent call last):\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\tornado\\ioloop.py\", line 738, in _run_callback\r\n    ret = callback()\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\tornado\\ioloop.py\", line 762, in _discard_future_result\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 367, in _f\r\n    instance_param.__set__(obj, val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 369, in _f\r\n    return f(self, obj, val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\__init__.py\", line 625, in __set__\r\n    super(Dynamic,self).__set__(obj,val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 369, in _f\r\n    return f(self, obj, val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\parameterized.py\", line 1201, in __set__\r\n    self._validate(val)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\__init__.py\", line 947, in _validate\r\n    self._validate_bounds(val, self.bounds, self.inclusive_bounds)\r\n  File \"C:\\Users\\R2D2\\PycharmProjects\\pythonProject\\abahatchi\\venv\\lib\\site-packages\\param\\__init__.py\", line 920, in _validate_bounds\r\n    raise ValueError(\"Parameter %r must be at least %s, \"\r\nValueError: Parameter 'active' must be at least 0, not -1.\r\n\r\n```\r\n\r\n\r\nSetting closable=True for a Tabs component and them closing all tabs, raises an error.\r\nThis should not happen, as it breaks the compnent. Adding tabs dynamically after that does not work\r\n\n", "before_files": [{"content": "\"\"\"\nLayout component to lay out objects in a set of tabs.\n\"\"\"\nfrom __future__ import annotations\n\nfrom collections import defaultdict\nfrom typing import (\n    TYPE_CHECKING, ClassVar, List, Mapping, Type,\n)\n\nimport param\n\nfrom bokeh.models import Spacer as BkSpacer, TabPanel as BkTabPanel\n\nfrom ..models.tabs import Tabs as BkTabs\nfrom ..viewable import Layoutable\nfrom .base import NamedListPanel\n\nif TYPE_CHECKING:\n    from bokeh.model import Model\n\n\nclass Tabs(NamedListPanel):\n    \"\"\"\n    The `Tabs` layout allows switching between multiple objects by clicking\n    on the corresponding tab header.\n\n    Tab labels may be defined explicitly as part of a tuple or will be\n    inferred from the `name` parameter of the tab\u2019s contents.\n\n    Like `Column` and `Row`, `Tabs` has a list-like API with methods to\n    `append`, `extend`, `clear`, `insert`, `pop`, `remove` and `__setitem__`,\n    which make it possible to interactively update and modify the tabs.\n\n    Reference: https://panel.holoviz.org/reference/layouts/Tabs.html\n\n    :Example:\n\n    >>> pn.Tabs(('Scatter', plot1), some_pane_with_a_name)\n    \"\"\"\n\n    closable = param.Boolean(default=False, doc=\"\"\"\n        Whether it should be possible to close tabs.\"\"\")\n\n    dynamic = param.Boolean(default=False, doc=\"\"\"\n        Dynamically populate only the active tab.\"\"\")\n\n    tabs_location = param.ObjectSelector(\n        default='above', objects=['above', 'below', 'left', 'right'], doc=\"\"\"\n        The location of the tabs relative to the tab contents.\"\"\")\n\n    height = param.Integer(default=None, bounds=(0, None))\n\n    width = param.Integer(default=None, bounds=(0, None))\n\n    _bokeh_model: ClassVar[Type[Model]] = BkTabs\n\n    _direction: ClassVar[str | None] = 'vertical'\n\n    _js_transforms: ClassVar[Mapping[str, str]] = {'tabs': \"\"\"\n    var ids = [];\n    for (var t of value) {{ ids.push(t.id) }};\n    var value = ids;\n    \"\"\"}\n\n    _manual_params: ClassVar[List[str]] = ['closable']\n\n    _rename: ClassVar[Mapping[str, str | None]] = {\n        'closable': None, 'dynamic': None, 'name': None, 'objects': 'tabs'\n    }\n\n    _source_transforms: ClassVar[Mapping[str, str | None]] = {\n        'dynamic': None, 'objects': None\n    }\n\n    def __init__(self, *objects, **params):\n        super().__init__(*objects, **params)\n        self._rendered = defaultdict(dict)\n        self.param.active.bounds = (0, len(self)-1)\n        self.param.watch(self._update_active, ['dynamic', 'active'])\n\n    def _update_names(self, event):\n        self.param.active.bounds = (0, len(event.new)-1)\n        super()._update_names(event)\n\n    def _cleanup(self, root: Model | None = None) -> None:\n        super()._cleanup(root)\n        if root:\n            if root.ref['id'] in self._panels:\n                del self._panels[root.ref['id']]\n            if root.ref['id'] in self._rendered:\n                del self._rendered[root.ref['id']]\n\n    @property\n    def _preprocess_params(self):\n        return NamedListPanel._preprocess_params + (['active'] if self.dynamic else [])\n\n    #----------------------------------------------------------------\n    # Callback API\n    #----------------------------------------------------------------\n\n    def _process_close(self, ref, attr, old, new):\n        \"\"\"\n        Handle closed tabs.\n        \"\"\"\n        model, _ = self._models.get(ref)\n        if model:\n            try:\n                inds = [old.index(tab) for tab in new]\n            except Exception:\n                return old, None\n            old = self.objects\n            new = [old[i] for i in inds]\n        return old, new\n\n    def _comm_change(self, doc, ref, comm, subpath, attr, old, new):\n        if attr in self._changing.get(ref, []):\n            self._changing[ref].remove(attr)\n            return\n        if attr == 'tabs':\n            old, new = self._process_close(ref, attr, old, new)\n            if new is None:\n                return\n        super()._comm_change(doc, ref, comm, subpath, attr, old, new)\n\n    def _server_change(self, doc, ref, subpath, attr, old, new):\n        if attr in self._changing.get(ref, []):\n            self._changing[ref].remove(attr)\n            return\n        if attr == 'tabs':\n            old, new = self._process_close(ref, attr, old, new)\n            if new is None:\n                return\n        super()._server_change(doc, ref, subpath, attr, old, new)\n\n    def _update_active(self, *events):\n        for event in events:\n            if event.name == 'dynamic' or (self.dynamic and event.name == 'active'):\n                self.param.trigger('objects')\n                return\n\n    def _compute_sizing_mode(self, children, props):\n        children = [child.child for child in children]\n        return super()._compute_sizing_mode(children, props)\n\n    #----------------------------------------------------------------\n    # Model API\n    #----------------------------------------------------------------\n\n    def _manual_update(self, events, model, doc, root, parent, comm):\n        for event in events:\n            if event.name == 'closable':\n                for child in model.tabs:\n                    child.closable = event.new\n\n    def _get_objects(self, model, old_objects, doc, root, comm=None):\n        \"\"\"\n        Returns new child models for the layout while reusing unchanged\n        models and cleaning up any dropped objects.\n        \"\"\"\n        from ..pane.base import RerenderError, panel\n        new_models = []\n        if len(self._names) != len(self):\n            raise ValueError('Tab names do not match objects, ensure '\n                             'that the Tabs.objects are not modified '\n                             'directly. Found %d names, expected %d.' %\n                             (len(self._names), len(self)))\n        for i, (name, pane) in enumerate(zip(self._names, self)):\n            pane = panel(pane, name=name)\n            self.objects[i] = pane\n\n        ref = root.ref['id']\n        panels = self._panels[ref]\n        rendered = self._rendered[ref]\n        for obj in old_objects:\n            if obj in self.objects:\n                continue\n            obj._cleanup(root)\n            panels.pop(id(obj), None)\n            rendered.pop(id(obj), None)\n\n        current_objects = list(self)\n        for i, (name, pane) in enumerate(zip(self._names, self)):\n            pref = id(pane)\n            hidden = self.dynamic and i != self.active\n            panel = panels.get(pref)\n            prev_hidden = (\n                hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) and\n                panel.child.tags == ['hidden']\n            )\n\n            # If object has not changed, we have not toggled between\n            # hidden and unhidden state or the tabs are not\n            # dynamic then reuse the panel\n            if (pane in old_objects and pref in panels and\n                (not (hidden ^ prev_hidden) or not (self.dynamic or prev_hidden))):\n                new_models.append(panel)\n                continue\n\n            if prev_hidden and not hidden and pref in rendered:\n                child = rendered[pref]\n            elif hidden:\n                child = BkSpacer(**{k: v for k, v in pane.param.values().items()\n                                    if k in Layoutable.param and v is not None and\n                                    k not in ('name', 'design')})\n                child.tags = ['hidden']\n            else:\n                try:\n                    rendered[pref] = child = pane._get_model(doc, root, model, comm)\n                except RerenderError:\n                    return self._get_objects(model, current_objects[:i], doc, root, comm)\n\n            panel = panels[pref] = BkTabPanel(\n                title=name, name=pane.name, child=child, closable=self.closable\n            )\n            new_models.append(panel)\n        return new_models\n", "path": "panel/layout/tabs.py"}], "after_files": [{"content": "\"\"\"\nLayout component to lay out objects in a set of tabs.\n\"\"\"\nfrom __future__ import annotations\n\nfrom collections import defaultdict\nfrom typing import (\n    TYPE_CHECKING, ClassVar, List, Mapping, Type,\n)\n\nimport param\n\nfrom bokeh.models import Spacer as BkSpacer, TabPanel as BkTabPanel\n\nfrom ..models.tabs import Tabs as BkTabs\nfrom ..viewable import Layoutable\nfrom .base import NamedListPanel\n\nif TYPE_CHECKING:\n    from bokeh.model import Model\n\n\nclass Tabs(NamedListPanel):\n    \"\"\"\n    The `Tabs` layout allows switching between multiple objects by clicking\n    on the corresponding tab header.\n\n    Tab labels may be defined explicitly as part of a tuple or will be\n    inferred from the `name` parameter of the tab\u2019s contents.\n\n    Like `Column` and `Row`, `Tabs` has a list-like API with methods to\n    `append`, `extend`, `clear`, `insert`, `pop`, `remove` and `__setitem__`,\n    which make it possible to interactively update and modify the tabs.\n\n    Reference: https://panel.holoviz.org/reference/layouts/Tabs.html\n\n    :Example:\n\n    >>> pn.Tabs(('Scatter', plot1), some_pane_with_a_name)\n    \"\"\"\n\n    closable = param.Boolean(default=False, doc=\"\"\"\n        Whether it should be possible to close tabs.\"\"\")\n\n    dynamic = param.Boolean(default=False, doc=\"\"\"\n        Dynamically populate only the active tab.\"\"\")\n\n    tabs_location = param.ObjectSelector(\n        default='above', objects=['above', 'below', 'left', 'right'], doc=\"\"\"\n        The location of the tabs relative to the tab contents.\"\"\")\n\n    height = param.Integer(default=None, bounds=(0, None))\n\n    width = param.Integer(default=None, bounds=(0, None))\n\n    _bokeh_model: ClassVar[Type[Model]] = BkTabs\n\n    _direction: ClassVar[str | None] = 'vertical'\n\n    _js_transforms: ClassVar[Mapping[str, str]] = {'tabs': \"\"\"\n    var ids = [];\n    for (var t of value) {{ ids.push(t.id) }};\n    var value = ids;\n    \"\"\"}\n\n    _manual_params: ClassVar[List[str]] = ['closable']\n\n    _rename: ClassVar[Mapping[str, str | None]] = {\n        'closable': None, 'dynamic': None, 'name': None, 'objects': 'tabs'\n    }\n\n    _source_transforms: ClassVar[Mapping[str, str | None]] = {\n        'dynamic': None, 'objects': None\n    }\n\n    def __init__(self, *objects, **params):\n        super().__init__(*objects, **params)\n        self._rendered = defaultdict(dict)\n        self.param.active.bounds = (-1, len(self)-1)\n        self.param.watch(self._update_active, ['dynamic', 'active'])\n\n    def _update_names(self, event):\n        # No active tabs will have a value of -1\n        self.param.active.bounds = (-1, len(event.new)-1)\n        super()._update_names(event)\n\n    def _cleanup(self, root: Model | None = None) -> None:\n        super()._cleanup(root)\n        if root:\n            if root.ref['id'] in self._panels:\n                del self._panels[root.ref['id']]\n            if root.ref['id'] in self._rendered:\n                del self._rendered[root.ref['id']]\n\n    @property\n    def _preprocess_params(self):\n        return NamedListPanel._preprocess_params + (['active'] if self.dynamic else [])\n\n    #----------------------------------------------------------------\n    # Callback API\n    #----------------------------------------------------------------\n\n    def _process_close(self, ref, attr, old, new):\n        \"\"\"\n        Handle closed tabs.\n        \"\"\"\n        model, _ = self._models.get(ref)\n        if model:\n            try:\n                inds = [old.index(tab) for tab in new]\n            except Exception:\n                return old, None\n            old = self.objects\n            new = [old[i] for i in inds]\n        return old, new\n\n    def _comm_change(self, doc, ref, comm, subpath, attr, old, new):\n        if attr in self._changing.get(ref, []):\n            self._changing[ref].remove(attr)\n            return\n        if attr == 'tabs':\n            old, new = self._process_close(ref, attr, old, new)\n            if new is None:\n                return\n        super()._comm_change(doc, ref, comm, subpath, attr, old, new)\n\n    def _server_change(self, doc, ref, subpath, attr, old, new):\n        if attr in self._changing.get(ref, []):\n            self._changing[ref].remove(attr)\n            return\n        if attr == 'tabs':\n            old, new = self._process_close(ref, attr, old, new)\n            if new is None:\n                return\n        super()._server_change(doc, ref, subpath, attr, old, new)\n\n    def _update_active(self, *events):\n        for event in events:\n            if event.name == 'dynamic' or (self.dynamic and event.name == 'active'):\n                self.param.trigger('objects')\n                return\n\n    def _compute_sizing_mode(self, children, props):\n        children = [child.child for child in children]\n        return super()._compute_sizing_mode(children, props)\n\n    #----------------------------------------------------------------\n    # Model API\n    #----------------------------------------------------------------\n\n    def _manual_update(self, events, model, doc, root, parent, comm):\n        for event in events:\n            if event.name == 'closable':\n                for child in model.tabs:\n                    child.closable = event.new\n\n    def _get_objects(self, model, old_objects, doc, root, comm=None):\n        \"\"\"\n        Returns new child models for the layout while reusing unchanged\n        models and cleaning up any dropped objects.\n        \"\"\"\n        from ..pane.base import RerenderError, panel\n        new_models = []\n        if len(self._names) != len(self):\n            raise ValueError('Tab names do not match objects, ensure '\n                             'that the Tabs.objects are not modified '\n                             'directly. Found %d names, expected %d.' %\n                             (len(self._names), len(self)))\n        for i, (name, pane) in enumerate(zip(self._names, self)):\n            pane = panel(pane, name=name)\n            self.objects[i] = pane\n\n        ref = root.ref['id']\n        panels = self._panels[ref]\n        rendered = self._rendered[ref]\n        for obj in old_objects:\n            if obj in self.objects:\n                continue\n            obj._cleanup(root)\n            panels.pop(id(obj), None)\n            rendered.pop(id(obj), None)\n\n        current_objects = list(self)\n        for i, (name, pane) in enumerate(zip(self._names, self)):\n            pref = id(pane)\n            hidden = self.dynamic and i != self.active\n            panel = panels.get(pref)\n            prev_hidden = (\n                hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) and\n                panel.child.tags == ['hidden']\n            )\n\n            # If object has not changed, we have not toggled between\n            # hidden and unhidden state or the tabs are not\n            # dynamic then reuse the panel\n            if (pane in old_objects and pref in panels and\n                (not (hidden ^ prev_hidden) or not (self.dynamic or prev_hidden))):\n                new_models.append(panel)\n                continue\n\n            if prev_hidden and not hidden and pref in rendered:\n                child = rendered[pref]\n            elif hidden:\n                child = BkSpacer(**{k: v for k, v in pane.param.values().items()\n                                    if k in Layoutable.param and v is not None and\n                                    k not in ('name', 'design')})\n                child.tags = ['hidden']\n            else:\n                try:\n                    rendered[pref] = child = pane._get_model(doc, root, model, comm)\n                except RerenderError:\n                    return self._get_objects(model, current_objects[:i], doc, root, comm)\n\n            panel = panels[pref] = BkTabPanel(\n                title=name, name=pane.name, child=child, closable=self.closable\n            )\n            new_models.append(panel)\n        return new_models\n", "path": "panel/layout/tabs.py"}]}