Widget (very WIP)

FashHTML widgets

Replacing ipywidgets with Fasthtml

ipywidgets handles:
- Layout: ipywidgets uses python, JS -> replace with FastHTML, HTMX.
- Comunication kernel-frontend: ipywidgets uses Comm -> Bridget/HTTP-Ajax
- Serialization: ipywidgets uses Traitlets -> replace with pydantic dataclasses, msgspec, pysignal.
- Observer pattern: ipywidgets uses Traitlets -> replace with custom or psignal.

bridge_cfg.update(auto_show=True, auto_mount=True)
app, brt, rt = get_app(hooks=True, show_logger=True)
brt.bridge.plugins
{'loader': <bridget.bridge.Loader at 0x11f6e7c50>,
 'htmx': <bridget.bridge.HTMXPlugin at 0x13f2a9700>,
 'fasthtmljs': <bridget.bridge.BridgePlugin at 0x14b283590>,
 'observer': <bridget.bridge.BridgePlugin at 0x128774e00>,
 'brd_mark': <bridget.bridge.BridgePlugin at 0x14b047f80>,
 'nbhooks': <bridget.bridge_plugins.NBHooksPlugin at 0x1181cf3e0>,
 'commander': <bridget.bridge_plugins.HTMXCommanderPlugin at 0x12ffd3200>,
 'bridget': <bridget.bridget.Bridget at 0x13808e960>}
dv = Div(cls='bridget slider')
dv.set(cls=f"{dv.get('class')} value")
with bridge_cfg(auto_show=False):
    display(dv)
