diff --git a/CHANGELOG.md b/CHANGELOG.md index b3a08cc136..c587766fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Releases +## Version 0.12.6 + +Date: 2021-12-08 + +The 0.12.6 release fixes a major regression introduced in the last release along with a small number of pre-existing bugs. + +Regressions: + +- Always load imported bokeh extensions ([#2957](https://github.com/holoviz/panel/pull/2957)) +- Fix regression rendering `HoloViews` plotly backend ([#2961](https://github.com/holoviz/panel/pull/2961)) + +Bug fixes: + +- Do not run `Ace` import on initialization ([#2959](https://github.com/holoviz/panel/pull/2959)) +- Improve handling of `ReactiveHTML` cleanup ([#2974](https://github.com/holoviz/panel/pull/2974), [#2993](https://github.com/holoviz/panel/pull/2993)) +- Ensure empty `Str` has same height as non-empty ([#2981](https://github.com/holoviz/panel/pull/2981)) +- Ensure `Tabulator` supports grouping on numeric columns ([#2987](https://github.com/holoviz/panel/pull/2987)) +- Fix `Tabulator` with multi-index and pagination ([#2989](https://github.com/holoviz/panel/pull/2989)) +- Allow index as column name in table widgets ([#2990](https://github.com/holoviz/panel/pull/2990)) +- Ensure TemplateActions component does not have height ([#2997](https://github.com/holoviz/panel/pull/2997)) + ## Version 0.12.5 Date: 2021-11-23 @@ -13,7 +34,7 @@ Compatibility: Enhancements: -- Add 'light' to list of button types ([#2814, [#2816](https://github.com/holoviz/panel/pull/2816)) +- Add 'light' to list of button types ([#2814](https://github.com/holoviz/panel/pull/2814), [#2816](https://github.com/holoviz/panel/pull/2816)) - Make OAuth cookie expiry configurable ([#2724](https://github.com/holoviz/panel/pull/2724)) - Run `onload` callbacks with `--warm` option ([#2844](https://github.com/holoviz/panel/pull/2844)) - Improve Plotly responsive sizing behavior ([#2838](https://github.com/holoviz/panel/pull/2838)) diff --git a/doc/releases.md b/doc/releases.md index a55b6f8add..cf0a6801b0 100644 --- a/doc/releases.md +++ b/doc/releases.md @@ -1,5 +1,26 @@ # Releases +## Version 0.12.6 + +Date: 2021-12-07 + +The 0.12.6 release fixes a major regression introduced in the last release along with a small number of pre-existing bugs. + +Regressions: + +- Always load imported bokeh extensions ([#2957](https://github.com/holoviz/panel/pull/2957)) +- Fix regression rendering `HoloViews` plotly backend ([#2961](https://github.com/holoviz/panel/pull/2961)) + +Bug fixes: + +- Do not run `Ace` import on initialization ([#2959](https://github.com/holoviz/panel/pull/2959)) +- Improve handling of `ReactiveHTML` cleanup ([#2974](https://github.com/holoviz/panel/pull/2974), [#2993](https://github.com/holoviz/panel/pull/2993)) +- Ensure empty `Str` has same height as non-empty ([#2981](https://github.com/holoviz/panel/pull/2981)) +- Ensure `Tabulator` supports grouping on numeric columns ([#2987](https://github.com/holoviz/panel/pull/2987)) +- Fix `Tabulator` with multi-index and pagination ([#2989](https://github.com/holoviz/panel/pull/2989)) +- Allow index as column name in table widgets ([#2990](https://github.com/holoviz/panel/pull/2990)) +- Ensure TemplateActions component does not have height ([#2997](https://github.com/holoviz/panel/pull/2997)) + ## Version 0.12.5 Date: 2021-11-23 @@ -13,7 +34,7 @@ Compatibility: Enhancements: -- Add 'light' to list of button types ([#2814, [#2816](https://github.com/holoviz/panel/pull/2816)) +- Add 'light' to list of button types ([#2814](https://github.com/holoviz/panel/pull/2814), [#2816](https://github.com/holoviz/panel/pull/2816)) - Make OAuth cookie expiry configurable ([#2724](https://github.com/holoviz/panel/pull/2724)) - Run `onload` callbacks with `--warm` option ([#2844](https://github.com/holoviz/panel/pull/2844)) - Improve Plotly responsive sizing behavior ([#2838](https://github.com/holoviz/panel/pull/2838)) diff --git a/panel/io/resources.py b/panel/io/resources.py index e281734276..27affc8147 100644 --- a/panel/io/resources.py +++ b/panel/io/resources.py @@ -123,7 +123,7 @@ def bundle_resources(roots, resources): css_files.extend(css_resources.css_files) css_raw.extend(css_resources.css_raw) - extensions = _bundle_extensions(roots, js_resources) + extensions = _bundle_extensions(None, js_resources) if mode == "inline": js_raw.extend([ Resources._inline(bundle.artifact_path) for bundle in extensions ]) elif mode == "server": diff --git a/panel/models/ace.ts b/panel/models/ace.ts index b45552a9d5..1253d5b749 100644 --- a/panel/models/ace.ts +++ b/panel/models/ace.ts @@ -14,7 +14,6 @@ function ID() { export class AcePlotView extends PanelHTMLBoxView { model: AcePlot - protected _ace: any protected _editor: any protected _langTools: any protected _modelist: any @@ -22,7 +21,6 @@ export class AcePlotView extends PanelHTMLBoxView { initialize(): void { super.initialize() - this._ace = (window as any).ace this._container = div({ id: ID(), style: { @@ -51,9 +49,9 @@ export class AcePlotView extends PanelHTMLBoxView { if (!(this._container === this.el.childNodes[0])) this.el.appendChild(this._container) this._container.textContent = this.model.code - this._editor = this._ace.edit(this._container.id) - this._langTools = this._ace.require('ace/ext/language_tools') - this._modelist = this._ace.require("ace/ext/modelist") + this._editor = (window as any).ace.edit(this._container.id) + this._langTools = (window as any).ace.require('ace/ext/language_tools') + this._modelist = (window as any).ace.require("ace/ext/modelist") this._editor.setOptions({ enableBasicAutocompletion: true, enableSnippets: true, diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 38eb00636b..939e896a53 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -259,10 +259,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): kwargs = {p: v for p, v in self.param.get_param_values() if p in Layoutable.param and p != 'name'} - pane_type = self._panes.get(backend, Pane) - if 'tight' in pane_type.param: - kwargs['tight'] = True - child_pane = pane_type(state, **kwargs) + child_pane = self._get_pane(backend, state, **kwargs) self._update_plot(plot, child_pane) model = child_pane._get_model(doc, root, parent, comm) if ref in self._plots: @@ -273,6 +270,12 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[ref] = (model, parent) return model + def _get_pane(self, backend, state, **kwargs): + pane_type = self._panes.get(backend, Pane) + if isinstance(pane_type, type) and issubclass(pane_type, Matplotlib): + kwargs['tight'] = True + return pane_type(state, **kwargs) + def _render(self, doc, comm, root): import holoviews as hv from holoviews import Store, renderer as load_renderer diff --git a/panel/pane/markup.py b/panel/pane/markup.py index e72bd91c79..ca390d7b6e 100644 --- a/panel/pane/markup.py +++ b/panel/pane/markup.py @@ -243,8 +243,8 @@ def applies(cls, obj): def _get_properties(self): properties = super()._get_properties() - if self.object is None: - text = '' + if self.object is None or (isinstance(self.object, str) and self.object == ''): + text = '
' else: text = '
'+str(self.object)+'' return dict(properties, text=escape(text)) diff --git a/panel/reactive.py b/panel/reactive.py index fed3bb7d02..be9bdf4291 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -1262,20 +1262,13 @@ def __init__(self, **params): else: params[children_param] = panel(child_value) super().__init__(**params) + self._panes = {} self._event_callbacks = defaultdict(lambda: defaultdict(list)) def _cleanup(self, root): - for children_param in self._parser.children.values(): - children = getattr(self, children_param) - mode = self._child_config.get(children_param) - if mode != 'model': - continue - if isinstance(children, dict): - children = children.values() - elif not isinstance(children, list): - children = [children] - for child in children: - child._cleanup(root) + for child, panes in self._panes.items(): + for pane in panes: + pane._cleanup(root) super()._cleanup(root) @property @@ -1341,12 +1334,11 @@ def _get_events(self): node_events[e] = False return events - def _get_children(self, doc, root, model, comm, old_children=None): + def _get_children(self, doc, root, model, comm): from .pane import panel - old_children = old_children or {} old_models = model.children new_models = {parent: [] for parent in self._parser.children} - new_panes = {} + new_panes, internal_panes = {}, {} for parent, children_param in self._parser.children.items(): mode = self._child_config.get(children_param, 'model') @@ -1363,20 +1355,13 @@ def _get_children(self, doc, root, model, comm, old_children=None): else: panes = [panel(panes)] new_panes[parent] = panes + if isinstance(panes, dict): + panes = list(panes.values()) + internal_panes[children_param] = panes - for children_param, old_panes in old_children.items(): - mode = self._child_config.get(children_param, 'model') - if mode == 'literal': - continue - panes = getattr(self, children_param) - if not isinstance(panes, (list, dict)): - panes = [panes] - old_panes = [old_panes] - elif isinstance(panes, dict): - panes = panes.values() - old_panes = old_panes.values() + for children_param, old_panes in self._panes.items(): for old_pane in old_panes: - if old_pane not in panes and hasattr(old_pane, '_cleanup'): + if old_pane not in internal_panes.get(children_param, []): old_pane._cleanup(root) for parent, child_panes in new_panes.items(): @@ -1386,11 +1371,9 @@ def _get_children(self, doc, root, model, comm, old_children=None): mode = self._child_config.get(children_param, 'model') if mode == 'literal': new_models[parent] = children_param - elif children_param in old_children: + elif children_param in self._panes: # Find existing models - old_panes = old_children[children_param] - if not isinstance(old_panes, (list, dict)): - old_panes = [old_panes] + old_panes = self._panes[children_param] for i, pane in enumerate(child_panes): if pane in old_panes and root.ref['id'] in pane._models: child, _ = pane._models[root.ref['id']] @@ -1405,6 +1388,7 @@ def _get_children(self, doc, root, model, comm, old_children=None): pane._get_model(doc, root, model, comm) for pane in child_panes ] + self._panes = internal_panes return self._process_children(doc, root, model, comm, new_models) def _get_template(self): @@ -1556,10 +1540,9 @@ def _update_model(self, events, msg, root, model, doc, comm): else: data_msg[prop] = v if new_children: - old_children = {key: events[key].old for key in new_children} if self._parser.looped: model_msg['html'] = escape(self._get_template()) - children = self._get_children(doc, root, model, comm, old_children) + children = self._get_children(doc, root, model, comm) else: children = None if children is not None: diff --git a/panel/template/base.py b/panel/template/base.py index 7933eed594..ef6da0ca62 100644 --- a/panel/template/base.py +++ b/panel/template/base.py @@ -335,7 +335,9 @@ class TemplateActions(ReactiveHTML): close_modal = param.Integer(default=0) - _template = "" + margin = param.Integer(default=0) + + _template = "" _scripts = { 'open_modal': ["document.getElementById('pn-Modal').style.display = 'block'"], diff --git a/panel/tests/test_reactive.py b/panel/tests/test_reactive.py index 964dd88fb2..caf4dc85ab 100644 --- a/panel/tests/test_reactive.py +++ b/panel/tests/test_reactive.py @@ -291,13 +291,19 @@ class TestChildren(ReactiveHTML): test = TestChildren(children=[widget]) root = test.get_root() assert root.children == {'div': [widget._models[root.ref['id']][0]]} + assert len(widget._models) == 1 + assert test._panes == {'children': [widget]} widget_new = TextInput() test.children = [widget_new] assert len(widget._models) == 0 assert root.children == {'div': [widget_new._models[root.ref['id']][0]]} + assert test._panes == {'children': [widget_new]} - + test._cleanup(root) + assert len(test._models) == 0 + assert len(widget_new._models) == 0 + def test_reactive_html_templated_children(): @@ -323,12 +329,52 @@ class TestTemplatedChildren(ReactiveHTML): root = test.get_root() assert root.looped == ['option'] assert root.children == {'option': [widget._models[root.ref['id']][0]]} + assert test._panes == {'children': [widget]} widget_new = TextInput() test.children = [widget_new] assert len(widget._models) == 0 assert root.children == {'option': [widget_new._models[root.ref['id']][0]]} + assert test._panes == {'children': [widget_new]} + + +def test_reactive_html_templated_dict_children(): + + class TestTemplatedChildren(ReactiveHTML): + + children = param.Dict(default={}) + + _template = """ +