millwork 0.1.0__py3-none-any.whl
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.
- millwork/__init__.py +45 -0
- millwork/_bundle/dist/.vite/manifest.json +92 -0
- millwork/_bundle/dist/charts-inner-Cq0vWbjt.js +39 -0
- millwork/_bundle/dist/favicon.svg +15 -0
- millwork/_bundle/dist/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- millwork/_bundle/dist/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- millwork/_bundle/dist/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- millwork/_bundle/dist/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- millwork/_bundle/dist/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- millwork/_bundle/dist/icons.svg +24 -0
- millwork/_bundle/dist/index.global-cOFus1M1.js +952 -0
- millwork/_bundle/dist/index.modern-CITEfQrV.js +135 -0
- millwork/_bundle/dist/lib-3DI92Zxk.js +1 -0
- millwork/_bundle/dist/main-BoZpFGOH.js +64 -0
- millwork/_bundle/dist/main-F2tEXmC5.css +2 -0
- millwork/_bundle/dist/react-markdown-DLcbNoI3.js +14 -0
- millwork/_bundle/dist/remark-gfm-DoHjwpPg.js +16 -0
- millwork/app.py +726 -0
- millwork/auth.py +877 -0
- millwork/chat.py +397 -0
- millwork/cli.py +316 -0
- millwork/context.py +42 -0
- millwork/files.py +450 -0
- millwork/forms.py +349 -0
- millwork/manual/__init__.py +449 -0
- millwork/manual/components/Hello.jsx +24 -0
- millwork/manual/components/Voice.jsx +46 -0
- millwork/manual/pages/app/auth.md +188 -0
- millwork/manual/pages/app/pages.md +200 -0
- millwork/manual/pages/app/running.md +242 -0
- millwork/manual/pages/app/state.md +151 -0
- millwork/manual/pages/app/testing.md +147 -0
- millwork/manual/pages/app/theming.md +122 -0
- millwork/manual/pages/components/charts.md +263 -0
- millwork/manual/pages/components/chat.md +280 -0
- millwork/manual/pages/components/custom.md +148 -0
- millwork/manual/pages/components/data.md +112 -0
- millwork/manual/pages/components/feedback.md +212 -0
- millwork/manual/pages/components/files.md +194 -0
- millwork/manual/pages/components/form.md +135 -0
- millwork/manual/pages/components/inputs.md +360 -0
- millwork/manual/pages/components/layout.md +154 -0
- millwork/manual/pages/components/tasks.md +140 -0
- millwork/manual/pages/components/text.md +145 -0
- millwork/manual/pages/index.md +67 -0
- millwork/manual/public/fonts/InterVariable.woff2 +0 -0
- millwork/manual/public/fonts/LICENSE.txt +2 -0
- millwork/manual/public/mark.svg +15 -0
- millwork/manual/public/theme.json +97 -0
- millwork/nodes.py +300 -0
- millwork/protocol.py +238 -0
- millwork/proxy.py +122 -0
- millwork/py.typed +0 -0
- millwork/session.py +1422 -0
- millwork/shell.py +226 -0
- millwork/tasks.py +473 -0
- millwork/testing.py +336 -0
- millwork/theme.py +338 -0
- millwork/ui/__init__.py +725 -0
- millwork/ui/charts.py +214 -0
- millwork/ui/chat.py +97 -0
- millwork/ui/files.py +101 -0
- millwork/ui/form.py +102 -0
- millwork/ui/table.py +145 -0
- millwork/ws.py +220 -0
- millwork-0.1.0.dist-info/METADATA +131 -0
- millwork-0.1.0.dist-info/RECORD +70 -0
- millwork-0.1.0.dist-info/WHEEL +4 -0
- millwork-0.1.0.dist-info/entry_points.txt +2 -0
- millwork-0.1.0.dist-info/licenses/LICENSE +202 -0
millwork/ui/__init__.py
ADDED
|
@@ -0,0 +1,725 @@
|
|
|
1
|
+
"""The catalogue, as Python classes.
|
|
2
|
+
|
|
3
|
+
Every component is a Pydantic model whose props the bundle understands.
|
|
4
|
+
Stateful components take ``value`` (server-owned) or ``default``
|
|
5
|
+
(client-owned), never both; ``_owner`` enforces that.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import datetime as dt
|
|
11
|
+
from typing import Any, ClassVar, Literal
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|
14
|
+
|
|
15
|
+
from millwork.files import FileRef
|
|
16
|
+
from millwork.forms import FormIssues, Issue
|
|
17
|
+
from millwork.nodes import Component, Text
|
|
18
|
+
from millwork.ui.charts import AreaChart, BarChart, LineChart
|
|
19
|
+
from millwork.ui.chat import Accept, Action, Chat, Command
|
|
20
|
+
from millwork.ui.files import Download, FileCard, FileUpload, Image, Pdf
|
|
21
|
+
from millwork.ui.form import Form
|
|
22
|
+
from millwork.ui.table import Column, MenuItem, Table
|
|
23
|
+
|
|
24
|
+
Tone = Literal["default", "muted", "primary", "accent", "success", "warning", "destructive"]
|
|
25
|
+
Variant = Literal["primary", "secondary", "outline", "ghost", "link", "destructive"]
|
|
26
|
+
Size = Literal["sm", "md", "lg"]
|
|
27
|
+
Align = Literal["start", "center", "end", "stretch", "baseline"]
|
|
28
|
+
Justify = Literal["start", "center", "end", "between", "around", "evenly"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Option(BaseModel):
|
|
32
|
+
"""One choice in a Select, RadioGroup, CheckboxGroup, ToggleGroup."""
|
|
33
|
+
|
|
34
|
+
model_config = ConfigDict(extra="forbid")
|
|
35
|
+
|
|
36
|
+
value: str
|
|
37
|
+
label: str | None = None
|
|
38
|
+
description: str | None = None
|
|
39
|
+
icon: str | None = None
|
|
40
|
+
disabled: bool = False
|
|
41
|
+
group: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def as_options(values: Any) -> list[Option]:
|
|
45
|
+
"""Accept strings, ``(value, label)`` pairs, dicts or ``Option``s."""
|
|
46
|
+
out: list[Option] = []
|
|
47
|
+
for v in values or []:
|
|
48
|
+
if isinstance(v, Option):
|
|
49
|
+
out.append(v)
|
|
50
|
+
elif isinstance(v, str):
|
|
51
|
+
out.append(Option(value=v, label=v))
|
|
52
|
+
elif isinstance(v, tuple | list) and len(v) == 2:
|
|
53
|
+
out.append(Option(value=str(v[0]), label=str(v[1])))
|
|
54
|
+
elif isinstance(v, dict):
|
|
55
|
+
out.append(Option(**v))
|
|
56
|
+
else:
|
|
57
|
+
raise TypeError(f"not an option: {v!r}")
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _Owned(Component):
|
|
62
|
+
"""A component with interaction state and one owner."""
|
|
63
|
+
|
|
64
|
+
owner_pairs: ClassVar[tuple[tuple[str, str], ...]] = (
|
|
65
|
+
("value", "default"),
|
|
66
|
+
("checked", "default_checked"),
|
|
67
|
+
("open", "default_open"),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@model_validator(mode="after")
|
|
71
|
+
def _owner(self) -> _Owned:
|
|
72
|
+
for server, client in self.owner_pairs:
|
|
73
|
+
if server in self.model_fields_set and client in self.model_fields_set:
|
|
74
|
+
raise ValueError(
|
|
75
|
+
f"{type(self).__name__} takes {server} (server-owned) or {client} "
|
|
76
|
+
"(client-owned), not both"
|
|
77
|
+
)
|
|
78
|
+
return self
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------ layout
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Header(Component):
|
|
85
|
+
"""The app bar: the theme's logo, a title, links, the page's actions (children)
|
|
86
|
+
and the user menu with the theme choice and sign-out."""
|
|
87
|
+
|
|
88
|
+
type: ClassVar[str] = "Header"
|
|
89
|
+
title: str | None = None
|
|
90
|
+
logo: bool = True
|
|
91
|
+
links: list[Link] = []
|
|
92
|
+
user_menu: bool = True
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class Logo(Component):
|
|
96
|
+
"""The theme's logo, the light or dark file with the scheme; nothing when the
|
|
97
|
+
theme has none. For a page outside the app bar: a login-like page, an error page."""
|
|
98
|
+
|
|
99
|
+
type: ClassVar[str] = "Logo"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Page(Component):
|
|
103
|
+
type: ClassVar[str] = "Page"
|
|
104
|
+
title: str | None = None
|
|
105
|
+
header: Header | None = None
|
|
106
|
+
padding: bool = True
|
|
107
|
+
max_width: str | None = None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class Stack(Component):
|
|
111
|
+
type: ClassVar[str] = "Stack"
|
|
112
|
+
gap: int = 4
|
|
113
|
+
align: Align = "stretch"
|
|
114
|
+
justify: Justify = "start"
|
|
115
|
+
grow: bool = False
|
|
116
|
+
divided: bool = False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Row(Component):
|
|
120
|
+
type: ClassVar[str] = "Row"
|
|
121
|
+
gap: int = 4
|
|
122
|
+
align: Align = "center"
|
|
123
|
+
justify: Justify = "start"
|
|
124
|
+
wrap: bool = False
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Grid(Component):
|
|
128
|
+
type: ClassVar[str] = "Grid"
|
|
129
|
+
cols: int | dict[str, int] = 2
|
|
130
|
+
gap: int = 4
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class Card(Component):
|
|
134
|
+
type: ClassVar[str] = "Card"
|
|
135
|
+
title: str | None = None
|
|
136
|
+
description: str | None = None
|
|
137
|
+
footer: Component | None = None
|
|
138
|
+
padding: bool = True
|
|
139
|
+
tone: Tone | None = None
|
|
140
|
+
on_click: Any = None
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class Split(Component):
|
|
144
|
+
"""Resizable panes. Sizes are percentages; the user's drag is client-owned."""
|
|
145
|
+
|
|
146
|
+
type: ClassVar[str] = "Split"
|
|
147
|
+
sizes: list[int] | None = None
|
|
148
|
+
min_sizes: list[int] | None = None
|
|
149
|
+
direction: Literal["horizontal", "vertical"] = "horizontal"
|
|
150
|
+
# True: every pane collapses when dragged under its minimum; a list says which.
|
|
151
|
+
collapsible: bool | list[bool] = False
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Sidebar(_Owned):
|
|
155
|
+
"""A collapsible side panel. ``Page`` with a ``Sidebar`` child lays out around it."""
|
|
156
|
+
|
|
157
|
+
type: ClassVar[str] = "Sidebar"
|
|
158
|
+
side: Literal["left", "right"] = "left"
|
|
159
|
+
width: str | None = None
|
|
160
|
+
collapsible: Literal["offcanvas", "icon", "none"] = "offcanvas"
|
|
161
|
+
open: bool | None = None
|
|
162
|
+
default_open: bool | None = None
|
|
163
|
+
title: str | None = None
|
|
164
|
+
on_toggle: Any = None
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class Tabs(_Owned):
|
|
168
|
+
type: ClassVar[str] = "Tabs"
|
|
169
|
+
value: str | None = None
|
|
170
|
+
default: str | None = None
|
|
171
|
+
variant: Literal["line", "pill"] = "line"
|
|
172
|
+
on_change: Any = None
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Tab(Component):
|
|
176
|
+
type: ClassVar[str] = "Tab"
|
|
177
|
+
id: str
|
|
178
|
+
label: str
|
|
179
|
+
icon: str | None = None
|
|
180
|
+
badge: str | None = None
|
|
181
|
+
disabled: bool = False
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Accordion(_Owned):
|
|
185
|
+
type: ClassVar[str] = "Accordion"
|
|
186
|
+
multiple: bool = False
|
|
187
|
+
value: list[str] | None = None
|
|
188
|
+
default: list[str] | None = None
|
|
189
|
+
on_change: Any = None
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class AccordionItem(Component):
|
|
193
|
+
type: ClassVar[str] = "AccordionItem"
|
|
194
|
+
id: str
|
|
195
|
+
title: str
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class Collapsible(_Owned):
|
|
199
|
+
type: ClassVar[str] = "Collapsible"
|
|
200
|
+
title: str
|
|
201
|
+
open: bool | None = None
|
|
202
|
+
default_open: bool | None = None
|
|
203
|
+
on_toggle: Any = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class Dialog(Component):
|
|
207
|
+
"""``open`` is always server-owned: a dialog is state the app reasons about."""
|
|
208
|
+
|
|
209
|
+
type: ClassVar[str] = "Dialog"
|
|
210
|
+
open: bool = False
|
|
211
|
+
title: str | None = None
|
|
212
|
+
description: str | None = None
|
|
213
|
+
footer: Component | None = None
|
|
214
|
+
size: Literal["sm", "md", "lg", "xl", "full"] = "md"
|
|
215
|
+
dismissable: bool = True
|
|
216
|
+
on_close: Any = None
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class Sheet(Component):
|
|
220
|
+
type: ClassVar[str] = "Sheet"
|
|
221
|
+
open: bool = False
|
|
222
|
+
side: Literal["left", "right", "top", "bottom"] = "right"
|
|
223
|
+
title: str | None = None
|
|
224
|
+
description: str | None = None
|
|
225
|
+
on_close: Any = None
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class ScrollArea(Component):
|
|
229
|
+
type: ClassVar[str] = "ScrollArea"
|
|
230
|
+
height: str | None = None
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class Separator(Component):
|
|
234
|
+
type: ClassVar[str] = "Separator"
|
|
235
|
+
orientation: Literal["horizontal", "vertical"] = "horizontal"
|
|
236
|
+
label: str | None = None
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class Empty(Component):
|
|
240
|
+
type: ClassVar[str] = "Empty"
|
|
241
|
+
title: str
|
|
242
|
+
description: str | None = None
|
|
243
|
+
icon: str | None = None
|
|
244
|
+
action: Component | None = None
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
# ------------------------------------------------------------------ text
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class Markdown(Component):
|
|
251
|
+
type: ClassVar[str] = "Markdown"
|
|
252
|
+
content: str = ""
|
|
253
|
+
tight: bool = False
|
|
254
|
+
|
|
255
|
+
def __init__(self, content: str = "", **props: Any) -> None:
|
|
256
|
+
super().__init__(content=content, **props)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Code(Component):
|
|
260
|
+
type: ClassVar[str] = "Code"
|
|
261
|
+
content: str = ""
|
|
262
|
+
language: str | None = None
|
|
263
|
+
title: str | None = None
|
|
264
|
+
copyable: bool = True
|
|
265
|
+
wrap: bool = False
|
|
266
|
+
max_height: str | None = None
|
|
267
|
+
|
|
268
|
+
def __init__(self, content: str = "", **props: Any) -> None:
|
|
269
|
+
super().__init__(content=content, **props)
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class Badge(Component):
|
|
273
|
+
type: ClassVar[str] = "Badge"
|
|
274
|
+
label: str = ""
|
|
275
|
+
tone: Tone = "default"
|
|
276
|
+
icon: str | None = None
|
|
277
|
+
|
|
278
|
+
def __init__(self, label: str = "", **props: Any) -> None:
|
|
279
|
+
super().__init__(label=label, **props)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Kbd(Component):
|
|
283
|
+
type: ClassVar[str] = "Kbd"
|
|
284
|
+
keys: list[str]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def safe_href(value: str) -> str:
|
|
288
|
+
"""Only http(s), mailto and same-origin paths: nothing a browser would execute."""
|
|
289
|
+
v = value.strip()
|
|
290
|
+
lower = v.lower()
|
|
291
|
+
if v.startswith("/") and not v.startswith("//") and "\\" not in v:
|
|
292
|
+
return v
|
|
293
|
+
if lower.startswith(("http://", "https://", "mailto:")):
|
|
294
|
+
return v
|
|
295
|
+
raise ValueError(f"unsafe href {value!r}: use http(s), mailto or a path")
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class Link(Component):
|
|
299
|
+
type: ClassVar[str] = "Link"
|
|
300
|
+
label: str
|
|
301
|
+
href: str
|
|
302
|
+
target: Literal["_self", "_blank"] = "_self"
|
|
303
|
+
icon: str | None = None
|
|
304
|
+
|
|
305
|
+
_href = field_validator("href")(staticmethod(safe_href))
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
Header.model_rebuild()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class Stat(Component):
|
|
312
|
+
"""A headline number. ``delta`` is the change, shown with its direction
|
|
313
|
+
and coloured by whether up is ``good``; ``trend`` draws a sparkline."""
|
|
314
|
+
|
|
315
|
+
type: ClassVar[str] = "Stat"
|
|
316
|
+
label: str
|
|
317
|
+
value: str
|
|
318
|
+
hint: str | None = None
|
|
319
|
+
tone: Tone | None = None
|
|
320
|
+
icon: str | None = None
|
|
321
|
+
delta: float | None = None
|
|
322
|
+
delta_format: Literal["percent", "number"] = "percent"
|
|
323
|
+
good: Literal["up", "down"] = "up"
|
|
324
|
+
trend: list[float] | None = None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class Alert(Component):
|
|
328
|
+
type: ClassVar[str] = "Alert"
|
|
329
|
+
title: str | None = None
|
|
330
|
+
description: str | None = None
|
|
331
|
+
tone: Tone = "default"
|
|
332
|
+
icon: str | None = None
|
|
333
|
+
dismissable: bool = False
|
|
334
|
+
actions: list[Component] = []
|
|
335
|
+
on_dismiss: Any = None
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
# ------------------------------------------------------------------ inputs
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class Button(Component):
|
|
342
|
+
type: ClassVar[str] = "Button"
|
|
343
|
+
label: str = ""
|
|
344
|
+
variant: Variant = "primary"
|
|
345
|
+
size: Size = "md"
|
|
346
|
+
icon: str | None = None
|
|
347
|
+
icon_position: Literal["start", "end"] = "start"
|
|
348
|
+
disabled: bool = False
|
|
349
|
+
loading: bool = False
|
|
350
|
+
tooltip: str | None = None
|
|
351
|
+
confirm: str | None = None
|
|
352
|
+
# The key of a Form on the page: the button submits it, from anywhere.
|
|
353
|
+
form: str | None = None
|
|
354
|
+
on_click: Any = None
|
|
355
|
+
|
|
356
|
+
def __init__(self, label: str = "", **props: Any) -> None:
|
|
357
|
+
super().__init__(label=label, **props)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
class ButtonGroup(Component):
|
|
361
|
+
"""Buttons joined into one control."""
|
|
362
|
+
|
|
363
|
+
type: ClassVar[str] = "ButtonGroup"
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
class DropdownMenu(Component):
|
|
367
|
+
"""A menu behind a trigger button. ``on_select`` receives the item's id."""
|
|
368
|
+
|
|
369
|
+
type: ClassVar[str] = "DropdownMenu"
|
|
370
|
+
trigger: Component
|
|
371
|
+
items: list[MenuItem]
|
|
372
|
+
align: Literal["start", "end"] = "start"
|
|
373
|
+
on_select: Any = None
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
class Field(Component):
|
|
377
|
+
"""A label, help text and an error around one control, for a form built by hand."""
|
|
378
|
+
|
|
379
|
+
type: ClassVar[str] = "Field"
|
|
380
|
+
label: str
|
|
381
|
+
help: str | None = None
|
|
382
|
+
error: str | None = None
|
|
383
|
+
required: bool = False
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
class ToggleGroup(_Owned):
|
|
387
|
+
type: ClassVar[str] = "ToggleGroup"
|
|
388
|
+
options: list[Option]
|
|
389
|
+
value: str | list[str] | None = None
|
|
390
|
+
default: str | list[str] | None = None
|
|
391
|
+
multiple: bool = False
|
|
392
|
+
size: Size = "md"
|
|
393
|
+
on_change: Any = None
|
|
394
|
+
|
|
395
|
+
_options = field_validator("options", mode="before")(staticmethod(as_options))
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class Input(_Owned):
|
|
399
|
+
type: ClassVar[str] = "Input"
|
|
400
|
+
value: str | None = None
|
|
401
|
+
default: str | None = None
|
|
402
|
+
placeholder: str | None = None
|
|
403
|
+
kind: Literal["text", "password", "email", "number", "search", "url"] = "text"
|
|
404
|
+
prefix_icon: str | None = None
|
|
405
|
+
suffix: str | None = None
|
|
406
|
+
disabled: bool = False
|
|
407
|
+
readonly: bool = False
|
|
408
|
+
clearable: bool = False
|
|
409
|
+
debounce_ms: int = 250
|
|
410
|
+
on_change: Any = None
|
|
411
|
+
on_submit: Any = None
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
class Textarea(_Owned):
|
|
415
|
+
type: ClassVar[str] = "Textarea"
|
|
416
|
+
value: str | None = None
|
|
417
|
+
default: str | None = None
|
|
418
|
+
placeholder: str | None = None
|
|
419
|
+
rows: int = 3
|
|
420
|
+
autosize: bool = False
|
|
421
|
+
max_length: int | None = None
|
|
422
|
+
disabled: bool = False
|
|
423
|
+
readonly: bool = False
|
|
424
|
+
debounce_ms: int = 250
|
|
425
|
+
on_change: Any = None
|
|
426
|
+
on_submit: Any = None
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class NumberInput(_Owned):
|
|
430
|
+
type: ClassVar[str] = "NumberInput"
|
|
431
|
+
value: float | None = None
|
|
432
|
+
default: float | None = None
|
|
433
|
+
min: float | None = None
|
|
434
|
+
max: float | None = None
|
|
435
|
+
step: float | None = None
|
|
436
|
+
suffix: str | None = None
|
|
437
|
+
placeholder: str | None = None
|
|
438
|
+
disabled: bool = False
|
|
439
|
+
on_change: Any = None
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class Combobox(_Owned):
|
|
443
|
+
"""A searchable single choice. ``options`` filters in the tab; ``on_search(query)``
|
|
444
|
+
returns the options for what was typed, answered over the socket without a render.
|
|
445
|
+
``allow_custom`` lets typed text that matches nothing be the value."""
|
|
446
|
+
|
|
447
|
+
type: ClassVar[str] = "Combobox"
|
|
448
|
+
options: list[Option] = []
|
|
449
|
+
value: str | None = None
|
|
450
|
+
default: str | None = None
|
|
451
|
+
placeholder: str | None = None
|
|
452
|
+
allow_custom: bool = False
|
|
453
|
+
clearable: bool = False
|
|
454
|
+
disabled: bool = False
|
|
455
|
+
on_change: Any = None
|
|
456
|
+
on_search: Any = None
|
|
457
|
+
|
|
458
|
+
_options = field_validator("options", mode="before")(staticmethod(as_options))
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class MultiSelect(_Owned):
|
|
462
|
+
"""Several choices from a list, shown as chips; typing filters the list."""
|
|
463
|
+
|
|
464
|
+
type: ClassVar[str] = "MultiSelect"
|
|
465
|
+
options: list[Option]
|
|
466
|
+
value: list[str] | None = None
|
|
467
|
+
default: list[str] | None = None
|
|
468
|
+
placeholder: str | None = None
|
|
469
|
+
searchable: bool = True
|
|
470
|
+
# At most this many chosen; None is no limit.
|
|
471
|
+
max: int | None = None
|
|
472
|
+
# The chips can be dragged into order, or moved with Alt and the arrow keys.
|
|
473
|
+
sortable: bool = False
|
|
474
|
+
disabled: bool = False
|
|
475
|
+
on_change: Any = None
|
|
476
|
+
|
|
477
|
+
_options = field_validator("options", mode="before")(staticmethod(as_options))
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def _iso_date(value: Any) -> Any:
|
|
481
|
+
if isinstance(value, dt.datetime):
|
|
482
|
+
return value.date().isoformat()
|
|
483
|
+
if isinstance(value, dt.date):
|
|
484
|
+
return value.isoformat()
|
|
485
|
+
if isinstance(value, list | tuple):
|
|
486
|
+
return [_iso_date(v) for v in value]
|
|
487
|
+
return value
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
class DatePicker(_Owned):
|
|
491
|
+
"""A date, or with ``range`` a ``[start, end]`` pair, as ISO strings (``date``
|
|
492
|
+
objects are accepted and converted). ``min`` and ``max`` bound what can be picked."""
|
|
493
|
+
|
|
494
|
+
type: ClassVar[str] = "DatePicker"
|
|
495
|
+
value: str | list[str | None] | None = None
|
|
496
|
+
default: str | list[str | None] | None = None
|
|
497
|
+
min: str | None = None
|
|
498
|
+
max: str | None = None
|
|
499
|
+
range: bool = False
|
|
500
|
+
placeholder: str | None = None
|
|
501
|
+
clearable: bool = False
|
|
502
|
+
disabled: bool = False
|
|
503
|
+
on_change: Any = None
|
|
504
|
+
|
|
505
|
+
_dates = field_validator("value", "default", "min", "max", mode="before")(
|
|
506
|
+
staticmethod(_iso_date)
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class Select(_Owned):
|
|
511
|
+
type: ClassVar[str] = "Select"
|
|
512
|
+
options: list[Option]
|
|
513
|
+
value: str | None = None
|
|
514
|
+
default: str | None = None
|
|
515
|
+
placeholder: str | None = None
|
|
516
|
+
clearable: bool = False
|
|
517
|
+
disabled: bool = False
|
|
518
|
+
on_change: Any = None
|
|
519
|
+
|
|
520
|
+
_options = field_validator("options", mode="before")(staticmethod(as_options))
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
class Checkbox(_Owned):
|
|
524
|
+
type: ClassVar[str] = "Checkbox"
|
|
525
|
+
label: str | None = None
|
|
526
|
+
description: str | None = None
|
|
527
|
+
checked: bool | None = None
|
|
528
|
+
default_checked: bool | None = None
|
|
529
|
+
disabled: bool = False
|
|
530
|
+
on_change: Any = None
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
class CheckboxGroup(_Owned):
|
|
534
|
+
type: ClassVar[str] = "CheckboxGroup"
|
|
535
|
+
options: list[Option]
|
|
536
|
+
value: list[str] | None = None
|
|
537
|
+
default: list[str] | None = None
|
|
538
|
+
label: str | None = None
|
|
539
|
+
disabled: bool = False
|
|
540
|
+
on_change: Any = None
|
|
541
|
+
|
|
542
|
+
_options = field_validator("options", mode="before")(staticmethod(as_options))
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
class RadioGroup(_Owned):
|
|
546
|
+
type: ClassVar[str] = "RadioGroup"
|
|
547
|
+
options: list[Option]
|
|
548
|
+
value: str | None = None
|
|
549
|
+
default: str | None = None
|
|
550
|
+
label: str | None = None
|
|
551
|
+
orientation: Literal["vertical", "horizontal"] = "vertical"
|
|
552
|
+
disabled: bool = False
|
|
553
|
+
on_change: Any = None
|
|
554
|
+
|
|
555
|
+
_options = field_validator("options", mode="before")(staticmethod(as_options))
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
class Switch(_Owned):
|
|
559
|
+
type: ClassVar[str] = "Switch"
|
|
560
|
+
label: str | None = None
|
|
561
|
+
description: str | None = None
|
|
562
|
+
checked: bool | None = None
|
|
563
|
+
default_checked: bool | None = None
|
|
564
|
+
disabled: bool = False
|
|
565
|
+
on_change: Any = None
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
class Slider(_Owned):
|
|
569
|
+
type: ClassVar[str] = "Slider"
|
|
570
|
+
min: float = 0
|
|
571
|
+
max: float = 100
|
|
572
|
+
step: float = 1
|
|
573
|
+
value: float | None = None
|
|
574
|
+
default: float | None = None
|
|
575
|
+
show_value: bool = True
|
|
576
|
+
# Labelled points under the track: values, or value to label.
|
|
577
|
+
marks: list[float] | dict[float, str] | None = None
|
|
578
|
+
disabled: bool = False
|
|
579
|
+
on_change: Any = None
|
|
580
|
+
|
|
581
|
+
def wire_extra(self) -> dict[str, Any]:
|
|
582
|
+
if self.marks is None:
|
|
583
|
+
return {}
|
|
584
|
+
if isinstance(self.marks, dict):
|
|
585
|
+
return {"marks": [{"value": v, "label": label} for v, label in self.marks.items()]}
|
|
586
|
+
return {"marks": [{"value": v, "label": None} for v in self.marks]}
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
class Tooltip(Component):
|
|
590
|
+
type: ClassVar[str] = "Tooltip"
|
|
591
|
+
content: str
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
class Custom(Component):
|
|
595
|
+
"""An app's own component: ``components/<name>.jsx``, compiled in the tab.
|
|
596
|
+
|
|
597
|
+
``props`` arrive as React props next to ``emit`` and ``children``; events
|
|
598
|
+
the component emits reach ``on_event`` as ``(session, (name, payload))``,
|
|
599
|
+
or a handler per event name through ``events``.
|
|
600
|
+
"""
|
|
601
|
+
|
|
602
|
+
type: ClassVar[str] = "Custom"
|
|
603
|
+
wire_exclude: ClassVar[frozenset[str]] = frozenset({"name", "props", "events"})
|
|
604
|
+
|
|
605
|
+
name: str
|
|
606
|
+
props: dict[str, Any] = {}
|
|
607
|
+
events: dict[str, Any] = {}
|
|
608
|
+
on_event: Any = None
|
|
609
|
+
|
|
610
|
+
def __init__(self, name: str, *children: Any, **kw: Any) -> None:
|
|
611
|
+
super().__init__(*children, name=name, **kw)
|
|
612
|
+
|
|
613
|
+
def wire_type(self) -> str:
|
|
614
|
+
return self.name
|
|
615
|
+
|
|
616
|
+
def wire_extra(self) -> dict[str, Any]:
|
|
617
|
+
return dict(self.props)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
# ------------------------------------------------------------------ data
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
class Progress(Component):
|
|
624
|
+
"""A bar; with ``task`` the value, label and message come from the task."""
|
|
625
|
+
|
|
626
|
+
type: ClassVar[str] = "Progress"
|
|
627
|
+
value: float | None = None
|
|
628
|
+
label: str | None = None
|
|
629
|
+
description: str | None = None
|
|
630
|
+
task: Any = None
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
class Steps(Component):
|
|
634
|
+
"""A step tree on Trace: the task's steps, or ``items`` given directly."""
|
|
635
|
+
|
|
636
|
+
type: ClassVar[str] = "Steps"
|
|
637
|
+
items: list[Any] | None = None
|
|
638
|
+
task: Any = None
|
|
639
|
+
compact: bool = False
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
class Skeleton(Component):
|
|
643
|
+
type: ClassVar[str] = "Skeleton"
|
|
644
|
+
rows: int = 3
|
|
645
|
+
shape: Literal["text", "card", "table"] = "text"
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
class Spinner(Component):
|
|
649
|
+
type: ClassVar[str] = "Spinner"
|
|
650
|
+
size: Size = "md"
|
|
651
|
+
label: str | None = None
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
__all__ = [
|
|
655
|
+
"Accept",
|
|
656
|
+
"Action",
|
|
657
|
+
"Accordion",
|
|
658
|
+
"AccordionItem",
|
|
659
|
+
"Alert",
|
|
660
|
+
"AreaChart",
|
|
661
|
+
"BarChart",
|
|
662
|
+
"Badge",
|
|
663
|
+
"Button",
|
|
664
|
+
"Card",
|
|
665
|
+
"Column",
|
|
666
|
+
"Chat",
|
|
667
|
+
"Checkbox",
|
|
668
|
+
"Code",
|
|
669
|
+
"CheckboxGroup",
|
|
670
|
+
"Collapsible",
|
|
671
|
+
"Command",
|
|
672
|
+
"Custom",
|
|
673
|
+
"Component",
|
|
674
|
+
"Dialog",
|
|
675
|
+
"Download",
|
|
676
|
+
"FileCard",
|
|
677
|
+
"FileRef",
|
|
678
|
+
"FileUpload",
|
|
679
|
+
"Empty",
|
|
680
|
+
"Form",
|
|
681
|
+
"FormIssues",
|
|
682
|
+
"Grid",
|
|
683
|
+
"Image",
|
|
684
|
+
"Input",
|
|
685
|
+
"Issue",
|
|
686
|
+
"Kbd",
|
|
687
|
+
"LineChart",
|
|
688
|
+
"Link",
|
|
689
|
+
"Markdown",
|
|
690
|
+
"MenuItem",
|
|
691
|
+
"NumberInput",
|
|
692
|
+
"Option",
|
|
693
|
+
"Page",
|
|
694
|
+
"Pdf",
|
|
695
|
+
"Progress",
|
|
696
|
+
"RadioGroup",
|
|
697
|
+
"Row",
|
|
698
|
+
"ScrollArea",
|
|
699
|
+
"Select",
|
|
700
|
+
"Field",
|
|
701
|
+
"DropdownMenu",
|
|
702
|
+
"ButtonGroup",
|
|
703
|
+
"DatePicker",
|
|
704
|
+
"MultiSelect",
|
|
705
|
+
"Separator",
|
|
706
|
+
"Sheet",
|
|
707
|
+
"Sidebar",
|
|
708
|
+
"Skeleton",
|
|
709
|
+
"Slider",
|
|
710
|
+
"Spinner",
|
|
711
|
+
"Split",
|
|
712
|
+
"Stack",
|
|
713
|
+
"Stat",
|
|
714
|
+
"Steps",
|
|
715
|
+
"Switch",
|
|
716
|
+
"Tab",
|
|
717
|
+
"Table",
|
|
718
|
+
"Tabs",
|
|
719
|
+
"Text",
|
|
720
|
+
"Logo",
|
|
721
|
+
"Textarea",
|
|
722
|
+
"ToggleGroup",
|
|
723
|
+
"Tooltip",
|
|
724
|
+
"as_options",
|
|
725
|
+
]
|