<div class="bridget slider value"></div>
nb = brt.bridge.state
nb.cells
(#3) [{'idx': 0, 'source': 'brt.bridge.plugins', 'id': 'X23sZmlsZQ==', 'cell_type': 'code', 'outputs': [{'output_type': 'display_data', 'data': {'text/plain': "{'loader': <bridget.bridge.Loader at 0x11f6e7c50>,\n 'htmx': <bridget.bridge.HTMXPlugin at 0x13f2a9700>,\n 'fasthtmljs': <bridget.bridge.BridgePlugin at 0x14b283590>,\n 'observer': <bridget.bridge.BridgePlugin at 0x128774e00>,\n 'brd_mark': <bridget.bridge.BridgePlugin at 0x14b047f80>,\n 'nbhooks': <bridget.bridge_plugins.NBHooksPlugin at 0x1181cf3e0>,\n 'commander': <bridget.bridge_plugins.HTMXCommanderPlugin at 0x12ffd3200>,\n 'bridget': <bridget.bridget.Bridget at 0x13808e960>}"}, 'metadata': {'bridge': {'captured': True}}}, {'output_type': 'execute_result', 'execution_count': 12, 'data': {'text/plain': "{'loader': <bridget.bridge.Loader at 0x11f6e7c50>,\n 'htmx': <bridget.bridge.HTMXPlugin at 0x13f2a9700>,\n 'fasthtmljs': <bridget.bridge.BridgePlugin at 0x14b283590>,\n 'observer': <bridget.bridge.BridgePlugin at 0x128774e00>,\n 'brd_mark': <bridget.bridge.BridgePlugin at 0x14b047f80>,\n 'nbhooks': <bridget.bridge_plugins.NBHooksPlugin at 0x1181cf3e0>,\n 'commander': <bridget.bridge_plugins.HTMXCommanderPlugin at 0x12ffd3200>,\n 'bridget': <bridget.bridget.Bridget at 0x13808e960>}"}, 'metadata': {}}], 'execution_count': 12},{'idx': 1, 'source': 'dv = Div(cls=\'bridget slider\')\ndv.set(cls=f"{dv.get(\'class\')} value")\nwith bridge_cfg(auto_show=False):\n    display(dv)', 'id': 'X24sZmlsZQ==', 'cell_type': 'code', 'outputs': [{'output_type': 'display_data', 'data': {'text/plain': "div((),{'class': 'bridget slider value'})", 'text/markdown': '```html\n<div class="bridget slider value"></div>\n\n```'}, 'metadata': {}}], 'execution_count': 13},{'idx': 2, 'source': 'nb = brt.bridge.state\nnb.cells', 'id': 'X25sZmlsZQ==', 'cell_type': 'code', 'outputs': []}]

Helpers

def show_routes(app):
    cprint(app.routes)
    while app.routes and (app := getattr(app.routes[-1], 'app', None)):
        if hasattr(app, 'routes') and isinstance(app.routes, list): cprint(app.routes)
        else: break

_debug_req

def _debug_req(name, url, method, body):
    return {
        'headers': {
            'HX-Request': 'true',
            'HX-Trigger-Name': name,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        'headerNames': {'hx-request': 'HX-Request', 'hx-current-url': 'HX-Current-URL', 
                        'hx-trigger-name': 'HX-Trigger-Name', 'hx-trigger-value': 'HX-Trigger-Value',
                        'content-type':'Content-Type'},
        'status': 0,
        'method': method,
        'url': url,
        'async': True,
        'timeout': 0,
        'withCredentials': False,
        'body': body,
    }
@dataclass
class Person:
    name: str
    age: int = 0

    events: ClassVar[psygnal.SignalGroupDescriptor] = psygnal.SignalGroupDescriptor()

@dataclass
class Citizen(Person):
    country: str = 'USA'
    _city: str = 'New York'

@dataclass
class Employee(Citizen):
    company: str = 'Central Perk'

e = Employee(name='John', age=30)
psygnal.is_evented(Employee), psygnal.is_evented(e)
(True, True)
e.events.signals
mappingproxy({'_city': <_DataclassFieldSignalInstance '_city' on <SignalGroup 'EmployeeSignalGroup' with 5 signals>>,
              'age': <_DataclassFieldSignalInstance 'age' on <SignalGroup 'EmployeeSignalGroup' with 5 signals>>,
              'company': <_DataclassFieldSignalInstance 'company' on <SignalGroup 'EmployeeSignalGroup' with 5 signals>>,
              'country': <_DataclassFieldSignalInstance 'country' on <SignalGroup 'EmployeeSignalGroup' with 5 signals>>,
              'name': <_DataclassFieldSignalInstance 'name' on <SignalGroup 'EmployeeSignalGroup' with 5 signals>>})

BridgetRouteProvider

def attrgetterd(*attrs, default=None):
    def _call(obj):
        oo = tuple(getattr(obj, attr, default) for attr in attrs)
        return oo[0] if len(oo) == 1 else oo
    return _call
r = AD(name='a')
test_eq(attrgetterd('name')(r), 'a')
test_eq(attrgetterd('name', 'kind')(r), ('a', None))
def types(o, stop=-1): return reversed(type(o).__mro__[:stop])
[*types(APIRouterD(), None)]  # type: ignore
[object,
 fasthtml.core.APIRouter,
 bridget.routing.APIRouterC,
 bridget.routing.APIRouterD]
if typing.TYPE_CHECKING:
    class Control: ...

def _cls_attr(cls: type, attr_name='_wrapper_class') -> str:
    if (n := cls.__name__).startswith('_'): return ''
    if hasattr(cls, attr_name): return getattr(cls, attr_name)
    if issubclass(cls, Control): return n.lower()
    return ''

_get_css = attrgetterd('_css')
def _cls_attrs(o, f=_cls_attr): return ' '.join(filter(None, (f(_) for _ in types(o)))).strip()
class BridgetRouteProvider(RouteProvider):
    bridget = get_bridget()

    _updating_ = False
    @contextmanager
    def _updating(self): self._updating_ = True; yield; self._updating_ = False
    def setup_values(self): ...
    
    events: ClassVar[psygnal.SignalGroupDescriptor]
    def __init_subclass__(cls):
        # cls.bridget.clear_routes(cls)
        # cls.bridget.app.routes.clear()
        # cls.ar = ar = APIRouterD(f"/{cls.__name__}")
        # ar.__set_name__(cls, 'ar')
        if not 'events' in vars(cls): 
            cls.events = events = psygnal.SignalGroupDescriptor(warn_on_no_fields=False)
            events.__set_name__(cls, 'events')  # call explicitly: outside class definition
    def setup_events(self):
        if dataclasses.is_dataclass(self):
            fields = {fld.name:fld.metadata.get('bridget', {}) for fld in dataclasses.fields(self)}
        else: fields = {}
        for name in self.events.signals.keys():
            mtdt = fields.get(name, {})
            if mtdt.get('skip', False): continue
            mth = mtdt.get('on', None) or getattr(self, f"on_{name}", None)
            if not mth: mth = partial(getattr(self, 'on_attr'), attr=name)
            if isinstance(mth, (MethodType, partial)): self.events[name].connect(mth)
    def on_attr(self, nw: Any, old: Any, *, attr:str):
        if self.bridget and not self._updating_:
            with self._updating(): self.setup_values()
            self.bridget.bridge.commander.swap(f"#{self.wrapper_id()}", to_xml(self.__ft__()), swapStyle='innerHTML')

    ar = APIRouterD()
    _mounted = False
    def wrapper_id(self): return self.ar.name().replace(':', '_')
    def wrapper_class(self): return _cls_attrs(self)
    def style(self): return _cls_attrs(self, f=_get_css)
    def controls(self): return ()
    def wrapper(self): return Div(id=self.wrapper_id(), cls=self.wrapper_class(), style=self.style())
    def __ft__(self):
        if bridge_cfg.auto_mount and not self._mounted: get_bridget().mount(self, show=False)  # type: ignore
        return self.wrapper()(*self.controls())

    dh: DisplayHandle | None = None
    def _ipython_display_(self):
        # clear previous display; unlike ipywidgets, bridget shows only one view, MVC not welcomed here
        if self.dh is not None: self.dh.update(HTML())
        self.dh = self.bridget(self, display_id=self.dh)
        self.cell = this()

Control

class _Control(BridgetRouteProvider):
    disabled:bool=False; description:str=''
    def on_disabled(self, nw: Any, old: Any): ...
    def on_description(self, nw: Any, old: Any): ...


@dataclass
class Control(_Control):
    _: KW_ONLY
    disabled:bool = False
    description:str = ''
    
    def __post_init__(self):
        with self._updating(): self.setup_values()
        self.setup_events()
# app.routes.clear()
c = Control(disabled=True)
with bridge_cfg(auto_show=False):
    display(c.__ft__())
<div class="control"></div>
show_routes(app)
[]

Value

@dataclass
class Value(Control):
    value: Any = None

    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:str):
        with self._updating(): self.value = value
        return self.value

    def value_control(self):
        return H.Input(name='value', value=self.value,
                hx_post=self.ar.value.to(), hx_trigger='input changed', hx_swap='none')
    def controls(self):
        ctl = self.value_control()
        if hasattr(ctl, '__ft__'): ctl = ctl.__ft__()
        if isinstance(ctl, (FT, Control)): return (ctl,)
        if isinstance(ctl, tuple): return ctl
        if isinstance(ctl, Iterable): return tuple(ctl)
        return (ctl,)
app.routes.clear()
v = Value('Hello, world!')
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="Value_Value_1-1764705844" class="control value">
  <input name="value" value="Hello, world!" hx-post="/Value/Value_1-1764705844/value" hx-trigger="input changed" hx-swap="none">
</div>
show_routes(app)
[Mount(path='/Value', name='Value', app=<fasthtml.core.FastHTML object at 0x14b3ed1f0>)]
[Mount(path='/Value_1-1764705844', name='Value_1-1764705844', app=<fasthtml.core.FastHTML object at 0x14b283740>)]
[Route(path='/value', name='value', methods=['POST'])]
v.value
'Hello, world!'
v.value = 'Bye!'

String

Label

# W.Label('Hello, World!')
<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-label">Hello, World!</div>
@dataclass
class Label(Control):
    value: str = ''
    def controls(self): return (self.value, )
v = Label('Hello, world!')
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div class="control label">Hello, world!</div>
Hello, world!
v.value
'Hello, world!'

Text

# W.Text('Hello, World!')
<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-text">
    <label class="widget-label" for="8ffde047-0ce3-4324-97a8-8f9bb6bb5a22" style="display: none;"></label>
    <input type="text" id="8ffde047-0ce3-4324-97a8-8f9bb6bb5a22" class="widget-input" placeholder="​">
</div>
@dataclass
class Text(Value):
    value: str = ''
    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:str):
        with self._updating(): self.value = value
    def value_control(self):
        return H.Input(name='value', value=self.value,
                hx_post=self.ar.value, hx_trigger='input changed', hx_swap='none')
