@pyscript/core 0.4.56 → 0.5.0-rc1
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-BZEh__gm.js → codemirror-ZWX__zRS.js} +2 -2
- package/dist/{codemirror-BZEh__gm.js.map → codemirror-ZWX__zRS.js.map} +1 -1
- package/dist/{codemirror_commands-leH8shna.js → codemirror_commands-BvofQ4B2.js} +2 -2
- package/dist/{codemirror_commands-leH8shna.js.map → codemirror_commands-BvofQ4B2.js.map} +1 -1
- package/dist/{codemirror_lang-python-DuOzopOD.js → codemirror_lang-python-CAjFAwEr.js} +2 -2
- package/dist/{codemirror_lang-python-DuOzopOD.js.map → codemirror_lang-python-CAjFAwEr.js.map} +1 -1
- package/dist/{codemirror_language-Dakzaxks.js → codemirror_language-BzugrQDC.js} +2 -2
- package/dist/{codemirror_language-Dakzaxks.js.map → codemirror_language-BzugrQDC.js.map} +1 -1
- package/dist/{codemirror_view-Bm5_2vT5.js → codemirror_view-DrrFKRyn.js} +2 -2
- package/dist/codemirror_view-DrrFKRyn.js.map +1 -0
- package/dist/core-jHCq6GXZ.js +2 -0
- package/dist/core-jHCq6GXZ.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/{deprecations-manager-BYGF73-y.js → deprecations-manager-JLWNpJ-L.js} +2 -2
- package/dist/{deprecations-manager-BYGF73-y.js.map → deprecations-manager-JLWNpJ-L.js.map} +1 -1
- package/dist/{error-DgYfWJQg.js → error-BZdmXuMQ.js} +2 -2
- package/dist/{error-DgYfWJQg.js.map → error-BZdmXuMQ.js.map} +1 -1
- package/dist/{index-DA5aljNV.js → index-YJCpGJSa.js} +2 -2
- package/dist/{index-DA5aljNV.js.map → index-YJCpGJSa.js.map} +1 -1
- package/dist/{mpy-DiqSc6w9.js → mpy-CA-8GLzg.js} +2 -2
- package/dist/{mpy-DiqSc6w9.js.map → mpy-CA-8GLzg.js.map} +1 -1
- package/dist/{py-editor-DHmqLMO5.js → py-editor-BFwOcA7b.js} +2 -2
- package/dist/{py-editor-DHmqLMO5.js.map → py-editor-BFwOcA7b.js.map} +1 -1
- package/dist/{py-terminal-E6BBcU6N.js → py-terminal-GLATgrHP.js} +2 -2
- package/dist/{py-terminal-E6BBcU6N.js.map → py-terminal-GLATgrHP.js.map} +1 -1
- package/dist/{py-DEubHlb_.js → py-tfDQPNNm.js} +2 -2
- package/dist/{py-DEubHlb_.js.map → py-tfDQPNNm.js.map} +1 -1
- package/package.json +6 -6
- package/src/stdlib/pyscript/magic_js.py +4 -12
- package/src/stdlib/pyscript/web/__init__.py +7 -4
- package/src/stdlib/pyscript/web/elements.py +134 -114
- package/src/stdlib/pyscript.js +3 -3
- package/dist/codemirror_view-Bm5_2vT5.js.map +0 -1
- package/dist/core-DKrwnOQh.js +0 -3
- package/dist/core-DKrwnOQh.js.map +0 -1
- package/dist.zip +0 -0
@@ -1,3 +1,4 @@
|
|
1
|
+
# noinspection PyPep8Naming
|
1
2
|
import inspect
|
2
3
|
import sys
|
3
4
|
|
@@ -34,7 +35,7 @@ def getmembers_static(cls):
|
|
34
35
|
|
35
36
|
|
36
37
|
class DOMProperty:
|
37
|
-
"""A descriptor representing a DOM property on an Element
|
38
|
+
"""A descriptor representing a DOM property on an `Element` instance.
|
38
39
|
|
39
40
|
This maps a property on an `Element` instance, to the property with the specified
|
40
41
|
name on the element's underlying DOM element.
|
@@ -53,41 +54,11 @@ class DOMProperty:
|
|
53
54
|
setattr(obj._dom_element, self.name, value)
|
54
55
|
|
55
56
|
|
56
|
-
def element_from_dom(dom_element):
|
57
|
-
"""Create an instance of the appropriate subclass of `Element` for a DOM element.
|
58
|
-
|
59
|
-
If the DOM element was created via an `Element` (i.e. by us) it will have a data
|
60
|
-
attribute named `data-pyscript-type` that contains the name of the subclass
|
61
|
-
that created it. If the `data-pyscript-type` attribute *is* present we look up the
|
62
|
-
subclass by name and create an instance of that. Otherwise, we make a 'best-guess'
|
63
|
-
and look up the `Element` subclass by the DOM element's tag name (this is NOT
|
64
|
-
fool-proof as many subclasses might use a `<div>`, but close enough for jazz).
|
65
|
-
"""
|
66
|
-
|
67
|
-
# We use "getAttribute" here instead of `js_element.dataset.pyscriptType` as the
|
68
|
-
# latter throws an `AttributeError` if the value isn't set. This way we just get
|
69
|
-
# `None` which seems cleaner.
|
70
|
-
cls_name = dom_element.getAttribute("data-pyscript-type")
|
71
|
-
if cls_name:
|
72
|
-
cls = ELEMENT_CLASSES_BY_NAME.get(cls_name.lower())
|
73
|
-
|
74
|
-
else:
|
75
|
-
cls = ELEMENT_CLASSES_BY_TAG.get(dom_element.tagName.lower())
|
76
|
-
|
77
|
-
# For any unknown elements (custom tags etc.) create an instance of the 'Element'
|
78
|
-
# class.
|
79
|
-
if not cls:
|
80
|
-
cls = Element
|
81
|
-
|
82
|
-
return cls(dom_element=dom_element)
|
83
|
-
|
84
|
-
|
85
57
|
class Element:
|
86
58
|
tag = "div"
|
87
59
|
|
88
|
-
# GLOBAL ATTRIBUTES.
|
89
60
|
# These are attribute that all elements have (this list is a subset of the official
|
90
|
-
# one
|
61
|
+
# one - we are just trying to capture the most used ones).
|
91
62
|
accesskey = DOMProperty("accesskey")
|
92
63
|
autofocus = DOMProperty("autofocus")
|
93
64
|
autocapitalize = DOMProperty("autocapitalize")
|
@@ -105,11 +76,43 @@ class Element:
|
|
105
76
|
slot = DOMProperty("slot")
|
106
77
|
spellcheck = DOMProperty("spellcheck")
|
107
78
|
tabindex = DOMProperty("tabindex")
|
108
|
-
|
79
|
+
tagName = DOMProperty("tagName")
|
80
|
+
textContent = DOMProperty("textContent")
|
109
81
|
title = DOMProperty("title")
|
110
82
|
translate = DOMProperty("translate")
|
111
83
|
virtualkeyboardpolicy = DOMProperty("virtualkeyboardpolicy")
|
112
84
|
|
85
|
+
@classmethod
|
86
|
+
def from_dom_element(cls, dom_element):
|
87
|
+
"""Create an instance of the appropriate subclass of `Element` for a DOM
|
88
|
+
element.
|
89
|
+
|
90
|
+
If the DOM element was created via an `Element` (i.e. by us) it will have a data
|
91
|
+
attribute named `data-pyscript-type` that contains the name of the subclass
|
92
|
+
that created it. Hence, if the `data-pyscript-type` attribute *is* present we
|
93
|
+
look up the subclass by name and create an instance of that. Otherwise, we make
|
94
|
+
a 'best-guess' and look up the `Element` subclass by the DOM element's tag name
|
95
|
+
(this is NOT fool-proof as many subclasses might use a `<div>`, but close enough
|
96
|
+
for jazz).
|
97
|
+
"""
|
98
|
+
|
99
|
+
# We use "getAttribute" here instead of `js_element.dataset.pyscriptType` as the
|
100
|
+
# latter throws an `AttributeError` if the value isn't set. This way we just get
|
101
|
+
# `None` which seems cleaner.
|
102
|
+
cls_name = dom_element.getAttribute("data-pyscript-type")
|
103
|
+
if cls_name:
|
104
|
+
element_cls = ELEMENT_CLASSES_BY_NAME.get(cls_name.lower())
|
105
|
+
|
106
|
+
else:
|
107
|
+
element_cls = ELEMENT_CLASSES_BY_TAG.get(dom_element.tagName.lower())
|
108
|
+
|
109
|
+
# For any unknown elements (custom tags etc.) create an instance of this
|
110
|
+
# class ('Element').
|
111
|
+
if not element_cls:
|
112
|
+
element_cls = cls
|
113
|
+
|
114
|
+
return element_cls(dom_element=dom_element)
|
115
|
+
|
113
116
|
def __init__(self, dom_element=None, classes=None, style=None, **kwargs):
|
114
117
|
"""Create a new, or wrap an existing DOM element.
|
115
118
|
|
@@ -122,7 +125,7 @@ class Element:
|
|
122
125
|
#
|
123
126
|
# Using the `dataset` attribute is how you programmatically add `data-xxx`
|
124
127
|
# attributes to a DOM element. In this case it will set an attribute that
|
125
|
-
# appears in
|
128
|
+
# appears in the DOM as `data-pyscript-type`.
|
126
129
|
self._dom_element.dataset.pyscriptType = type(self).__name__
|
127
130
|
|
128
131
|
self._parent = None
|
@@ -138,24 +141,24 @@ class Element:
|
|
138
141
|
if classes:
|
139
142
|
self.classes.add(classes)
|
140
143
|
|
141
|
-
# Set any specified styles.
|
142
144
|
if isinstance(style, dict):
|
143
145
|
self.style.set(**style)
|
144
146
|
|
145
147
|
elif style is not None:
|
146
148
|
raise ValueError(
|
147
|
-
f"Style should be a dictionary, received {style}
|
149
|
+
f"Style should be a dictionary, received {style} "
|
150
|
+
f"(type {type(style)}) instead."
|
148
151
|
)
|
149
152
|
|
150
153
|
self._set_dom_properties(**kwargs)
|
151
154
|
|
152
155
|
def _set_dom_properties(self, **kwargs):
|
153
|
-
"""Set
|
154
|
-
of the class instance.
|
156
|
+
"""Set the specified DOM properties.
|
155
157
|
|
156
158
|
Args:
|
157
159
|
**kwargs: The properties to set
|
158
160
|
"""
|
161
|
+
# Harvest all DOM properties from the instance's class.
|
159
162
|
dom_properties = {
|
160
163
|
attribute_name: attribute_value
|
161
164
|
for attribute_name, attribute_value in getmembers_static(self.__class__)
|
@@ -179,7 +182,7 @@ class Element:
|
|
179
182
|
@property
|
180
183
|
def children(self):
|
181
184
|
return ElementCollection(
|
182
|
-
[
|
185
|
+
[Element.from_dom_element(el) for el in self._dom_element.children]
|
183
186
|
)
|
184
187
|
|
185
188
|
@property
|
@@ -192,7 +195,7 @@ class Element:
|
|
192
195
|
return self._parent
|
193
196
|
|
194
197
|
if self._dom_element.parentElement:
|
195
|
-
self._parent =
|
198
|
+
self._parent = Element.from_dom_element(self._dom_element.parentElement)
|
196
199
|
|
197
200
|
return self._parent
|
198
201
|
|
@@ -228,12 +231,13 @@ class Element:
|
|
228
231
|
except AttributeError:
|
229
232
|
# Nope! This is not an element or a NodeList.
|
230
233
|
raise TypeError(
|
231
|
-
f'Element "{child}" is a proxy object,
|
234
|
+
f'Element "{child}" is a proxy object, "'
|
235
|
+
f"but not a valid element or a NodeList."
|
232
236
|
)
|
233
237
|
|
234
238
|
def clone(self, clone_id=None):
|
235
239
|
"""Make a clone of the element (clones the underlying DOM object too)."""
|
236
|
-
clone =
|
240
|
+
clone = Element.from_dom_element(self._dom_element.cloneNode(True))
|
237
241
|
clone.id = clone_id
|
238
242
|
return clone
|
239
243
|
|
@@ -249,8 +253,8 @@ class Element:
|
|
249
253
|
"""
|
250
254
|
return ElementCollection(
|
251
255
|
[
|
252
|
-
|
253
|
-
for
|
256
|
+
Element.from_dom_element(dom_element)
|
257
|
+
for dom_element in self._dom_element.querySelectorAll(selector)
|
254
258
|
]
|
255
259
|
)
|
256
260
|
|
@@ -322,13 +326,13 @@ class Classes:
|
|
322
326
|
self.remove(old_class)
|
323
327
|
self.add(new_class)
|
324
328
|
|
325
|
-
def toggle(self,
|
326
|
-
|
327
|
-
self
|
328
|
-
|
329
|
+
def toggle(self, *class_names):
|
330
|
+
for class_name in class_names:
|
331
|
+
if class_name in self:
|
332
|
+
self.remove(class_name)
|
329
333
|
|
330
|
-
|
331
|
-
|
334
|
+
else:
|
335
|
+
self.add(class_name)
|
332
336
|
|
333
337
|
|
334
338
|
class HasOptions:
|
@@ -365,7 +369,7 @@ class Options:
|
|
365
369
|
**kws,
|
366
370
|
) -> None:
|
367
371
|
"""Add a new option to the select element"""
|
368
|
-
|
372
|
+
|
369
373
|
option = document.createElement("option")
|
370
374
|
if value is not None:
|
371
375
|
kws["value"] = value
|
@@ -395,7 +399,9 @@ class Options:
|
|
395
399
|
@property
|
396
400
|
def options(self):
|
397
401
|
"""Return the list of options"""
|
398
|
-
return [
|
402
|
+
return [
|
403
|
+
Element.from_dom_element(opt) for opt in self._element._dom_element.options
|
404
|
+
]
|
399
405
|
|
400
406
|
@property
|
401
407
|
def selected(self):
|
@@ -449,6 +455,8 @@ class Style:
|
|
449
455
|
|
450
456
|
|
451
457
|
class ContainerElement(Element):
|
458
|
+
"""Base class for elements that can contain other elements."""
|
459
|
+
|
452
460
|
def __init__(
|
453
461
|
self, *args, children=None, dom_element=None, style=None, classes=None, **kwargs
|
454
462
|
):
|
@@ -606,12 +614,15 @@ class canvas(ContainerElement):
|
|
606
614
|
Output:
|
607
615
|
None
|
608
616
|
"""
|
609
|
-
|
610
|
-
|
611
|
-
link
|
612
|
-
|
617
|
+
download_link = a(download=filename, href=self._dom_element.toDataURL())
|
618
|
+
|
619
|
+
# Adding the link to the DOM is recommended for browser compatibility to make
|
620
|
+
# sure that the click works.
|
621
|
+
self.append(download_link)
|
613
622
|
|
614
|
-
|
623
|
+
download_link._dom_element.click()
|
624
|
+
|
625
|
+
def draw(self, what, width=None, height=None):
|
615
626
|
"""Draw `what` on the current element
|
616
627
|
|
617
628
|
Inputs:
|
@@ -626,7 +637,12 @@ class canvas(ContainerElement):
|
|
626
637
|
what = what._dom_element
|
627
638
|
|
628
639
|
# https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
|
629
|
-
self._dom_element.getContext("2d")
|
640
|
+
ctx = self._dom_element.getContext("2d")
|
641
|
+
if width or height:
|
642
|
+
ctx.drawImage(what, 0, 0, width, height)
|
643
|
+
|
644
|
+
else:
|
645
|
+
ctx.drawImage(what, 0, 0)
|
630
646
|
|
631
647
|
|
632
648
|
class caption(ContainerElement):
|
@@ -1396,6 +1412,8 @@ class video(ContainerElement):
|
|
1396
1412
|
preload = DOMProperty("preload")
|
1397
1413
|
src = DOMProperty("src")
|
1398
1414
|
width = DOMProperty("width")
|
1415
|
+
videoHeight = DOMProperty("videoHeight")
|
1416
|
+
videoWidth = DOMProperty("videoWidth")
|
1399
1417
|
|
1400
1418
|
def snap(
|
1401
1419
|
self,
|
@@ -1404,48 +1422,44 @@ class video(ContainerElement):
|
|
1404
1422
|
height: int | None = None,
|
1405
1423
|
):
|
1406
1424
|
"""
|
1407
|
-
|
1425
|
+
Capture a snapshot (i.e. a single frame) of a video to a canvas.
|
1408
1426
|
|
1409
1427
|
Inputs:
|
1410
1428
|
|
1411
|
-
* to:
|
1412
|
-
* width: width of the
|
1413
|
-
* height: height of the
|
1429
|
+
* to: the canvas to save the video frame to (if None, one is created).
|
1430
|
+
* width: width of the snapshot (defaults to the video width).
|
1431
|
+
* height: height of the snapshot (defaults to the video height).
|
1414
1432
|
|
1415
1433
|
Output:
|
1416
1434
|
(Element) canvas element where the video frame snapshot was drawn into
|
1417
1435
|
"""
|
1436
|
+
width = width if width is not None else self.videoWidth
|
1437
|
+
height = height if height is not None else self.videoHeight
|
1438
|
+
|
1418
1439
|
if to is None:
|
1419
|
-
|
1420
|
-
if width is None:
|
1421
|
-
width = self._dom_element.width
|
1422
|
-
if height is None:
|
1423
|
-
height = self._dom_element.height
|
1424
|
-
to_canvas._dom_element.width = width
|
1425
|
-
to_canvas._dom_element.height = height
|
1440
|
+
to = canvas(width=width, height=height)
|
1426
1441
|
|
1427
1442
|
elif isinstance(to, Element):
|
1428
|
-
if to.
|
1429
|
-
raise TypeError("Element to snap to must a canvas.")
|
1430
|
-
to_canvas = to
|
1443
|
+
if to.tag != "canvas":
|
1444
|
+
raise TypeError("Element to snap to must be a canvas.")
|
1431
1445
|
|
1432
1446
|
elif getattr(to, "tagName", "") == "CANVAS":
|
1433
|
-
|
1447
|
+
to = canvas(dom_element=to)
|
1434
1448
|
|
1435
1449
|
# If 'to' is a string, then assume it is a query selector.
|
1436
1450
|
elif isinstance(to, str):
|
1437
|
-
nodelist = document.querySelectorAll(to)
|
1451
|
+
nodelist = document.querySelectorAll(to) # NOQA
|
1438
1452
|
if nodelist.length == 0:
|
1439
1453
|
raise TypeError("No element with selector {to} to snap to.")
|
1440
1454
|
|
1441
1455
|
if nodelist[0].tagName != "CANVAS":
|
1442
|
-
raise TypeError("Element to snap to must a
|
1456
|
+
raise TypeError("Element to snap to must be a canvas.")
|
1443
1457
|
|
1444
|
-
|
1458
|
+
to = canvas(dom_element=nodelist[0])
|
1445
1459
|
|
1446
|
-
|
1460
|
+
to.draw(self, width, height)
|
1447
1461
|
|
1448
|
-
return
|
1462
|
+
return to
|
1449
1463
|
|
1450
1464
|
|
1451
1465
|
class wbr(Element):
|
@@ -1464,7 +1478,7 @@ class grid(ContainerElement):
|
|
1464
1478
|
self.style["grid-template-columns"] = layout
|
1465
1479
|
|
1466
1480
|
# TODO: This should be a property
|
1467
|
-
if
|
1481
|
+
if gap is not None:
|
1468
1482
|
self.style["gap"] = gap
|
1469
1483
|
|
1470
1484
|
|
@@ -1513,9 +1527,9 @@ class ClassesCollection:
|
|
1513
1527
|
for element in self._collection:
|
1514
1528
|
element.classes.replace(old_class, new_class)
|
1515
1529
|
|
1516
|
-
def toggle(self,
|
1530
|
+
def toggle(self, *class_names):
|
1517
1531
|
for element in self._collection:
|
1518
|
-
element.classes.toggle(
|
1532
|
+
element.classes.toggle(*class_names)
|
1519
1533
|
|
1520
1534
|
def _all_class_names(self):
|
1521
1535
|
all_class_names = set()
|
@@ -1554,37 +1568,8 @@ class ElementCollection:
|
|
1554
1568
|
self._classes = ClassesCollection(self)
|
1555
1569
|
self._style = StyleCollection(self)
|
1556
1570
|
|
1557
|
-
@property
|
1558
|
-
def children(self):
|
1559
|
-
return self._elements
|
1560
|
-
|
1561
|
-
@property
|
1562
|
-
def classes(self):
|
1563
|
-
return self._classes
|
1564
|
-
|
1565
|
-
@property
|
1566
|
-
def style(self):
|
1567
|
-
return self._style
|
1568
|
-
|
1569
|
-
@property
|
1570
|
-
def innerHTML(self):
|
1571
|
-
return self._get_attribute("innerHTML")
|
1572
|
-
|
1573
|
-
@innerHTML.setter
|
1574
|
-
def innerHTML(self, value):
|
1575
|
-
self._set_attribute("innerHTML", value)
|
1576
|
-
|
1577
|
-
@property
|
1578
|
-
def value(self):
|
1579
|
-
return self._get_attribute("value")
|
1580
|
-
|
1581
|
-
@value.setter
|
1582
|
-
def value(self, value):
|
1583
|
-
self._set_attribute("value", value)
|
1584
|
-
|
1585
1571
|
def __eq__(self, obj):
|
1586
|
-
"""Check
|
1587
|
-
the underlying DOM element"""
|
1572
|
+
"""Check for equality by comparing the underlying DOM elements."""
|
1588
1573
|
return isinstance(obj, ElementCollection) and obj._elements == self._elements
|
1589
1574
|
|
1590
1575
|
def __getitem__(self, key):
|
@@ -1598,8 +1583,7 @@ class ElementCollection:
|
|
1598
1583
|
return ElementCollection(self._elements[key])
|
1599
1584
|
|
1600
1585
|
# If it's anything else (basically a string) we use it as a query selector.
|
1601
|
-
|
1602
|
-
return ElementCollection([element_from_dom(el) for el in elements])
|
1586
|
+
return self.find(key)
|
1603
1587
|
|
1604
1588
|
def __iter__(self):
|
1605
1589
|
yield from self._elements
|
@@ -1608,7 +1592,43 @@ class ElementCollection:
|
|
1608
1592
|
return len(self._elements)
|
1609
1593
|
|
1610
1594
|
def __repr__(self):
|
1611
|
-
return
|
1595
|
+
return (
|
1596
|
+
f"{self.__class__.__name__} (length: {len(self._elements)}) "
|
1597
|
+
f"{self._elements}"
|
1598
|
+
)
|
1599
|
+
|
1600
|
+
def __getattr__(self, item):
|
1601
|
+
return self._get_attribute(item)
|
1602
|
+
|
1603
|
+
def __setattr__(self, key, value):
|
1604
|
+
# This class overrides `__setattr__` to delegate "public" attributes to the
|
1605
|
+
# elements in the collection, but we don't use the usual Python pattern where we
|
1606
|
+
# set attributes on the collection itself via `self.__dict__` as it is not yet
|
1607
|
+
# supported in our build of MicroPython. Instead, we handle it here.
|
1608
|
+
if key.startswith("_"):
|
1609
|
+
super().__setattr__(key, value)
|
1610
|
+
|
1611
|
+
else:
|
1612
|
+
self._set_attribute(key, value)
|
1613
|
+
|
1614
|
+
@property
|
1615
|
+
def children(self):
|
1616
|
+
return self._elements
|
1617
|
+
|
1618
|
+
@property
|
1619
|
+
def classes(self):
|
1620
|
+
return self._classes
|
1621
|
+
|
1622
|
+
@property
|
1623
|
+
def style(self):
|
1624
|
+
return self._style
|
1625
|
+
|
1626
|
+
def find(self, selector):
|
1627
|
+
elements = []
|
1628
|
+
for element in self._elements:
|
1629
|
+
elements.extend(element.find(selector))
|
1630
|
+
|
1631
|
+
return ElementCollection(elements)
|
1612
1632
|
|
1613
1633
|
def _get_attribute(self, attr, index=None):
|
1614
1634
|
if index is None:
|