Repository: holoviz/panel

Issue:
Accordion's active does not work correctly
#### ALL software version info
Python 3.7.6, panel==0.10.3

#### Description of expected behavior and the observed behavior
1. I would expect `accordion.active` to return list of active cards. Now it returns an empty list even if some card is open.
2. Also setting values to `accordion.active` I would expect to open only selected cards. Now it opens selected cards, but does not close the ones that were not selected.

#### Complete, minimal, self-contained example code that reproduces the issue

```python
# based on https://panel.holoviz.org/reference/layouts/Accordion.html
import panel as pn
pn.extension()

from bokeh.plotting import figure

p1 = figure(width=300, height=80, name='Scatter', margin=5)
p1.scatter([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0])

p2 = figure(width=300, height=80, name='Line', margin=5)
p2.line([0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 2, 1, 0])

accordion = pn.Accordion(p1, p2)
accordion

```

```
accordion.active
```

```
accordion.active = [0]
```

#### Screenshots or screencasts of the bug in action
![Peek 2021-02-25 12-42](https://user-images.githubusercontent.com/33317987/109148806-05f1c680-7767-11eb-9487-d8895d4fad4f.gif)



Discussion:

Resolving patch:
diff --git a/panel/layout/accordion.py b/panel/layout/accordion.py
--- a/panel/layout/accordion.py
+++ b/panel/layout/accordion.py
@@ -44,6 +44,7 @@ class Accordion(NamedListPanel):
 
     def __init__(self, *objects, **params):
         super().__init__(*objects, **params)
+        self._updating_active = False
         self.param.watch(self._update_active, ['active'])
         self.param.watch(self._update_cards, self._synced_properties)
 
@@ -82,6 +83,7 @@ def _get_objects(self, model, old_objects, doc, root, comm=None):
                     header_css_classes=['accordion-header'],
                     margin=self.margin
                 )
+                card.param.watch(self._set_active, ['collapsed'])
                 self._panels[id(pane)] = card
             card.param.set_param(**params)
             if ref in card._models:
@@ -94,7 +96,6 @@ def _get_objects(self, model, old_objects, doc, root, comm=None):
                         panel.js_on_change('collapsed', cb)
                 except RerenderError:
                     return self._get_objects(model, current_objects[:i], doc, root, comm)
-            
             new_models.append(panel)
         self._update_cards()
         self._update_active()
@@ -114,11 +115,32 @@ def _apply_style(self, i):
             margin = (0, 5, 0, 5)
         return dict(margin=margin, collapsed = i not in self.active)
 
+    def _set_active(self, *events):
+        if self._updating_active:
+            return
+        active = []
+        self._updating_active = True
+        try:
+            for i, pane in enumerate(self.objects):
+                if id(pane) not in self._panels:
+                    continue
+                elif not self._panels[id(pane)].collapsed:
+                    active.append(i)
+            self.active = active
+        finally:
+            self._updating_active = False
+
     def _update_active(self, *events):
-        for i, pane in enumerate(self.objects):
-            if id(pane) not in self._panels:
-                continue
-            self._panels[id(pane)].collapsed = i not in self.active
+        if self._updating_active:
+            return
+        self._updating_active = True
+        try:
+            for i, pane in enumerate(self.objects):
+                if id(pane) not in self._panels:
+                    continue
+                self._panels[id(pane)].collapsed = i not in self.active
+        finally:
+            self._updating_active = False
 
     def _update_cards(self, *events):
         params = {k: v for k, v in self.param.get_param_values()
diff --git a/panel/layout/card.py b/panel/layout/card.py
--- a/panel/layout/card.py
+++ b/panel/layout/card.py
@@ -49,6 +49,8 @@ class Card(Column):
 
     _bokeh_model = BkCard
     
+    _linked_props = ['collapsed']
+
     _rename = dict(Column._rename, title=None, header=None, title_css_classes=None)
 
     def __init__(self, *objects, **params):


Test patch:
diff --git a/panel/tests/layout/test_accordion.py b/panel/tests/layout/test_accordion.py
--- a/panel/tests/layout/test_accordion.py
+++ b/panel/tests/layout/test_accordion.py
@@ -121,5 +121,19 @@ def test_accordion_active(document, comm, accordion):
     accordion.active = [0, 1]
     assert not model.children[0].collapsed
     assert not model.children[1].collapsed
-    
 
+
+def test_accordion_set_card_collapsed(document, comm, accordion):
+    accordion.get_root(document, comm=comm)
+
+    c1, c2 = accordion._panels.values()
+
+    c1.collapsed = False
+    assert accordion.active == [0]
+
+    c2.collapsed = False
+    assert accordion.active == [0, 1]
+
+    c1.collapsed = True
+    c2.collapsed = True
+    assert accordion.active == []