v = Text('Hello, world!')
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="Text_Text_1-1764705844" class="control value text">
  <input name="value" value="Hello, world!" hx-post="/Text/Text_1-1764705844/value" hx-trigger="input changed" hx-swap="none">
</div>
v.value
'Hello, world!'
v.value = 'Good-Bye to All That'
show_routes(app)
[
    Mount(path='/Value', name='Value', app=<fasthtml.core.FastHTML object at 0x14b3ed1f0>),
    Mount(path='/Text', name='Text', app=<fasthtml.core.FastHTML object at 0x14b41db80>)
]
[Mount(path='/Text_1-1764705844', name='Text_1-1764705844', app=<fasthtml.core.FastHTML object at 0x14b41e1e0>)]
[Route(path='/value', name='value', methods=['POST'])]

TextArea

# W.Textarea('Hello, World!')
<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-textarea">
    <label class="widget-label" for="ea87d53a-15a1-474f-a14d-2f84302a10d5" style="display: none;"></label>
    <textarea rows="" id="ea87d53a-15a1-474f-a14d-2f84302a10d5" class="widget-input" placeholder="​"></textarea>
</div>
@dataclass
class TextArea(Value):
    value: str = ''
    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:str):
        with self._updating(): self.value = value
    def value_control(self):
        return H.Textarea(name='value', 
                hx_post=self.ar.value, hx_trigger='keyup changed throttle:100ms', hx_swap='none')(self.value)
app.routes.clear()
v = TextArea('Hello, world!')
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="TextArea_TextArea_1-1764705844" class="control value textarea">
<textarea name="value" hx-post="/TextArea/TextArea_1-1764705844/value" hx-trigger="keyup changed throttle:100ms" hx-swap="none">Hello, world!</textarea></div>
v.value
'Hello, world!'
v.value = 'Adieu!'
show_routes(app)
[Mount(path='/TextArea', name='TextArea', app=<fasthtml.core.FastHTML object at 0x12f21e5d0>)]
[Mount(path='/TextArea_1-1764705844', name='TextArea_1-1764705844', app=<fasthtml.core.FastHTML object at 0x14b41eae0>)]
[Route(path='/value', name='value', methods=['POST'])]

Combobox

<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-text">
    <label class="widget-label" for="81e63e64-d351-4dd4-860a-6982c4d65bee" style="">Combobox:</label>
    <input type="text" id="81e63e64-d351-4dd4-860a-6982c4d65bee" class="widget-input" placeholder="Choose Someone" list="62fd0f57-8eef-4b16-97d8-2648d373e452">
    <datalist id="62fd0f57-8eef-4b16-97d8-2648d373e452">
        <option value="Paul"></option>
        <option value="John"></option>
        <option value="George"></option>
        <option value="Ringo"></option>
    </datalist>
</div>
@dataclass
class Combobox(Value):
    value: str = ''
    options: list[str] = field(default_factory=list)
    def value_control(self):
        return (
            H.Input(type='text', name='value', list='abc-456', placeholder='Choose Someone', 
                hx_post=self.ar.value, hx_swap='none',
            ),
            H.Datalist(id='abc-456')(*map(lambda x:H.Option(value=x), self.options)),
        )
v = Combobox('Ringo', ['Paul', 'John', 'George', 'Ringo'])
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="Combobox_Combobox_1-1764705844" class="control value combobox">
  <input type="text" name="value" list="abc-456" placeholder="Choose Someone" hx-post="/Combobox/Combobox_1-1764705844/value" hx-swap="none">
<datalist id="abc-456"><option value="Paul"></option><option value="John"></option><option value="George"></option><option value="Ringo"></option></datalist></div>
v.value
'Ringo'
v.value = 'George'
show_routes(app)
[
    Mount(path='/TextArea', name='TextArea', app=<fasthtml.core.FastHTML object at 0x12f21e5d0>),
    Mount(path='/Combobox', name='Combobox', app=<fasthtml.core.FastHTML object at 0x14b54cb00>)
]
[Mount(path='/Combobox_1-1764705844', name='Combobox_1-1764705844', app=<fasthtml.core.FastHTML object at 0x14b54cb30>)]
[Route(path='/value', name='value', methods=['POST'])]

Button

<button class="lm-Widget jupyter-widgets jupyter-button widget-button" title="Click me"><i class="fa fa-check"></i>Click me</button>
@dataclass
class Button(Control):
    icon: str = ''
    def controls(self):
        return (H.Button(title=self.description)(H.I(cls=f"fa fa-{self.icon}"), self.description), )
v = Button('check', description='Click me')
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div class="control button">
<button title="Click me"><i class="fa fa-check"></i>Click me</button></div>

Containers

Box

primes = list(map(int, """
2   3   5   7   11  13  17  19  23  29  31  37  41  43  47  53  59  61  67  71
73  79  83  89  97  101 103 107 109 113 127 131 137 139 149 151 157 163 167 173
179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281
283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409
419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541
547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659
661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809
811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941
947 953 967 971 977 983 991 997""".split()))
len(primes)
168
@dataclass
class Box(Control):
    children: list[Bridgeable] = field(default_factory=list)
    _css = 'display: flex; margin: 0;'

    def controls(self): return self.children
    def on_children(self, nw: Any, old: Any):
        dh = self.bridget(self, display_id=self.dh)
        if self.dh is None: self.dh = dh

@dataclass
class HBox(Box):
    _css = 'flex-direction: row; gap: 10px; column-gap: 10px;'

@dataclass
class VBox(Box):
    _css = 'flex-direction: column; row-gap: 5px;'
