diff --git a/ipywidgets/widgets/domwidget.py b/ipywidgets/widgets/domwidget.py index 6584220874..f86b4ced70 100644 --- a/ipywidgets/widgets/domwidget.py +++ b/ipywidgets/widgets/domwidget.py @@ -3,10 +3,11 @@ """Contains the DOMWidget class""" -from traitlets import Unicode, Bool, Tuple, default +from traitlets import Unicode, Tuple from .widget import Widget, widget_serialization -from .trait_types import Color, InstanceDict +from .trait_types import InstanceDict from .widget_layout import Layout +from .widget_style import Style class DOMWidget(Widget): @@ -37,8 +38,17 @@ def remove_class(self, className): return self -class LabeledWidget(DOMWidget): - """Widget that has a description label to the side.""" +class DescriptionStyle(Style, Widget): + """Description style widget.""" + _model_name = Unicode('DescriptionStyleModel').tag(sync=True) + description_width = Unicode().tag(sync=True) + - _model_name = Unicode('LabeledWidgetModel').tag(sync=True) +class DescriptionWidget(DOMWidget): + """Widget that has a description label to the side.""" + _model_name = Unicode('DescriptionModel').tag(sync=True) description = Unicode('', help="Description of the control.").tag(sync=True) + style = InstanceDict(DescriptionStyle).tag(sync=True, **widget_serialization) + +# For backwards compatibility to ipywidgets 6.0 +LabeledWidget = DescriptionWidget diff --git a/ipywidgets/widgets/widget_int.py b/ipywidgets/widgets/widget_int.py index a526da865f..630d0de9eb 100644 --- a/ipywidgets/widgets/widget_int.py +++ b/ipywidgets/widgets/widget_int.py @@ -6,11 +6,10 @@ Represents an unbounded int using a widget. """ -from .domwidget import LabeledWidget +from .domwidget import LabeledWidget, DescriptionStyle from .valuewidget import ValueWidget from .widget import register, widget_serialization from .widget_core import CoreWidget -from .widget_style import Style from traitlets import Instance from .trait_types import Color, InstanceDict from traitlets import ( @@ -143,7 +142,7 @@ class BoundedIntText(_BoundedInt): @register -class SliderStyle(Style, CoreWidget): +class SliderStyle(DescriptionStyle, CoreWidget): """Button style widget.""" _model_name = Unicode('SliderStyleModel').tag(sync=True) handle_color = Color(None, allow_none=True).tag(sync=True) @@ -167,7 +166,7 @@ class IntSlider(_BoundedInt): @register -class ProgressStyle(Style, CoreWidget): +class ProgressStyle(DescriptionStyle, CoreWidget): """Button style widget.""" _model_name = Unicode('ProgressStyleModel').tag(sync=True) bar_color = Color(None, allow_none=True).tag(sync=True) diff --git a/jupyter-js-widgets/.gitignore b/jupyter-js-widgets/.gitignore index cfedd285e0..6ef1d5f4da 100644 --- a/jupyter-js-widgets/.gitignore +++ b/jupyter-js-widgets/.gitignore @@ -2,3 +2,4 @@ css/widgets.built.css lib-embed/ docs-embed/ dist/ +test/coverage/ diff --git a/jupyter-js-widgets/css/widgets-base.css b/jupyter-js-widgets/css/widgets-base.css index 6468291fe8..a57d3d8191 100644 --- a/jupyter-js-widgets/css/widgets-base.css +++ b/jupyter-js-widgets/css/widgets-base.css @@ -21,8 +21,7 @@ --jp-widgets-inline-width-short: calc(var(--jp-widgets-inline-width) / 2 - var(--jp-widgets-margin)); --jp-widgets-inline-width-tiny: calc(var(--jp-widgets-inline-width-short) / 2 - var(--jp-widgets-margin)); --jp-widgets-inline-margin: 4px; /* margin between inline elements */ - --jp-widgets-inline-label-min-width: 80px; - --jp-widgets-inline-label-max-width: calc(var(--jp-widgets-inline-width) - var(--jp-widgets-inline-margin) - var(--jp-widgets-inline-width-short)); + --jp-widgets-inline-label-width: 80px; --jp-widgets-border-width: var(--jp-border-width); --jp-widgets-vertical-height: 200px; --jp-widgets-horizontal-tab-height: 24px; @@ -262,6 +261,11 @@ /* Widget Label Styling */ +/* Override Bootstrap label css */ +.jupyter-widgets label.widget-label { + margin-bottom: initial; +} + .widget-label-basic { /* Basic Label */ color: var(--jp-widgets-label-color); @@ -288,8 +292,7 @@ color: var(--jp-widgets-label-color); text-align: right; margin-right: calc( var(--jp-widgets-inline-margin) * 2 ); - max-width: var(--jp-widgets-inline-label-max-width); - min-width: var(--jp-widgets-inline-label-min-width); + width: var(--jp-widgets-inline-label-width); flex-shrink: 0; } diff --git a/jupyter-js-widgets/src/index.ts b/jupyter-js-widgets/src/index.ts index b9192230f8..120adda433 100644 --- a/jupyter-js-widgets/src/index.ts +++ b/jupyter-js-widgets/src/index.ts @@ -20,6 +20,7 @@ export * from "./widget_controller"; export * from "./widget_selection"; export * from "./widget_selectioncontainer"; export * from "./widget_string"; +export * from "./widget_description"; export const version = (require('../package.json') as any).version; diff --git a/jupyter-js-widgets/src/widget.ts b/jupyter-js-widgets/src/widget.ts index ef037bb1dd..765829ef91 100644 --- a/jupyter-js-widgets/src/widget.ts +++ b/jupyter-js-widgets/src/widget.ts @@ -953,41 +953,3 @@ class DOMWidgetView extends WidgetView { layoutPromise: Promise; stylePromise: Promise; } - - -export -class LabeledDOMWidgetModel extends DOMWidgetModel { - defaults() { - return _.extend(super.defaults(), { - description: '', - }); - } -} - -export -class LabeledDOMWidgetView extends DOMWidgetView { - - render() { - this.label = document.createElement('div'); - this.el.appendChild(this.label); - this.label.className = 'widget-label'; - this.label.style.display = 'none'; - - this.listenTo(this.model, 'change:description', this.updateDescription); - this.updateDescription(); - } - - updateDescription() { - let description = this.model.get('description'); - if (description.length === 0) { - this.label.style.display = 'none'; - } else { - this.label.innerHTML = description; - this.typeset(this.label); - this.label.style.display = ''; - } - this.label.title = description; - } - - label: HTMLDivElement; -} diff --git a/jupyter-js-widgets/src/widget_bool.ts b/jupyter-js-widgets/src/widget_bool.ts index 29972009b2..22b19e6dd7 100644 --- a/jupyter-js-widgets/src/widget_bool.ts +++ b/jupyter-js-widgets/src/widget_bool.ts @@ -2,18 +2,22 @@ // Distributed under the terms of the Modified BSD License. import { - CoreLabeledDOMWidgetModel + CoreDescriptionModel } from './widget_core'; import { - DOMWidgetView, LabeledDOMWidgetView + DescriptionView +} from './widget_description'; + +import { + DOMWidgetView } from './widget'; import * as _ from 'underscore'; export -class BoolModel extends CoreLabeledDOMWidgetModel { +class BoolModel extends CoreDescriptionModel { defaults() { return _.extend(super.defaults(), { value: false, @@ -24,7 +28,7 @@ class BoolModel extends CoreLabeledDOMWidgetModel { } export -class CheckboxModel extends CoreLabeledDOMWidgetModel { +class CheckboxModel extends CoreDescriptionModel { defaults() { return _.extend(super.defaults(), { indent: true, @@ -35,7 +39,7 @@ class CheckboxModel extends CoreLabeledDOMWidgetModel { } export -class CheckboxView extends LabeledDOMWidgetView { +class CheckboxView extends DescriptionView { /** * Called when view is rendered. */ @@ -261,7 +265,7 @@ class ValidModel extends BoolModel { } export -class ValidView extends LabeledDOMWidgetView { +class ValidView extends DescriptionView { /** * Called when view is rendered. */ diff --git a/jupyter-js-widgets/src/widget_color.ts b/jupyter-js-widgets/src/widget_color.ts index 56380e4f45..483d11c149 100644 --- a/jupyter-js-widgets/src/widget_color.ts +++ b/jupyter-js-widgets/src/widget_color.ts @@ -2,18 +2,22 @@ // Distributed under the terms of the Modified BSD License. import { - LabeledDOMWidgetView -} from './widget'; + CoreDescriptionModel +} from './widget_core'; import { - CoreLabeledDOMWidgetModel -} from './widget_core'; + DescriptionView +} from './widget_description'; + +import { + uuid +} from './utils'; import * as _ from 'underscore'; export -class ColorPickerModel extends CoreLabeledDOMWidgetModel { +class ColorPickerModel extends CoreDescriptionModel { defaults() { return _.extend(super.defaults(), { value: 'black', @@ -25,7 +29,7 @@ class ColorPickerModel extends CoreLabeledDOMWidgetModel { } export -class ColorPickerView extends LabeledDOMWidgetView { +class ColorPickerView extends DescriptionView { render() { super.render(); this.el.classList.add('jupyter-widgets'); @@ -38,6 +42,7 @@ class ColorPickerView extends LabeledDOMWidgetView { this._textbox = document.createElement('input'); this._textbox.setAttribute('type', 'text'); + this._textbox.id = this.label.htmlFor = uuid(); this._color_container.appendChild(this._textbox); this._textbox.value = this.model.get('value'); diff --git a/jupyter-js-widgets/src/widget_core.ts b/jupyter-js-widgets/src/widget_core.ts index 926f244db8..53ccd7253b 100644 --- a/jupyter-js-widgets/src/widget_core.ts +++ b/jupyter-js-widgets/src/widget_core.ts @@ -5,9 +5,13 @@ // that are not to be used directly by third-party widget authors. import { - DOMWidgetModel, WidgetModel, LabeledDOMWidgetModel + DOMWidgetModel, WidgetModel } from './widget'; +import { + DescriptionModel +} from './widget_description'; + import * as _ from 'underscore'; export @@ -29,10 +33,10 @@ class CoreDOMWidgetModel extends DOMWidgetModel { } export -class CoreLabeledDOMWidgetModel extends LabeledDOMWidgetModel { +class CoreDescriptionModel extends DescriptionModel { defaults() { return _.extend(super.defaults(), { - _model_name: 'CoreLabeledDOMWidgetModel', + _model_name: 'CoreDescriptionModel', }); } } diff --git a/jupyter-js-widgets/src/widget_date.ts b/jupyter-js-widgets/src/widget_date.ts index aff96406fc..70c71d01c3 100644 --- a/jupyter-js-widgets/src/widget_date.ts +++ b/jupyter-js-widgets/src/widget_date.ts @@ -2,13 +2,17 @@ // Distributed under the terms of the Modified BSD License. import { - LabeledDOMWidgetView -} from './widget'; + DescriptionView +} from './widget_description'; import { - CoreLabeledDOMWidgetModel + CoreDescriptionModel } from './widget_core'; +import { + uuid +} from './utils'; + import { ManagerBase } from './manager-base' @@ -58,9 +62,9 @@ function deserialize_date(value: SerializedDate) { }; export -class DatePickerModel extends CoreLabeledDOMWidgetModel { +class DatePickerModel extends CoreDescriptionModel { static serializers = { - ...CoreLabeledDOMWidgetModel.serializers, + ...CoreDescriptionModel.serializers, value: { serialize: serialize_date, deserialize: deserialize_date @@ -77,7 +81,7 @@ class DatePickerModel extends CoreLabeledDOMWidgetModel { } export -class DatePickerView extends LabeledDOMWidgetView { +class DatePickerView extends DescriptionView { render() { super.render(); this.el.classList.add('jupyter-widgets'); @@ -86,6 +90,8 @@ class DatePickerView extends LabeledDOMWidgetView { this._datepicker = document.createElement('input'); this._datepicker.setAttribute('type', 'date'); + this._datepicker.id = this.label.htmlFor = uuid(); + this.el.appendChild(this._datepicker); this.listenTo(this.model, 'change:value', this._update_value); diff --git a/jupyter-js-widgets/src/widget_description.ts b/jupyter-js-widgets/src/widget_description.ts new file mode 100644 index 0000000000..affdb9f899 --- /dev/null +++ b/jupyter-js-widgets/src/widget_description.ts @@ -0,0 +1,80 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +import { + DOMWidgetModel, DOMWidgetView +} from './widget'; + +import { + StyleModel +} from './widget_style'; + +export +class DescriptionStyleModel extends StyleModel { + defaults() { + return {...super.defaults(), + _model_name: 'DescriptionStyleModel', + }; + } + + public static styleProperties = { + description_width: { + selector: '.widget-label', + attribute: 'width', + default: null + }, + }; +} + +export +class DescriptionModel extends DOMWidgetModel { + defaults() { + return {...super.defaults(), + description: '', + }; + } +} + +export +class DescriptionView extends DOMWidgetView { + + render() { + this.label = document.createElement('label'); + this.el.appendChild(this.label); + this.label.className = 'widget-label'; + this.label.style.display = 'none'; + + this.listenTo(this.model, 'change:description', this.updateDescription); + this.updateDescription(); + } + + updateDescription() { + let description = this.model.get('description'); + if (description.length === 0) { + this.label.style.display = 'none'; + } else { + this.label.innerHTML = description; + this.typeset(this.label); + this.label.style.display = ''; + } + this.label.title = description; + } + + label: HTMLLabelElement; +} + +/** + * For backwards compatibility with jupyter-js-widgets 2.x. + * + * Use DescriptionModel instead. + */ +export +class LabeledDOMWidgetModel extends DescriptionModel {}; + +/** + * For backwards compatibility with jupyter-js-widgets 2.x. + * + * Use DescriptionView instead. + */ +export +class LabeledDOMWidgetView extends DescriptionView {}; diff --git a/jupyter-js-widgets/src/widget_float.ts b/jupyter-js-widgets/src/widget_float.ts index 2be8d6da70..b451c8f5b6 100644 --- a/jupyter-js-widgets/src/widget_float.ts +++ b/jupyter-js-widgets/src/widget_float.ts @@ -2,12 +2,12 @@ // Distributed under the terms of the Modified BSD License. import { - LabeledDOMWidgetView -} from './widget'; + CoreDescriptionModel +} from './widget_core'; import { - CoreLabeledDOMWidgetModel -} from './widget_core'; + DescriptionView +} from './widget_description'; import * as _ from 'underscore'; @@ -19,7 +19,7 @@ import { var d3format: any = (require('d3-format') as any).format; export -class FloatModel extends CoreLabeledDOMWidgetModel { +class FloatModel extends CoreDescriptionModel { defaults() { return _.extend(super.defaults(), { _model_name: "FloatModel", diff --git a/jupyter-js-widgets/src/widget_int.ts b/jupyter-js-widgets/src/widget_int.ts index b2bd4f6c49..51583f1b56 100644 --- a/jupyter-js-widgets/src/widget_int.ts +++ b/jupyter-js-widgets/src/widget_int.ts @@ -2,16 +2,20 @@ // Distributed under the terms of the Modified BSD License. import { - CoreLabeledDOMWidgetModel + CoreDescriptionModel } from './widget_core'; import { - DOMWidgetView, LabeledDOMWidgetView + DescriptionView, DescriptionStyleModel +} from './widget_description'; + +import { + DOMWidgetView } from './widget'; import { - StyleModel -} from './widget_style'; + uuid +} from './utils'; import * as _ from 'underscore'; import * as $ from 'jquery'; @@ -20,7 +24,7 @@ import 'jquery-ui/ui/widgets/slider'; var d3format: any = (require('d3-format') as any).format; export -class IntModel extends CoreLabeledDOMWidgetModel { +class IntModel extends CoreDescriptionModel { defaults() { return _.extend(super.defaults(), { _model_name: 'IntModel', @@ -42,14 +46,15 @@ class BoundedIntModel extends IntModel { } export -class SliderStyleModel extends StyleModel { +class SliderStyleModel extends DescriptionStyleModel { defaults() { - return _.extend(super.defaults(), { + return {...super.defaults(), _model_name: 'SliderStyleModel', - }); + }; } public static styleProperties = { + ...DescriptionStyleModel.styleProperties, handle_color: { selector: '.ui-slider-handle', attribute: 'background-color', @@ -87,7 +92,7 @@ export class IntRangeSliderModel extends IntSliderModel {} export -abstract class BaseIntSliderView extends LabeledDOMWidgetView { +abstract class BaseIntSliderView extends DescriptionView { render() { super.render(); this.el.classList.add('jupyter-widgets'); @@ -475,7 +480,7 @@ class BoundedIntTextModel extends BoundedIntModel { } export -class IntTextView extends LabeledDOMWidgetView { +class IntTextView extends DescriptionView { render() { super.render(); this.el.classList.add('jupyter-widgets'); @@ -484,6 +489,7 @@ class IntTextView extends LabeledDOMWidgetView { this.textbox = document.createElement('input'); this.textbox.setAttribute('type', 'text'); + this.textbox.id = this.label.htmlFor = uuid(); this.el.appendChild(this.textbox); this.update(); // Set defaults. @@ -600,14 +606,15 @@ class IntTextView extends LabeledDOMWidgetView { export -class ProgressStyleModel extends StyleModel { +class ProgressStyleModel extends DescriptionStyleModel { defaults() { - return _.extend(super.defaults(), { + return {...super.defaults(), _model_name: 'ProgressStyleModel', - }); + }; } public static styleProperties = { + ...DescriptionStyleModel.styleProperties, bar_color: { selector: '.progress-bar', attribute: 'background-color', @@ -632,7 +639,7 @@ class IntProgressModel extends BoundedIntModel { export -class ProgressView extends LabeledDOMWidgetView { +class ProgressView extends DescriptionView { initialize(parameters) { super.initialize(parameters); this.listenTo(this.model, 'change:bar_style', this.update_bar_style); diff --git a/jupyter-js-widgets/src/widget_selection.ts b/jupyter-js-widgets/src/widget_selection.ts index b068f32373..a8d0f4a165 100644 --- a/jupyter-js-widgets/src/widget_selection.ts +++ b/jupyter-js-widgets/src/widget_selection.ts @@ -2,11 +2,19 @@ // Distributed under the terms of the Modified BSD License. import { - CoreLabeledDOMWidgetModel, + CoreDescriptionModel, } from './widget_core'; import { - LabeledDOMWidgetView, unpack_models, ViewList + DescriptionView +} from './widget_description'; + +import { + uuid +} from './utils'; + +import { + unpack_models, ViewList } from './widget'; import * as _ from 'underscore'; @@ -24,7 +32,7 @@ function scrollIfNeeded(area, elem) { } export -class SelectionModel extends CoreLabeledDOMWidgetModel { +class SelectionModel extends CoreDescriptionModel { defaults() { return {...super.defaults(), _model_name: 'SelectionModel', @@ -54,7 +62,7 @@ class DropdownModel extends SelectionModel { // For the old code, see commit f68bfbc566f3a78a8f3350b438db8ed523ce3642 export -class DropdownView extends LabeledDOMWidgetView { +class DropdownView extends DescriptionView { /** * Public constructor. */ @@ -74,6 +82,7 @@ class DropdownView extends LabeledDOMWidgetView { this.el.classList.add('widget-dropdown'); this.listbox = document.createElement('select'); + this.listbox.id = this.label.htmlFor = uuid(); this.el.appendChild(this.listbox); this._updateOptions(); this.update(); @@ -136,7 +145,7 @@ class SelectModel extends SelectionModel { } export -class SelectView extends LabeledDOMWidgetView { +class SelectView extends DescriptionView { /** * Public constructor. */ @@ -156,6 +165,7 @@ class SelectView extends LabeledDOMWidgetView { this.el.classList.add('widget-select'); this.listbox = document.createElement('select'); + this.listbox.id = this.label.htmlFor = uuid(); this.el.appendChild(this.listbox); this._updateOptions(); this.update(); @@ -223,7 +233,7 @@ class RadioButtonsModel extends SelectionModel { export -class RadioButtonsView extends LabeledDOMWidgetView { +class RadioButtonsView extends DescriptionView { /** * Called when view is rendered. */ @@ -361,7 +371,7 @@ class ToggleButtonsModel extends SelectionModel { export -class ToggleButtonsView extends LabeledDOMWidgetView { +class ToggleButtonsView extends DescriptionView { initialize(options) { this._css_state = {}; super.initialize(options); @@ -546,7 +556,7 @@ class SelectionSliderModel extends SelectionModel { export -class SelectionSliderView extends LabeledDOMWidgetView { +class SelectionSliderView extends DescriptionView { /** * Called when view is rendered. */ diff --git a/jupyter-js-widgets/src/widget_string.ts b/jupyter-js-widgets/src/widget_string.ts index 4cdd80afa7..883c26ca7f 100644 --- a/jupyter-js-widgets/src/widget_string.ts +++ b/jupyter-js-widgets/src/widget_string.ts @@ -2,18 +2,21 @@ // Distributed under the terms of the Modified BSD License. import { - LabeledDOMWidgetView -} from './widget'; + CoreDescriptionModel +} from './widget_core'; import { - CoreLabeledDOMWidgetModel -} from './widget_core'; + DescriptionView +} from './widget_description'; +import { + uuid +} from './utils'; import * as _ from 'underscore'; export -class StringModel extends CoreLabeledDOMWidgetModel { +class StringModel extends CoreDescriptionModel { defaults() { return _.extend(super.defaults(), { value: '', @@ -35,7 +38,7 @@ class HTMLModel extends StringModel { } export -class HTMLView extends LabeledDOMWidgetView { +class HTMLView extends DescriptionView { /** * Called when view is rendered. */ @@ -76,7 +79,7 @@ class HTMLMathModel extends StringModel { } export -class HTMLMathView extends LabeledDOMWidgetView { +class HTMLMathView extends DescriptionView { /** * Called when view is rendered. */ @@ -114,7 +117,7 @@ class LabelModel extends StringModel { } export -class LabelView extends LabeledDOMWidgetView { +class LabelView extends DescriptionView { /** * Called when view is rendered. */ @@ -149,7 +152,7 @@ class TextareaModel extends StringModel { } export -class TextareaView extends LabeledDOMWidgetView { +class TextareaView extends DescriptionView { /** * Called when view is rendered. */ @@ -161,6 +164,7 @@ class TextareaView extends LabeledDOMWidgetView { this.textbox = document.createElement('textarea'); this.textbox.setAttribute('rows', '5'); + this.textbox.id = this.label.htmlFor = uuid(); this.el.appendChild(this.textbox); this.update(); // Set defaults. @@ -267,7 +271,7 @@ class TextModel extends StringModel { } export -class TextView extends LabeledDOMWidgetView { +class TextView extends DescriptionView { /** * Called when view is rendered. */ @@ -279,6 +283,7 @@ class TextView extends LabeledDOMWidgetView { this.textbox = document.createElement('input'); this.textbox.setAttribute('type', this.inputType); + this.textbox.id = this.label.htmlFor = uuid(); this.el.appendChild(this.textbox); this.update(); // Set defaults. diff --git a/jupyter-js-widgets/src/widget_style.ts b/jupyter-js-widgets/src/widget_style.ts index 97f4895e36..3fdcc83af3 100644 --- a/jupyter-js-widgets/src/widget_style.ts +++ b/jupyter-js-widgets/src/widget_style.ts @@ -6,13 +6,13 @@ import { } from './widget'; import { - CoreWidgetModel -} from './widget_core'; + WidgetModel +} from './widget'; import * as _ from 'underscore'; export -class StyleModel extends CoreWidgetModel { +class StyleModel extends WidgetModel { defaults() { let Derived = this.constructor as typeof StyleModel; return _.extend(super.defaults(), {