@pyscript/core 0.7.10 → 0.7.12
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.
- package/dist/{codemirror-7vXPINKi.js → codemirror-WbVPJuAs.js} +2 -2
- package/dist/codemirror-WbVPJuAs.js.map +1 -0
- package/dist/{codemirror_commands-CN4gxvZk.js → codemirror_commands-BRu2f-2p.js} +2 -2
- package/dist/codemirror_commands-BRu2f-2p.js.map +1 -0
- package/dist/codemirror_lang-python-q-wuh0nL.js +2 -0
- package/dist/codemirror_lang-python-q-wuh0nL.js.map +1 -0
- package/dist/codemirror_language-DqPeLcFN.js +2 -0
- package/dist/codemirror_language-DqPeLcFN.js.map +1 -0
- package/dist/{codemirror_state-BIAL8JKm.js → codemirror_state-DWQh5Ruf.js} +2 -2
- package/dist/codemirror_state-DWQh5Ruf.js.map +1 -0
- package/dist/codemirror_view-CMXZSWgf.js +2 -0
- package/dist/codemirror_view-CMXZSWgf.js.map +1 -0
- package/dist/core-DmpFMpAn.js +4 -0
- package/dist/core-DmpFMpAn.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/{deprecations-manager-qW023Rjf.js → deprecations-manager-HQLYNCYn.js} +2 -2
- package/dist/{deprecations-manager-qW023Rjf.js.map → deprecations-manager-HQLYNCYn.js.map} +1 -1
- package/dist/{donkey-7fH6-q0I.js → donkey-B3F8VdSV.js} +2 -2
- package/dist/{donkey-7fH6-q0I.js.map → donkey-B3F8VdSV.js.map} +1 -1
- package/dist/{error-CO7OuWCh.js → error-DS_5_C5_.js} +2 -2
- package/dist/{error-CO7OuWCh.js.map → error-DS_5_C5_.js.map} +1 -1
- package/dist/index-DaYI1YXo.js +2 -0
- package/dist/index-DaYI1YXo.js.map +1 -0
- package/dist/{mpy-BZxQ23WL.js → mpy-Bo2uW6nt.js} +2 -2
- package/dist/{mpy-BZxQ23WL.js.map → mpy-Bo2uW6nt.js.map} +1 -1
- package/dist/{py-DI_TP8Id.js → py-BEf8j7L5.js} +2 -2
- package/dist/{py-DI_TP8Id.js.map → py-BEf8j7L5.js.map} +1 -1
- package/dist/{py-editor-BJBbMtNv.js → py-editor-C0XF2rwE.js} +2 -2
- package/dist/{py-editor-BJBbMtNv.js.map → py-editor-C0XF2rwE.js.map} +1 -1
- package/dist/{py-game-C7N5JK0D.js → py-game-eWNz96mt.js} +2 -2
- package/dist/{py-game-C7N5JK0D.js.map → py-game-eWNz96mt.js.map} +1 -1
- package/dist/{py-terminal-Cy8siD6F.js → py-terminal-VtavPj1S.js} +2 -2
- package/dist/{py-terminal-Cy8siD6F.js.map → py-terminal-VtavPj1S.js.map} +1 -1
- package/dist/xterm_addon-fit-DxKdSnof.js +14 -0
- package/dist/xterm_addon-fit-DxKdSnof.js.map +1 -0
- package/dist/xterm_addon-web-links-B6rWzrcs.js +14 -0
- package/dist/xterm_addon-web-links-B6rWzrcs.js.map +1 -0
- package/dist/zip-CgZGjqjF.js +2 -0
- package/dist/zip-CgZGjqjF.js.map +1 -0
- package/package.json +16 -15
- package/src/3rd-party/xterm_addon-fit.js +14 -2
- package/src/3rd-party/xterm_addon-web-links.js +14 -2
- package/src/core.js +13 -2
- package/src/stdlib/pyscript/__init__.py +100 -31
- package/src/stdlib/pyscript/context.py +198 -0
- package/src/stdlib/pyscript/display.py +211 -127
- package/src/stdlib/pyscript/events.py +191 -88
- package/src/stdlib/pyscript/fetch.py +156 -25
- package/src/stdlib/pyscript/ffi.py +132 -16
- package/src/stdlib/pyscript/flatted.py +78 -1
- package/src/stdlib/pyscript/fs.py +207 -50
- package/src/stdlib/pyscript/media.py +210 -50
- package/src/stdlib/pyscript/storage.py +214 -27
- package/src/stdlib/pyscript/util.py +28 -7
- package/src/stdlib/pyscript/web.py +1079 -881
- package/src/stdlib/pyscript/websocket.py +252 -45
- package/src/stdlib/pyscript/workers.py +176 -27
- package/src/stdlib/pyscript.js +13 -13
- package/src/sync.js +1 -1
- package/types/stdlib/pyscript.d.ts +1 -1
- package/dist/codemirror-7vXPINKi.js.map +0 -1
- package/dist/codemirror_commands-CN4gxvZk.js.map +0 -1
- package/dist/codemirror_lang-python-CkOVBHci.js +0 -2
- package/dist/codemirror_lang-python-CkOVBHci.js.map +0 -1
- package/dist/codemirror_language-DOkvasqm.js +0 -2
- package/dist/codemirror_language-DOkvasqm.js.map +0 -1
- package/dist/codemirror_state-BIAL8JKm.js.map +0 -1
- package/dist/codemirror_view-Bt4sLgyA.js +0 -2
- package/dist/codemirror_view-Bt4sLgyA.js.map +0 -1
- package/dist/core-5ORB_Mcj.js +0 -4
- package/dist/core-5ORB_Mcj.js.map +0 -1
- package/dist/index-jZ1aOVVJ.js +0 -2
- package/dist/index-jZ1aOVVJ.js.map +0 -1
- package/dist/xterm_addon-fit--gyF3PcZ.js +0 -2
- package/dist/xterm_addon-fit--gyF3PcZ.js.map +0 -1
- package/dist/xterm_addon-web-links-D95xh2la.js +0 -2
- package/dist/xterm_addon-web-links-D95xh2la.js.map +0 -1
- package/dist/zip-CakRHzZu.js +0 -2
- package/dist/zip-CakRHzZu.js.map +0 -1
- package/src/stdlib/pyscript/magic_js.py +0 -84
|
@@ -1,184 +1,585 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""
|
|
2
|
+
A lightweight Pythonic interface to the DOM and HTML elements that helps you
|
|
3
|
+
interact with web pages, making it easy to find, create, manipulate, and
|
|
4
|
+
compose HTML elements from Python.
|
|
5
|
+
|
|
6
|
+
Highlights include:
|
|
2
7
|
|
|
3
|
-
|
|
4
|
-
|
|
8
|
+
Use the `page` object to find elements on the current page:
|
|
9
|
+
|
|
10
|
+
```python
|
|
11
|
+
from pyscript import web
|
|
5
12
|
|
|
6
|
-
|
|
13
|
+
|
|
14
|
+
# Find by CSS selector (returns an ElementCollection).
|
|
15
|
+
divs = web.page.find("div")
|
|
16
|
+
buttons = web.page.find(".button-class")
|
|
17
|
+
|
|
18
|
+
# Get element by ID (returns single Element or None).
|
|
19
|
+
header = web.page["header-id"]
|
|
20
|
+
header = web.page["#header-id"] # the "#" prefix is optional.
|
|
21
|
+
|
|
22
|
+
# Access page structure.
|
|
23
|
+
web.page.body.append(some_element)
|
|
24
|
+
web.page.title = "New Page Title"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Create new elements and compose them together:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# Create simple elements.
|
|
31
|
+
div = web.div("Hello, World!")
|
|
32
|
+
paragraph = web.p("Some text", id="my-para", className="text-content")
|
|
33
|
+
|
|
34
|
+
# Compose elements together.
|
|
35
|
+
container = web.div(
|
|
36
|
+
web.h1("Title"),
|
|
37
|
+
web.p("First paragraph"),
|
|
38
|
+
web.p("Second paragraph"),
|
|
39
|
+
id="container"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Add to the page.
|
|
43
|
+
web.page.body.append(container)
|
|
44
|
+
|
|
45
|
+
# Create with initial attributes.
|
|
46
|
+
link = web.a(
|
|
47
|
+
"Click me",
|
|
48
|
+
href="https://example.com",
|
|
49
|
+
target="_blank",
|
|
50
|
+
classes=["link", "external"]
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Modify element content and attributes:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# Update content.
|
|
58
|
+
element.innerHTML = "<b>Bold text</b>"
|
|
59
|
+
element.textContent = "Plain text"
|
|
60
|
+
|
|
61
|
+
# Update attributes.
|
|
62
|
+
element.id = "new-id"
|
|
63
|
+
element.title = "Tooltip text"
|
|
64
|
+
|
|
65
|
+
# Bulk update with convenience method.
|
|
66
|
+
element.update(
|
|
67
|
+
classes=["active", "highlighted"],
|
|
68
|
+
style={"color": "red", "font-size": "16px"},
|
|
69
|
+
title="Updated tooltip"
|
|
70
|
+
)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
An element's CSS classes behave like a Python `set`:
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# Add and remove classes
|
|
77
|
+
element.classes.add("active")
|
|
78
|
+
element.classes.add("highlighted")
|
|
79
|
+
element.classes.remove("hidden")
|
|
80
|
+
|
|
81
|
+
# Check membership.
|
|
82
|
+
if "active" in element.classes:
|
|
83
|
+
print("Element is active")
|
|
84
|
+
|
|
85
|
+
# Clear all classes.
|
|
86
|
+
element.classes.clear()
|
|
87
|
+
|
|
88
|
+
# Discard (no error if missing).
|
|
89
|
+
element.classes.discard("maybe-not-there")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
An element's styles behave like a Python `dict`:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# Set individual styles.
|
|
96
|
+
element.style["color"] = "red"
|
|
97
|
+
element.style["background-color"] = "#f0f0f0"
|
|
98
|
+
element.style["font-size"] = "16px"
|
|
99
|
+
|
|
100
|
+
# Remove a style.
|
|
101
|
+
del element.style["margin"]
|
|
102
|
+
|
|
103
|
+
# Check if style is set.
|
|
104
|
+
if "color" in element.style:
|
|
105
|
+
print(f"Color is {element.style['color']}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Update multiple elements at once via an `ElementCollection`:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Find multiple elements (returns an ElementCollection).
|
|
112
|
+
items = web.page.find(".list-item")
|
|
113
|
+
|
|
114
|
+
# Iterate over collection.
|
|
115
|
+
for item in items:
|
|
116
|
+
item.innerHTML = "Updated"
|
|
117
|
+
item.classes.add("processed")
|
|
118
|
+
|
|
119
|
+
# Bulk update all elements.
|
|
120
|
+
items.update_all(
|
|
121
|
+
innerHTML="Hello",
|
|
122
|
+
className="updated-item"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# Index and slice collections.
|
|
126
|
+
first = items[0]
|
|
127
|
+
subset = items[1:3]
|
|
128
|
+
|
|
129
|
+
# Get an element by ID within the collection.
|
|
130
|
+
special = items["special-id"]
|
|
131
|
+
|
|
132
|
+
# Find descendants within the collection.
|
|
133
|
+
subitems = items.find(".sub-item")
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Manage `select` element options (also for `datalist` and `optgroup`):
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
# Get existing select.
|
|
140
|
+
select = web.page["my-select"]
|
|
141
|
+
|
|
142
|
+
# Add options.
|
|
143
|
+
select.options.add(value="1", html="Option 1")
|
|
144
|
+
select.options.add(value="2", html="Option 2", selected=True)
|
|
145
|
+
|
|
146
|
+
# Get selected option.
|
|
147
|
+
selected = select.options.selected
|
|
148
|
+
print(f"Selected: {selected.value}")
|
|
149
|
+
|
|
150
|
+
# Iterate over options.
|
|
151
|
+
for option in select.options:
|
|
152
|
+
print(f"{option.value}: {option.innerHTML}")
|
|
153
|
+
|
|
154
|
+
# Clear all options.
|
|
155
|
+
select.options.clear()
|
|
156
|
+
|
|
157
|
+
# Remove specific option by index.
|
|
158
|
+
select.options.remove(0)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Attach event handlers to elements:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from pyscript import when
|
|
165
|
+
|
|
166
|
+
button = web.button("Click me", id="my-button")
|
|
167
|
+
|
|
168
|
+
# Use the when decorator.
|
|
169
|
+
@when("click", button)
|
|
170
|
+
def handle_click(event):
|
|
171
|
+
print("Button clicked!")
|
|
172
|
+
|
|
173
|
+
# Or add directly to the event.
|
|
174
|
+
def another_handler(event):
|
|
175
|
+
print("Another handler")
|
|
176
|
+
|
|
177
|
+
button.on_click.add_listener(another_handler)
|
|
178
|
+
|
|
179
|
+
# Pass handler during creation.
|
|
180
|
+
button = web.button("Click", on_click=handle_click)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
All `Element` instances provide direct access to the underlying DOM element
|
|
184
|
+
via attribute delegation:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# Most DOM methods are accessible directly.
|
|
188
|
+
element.scrollIntoView()
|
|
189
|
+
element.focus()
|
|
190
|
+
element.blur()
|
|
191
|
+
|
|
192
|
+
# But we do have a historic convenience method for scrolling into view.
|
|
193
|
+
element.show_me() # Calls scrollIntoView()
|
|
194
|
+
|
|
195
|
+
# Access the raw DOM element when needed for special cases.
|
|
196
|
+
dom_element = element._dom_element
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The main entry point is the `page` object, which represents the current
|
|
200
|
+
document and provides access to common elements like `page.body` and methods
|
|
201
|
+
like `page.find()` for querying the DOM.
|
|
202
|
+
"""
|
|
7
203
|
|
|
8
204
|
from pyscript import document, when, Event # noqa: F401
|
|
9
205
|
from pyscript.ffi import create_proxy, is_none
|
|
10
206
|
|
|
11
207
|
|
|
12
|
-
|
|
13
|
-
|
|
208
|
+
# Utility functions for finding and wrapping DOM elements.
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _wrap_if_not_none(dom_element):
|
|
212
|
+
"""
|
|
213
|
+
Wrap a `dom_element`, returning `None` if the element is `None`/`null`.
|
|
214
|
+
"""
|
|
215
|
+
return Element.wrap_dom_element(dom_element) if not is_none(dom_element) else None
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _find_by_id(dom_node, target_id):
|
|
219
|
+
"""
|
|
220
|
+
Find an element by `id` within a `dom_node`.
|
|
221
|
+
|
|
222
|
+
The `target_id` can optionally start with '#'. Returns a wrapped `Element`
|
|
223
|
+
or `None` if not found.
|
|
224
|
+
"""
|
|
225
|
+
element_id = target_id[1:] if target_id.startswith("#") else target_id
|
|
226
|
+
result = dom_node.querySelector(f"#{element_id}")
|
|
227
|
+
return _wrap_if_not_none(result)
|
|
228
|
+
|
|
14
229
|
|
|
15
|
-
|
|
16
|
-
and use its class method.
|
|
230
|
+
def _find_and_wrap(dom_node, selector):
|
|
17
231
|
"""
|
|
232
|
+
Find all descendants of `dom_node` matching the CSS `selector`.
|
|
18
233
|
|
|
19
|
-
|
|
234
|
+
Returns an `ElementCollection` of wrapped elements.
|
|
235
|
+
"""
|
|
236
|
+
return ElementCollection.wrap_dom_elements(dom_node.querySelectorAll(selector))
|
|
20
237
|
|
|
21
238
|
|
|
22
239
|
class Element:
|
|
23
|
-
|
|
24
|
-
|
|
240
|
+
"""
|
|
241
|
+
The base class for all [HTML elements](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements).
|
|
242
|
+
|
|
243
|
+
Provides a Pythonic interface to DOM elements with support for attributes,
|
|
244
|
+
events, styles, classes, and DOM manipulation. It can create new elements
|
|
245
|
+
or wrap existing DOM elements.
|
|
246
|
+
|
|
247
|
+
Elements are typically created using the tag-specific classes found
|
|
248
|
+
within this namespace (e.g. `web.div`, `web.span`, `web.button`):
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
from pyscript import web
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
# Create a simple div.
|
|
255
|
+
div = web.div("Hello, World!")
|
|
256
|
+
|
|
257
|
+
# Create with attributes.
|
|
258
|
+
link = web.a("Click me", href="https://example.com", target="_blank")
|
|
259
|
+
|
|
260
|
+
# Create with classes and styles.
|
|
261
|
+
button = web.button(
|
|
262
|
+
"Submit",
|
|
263
|
+
classes=["primary", "large"],
|
|
264
|
+
style={"background-color": "blue", "color": "white"},
|
|
265
|
+
id="submit-btn"
|
|
266
|
+
)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
!!! info
|
|
270
|
+
|
|
271
|
+
Some elements have an underscore suffix in their class names (e.g.
|
|
272
|
+
`select_`, `input_`).
|
|
273
|
+
|
|
274
|
+
This is to avoid clashes with Python keywords. The underscore is removed
|
|
275
|
+
when determining the actual HTML tag name.
|
|
276
|
+
|
|
277
|
+
Wrap existing DOM elements found on the page:
|
|
278
|
+
|
|
279
|
+
```python
|
|
280
|
+
# Find and wrap an element by CSS selector.
|
|
281
|
+
existing = web.page.find(".my_class")[0]
|
|
282
|
+
|
|
283
|
+
# Or, better, just use direct ID lookup (with or without the
|
|
284
|
+
# leading '#').
|
|
285
|
+
existing = web.page["my-element"]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Element attributes are accessible as Python properties:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
# Get attributes.
|
|
292
|
+
element_id = div.id
|
|
293
|
+
element_title = div.title
|
|
294
|
+
element_href = link.href
|
|
295
|
+
|
|
296
|
+
# Set attributes.
|
|
297
|
+
div.id = "new-id"
|
|
298
|
+
div.title = "Tooltip text"
|
|
299
|
+
link.href = "https://new-url.com"
|
|
300
|
+
|
|
301
|
+
# HTML content.
|
|
302
|
+
div.innerHTML = "<b>Bold text</b>"
|
|
303
|
+
div.textContent = "Plain text"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
CSS classes are managed through a `set`-like interface:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
# Add classes.
|
|
310
|
+
element.classes.add("active")
|
|
311
|
+
element.classes.add("highlighted")
|
|
312
|
+
|
|
313
|
+
# Remove classes.
|
|
314
|
+
element.classes.remove("inactive")
|
|
315
|
+
element.classes.discard("maybe-missing") # No error if absent
|
|
316
|
+
|
|
317
|
+
# Check membership.
|
|
318
|
+
if "active" in element.classes:
|
|
319
|
+
print("Element is active")
|
|
320
|
+
|
|
321
|
+
# Iterate over classes.
|
|
322
|
+
for cls in element.classes:
|
|
323
|
+
print(cls)
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Explicit CSS styles are managed through a `dict`-like interface:
|
|
327
|
+
|
|
328
|
+
```python
|
|
329
|
+
# Set styles using CSS property names (hyphenated).
|
|
330
|
+
element.style["color"] = "red"
|
|
331
|
+
element.style["background-color"] = "#f0f0f0"
|
|
332
|
+
element.style["font-size"] = "16px"
|
|
333
|
+
|
|
334
|
+
# Get styles.
|
|
335
|
+
color = element.style["color"]
|
|
336
|
+
|
|
337
|
+
# Remove styles.
|
|
338
|
+
del element.style["margin"]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Add, find, and navigate elements:
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
# Append children.
|
|
345
|
+
parent.append(child_element)
|
|
346
|
+
parent.append(child1, child2, child3) # Multiple at once
|
|
347
|
+
|
|
348
|
+
# Find descendants using CSS selectors.
|
|
349
|
+
buttons = parent.find("button")
|
|
350
|
+
items = parent.find(".item-class")
|
|
351
|
+
|
|
352
|
+
# Navigate the tree.
|
|
353
|
+
child = parent.children[0]
|
|
354
|
+
parent_elem = child.parent
|
|
355
|
+
|
|
356
|
+
# Access children by index or slice.
|
|
357
|
+
first_child = parent[0]
|
|
358
|
+
first_three = parent[0:3]
|
|
359
|
+
|
|
360
|
+
# Get a child explicitly by ID. Returns None if not found.
|
|
361
|
+
specific = parent["child-id"]
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Attach event listeners to elements:
|
|
365
|
+
|
|
366
|
+
```python
|
|
367
|
+
button = web.button("Click me")
|
|
368
|
+
|
|
369
|
+
# Use the @when decorator with event name.
|
|
370
|
+
from pyscript import when
|
|
371
|
+
|
|
372
|
+
@when("click", button)
|
|
373
|
+
def handle_click(event):
|
|
374
|
+
print("Clicked!")
|
|
375
|
+
|
|
376
|
+
# Or use the on_* event directly with @when.
|
|
377
|
+
@when(button.on_click)
|
|
378
|
+
def handle_click(event):
|
|
379
|
+
print("Also works!")
|
|
380
|
+
|
|
381
|
+
# Pass handlers during element creation.
|
|
382
|
+
button = web.button("Click", on_click=handle_click)
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Update multiple properties at once:
|
|
386
|
+
|
|
387
|
+
```python
|
|
388
|
+
element.update(
|
|
389
|
+
classes=["active", "highlighted"],
|
|
390
|
+
style={"color": "red", "font-size": "20px"},
|
|
391
|
+
id="updated-id",
|
|
392
|
+
title="New tooltip"
|
|
393
|
+
)
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
!!! warning
|
|
397
|
+
**Some HTML attributes clash with Python keywords and use trailing
|
|
398
|
+
underscores**.
|
|
399
|
+
|
|
400
|
+
Use `for_` instead of `for`, and `class_` instead of `class`.
|
|
401
|
+
|
|
402
|
+
```python
|
|
403
|
+
# The 'for' attribute (on labels)
|
|
404
|
+
label = web.label("Username", for_="username-input")
|
|
405
|
+
|
|
406
|
+
# The 'class' attribute (although 'classes' is preferred)
|
|
407
|
+
div.class_ = "my-class"
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Create copies of elements:
|
|
411
|
+
|
|
412
|
+
```python
|
|
413
|
+
original = web.div("Original content", id="original")
|
|
414
|
+
clone = original.clone(clone_id="cloned")
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Access the underlying DOM element when needed:
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
# Most DOM properties and methods are accessible directly.
|
|
421
|
+
element.focus()
|
|
422
|
+
element.scrollIntoView()
|
|
423
|
+
bounding_rect = element.getBoundingClientRect()
|
|
424
|
+
|
|
425
|
+
# Or access the raw DOM element.
|
|
426
|
+
dom_element = element._dom_element
|
|
427
|
+
```
|
|
428
|
+
"""
|
|
429
|
+
|
|
430
|
+
# Lookup table: tag name -> Element subclass.
|
|
25
431
|
element_classes_by_tag_name = {}
|
|
26
432
|
|
|
27
433
|
@classmethod
|
|
28
434
|
def get_tag_name(cls):
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
For classes that have a trailing underscore (because they clash with a Python
|
|
32
|
-
keyword or built-in), we remove it to get the tag name. e.g. for the `input_`
|
|
33
|
-
class, the tag name is `input`.
|
|
435
|
+
"""
|
|
436
|
+
Get the HTML tag name for this class.
|
|
34
437
|
|
|
438
|
+
Classes ending with underscore (e.g. `input_`) have it removed to get
|
|
439
|
+
the actual HTML tag name.
|
|
35
440
|
"""
|
|
36
441
|
return cls.__name__.replace("_", "")
|
|
37
442
|
|
|
38
443
|
@classmethod
|
|
39
444
|
def register_element_classes(cls, element_classes):
|
|
40
|
-
"""
|
|
445
|
+
"""
|
|
446
|
+
Register `Element` subclasses for tag-based lookup.
|
|
447
|
+
"""
|
|
41
448
|
for element_class in element_classes:
|
|
42
449
|
tag_name = element_class.get_tag_name()
|
|
43
450
|
cls.element_classes_by_tag_name[tag_name] = element_class
|
|
44
451
|
|
|
45
452
|
@classmethod
|
|
46
453
|
def unregister_element_classes(cls, element_classes):
|
|
47
|
-
"""
|
|
454
|
+
"""
|
|
455
|
+
Unregister `Element` subclasses from tag-based lookup.
|
|
456
|
+
"""
|
|
48
457
|
for element_class in element_classes:
|
|
49
458
|
tag_name = element_class.get_tag_name()
|
|
50
459
|
cls.element_classes_by_tag_name.pop(tag_name, None)
|
|
51
460
|
|
|
52
461
|
@classmethod
|
|
53
462
|
def wrap_dom_element(cls, dom_element):
|
|
54
|
-
"""
|
|
463
|
+
"""
|
|
464
|
+
Wrap a DOM element in the appropriate `Element` subclass.
|
|
55
465
|
|
|
56
|
-
|
|
57
|
-
|
|
466
|
+
Looks up the subclass by tag name. Unknown tags use the base `Element`
|
|
467
|
+
class.
|
|
58
468
|
"""
|
|
59
469
|
element_cls = cls.element_classes_by_tag_name.get(
|
|
60
470
|
dom_element.tagName.lower(), cls
|
|
61
471
|
)
|
|
62
|
-
|
|
63
472
|
return element_cls(dom_element=dom_element)
|
|
64
473
|
|
|
65
474
|
def __init__(self, dom_element=None, classes=None, style=None, **kwargs):
|
|
66
|
-
"""Create a new, or wrap an existing DOM element.
|
|
67
|
-
|
|
68
|
-
If `dom_element` is None we are being called to *create* a new element.
|
|
69
|
-
Otherwise, we are being called to *wrap* an existing DOM element.
|
|
70
475
|
"""
|
|
71
|
-
|
|
72
|
-
document.createElement(type(self).get_tag_name())
|
|
73
|
-
if is_none(dom_element)
|
|
74
|
-
else dom_element
|
|
75
|
-
)
|
|
476
|
+
Create or wrap a DOM element.
|
|
76
477
|
|
|
77
|
-
|
|
478
|
+
If `dom_element` is `None`, this creates a new element. Otherwise wraps
|
|
479
|
+
the provided DOM element. The `**kwargs` can include HTML attributes
|
|
480
|
+
and event handlers (names starting with `on_`).
|
|
481
|
+
"""
|
|
482
|
+
# Create or wrap the DOM element.
|
|
483
|
+
if is_none(dom_element):
|
|
484
|
+
self._dom_element = document.createElement(type(self).get_tag_name())
|
|
485
|
+
else:
|
|
486
|
+
self._dom_element = dom_element
|
|
487
|
+
# Event handling.
|
|
78
488
|
self._on_events = {}
|
|
79
|
-
|
|
80
|
-
# Handle kwargs for handling named events with a default handler function.
|
|
81
|
-
properties = {}
|
|
82
|
-
for name, handler in kwargs.items():
|
|
83
|
-
if name.startswith("on_"):
|
|
84
|
-
ev = self.get_event(name) # Create the default Event instance.
|
|
85
|
-
ev.add_listener(handler)
|
|
86
|
-
else:
|
|
87
|
-
properties[name] = handler
|
|
88
|
-
|
|
89
|
-
# A set-like interface to the element's `classList`.
|
|
90
|
-
self._classes = Classes(self)
|
|
91
|
-
|
|
92
|
-
# A dict-like interface to the element's `style` attribute.
|
|
93
|
-
self._style = Style(self)
|
|
94
|
-
|
|
95
|
-
# Set any specified classes, styles, and DOM properties.
|
|
96
|
-
self.update(classes=classes, style=style, **properties)
|
|
489
|
+
self.update(classes=classes, style=style, **kwargs)
|
|
97
490
|
|
|
98
491
|
def __eq__(self, obj):
|
|
99
|
-
"""
|
|
492
|
+
"""
|
|
493
|
+
Check equality by comparing underlying DOM elements.
|
|
494
|
+
"""
|
|
100
495
|
return isinstance(obj, Element) and obj._dom_element == self._dom_element
|
|
101
496
|
|
|
102
497
|
def __getitem__(self, key):
|
|
103
|
-
"""
|
|
498
|
+
"""
|
|
499
|
+
Get an item within this element.
|
|
104
500
|
|
|
105
|
-
|
|
106
|
-
|
|
501
|
+
Behaviour depends on the key type:
|
|
502
|
+
|
|
503
|
+
- Integer: returns the child at that index.
|
|
504
|
+
- Slice: returns a collection of children in that slice.
|
|
505
|
+
- String: looks up an element by id (with or without '#' prefix).
|
|
506
|
+
|
|
507
|
+
```python
|
|
508
|
+
element[0] # First child.
|
|
509
|
+
element[1:3] # Second and third children.
|
|
510
|
+
element["my-id"] # Element with id="my-id" (or None).
|
|
511
|
+
element["#my-id"] # Same as above (# is optional).
|
|
512
|
+
```
|
|
107
513
|
"""
|
|
514
|
+
|
|
108
515
|
if isinstance(key, (int, slice)):
|
|
109
516
|
return self.children[key]
|
|
110
|
-
|
|
111
|
-
|
|
517
|
+
if isinstance(key, str):
|
|
518
|
+
return _find_by_id(self._dom_element, key)
|
|
519
|
+
raise TypeError(
|
|
520
|
+
f"Element indices must be integers, slices, or strings, "
|
|
521
|
+
f"not {type(key).__name__}."
|
|
522
|
+
)
|
|
112
523
|
|
|
113
524
|
def __getattr__(self, name):
|
|
114
525
|
"""
|
|
115
526
|
Get an attribute from the element.
|
|
116
527
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
underlying DOM element.
|
|
528
|
+
Attributes starting with `on_` return `Event` instances. Other
|
|
529
|
+
attributes are retrieved from the underlying DOM element.
|
|
120
530
|
"""
|
|
121
531
|
if name.startswith("on_"):
|
|
122
532
|
return self.get_event(name)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
# attribute `for` which is a Python keyword, so you can access it on the
|
|
126
|
-
# Element instance via `for_`).
|
|
127
|
-
if name.endswith("_"):
|
|
128
|
-
name = name[:-1] # noqa: FURB188 No str.removesuffix() in MicroPython.
|
|
129
|
-
if name == "for":
|
|
130
|
-
# The `for` attribute is a special case as it is a keyword in both
|
|
131
|
-
# Python and JavaScript.
|
|
132
|
-
# We need to get it from the underlying DOM element as `htmlFor`.
|
|
133
|
-
name = "htmlFor"
|
|
134
|
-
return getattr(self._dom_element, name)
|
|
533
|
+
dom_name = self._normalize_attribute_name(name)
|
|
534
|
+
return getattr(self._dom_element, dom_name)
|
|
135
535
|
|
|
136
536
|
def __setattr__(self, name, value):
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
537
|
+
"""
|
|
538
|
+
Set an attribute on the element.
|
|
539
|
+
|
|
540
|
+
Private attributes (starting with `_`) are set on the Python object.
|
|
541
|
+
Public attributes are set on the underlying DOM element. Attributes
|
|
542
|
+
starting with `on_` are treated as events.
|
|
543
|
+
"""
|
|
142
544
|
if name.startswith("_"):
|
|
143
545
|
super().__setattr__(name, value)
|
|
144
|
-
|
|
546
|
+
elif name.startswith("on_"):
|
|
547
|
+
# Separate events...
|
|
548
|
+
self.get_event(name).add_listener(value)
|
|
145
549
|
else:
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setattr(self._dom_element, name, value)
|
|
550
|
+
# ...from regular attributes.
|
|
551
|
+
dom_name = self._normalize_attribute_name(name)
|
|
552
|
+
setattr(self._dom_element, dom_name, value)
|
|
553
|
+
|
|
554
|
+
def _normalize_attribute_name(self, name):
|
|
555
|
+
"""
|
|
556
|
+
Normalize Python attribute names to DOM attribute names.
|
|
557
|
+
|
|
558
|
+
Removes trailing underscores and maps special cases.
|
|
559
|
+
"""
|
|
560
|
+
if name.endswith("_"):
|
|
561
|
+
name = name[:-1]
|
|
562
|
+
if name == "for":
|
|
563
|
+
return "htmlFor"
|
|
564
|
+
if name == "class":
|
|
565
|
+
return "className"
|
|
566
|
+
return name
|
|
164
567
|
|
|
165
568
|
def get_event(self, name):
|
|
166
569
|
"""
|
|
167
570
|
Get an `Event` instance for the specified event name.
|
|
571
|
+
|
|
572
|
+
Event names must start with `on_` (e.g. `on_click`). Creates and
|
|
573
|
+
caches `Event` instances that are triggered when the DOM event fires.
|
|
168
574
|
"""
|
|
169
575
|
if not name.startswith("on_"):
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
event_name = name[3:] # Remove the "on_" prefix.
|
|
576
|
+
raise ValueError("Event names must start with 'on_'.")
|
|
577
|
+
event_name = name[3:] # Remove 'on_' prefix.
|
|
173
578
|
if not hasattr(self._dom_element, event_name):
|
|
174
|
-
|
|
175
|
-
raise ValueError(msg)
|
|
579
|
+
raise ValueError(f"Element has no '{event_name}' event.")
|
|
176
580
|
if name in self._on_events:
|
|
177
581
|
return self._on_events[name]
|
|
178
|
-
#
|
|
179
|
-
# wrapped it in an Event instance. Let's do that now. When the
|
|
180
|
-
# underlying DOM element's event is triggered, the Event instance
|
|
181
|
-
# will be triggered too.
|
|
582
|
+
# Create Event instance and wire it to the DOM event.
|
|
182
583
|
ev = Event()
|
|
183
584
|
self._on_events[name] = ev
|
|
184
585
|
self._dom_element.addEventListener(event_name, create_proxy(ev.trigger))
|
|
@@ -186,199 +587,307 @@ class Element:
|
|
|
186
587
|
|
|
187
588
|
@property
|
|
188
589
|
def children(self):
|
|
189
|
-
"""
|
|
590
|
+
"""
|
|
591
|
+
Return this element's children as an `ElementCollection`.
|
|
592
|
+
"""
|
|
190
593
|
return ElementCollection.wrap_dom_elements(self._dom_element.children)
|
|
191
594
|
|
|
192
595
|
@property
|
|
193
596
|
def classes(self):
|
|
194
|
-
"""
|
|
597
|
+
"""
|
|
598
|
+
Return the element's CSS classes as a `set`-like `Classes` object.
|
|
599
|
+
|
|
600
|
+
Supports set operations: `add`, `remove`, `discard`, `clear`.
|
|
601
|
+
Check membership with `in`, iterate with `for`, get length with `len()`.
|
|
602
|
+
|
|
603
|
+
```python
|
|
604
|
+
element.classes.add("active")
|
|
605
|
+
if "disabled" in element.classes:
|
|
606
|
+
...
|
|
607
|
+
```
|
|
608
|
+
"""
|
|
609
|
+
if not hasattr(self, "_classes"):
|
|
610
|
+
self._classes = Classes(self)
|
|
195
611
|
return self._classes
|
|
196
612
|
|
|
613
|
+
@property
|
|
614
|
+
def style(self):
|
|
615
|
+
"""
|
|
616
|
+
Return the element's CSS styles as a `dict`-like `Style` object.
|
|
617
|
+
|
|
618
|
+
Access using `dict`-style syntax with standard
|
|
619
|
+
[CSS property names (hyphenated)](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference).
|
|
620
|
+
|
|
621
|
+
```python
|
|
622
|
+
element.style["background-color"] = "red"
|
|
623
|
+
element.style["font-size"] = "16px"
|
|
624
|
+
del element.style["margin"]
|
|
625
|
+
```
|
|
626
|
+
"""
|
|
627
|
+
if not hasattr(self, "_style"):
|
|
628
|
+
self._style = Style(self)
|
|
629
|
+
return self._style
|
|
630
|
+
|
|
197
631
|
@property
|
|
198
632
|
def parent(self):
|
|
199
|
-
"""
|
|
633
|
+
"""
|
|
634
|
+
Return this element's parent `Element`, or `None`.
|
|
635
|
+
"""
|
|
200
636
|
if is_none(self._dom_element.parentElement):
|
|
201
637
|
return None
|
|
202
|
-
|
|
203
638
|
return Element.wrap_dom_element(self._dom_element.parentElement)
|
|
204
639
|
|
|
205
|
-
@property
|
|
206
|
-
def style(self):
|
|
207
|
-
"""Return the element's `style` attribute as a `Style` instance."""
|
|
208
|
-
return self._style
|
|
209
|
-
|
|
210
640
|
def append(self, *items):
|
|
211
|
-
"""
|
|
641
|
+
"""
|
|
642
|
+
Append items to this element's `children`.
|
|
643
|
+
|
|
644
|
+
Accepts `Element` instances, `ElementCollection` instances, lists,
|
|
645
|
+
tuples, raw DOM elements, and NodeLists.
|
|
646
|
+
"""
|
|
212
647
|
for item in items:
|
|
213
648
|
if isinstance(item, Element):
|
|
214
649
|
self._dom_element.appendChild(item._dom_element)
|
|
215
|
-
|
|
216
650
|
elif isinstance(item, ElementCollection):
|
|
217
651
|
for element in item:
|
|
218
652
|
self._dom_element.appendChild(element._dom_element)
|
|
219
|
-
|
|
220
|
-
# We check for list/tuple here and NOT for any iterable as it will match
|
|
221
|
-
# a JS Nodelist which is handled explicitly below.
|
|
222
|
-
# NodeList.
|
|
223
653
|
elif isinstance(item, (list, tuple)):
|
|
224
654
|
for child in item:
|
|
225
655
|
self.append(child)
|
|
226
|
-
|
|
656
|
+
elif hasattr(item, "tagName"):
|
|
657
|
+
# Raw DOM element.
|
|
658
|
+
self._dom_element.appendChild(item)
|
|
659
|
+
elif hasattr(item, "length"):
|
|
660
|
+
# NodeList or similar iterable.
|
|
661
|
+
for element in item:
|
|
662
|
+
self._dom_element.appendChild(element)
|
|
227
663
|
else:
|
|
228
|
-
|
|
229
|
-
# we guess that it's either a DOM element or NodeList returned via the
|
|
230
|
-
# ffi.
|
|
231
|
-
try:
|
|
232
|
-
# First, we try to see if it's an element by accessing the 'tagName'
|
|
233
|
-
# attribute.
|
|
234
|
-
item.tagName
|
|
235
|
-
self._dom_element.appendChild(item)
|
|
236
|
-
|
|
237
|
-
except AttributeError:
|
|
238
|
-
try:
|
|
239
|
-
# Ok, it's not an element, so let's see if it's a NodeList by
|
|
240
|
-
# accessing the 'length' attribute.
|
|
241
|
-
item.length
|
|
242
|
-
for element_ in item:
|
|
243
|
-
self._dom_element.appendChild(element_)
|
|
244
|
-
|
|
245
|
-
except AttributeError:
|
|
246
|
-
# Nope! This is not an element or a NodeList.
|
|
247
|
-
msg = (
|
|
248
|
-
f'Element "{item}" is a proxy object, "'
|
|
249
|
-
f"but not a valid element or a NodeList."
|
|
250
|
-
)
|
|
251
|
-
raise TypeError(msg)
|
|
664
|
+
raise TypeError(f"Cannot append {type(item).__name__} to element.")
|
|
252
665
|
|
|
253
666
|
def clone(self, clone_id=None):
|
|
254
|
-
"""
|
|
667
|
+
"""
|
|
668
|
+
Clone this element and its underlying DOM element.
|
|
669
|
+
|
|
670
|
+
Optionally assign a new `id` to the clone.
|
|
671
|
+
"""
|
|
255
672
|
clone = Element.wrap_dom_element(self._dom_element.cloneNode(True))
|
|
256
673
|
clone.id = clone_id
|
|
257
674
|
return clone
|
|
258
675
|
|
|
259
676
|
def find(self, selector):
|
|
260
|
-
"""
|
|
677
|
+
"""
|
|
678
|
+
Find all descendant elements matching the
|
|
679
|
+
[CSS `selector`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors).
|
|
261
680
|
|
|
262
|
-
|
|
681
|
+
Returns an `ElementCollection` (possibly empty).
|
|
682
|
+
|
|
683
|
+
```python
|
|
684
|
+
element.find("div") # All div descendants.
|
|
685
|
+
element.find(".my-class") # All elements with class.
|
|
686
|
+
element.find("#my-id") # Element with id (as collection).
|
|
687
|
+
element.find("div.my-class") # All divs with class.
|
|
688
|
+
```
|
|
263
689
|
"""
|
|
264
|
-
return
|
|
265
|
-
self._dom_element.querySelectorAll(selector)
|
|
266
|
-
)
|
|
690
|
+
return _find_and_wrap(self._dom_element, selector)
|
|
267
691
|
|
|
268
692
|
def show_me(self):
|
|
269
|
-
"""
|
|
693
|
+
"""
|
|
694
|
+
Scroll this element into view.
|
|
695
|
+
"""
|
|
270
696
|
self._dom_element.scrollIntoView()
|
|
271
697
|
|
|
272
698
|
def update(self, classes=None, style=None, **kwargs):
|
|
273
|
-
"""
|
|
699
|
+
"""
|
|
700
|
+
Update this element's `classes`, `style`, and `attributes`
|
|
701
|
+
(via arbitrary `**kwargs`).
|
|
274
702
|
|
|
703
|
+
Convenience method for bulk updates.
|
|
704
|
+
"""
|
|
275
705
|
if classes:
|
|
276
|
-
|
|
277
|
-
|
|
706
|
+
if isinstance(classes, str):
|
|
707
|
+
self.classes.add(classes)
|
|
708
|
+
else:
|
|
709
|
+
for class_name in classes:
|
|
710
|
+
self.classes.add(class_name)
|
|
278
711
|
if style:
|
|
279
|
-
|
|
280
|
-
|
|
712
|
+
for key, value in style.items():
|
|
713
|
+
self.style[key] = value
|
|
281
714
|
for name, value in kwargs.items():
|
|
282
715
|
setattr(self, name, value)
|
|
283
716
|
|
|
284
717
|
|
|
285
|
-
class Classes:
|
|
286
|
-
"""
|
|
718
|
+
class Classes(set):
|
|
719
|
+
"""
|
|
720
|
+
Behaves like a Python `set` with changes automatically reflected in the
|
|
721
|
+
element's `classList`.
|
|
722
|
+
|
|
723
|
+
```python
|
|
724
|
+
# Add and remove classes.
|
|
725
|
+
element.classes.add("active")
|
|
726
|
+
element.classes.remove("inactive")
|
|
727
|
+
element.classes.discard("maybe-missing") # No error if absent.
|
|
728
|
+
|
|
729
|
+
# Check membership.
|
|
730
|
+
if "active" in element.classes:
|
|
731
|
+
print("Element is active")
|
|
732
|
+
|
|
733
|
+
# Clear all classes.
|
|
734
|
+
element.classes.clear()
|
|
735
|
+
|
|
736
|
+
# Iterate over classes.
|
|
737
|
+
for cls in element.classes:
|
|
738
|
+
print(cls)
|
|
739
|
+
```
|
|
740
|
+
"""
|
|
287
741
|
|
|
288
|
-
def __init__(self, element
|
|
289
|
-
|
|
290
|
-
self._class_list =
|
|
742
|
+
def __init__(self, element):
|
|
743
|
+
"""Initialise the Classes set for the given element."""
|
|
744
|
+
self._class_list = element._dom_element.classList
|
|
745
|
+
super().__init__(self._class_list)
|
|
291
746
|
|
|
292
|
-
def
|
|
293
|
-
|
|
747
|
+
def _extract_class_names(self, class_name):
|
|
748
|
+
"""
|
|
749
|
+
If the class_name contains several class names separated by spaces,
|
|
750
|
+
split them and return as a list. Otherwise, return the class_name as is
|
|
751
|
+
in a list.
|
|
752
|
+
"""
|
|
753
|
+
return (
|
|
754
|
+
[name for name in class_name.split() if name]
|
|
755
|
+
if " " in class_name
|
|
756
|
+
else [class_name]
|
|
757
|
+
)
|
|
294
758
|
|
|
295
|
-
def
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
759
|
+
def add(self, class_name):
|
|
760
|
+
"""Add a class."""
|
|
761
|
+
for name in self._extract_class_names(class_name):
|
|
762
|
+
super().add(name)
|
|
763
|
+
self._class_list.add(name)
|
|
764
|
+
|
|
765
|
+
def remove(self, class_name):
|
|
766
|
+
"""Remove a class."""
|
|
767
|
+
for name in self._extract_class_names(class_name):
|
|
768
|
+
super().remove(name)
|
|
769
|
+
self._class_list.remove(name)
|
|
770
|
+
|
|
771
|
+
def discard(self, class_name):
|
|
772
|
+
"""Remove a class if present."""
|
|
773
|
+
for name in self._extract_class_names(class_name):
|
|
774
|
+
super().discard(name)
|
|
775
|
+
if name in self._class_list:
|
|
776
|
+
self._class_list.remove(name)
|
|
299
777
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
778
|
+
def clear(self):
|
|
779
|
+
"""Remove all classes."""
|
|
780
|
+
super().clear()
|
|
781
|
+
while self._class_list.length > 0:
|
|
782
|
+
self._class_list.remove(self._class_list.item(0))
|
|
305
783
|
|
|
306
|
-
except TypeError:
|
|
307
|
-
return False
|
|
308
784
|
|
|
309
|
-
|
|
785
|
+
class Style(dict):
|
|
786
|
+
"""
|
|
787
|
+
Behaves like a Python `dict` with changes automatically reflected in the
|
|
788
|
+
element's `style` attribute.
|
|
310
789
|
|
|
311
|
-
|
|
312
|
-
|
|
790
|
+
```python
|
|
791
|
+
# Set and get styles using CSS property names (hyphenated).
|
|
792
|
+
element.style["color"] = "red"
|
|
793
|
+
element.style["background-color"] = "#f0f0f0"
|
|
794
|
+
element.style["font-size"] = "16px"
|
|
313
795
|
|
|
314
|
-
|
|
315
|
-
|
|
796
|
+
# Get a style value.
|
|
797
|
+
color = element.style["color"]
|
|
316
798
|
|
|
317
|
-
|
|
318
|
-
|
|
799
|
+
# Remove a style.
|
|
800
|
+
del element.style["margin"]
|
|
319
801
|
|
|
320
|
-
|
|
321
|
-
|
|
802
|
+
# Check if a style is set.
|
|
803
|
+
if "color" in element.style:
|
|
804
|
+
print(f"Color is {element.style['color']}")
|
|
805
|
+
```
|
|
806
|
+
"""
|
|
322
807
|
|
|
323
|
-
def
|
|
324
|
-
"""
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for item in class_name:
|
|
328
|
-
self.add(item)
|
|
808
|
+
def __init__(self, element):
|
|
809
|
+
"""Initialise the Style dict for the given element."""
|
|
810
|
+
self._style = element._dom_element.style
|
|
811
|
+
super().__init__()
|
|
329
812
|
|
|
330
|
-
|
|
331
|
-
|
|
813
|
+
def __setitem__(self, key, value):
|
|
814
|
+
"""Set a style property."""
|
|
815
|
+
super().__setitem__(key, value)
|
|
816
|
+
self._style.setProperty(key, str(value))
|
|
332
817
|
|
|
333
|
-
def
|
|
334
|
-
"""
|
|
335
|
-
|
|
818
|
+
def __delitem__(self, key):
|
|
819
|
+
"""Remove a style property."""
|
|
820
|
+
super().__delitem__(key)
|
|
821
|
+
self._style.removeProperty(key)
|
|
336
822
|
|
|
337
|
-
def remove(self, *class_names):
|
|
338
|
-
"""Remove one or more classes from the element."""
|
|
339
|
-
for class_name in class_names:
|
|
340
|
-
if isinstance(class_name, list):
|
|
341
|
-
for item in class_name:
|
|
342
|
-
self.remove(item)
|
|
343
823
|
|
|
344
|
-
|
|
345
|
-
|
|
824
|
+
class HasOptions:
|
|
825
|
+
"""
|
|
826
|
+
Mixin for elements with options (`datalist`, `optgroup`, `select`).
|
|
346
827
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
self.remove(old_class)
|
|
350
|
-
self.add(new_class)
|
|
828
|
+
Provides an `options` property that returns an `Options` instance. Used
|
|
829
|
+
in conjunction with the `Options` class.
|
|
351
830
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if class_name in self:
|
|
356
|
-
self.remove(class_name)
|
|
831
|
+
```python
|
|
832
|
+
# Get a select element and work with its options.
|
|
833
|
+
select = web.page["my-select"]
|
|
357
834
|
|
|
358
|
-
|
|
359
|
-
|
|
835
|
+
# Add options.
|
|
836
|
+
select.options.add(value="1", html="Option 1")
|
|
837
|
+
select.options.add(value="2", html="Option 2", selected=True)
|
|
360
838
|
|
|
839
|
+
# Get the selected option.
|
|
840
|
+
selected = select.options.selected
|
|
361
841
|
|
|
362
|
-
|
|
363
|
-
|
|
842
|
+
# Iterate over options.
|
|
843
|
+
for option in select.options:
|
|
844
|
+
print(f"{option.value}: {option.innerHTML}")
|
|
364
845
|
|
|
365
|
-
|
|
846
|
+
# Clear all options.
|
|
847
|
+
select.options.clear()
|
|
848
|
+
```
|
|
366
849
|
"""
|
|
367
850
|
|
|
368
851
|
@property
|
|
369
852
|
def options(self):
|
|
370
|
-
"""Return
|
|
853
|
+
"""Return this element's options as an `Options` instance."""
|
|
371
854
|
if not hasattr(self, "_options"):
|
|
372
855
|
self._options = Options(self)
|
|
373
|
-
|
|
374
856
|
return self._options
|
|
375
857
|
|
|
376
858
|
|
|
377
859
|
class Options:
|
|
378
|
-
"""
|
|
860
|
+
"""
|
|
861
|
+
Interface to the options of a `datalist`, `optgroup`, or `select` element.
|
|
862
|
+
|
|
863
|
+
Supports adding, removing, and accessing option elements. Used in
|
|
864
|
+
conjunction with the `HasOptions` mixin.
|
|
865
|
+
|
|
866
|
+
```python
|
|
867
|
+
# Add options to a select element.
|
|
868
|
+
select.options.add(value="1", html="Option 1")
|
|
869
|
+
select.options.add(value="2", html="Option 2", selected=True)
|
|
870
|
+
|
|
871
|
+
# Insert option at specific position.
|
|
872
|
+
select.options.add(value="1.5", html="Option 1.5", before=1)
|
|
873
|
+
|
|
874
|
+
# Access options by index.
|
|
875
|
+
first_option = select.options[0]
|
|
876
|
+
|
|
877
|
+
# Get the selected option.
|
|
878
|
+
selected = select.options.selected
|
|
879
|
+
print(f"Selected: {selected.value}")
|
|
880
|
+
|
|
881
|
+
# Iterate over all options.
|
|
882
|
+
for option in select.options:
|
|
883
|
+
print(option.innerHTML)
|
|
884
|
+
|
|
885
|
+
# Remove option by index.
|
|
886
|
+
select.options.remove(0)
|
|
379
887
|
|
|
380
|
-
|
|
381
|
-
|
|
888
|
+
# Clear all options.
|
|
889
|
+
select.options.clear()
|
|
890
|
+
```
|
|
382
891
|
"""
|
|
383
892
|
|
|
384
893
|
def __init__(self, element):
|
|
@@ -394,30 +903,39 @@ class Options:
|
|
|
394
903
|
return len(self.options)
|
|
395
904
|
|
|
396
905
|
def __repr__(self):
|
|
397
|
-
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
|
|
906
|
+
return f"{self.__class__.__name__} (length: {len(self)}) " f"{self.options}"
|
|
398
907
|
|
|
399
908
|
@property
|
|
400
909
|
def options(self):
|
|
401
|
-
"""
|
|
910
|
+
"""
|
|
911
|
+
Return the list of option elements.
|
|
912
|
+
"""
|
|
402
913
|
return [Element.wrap_dom_element(o) for o in self._element._dom_element.options]
|
|
403
914
|
|
|
404
915
|
@property
|
|
405
916
|
def selected(self):
|
|
406
|
-
"""
|
|
917
|
+
"""
|
|
918
|
+
Return the currently selected option.
|
|
919
|
+
"""
|
|
407
920
|
return self.options[self._element._dom_element.selectedIndex]
|
|
408
921
|
|
|
409
922
|
def add(self, value=None, html=None, text=None, before=None, **kwargs):
|
|
410
|
-
"""
|
|
411
|
-
|
|
412
|
-
kwargs["value"] = value
|
|
923
|
+
"""
|
|
924
|
+
Add a new option to the element.
|
|
413
925
|
|
|
414
|
-
|
|
926
|
+
Can specify `value`, `html` content, and `text`. The `before` parameter can
|
|
927
|
+
be an `Element` or index to insert before. The `**kwargs` are additional
|
|
928
|
+
arbitrary attributes for the new option element.
|
|
929
|
+
"""
|
|
930
|
+
if value:
|
|
931
|
+
kwargs["value"] = value
|
|
932
|
+
if html:
|
|
415
933
|
kwargs["innerHTML"] = html
|
|
416
|
-
|
|
417
|
-
if text is not None:
|
|
934
|
+
if text:
|
|
418
935
|
kwargs["text"] = text
|
|
419
936
|
|
|
420
|
-
|
|
937
|
+
# The `option` element class is dynamically created below.
|
|
938
|
+
new_option = option(**kwargs) # noqa: F821
|
|
421
939
|
|
|
422
940
|
if before and isinstance(before, Element):
|
|
423
941
|
before = before._dom_element
|
|
@@ -425,56 +943,62 @@ class Options:
|
|
|
425
943
|
self._element._dom_element.add(new_option._dom_element, before)
|
|
426
944
|
|
|
427
945
|
def clear(self):
|
|
428
|
-
"""
|
|
429
|
-
|
|
430
|
-
|
|
946
|
+
"""
|
|
947
|
+
Remove all options.
|
|
948
|
+
"""
|
|
949
|
+
self._element._dom_element.length = 0
|
|
431
950
|
|
|
432
951
|
def remove(self, index):
|
|
433
|
-
"""
|
|
952
|
+
"""
|
|
953
|
+
Remove the option at the specified `index`.
|
|
954
|
+
"""
|
|
434
955
|
self._element._dom_element.remove(index)
|
|
435
956
|
|
|
436
957
|
|
|
437
|
-
class
|
|
438
|
-
"""
|
|
439
|
-
|
|
440
|
-
def __init__(self, element: Element):
|
|
441
|
-
self._element = element
|
|
442
|
-
self._style = self._element._dom_element.style
|
|
443
|
-
|
|
444
|
-
def __getitem__(self, key):
|
|
445
|
-
return self._style.getPropertyValue(key)
|
|
958
|
+
class ContainerElement(Element):
|
|
959
|
+
"""
|
|
960
|
+
Base class for elements that can contain other elements.
|
|
446
961
|
|
|
447
|
-
|
|
448
|
-
self._style.setProperty(key, value)
|
|
962
|
+
Extends `Element` with convenient child handling during initialization.
|
|
449
963
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
self._style.removeProperty(key)
|
|
964
|
+
```python
|
|
965
|
+
from pyscript import web
|
|
453
966
|
|
|
454
|
-
def set(self, **kwargs):
|
|
455
|
-
"""Set one or more CSS properties on the element."""
|
|
456
|
-
for key, value in kwargs.items():
|
|
457
|
-
self._element._dom_element.style.setProperty(key, value)
|
|
458
967
|
|
|
459
|
-
#
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
return self._element._dom_element.style.visibility
|
|
968
|
+
# Create with child elements as arguments.
|
|
969
|
+
div = web.div(
|
|
970
|
+
web.h1("Title"),
|
|
971
|
+
web.p("Paragraph 1"),
|
|
972
|
+
web.p("Paragraph 2")
|
|
973
|
+
)
|
|
466
974
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
self._element._dom_element.style.visibility = value
|
|
975
|
+
# Or use the children keyword argument.
|
|
976
|
+
div = web.div(children=[web.p("Child 1"), web.p("Child 2")])
|
|
470
977
|
|
|
978
|
+
# Mix elements and HTML strings.
|
|
979
|
+
div = web.div(
|
|
980
|
+
web.h1("Title"),
|
|
981
|
+
"<p>HTML content</p>",
|
|
982
|
+
web.button("Click me")
|
|
983
|
+
)
|
|
471
984
|
|
|
472
|
-
|
|
473
|
-
|
|
985
|
+
# Iterate over children.
|
|
986
|
+
for child in div:
|
|
987
|
+
print(child.innerHTML)
|
|
988
|
+
```
|
|
989
|
+
"""
|
|
474
990
|
|
|
475
991
|
def __init__(
|
|
476
992
|
self, *args, children=None, dom_element=None, style=None, classes=None, **kwargs
|
|
477
993
|
):
|
|
994
|
+
"""
|
|
995
|
+
Create a container element with optional `children`.
|
|
996
|
+
|
|
997
|
+
Children can be passed as positional `*args` or via the `children`
|
|
998
|
+
keyword argument. String children are inserted as unescaped HTML. The
|
|
999
|
+
`style`, `classes`, and `**kwargs` are passed to the base `Element`
|
|
1000
|
+
initializer.
|
|
1001
|
+
"""
|
|
478
1002
|
super().__init__(
|
|
479
1003
|
dom_element=dom_element, style=style, classes=classes, **kwargs
|
|
480
1004
|
)
|
|
@@ -482,7 +1006,6 @@ class ContainerElement(Element):
|
|
|
482
1006
|
for child in list(args) + (children or []):
|
|
483
1007
|
if isinstance(child, (Element, ElementCollection)):
|
|
484
1008
|
self.append(child)
|
|
485
|
-
|
|
486
1009
|
else:
|
|
487
1010
|
self._dom_element.insertAdjacentHTML("beforeend", child)
|
|
488
1011
|
|
|
@@ -490,124 +1013,93 @@ class ContainerElement(Element):
|
|
|
490
1013
|
yield from self.children
|
|
491
1014
|
|
|
492
1015
|
|
|
493
|
-
class
|
|
494
|
-
"""
|
|
495
|
-
|
|
496
|
-
def __init__(self, collection):
|
|
497
|
-
self._collection = collection
|
|
498
|
-
|
|
499
|
-
def __contains__(self, class_name):
|
|
500
|
-
for element in self._collection:
|
|
501
|
-
if class_name in element.classes:
|
|
502
|
-
return True
|
|
503
|
-
|
|
504
|
-
return False
|
|
505
|
-
|
|
506
|
-
def __eq__(self, other):
|
|
507
|
-
return (
|
|
508
|
-
isinstance(other, ClassesCollection)
|
|
509
|
-
and self._collection == other._collection
|
|
510
|
-
)
|
|
1016
|
+
class ElementCollection:
|
|
1017
|
+
"""
|
|
1018
|
+
A collection of Element instances with `list`-like operations.
|
|
511
1019
|
|
|
512
|
-
|
|
513
|
-
|
|
1020
|
+
Supports iteration, indexing, slicing, and finding descendants.
|
|
1021
|
+
For bulk operations, iterate over the collection explicitly or use
|
|
1022
|
+
the `update_all` method.
|
|
514
1023
|
|
|
515
|
-
|
|
516
|
-
|
|
1024
|
+
```python
|
|
1025
|
+
# Get a collection of elements.
|
|
1026
|
+
items = web.page.find(".item")
|
|
517
1027
|
|
|
518
|
-
|
|
519
|
-
|
|
1028
|
+
# Access by index.
|
|
1029
|
+
first = items[0]
|
|
1030
|
+
last = items[-1]
|
|
520
1031
|
|
|
521
|
-
|
|
522
|
-
|
|
1032
|
+
# Slice the collection.
|
|
1033
|
+
subset = items[1:3]
|
|
523
1034
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
for element in self._collection:
|
|
527
|
-
element.classes.add(*class_names)
|
|
1035
|
+
# Look up a specific element by id (returns None if not found).
|
|
1036
|
+
specific = items["item-id"]
|
|
528
1037
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
1038
|
+
# Iterate over elements.
|
|
1039
|
+
for item in items:
|
|
1040
|
+
item.innerHTML = "Updated"
|
|
1041
|
+
item.classes.add("processed")
|
|
532
1042
|
|
|
533
|
-
|
|
534
|
-
|
|
1043
|
+
# Bulk update all contained elements.
|
|
1044
|
+
items.update_all(innerHTML="Hello", className="updated")
|
|
535
1045
|
|
|
536
|
-
|
|
537
|
-
|
|
1046
|
+
# Find matches within the collection.
|
|
1047
|
+
buttons = items.find("button")
|
|
538
1048
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
1049
|
+
# Get the count.
|
|
1050
|
+
count = len(items)
|
|
1051
|
+
```
|
|
1052
|
+
"""
|
|
543
1053
|
|
|
544
|
-
def toggle(self, *class_names):
|
|
545
|
-
"""Toggle one or more classes on the elements in the collection."""
|
|
546
|
-
for element in self._collection:
|
|
547
|
-
element.classes.toggle(*class_names)
|
|
548
|
-
|
|
549
|
-
def _all_class_names(self):
|
|
550
|
-
all_class_names = set()
|
|
551
|
-
for element in self._collection:
|
|
552
|
-
for class_name in element.classes:
|
|
553
|
-
all_class_names.add(class_name)
|
|
554
|
-
|
|
555
|
-
return all_class_names
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
class StyleCollection:
|
|
559
|
-
"""A dict-like interface to the styles of the elements in a collection."""
|
|
560
|
-
|
|
561
|
-
def __init__(self, collection):
|
|
562
|
-
self._collection = collection
|
|
563
|
-
|
|
564
|
-
def __getitem__(self, key):
|
|
565
|
-
return [element.style[key] for element in self._collection._elements]
|
|
566
|
-
|
|
567
|
-
def __setitem__(self, key, value):
|
|
568
|
-
for element in self._collection._elements:
|
|
569
|
-
element.style[key] = value
|
|
570
|
-
|
|
571
|
-
def __repr__(self):
|
|
572
|
-
return f"StyleCollection({self._collection!r})"
|
|
573
|
-
|
|
574
|
-
def remove(self, key):
|
|
575
|
-
"""Remove a CSS property from the elements in the collection."""
|
|
576
|
-
for element in self._collection._elements:
|
|
577
|
-
element.style.remove(key)
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
class ElementCollection:
|
|
581
1054
|
@classmethod
|
|
582
1055
|
def wrap_dom_elements(cls, dom_elements):
|
|
583
|
-
"""
|
|
584
|
-
|
|
1056
|
+
"""
|
|
1057
|
+
Wrap an iterable of DOM elements in an `ElementCollection`.
|
|
1058
|
+
"""
|
|
585
1059
|
return cls(
|
|
586
1060
|
[Element.wrap_dom_element(dom_element) for dom_element in dom_elements]
|
|
587
1061
|
)
|
|
588
1062
|
|
|
589
|
-
def __init__(self, elements
|
|
1063
|
+
def __init__(self, elements):
|
|
590
1064
|
self._elements = elements
|
|
591
|
-
self._classes = ClassesCollection(self)
|
|
592
|
-
self._style = StyleCollection(self)
|
|
593
1065
|
|
|
594
1066
|
def __eq__(self, obj):
|
|
595
|
-
"""
|
|
1067
|
+
"""
|
|
1068
|
+
Check equality by comparing elements.
|
|
1069
|
+
"""
|
|
596
1070
|
return isinstance(obj, ElementCollection) and obj._elements == self._elements
|
|
597
1071
|
|
|
598
1072
|
def __getitem__(self, key):
|
|
599
|
-
"""
|
|
1073
|
+
"""
|
|
1074
|
+
Get items from the collection.
|
|
600
1075
|
|
|
601
|
-
|
|
602
|
-
|
|
1076
|
+
Behaviour depends on the key type:
|
|
1077
|
+
|
|
1078
|
+
- Integer: returns the element at that index.
|
|
1079
|
+
- Slice: returns a new collection with elements in that slice.
|
|
1080
|
+
- String: looks up an element by id (with or without '#' prefix).
|
|
1081
|
+
|
|
1082
|
+
```python
|
|
1083
|
+
collection[0] # First element.
|
|
1084
|
+
collection[1:3] # New collection with 2nd and 3rd elements.
|
|
1085
|
+
collection["my-id"] # Element with id="my-id" (or None).
|
|
1086
|
+
collection["#my-id"] # Same as above (# is optional).
|
|
1087
|
+
```
|
|
603
1088
|
"""
|
|
604
1089
|
if isinstance(key, int):
|
|
605
1090
|
return self._elements[key]
|
|
606
|
-
|
|
607
1091
|
if isinstance(key, slice):
|
|
608
1092
|
return ElementCollection(self._elements[key])
|
|
609
|
-
|
|
610
|
-
|
|
1093
|
+
if isinstance(key, str):
|
|
1094
|
+
for element in self._elements:
|
|
1095
|
+
result = _find_by_id(element._dom_element, key)
|
|
1096
|
+
if result:
|
|
1097
|
+
return result
|
|
1098
|
+
return None
|
|
1099
|
+
raise TypeError(
|
|
1100
|
+
f"Collection indices must be integers, slices, or strings, "
|
|
1101
|
+
f"not {type(key).__name__}"
|
|
1102
|
+
)
|
|
611
1103
|
|
|
612
1104
|
def __iter__(self):
|
|
613
1105
|
yield from self._elements
|
|
@@ -621,621 +1113,327 @@ class ElementCollection:
|
|
|
621
1113
|
f"{self._elements}"
|
|
622
1114
|
)
|
|
623
1115
|
|
|
624
|
-
def __getattr__(self, name):
|
|
625
|
-
return [getattr(element, name) for element in self._elements]
|
|
626
|
-
|
|
627
|
-
def __setattr__(self, name, value):
|
|
628
|
-
# This class overrides `__setattr__` to delegate "public" attributes to the
|
|
629
|
-
# elements in the collection. BUT, we don't use the usual Python pattern where
|
|
630
|
-
# we set attributes on the collection itself via `self.__dict__` as that is not
|
|
631
|
-
# yet supported in our build of MicroPython. Instead, we handle it here by
|
|
632
|
-
# using super for all "private" attributes (those starting with an underscore).
|
|
633
|
-
if name.startswith("_"):
|
|
634
|
-
super().__setattr__(name, value)
|
|
635
|
-
|
|
636
|
-
else:
|
|
637
|
-
for element in self._elements:
|
|
638
|
-
setattr(element, name, value)
|
|
639
|
-
|
|
640
|
-
@property
|
|
641
|
-
def classes(self):
|
|
642
|
-
"""Return the classes of the elements in the collection as a `ClassesCollection`."""
|
|
643
|
-
return self._classes
|
|
644
|
-
|
|
645
1116
|
@property
|
|
646
1117
|
def elements(self):
|
|
647
|
-
"""
|
|
1118
|
+
"""
|
|
1119
|
+
Return the underlying `list` of elements.
|
|
1120
|
+
"""
|
|
648
1121
|
return self._elements
|
|
649
1122
|
|
|
650
|
-
@property
|
|
651
|
-
def style(self):
|
|
652
|
-
""""""
|
|
653
|
-
return self._style
|
|
654
|
-
|
|
655
1123
|
def find(self, selector):
|
|
656
|
-
"""
|
|
1124
|
+
"""
|
|
1125
|
+
Find all descendants matching the
|
|
1126
|
+
[CSS `selector`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors).
|
|
657
1127
|
|
|
658
|
-
|
|
1128
|
+
Searches within all elements in the collection.
|
|
1129
|
+
|
|
1130
|
+
```python
|
|
1131
|
+
collection.find("div") # All div descendants.
|
|
1132
|
+
collection.find(".my-class") # All elements with class.
|
|
1133
|
+
collection.find("#my-id") # Element with id (as collection).
|
|
1134
|
+
```
|
|
659
1135
|
"""
|
|
660
1136
|
elements = []
|
|
661
1137
|
for element in self._elements:
|
|
662
|
-
elements.extend(element.
|
|
663
|
-
|
|
1138
|
+
elements.extend(_find_and_wrap(element._dom_element, selector))
|
|
664
1139
|
return ElementCollection(elements)
|
|
665
1140
|
|
|
1141
|
+
def update_all(self, **kwargs):
|
|
1142
|
+
"""
|
|
1143
|
+
Explicitly update all elements with the given attributes.
|
|
666
1144
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
class abbr(ContainerElement):
|
|
677
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
class address(ContainerElement):
|
|
681
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
class area(Element):
|
|
685
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
class article(ContainerElement):
|
|
689
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
class aside(ContainerElement):
|
|
693
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
class audio(ContainerElement):
|
|
697
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
class b(ContainerElement):
|
|
701
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
class base(Element):
|
|
705
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base"""
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
class blockquote(ContainerElement):
|
|
709
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
class body(ContainerElement):
|
|
713
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body"""
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
class br(Element):
|
|
717
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""
|
|
1145
|
+
```python
|
|
1146
|
+
collection.update_all(innerHTML="Hello")
|
|
1147
|
+
collection.update_all(className="active", title="Updated")
|
|
1148
|
+
```
|
|
1149
|
+
"""
|
|
1150
|
+
for element in self._elements:
|
|
1151
|
+
for name, value in kwargs.items():
|
|
1152
|
+
setattr(element, name, value)
|
|
718
1153
|
|
|
719
1154
|
|
|
720
|
-
|
|
721
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""
|
|
1155
|
+
# Special elements with custom methods and mixins.
|
|
722
1156
|
|
|
723
1157
|
|
|
724
1158
|
class canvas(ContainerElement):
|
|
725
|
-
"""
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
Inputs:
|
|
731
|
-
* filename (str): name of the file being downloaded
|
|
1159
|
+
"""
|
|
1160
|
+
A bespoke
|
|
1161
|
+
[HTML canvas element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas)
|
|
1162
|
+
with Pythonic drawing and download capabilities.
|
|
1163
|
+
"""
|
|
732
1164
|
|
|
733
|
-
|
|
734
|
-
None
|
|
1165
|
+
def download(self, filename="snapped.png"):
|
|
735
1166
|
"""
|
|
736
|
-
|
|
1167
|
+
Download the canvas content as an image file.
|
|
737
1168
|
|
|
738
|
-
|
|
739
|
-
|
|
1169
|
+
Creates a temporary download link and triggers it.
|
|
1170
|
+
"""
|
|
1171
|
+
# The `a` element class is dynamically created below.
|
|
1172
|
+
download_link = a( # noqa: F821
|
|
1173
|
+
download=filename, href=self._dom_element.toDataURL()
|
|
1174
|
+
)
|
|
740
1175
|
self.append(download_link)
|
|
741
|
-
|
|
742
1176
|
download_link._dom_element.click()
|
|
743
1177
|
|
|
744
1178
|
def draw(self, what, width=None, height=None):
|
|
745
|
-
"""
|
|
746
|
-
|
|
747
|
-
|
|
1179
|
+
"""
|
|
1180
|
+
Draw a 2d image source (`what`) onto the canvas. Optionally scale to
|
|
1181
|
+
`width` and `height`.
|
|
748
1182
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a
|
|
753
|
-
VideoFrame.
|
|
1183
|
+
Accepts canvas image sources: `HTMLImageElement`, `SVGImageElement`,
|
|
1184
|
+
`HTMLVideoElement`, `HTMLCanvasElement`, `ImageBitmap`,
|
|
1185
|
+
`OffscreenCanvas`, or `VideoFrame`.
|
|
754
1186
|
"""
|
|
755
1187
|
if isinstance(what, Element):
|
|
756
1188
|
what = what._dom_element
|
|
757
1189
|
|
|
758
|
-
# https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
|
|
759
1190
|
ctx = self._dom_element.getContext("2d")
|
|
760
1191
|
if width or height:
|
|
761
1192
|
ctx.drawImage(what, 0, 0, width, height)
|
|
762
|
-
|
|
763
1193
|
else:
|
|
764
1194
|
ctx.drawImage(what, 0, 0)
|
|
765
1195
|
|
|
766
1196
|
|
|
767
|
-
class
|
|
768
|
-
"""
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
"""
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
class code(ContainerElement):
|
|
776
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
class col(Element):
|
|
780
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col"""
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
class colgroup(ContainerElement):
|
|
784
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup"""
|
|
1197
|
+
class video(ContainerElement):
|
|
1198
|
+
"""
|
|
1199
|
+
A bespoke
|
|
1200
|
+
[HTML video element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video)
|
|
1201
|
+
with Pythonic snapshot capability (to render an image to a canvas).
|
|
1202
|
+
"""
|
|
785
1203
|
|
|
1204
|
+
def snap(self, to=None, width=None, height=None):
|
|
1205
|
+
"""
|
|
1206
|
+
Capture a video frame `to` a canvas element. Optionally scale to
|
|
1207
|
+
`width` and `height`.
|
|
786
1208
|
|
|
787
|
-
|
|
788
|
-
|
|
1209
|
+
If no canvas is provided, this will create one. The to parameter
|
|
1210
|
+
can be a canvas Element, raw DOM canvas, or CSS selector string.
|
|
1211
|
+
"""
|
|
1212
|
+
width = width if width else self.videoWidth
|
|
1213
|
+
height = height if height else self.videoHeight
|
|
1214
|
+
if is_none(to):
|
|
1215
|
+
to = canvas(width=width, height=height)
|
|
1216
|
+
elif isinstance(to, Element):
|
|
1217
|
+
if to.tag != "canvas":
|
|
1218
|
+
raise TypeError("Element to snap to must be a canvas.")
|
|
1219
|
+
elif getattr(to, "tagName", "") == "CANVAS":
|
|
1220
|
+
to = canvas(dom_element=to)
|
|
1221
|
+
elif isinstance(to, str):
|
|
1222
|
+
nodelist = document.querySelectorAll(to)
|
|
1223
|
+
if nodelist.length == 0:
|
|
1224
|
+
raise TypeError(f"No element with selector {to} to snap to.")
|
|
1225
|
+
if nodelist[0].tagName != "CANVAS":
|
|
1226
|
+
raise TypeError("Element to snap to must be a canvas.")
|
|
1227
|
+
to = canvas(dom_element=nodelist[0])
|
|
1228
|
+
to.draw(self, width, height)
|
|
1229
|
+
return to
|
|
789
1230
|
|
|
790
1231
|
|
|
791
1232
|
class datalist(ContainerElement, HasOptions):
|
|
792
|
-
"""
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
class dd(ContainerElement):
|
|
796
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
class del_(ContainerElement):
|
|
800
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
class details(ContainerElement):
|
|
804
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
class dialog(ContainerElement):
|
|
808
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
class div(ContainerElement):
|
|
812
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
class dl(ContainerElement):
|
|
816
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
class dt(ContainerElement):
|
|
820
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
class em(ContainerElement):
|
|
824
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
class embed(Element):
|
|
828
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
class fieldset(ContainerElement):
|
|
832
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
class figcaption(ContainerElement):
|
|
836
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
class figure(ContainerElement):
|
|
840
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
class footer(ContainerElement):
|
|
844
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
class form(ContainerElement):
|
|
848
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
class h1(ContainerElement):
|
|
852
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
class h2(ContainerElement):
|
|
856
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
class h3(ContainerElement):
|
|
860
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
class h4(ContainerElement):
|
|
864
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
class h5(ContainerElement):
|
|
868
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
class h6(ContainerElement):
|
|
872
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
class head(ContainerElement):
|
|
876
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head"""
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
class header(ContainerElement):
|
|
880
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
class hgroup(ContainerElement):
|
|
884
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
class hr(Element):
|
|
888
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
class html(ContainerElement):
|
|
892
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html"""
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
class i(ContainerElement):
|
|
896
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
class iframe(ContainerElement):
|
|
900
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
class img(Element):
|
|
904
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
class input_(Element):
|
|
908
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
class ins(ContainerElement):
|
|
912
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
class kbd(ContainerElement):
|
|
916
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
class label(ContainerElement):
|
|
920
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
class legend(ContainerElement):
|
|
924
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
class li(ContainerElement):
|
|
928
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
class link(Element):
|
|
932
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
class main(ContainerElement):
|
|
936
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
class map_(ContainerElement):
|
|
940
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
class mark(ContainerElement):
|
|
944
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
class menu(ContainerElement):
|
|
948
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
class meta(ContainerElement):
|
|
952
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta"""
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
class meter(ContainerElement):
|
|
956
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
class nav(ContainerElement):
|
|
960
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
class object_(ContainerElement):
|
|
964
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""
|
|
965
|
-
|
|
1233
|
+
"""
|
|
1234
|
+
HTML datalist element with options support.
|
|
966
1235
|
|
|
967
|
-
|
|
968
|
-
"""
|
|
1236
|
+
Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist
|
|
1237
|
+
"""
|
|
969
1238
|
|
|
970
1239
|
|
|
971
1240
|
class optgroup(ContainerElement, HasOptions):
|
|
972
|
-
"""
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
class option(ContainerElement):
|
|
976
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
class output(ContainerElement):
|
|
980
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
class p(ContainerElement):
|
|
984
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
class param(ContainerElement):
|
|
988
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/param"""
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
class picture(ContainerElement):
|
|
992
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
class pre(ContainerElement):
|
|
996
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
class progress(ContainerElement):
|
|
1000
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
class q(ContainerElement):
|
|
1004
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
class s(ContainerElement):
|
|
1008
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
class script(ContainerElement):
|
|
1012
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""
|
|
1013
|
-
|
|
1241
|
+
"""
|
|
1242
|
+
HTML optgroup element with options support.
|
|
1014
1243
|
|
|
1015
|
-
|
|
1016
|
-
"""
|
|
1244
|
+
Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup
|
|
1245
|
+
"""
|
|
1017
1246
|
|
|
1018
1247
|
|
|
1019
1248
|
class select(ContainerElement, HasOptions):
|
|
1020
|
-
"""
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
class small(ContainerElement):
|
|
1024
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
class source(Element):
|
|
1028
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
class span(ContainerElement):
|
|
1032
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
class strong(ContainerElement):
|
|
1036
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
class style(ContainerElement):
|
|
1040
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
class sub(ContainerElement):
|
|
1044
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
class summary(ContainerElement):
|
|
1048
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
class sup(ContainerElement):
|
|
1052
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
class table(ContainerElement):
|
|
1056
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
class tbody(ContainerElement):
|
|
1060
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
class td(ContainerElement):
|
|
1064
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
class template(ContainerElement):
|
|
1068
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
class textarea(ContainerElement):
|
|
1072
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
class tfoot(ContainerElement):
|
|
1076
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
class th(ContainerElement):
|
|
1080
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
class thead(ContainerElement):
|
|
1084
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
class time(ContainerElement):
|
|
1088
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
class title(ContainerElement):
|
|
1092
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
class tr(ContainerElement):
|
|
1096
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
class track(Element):
|
|
1100
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
class u(ContainerElement):
|
|
1104
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
class ul(ContainerElement):
|
|
1108
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
class var(ContainerElement):
|
|
1112
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
class video(ContainerElement):
|
|
1116
|
-
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""
|
|
1117
|
-
|
|
1118
|
-
def snap(
|
|
1119
|
-
self,
|
|
1120
|
-
to: Element | str = None,
|
|
1121
|
-
width: int | None = None,
|
|
1122
|
-
height: int | None = None,
|
|
1123
|
-
):
|
|
1124
|
-
"""
|
|
1125
|
-
Capture a snapshot (i.e. a single frame) of a video to a canvas.
|
|
1249
|
+
"""
|
|
1250
|
+
HTML select element with options support.
|
|
1126
1251
|
|
|
1127
|
-
|
|
1252
|
+
Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select
|
|
1253
|
+
"""
|
|
1128
1254
|
|
|
1129
|
-
* to: the canvas to save the video frame to (if None, one is created).
|
|
1130
|
-
* width: width of the snapshot (defaults to the video width).
|
|
1131
|
-
* height: height of the snapshot (defaults to the video height).
|
|
1132
1255
|
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1256
|
+
# Container elements that can have children.
|
|
1257
|
+
# Note: canvas, video, datalist, optgroup, and select are defined above
|
|
1258
|
+
# with special implementations due to the HasOptions mixin.
|
|
1259
|
+
# fmt: off
|
|
1260
|
+
CONTAINER_TAGS = [
|
|
1261
|
+
"a", "abbr", "address", "article", "aside", "audio",
|
|
1262
|
+
"b", "blockquote", "body", "button",
|
|
1263
|
+
"caption", "cite", "code", "colgroup",
|
|
1264
|
+
"data", "dd", "del", "details", "dialog", "div", "dl", "dt",
|
|
1265
|
+
"em",
|
|
1266
|
+
"fieldset", "figcaption", "figure", "footer", "form",
|
|
1267
|
+
"h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "html",
|
|
1268
|
+
"i", "iframe", "ins",
|
|
1269
|
+
"kbd",
|
|
1270
|
+
"label", "legend", "li",
|
|
1271
|
+
"main", "map", "mark", "menu", "meta", "meter",
|
|
1272
|
+
"nav",
|
|
1273
|
+
"object", "ol", "option", "output",
|
|
1274
|
+
"p", "param", "picture", "pre", "progress",
|
|
1275
|
+
"q",
|
|
1276
|
+
"s", "script", "section", "small", "span", "strong", "style",
|
|
1277
|
+
"sub", "summary", "sup",
|
|
1278
|
+
"table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead",
|
|
1279
|
+
"time", "title", "tr",
|
|
1280
|
+
"u", "ul",
|
|
1281
|
+
"var",
|
|
1282
|
+
"wbr",
|
|
1283
|
+
]
|
|
1284
|
+
"""
|
|
1285
|
+
Container elements that can have children. Each becomes a class in the
|
|
1286
|
+
`pyscript.web` namespace and corresponds to an HTML tag.
|
|
1287
|
+
"""
|
|
1288
|
+
# fmt: on
|
|
1138
1289
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1290
|
+
# Void elements that cannot have children.
|
|
1291
|
+
VOID_TAGS = [
|
|
1292
|
+
"area",
|
|
1293
|
+
"base",
|
|
1294
|
+
"br",
|
|
1295
|
+
"col",
|
|
1296
|
+
"embed",
|
|
1297
|
+
"hr",
|
|
1298
|
+
"img",
|
|
1299
|
+
"input",
|
|
1300
|
+
"link",
|
|
1301
|
+
"source",
|
|
1302
|
+
"track",
|
|
1303
|
+
]
|
|
1304
|
+
"""
|
|
1305
|
+
Void elements that cannot have children. Each becomes a class in the
|
|
1306
|
+
`pyscript.web` namespace and corresponds to an HTML tag.
|
|
1307
|
+
"""
|
|
1141
1308
|
|
|
1142
|
-
elif isinstance(to, Element):
|
|
1143
|
-
if to.tag != "canvas":
|
|
1144
|
-
msg = "Element to snap to must be a canvas."
|
|
1145
|
-
raise TypeError(msg)
|
|
1146
1309
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1310
|
+
def _create_element_classes():
|
|
1311
|
+
"""
|
|
1312
|
+
Create element classes dynamically and register them.
|
|
1149
1313
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1314
|
+
Generates classes for all standard HTML elements, using the appropriate
|
|
1315
|
+
base class (ContainerElement or Element) for each tag.
|
|
1316
|
+
"""
|
|
1317
|
+
# The existing special element classes defined above.
|
|
1318
|
+
classes = [canvas, video, datalist, optgroup, select]
|
|
1319
|
+
for tag in CONTAINER_TAGS:
|
|
1320
|
+
# Tags that clash with Python keywords get a trailing underscore.
|
|
1321
|
+
class_name = f"{tag}_" if tag in ("del", "map", "object") else tag
|
|
1322
|
+
doc = (
|
|
1323
|
+
f"HTML <{tag}> element. "
|
|
1324
|
+
f"Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/"
|
|
1325
|
+
f"Element/{tag}"
|
|
1326
|
+
)
|
|
1327
|
+
cls = type(class_name, (ContainerElement,), {"__doc__": doc})
|
|
1328
|
+
globals()[class_name] = cls
|
|
1329
|
+
classes.append(cls)
|
|
1330
|
+
for tag in VOID_TAGS:
|
|
1331
|
+
class_name = f"{tag}_" if tag == "input" else tag
|
|
1332
|
+
doc = (
|
|
1333
|
+
f"HTML <{tag}> element. "
|
|
1334
|
+
f"Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/"
|
|
1335
|
+
f"Element/{tag}"
|
|
1336
|
+
)
|
|
1337
|
+
cls = type(class_name, (Element,), {"__doc__": doc})
|
|
1338
|
+
globals()[class_name] = cls
|
|
1339
|
+
classes.append(cls)
|
|
1340
|
+
Element.register_element_classes(classes)
|
|
1156
1341
|
|
|
1157
|
-
if nodelist[0].tagName != "CANVAS":
|
|
1158
|
-
msg = "Element to snap to must be a canvas."
|
|
1159
|
-
raise TypeError(msg)
|
|
1160
1342
|
|
|
1161
|
-
|
|
1343
|
+
# Initialize element classes at module load time. :-)
|
|
1344
|
+
_create_element_classes()
|
|
1162
1345
|
|
|
1163
|
-
to.draw(self, width, height)
|
|
1164
1346
|
|
|
1165
|
-
|
|
1347
|
+
class Page:
|
|
1348
|
+
"""
|
|
1349
|
+
Represents the current web page.
|
|
1166
1350
|
|
|
1351
|
+
Provides access to the document's `html`, `head`, and `body` elements,
|
|
1352
|
+
plus convenience methods for finding elements and appending to the body.
|
|
1167
1353
|
|
|
1168
|
-
|
|
1169
|
-
|
|
1354
|
+
```python
|
|
1355
|
+
from pyscript import web
|
|
1170
1356
|
|
|
1171
1357
|
|
|
1172
|
-
#
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
canvas, caption, cite, code, col, colgroup,
|
|
1177
|
-
data, datalist, dd, del_, details, dialog, div, dl, dt,
|
|
1178
|
-
em, embed,
|
|
1179
|
-
fieldset, figcaption, figure, footer, form,
|
|
1180
|
-
h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html,
|
|
1181
|
-
i, iframe, img, input_, ins,
|
|
1182
|
-
kbd,
|
|
1183
|
-
label, legend, li, link,
|
|
1184
|
-
main, map_, mark, menu, meta, meter,
|
|
1185
|
-
nav,
|
|
1186
|
-
object_, ol, optgroup, option, output,
|
|
1187
|
-
p, param, picture, pre, progress,
|
|
1188
|
-
q,
|
|
1189
|
-
s, script, section, select, small, source, span, strong, style, sub, summary, sup,
|
|
1190
|
-
table, tbody, td, template, textarea, tfoot, th, thead, time, title, tr, track,
|
|
1191
|
-
u, ul,
|
|
1192
|
-
var, video,
|
|
1193
|
-
wbr,
|
|
1194
|
-
]
|
|
1195
|
-
# fmt: on
|
|
1358
|
+
# Access page structure.
|
|
1359
|
+
web.page.html # The <html> element.
|
|
1360
|
+
web.page.head # The <head> element.
|
|
1361
|
+
web.page.body # The <body> element.
|
|
1196
1362
|
|
|
1363
|
+
# Get and set page title.
|
|
1364
|
+
web.page.title = "New Title"
|
|
1365
|
+
print(web.page.title)
|
|
1197
1366
|
|
|
1198
|
-
#
|
|
1199
|
-
|
|
1367
|
+
# Find elements by CSS selector.
|
|
1368
|
+
divs = web.page.find("div")
|
|
1369
|
+
items = web.page.find(".item-class")
|
|
1200
1370
|
|
|
1371
|
+
# Look up element by id.
|
|
1372
|
+
element = web.page["my-id"]
|
|
1373
|
+
element = web.page["#my-id"] # The "#" prefix is optional.
|
|
1201
1374
|
|
|
1202
|
-
|
|
1203
|
-
|
|
1375
|
+
# Append to the body (shortcut for page.body.append).
|
|
1376
|
+
web.page.append(web.div("Hello"))
|
|
1377
|
+
```
|
|
1378
|
+
"""
|
|
1204
1379
|
|
|
1205
1380
|
def __init__(self):
|
|
1206
1381
|
self.html = Element.wrap_dom_element(document.documentElement)
|
|
1207
1382
|
self.body = Element.wrap_dom_element(document.body)
|
|
1208
1383
|
self.head = Element.wrap_dom_element(document.head)
|
|
1209
1384
|
|
|
1210
|
-
def __getitem__(self,
|
|
1211
|
-
"""
|
|
1385
|
+
def __getitem__(self, key):
|
|
1386
|
+
"""
|
|
1387
|
+
Look up an element by id.
|
|
1388
|
+
|
|
1389
|
+
The '#' prefix is optional and will be stripped if present.
|
|
1390
|
+
Returns None if no element with that id exists.
|
|
1212
1391
|
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1392
|
+
```python
|
|
1393
|
+
page["my-id"] # Element with id="my-id" (or None)
|
|
1394
|
+
page["#my-id"] # Same as above (# is optional)
|
|
1395
|
+
```
|
|
1216
1396
|
"""
|
|
1217
|
-
return
|
|
1397
|
+
return _find_by_id(document, key)
|
|
1218
1398
|
|
|
1219
1399
|
@property
|
|
1220
1400
|
def title(self):
|
|
1221
|
-
"""
|
|
1401
|
+
"""
|
|
1402
|
+
Get the page `title`.
|
|
1403
|
+
"""
|
|
1222
1404
|
return document.title
|
|
1223
1405
|
|
|
1224
1406
|
@title.setter
|
|
1225
1407
|
def title(self, value):
|
|
1226
|
-
"""
|
|
1408
|
+
"""
|
|
1409
|
+
Set the page `title`.
|
|
1410
|
+
"""
|
|
1227
1411
|
document.title = value
|
|
1228
1412
|
|
|
1229
1413
|
def append(self, *items):
|
|
1230
|
-
"""
|
|
1414
|
+
"""
|
|
1415
|
+
Append items to the page `body`.
|
|
1416
|
+
|
|
1417
|
+
Shortcut for `page.body.append(*items)`.
|
|
1418
|
+
"""
|
|
1231
1419
|
self.body.append(*items)
|
|
1232
1420
|
|
|
1233
|
-
def find(self, selector):
|
|
1234
|
-
"""
|
|
1421
|
+
def find(self, selector):
|
|
1422
|
+
"""
|
|
1423
|
+
Find all elements matching the
|
|
1424
|
+
[CSS `selector`](https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Selectors).
|
|
1425
|
+
|
|
1426
|
+
Returns an `ElementCollection` of matching elements.
|
|
1235
1427
|
|
|
1236
|
-
|
|
1428
|
+
```python
|
|
1429
|
+
page.find("div") # All divs on the page
|
|
1430
|
+
page.find(".my-class") # All elements with class
|
|
1431
|
+
page.find("#my-id") # Element with id (as collection)
|
|
1432
|
+
page.find("div.my-class") # All divs with class
|
|
1433
|
+
```
|
|
1237
1434
|
"""
|
|
1238
|
-
return
|
|
1435
|
+
return _find_and_wrap(document, selector)
|
|
1239
1436
|
|
|
1240
1437
|
|
|
1241
1438
|
page = Page()
|
|
1439
|
+
"""A reference to the current web page. An instance of the `Page` class."""
|