v = Box(['Hello', Text('world!')])
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div class="control box" style="display: flex; margin: 0;">
Hello  <div id="Text_Text_2-1764705844" class="control value text">
    <input name="value" value="world!" hx-post="/Text/Text_2-1764705844/value" hx-trigger="input changed" hx-swap="none">
  </div>
</div>
Hello
w = HBox(['Hello', Text('world!')])
with bridge_cfg(auto_show=False):
    display(w.__ft__())
w
<div class="control box hbox" style="display: flex; margin: 0; flex-direction: row; gap: 10px; column-gap: 10px;">
Hello  <div id="Text_Text_3-1764705844" class="control value text">
    <input name="value" value="world!" hx-post="/Text/Text_3-1764705844/value" hx-trigger="input changed" hx-swap="none">
  </div>
</div>
Hello
w = VBox(['Hello', Text('world!')])
with bridge_cfg(auto_show=False):
    display(w.__ft__())
w
<div class="control box vbox" style="display: flex; margin: 0; flex-direction: column; row-gap: 5px;">
Hello  <div id="Text_Text_4-1764705844" class="control value text">
    <input name="value" value="world!" hx-post="/Text/Text_4-1764705844/value" hx-trigger="input changed" hx-swap="none">
  </div>
</div>
Hello
w = HBox()
with bridge_cfg(auto_show=False):
    display(w.__ft__())
w
<div class="control box hbox" style="display: flex; margin: 0; flex-direction: row; gap: 10px; column-gap: 10px;"></div>
asdf
w.cell#nb.cell_by_did(w.dh.display_id)
NBCell@59
  • idx: 59
  • source: w = HBox() with bridge_cfg(auto_show=False): display(w.__ft__()) w
  • id: Y164sZmlsZQ==
  • cell_type: code
  • outputs
      0
      • output_type: display_data
      • data
        • text/plain: div((),{'id': '', 'class': 'control box hbox', 'style': 'display: flex; margin: 0; flex-direction: row; gap: 10px; column-gap: 10px;'})
        • text/markdown: ```html <div class="control box hbox" style="display: flex; margin: 0; flex-direction: row; gap: 10px; column-gap: 10px;"></div> ```
      • metadata: {}
      1
      • output_type: display_data
      • data
        • text/plain: <IPython.core.display.HTML object>
        • text/html: <div class="control box hbox" style="display: flex; margin: 0; flex-direction: row; gap: 10px; column-gap: 10px;"></div> <brd-mark id="aa2f…
      • metadata: {'brd_did': 'aa2f95d6aaf436220ed671faf4f65e20'}
  • execution_count: 75
test_eq(w.children, [])
w.children = [vt1 := H.Text('asdf')]

test_eq(w.children, [vt1])
# test_eq(w.dhdl.contents(), f'<div id="{w.ar.name()}" class="widget">\n<text>asdf</text></div>\n')  # type: ignore
test_is('asdf' in w.cell.outputs[1]['data']['text/html'], True)
w.children = [*w.children, vb1 :=H.Button('asdf')]

test_eq(w.children, [vt1, vb1])
test_is('button' in w.cell.outputs[1]['data']['text/html'], True)
w.children = [*w.children, vb2 := Button('check', description='Click me')]

test_eq(w.children, [vt1, vb1, vb2])
test_is(vb2.ar.name() in w.cell.outputs[1]['data']['text/html'], True)
# w = Widget(contents=[Button('asdf'), Text('asdf')])
# help(Widget)
show_routes(app)
[
    Mount(path='/TextArea', name='TextArea', app=<fasthtml.core.FastHTML object at 0x12f21e5d0>),
    Mount(path='/Combobox', name='Combobox', app=<fasthtml.core.FastHTML object at 0x14b54cb00>),
    Mount(path='/Text', name='Text', app=<fasthtml.core.FastHTML object at 0x14b3ed760>)
]
[
    Mount(path='/Text_2-1764705844', name='Text_2-1764705844', app=<fasthtml.core.FastHTML object at 0x14b54e690>),
    Mount(path='/Text_3-1764705844', name='Text_3-1764705844', app=<fasthtml.core.FastHTML object at 0x14b734b90>),
    Mount(path='/Text_4-1764705844', name='Text_4-1764705844', app=<fasthtml.core.FastHTML object at 0x14b735a00>)
]
[Route(path='/value', name='value', methods=['POST'])]
cprint(app.routes[1].app.routes)  # type: ignore
[Mount(path='/Combobox_1-1764705844', name='Combobox_1-1764705844', app=<fasthtml.core.FastHTML object at 0x14b54cb30>)]

Numeric

IntText

@dataclass
class IntText(Value):
    value: int = 0
    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:int):
        with self._updating(): self.value = value
        # return the update to allow feedback
        return self.value or str(self.value)  # fasthtml inconditionally handles 0s from route handlers as falsy, not bona fide values
    def value_control(self):
        return H.Input(name='value', value=str(self.value), type='number', 
                # hx_post=f"{self.ar.to('changed')}", hx_trigger='input changed throttle:50ms', hx_swap='none')
                hx_post=self.ar.value, hx_trigger='input changed', hx_swap='none')
app.routes.clear()
v = IntText(5)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="IntText_IntText_1-1764705844" class="control value inttext">
  <input name="value" value="5" type="number" hx-post="/IntText/IntText_1-1764705844/value" hx-trigger="input changed" hx-swap="none">
</div>
v.value
5
v.value = 10
show_routes(app)
[Mount(path='/IntText', name='IntText', app=<fasthtml.core.FastHTML object at 0x12f21e5d0>)]
[Mount(path='/IntText_1-1764705844', name='IntText_1-1764705844', app=<fasthtml.core.FastHTML object at 0x14b7357f0>)]
[Route(path='/value', name='value', methods=['POST'])]

BoundedInt

@dataclass
class BoundedInt(IntText):
    min: int = 0
    max: int = 100
    step: int = 1

    def setup_values(self):
        if (val := self.value) < self.min: self.value = self.min
        elif val > self.max: self.value = self.max

    def value_control(self):
        return super().value_control()(min=str(self.min), max=str(self.max), step=str(self.step))
