streamlit-sortable-multiselect 0.7.5__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.5/streamlit_sortable_multiselect.egg-info → streamlit_sortable_multiselect-0.7.6}/PKG-INFO +108 -5
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/README.md +107 -4
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/pyproject.toml +1 -1
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect/__init__.py +178 -23
- 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.5 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect/frontend/build/index.html +2 -2
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6/streamlit_sortable_multiselect.egg-info}/PKG-INFO +108 -5
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/SOURCES.txt +2 -2
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/tests/test_wrapper.py +136 -4
- streamlit_sortable_multiselect-0.7.5/streamlit_sortable_multiselect/frontend/build/assets/index-DGpJ50qI.css +0 -1
- streamlit_sortable_multiselect-0.7.5/streamlit_sortable_multiselect/frontend/build/assets/index-RH2LvVA2.js +0 -67
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/LICENSE +0 -0
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/MANIFEST.in +0 -0
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/setup.cfg +0 -0
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/dependency_links.txt +0 -0
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/requires.txt +0 -0
- {streamlit_sortable_multiselect-0.7.5 → streamlit_sortable_multiselect-0.7.6}/streamlit_sortable_multiselect.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: streamlit-sortable-multiselect
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
4
4
|
Summary: A Streamlit custom component for selecting and reordering multiple string values.
|
|
5
5
|
Author: Kentaro Aoki
|
|
6
6
|
License-Expression: MIT
|
|
@@ -101,8 +101,12 @@ st.write(selected)
|
|
|
101
101
|
| `disabled` | `bool` | `False` | Disables searching, selecting, removing, dragging, and move buttons. |
|
|
102
102
|
| `show_move_buttons` | `bool` | `True` | Shows up/down buttons on selected items. Drag sorting remains available unless `disabled=True`. |
|
|
103
103
|
| `show_numbers` | `bool` | `False` | Shows 1-based position numbers before selected item labels. |
|
|
104
|
-
| `base_color` | `str \| None` | `None` |
|
|
105
|
-
| `order_colors` | `Mapping[int, str] \| None` | `None` | Per-position
|
|
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). |
|
|
106
110
|
| `max_selections` | `int \| None` | `None` | Maximum number of selected items. `None` means no limit. Use `0` to prevent any selections. |
|
|
107
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`. |
|
|
108
112
|
| `empty_message` | `str` | `"No items selected"` | Message shown where the selected list appears when no items are selected. |
|
|
@@ -116,6 +120,7 @@ st.write(selected)
|
|
|
116
120
|
| `suggestions_label_path` | `str` | `"label"` | Dot-separated path to each suggestion's display label. |
|
|
117
121
|
| `suggestions_value_path` | `str` | `"value"` | Dot-separated path to each suggestion's returned value. |
|
|
118
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. |
|
|
119
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. |
|
|
120
125
|
| `suggestions_min_chars` | `int` | `1` | Minimum trimmed query length before requesting suggestions. Use `0` to allow an empty query. |
|
|
121
126
|
| `suggestions_debounce_ms` | `int` | `300` | Delay in milliseconds between the latest input and the API request. |
|
|
@@ -126,10 +131,106 @@ st.write(selected)
|
|
|
126
131
|
Option dictionaries use this shape:
|
|
127
132
|
|
|
128
133
|
```python
|
|
129
|
-
{
|
|
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
|
+
)
|
|
130
195
|
```
|
|
131
196
|
|
|
132
|
-
|
|
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.
|
|
133
234
|
|
|
134
235
|
## API Suggestions
|
|
135
236
|
|
|
@@ -190,6 +291,8 @@ Run the example app:
|
|
|
190
291
|
|
|
191
292
|
```bash
|
|
192
293
|
streamlit run examples/basic.py
|
|
294
|
+
streamlit run examples/color_by_id.py
|
|
295
|
+
streamlit run examples/tooltip_labels.py
|
|
193
296
|
streamlit run examples/api_suggestions.py
|
|
194
297
|
```
|
|
195
298
|
|
|
@@ -73,8 +73,12 @@ st.write(selected)
|
|
|
73
73
|
| `disabled` | `bool` | `False` | Disables searching, selecting, removing, dragging, and move buttons. |
|
|
74
74
|
| `show_move_buttons` | `bool` | `True` | Shows up/down buttons on selected items. Drag sorting remains available unless `disabled=True`. |
|
|
75
75
|
| `show_numbers` | `bool` | `False` | Shows 1-based position numbers before selected item labels. |
|
|
76
|
-
| `base_color` | `str \| None` | `None` |
|
|
77
|
-
| `order_colors` | `Mapping[int, str] \| None` | `None` | Per-position
|
|
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). |
|
|
78
82
|
| `max_selections` | `int \| None` | `None` | Maximum number of selected items. `None` means no limit. Use `0` to prevent any selections. |
|
|
79
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`. |
|
|
80
84
|
| `empty_message` | `str` | `"No items selected"` | Message shown where the selected list appears when no items are selected. |
|
|
@@ -88,6 +92,7 @@ st.write(selected)
|
|
|
88
92
|
| `suggestions_label_path` | `str` | `"label"` | Dot-separated path to each suggestion's display label. |
|
|
89
93
|
| `suggestions_value_path` | `str` | `"value"` | Dot-separated path to each suggestion's returned value. |
|
|
90
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. |
|
|
91
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. |
|
|
92
97
|
| `suggestions_min_chars` | `int` | `1` | Minimum trimmed query length before requesting suggestions. Use `0` to allow an empty query. |
|
|
93
98
|
| `suggestions_debounce_ms` | `int` | `300` | Delay in milliseconds between the latest input and the API request. |
|
|
@@ -98,10 +103,106 @@ st.write(selected)
|
|
|
98
103
|
Option dictionaries use this shape:
|
|
99
104
|
|
|
100
105
|
```python
|
|
101
|
-
{
|
|
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
|
+
)
|
|
102
167
|
```
|
|
103
168
|
|
|
104
|
-
|
|
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.
|
|
105
206
|
|
|
106
207
|
## API Suggestions
|
|
107
208
|
|
|
@@ -162,6 +263,8 @@ Run the example app:
|
|
|
162
263
|
|
|
163
264
|
```bash
|
|
164
265
|
streamlit run examples/basic.py
|
|
266
|
+
streamlit run examples/color_by_id.py
|
|
267
|
+
streamlit run examples/tooltip_labels.py
|
|
165
268
|
streamlit run examples/api_suggestions.py
|
|
166
269
|
```
|
|
167
270
|
|
{streamlit_sortable_multiselect-0.7.5 → 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"
|
|
@@ -4,18 +4,29 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any, Iterable, Mapping, Sequence, cast
|
|
7
|
+
from typing import Any, Iterable, Mapping, Sequence, Union, cast
|
|
8
8
|
from urllib.parse import urlparse
|
|
9
9
|
|
|
10
10
|
import streamlit.components.v1 as components
|
|
11
11
|
|
|
12
|
-
__version__ = "0.7.
|
|
13
|
-
__all__ = ["sortable_multiselect"]
|
|
12
|
+
__version__ = "0.7.6"
|
|
13
|
+
__all__ = ["COLOR_FIELDS", "COLOR_SOURCES", "ColorValue", "sortable_multiselect"]
|
|
14
14
|
|
|
15
15
|
_COMPONENT_NAME = "streamlit_sortable_multiselect"
|
|
16
16
|
_DEV_SERVER_URL = "http://localhost:5173"
|
|
17
17
|
_RELEASE = os.environ.get("STREAMLIT_SORTABLE_MULTISELECT_DEV") != "1"
|
|
18
18
|
|
|
19
|
+
#: A color is either a CSS color string (used as the background) or a mapping
|
|
20
|
+
#: that sets any subset of the fields in :data:`COLOR_FIELDS`.
|
|
21
|
+
ColorValue = Union[str, Mapping[str, str]]
|
|
22
|
+
|
|
23
|
+
#: Styleable parts of a selected item.
|
|
24
|
+
COLOR_FIELDS = ("background", "text", "border")
|
|
25
|
+
|
|
26
|
+
#: Color sources, in default precedence order. Each source contributes a color
|
|
27
|
+
#: spec, and the highest-priority source that sets a field wins that field.
|
|
28
|
+
COLOR_SOURCES = ("value", "option", "order", "palette", "base")
|
|
29
|
+
|
|
19
30
|
|
|
20
31
|
def _declare_component():
|
|
21
32
|
if _RELEASE:
|
|
@@ -40,11 +51,52 @@ def _validate_string_sequence(name: str, values: Sequence[str] | None) -> list[s
|
|
|
40
51
|
return result
|
|
41
52
|
|
|
42
53
|
|
|
43
|
-
def
|
|
54
|
+
def _normalize_color(name: str, color: ColorValue | None) -> dict[str, str] | None:
|
|
55
|
+
"""Return a color as a field mapping, or None when nothing is set."""
|
|
56
|
+
if color is None:
|
|
57
|
+
return None
|
|
58
|
+
if isinstance(color, str):
|
|
59
|
+
if not color:
|
|
60
|
+
raise ValueError(f"{name} must not be an empty color string.")
|
|
61
|
+
return {"background": color}
|
|
62
|
+
if not isinstance(color, Mapping):
|
|
63
|
+
raise TypeError(
|
|
64
|
+
f"{name} must be a CSS color string or a mapping with any of: "
|
|
65
|
+
f"{', '.join(COLOR_FIELDS)}."
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
spec: dict[str, str] = {}
|
|
69
|
+
for field, field_value in color.items():
|
|
70
|
+
if field not in COLOR_FIELDS:
|
|
71
|
+
raise ValueError(
|
|
72
|
+
f"{name} contains an unknown color field {field!r}. "
|
|
73
|
+
f"Use any of: {', '.join(COLOR_FIELDS)}."
|
|
74
|
+
)
|
|
75
|
+
if not isinstance(field_value, str):
|
|
76
|
+
raise TypeError(f"{name} color fields must be strings.")
|
|
77
|
+
if not field_value:
|
|
78
|
+
raise ValueError(f"{name} color fields must not be empty.")
|
|
79
|
+
spec[field] = field_value
|
|
80
|
+
return spec or None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _require_color(name: str, color: ColorValue | None) -> dict[str, str]:
|
|
84
|
+
"""Return a color that must actually set something."""
|
|
85
|
+
if color is None:
|
|
86
|
+
raise TypeError(f"{name} must be a color, not None.")
|
|
87
|
+
spec = _normalize_color(name, color)
|
|
88
|
+
if spec is None:
|
|
89
|
+
raise ValueError(f"{name} must set at least one of: {', '.join(COLOR_FIELDS)}.")
|
|
90
|
+
return spec
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _normalize_options(
|
|
94
|
+
options: Sequence[str | Mapping[str, Any]],
|
|
95
|
+
) -> list[dict[str, Any]]:
|
|
44
96
|
if isinstance(options, str) or not isinstance(options, Iterable):
|
|
45
97
|
raise TypeError("options must be a sequence of strings or option dictionaries.")
|
|
46
98
|
|
|
47
|
-
normalized_options: list[dict[str,
|
|
99
|
+
normalized_options: list[dict[str, Any]] = []
|
|
48
100
|
for option in options:
|
|
49
101
|
if isinstance(option, str):
|
|
50
102
|
normalized_options.append({"label": option, "value": option, "icon_url": None})
|
|
@@ -63,26 +115,94 @@ def _normalize_options(options: Sequence[str | Mapping[str, Any]]) -> list[dict[
|
|
|
63
115
|
if icon_url is not None and not isinstance(icon_url, str):
|
|
64
116
|
raise TypeError("option icon_url must be a string or None.")
|
|
65
117
|
|
|
66
|
-
|
|
118
|
+
normalized_option: dict[str, Any] = {
|
|
119
|
+
"label": label,
|
|
120
|
+
"value": value,
|
|
121
|
+
"icon_url": icon_url,
|
|
122
|
+
}
|
|
123
|
+
# Omitted rather than sent as null: with tens of thousands of options this
|
|
124
|
+
# key alone is a fifth of the payload the browser has to receive.
|
|
125
|
+
if "color" in option:
|
|
126
|
+
normalized_option["color"] = _require_color(
|
|
127
|
+
f"option color for {value!r}", option["color"]
|
|
128
|
+
)
|
|
129
|
+
normalized_options.append(normalized_option)
|
|
67
130
|
|
|
68
131
|
return normalized_options
|
|
69
132
|
|
|
70
133
|
|
|
71
|
-
def _validate_order_colors(
|
|
134
|
+
def _validate_order_colors(
|
|
135
|
+
order_colors: Mapping[int, ColorValue] | None,
|
|
136
|
+
) -> dict[int, dict[str, str]]:
|
|
72
137
|
if order_colors is None:
|
|
73
138
|
return {}
|
|
74
139
|
if not isinstance(order_colors, Mapping):
|
|
75
|
-
raise TypeError("order_colors must be a mapping of
|
|
140
|
+
raise TypeError("order_colors must be a mapping of positions to colors.")
|
|
76
141
|
|
|
77
|
-
result: dict[int, str] = {}
|
|
142
|
+
result: dict[int, dict[str, str]] = {}
|
|
78
143
|
for position, color in order_colors.items():
|
|
79
|
-
if not isinstance(position, int):
|
|
144
|
+
if isinstance(position, bool) or not isinstance(position, int):
|
|
80
145
|
raise TypeError("order_colors keys must be integers.")
|
|
81
|
-
if position
|
|
82
|
-
raise ValueError(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
146
|
+
if position == 0:
|
|
147
|
+
raise ValueError(
|
|
148
|
+
"order_colors keys must be non-zero integers. "
|
|
149
|
+
"Positive keys count from the top, negative keys count from the bottom."
|
|
150
|
+
)
|
|
151
|
+
result[position] = _require_color(f"order_colors[{position}]", color)
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _validate_value_colors(
|
|
156
|
+
value_colors: Mapping[str, ColorValue] | None,
|
|
157
|
+
) -> dict[str, dict[str, str]]:
|
|
158
|
+
if value_colors is None:
|
|
159
|
+
return {}
|
|
160
|
+
if not isinstance(value_colors, Mapping):
|
|
161
|
+
raise TypeError("value_colors must be a mapping of option values to colors.")
|
|
162
|
+
|
|
163
|
+
result: dict[str, dict[str, str]] = {}
|
|
164
|
+
for value, color in value_colors.items():
|
|
165
|
+
if not isinstance(value, str):
|
|
166
|
+
raise TypeError("value_colors keys must be strings.")
|
|
167
|
+
result[value] = _require_color(f"value_colors[{value!r}]", color)
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _validate_color_palette(
|
|
172
|
+
color_palette: Sequence[ColorValue] | None,
|
|
173
|
+
) -> list[dict[str, str]]:
|
|
174
|
+
if color_palette is None:
|
|
175
|
+
return []
|
|
176
|
+
if isinstance(color_palette, (str, Mapping)) or not isinstance(color_palette, Iterable):
|
|
177
|
+
raise TypeError("color_palette must be a sequence of colors.")
|
|
178
|
+
|
|
179
|
+
return [
|
|
180
|
+
_require_color(f"color_palette[{index}]", color)
|
|
181
|
+
for index, color in enumerate(color_palette)
|
|
182
|
+
]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _validate_color_priority(color_priority: Sequence[str] | None) -> list[str]:
|
|
186
|
+
if color_priority is None:
|
|
187
|
+
return list(COLOR_SOURCES)
|
|
188
|
+
if isinstance(color_priority, str) or not isinstance(color_priority, Iterable):
|
|
189
|
+
raise TypeError("color_priority must be a sequence of color source names.")
|
|
190
|
+
|
|
191
|
+
result: list[str] = []
|
|
192
|
+
for source in color_priority:
|
|
193
|
+
if not isinstance(source, str):
|
|
194
|
+
raise TypeError("color_priority must contain only strings.")
|
|
195
|
+
if source not in COLOR_SOURCES:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"color_priority contains an unknown color source {source!r}. "
|
|
198
|
+
f"Use any of: {', '.join(COLOR_SOURCES)}."
|
|
199
|
+
)
|
|
200
|
+
if source in result:
|
|
201
|
+
raise ValueError(f"color_priority must not repeat {source!r}.")
|
|
202
|
+
result.append(source)
|
|
203
|
+
|
|
204
|
+
# Sources the caller left out still apply, ranked below the listed ones.
|
|
205
|
+
result.extend(source for source in COLOR_SOURCES if source not in result)
|
|
86
206
|
return result
|
|
87
207
|
|
|
88
208
|
|
|
@@ -173,8 +293,8 @@ def sortable_multiselect(
|
|
|
173
293
|
disabled: bool = False,
|
|
174
294
|
show_move_buttons: bool = True,
|
|
175
295
|
show_numbers: bool = False,
|
|
176
|
-
base_color:
|
|
177
|
-
order_colors: Mapping[int,
|
|
296
|
+
base_color: ColorValue | None = None,
|
|
297
|
+
order_colors: Mapping[int, ColorValue] | None = None,
|
|
178
298
|
max_selections: int | None = None,
|
|
179
299
|
max_selections_placeholder: str = "Selection limit reached",
|
|
180
300
|
empty_message: str = "No items selected",
|
|
@@ -193,6 +313,11 @@ def sortable_multiselect(
|
|
|
193
313
|
suggestions_debounce_ms: int = 300,
|
|
194
314
|
suggestions_loading_message: str = "Loading suggestions...",
|
|
195
315
|
suggestions_error_message: str = "Failed to load suggestions",
|
|
316
|
+
value_colors: Mapping[str, ColorValue] | None = None,
|
|
317
|
+
color_palette: Sequence[ColorValue] | None = None,
|
|
318
|
+
color_priority: Sequence[str] | None = None,
|
|
319
|
+
tooltip_color: ColorValue | None = None,
|
|
320
|
+
suggestions_color_path: str | None = None,
|
|
196
321
|
key: str | None = None,
|
|
197
322
|
) -> list[str]:
|
|
198
323
|
"""Select multiple string values and return them in user-defined order.
|
|
@@ -202,7 +327,8 @@ def sortable_multiselect(
|
|
|
202
327
|
label:
|
|
203
328
|
Text label rendered above the component.
|
|
204
329
|
options:
|
|
205
|
-
Available string values, or dictionaries with label, value, and optional
|
|
330
|
+
Available string values, or dictionaries with label, value, and optional
|
|
331
|
+
icon_url and color.
|
|
206
332
|
default:
|
|
207
333
|
Initially selected values, in the desired initial order.
|
|
208
334
|
placeholder:
|
|
@@ -214,9 +340,11 @@ def sortable_multiselect(
|
|
|
214
340
|
show_numbers:
|
|
215
341
|
Show 1-based numbers before selected items.
|
|
216
342
|
base_color:
|
|
217
|
-
|
|
343
|
+
Fallback color for every selected item. A CSS color string sets the background;
|
|
344
|
+
a mapping may set any of "background", "text", and "border".
|
|
218
345
|
order_colors:
|
|
219
|
-
Per-position
|
|
346
|
+
Per-position colors keyed by selected item position. Positive keys count from
|
|
347
|
+
the top (1 is first), negative keys count from the bottom (-1 is last).
|
|
220
348
|
max_selections:
|
|
221
349
|
Maximum number of selected items. None means no limit.
|
|
222
350
|
max_selections_placeholder:
|
|
@@ -253,6 +381,20 @@ def sortable_multiselect(
|
|
|
253
381
|
Text shown while suggestions are loading.
|
|
254
382
|
suggestions_error_message:
|
|
255
383
|
Text shown when suggestions cannot be loaded.
|
|
384
|
+
value_colors:
|
|
385
|
+
Per-value colors keyed by option value. These follow an item as it is reordered.
|
|
386
|
+
color_palette:
|
|
387
|
+
Colors cycled across selected positions. Position 1 uses the first entry, and
|
|
388
|
+
the palette repeats once there are more items than entries.
|
|
389
|
+
color_priority:
|
|
390
|
+
Ranking of the color sources "value", "option", "order", "palette", and "base".
|
|
391
|
+
Earlier sources win, field by field. Omitted sources keep their default rank.
|
|
392
|
+
tooltip_color:
|
|
393
|
+
Color of the tooltip shown for labels that do not fit. A string sets the
|
|
394
|
+
background and the text follows for contrast; a mapping may also set "text"
|
|
395
|
+
and "border". None uses the built-in dark tooltip.
|
|
396
|
+
suggestions_color_path:
|
|
397
|
+
Optional dot-separated path to each suggestion's color. None disables API colors.
|
|
256
398
|
key:
|
|
257
399
|
Optional Streamlit component key.
|
|
258
400
|
"""
|
|
@@ -276,13 +418,18 @@ def sortable_multiselect(
|
|
|
276
418
|
raise TypeError("show_move_buttons must be a bool.")
|
|
277
419
|
if not isinstance(show_numbers, bool):
|
|
278
420
|
raise TypeError("show_numbers must be a bool.")
|
|
279
|
-
if base_color is not None and not isinstance(base_color, str):
|
|
280
|
-
raise TypeError("base_color must be a string or None.")
|
|
281
421
|
|
|
422
|
+
base_color_value = None if base_color is None else _require_color("base_color", base_color)
|
|
423
|
+
tooltip_color_value = (
|
|
424
|
+
None if tooltip_color is None else _require_color("tooltip_color", tooltip_color)
|
|
425
|
+
)
|
|
282
426
|
option_items = _normalize_options(options)
|
|
283
427
|
option_values = [option["value"] for option in option_items]
|
|
284
428
|
default_values = _validate_string_sequence("default", default)
|
|
285
429
|
order_color_values = _validate_order_colors(order_colors)
|
|
430
|
+
value_color_values = _validate_value_colors(value_colors)
|
|
431
|
+
color_palette_values = _validate_color_palette(color_palette)
|
|
432
|
+
color_priority_values = _validate_color_priority(color_priority)
|
|
286
433
|
max_selection_count = _validate_max_selections(max_selections)
|
|
287
434
|
icon_size_value = _validate_icon_size(icon_size)
|
|
288
435
|
options_max_height_value = _validate_options_max_height(options_max_height)
|
|
@@ -301,6 +448,9 @@ def sortable_multiselect(
|
|
|
301
448
|
suggestions_icon_url_path_value = _validate_optional_non_empty_string(
|
|
302
449
|
"suggestions_icon_url_path", suggestions_icon_url_path
|
|
303
450
|
)
|
|
451
|
+
suggestions_color_path_value = _validate_optional_non_empty_string(
|
|
452
|
+
"suggestions_color_path", suggestions_color_path
|
|
453
|
+
)
|
|
304
454
|
suggestions_headers_value = _normalize_suggestions_headers(suggestions_headers)
|
|
305
455
|
suggestions_min_chars_value = _validate_non_negative_int(
|
|
306
456
|
"suggestions_min_chars", suggestions_min_chars
|
|
@@ -351,8 +501,12 @@ def sortable_multiselect(
|
|
|
351
501
|
disabled=disabled,
|
|
352
502
|
show_move_buttons=show_move_buttons,
|
|
353
503
|
show_numbers=show_numbers,
|
|
354
|
-
base_color=
|
|
504
|
+
base_color=base_color_value,
|
|
355
505
|
order_colors=order_color_values,
|
|
506
|
+
value_colors=value_color_values,
|
|
507
|
+
color_palette=color_palette_values,
|
|
508
|
+
color_priority=color_priority_values,
|
|
509
|
+
tooltip_color=tooltip_color_value,
|
|
356
510
|
max_selections=max_selection_count,
|
|
357
511
|
max_selections_placeholder=max_selections_placeholder,
|
|
358
512
|
empty_message=empty_message,
|
|
@@ -366,6 +520,7 @@ def sortable_multiselect(
|
|
|
366
520
|
suggestions_label_path=suggestions_label_path_value,
|
|
367
521
|
suggestions_value_path=suggestions_value_path_value,
|
|
368
522
|
suggestions_icon_url_path=suggestions_icon_url_path_value,
|
|
523
|
+
suggestions_color_path=suggestions_color_path_value,
|
|
369
524
|
suggestions_headers=suggestions_headers_value,
|
|
370
525
|
suggestions_min_chars=suggestions_min_chars_value,
|
|
371
526
|
suggestions_debounce_ms=suggestions_debounce_ms_value,
|