diff --git a/packages/controls/src/index.ts b/packages/controls/src/index.ts index 912458d981..bb77d10b2a 100644 --- a/packages/controls/src/index.ts +++ b/packages/controls/src/index.ts @@ -23,5 +23,6 @@ export * from './widget_tagsinput'; export * from './widget_string'; export * from './widget_description'; export * from './widget_upload'; +export * from './widget_fieldset'; export const version = (require('../package.json') as any).version; diff --git a/packages/controls/src/widget_fieldset.ts b/packages/controls/src/widget_fieldset.ts new file mode 100644 index 0000000000..4f858b1bec --- /dev/null +++ b/packages/controls/src/widget_fieldset.ts @@ -0,0 +1,127 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + DOMWidgetView, + unpack_models, + ViewList, + JupyterLuminoPanelWidget, + reject, + WidgetModel, + WidgetView, +} from '@jupyter-widgets/base'; + +import { CoreDOMWidgetModel } from './widget_core'; + +import { ArrayExt } from '@lumino/algorithm'; + +import { MessageLoop } from '@lumino/messaging'; + +import { Widget } from '@lumino/widgets'; + +import $ from 'jquery'; + +export class FieldsetModel extends CoreDOMWidgetModel { + defaults(): Backbone.ObjectHash { + return { + ...super.defaults(), + _view_name: 'FieldsetView', + _model_name: 'FieldsetModel', + children: [], + box_style: '', + }; + } + + static serializers = { + ...CoreDOMWidgetModel.serializers, + children: { deserialize: unpack_models }, + }; +} + + +export class FieldsetView extends DOMWidgetView { + _createElement(tagName: string): HTMLElement { + this.luminoWidget = new JupyterLuminoPanelWidget({ view: this }); + return this.luminoWidget.node; + } + + _setElement(el: HTMLElement): void { + if (this.el || el !== this.luminoWidget.node) { + // Boxes don't allow setting the element beyond the initial creation. + throw new Error('Cannot reset the DOM element.'); + } + + this.el = this.luminoWidget.node; + this.$el = $(this.luminoWidget.node); + } + + initialize(parameters: WidgetView.IInitializeParameters): void { + super.initialize(parameters); + this.children_views = new ViewList(this.add_child_model, null, this); + this.listenTo(this.model, 'change:children', this.update_children); + this.listenTo(this.model, 'change:fieldset_style', this.update_fieldset_style); + + this.luminoWidget.addClass('jupyter-widgets'); + this.luminoWidget.addClass('widget-container'); + } + + render(): void { + super.render(); + this.update_children(); + this.set_fieldset_style(); + } + + update_children(): void { + this.children_views + ?.update(this.model.get('children')) + .then((views: DOMWidgetView[]) => { + // Notify all children that their sizes may have changed. + views.forEach((view) => { + MessageLoop.postMessage( + view.luminoWidget, + Widget.ResizeMessage.UnknownSize + ); + }); + }); + } + + update_fieldset_style(): void { + this.update_mapped_classes(FieldsetView.class_map, 'fieldset_style'); + } + + set_fieldset_style(): void { + this.set_mapped_classes(FieldsetView.class_map, 'fieldset_style'); + } + + add_child_model(model: WidgetModel): Promise { + // we insert a dummy element so the order is preserved when we add + // the rendered content later. + const dummy = new Widget(); + this.luminoWidget.addWidget(dummy); + + return this.create_child_view(model) + .then((view: DOMWidgetView) => { + // replace the dummy widget with the new one. + const i = ArrayExt.firstIndexOf(this.luminoWidget.widgets, dummy); + this.luminoWidget.insertWidget(i, view.luminoWidget); + dummy.dispose(); + return view; + }) + .catch(reject('Could not add child view to fieldset', true)); + } + + remove(): void { + this.children_views = null; + super.remove(); + } + + children_views: ViewList | null; + luminoWidget: JupyterLuminoPanelWidget; + + static class_map = { + success: ['alert', 'alert-success'], + info: ['alert', 'alert-info'], + warning: ['alert', 'alert-warning'], + danger: ['alert', 'alert-danger'], + }; +} diff --git a/python/ipywidgets/ipywidgets/widgets/__init__.py b/python/ipywidgets/ipywidgets/widgets/__init__.py index b90d3ee111..69a4a8a9d0 100644 --- a/python/ipywidgets/ipywidgets/widgets/__init__.py +++ b/python/ipywidgets/ipywidgets/widgets/__init__.py @@ -11,18 +11,48 @@ from .widget_bool import Checkbox, ToggleButton, Valid from .widget_button import Button, ButtonStyle from .widget_box import Box, HBox, VBox, GridBox -from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider, FloatLogSlider -from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider, Play, SliderStyle +from .widget_float import ( + FloatText, + BoundedFloatText, + FloatSlider, + FloatProgress, + FloatRangeSlider, + FloatLogSlider, +) +from .widget_int import ( + IntText, + BoundedIntText, + IntSlider, + IntProgress, + IntRangeSlider, + Play, + SliderStyle, +) from .widget_color import ColorPicker from .widget_date import DatePicker from .widget_datetime import DatetimePicker, NaiveDatetimePicker from .widget_time import TimePicker from .widget_output import Output -from .widget_selection import RadioButtons, ToggleButtons, ToggleButtonsStyle, Dropdown, Select, SelectionSlider, SelectMultiple, SelectionRangeSlider -from .widget_selectioncontainer import Tab, Accordion, Stack +from .widget_selection import ( + RadioButtons, + ToggleButtons, + ToggleButtonsStyle, + Dropdown, + Select, + SelectionSlider, + SelectMultiple, + SelectionRangeSlider, +) +from .....branches.widget_selectioncontainer import Tab, Accordion, Stack from .widget_string import HTML, HTMLMath, Label, Text, Textarea, Password, Combobox from .widget_controller import Controller -from .interaction import interact, interactive, fixed, interact_manual, interactive_output +from .interaction import ( + interact, + interactive, + fixed, + interact_manual, + interactive_output, +) from .widget_link import jslink, jsdlink from .widget_layout import Layout from .widget_media import Image, Video, Audio @@ -30,3 +60,4 @@ from .widget_style import Style from .widget_templates import TwoByTwoLayout, AppLayout, GridspecLayout from .widget_upload import FileUpload +from .widget_fieldset import Fieldset diff --git a/python/ipywidgets/ipywidgets/widgets/widget_fieldset.py b/python/ipywidgets/ipywidgets/widgets/widget_fieldset.py new file mode 100644 index 0000000000..4c483d557f --- /dev/null +++ b/python/ipywidgets/ipywidgets/widgets/widget_fieldset.py @@ -0,0 +1,68 @@ +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +"""Fieldset widget. + +This widget is used to group other control widgets into a fieldset. +""" + +from .widget import register, widget_serialization, Widget +from .domwidget import DOMWidget +from .widget_core import CoreWidget +from .docutils import doc_subst +from .trait_types import TypedTuple +from traitlets import Unicode, CaselessStrEnum, Instance + + +_doc_snippets = {} +_doc_snippets[ + "box_params" +] = """ + children: iterable of Widget instances + list of widgets to display + + box_style: str + one of 'success', 'info', 'warning' or 'danger', or ''. + Applies a predefined style to the box. Defaults to '', + which applies no pre-defined style. +""" + + +@register +@doc_subst(_doc_snippets) +class Fieldset(DOMWidget, CoreWidget): + """Displays controls grouped together, optionally with a caption. + + The widgets are laid out horizontally. + + Parameters + ---------- + {box_params} + + Examples + -------- + >>> import ipywidgets as widgets + >>> slider = widgets.IntSlider() + >>> widgets.Fieldset(legend="Fieldset Example", children=[slider]) + """ + + _model_name = Unicode("FieldsetModel").tag(sync=True) + _view_name = Unicode("FieldsetView").tag(sync=True) + + # Child widgets in the container. + # Using a tuple here to force reassignment to update the list. + # When a proper notifying-list trait exists, use that instead. + children = TypedTuple(trait=Instance(Widget), help="List of widget children").tag( + sync=True, **widget_serialization + ) + + box_style = CaselessStrEnum( + values=["success", "info", "warning", "danger", ""], + default_value="", + help="""Use a predefined styling for the box.""", + ).tag(sync=True) + + def __init__(self, legend="", children=(), **kwargs): + kwargs["legend"] = legend + kwargs["children"] = children + super().__init__(**kwargs)