v = BoundedInt(10, 5, 15)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="BoundedInt_BoundedInt_1-1764705844" class="control value inttext boundedint">
  <input name="value" value="10" type="number" hx-post="/BoundedInt/BoundedInt_1-1764705844/value" hx-trigger="input changed" hx-swap="none" min="5" max="15" step="1">
</div>
v.value
10
v.max = 10
test_eq(v.value, 10)

IntSlider

@dataclass
class IntSlider(BoundedInt):
    readout: bool = True
    readout_format: str = 'd'
    
    _wrapper_class = 'slider'
    def value_control(self):
        return super().value_control()(type='range', hx_target='next text', hx_swap='textContent')
    def controls(self):
        return (H.Label(_for='value')('Scale'), _n,
                self.value_control(),
                H.Text(id='spanscale', style='inline')(self.value), _n)
# brt.bridge.logger.show(clear=True)
iv = IntSlider()
with bridge_cfg(auto_show=False):
    display(iv.__ft__())
iv
<div id="IntSlider_IntSlider_1-1764705844" class="control value inttext boundedint slider">
<label for="value">Scale</label>
  <input name="value" value="0" type="range" hx-post="/IntSlider/IntSlider_1-1764705844/value" hx-trigger="input changed" hx-swap="textContent" min="0" max="100" step="1" hx-target="next text">
<text id="spanscale" style="inline">0</text>
</div>
0
# response = brt(_debug_req('value', iv.ar.value.to(), 'POST', 'value=50'))
iv.value
0
iv.value = 89
iv.min = 20
test_eq(iv.value, 89)
iv.max = 80
test_eq(iv.value, 80)
iv2 = IntSlider(33, step=2)
iv2
33
iv3 = IntSlider()
iv4 = IntSlider()
link((iv3, 'value'), (iv4, 'value'))

box = Div(style='display: flex; gap: 1em;')(iv3, iv4)
box
0
0
iv3.value = 22
test_eq((iv3.value, iv4.value), (22, 22))
iv5 = IntSlider(57)
iv6 = IntSlider()
link((iv5, 'value'), (iv6, 'value'))

box = HBox([iv5, iv6])
box
57
0

Bool

class _Bool(Value):
    value: bool = False
    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:str='', abc:bool=False):
        with self._updating(): self.value = not self.value
        return ''
<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-checkbox">
    <label class="widget-label" style=""></label>
    <label class="widget-label-basic">
        <input type="checkbox">
        <span title=""></span>
    </label>
</div>
# w = W.Checkbox(True)
# w
class Checkbox(_Bool):
    def value_control(self):
        return H.Div(cls='inline-hbox')(
            H.Label('Check')(
                Input(type='checkbox', name='value', checked=self.value, 
                    hx_post=self.ar.value, hx_trigger='click', 
                    hx_vals="js:{'abc':event.target.checked}", hx_swap='none')
            ),
        )
# brt.bridge.logger.show(clear=True)
# app.routes.clear()
v = Checkbox(False)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="Checkbox_Checkbox_1-1764705844" class="control value checkbox">
  <div class="inline-hbox">
<label>Check      <input type="checkbox" name="value" hx-post="/Checkbox/Checkbox_1-1764705844/value" hx-trigger="click" hx-swap="none" hx-vals="js:{'abc':event.target.checked}">
</label>  </div>
</div>
# response = brt(_debug_req('value', f"{v.ar.to('changed')}", 'POST', 'value=on&abc=true'))
test_is(v.value, False)
v.value = True
test_is(v.value, True)
<div class="lm-Widget p-Widget jupyter-widgets widget-valid widget-inline-hbox mod-invalid"><label class="widget-label" title="null" style="display: none;"></label><i class="fa fa-fw fa-times"></i><span class="widget-valid-readout widget-readout">Invalid</span></div>

<div class="lm-Widget jupyter-widgets widget-valid widget-inline-hbox mod-valid"><label class="widget-label" title="null" style="display: none;"></label><i class="fa fa-fw fa-check"></i><span class="widget-valid-readout widget-readout">Invalid</span></div>
# w = W.Valid(True)
# w
class Valid(_Bool):
    def value_control(self):
        return H.Div(cls='inline-hbox mod-invalid')(
            H.I(cls=f"fa fa-fw fa-{'check' if self.value else 'times'}"),
            H.Text(cls='readout')('valid' if self.value else 'invalid')
        )
v = Valid(True)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="Valid_Valid_1-1764705844" class="control value valid">
  <div class="inline-hbox mod-invalid">
<i class="fa fa-fw fa-check"></i><text class="readout">valid</text>  </div>
</div>
valid
v.value
True
v.value = False
<button class="lm-Widget jupyter-widgets jupyter-button widget-toggle-button mod-active" tabbable="null" title="null">Click me</button>
<button class="lm-Widget jupyter-widgets jupyter-button widget-toggle-button" tabbable="null" title="null">Click me</button>
class ToggleButton(_Bool):
    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:bool):
        with self._updating(): self.value = not value
        return self.value_control()
    def value_control(self):
        return H.Button('Click me')(
            cls=f"button", name='value', 
            value=f"{'on' if self.value else 'off'}", style=f"{'background-color:darkgray' if self.value else ''}",
            hx_post=self.ar.value, hx_swap='outerHTML')
v = ToggleButton(False)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="ToggleButton_ToggleButton_1-1764705844" class="control value togglebutton">
<button class="button" name="value" value="off" hx-post="/ToggleButton/ToggleButton_1-1764705844/value" hx-swap="outerHTML">Click me</button></div>
# response = brt(_debug_req('value', f"{v.ar.to('changed')}", 'POST', 'value=on'))
# print(v.value)

Selection

Select

<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-select">
    <label class="widget-label" for="a3c9d534-7c03-4948-9cf2-87dc8f353e1f" style="">OS:</label>
    <select id="a3c9d534-7c03-4948-9cf2-87dc8f353e1f" size="5">
        <option data-value="Linux" value="Linux">Linux</option>
        <option data-value="Windows" value="Windows">Windows</option>
        <option data-value="macOS" value="macOS">macOS</option>
    </select>
