streamlit-sortable-multiselect 0.7.4__tar.gz → 0.7.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- streamlit_sortable_multiselect-0.7.6/PKG-INFO +338 -0
- streamlit_sortable_multiselect-0.7.6/README.md +310 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/pyproject.toml +1 -1
- streamlit_sortable_multiselect-0.7.6/streamlit_sortable_multiselect/__init__.py +535 -0
- streamlit_sortable_multiselect-0.7.6/streamlit_sortable_multiselect/frontend/build/assets/index-B3sxegjX.js +67 -0
- streamlit_sortable_multiselect-0.7.6/streamlit_sortable_multiselect/frontend/build/assets/index-CJc6AEWC.css +1 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect/frontend/build/index.html +2 -2
- streamlit_sortable_multiselect-0.7.6/streamlit_sortable_multiselect.egg-info/PKG-INFO +338 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/SOURCES.txt +2 -2
- streamlit_sortable_multiselect-0.7.6/tests/test_wrapper.py +345 -0
- streamlit_sortable_multiselect-0.7.4/PKG-INFO +0 -148
- streamlit_sortable_multiselect-0.7.4/README.md +0 -120
- streamlit_sortable_multiselect-0.7.4/streamlit_sortable_multiselect/__init__.py +0 -256
- streamlit_sortable_multiselect-0.7.4/streamlit_sortable_multiselect/frontend/build/assets/index-BxYP2Xl8.js +0 -67
- streamlit_sortable_multiselect-0.7.4/streamlit_sortable_multiselect/frontend/build/assets/index-DFEJzS1A.css +0 -1
- streamlit_sortable_multiselect-0.7.4/streamlit_sortable_multiselect.egg-info/PKG-INFO +0 -148
- streamlit_sortable_multiselect-0.7.4/tests/test_wrapper.py +0 -149
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/LICENSE +0 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/MANIFEST.in +0 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/setup.cfg +0 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/dependency_links.txt +0 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/requires.txt +0 -0
- {streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: streamlit-sortable-multiselect
|
|
3
|
+
Version: 0.7.6
|
|
4
|
+
Summary: A Streamlit custom component for selecting and reordering multiple string values.
|
|
5
|
+
Author: Kentaro Aoki
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/KentaroAOKI/streamlit-sortable-multiselect
|
|
8
|
+
Project-URL: Repository, https://github.com/KentaroAOKI/streamlit-sortable-multiselect
|
|
9
|
+
Project-URL: Issues, https://github.com/KentaroAOKI/streamlit-sortable-multiselect/issues
|
|
10
|
+
Keywords: streamlit,component,multiselect,sortable,drag-and-drop
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: streamlit>=1.30
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
26
|
+
Requires-Dist: twine>=6; extra == "dev"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# streamlit-sortable-multiselect
|
|
30
|
+
|
|
31
|
+
A Streamlit custom component for searching, selecting, and reordering multiple string values.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python -m pip install streamlit-sortable-multiselect
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Upgrade an existing installation:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m pip install --upgrade streamlit-sortable-multiselect
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For local development:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python -m pip install -e ".[dev]"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For frontend development:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd streamlit_sortable_multiselect/frontend
|
|
55
|
+
npm install
|
|
56
|
+
npm run dev
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import streamlit as st
|
|
63
|
+
from streamlit_sortable_multiselect import sortable_multiselect
|
|
64
|
+
|
|
65
|
+
selected = sortable_multiselect(
|
|
66
|
+
"Favorite frameworks",
|
|
67
|
+
options=[
|
|
68
|
+
{"label": "Streamlit", "value": "streamlit", "icon_url": "https://streamlit.io/images/brand/streamlit-mark-color.png"},
|
|
69
|
+
{"label": "FastAPI", "value": "fastapi", "icon_url": "https://fastapi.tiangolo.com/img/favicon.png"},
|
|
70
|
+
{"label": "Django", "value": "django"},
|
|
71
|
+
{"label": "Flask", "value": "flask"},
|
|
72
|
+
],
|
|
73
|
+
default=["streamlit"],
|
|
74
|
+
placeholder="Search frameworks...",
|
|
75
|
+
show_move_buttons=True,
|
|
76
|
+
show_numbers=True,
|
|
77
|
+
base_color="#eef2ff",
|
|
78
|
+
order_colors={1: "#fee2e2", 2: "#dcfce7"},
|
|
79
|
+
max_selections=3,
|
|
80
|
+
max_selections_placeholder="Choose up to 3 frameworks",
|
|
81
|
+
empty_message="No frameworks selected",
|
|
82
|
+
no_options_placeholder="All frameworks selected",
|
|
83
|
+
selected_position="top",
|
|
84
|
+
icon_size=24,
|
|
85
|
+
options_max_height=260,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
st.write(selected)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Settings
|
|
92
|
+
|
|
93
|
+
`sortable_multiselect` returns the selected option values as a `list[str]` in the current display order.
|
|
94
|
+
|
|
95
|
+
| Argument | Type | Default | Description |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| `label` | `str` | required | Label displayed above the component. |
|
|
98
|
+
| `options` | `Sequence[str \| Mapping[str, Any]]` | required | Available options. Each option can be a plain string, or a dictionary with `label`, `value`, and optional `icon_url`. Option values must be unique. |
|
|
99
|
+
| `default` | `Sequence[str] \| None` | `None` | Initially selected values, in the initial order. Values must exist in `options` and must not contain duplicates. |
|
|
100
|
+
| `placeholder` | `str` | `"Select..."` | Placeholder shown in the search/add input when options are available. |
|
|
101
|
+
| `disabled` | `bool` | `False` | Disables searching, selecting, removing, dragging, and move buttons. |
|
|
102
|
+
| `show_move_buttons` | `bool` | `True` | Shows up/down buttons on selected items. Drag sorting remains available unless `disabled=True`. |
|
|
103
|
+
| `show_numbers` | `bool` | `False` | Shows 1-based position numbers before selected item labels. |
|
|
104
|
+
| `base_color` | `str \| Mapping[str, str] \| None` | `None` | Fallback color for every selected item. See [Colors](#colors). |
|
|
105
|
+
| `order_colors` | `Mapping[int, str \| Mapping[str, str]] \| None` | `None` | Per-position colors. Positive keys count from the top (`1` is first), negative keys count from the bottom (`-1` is last), for example `{1: "#fee2e2", -1: "#dcfce7"}`. |
|
|
106
|
+
| `value_colors` | `Mapping[str, str \| Mapping[str, str]] \| None` | `None` | Per-value colors keyed by option value, for example `{"python": "#3776ab"}`. These follow an item as it is reordered. |
|
|
107
|
+
| `color_palette` | `Sequence[str \| Mapping[str, str]] \| None` | `None` | Colors cycled across selected positions. Position 1 uses the first entry and the palette repeats for longer selections. |
|
|
108
|
+
| `color_priority` | `Sequence[str] \| None` | `None` | Ranking of the color sources `"value"`, `"option"`, `"order"`, `"palette"`, and `"base"`. Omitted sources keep their default rank. |
|
|
109
|
+
| `tooltip_color` | `str \| Mapping[str, str] \| None` | `None` | Color of the tooltip shown for labels that do not fit. `None` uses the built-in dark tooltip. See [Labels](#labels). |
|
|
110
|
+
| `max_selections` | `int \| None` | `None` | Maximum number of selected items. `None` means no limit. Use `0` to prevent any selections. |
|
|
111
|
+
| `max_selections_placeholder` | `str` | `"Selection limit reached"` | Placeholder shown when `max_selections` has been reached. This takes precedence over `placeholder` and `no_options_placeholder`. |
|
|
112
|
+
| `empty_message` | `str` | `"No items selected"` | Message shown where the selected list appears when no items are selected. |
|
|
113
|
+
| `no_options_placeholder` | `str` | `"No more options"` | Placeholder shown when every option is already selected and there are no more options to add. |
|
|
114
|
+
| `selected_position` | `str` | `"bottom"` | Position of the selected item list relative to the search/add input. Use `"bottom"` or `"top"`. |
|
|
115
|
+
| `icon_size` | `int` | `20` | Icon display size in pixels for `icon_url` images. Images are displayed inside a square area while preserving their aspect ratio. |
|
|
116
|
+
| `options_max_height` | `int` | `190` | Maximum height in pixels for the available options dropdown. |
|
|
117
|
+
| `suggestions_api_url` | `str \| None` | `None` | Absolute HTTP(S) endpoint used to fetch suggestions in the browser. `None` disables API suggestions. |
|
|
118
|
+
| `suggestions_query_param` | `str` | `"q"` | Query parameter name used to send the current search text. |
|
|
119
|
+
| `suggestions_response_path` | `str` | `""` | Dot-separated path to the suggestions array in the JSON response. Empty means the response root. |
|
|
120
|
+
| `suggestions_label_path` | `str` | `"label"` | Dot-separated path to each suggestion's display label. |
|
|
121
|
+
| `suggestions_value_path` | `str` | `"value"` | Dot-separated path to each suggestion's returned value. |
|
|
122
|
+
| `suggestions_icon_url_path` | `str \| None` | `"icon_url"` | Optional dot-separated path to each suggestion's icon URL. `None` disables API icons. |
|
|
123
|
+
| `suggestions_color_path` | `str \| None` | `None` | Optional dot-separated path to each suggestion's color. The value may be a CSS color string or a mapping of color fields. `None` disables API colors. |
|
|
124
|
+
| `suggestions_headers` | `Mapping[str, str] \| None` | `None` | HTTP headers sent with suggestions requests. Header values are visible to browser users and must not contain secrets. |
|
|
125
|
+
| `suggestions_min_chars` | `int` | `1` | Minimum trimmed query length before requesting suggestions. Use `0` to allow an empty query. |
|
|
126
|
+
| `suggestions_debounce_ms` | `int` | `300` | Delay in milliseconds between the latest input and the API request. |
|
|
127
|
+
| `suggestions_loading_message` | `str` | `"Loading suggestions..."` | Message shown while an API request is in progress. |
|
|
128
|
+
| `suggestions_error_message` | `str` | `"Failed to load suggestions"` | Message shown when the request or response cannot be processed. |
|
|
129
|
+
| `key` | `str \| None` | `None` | Optional Streamlit component key. Use this when rendering multiple sortable multiselects. |
|
|
130
|
+
|
|
131
|
+
Option dictionaries use this shape:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
{
|
|
135
|
+
"label": "Python",
|
|
136
|
+
"value": "python",
|
|
137
|
+
"icon_url": "https://www.python.org/static/favicon.ico",
|
|
138
|
+
"color": "#3776ab",
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`icon_url` and `color` may be omitted. The returned value is always the `value`, not the display `label`.
|
|
143
|
+
|
|
144
|
+
## Colors
|
|
145
|
+
|
|
146
|
+
Anywhere a color is accepted, pass either a CSS color string or a mapping that sets
|
|
147
|
+
any subset of `background`, `text`, and `border`:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
"#fee2e2"
|
|
151
|
+
{"background": "#111827", "text": "#facc15", "border": "#f74c00"}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A string sets the background. When `text` is not set, the component picks black or
|
|
155
|
+
white for readability against the resolved background.
|
|
156
|
+
|
|
157
|
+
Colors come from five sources. Each source contributes a color, and the highest
|
|
158
|
+
ranked source that sets a field wins that field, so a value color that sets only
|
|
159
|
+
`text` still inherits the background from `base_color`:
|
|
160
|
+
|
|
161
|
+
| Source | Set with | Keyed by |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `value` | `value_colors` | Option value. Follows the item as it is reordered. |
|
|
164
|
+
| `option` | `color` in an option dictionary, or `suggestions_color_path` | The option itself. |
|
|
165
|
+
| `order` | `order_colors` | Selected position. `1` is first, `-1` is last. |
|
|
166
|
+
| `palette` | `color_palette` | Selected position, cycling through the palette. |
|
|
167
|
+
| `base` | `base_color` | Every selected item. |
|
|
168
|
+
|
|
169
|
+
The default ranking is the table order. Reorder it with `color_priority`; sources you
|
|
170
|
+
leave out keep their default rank, so `color_priority=["order"]` only promotes
|
|
171
|
+
position colors above everything else.
|
|
172
|
+
|
|
173
|
+
When a positive and a negative `order_colors` key address the same slot, such as `1`
|
|
174
|
+
and `-3` in a three item list, the positive key wins. Every color must set at least
|
|
175
|
+
one field: `None` and `{}` are rejected rather than ignored, so a mistyped color
|
|
176
|
+
surfaces as an error instead of silently doing nothing.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
selected = sortable_multiselect(
|
|
180
|
+
"Podium",
|
|
181
|
+
options=[
|
|
182
|
+
{"label": "Python", "value": "python", "color": "#3776ab"},
|
|
183
|
+
{"label": "Rust", "value": "rust"},
|
|
184
|
+
{"label": "Go", "value": "go"},
|
|
185
|
+
],
|
|
186
|
+
default=["python", "rust", "go"],
|
|
187
|
+
# Gold, silver, and bronze by position, ranked above each option's own color.
|
|
188
|
+
order_colors={
|
|
189
|
+
1: {"background": "#fde68a", "border": "#f59e0b"},
|
|
190
|
+
2: {"background": "#e5e7eb", "border": "#9ca3af"},
|
|
191
|
+
-1: {"background": "#fed7aa", "border": "#ea580c"},
|
|
192
|
+
},
|
|
193
|
+
color_priority=["order"],
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Options that resolve to a color show a small swatch in the options dropdown.
|
|
198
|
+
|
|
199
|
+
Because an option's `value` is its id, `value_colors` binds a color to an item rather
|
|
200
|
+
than to a slot. `examples/color_by_id.py` puts both side by side: two lists holding the
|
|
201
|
+
same items, one colored by position and one colored by id. Dragging an item in each
|
|
202
|
+
shows position colors staying with the slot while id colors travel with the item.
|
|
203
|
+
|
|
204
|
+
## Labels
|
|
205
|
+
|
|
206
|
+
Option labels in the dropdown are shown on one line and cut off with an ellipsis when
|
|
207
|
+
they do not fit. Hovering a cut-off label shows the full text in a tooltip anchored to
|
|
208
|
+
that label, and labels that fit are left without one. Selected item labels wrap onto
|
|
209
|
+
multiple lines instead of being cut off, so they only get a tooltip if surrounding
|
|
210
|
+
styles clip them.
|
|
211
|
+
|
|
212
|
+
The tooltip is drawn by the component rather than by the browser's native `title`, so
|
|
213
|
+
it is styled with the rest of the component, appears after a short delay instead of
|
|
214
|
+
about a second, and flips above or below the label to stay inside the component frame.
|
|
215
|
+
It disappears as soon as the pointer leaves, the list scrolls, or the row it points at
|
|
216
|
+
changes. Screen readers are unaffected either way: a clipped label is cut off visually
|
|
217
|
+
but its full text is always in the DOM.
|
|
218
|
+
|
|
219
|
+
`tooltip_color` restyles it, using the same color form as everything above. A string
|
|
220
|
+
sets the background and the text follows for contrast; a mapping may also set `text`
|
|
221
|
+
and `border`. Because the component renders inside an iframe, this argument is the only
|
|
222
|
+
way to change the tooltip: styles from the surrounding app cannot reach it.
|
|
223
|
+
|
|
224
|
+
```python
|
|
225
|
+
tooltip_color="#1e3a8a" # white text follows
|
|
226
|
+
tooltip_color={"background": "#ffffff", "text": "#111827", "border": "#d1d5db"}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A light tooltip needs a `border` to separate it from the content behind it. The arrow
|
|
230
|
+
picks up both the background and the border, so it stays attached either way.
|
|
231
|
+
|
|
232
|
+
`examples/tooltip_labels.py` shows the same options in a narrow list and a wide one,
|
|
233
|
+
so the conditional behavior is visible side by side.
|
|
234
|
+
|
|
235
|
+
## API Suggestions
|
|
236
|
+
|
|
237
|
+
Set `suggestions_api_url` to request suggestions as the user types. Static `options` that match the query are shown first, followed by API results. Duplicate `value` entries prefer the static option.
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
selected = sortable_multiselect(
|
|
241
|
+
"Repositories",
|
|
242
|
+
options=[{"label": "Streamlit", "value": "streamlit/streamlit"}],
|
|
243
|
+
suggestions_api_url="https://api.example.com/repositories",
|
|
244
|
+
suggestions_query_param="query",
|
|
245
|
+
suggestions_response_path="data.items",
|
|
246
|
+
suggestions_label_path="name",
|
|
247
|
+
suggestions_value_path="full_name",
|
|
248
|
+
suggestions_icon_url_path="owner.avatar_url",
|
|
249
|
+
suggestions_headers={"X-Public-Client": "streamlit-app"},
|
|
250
|
+
suggestions_min_chars=2,
|
|
251
|
+
suggestions_debounce_ms=300,
|
|
252
|
+
suggestions_loading_message="Searching repositories...",
|
|
253
|
+
suggestions_error_message="Repository search is unavailable",
|
|
254
|
+
)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The example above accepts a response such as:
|
|
258
|
+
|
|
259
|
+
```json
|
|
260
|
+
{
|
|
261
|
+
"data": {
|
|
262
|
+
"items": [
|
|
263
|
+
{
|
|
264
|
+
"name": "streamlit-sortable-multiselect",
|
|
265
|
+
"full_name": "example/streamlit-sortable-multiselect",
|
|
266
|
+
"owner": {
|
|
267
|
+
"avatar_url": "https://example.com/avatar.png"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Requests are made directly from the component iframe, so the endpoint must allow browser requests with CORS. Values in `suggestions_headers` are exposed to browser users. Do not pass API secrets, private bearer tokens, or other credentials. Use a server-side proxy when authentication must remain private.
|
|
276
|
+
|
|
277
|
+
The component sends a `GET` request after the trimmed input reaches
|
|
278
|
+
`suggestions_min_chars` and remains unchanged for `suggestions_debounce_ms`.
|
|
279
|
+
Changing the input cancels the previous in-flight request. Set `options=[]` to
|
|
280
|
+
use only API results. Selected API values remain selected when a later query
|
|
281
|
+
returns different suggestions.
|
|
282
|
+
|
|
283
|
+
Build the frontend before packaging or using release mode:
|
|
284
|
+
|
|
285
|
+
```bash
|
|
286
|
+
cd streamlit_sortable_multiselect/frontend
|
|
287
|
+
npm run build
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Run the example app:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
streamlit run examples/basic.py
|
|
294
|
+
streamlit run examples/color_by_id.py
|
|
295
|
+
streamlit run examples/tooltip_labels.py
|
|
296
|
+
streamlit run examples/api_suggestions.py
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
`api_suggestions.py` starts a CORS-enabled sample API on a local random port, so
|
|
300
|
+
it can be tried without a separate API process. This embedded server is intended
|
|
301
|
+
for local development; use a separately deployed HTTPS API in remote deployments.
|
|
302
|
+
The Streamlit page displays the address and port selected for the API. Test the
|
|
303
|
+
endpoint from the same machine with:
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
curl "http://127.0.0.1:<displayed-port>/suggest?q=py"
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Pass a fixed API port after `--` when needed:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
streamlit run examples/api_suggestions.py -- --api-port 8765
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
The embedded API binds to `127.0.0.1`. Because suggestions are fetched by the
|
|
316
|
+
browser, it works only when the browser and Streamlit run on the same machine.
|
|
317
|
+
For Docker, another computer, or a hosted Streamlit app, configure
|
|
318
|
+
`suggestions_api_url` with a browser-accessible HTTPS endpoint instead.
|
|
319
|
+
|
|
320
|
+
## Release
|
|
321
|
+
|
|
322
|
+
Build and check the distribution files:
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
cd streamlit_sortable_multiselect/frontend
|
|
326
|
+
npm install
|
|
327
|
+
npm run build
|
|
328
|
+
cd ../..
|
|
329
|
+
python -m pip install -e ".[dev]"
|
|
330
|
+
python -m build
|
|
331
|
+
python -m twine check dist/*
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Upload to PyPI with an API token:
|
|
335
|
+
|
|
336
|
+
```bash
|
|
337
|
+
python -m twine upload dist/*
|
|
338
|
+
```
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# streamlit-sortable-multiselect
|
|
2
|
+
|
|
3
|
+
A Streamlit custom component for searching, selecting, and reordering multiple string values.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
python -m pip install streamlit-sortable-multiselect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Upgrade an existing installation:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m pip install --upgrade streamlit-sortable-multiselect
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For local development:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
python -m pip install -e ".[dev]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For frontend development:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
cd streamlit_sortable_multiselect/frontend
|
|
27
|
+
npm install
|
|
28
|
+
npm run dev
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import streamlit as st
|
|
35
|
+
from streamlit_sortable_multiselect import sortable_multiselect
|
|
36
|
+
|
|
37
|
+
selected = sortable_multiselect(
|
|
38
|
+
"Favorite frameworks",
|
|
39
|
+
options=[
|
|
40
|
+
{"label": "Streamlit", "value": "streamlit", "icon_url": "https://streamlit.io/images/brand/streamlit-mark-color.png"},
|
|
41
|
+
{"label": "FastAPI", "value": "fastapi", "icon_url": "https://fastapi.tiangolo.com/img/favicon.png"},
|
|
42
|
+
{"label": "Django", "value": "django"},
|
|
43
|
+
{"label": "Flask", "value": "flask"},
|
|
44
|
+
],
|
|
45
|
+
default=["streamlit"],
|
|
46
|
+
placeholder="Search frameworks...",
|
|
47
|
+
show_move_buttons=True,
|
|
48
|
+
show_numbers=True,
|
|
49
|
+
base_color="#eef2ff",
|
|
50
|
+
order_colors={1: "#fee2e2", 2: "#dcfce7"},
|
|
51
|
+
max_selections=3,
|
|
52
|
+
max_selections_placeholder="Choose up to 3 frameworks",
|
|
53
|
+
empty_message="No frameworks selected",
|
|
54
|
+
no_options_placeholder="All frameworks selected",
|
|
55
|
+
selected_position="top",
|
|
56
|
+
icon_size=24,
|
|
57
|
+
options_max_height=260,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
st.write(selected)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Settings
|
|
64
|
+
|
|
65
|
+
`sortable_multiselect` returns the selected option values as a `list[str]` in the current display order.
|
|
66
|
+
|
|
67
|
+
| Argument | Type | Default | Description |
|
|
68
|
+
| --- | --- | --- | --- |
|
|
69
|
+
| `label` | `str` | required | Label displayed above the component. |
|
|
70
|
+
| `options` | `Sequence[str \| Mapping[str, Any]]` | required | Available options. Each option can be a plain string, or a dictionary with `label`, `value`, and optional `icon_url`. Option values must be unique. |
|
|
71
|
+
| `default` | `Sequence[str] \| None` | `None` | Initially selected values, in the initial order. Values must exist in `options` and must not contain duplicates. |
|
|
72
|
+
| `placeholder` | `str` | `"Select..."` | Placeholder shown in the search/add input when options are available. |
|
|
73
|
+
| `disabled` | `bool` | `False` | Disables searching, selecting, removing, dragging, and move buttons. |
|
|
74
|
+
| `show_move_buttons` | `bool` | `True` | Shows up/down buttons on selected items. Drag sorting remains available unless `disabled=True`. |
|
|
75
|
+
| `show_numbers` | `bool` | `False` | Shows 1-based position numbers before selected item labels. |
|
|
76
|
+
| `base_color` | `str \| Mapping[str, str] \| None` | `None` | Fallback color for every selected item. See [Colors](#colors). |
|
|
77
|
+
| `order_colors` | `Mapping[int, str \| Mapping[str, str]] \| None` | `None` | Per-position colors. Positive keys count from the top (`1` is first), negative keys count from the bottom (`-1` is last), for example `{1: "#fee2e2", -1: "#dcfce7"}`. |
|
|
78
|
+
| `value_colors` | `Mapping[str, str \| Mapping[str, str]] \| None` | `None` | Per-value colors keyed by option value, for example `{"python": "#3776ab"}`. These follow an item as it is reordered. |
|
|
79
|
+
| `color_palette` | `Sequence[str \| Mapping[str, str]] \| None` | `None` | Colors cycled across selected positions. Position 1 uses the first entry and the palette repeats for longer selections. |
|
|
80
|
+
| `color_priority` | `Sequence[str] \| None` | `None` | Ranking of the color sources `"value"`, `"option"`, `"order"`, `"palette"`, and `"base"`. Omitted sources keep their default rank. |
|
|
81
|
+
| `tooltip_color` | `str \| Mapping[str, str] \| None` | `None` | Color of the tooltip shown for labels that do not fit. `None` uses the built-in dark tooltip. See [Labels](#labels). |
|
|
82
|
+
| `max_selections` | `int \| None` | `None` | Maximum number of selected items. `None` means no limit. Use `0` to prevent any selections. |
|
|
83
|
+
| `max_selections_placeholder` | `str` | `"Selection limit reached"` | Placeholder shown when `max_selections` has been reached. This takes precedence over `placeholder` and `no_options_placeholder`. |
|
|
84
|
+
| `empty_message` | `str` | `"No items selected"` | Message shown where the selected list appears when no items are selected. |
|
|
85
|
+
| `no_options_placeholder` | `str` | `"No more options"` | Placeholder shown when every option is already selected and there are no more options to add. |
|
|
86
|
+
| `selected_position` | `str` | `"bottom"` | Position of the selected item list relative to the search/add input. Use `"bottom"` or `"top"`. |
|
|
87
|
+
| `icon_size` | `int` | `20` | Icon display size in pixels for `icon_url` images. Images are displayed inside a square area while preserving their aspect ratio. |
|
|
88
|
+
| `options_max_height` | `int` | `190` | Maximum height in pixels for the available options dropdown. |
|
|
89
|
+
| `suggestions_api_url` | `str \| None` | `None` | Absolute HTTP(S) endpoint used to fetch suggestions in the browser. `None` disables API suggestions. |
|
|
90
|
+
| `suggestions_query_param` | `str` | `"q"` | Query parameter name used to send the current search text. |
|
|
91
|
+
| `suggestions_response_path` | `str` | `""` | Dot-separated path to the suggestions array in the JSON response. Empty means the response root. |
|
|
92
|
+
| `suggestions_label_path` | `str` | `"label"` | Dot-separated path to each suggestion's display label. |
|
|
93
|
+
| `suggestions_value_path` | `str` | `"value"` | Dot-separated path to each suggestion's returned value. |
|
|
94
|
+
| `suggestions_icon_url_path` | `str \| None` | `"icon_url"` | Optional dot-separated path to each suggestion's icon URL. `None` disables API icons. |
|
|
95
|
+
| `suggestions_color_path` | `str \| None` | `None` | Optional dot-separated path to each suggestion's color. The value may be a CSS color string or a mapping of color fields. `None` disables API colors. |
|
|
96
|
+
| `suggestions_headers` | `Mapping[str, str] \| None` | `None` | HTTP headers sent with suggestions requests. Header values are visible to browser users and must not contain secrets. |
|
|
97
|
+
| `suggestions_min_chars` | `int` | `1` | Minimum trimmed query length before requesting suggestions. Use `0` to allow an empty query. |
|
|
98
|
+
| `suggestions_debounce_ms` | `int` | `300` | Delay in milliseconds between the latest input and the API request. |
|
|
99
|
+
| `suggestions_loading_message` | `str` | `"Loading suggestions..."` | Message shown while an API request is in progress. |
|
|
100
|
+
| `suggestions_error_message` | `str` | `"Failed to load suggestions"` | Message shown when the request or response cannot be processed. |
|
|
101
|
+
| `key` | `str \| None` | `None` | Optional Streamlit component key. Use this when rendering multiple sortable multiselects. |
|
|
102
|
+
|
|
103
|
+
Option dictionaries use this shape:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
{
|
|
107
|
+
"label": "Python",
|
|
108
|
+
"value": "python",
|
|
109
|
+
"icon_url": "https://www.python.org/static/favicon.ico",
|
|
110
|
+
"color": "#3776ab",
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`icon_url` and `color` may be omitted. The returned value is always the `value`, not the display `label`.
|
|
115
|
+
|
|
116
|
+
## Colors
|
|
117
|
+
|
|
118
|
+
Anywhere a color is accepted, pass either a CSS color string or a mapping that sets
|
|
119
|
+
any subset of `background`, `text`, and `border`:
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
"#fee2e2"
|
|
123
|
+
{"background": "#111827", "text": "#facc15", "border": "#f74c00"}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A string sets the background. When `text` is not set, the component picks black or
|
|
127
|
+
white for readability against the resolved background.
|
|
128
|
+
|
|
129
|
+
Colors come from five sources. Each source contributes a color, and the highest
|
|
130
|
+
ranked source that sets a field wins that field, so a value color that sets only
|
|
131
|
+
`text` still inherits the background from `base_color`:
|
|
132
|
+
|
|
133
|
+
| Source | Set with | Keyed by |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `value` | `value_colors` | Option value. Follows the item as it is reordered. |
|
|
136
|
+
| `option` | `color` in an option dictionary, or `suggestions_color_path` | The option itself. |
|
|
137
|
+
| `order` | `order_colors` | Selected position. `1` is first, `-1` is last. |
|
|
138
|
+
| `palette` | `color_palette` | Selected position, cycling through the palette. |
|
|
139
|
+
| `base` | `base_color` | Every selected item. |
|
|
140
|
+
|
|
141
|
+
The default ranking is the table order. Reorder it with `color_priority`; sources you
|
|
142
|
+
leave out keep their default rank, so `color_priority=["order"]` only promotes
|
|
143
|
+
position colors above everything else.
|
|
144
|
+
|
|
145
|
+
When a positive and a negative `order_colors` key address the same slot, such as `1`
|
|
146
|
+
and `-3` in a three item list, the positive key wins. Every color must set at least
|
|
147
|
+
one field: `None` and `{}` are rejected rather than ignored, so a mistyped color
|
|
148
|
+
surfaces as an error instead of silently doing nothing.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
selected = sortable_multiselect(
|
|
152
|
+
"Podium",
|
|
153
|
+
options=[
|
|
154
|
+
{"label": "Python", "value": "python", "color": "#3776ab"},
|
|
155
|
+
{"label": "Rust", "value": "rust"},
|
|
156
|
+
{"label": "Go", "value": "go"},
|
|
157
|
+
],
|
|
158
|
+
default=["python", "rust", "go"],
|
|
159
|
+
# Gold, silver, and bronze by position, ranked above each option's own color.
|
|
160
|
+
order_colors={
|
|
161
|
+
1: {"background": "#fde68a", "border": "#f59e0b"},
|
|
162
|
+
2: {"background": "#e5e7eb", "border": "#9ca3af"},
|
|
163
|
+
-1: {"background": "#fed7aa", "border": "#ea580c"},
|
|
164
|
+
},
|
|
165
|
+
color_priority=["order"],
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Options that resolve to a color show a small swatch in the options dropdown.
|
|
170
|
+
|
|
171
|
+
Because an option's `value` is its id, `value_colors` binds a color to an item rather
|
|
172
|
+
than to a slot. `examples/color_by_id.py` puts both side by side: two lists holding the
|
|
173
|
+
same items, one colored by position and one colored by id. Dragging an item in each
|
|
174
|
+
shows position colors staying with the slot while id colors travel with the item.
|
|
175
|
+
|
|
176
|
+
## Labels
|
|
177
|
+
|
|
178
|
+
Option labels in the dropdown are shown on one line and cut off with an ellipsis when
|
|
179
|
+
they do not fit. Hovering a cut-off label shows the full text in a tooltip anchored to
|
|
180
|
+
that label, and labels that fit are left without one. Selected item labels wrap onto
|
|
181
|
+
multiple lines instead of being cut off, so they only get a tooltip if surrounding
|
|
182
|
+
styles clip them.
|
|
183
|
+
|
|
184
|
+
The tooltip is drawn by the component rather than by the browser's native `title`, so
|
|
185
|
+
it is styled with the rest of the component, appears after a short delay instead of
|
|
186
|
+
about a second, and flips above or below the label to stay inside the component frame.
|
|
187
|
+
It disappears as soon as the pointer leaves, the list scrolls, or the row it points at
|
|
188
|
+
changes. Screen readers are unaffected either way: a clipped label is cut off visually
|
|
189
|
+
but its full text is always in the DOM.
|
|
190
|
+
|
|
191
|
+
`tooltip_color` restyles it, using the same color form as everything above. A string
|
|
192
|
+
sets the background and the text follows for contrast; a mapping may also set `text`
|
|
193
|
+
and `border`. Because the component renders inside an iframe, this argument is the only
|
|
194
|
+
way to change the tooltip: styles from the surrounding app cannot reach it.
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
tooltip_color="#1e3a8a" # white text follows
|
|
198
|
+
tooltip_color={"background": "#ffffff", "text": "#111827", "border": "#d1d5db"}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
A light tooltip needs a `border` to separate it from the content behind it. The arrow
|
|
202
|
+
picks up both the background and the border, so it stays attached either way.
|
|
203
|
+
|
|
204
|
+
`examples/tooltip_labels.py` shows the same options in a narrow list and a wide one,
|
|
205
|
+
so the conditional behavior is visible side by side.
|
|
206
|
+
|
|
207
|
+
## API Suggestions
|
|
208
|
+
|
|
209
|
+
Set `suggestions_api_url` to request suggestions as the user types. Static `options` that match the query are shown first, followed by API results. Duplicate `value` entries prefer the static option.
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
selected = sortable_multiselect(
|
|
213
|
+
"Repositories",
|
|
214
|
+
options=[{"label": "Streamlit", "value": "streamlit/streamlit"}],
|
|
215
|
+
suggestions_api_url="https://api.example.com/repositories",
|
|
216
|
+
suggestions_query_param="query",
|
|
217
|
+
suggestions_response_path="data.items",
|
|
218
|
+
suggestions_label_path="name",
|
|
219
|
+
suggestions_value_path="full_name",
|
|
220
|
+
suggestions_icon_url_path="owner.avatar_url",
|
|
221
|
+
suggestions_headers={"X-Public-Client": "streamlit-app"},
|
|
222
|
+
suggestions_min_chars=2,
|
|
223
|
+
suggestions_debounce_ms=300,
|
|
224
|
+
suggestions_loading_message="Searching repositories...",
|
|
225
|
+
suggestions_error_message="Repository search is unavailable",
|
|
226
|
+
)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The example above accepts a response such as:
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"data": {
|
|
234
|
+
"items": [
|
|
235
|
+
{
|
|
236
|
+
"name": "streamlit-sortable-multiselect",
|
|
237
|
+
"full_name": "example/streamlit-sortable-multiselect",
|
|
238
|
+
"owner": {
|
|
239
|
+
"avatar_url": "https://example.com/avatar.png"
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Requests are made directly from the component iframe, so the endpoint must allow browser requests with CORS. Values in `suggestions_headers` are exposed to browser users. Do not pass API secrets, private bearer tokens, or other credentials. Use a server-side proxy when authentication must remain private.
|
|
248
|
+
|
|
249
|
+
The component sends a `GET` request after the trimmed input reaches
|
|
250
|
+
`suggestions_min_chars` and remains unchanged for `suggestions_debounce_ms`.
|
|
251
|
+
Changing the input cancels the previous in-flight request. Set `options=[]` to
|
|
252
|
+
use only API results. Selected API values remain selected when a later query
|
|
253
|
+
returns different suggestions.
|
|
254
|
+
|
|
255
|
+
Build the frontend before packaging or using release mode:
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
cd streamlit_sortable_multiselect/frontend
|
|
259
|
+
npm run build
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Run the example app:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
streamlit run examples/basic.py
|
|
266
|
+
streamlit run examples/color_by_id.py
|
|
267
|
+
streamlit run examples/tooltip_labels.py
|
|
268
|
+
streamlit run examples/api_suggestions.py
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
`api_suggestions.py` starts a CORS-enabled sample API on a local random port, so
|
|
272
|
+
it can be tried without a separate API process. This embedded server is intended
|
|
273
|
+
for local development; use a separately deployed HTTPS API in remote deployments.
|
|
274
|
+
The Streamlit page displays the address and port selected for the API. Test the
|
|
275
|
+
endpoint from the same machine with:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
curl "http://127.0.0.1:<displayed-port>/suggest?q=py"
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Pass a fixed API port after `--` when needed:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
streamlit run examples/api_suggestions.py -- --api-port 8765
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The embedded API binds to `127.0.0.1`. Because suggestions are fetched by the
|
|
288
|
+
browser, it works only when the browser and Streamlit run on the same machine.
|
|
289
|
+
For Docker, another computer, or a hosted Streamlit app, configure
|
|
290
|
+
`suggestions_api_url` with a browser-accessible HTTPS endpoint instead.
|
|
291
|
+
|
|
292
|
+
## Release
|
|
293
|
+
|
|
294
|
+
Build and check the distribution files:
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
cd streamlit_sortable_multiselect/frontend
|
|
298
|
+
npm install
|
|
299
|
+
npm run build
|
|
300
|
+
cd ../..
|
|
301
|
+
python -m pip install -e ".[dev]"
|
|
302
|
+
python -m build
|
|
303
|
+
python -m twine check dist/*
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Upload to PyPI with an API token:
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
python -m twine upload dist/*
|
|
310
|
+
```
|
{streamlit_sortable_multiselect-0.7.4 → streamlit_sortable_multiselect-0.7.6}/pyproject.toml
RENAMED
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "streamlit-sortable-multiselect"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.6"
|
|
8
8
|
description = "A Streamlit custom component for selecting and reordering multiple string values."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|