diff --git a/example.py b/example.py index 17a09ce..d046626 100644 --- a/example.py +++ b/example.py @@ -79,69 +79,69 @@ def search_empty_list(_: str): # searchbox configurations, see __init__.py for details # will pass all kwargs to the searchbox component boxes = [ - # dict( - # search_function=search_wikipedia_ids, - # placeholder="Search Wikipedia", - # label=search_wikipedia_ids.__name__, - # default="SOME DEFAULT", - # clear_on_submit=False, - # key=search_wikipedia_ids.__name__, - # ), - # dict( - # search_function=search, - # default=None, - # label=search.__name__, - # clear_on_submit=False, - # key=search.__name__, - # ), - # dict( - # search_function=search_rnd_delay, - # default=None, - # clear_on_submit=False, - # label=search_rnd_delay.__name__, - # key=search_rnd_delay.__name__, - # ), - # dict( - # search_function=search_enum_return, - # clear_on_submit=True, - # key=search_enum_return.__name__, - # label=search_enum_return.__name__, - # ), - # dict( - # search_function=search_empty_list, - # clear_on_submit=True, - # key=search_empty_list.__name__, - # label=search_empty_list.__name__, - # ), - # dict( - # search_function=search, - # default_options=["inital", "list", "of", "options"], - # key=f"{search.__name__}_default_options", - # label=f"{search.__name__}_default_options", - # ), - # dict( - # search_function=search, - # default="initial", - # default_options=["inital", "list", "of", "options"], - # key=f"{search.__name__}_default_options_all", - # label=f"{search.__name__}_default_options_all", - # ), - # dict( - # search_function=search, - # default_options=[("inital", "i"), ("list", "l")], - # key=f"{search.__name__}_default_options_tuple", - # label=f"{search.__name__}_default_options_tuple", - # ), - # dict( - # search_function=search, - # key=f"{search.__name__}_rerun_disabled", - # rerun_on_update=False, - # label=f"{search.__name__}_rerun_disabled", - # ), + dict( + search_function=search_wikipedia_ids, + placeholder="Search Wikipedia", + label=search_wikipedia_ids.__name__, + default="SOME DEFAULT", + clear_on_submit=False, + key=search_wikipedia_ids.__name__, + ), + dict( + search_function=search, + default=None, + label=search.__name__, + clear_on_submit=False, + key=search.__name__, + ), + dict( + search_function=search_rnd_delay, + default=None, + clear_on_submit=False, + label=search_rnd_delay.__name__, + key=search_rnd_delay.__name__, + ), + dict( + search_function=search_enum_return, + clear_on_submit=True, + key=search_enum_return.__name__, + label=search_enum_return.__name__, + ), + dict( + search_function=search_empty_list, + clear_on_submit=True, + key=search_empty_list.__name__, + label=search_empty_list.__name__, + ), + dict( + search_function=search, + default_options=["inital", "list", "of", "options"], + key=f"{search.__name__}_default_options", + label=f"{search.__name__}_default_options", + ), + dict( + search_function=search, + default="initial", + default_options=["inital", "list", "of", "options"], + key=f"{search.__name__}_default_options_all", + label=f"{search.__name__}_default_options_all", + ), + dict( + search_function=search, + default_options=[("inital", "i"), ("list", "l")], + key=f"{search.__name__}_default_options_tuple", + label=f"{search.__name__}_default_options_tuple", + ), + dict( + search_function=search, + key=f"{search.__name__}_rerun_disabled", + rerun_on_update=False, + label=f"{search.__name__}_rerun_disabled", + ), dict( search_function=search, key=f"{search.__name__}_keep_value_on_submit", - input_value="selection", + editable_after_submit=True, label=f"{search.__name__}_keep_value_on_submit", ), ] @@ -166,49 +166,49 @@ def search_empty_list(_: str): st.markdown("---") -# with visual_ref: -# st.multiselect( -# "Multiselect", -# [1, 2, 3, 4, 5], -# default=[1, 2], -# key="multiselect", -# ) -# st.selectbox( -# "Selectbox", -# [1, 2, 3], -# index=1, -# key="selectbox", -# ) -# -# with form_example: -# with st.form("myform"): -# c1, c2 = st.columns(2) -# with c1: -# sr = st_searchbox( -# search_function=search, -# key=f"{search.__name__}_form", -# ) -# with c2: -# st.form_submit_button("load suggestions") -# -# submit = st.form_submit_button("real submit") -# if submit: -# st.write("form submitted") -# st.write(sr) - -# with manual_example: -# key = f"{search.__name__}_manual" -# -# if key in st.session_state: -# st.session_state[key]["options_js"] = [ -# {"label": f"{st.session_state[key]['search']}_{i}", "value": i} -# for i in range(5) -# ] -# st.session_state[key]["options_py"] = [i for i in range(5)] -# -# manual = st_searchbox( -# search_function=lambda _: [], -# key=key, -# ) -# -# st.write(manual) +with visual_ref: + st.multiselect( + "Multiselect", + [1, 2, 3, 4, 5], + default=[1, 2], + key="multiselect", + ) + st.selectbox( + "Selectbox", + [1, 2, 3], + index=1, + key="selectbox", + ) + +with form_example: + with st.form("myform"): + c1, c2 = st.columns(2) + with c1: + sr = st_searchbox( + search_function=search, + key=f"{search.__name__}_form", + ) + with c2: + st.form_submit_button("load suggestions") + + submit = st.form_submit_button("real submit") + if submit: + st.write("form submitted") + st.write(sr) + +with manual_example: + key = f"{search.__name__}_manual" + + if key in st.session_state: + st.session_state[key]["options_js"] = [ + {"label": f"{st.session_state[key]['search']}_{i}", "value": i} + for i in range(5) + ] + st.session_state[key]["options_py"] = [i for i in range(5)] + + manual = st_searchbox( + search_function=lambda _: [], + key=key, + ) + + st.write(manual) diff --git a/streamlit_searchbox/__init__.py b/streamlit_searchbox/__init__.py index 26a6d87..ab42a28 100644 --- a/streamlit_searchbox/__init__.py +++ b/streamlit_searchbox/__init__.py @@ -8,7 +8,7 @@ import logging import os import time -from typing import Any, Callable, List, Literal +from typing import Any, Callable, List import streamlit as st import streamlit.components.v1 as components @@ -126,7 +126,7 @@ def st_searchbox( default_options: List[Any] | None = None, clear_on_submit: bool = False, rerun_on_update: bool = True, - input_value: Literal["default", "selection", "append"] = "default", + editable_after_submit: bool = False, key: str = "searchbox", **kwargs, ) -> Any: @@ -161,7 +161,7 @@ def st_searchbox( clear_on_submit=clear_on_submit, placeholder=placeholder, label=label, - input_value=input_value, + editable_after_submit=editable_after_submit, # react return state within streamlit session_state key=st.session_state[key]["key_react"], **kwargs, diff --git a/streamlit_searchbox/frontend/src/Searchbox.tsx b/streamlit_searchbox/frontend/src/Searchbox.tsx index 423fcd0..64caecf 100644 --- a/streamlit_searchbox/frontend/src/Searchbox.tsx +++ b/streamlit_searchbox/frontend/src/Searchbox.tsx @@ -23,6 +23,7 @@ interface StreamlitReturn { interaction: "submit" | "search" | "reset"; value: any; } +const Input = (props: any) => ; export function streamlitReturn(interaction: string, value: any): void { Streamlit.setComponentValue({ @@ -50,6 +51,7 @@ class Searchbox extends StreamlitComponentBase { private callbackSearch = (input: string): void => { this.setState({ inputValue: input, + option: null, }); streamlitReturn("search", input); @@ -73,27 +75,18 @@ class Searchbox extends StreamlitComponentBase { * @param option */ private callbackSubmit(option: Option) { - console.log("callbackSubmit"); - console.log("option", this.state.option, option); - if (this.props.args.clear_on_submit) { this.setState({ menu: false, - option: null, inputValue: "", + option: null, }); } else { - console.log("keep value on submit", this.props.args.input_value); - console.log("option", option); - this.setState({ menu: false, // keep value on submit - option: { - value: option.value, - label: option.label, - }, inputValue: option.label, + option: option, }); } @@ -105,21 +98,12 @@ class Searchbox extends StreamlitComponentBase { * @returns */ public render = (): ReactNode => { - console.log( - "render", - this.state.option, - this.state.inputValue, - this.props.disabled - ); - console.log(this.props); - - // TODO: broken - - // https://react-select.com/advanced - // "Below is an example of replicating the behaviour of the deprecated props from react-select v1, onSelectResetsInput and closeOnSelect" - - // describes the issue with resetting input value - // https://github.com/JedWatson/react-select/discussions/4302 + // always focus the input field to enable edits + const onFocus = () => { + if (this.props.args.editable_after_submit && this.state.inputValue) { + this.state.inputValue && this.ref.current.select.inputRef.select(); + } + }; return ( @@ -128,15 +112,15 @@ class Searchbox extends StreamlitComponentBase { )} { components={{ ClearIndicator: (props) => this.style.clearIndicator(props), DropdownIndicator: () => this.style.iconDropdown(this.state.menu), - IndicatorSeparator: () => , + IndicatorSeparator: () => null, + Input: this.props.args.editable_after_submit + ? Input + : components.Input, }} // handlers filterOption={(_, __) => true} + onFocus={() => onFocus()} // eslint-disable-next-line @typescript-eslint/no-explicit-any onChange={(option: any, a: any) => { switch (a.action) { @@ -161,7 +149,6 @@ class Searchbox extends StreamlitComponentBase { this.callbackReset(); return; } - console.log("onChange", a); }} onInputChange={( inputValue: string, @@ -171,11 +158,8 @@ class Searchbox extends StreamlitComponentBase { // ignore menu close or blur/unfocus events case "input-change": this.callbackSearch(inputValue); - return inputValue; + return; } - - console.log("onInputChange", inputValue, action, prevInputValue); - return prevInputValue; }} onMenuOpen={() => this.setState({ menu: true })} onMenuClose={() => this.setState({ menu: false })}