</div>
@dataclass
class SelectArea(_Select):
    rows: int = 5

    def value_control(self):
        return H.Div(cls='inline-hbox')(
            H.Label(self.description)(
                H.Select(name='value', size=self.rows,
                    hx_post=self.ar.value, 
                    hx_vals="js:{'index':event.target.selectedIndex}", hx_swap='none')(
                    *[H.Option(value=opt[1], selected=i==self.index)(opt[0]) for i,opt in enumerate(self.options)]
                )
            )
        )
v = SelectArea(
    options=['Linux', 'Windows', 'macOS'],
    value='macOS',
    # rows=10,
    description='OS:',
)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="SelectArea_SelectArea_1-1764705844" class="control value selectarea">
  <div class="inline-hbox">
<label>OS:<select name="value" size="5" hx-post="/SelectArea/SelectArea_1-1764705844/value" hx-swap="none" hx-vals="js:{'index':event.target.selectedIndex}"><option value="Linux">Linux</option><option value="Windows">Windows</option><option value="macOS" selected>macOS</option></select></label>  </div>
</div>
v.value, v.index
('macOS', 2)
v.value = 'Windows'

SelectMultiple

<div class="lm-Widget jupyter-widgets widget-inline-hbox widget-select widget-select-multiple">
    <label class="widget-label" for="c3af7603-7856-431f-ad6a-15ede273e09f" style="">Fruits</label>
    <select multiple="" id="c3af7603-7856-431f-ad6a-15ede273e09f" size="5">
        <option data-value="Apples" value="Apples">Apples</option>
        <option data-value="Oranges" value="Oranges">Oranges</option>
        <option data-value="Pears" value="Pears">Pears</option>
    </select>
</div>
@dataclass
class SelectMultiple(_Select):
    value: list[Any] = field(default_factory=list)
    # index: list[int] = field(default_factory=list)
    rows: int = 5

    @property
    def index(self) -> list[int]: return [self.values.index(_) for _ in self.value]
    @index.setter
    def index(self, ii:list[int]): self.value = [self.values[_] for _ in ii if _ in range(len(self.options))]

    @ar('/value', methods=['POST'], name='value')
    def changed(self, value:list[str]=[], index:list[int]=[]):
        with self._updating():
            # self.index = index
            self.value = [self.values[_] for _ in index if _ in range(len(self.options))]
            self.setup_values()

    def value_control(self):
        return H.Div(cls='inline-hbox')(
            H.Label(self.description)(
                H.Select(name='value', size=self.rows, multiple=True, 
                    hx_post=self.ar.value, 
                    hx_vals="js:{'index':[...event.target.selectedOptions].map(i=>i.index)}", hx_swap='none')(
                    *[H.Option(value=opt[1], selected=i in self.index)(opt[0]) for i,opt in enumerate(self.options)]
                )
            )
        )
v = SelectMultiple(
    options=['Apples', 'Oranges', 'Pears'],
    value=['Pears'],
    description='Fruits',
)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="SelectMultiple_SelectMultiple_1-1764705844" class="control value selectmultiple">
  <div class="inline-hbox">
<label>Fruits<select name="value" size="5" multiple hx-post="/SelectMultiple/SelectMultiple_1-1764705844/value" hx-swap="none" hx-vals="js:{'index':[...event.target.selectedOptions].map(i=&gt;i.index)}"><option value="Apples">Apples</option><option value="Oranges">Oranges</option><option value="Pears" selected>Pears</option></select></label>  </div>
</div>
v.value, v.index
(['Pears'], [2])

RadioButtons

<div class="lm-Widget widget-radio">
    <label class="widget-label" title="null" style="">Pizza topping:</label>
    <div class="widget-radio-box widget-radio-box-vertical" style="margin-bottom: 2px;">
        <label>pepperoni<input type="radio" value="0" data-value="pepperoni"></label>
        <label>pineapple<input type="radio" value="1" data-value="pineapple"></label>
        <label>anchovies<input type="radio" value="2" data-value="anchovies"></label>
    </div>
</div>
app.routes.clear()
@dataclass
class RadioButtons(_Select):
    @ar('/value', methods=['POST'], name='value')
    def changed(self, index:int):
        with self._updating():
            self.value = self.options[index-1][1]
    def value_control(self):
        n = self.ar.name().split(':')[-1]
        wid = self.wrapper_id()
        return H.Div(cls='inline-hbox')(
            H.Label(self.description)(
                Div(name='value', hx_post=self.ar.value, 
                    hx_vals="js:{'index':[...document.querySelectorAll('#" + wid + " input')].filter(e=>e.checked)[0].value}",
                    hx_trigger=f"click from:(#{wid} input)", hx_swap='none')(
                    *[H.Label(opt[0], 
                        H.Input(type='radio', value=i+1, name=n, checked=i==self.index)) 
                    for i,opt in enumerate(self.options)]
                )
            )
        )
v = RadioButtons(
    options=['Larry', 'Moe', 'Curly'],
    value='Moe',
    description='3S:',
)
with bridge_cfg(auto_show=False):
    display(v.__ft__())
v
<div id="RadioButtons_RadioButtons_1-1764705844" class="control value radiobuttons">
  <div class="inline-hbox">
