// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { DOMWidgetView } from '@jupyter-widgets/base'; import { CoreDescriptionModel } from './widget_core'; import { DescriptionStyleModel, DescriptionView } from './widget_description'; export class CheckboxStyleModel extends DescriptionStyleModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), _model_name: 'CheckboxStyleModel', }; } public static styleProperties = { ...DescriptionStyleModel.styleProperties, background: { selector: '', attribute: 'background', default: null as any, }, }; } export class ToggleButtonStyleModel extends DescriptionStyleModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), _model_name: 'ToggleButtonStyleModel', }; } public static styleProperties = { ...DescriptionStyleModel.styleProperties, font_family: { selector: '', attribute: 'font-family', default: '', }, font_size: { selector: '', attribute: 'font-size', default: '', }, font_style: { selector: '', attribute: 'font-style', default: '', }, font_variant: { selector: '', attribute: 'font-variant', default: '', }, font_weight: { selector: '', attribute: 'font-weight', default: '', }, text_color: { selector: '', attribute: 'color', default: '', }, text_decoration: { selector: '', attribute: 'text-decoration', default: '', }, }; } export class BoolModel extends CoreDescriptionModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), value: false, disabled: false, _model_name: 'BoolModel', }; } } export class CheckboxModel extends CoreDescriptionModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), indent: true, style: null, _view_name: 'CheckboxView', _model_name: 'CheckboxModel', }; } } export class CheckboxView extends DescriptionView { /** * Called when view is rendered. */ render(): void { super.render(); this.el.classList.add('jupyter-widgets'); this.el.classList.add('widget-inline-hbox'); this.el.classList.add('widget-checkbox'); // adding a zero-width space to the label to help // the browser set the baseline correctly this.label.innerHTML = '​'; // label containing the checkbox and description span this.checkboxLabel = document.createElement('label'); this.checkboxLabel.classList.add('widget-label-basic'); this.el.appendChild(this.checkboxLabel); // checkbox this.checkbox = document.createElement('input'); this.checkbox.setAttribute('type', 'checkbox'); this.checkboxLabel.appendChild(this.checkbox); // span to the right of the checkbox that will render the description this.descriptionSpan = document.createElement('span'); this.checkboxLabel.appendChild(this.descriptionSpan); this.listenTo(this.model, 'change:indent', this.updateIndent); this.listenTo(this.model, 'change:tabbable', this.updateTabindex); this.update(); // Set defaults. this.updateDescription(); this.updateIndent(); this.updateTabindex(); this.updateTooltip(); } /** * Overridden from super class * * Update the description span (rather than the label) since * we want the description to the right of the checkbox. */ updateDescription(): void { // can be called before the view is fully initialized if (this.checkboxLabel == null) { return; } const description = this.model.get('description'); if (this.model.get('description_allow_html')) { this.descriptionSpan.innerHTML = this.model.widget_manager.inline_sanitize(description); } else { this.descriptionSpan.textContent = description; } this.typeset(this.descriptionSpan); this.descriptionSpan.title = description; this.checkbox.title = description; } /** * Update the visibility of the label in the super class * to provide the optional indent. */ updateIndent(): void { const indent = this.model.get('indent'); this.label.style.display = indent ? '' : 'none'; } updateTabindex(): void { if (!this.checkbox) { return; // we might be constructing the parent } const tabbable = this.model.get('tabbable'); if (tabbable === true) { this.checkbox.setAttribute('tabIndex', '0'); } else if (tabbable === false) { this.checkbox.setAttribute('tabIndex', '-1'); } else if (tabbable === null) { this.checkbox.removeAttribute('tabIndex'); } } updateTooltip(): void { if (!this.checkbox) return; // we might be constructing the parent const title = this.model.get('tooltip'); if (!title) { this.checkbox.removeAttribute('title'); } else if (this.model.get('description').length === 0) { this.checkbox.setAttribute('title', title); } } events(): { [e: string]: string } { return { 'click input[type="checkbox"]': '_handle_click', }; } /** * Handles when the checkbox is clicked. * * Calling model.set will trigger all of the other views of the * model to update. */ _handle_click(): void { const value = this.model.get('value'); this.model.set('value', !value, { updated_view: this }); this.touch(); } /** * Update the contents of this view * * Called when the model is changed. The model may have been * changed by another view or by a state update from the back-end. */ update(options?: any): void { this.checkbox.checked = this.model.get('value'); if (options === undefined || options.updated_view != this) { this.checkbox.disabled = this.model.get('disabled'); } return super.update(); } /** * Handle message sent to the front end. * * Used to focus or blur the widget. */ handle_message(content: any): void { if (content.do == 'focus') { this.checkbox.focus(); } else if (content.do == 'blur') { this.checkbox.blur(); } } checkbox: HTMLInputElement; checkboxLabel: HTMLLabelElement; descriptionSpan: HTMLSpanElement; } export class ToggleButtonModel extends BoolModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), _view_name: 'ToggleButtonView', _model_name: 'ToggleButtonModel', tooltip: '', icon: '', button_style: '', style: null, }; } } export class ToggleButtonView extends DOMWidgetView { /** * Called when view is rendered. */ render(): void { super.render(); this.el.classList.add('jupyter-widgets'); this.el.classList.add('jupyter-button'); this.el.classList.add('widget-toggle-button'); this.listenTo(this.model, 'change:button_style', this.update_button_style); this.listenTo(this.model, 'change:tabbable', this.updateTabindex); this.set_button_style(); this.update(); // Set defaults. } update_button_style(): void { this.update_mapped_classes(ToggleButtonView.class_map, 'button_style'); } set_button_style(): void { this.set_mapped_classes(ToggleButtonView.class_map, 'button_style'); } /** * Update the contents of this view * * Called when the model is changed. The model may have been * changed by another view or by a state update from the back-end. */ update(options?: any): void { if (this.model.get('value')) { this.el.classList.add('mod-active'); } else { this.el.classList.remove('mod-active'); } if (options === undefined || options.updated_view !== this) { this.el.disabled = this.model.get('disabled'); this.el.setAttribute('tabbable', this.model.get('tabbable')); this.el.setAttribute('title', this.model.get('tooltip')); const description = this.model.get('description'); const icon = this.model.get('icon'); if (description.trim().length === 0 && icon.trim().length === 0) { this.el.innerHTML = ' '; // Preserve button height } else { this.el.textContent = ''; if (icon.trim().length) { const i = document.createElement('i'); this.el.appendChild(i); i.classList.add('fa'); i.classList.add('fa-' + icon); } this.el.appendChild(document.createTextNode(description)); } } this.updateTabindex(); return super.update(); } events(): { [e: string]: string } { return { // Dictionary of events and their handlers. click: '_handle_click', }; } /** * Handles and validates user input. * * Calling model.set will trigger all of the other views of the * model to update. */ _handle_click(event: MouseEvent): void { event.preventDefault(); const value = this.model.get('value'); this.model.set('value', !value, { updated_view: this }); this.touch(); } preinitialize() { // Must set this before the initialize method creates the element this.tagName = 'button'; } el: HTMLButtonElement; static class_map = { primary: ['mod-primary'], success: ['mod-success'], info: ['mod-info'], warning: ['mod-warning'], danger: ['mod-danger'], }; } export class ValidModel extends BoolModel { defaults(): Backbone.ObjectHash { return { ...super.defaults(), readout: 'Invalid', _view_name: 'ValidView', _model_name: 'ValidModel', }; } } export class ValidView extends DescriptionView { /** * Called when view is rendered. */ render(): void { super.render(); this.el.classList.add('jupyter-widgets'); this.el.classList.add('widget-valid'); this.el.classList.add('widget-inline-hbox'); this.icon = document.createElement('i'); this.icon.classList.add('fa', 'fa-fw'); this.el.appendChild(this.icon); this.readout = document.createElement('span'); this.readout.classList.add('widget-valid-readout'); this.readout.classList.add('widget-readout'); this.el.appendChild(this.readout); this.update(); } /** * Update the contents of this view * * Called when the model is changed. The model may have been * changed by another view or by a state update from the back-end. */ update(): void { this.el.classList.remove('mod-valid'); this.el.classList.remove('mod-invalid'); this.icon.classList.remove('fa-check'); this.icon.classList.remove('fa-times'); this.readout.textContent = this.model.get('readout'); if (this.model.get('value')) { this.el.classList.add('mod-valid'); this.icon.classList.add('fa-check'); } else { this.el.classList.add('mod-invalid'); this.icon.classList.add('fa-times'); } } readout: HTMLSpanElement; icon: HTMLElement; }