Source code for panel.widgets.input

"""
The input widgets generally allow entering arbitrary information into
a text field or similar.
"""
from __future__ import annotations

import ast
import json

from base64 import b64decode
from datetime import date, datetime
from typing import (
    TYPE_CHECKING, Any, ClassVar, Dict, Mapping, Optional, Type,
)

import numpy as np
import param

from bokeh.models.formatters import TickFormatter
from bokeh.models.widgets import (
    Checkbox as _BkCheckbox, ColorPicker as _BkColorPicker,
    DatePicker as _BkDatePicker, Div as _BkDiv, FileInput as _BkFileInput,
    NumericInput as _BkNumericInput, PasswordInput as _BkPasswordInput,
    Spinner as _BkSpinner, Switch as _BkSwitch,
    TextAreaInput as _BkTextAreaInput, TextInput as _BkTextInput,
)

from ..config import config
from ..layout import Column, Panel
from ..models import DatetimePicker as _bkDatetimePicker
from ..util import param_reprs
from .base import CompositeWidget, Widget

if TYPE_CHECKING:
    from bokeh.document import Document
    from bokeh.model import Model
    from pyviz_comms import Comm

    from ..viewable import Viewable


[docs]class TextInput(Widget): """ The `TextInput` widget allows entering any string using a text input box. Reference: https://panel.holoviz.org/reference/widgets/TextInput.html :Example: >>> TextInput(name='Name', placeholder='Enter your name here ...') """ description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") max_length = param.Integer(default=5000, doc=""" Max count of characters in the input field.""") placeholder = param.String(default='', doc=""" Placeholder for empty input field.""") value = param.String(default='', allow_None=True, doc=""" Initial or entered text value updated when <enter> key is pressed.""") value_input = param.String(default='', allow_None=True, doc=""" Initial or entered text value updated on every key press.""") width = param.Integer(default=300, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") _widget_type: ClassVar[Type[Model]] = _BkTextInput
[docs] @classmethod def from_param(cls, parameter: param.Parameter, onkeyup=False, **params) -> Viewable: """ Construct a widget from a Parameter and link the two bi-directionally. Parameters ---------- parameter: param.Parameter A parameter to create the widget from. onkeyup: boolean Whether to trigger events on every key press. params: dict Keyword arguments to be passed to the widget constructor Returns ------- Widget instance linked to the supplied parameter """ params['onkeyup'] = onkeyup return super().from_param(parameter, **params)
[docs]class PasswordInput(TextInput): """ The `PasswordInput` allows entering any string using an obfuscated text input box. Reference: https://panel.holoviz.org/reference/widgets/PasswordInput.html :Example: >>> PasswordInput( ... name='Password', placeholder='Enter your password here...' ... ) """ _widget_type: ClassVar[Type[Model]] = _BkPasswordInput
[docs]class TextAreaInput(TextInput): """ The `TextAreaInput` allows entering any multiline string using a text input box. Lines are joined with the newline character `\n`. Reference: https://panel.holoviz.org/reference/widgets/TextAreaInput.html :Example: >>> TextAreaInput( ... name='Description', placeholder='Enter your description here...' ... ) """ _widget_type: ClassVar[Type[Model]] = _BkTextAreaInput
[docs]class FileInput(Widget): """ The `FileInput` allows the user to upload one or more files to the server. It makes the filename, MIME type and (bytes) content available in Python. Please note - you can in fact *drag and drop* files onto the `FileInput`. - you easily save the files using the `save` method. Reference: https://panel.holoviz.org/reference/widgets/FileInput.html :Example: >>> FileInput(accept='.png,.jpeg', multiple=True) """ accept = param.String(default=None) description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") filename = param.ClassSelector( default=None, class_=(str, list), is_instance=True) mime_type = param.ClassSelector( default=None, class_=(str, list), is_instance=True) multiple = param.Boolean(default=False) value = param.Parameter(default=None) _rename: ClassVar[Mapping[str, str | None]] = { 'filename': None, 'name': None } _source_transforms: ClassVar[Mapping[str, str | None]] = { 'value': "'data:' + source.mime_type + ';base64,' + value" } _widget_type: ClassVar[Type[Model]] = _BkFileInput def _process_param_change(self, msg): msg = super()._process_param_change(msg) if 'value' in msg: msg.pop('value') if 'mime_type' in msg: msg.pop('mime_type') return msg @property def _linked_properties(self): properties = super()._linked_properties return properties + ('filename',) def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg: if isinstance(msg['value'], str): msg['value'] = b64decode(msg['value']) else: msg['value'] = [b64decode(content) for content in msg['value']] return msg
[docs] def save(self, filename): """ Saves the uploaded FileInput data object(s) to file(s) or BytesIO object(s). Arguments --------- filename (str or list[str]): File path or file-like object """ value = self.value if isinstance(filename, list) and not isinstance(value, list): raise TypeError( "FileInput contains a list of files but only a single " "filename was given. Please provide a list of filenames or " "file-like objects." ) elif not isinstance(filename, list) and isinstance(value, list): raise TypeError( "FileInput contains a single files but a list of " "filenames was given. Please provide a single filename " "or file-like object." ) if not isinstance(value, list): value = [self.value] if not isinstance(filename, list): filename = [filename] for val, fn in zip(value, filename): if isinstance(fn, str): with open(fn, 'wb') as f: f.write(val) else: fn.write(val)
[docs]class StaticText(Widget): """ The `StaticText` widget displays a text value, but does not allow editing it. Reference: https://panel.holoviz.org/reference/widgets/StaticText.html :Example: >>> StaticText(name='Model', value='animagen2') """ value = param.Parameter(default=None, doc=""" The current value""") _format: ClassVar[str] = '<b>{title}</b>: {value}' _rename: ClassVar[Mapping[str, str | None]] = {'name': None, 'value': 'text'} _target_transforms: ClassVar[Mapping[str, str | None]] = { 'value': 'target.text.split(": ")[0]+": "+value' } _source_transforms: ClassVar[Mapping[str, str | None]] = { 'value': 'value.split(": ")[1]' } _widget_type: ClassVar[Type[Model]] = _BkDiv def _process_param_change(self, msg): msg = super()._process_param_change(msg) if 'text' in msg: text = str(msg.pop('text')) partial = self._format.replace('{value}', '').format(title=self.name) if self.name: text = self._format.format(title=self.name, value=text.replace(partial, '')) msg['text'] = text return msg
[docs]class DatePicker(Widget): """ The `DatePicker` allows selecting selecting a `date` value using a text box and a date-picking utility. Reference: https://panel.holoviz.org/reference/widgets/DatePicker.html :Example: >>> DatePicker( ... value=date(2025,1,1), ... start=date(2025,1,1), end=date(2025,12,31), ... name='Date' ... ) """ value = param.CalendarDate(default=None, doc=""" The current value""") start = param.CalendarDate(default=None, doc=""" Inclusive lower bound of the allowed date selection""") end = param.CalendarDate(default=None, doc=""" Inclusive upper bound of the allowed date selection""") disabled_dates = param.List(default=None, item_type=(date, str)) enabled_dates = param.List(default=None, item_type=(date, str)) width = param.Integer(default=300, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") _source_transforms: ClassVar[Mapping[str, str | None]] = {} _rename: ClassVar[Mapping[str, str | None]] = { 'start': 'min_date', 'end': 'max_date' } _widget_type: ClassVar[Type[Model]] = _BkDatePicker def _process_property_change(self, msg): msg = super()._process_property_change(msg) for p in ('start', 'end', 'value'): if p not in msg: continue value = msg[p] if isinstance(value, str): msg[p] = datetime.date(datetime.strptime(value, '%Y-%m-%d')) return msg
class _DatetimePickerBase(Widget): disabled_dates = param.List(default=None, item_type=(date, str), doc=""" Dates to make unavailable for selection.""") enabled_dates = param.List(default=None, item_type=(date, str), doc=""" Dates to make available for selection.""") enable_time = param.Boolean(default=True, doc=""" Enable editing of the time in the widget.""") enable_seconds = param.Boolean(default=True, doc=""" Enable editing of the seconds in the widget.""") end = param.Date(default=None, doc=""" Inclusive upper bound of the allowed date selection.""") military_time = param.Boolean(default=True, doc=""" Whether to display time in 24 hour format.""") start = param.Date(default=None, doc=""" Inclusive lower bound of the allowed date selection.""") width = param.Integer(default=300, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") _source_transforms: ClassVar[Mapping[str, str | None]] = { 'value': None, 'start': None, 'end': None, 'mode': None } _rename: ClassVar[Mapping[str, str | None]] = { 'start': 'min_date', 'end': 'max_date' } _widget_type: ClassVar[Type[Model]] = _bkDatetimePicker __abstract = True def __init__(self, **params): super().__init__(**params) self._update_value_bounds() @staticmethod def _convert_to_datetime(v): if isinstance(v, datetime): return v elif isinstance(v, date): return datetime(v.year, v.month, v.day) @param.depends('start', 'end', watch=True) def _update_value_bounds(self): self.param.value.bounds = ( self._convert_to_datetime(self.start), self._convert_to_datetime(self.end) ) self.param.value._validate(self.value) def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg: msg['value'] = self._serialize_value(msg['value']) return msg def _process_param_change(self, msg): msg = super()._process_param_change(msg) if 'value' in msg: msg['value'] = self._deserialize_value(msg['value']) if 'min_date' in msg: msg['min_date'] = self._convert_to_datetime(msg['min_date']) if 'max_date' in msg: msg['max_date'] = self._convert_to_datetime(msg['max_date']) return msg
[docs]class DatetimePicker(_DatetimePickerBase): """ The `DatetimePicker` allows selecting selecting a `datetime` value using a textbox and a datetime-picking utility. Reference: https://panel.holoviz.org/reference/widgets/DatetimePicker.html :Example: >>> DatetimePicker( ... value=datetime(2025,1,1,22,0), ... start=date(2025,1,1), end=date(2025,12,31), ... military_time=True, name='Date and time' ... ) """ value = param.Date(default=None) mode = param.String('single', constant=True) def _serialize_value(self, value): if isinstance(value, str) and value: value = datetime.strptime(value, r'%Y-%m-%d %H:%M:%S') return value def _deserialize_value(self, value): if isinstance(value, (datetime, date)): value = value.strftime(r'%Y-%m-%d %H:%M:%S') return value
[docs]class DatetimeRangePicker(_DatetimePickerBase): """ The `DatetimeRangePicker` allows selecting selecting a `datetime` range using a text box and a datetime-range-picking utility. Reference: https://panel.holoviz.org/reference/widgets/DatetimeRangePicker.html :Example: >>> DatetimeRangePicker( ... value=(datetime(2025,1,1,22,0), datetime(2025,1,2,22,0)), ... start=date(2025,1,1), end=date(2025,12,31), ... military_time=True, name='Datetime Range' ... ) """ value = param.DateRange(default=None, doc=""" The current value""") mode = param.String('range', constant=True) def _serialize_value(self, value): if isinstance(value, str) and value: value = [ datetime.strptime(value, r'%Y-%m-%d %H:%M:%S') for value in value.split(' to ') ] value = tuple(value) return value def _deserialize_value(self, value): if isinstance(value, tuple): value = " to ".join(v.strftime(r'%Y-%m-%d %H:%M:%S') for v in value) if value is None: value = "" return value
[docs]class ColorPicker(Widget): """ The `ColorPicker` widget allows selecting a hexadecimal RGB color value using the browser’s color-picking widget. Reference: https://panel.holoviz.org/reference/widgets/ColorPicker.html :Example: >>> ColorPicker(name='Color', value='#99ef78') """ description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") value = param.Color(default=None, doc=""" The selected color""") width = param.Integer(default=52, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") _widget_type: ClassVar[Type[Model]] = _BkColorPicker _rename: ClassVar[Mapping[str, str | None]] = {'value': 'color'}
class _NumericInputBase(Widget): description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") value = param.Number(default=0, allow_None=True, doc=""" The current value of the spinner.""") placeholder = param.String(default='0', doc=""" Placeholder for empty input field.""") format = param.ClassSelector(default=None, class_=(str, TickFormatter,), doc=""" Allows defining a custom format string or bokeh TickFormatter.""") start = param.Parameter(default=None, allow_None=True, doc=""" Optional minimum allowable value.""") end = param.Parameter(default=None, allow_None=True, doc=""" Optional maximum allowable value.""") _rename: ClassVar[Mapping[str, str | None]] = {'start': 'low', 'end': 'high'} _widget_type: ClassVar[Type[Model]] = _BkNumericInput __abstract = True class _IntInputBase(_NumericInputBase): value = param.Integer(default=0, allow_None=True, doc=""" The current value of the spinner.""") start = param.Integer(default=None, allow_None=True, doc=""" Optional minimum allowable value.""") end = param.Integer(default=None, allow_None=True, doc=""" Optional maximum allowable value.""") mode = param.String(default='int', constant=True, doc=""" Define the type of number which can be enter in the input""") __abstract = True class _FloatInputBase(_NumericInputBase): value = param.Number(default=0, allow_None=True, doc=""" The current value of the spinner.""") start = param.Number(default=None, allow_None=True, doc=""" Optional minimum allowable value.""") end = param.Number(default=None, allow_None=True, doc=""" Optional maximum allowable value.""") mode = param.String(default='float', constant=True, doc=""" Define the type of number which can be enter in the input""") __abstract = True class _SpinnerBase(_NumericInputBase): page_step_multiplier = param.Integer(default=10, bounds=(0, None), doc=""" Defines the multiplication factor applied to step when the page up and page down keys are pressed.""") wheel_wait = param.Integer(default=100, doc=""" Defines the debounce time in ms before updating `value_throttled` when the mouse wheel is used to change the input.""") width = param.Integer(default=300, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") _widget_type: ClassVar[Type[Model]] = _BkSpinner __abstract = True def __init__(self, **params): if 'value' not in params: value = params.get('start', self.value) if value is not None: params['value'] = value if 'value' in params and 'value_throttled' in self.param: params['value_throttled'] = params['value'] super().__init__(**params) def __repr__(self, depth=0): return '{cls}({params})'.format(cls=type(self).__name__, params=', '.join(param_reprs(self, ['value_throttled']))) def _update_model( self, events: Dict[str, param.parameterized.Event], msg: Dict[str, Any], root: Model, model: Model, doc: Document, comm: Optional[Comm] ) -> None: if 'value_throttled' in msg: del msg['value_throttled'] return super()._update_model(events, msg, root, model, doc, comm) def _process_param_change(self, msg): # Workaround for -inf serialization errors if 'value' in msg and msg['value'] == float('-inf'): msg['value'] = None msg['value_throttled'] = None return super()._process_param_change(msg) def _process_property_change(self, msg): if config.throttled: if "value" in msg: del msg["value"] if "value_throttled" in msg: msg["value"] = msg["value_throttled"] return super()._process_property_change(msg)
[docs]class IntInput(_SpinnerBase, _IntInputBase): """ The `IntInput` allows selecting an integer value using a spinbox. It behaves like a slider except that lower and upper bounds are optional and a specific value can be entered. The value can be changed using the keyboard (up, down, page up, page down), mouse wheel and arrow buttons. Reference: https://panel.holoviz.org/reference/widgets/IntInput.html :Example: >>> IntInput(name='Value', value=100, start=0, end=1000, step=10) """ step = param.Integer(default=1, doc=""" The step size.""") value_throttled = param.Integer(default=None, constant=True, doc=""" The current value. Updates only on `<enter>` or when the widget looses focus.""")
[docs]class FloatInput(_SpinnerBase, _FloatInputBase): """ The `FloatInput` allows selecting a floating point value using a spinbox. It behaves like a slider except that the lower and upper bounds are optional and a specific value can be entered. The value can be changed using the keyboard (up, down, page up, page down), mouse wheel and arrow buttons. Reference: https://panel.holoviz.org/reference/widgets/FloatInput.html :Example: >>> FloatInput(name='Value', value=5., step=1e-1, start=0, end=10) """ placeholder = param.String(default='', doc=""" Placeholder when the value is empty.""") step = param.Number(default=0.1, doc=""" The step size.""") value_throttled = param.Number(default=None, constant=True, doc=""" The current value. Updates only on `<enter>` or when the widget looses focus.""") def _process_param_change(self, msg): if msg.get('value', False) is None: msg['value'] = float('NaN') if msg.get('value_throttled', False) is None: msg['value_throttled'] = float('NaN') return super()._process_param_change(msg) def _process_property_change(self, msg): if msg.get('value', False) and np.isnan(msg['value']): msg['value'] = None if msg.get('value_throttled', False) and np.isnan(msg['value_throttled']): msg['value_throttled'] = None return super()._process_property_change(msg)
[docs]class NumberInput(_SpinnerBase): def __new__(self, **params): param_list = ["value", "start", "stop", "step"] if all(isinstance(params.get(p, 0), int) for p in param_list): return IntInput(**params) else: return FloatInput(**params)
# Backward compatibility Spinner = NumberInput
[docs]class LiteralInput(Widget): """ The `LiteralInput` allows declaring Python literals using a text input widget. A *literal* is some specific primitive value of type `str` , `int`, `float`, `bool` etc or a `dict`, `list`, `tuple`, `set` etc of primitive values. Optionally the literal `type` may be declared. Reference: https://panel.holoviz.org/reference/widgets/LiteralInput.html :Example: >>> LiteralInput(name='Dictionary', value={'key': [1, 2, 3]}, type=dict) """ description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") placeholder = param.String(default='', doc=""" Placeholder for empty input field.""") serializer = param.ObjectSelector(default='ast', objects=['ast', 'json'], doc=""" The serialization (and deserialization) method to use. 'ast' uses ast.literal_eval and 'json' uses json.loads and json.dumps. """) type = param.ClassSelector(default=None, class_=(type, tuple), is_instance=True) value = param.Parameter(default=None) width = param.Integer(default=300, allow_None=True, doc=""" Width of this component. If sizing_mode is set to stretch or scale mode this will merely be used as a suggestion.""") _rename: ClassVar[Mapping[str, str | None]] = { 'type': None, 'serializer': None } _source_transforms: ClassVar[Mapping[str, str | None]] = { 'serializer': None, 'value': """JSON.parse(value.replace(/'/g, '"'))""" } _target_transforms: ClassVar[Mapping[str, str | None]] = { 'value': """JSON.stringify(value).replace(/,/g, ",").replace(/:/g, ": ")""" } _widget_type: ClassVar[Type[Model]] = _BkTextInput def __init__(self, **params): super().__init__(**params) self._state = '' self._validate(None) self._internal_callbacks.append(self.param.watch(self._validate, 'value')) def _validate(self, event): if self.type is None: return new = self.value if not isinstance(new, self.type) and new is not None: if event: self.value = event.old types = repr(self.type) if isinstance(self.type, tuple) else self.type.__name__ raise ValueError('LiteralInput expected %s type but value %s ' 'is of type %s.' % (types, new, type(new).__name__)) def _process_property_change(self, msg): msg = super()._process_property_change(msg) new_state = '' if 'value' in msg: value = msg.pop('value') try: if value == '': value = '' elif self.serializer == 'json': value = json.loads(value) else: value = ast.literal_eval(value) except Exception: new_state = ' (invalid)' value = self.value else: if self.type and not isinstance(value, self.type): vtypes = self.type if isinstance(self.type, tuple) else (self.type,) typed_value = None for vtype in vtypes: try: typed_value = vtype(value) except Exception: pass else: break if typed_value is None and value == '': value = None elif typed_value is None and value is not None: new_state = ' (wrong type)' value = self.value else: value = typed_value msg['value'] = value msg['name'] = msg.get('title', self.name).replace(self._state, '') + new_state self._state = new_state self.param.trigger('name') return msg def _process_param_change(self, msg): msg = super()._process_param_change(msg) if 'value' in msg: value = msg['value'] if isinstance(value, str): value = repr(value) elif self.serializer == 'json': value = json.dumps(value) else: value = '' if value is None else str(value) msg['value'] = value msg['title'] = self.name return msg
[docs]class ArrayInput(LiteralInput): """ The `ArrayInput` allows rendering and editing NumPy arrays in a text input widget. Arrays larger than the `max_array_size` will be summarized and editing will be disabled. Reference: https://panel.holoviz.org/reference/widgets/ArrayInput.html :Example: >>> To be determined ... """ max_array_size = param.Number(default=1000, doc=""" Arrays larger than this limit will be allowed in Python but will not be serialized into JavaScript. Although such large arrays will thus not be editable in the widget, such a restriction helps avoid overwhelming the browser and lets other widgets remain usable.""") _rename: ClassVar[Mapping[str, str | None]] = { 'max_array_size': None } _source_transforms: ClassVar[Mapping[str, str | None]] = { 'serializer': None, 'type': None, 'value': None } def __init__(self, **params): super().__init__(**params) self._auto_disabled = False def _process_property_change(self, msg): msg = super()._process_property_change(msg) if 'value' in msg and isinstance(msg['value'], list): msg['value'] = np.asarray(msg['value']) return msg def _process_param_change(self, msg): if msg.get('disabled', False): self._auto_disabled = False value = msg.get('value') if value is None: return super()._process_param_change(msg) if value.size <= self.max_array_size: msg['value'] = value.tolist() # If array is no longer larger than max_array_size # unset disabled if self.disabled and self._auto_disabled: self.disabled = False msg['disabled'] = False self._auto_disabled = False else: msg['value'] = np.array2string( msg['value'], separator=',', threshold=self.max_array_size ) if not self.disabled: self.param.warning( f"Number of array elements ({value.size}) exceeds " f"`max_array_size` ({self.max_array_size}), editing " "will be disabled." ) self.disabled = True msg['disabled'] = True self._auto_disabled = True return super()._process_param_change(msg)
[docs]class DatetimeInput(LiteralInput): """ The `DatetimeInput` allows specifying Python `datetime` like values using a text input widget. An optional `type` may be declared. Reference: https://panel.holoviz.org/reference/widgets/DatetimeInput.html :Example: >>> DatetimeInput(name='Datetime', value=datetime(2019, 2, 8)) """ value = param.Date(default=None, doc=""" The current value""") start = param.Date(default=None, doc=""" Inclusive lower bound of the allowed date selection""") end = param.Date(default=None, doc=""" Inclusive upper bound of the allowed date selection""") format = param.String(default='%Y-%m-%d %H:%M:%S', doc=""" Datetime format used for parsing and formatting the datetime.""") type = datetime _source_transforms: ClassVar[Mapping[str, str | None]] = { 'value': None, 'start': None, 'end': None } _rename: ClassVar[Mapping[str, str | None]] = { 'format': None, 'type': None, 'start': None, 'end': None, 'serializer': None } def __init__(self, **params): super().__init__(**params) self.param.watch(self._validate, 'value') self._validate(None) def _validate(self, event): new = self.value if new is not None and ((self.start is not None and self.start > new) or (self.end is not None and self.end < new)): value = datetime.strftime(new, self.format) start = datetime.strftime(self.start, self.format) end = datetime.strftime(self.end, self.format) if event: self.value = event.old raise ValueError('DatetimeInput value must be between {start} and {end}, ' 'supplied value is {value}'.format(start=start, end=end, value=value)) def _process_property_change(self, msg): msg = Widget._process_property_change(self, msg) new_state = '' if 'value' in msg: value = msg.pop('value') try: value = datetime.strptime(value, self.format) except Exception: new_state = ' (invalid)' value = self.value else: if value is not None and ((self.start is not None and self.start > value) or (self.end is not None and self.end < value)): new_state = ' (out of bounds)' value = self.value msg['value'] = value msg['name'] = msg.get('title', self.name).replace(self._state, '') + new_state self._state = new_state return msg def _process_param_change(self, msg): msg = Widget._process_param_change(self, msg) if 'value' in msg: value = msg['value'] if value is None: value = '' else: value = datetime.strftime(msg['value'], self.format) msg['value'] = value msg['title'] = self.name return msg
[docs]class DatetimeRangeInput(CompositeWidget): """ The `DatetimeRangeInput` widget allows selecting a `datetime` range using two `DatetimeInput` widgets, which return a `tuple` range. Reference: https://panel.holoviz.org/reference/widgets/DatetimeRangeInput.html :Example: >>> DatetimeRangeInput( ... name='Datetime Range', ... value=(datetime(2017, 1, 1), datetime(2018, 1, 10)), ... start=datetime(2017, 1, 1), end=datetime(2019, 1, 1), ... ) """ value = param.Tuple(default=(None, None), length=2, doc=""" The current value""") start = param.Date(default=None, doc=""" Inclusive lower bound of the allowed date selection""") end = param.Date(default=None, doc=""" Inclusive upper bound of the allowed date selection""") format = param.String(default='%Y-%m-%d %H:%M:%S', doc=""" Datetime format used for parsing and formatting the datetime.""") _composite_type: ClassVar[Type[Panel]] = Column def __init__(self, **params): self._text = StaticText(margin=(5, 0, 0, 0), styles={'white-space': 'nowrap'}) self._start = DatetimeInput(sizing_mode='stretch_width', margin=(5, 0, 0, 0)) self._end = DatetimeInput(sizing_mode='stretch_width', margin=(5, 0, 0, 0)) if 'value' not in params: params['value'] = (params['start'], params['end']) super().__init__(**params) self._msg = '' self._composite.extend([self._text, self._start, self._end]) self._updating = False self.param.watch(self._update_widgets, [p for p in self.param if p != 'name']) self._update_widgets() self._update_label() @param.depends('name', '_start.name', '_end.name', watch=True) def _update_label(self): self._text.value = f'{self.name}{self._start.name}{self._end.name}{self._msg}' @param.depends('_start.value', '_end.value', watch=True) def _update(self): if self._updating: return if (self._start.value is not None and self._end.value is not None and self._start.value > self._end.value): self._msg = ' (start of range must be <= end)' self._update_label() return elif self._msg: self._msg = '' self._update_label() try: self._updating = True self.value = (self._start.value, self._end.value) finally: self._updating = False def _update_widgets(self, *events): filters = [event.name for event in events] if events else list(self.param) if 'name' in filters: filters.remove('name') if self._updating: return try: self._updating = True params = {k: v for k, v in self.param.values().items() if k in filters} start_params = dict(params, value=self.value[0]) end_params = dict(params, value=self.value[1]) self._start.param.update(**start_params) self._end.param.update(**end_params) finally: self._updating = False
class _BooleanWidget(Widget): value = param.Boolean(default=False, doc=""" The current value""") _supports_embed: ClassVar[bool] = True _rename: ClassVar[Mapping[str, str | None]] = {'value': 'active', 'name': 'label'} __abstract = True def _get_embed_state(self, root, values=None, max_opts=3): return (self, self._models[root.ref['id']][0], [False, True], lambda x: x.active, 'active', "cb_obj.active")
[docs]class Checkbox(_BooleanWidget): """ The `Checkbox` allows toggling a single condition between `True`/`False` states by ticking a checkbox. This widget is interchangeable with the `Toggle` widget. Reference: https://panel.holoviz.org/reference/widgets/Checkbox.html :Example: >>> Checkbox(name='Works with the tools you know and love', value=True) """ _widget_type: ClassVar[Type[Model]] = _BkCheckbox
[docs]class Switch(_BooleanWidget): """ The `Switch` allows toggling a single condition between `True`/`False` states by ticking a checkbox. This widget is interchangeable with the `Toggle` widget. Reference: https://panel.holoviz.org/reference/widgets/Switch.html :Example: >>> Switch(name='Works with the tools you know and love', value=True) """ _rename: ClassVar[Mapping[str, str | None]] = { 'name': None, 'value': 'active' } _widget_type: ClassVar[Type[Model]] = _BkSwitch