<label>3S:      <div name="value" hx-post="/RadioButtons/RadioButtons_1-1764705844/value" hx-trigger="click from:(#RadioButtons_RadioButtons_1-1764705844 input)" hx-swap="none" hx-vals="js:{'index':[...document.querySelectorAll('#RadioButtons_RadioButtons_1-1764705844 input')].filter(e=&gt;e.checked)[0].value}">
<label>Larry          <input type="radio" value="1" name="RadioButtons_1-1764705844">
</label><label>Moe          <input type="radio" value="2" name="RadioButtons_1-1764705844" checked>
</label><label>Curly          <input type="radio" value="3" name="RadioButtons_1-1764705844">
</label>      </div>
</label>  </div>
</div>
v.value, v.index
('Moe', 1)
show_routes(app)
[Mount(path='/RadioButtons', name='RadioButtons', app=<fasthtml.core.FastHTML object at 0x12f21e5d0>)]
[
    Mount(path='/RadioButtons_1-1764705844', name='RadioButtons_1-1764705844', app=<fasthtml.core.FastHTML object at 
0x14b94aa80>)
]
[Route(path='/value', name='value', methods=['POST'])]
<div class="widget-box">
    <div class="lm-Widget jupyter-widgets widget-inline-hbox widget-label">Pizza topping with a very long label:</div>
    <div class="radio" style="width: max-content;">
        <label class="widget-label" title="null" style="display: none;"></label>
        <div class="radio-box radio-box-vertical">
            <label>pepperoni<input type="radio" value="0" data-value="pepperoni"></label>
            <label>pineapple<input type="radio" value="1" data-value="pineapple"></label>
            <label>anchovies<input type="radio" value="2" data-value="anchovies"></label>
            <label>and the long name that will fit fine and the long name that will fit fine and the long name that will fit fine <input type="radio" value="3" data-value="and%20the%20long%20name%20that%20will%20fit%20fine%20and%20the%20long%20name%20that%20will%20fit%20fine%20and%20the%20long%20name%20that%20will%20fit%20fine%20"></label>
        </div>
    </div>
</div>

ToggleButtons

<div class="lm-Widget widget-toggle-buttons">
    <label class="widget-label" title="null" style="">Speed:</label>
    <div>
        <button type="button" class="widget-toggle-button jupyter-button mod-active" data-value="Slow" value="0" title="Description of slow">Slow<i></i></button>
        <button type="button" class="widget-toggle-button jupyter-button" data-value="Regular" value="1" title="Description of regular">Regular<i></i></button>
        <button type="button" class="widget-toggle-button jupyter-button" data-value="Fast" value="2" title="Description of fast">Fast<i></i></button>
    </div>
</div>

Simple widgets (Traitlets)

Value

# class Value(T.HasTraits, Control):
#     value = T.Any()
    
#     @T.observe('value')
#     def on_value(self, _):
#         if self.bridget and not self._updating_:
#             self.bridget.bridge.commander.swap(f"#{self.ar.name()}", to_xml(self.__ft__()), swapStyle='innerHTML')
# app.routes.clear()
# v = Value(value='Hello, world!')
# with bridge_cfg(auto_show=False):
#     display(v.__ft__())
# v
# v.value
# v.value = 'bye!'

Numeric

# class Numeric(Value):
#     value: int
#     min, max, step = None, None, None
#     input_type = 'number'
#     hx_target, hx_swap = None, 'none'
#     @ar('/value', methods=['POST'])
#     def changed(self, value:int):
#         with self._updating(): self.value = value
#         return self.value  # fasthtml handles 0s from route handlers as falsy, not bona fide values
#     def __ft__(self):
#         return Input(type=self.input_type, name='value', 
#                     value=self.value, min=self.min, max=self.max, step=self.step,
#                     hx_post=f"/{self.ar.to()}/value", hx_trigger='input changed throttle:100ms', 
#                     hx_target=self.hx_target, hx_swap=self.hx_swap)
# class _Int(_Value):
#     value: int
#     min, max, step = None, None, None
#     input_type = 'number'
#     hx_target, hx_swap = None, 'none'
#     @ar('/value', methods=['POST'])
#     def changed(self, value:int):
#         with self._updating(): self.value = value
#         return self.value  # fasthtml handles 0s from route handlers as falsy, not bona fide values
#     def value_control(self):
#         return Input(type=self.input_type, name='value', value=self.value, min=self.min, max=self.max, step=self.step,
#                 hx_post=f"/{self.ar.to()}/value", hx_trigger='input changed throttle:100ms', 
#                 hx_target=self.hx_target, hx_swap=self.hx_swap)

IntText

# class IntText(Value, _Int):
#     value = T.CInt()
# v = IntText(value=5)
# with bridge_cfg(auto_show=False):
#     display(v.__ft__())
# v
# v.value
# v.value = 17

BoundedInt

# class BoundedInt(IntText):
#     min = T.CInt(0)
#     max = T.CInt(100)
#     step = T.Int(1)

#     def value_control(self): return super().value_control()(min=self.min, max=self.max, step=self.step)
# v = BoundedInt(value=10, min=5, max=15)
# with bridge_cfg(auto_show=False):
#     display(v.__ft__())
# v
# v.value
# v.max = 17

IntSlider

# class IntSlider(BoundedInt):
#     readout:bool=True; readout_format:str='d'
    
#     _wrapper_class = 'slider'
#     def value_control(self):
#         return Input(type='range', name='value', min=self.min, max=self.max, step=self.step, value=self.value,
#                 hx_post=f"/{self.ar.to()}/value", hx_trigger='input changed throttle:100ms', 
#                 hx_target='next text', hx_swap='textContent')
#     def setup_controls(self):
#         return (Label(_for='value')('Scale'), _n,
#                 self.value_control(),
#                 Text(id='spanscale', style='inline')(self.value), _n)
# iv = IntSlider()
# with bridge_cfg(auto_show=False):
#     display(iv.__ft__())
# iv
# iv.value
# iv.value = 77
# iv2 = IntSlider(step=2)
# iv2
# iv3 = IntSlider()
# iv4 = IntSlider()
# # brt.mount(iv3, show=False)
# # brt.mount(iv4, show=False)
# T.link((iv3, 'value'), (iv4, 'value'))

# box = Div(style='display: flex; gap: 1em;')(iv3, iv4)
# box
# iv3.value = 22
# test_eq(iv4.value, 22)

Simple Widget (psignal) - Not working

Output

out = W.Output()
out
msg_id = None
with out:
    msg_id = out.msg_id
    out.clear_output()
    display(Text('Hello, world!'))
msg_id
out.append_display_data(HTML('<div id="asdf">asdf</div>'))
out.append_display_data(Div(style='color: red;')(Text('Hi!')))
out.append_display_data(Markdown('Hi!'))
# o = HTML('<div id="asdf">asdf</div>')
# display(o)
from IPython.utils.capture import RichOutput, CapturedIO, capture_output
with capture_output() as io:
    display(Div(style='color: red;')(Text('Hi!')), HTML('<div id="asdf">asdf</div>'), 'Hi!', Markdown('Hi!'))
# io.outputs
# io.show()
io()
def _capture(*objs, **kwargs):
    if not InteractiveShell.initialized():
        raise RuntimeError('IPython not initialized')
    outs = []
    format = InteractiveShell.instance().display_formatter.format
    for obj in objs:
        format_dict, md_dict = format(obj)
        if not format_dict:
            # nothing to display (e.g. _ipython_display_ took over)
            outs.append((None, None))
            continue
        outs.append((format_dict, md_dict))
    return outs

_capture(Div(style='color: red;')(Text('Hi!')), HTML('<div id="asdf">asdf</div>'), 'Hi!', Markdown('Hi!'))
def _capture_output(*objs, **kwargs):
    with capture_output() as io:
        display(*objs, **kwargs)
    return io

capt = _capture_output(Div(style='color: red;')(Text('Hi!')), HTML('<div id="asdf">asdf</div>'), 'Hi!', Markdown('Hi!'))
capt()
for c in capt.outputs:
    print(c._repr_mimebundle_())
class Output(Widget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.contents = []
        Bridget().mount(self, show=False)
    # def __ft__(self):
    #     return super().__ft__()(id=self.idx)
    # def _ipython_display_(self):
    #     Bridget()(self, display_id=self.idx)
    def clear_output(self, wait=False):
        self.contents = []
        if not wait and self.dhdl: self.dhdl.update()
    # def capture(self, clear_output=False, *clear_args, **clear_kwargs):
    #     pass
    # @contextmanager
    # def managed_resource(*args, **kwds):
    #     # Code to acquire resource, e.g.:
    #     resource = acquire_resource(*args, **kwds)
    #     try:
    #         yield resource
    #     finally:
    #         # Code to release resource, e.g.:
    #         release_resource(resource)

    def __enter__(self):
        self._capt = capture_output()
        self._cio = self._capt.__enter__()
    def __exit__(self, etype, evalue, tb):
        bridge_cfg.current_did = None
        self._capt.__exit__(etype, evalue, tb)
        outputs, self._cio, self._capt = self._cio.outputs, None, None
        if not self.dhdl:
            dhdl = display(display_id=True)
            self.dhdl = DisplayId(dhdl.display_id)
        dhdl = self.dhdl.display_id
        for c in outputs:
            mime = {'data': c.data, 'metadata': c.metadata}
            self.contents.append(mime)
            display(mime['data'], metadata=mime['metadata'], raw=True, update=True, display_id=dhdl)
    
    # def _flush(self):
    #     pass
    def append_stdout(self, text):
        brt = self.bridget
        brt.swap(f"#{self.ar.name}", text+'<br>', swapStyle='beforeend')
        self.contents.append(text)
    def append_stderr(self, text):
        brt = self.bridget
        brt.swap(f"#{self.ar.name}", text+'<br>', swapStyle='beforeend')
        self.contents.append(text)
    def append_display_data(self, display_object):
        with capture_output() as io:
            display(display_object)
        mimebundle = io.outputs[0]._repr_mimebundle_()
        html = mimebundle[0]['text/html']
        brt = self.bridget
        brt.swap(f"#{self.ar.name}", html, swapStyle='beforeend')
        self.contents.append(display_object)
out = Output()
with bridge_cfg(auto_show=False):
    display(out.__ft__())
out
out(Text('Hello, world!'))
out.append_stdout('Hello, universe!')
out.append_display_data(Div(style='color: red; padding: 0px;')(Text('Hello, red!')))
out.append_display_data(Button('adfrt'))
out.contents
out = Output()
with out:
    display('adfrt')
# out.contents
out.append_display_data(Div(style='color: red; padding: 0px;')(Text('Hello, red!')))
out.contents
import matplotlib.pyplot as plt
with out:
    fig = plt.figure(figsize=(4, 2.67))
    plt.plot([1,2,3,4])
    # plt.show()
out.contents
# with out:
#     plt.plot([1,2,3,4])
#     plt.show()

out = Output(layout={'border': '1px solid black'})
out
with out:
    for i in range(10):
        print(i, 'Hello world!')
from IPython.display import YouTubeVideo
with out:
    display(YouTubeVideo('eWzY2nGfkXk'))
with out:
    display(widgets.IntSlider())
out = widgets.Output(layout={'border': '1px solid black'})
out.append_stdout('Output appended with append_stdout')
out.append_display_data(YouTubeVideo('eWzY2nGfkXk'))
out
@out.capture()
def function_with_captured_output():
    print('This goes into the output widget')
    raise Exception('As does this')
    
function_with_captured_output()
a = widgets.IntSlider(description='a')
b = widgets.IntSlider(description='b')
c = widgets.IntSlider(description='c')
def f(a, b, c):
    print('{}*{}*{}={}'.format(a, b, c, a*b*c))

out = widgets.interactive_output(f, {'a': a, 'b': b, 'c': c})

widgets.HBox([widgets.VBox([a, b, c]), out])

interactive_output

interactive

def f(a, b):
    display(a + b)
    return a+b
w = W.interactive(f, a=10, b=20)
w
from ipywidgets import interactive
import matplotlib.pyplot as plt
import numpy as np

def f(m, b):
    plt.figure(2)
    x = np.linspace(-10, 10, num=1000)
    plt.plot(x, m * x + b)
    plt.ylim(-5, 5)
    plt.show()

interactive_plot = interactive(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))
output = interactive_plot.out#children[-1]
output.layout.height = '420px'
interactive_plot

interact

def f(x):
    return x
interact(f, x=10);
interact(f, x=True);
interact(f, x='Hi there!');
@interact(x=True, y=1.0)
def g(x, y):
    return (x, y)
IntSlider(min=-10, max=30, step=1, value=10)
interact(f, x=widgets.IntSlider(min=-10, max=30, step=1, value=10));

interact_manual